From cb4630ded95d0a8d981d2f2109f6a2a2f498a8c1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 4 Jul 2007 08:24:30 +0000 Subject: [PATCH 0001/1009] Initial import. --- deluge/glade/aboutdialog.glade | 38 + deluge/glade/delugegtk.glade | 1140 +++++++ deluge/glade/dgtkpopups.glade | 406 +++ deluge/glade/edit_trackers.glade | 120 + deluge/glade/file_tab_menu.glade | 100 + deluge/glade/plugin_dialog.glade | 114 + deluge/glade/preferences_dialog.glade | 1461 +++++++++ deluge/glade/torrent_menu.glade | 165 + deluge/glade/tray_menu.glade | 158 + deluge/pixmaps/deluge-about.png | Bin 0 -> 9944 bytes deluge/pixmaps/deluge128.png | Bin 0 -> 13952 bytes deluge/pixmaps/deluge192.png | Bin 0 -> 24460 bytes deluge/pixmaps/deluge22.png | Bin 0 -> 1103 bytes deluge/pixmaps/deluge256.png | Bin 0 -> 36758 bytes deluge/pixmaps/deluge32.png | Bin 0 -> 1909 bytes deluge/pixmaps/downloading16.png | Bin 0 -> 662 bytes deluge/pixmaps/inactive16.png | Bin 0 -> 588 bytes deluge/pixmaps/seeding16.png | Bin 0 -> 612 bytes deluge/share/applications/deluge.desktop | 13 + deluge/share/pixmaps/deluge.xpm | 415 +++ deluge/src/__init__.py | 0 deluge/src/core.py | 43 + deluge/src/daemon.py | 61 + deluge/src/main.py | 99 + deluge/src/ui.py | 48 + ez_setup.py | 231 ++ libtorrent/AUTHORS | 25 + libtorrent/COPYING | 28 + libtorrent/ChangeLog | 216 ++ libtorrent/NEWS | 3 + libtorrent/README | 25 + libtorrent/bindings/README.txt | 3 + libtorrent/bindings/python/Jamfile | 29 + libtorrent/bindings/python/client.py | 355 ++ libtorrent/bindings/python/simple_client.py | 24 + libtorrent/bindings/python/src/alert.cpp | 162 + libtorrent/bindings/python/src/big_number.cpp | 20 + libtorrent/bindings/python/src/converters.cpp | 5 + libtorrent/bindings/python/src/datetime.cpp | 81 + libtorrent/bindings/python/src/docstrings.cpp | 266 ++ libtorrent/bindings/python/src/entry.cpp | 132 + libtorrent/bindings/python/src/extensions.cpp | 148 + libtorrent/bindings/python/src/filesystem.cpp | 51 + .../bindings/python/src/fingerprint.cpp | 27 + libtorrent/bindings/python/src/gil.hpp | 144 + libtorrent/bindings/python/src/module.cpp | 48 + libtorrent/bindings/python/src/optional.hpp | 31 + libtorrent/bindings/python/src/peer_info.cpp | 63 + .../bindings/python/src/peer_plugin.cpp | 344 ++ libtorrent/bindings/python/src/session.cpp | 214 ++ .../bindings/python/src/session_settings.cpp | 77 + libtorrent/bindings/python/src/torrent.cpp | 15 + .../bindings/python/src/torrent_handle.cpp | 163 + .../bindings/python/src/torrent_info.cpp | 103 + .../bindings/python/src/torrent_status.cpp | 107 + libtorrent/bindings/python/src/utility.cpp | 37 + libtorrent/bindings/python/src/version.cpp | 16 + libtorrent/include/Makefile.am | 232 ++ libtorrent/include/asio.hpp | 74 + libtorrent/include/asio/CVS/Entries | 45 + libtorrent/include/asio/CVS/Repository | 1 + libtorrent/include/asio/CVS/Root | 1 + .../include/asio/basic_datagram_socket.hpp | 803 +++++ .../include/asio/basic_deadline_timer.hpp | 381 +++ libtorrent/include/asio/basic_io_object.hpp | 75 + libtorrent/include/asio/basic_socket.hpp | 972 ++++++ .../include/asio/basic_socket_acceptor.hpp | 824 +++++ .../include/asio/basic_socket_iostream.hpp | 146 + .../include/asio/basic_socket_streambuf.hpp | 284 ++ .../include/asio/basic_stream_socket.hpp | 718 +++++ libtorrent/include/asio/basic_streambuf.hpp | 200 ++ libtorrent/include/asio/buffer.hpp | 790 +++++ .../include/asio/buffered_read_stream.hpp | 407 +++ .../include/asio/buffered_read_stream_fwd.hpp | 29 + libtorrent/include/asio/buffered_stream.hpp | 243 ++ .../include/asio/buffered_stream_fwd.hpp | 29 + .../include/asio/buffered_write_stream.hpp | 361 +++ .../asio/buffered_write_stream_fwd.hpp | 29 + .../include/asio/completion_condition.hpp | 101 + .../include/asio/datagram_socket_service.hpp | 320 ++ libtorrent/include/asio/deadline_timer.hpp | 37 + .../include/asio/deadline_timer_service.hpp | 164 + libtorrent/include/asio/detail/CVS/Entries | 74 + libtorrent/include/asio/detail/CVS/Repository | 1 + libtorrent/include/asio/detail/CVS/Root | 1 + .../include/asio/detail/bind_handler.hpp | 349 ++ .../asio/detail/buffer_resize_guard.hpp | 70 + .../asio/detail/buffered_stream_storage.hpp | 127 + libtorrent/include/asio/detail/call_stack.hpp | 90 + .../asio/detail/const_buffers_iterator.hpp | 151 + .../include/asio/detail/consuming_buffers.hpp | 205 ++ .../asio/detail/deadline_timer_service.hpp | 199 ++ .../include/asio/detail/epoll_reactor.hpp | 613 ++++ .../include/asio/detail/epoll_reactor_fwd.hpp | 47 + libtorrent/include/asio/detail/event.hpp | 50 + .../include/asio/detail/fd_set_adapter.hpp | 41 + .../asio/detail/handler_alloc_helpers.hpp | 256 ++ .../asio/detail/handler_invoke_helpers.hpp | 47 + libtorrent/include/asio/detail/hash_map.hpp | 209 ++ libtorrent/include/asio/detail/io_control.hpp | 137 + .../include/asio/detail/kqueue_reactor.hpp | 620 ++++ .../asio/detail/kqueue_reactor_fwd.hpp | 41 + .../asio/detail/local_free_on_block_exit.hpp | 59 + libtorrent/include/asio/detail/mutex.hpp | 50 + .../include/asio/detail/noncopyable.hpp | 55 + libtorrent/include/asio/detail/null_event.hpp | 68 + libtorrent/include/asio/detail/null_mutex.hpp | 66 + .../asio/detail/null_signal_blocker.hpp | 63 + .../include/asio/detail/null_thread.hpp | 68 + .../include/asio/detail/null_tss_ptr.hpp | 70 + .../asio/detail/old_win_sdk_compat.hpp | 321 ++ .../asio/detail/pipe_select_interrupter.hpp | 104 + .../include/asio/detail/pop_options.hpp | 88 + .../include/asio/detail/posix_event.hpp | 111 + .../asio/detail/posix_fd_set_adapter.hpp | 72 + .../include/asio/detail/posix_mutex.hpp | 100 + .../asio/detail/posix_signal_blocker.hpp | 90 + .../include/asio/detail/posix_thread.hpp | 127 + .../include/asio/detail/posix_tss_ptr.hpp | 86 + .../include/asio/detail/push_options.hpp | 106 + .../asio/detail/reactive_socket_service.hpp | 1574 +++++++++ .../include/asio/detail/reactor_op_queue.hpp | 384 +++ .../include/asio/detail/resolver_service.hpp | 357 ++ .../include/asio/detail/scoped_lock.hpp | 79 + .../asio/detail/select_interrupter.hpp | 41 + .../include/asio/detail/select_reactor.hpp | 456 +++ .../asio/detail/select_reactor_fwd.hpp | 31 + .../include/asio/detail/service_base.hpp | 49 + libtorrent/include/asio/detail/service_id.hpp | 37 + .../include/asio/detail/service_registry.hpp | 198 ++ .../asio/detail/service_registry_fwd.hpp | 30 + .../include/asio/detail/signal_blocker.hpp | 50 + .../include/asio/detail/signal_init.hpp | 51 + .../include/asio/detail/socket_holder.hpp | 95 + libtorrent/include/asio/detail/socket_ops.hpp | 1653 ++++++++++ .../include/asio/detail/socket_option.hpp | 298 ++ .../asio/detail/socket_select_interrupter.hpp | 187 ++ .../include/asio/detail/socket_types.hpp | 179 ++ .../include/asio/detail/strand_service.hpp | 526 +++ .../include/asio/detail/task_io_service.hpp | 538 ++++ .../asio/detail/task_io_service_fwd.hpp | 31 + libtorrent/include/asio/detail/thread.hpp | 50 + .../include/asio/detail/throw_error.hpp | 44 + .../include/asio/detail/timer_queue.hpp | 348 ++ .../include/asio/detail/timer_queue_base.hpp | 56 + libtorrent/include/asio/detail/tss_ptr.hpp | 65 + libtorrent/include/asio/detail/win_event.hpp | 90 + .../asio/detail/win_fd_set_adapter.hpp | 84 + .../asio/detail/win_iocp_io_service.hpp | 424 +++ .../asio/detail/win_iocp_io_service_fwd.hpp | 46 + .../asio/detail/win_iocp_operation.hpp | 81 + .../asio/detail/win_iocp_socket_service.hpp | 2000 ++++++++++++ libtorrent/include/asio/detail/win_mutex.hpp | 146 + .../asio/detail/win_signal_blocker.hpp | 67 + libtorrent/include/asio/detail/win_thread.hpp | 123 + .../include/asio/detail/win_tss_ptr.hpp | 87 + .../include/asio/detail/winsock_init.hpp | 118 + .../include/asio/detail/wrapped_handler.hpp | 187 ++ libtorrent/include/asio/error.hpp | 367 +++ libtorrent/include/asio/error_code.hpp | 139 + .../include/asio/handler_alloc_hook.hpp | 88 + .../include/asio/handler_invoke_hook.hpp | 69 + libtorrent/include/asio/impl/CVS/Entries | 6 + libtorrent/include/asio/impl/CVS/Repository | 1 + libtorrent/include/asio/impl/CVS/Root | 1 + libtorrent/include/asio/impl/error_code.ipp | 98 + libtorrent/include/asio/impl/io_service.ipp | 213 ++ libtorrent/include/asio/impl/read.ipp | 314 ++ libtorrent/include/asio/impl/read_until.ipp | 750 +++++ libtorrent/include/asio/impl/write.ipp | 279 ++ libtorrent/include/asio/io_service.hpp | 502 +++ libtorrent/include/asio/ip/CVS/Entries | 17 + libtorrent/include/asio/ip/CVS/Repository | 1 + libtorrent/include/asio/ip/CVS/Root | 1 + libtorrent/include/asio/ip/address.hpp | 277 ++ libtorrent/include/asio/ip/address_v4.hpp | 283 ++ libtorrent/include/asio/ip/address_v6.hpp | 401 +++ libtorrent/include/asio/ip/basic_endpoint.hpp | 368 +++ libtorrent/include/asio/ip/basic_resolver.hpp | 245 ++ .../include/asio/ip/basic_resolver_entry.hpp | 95 + .../asio/ip/basic_resolver_iterator.hpp | 156 + .../include/asio/ip/basic_resolver_query.hpp | 149 + libtorrent/include/asio/ip/detail/CVS/Entries | 2 + .../include/asio/ip/detail/CVS/Repository | 1 + libtorrent/include/asio/ip/detail/CVS/Root | 1 + .../include/asio/ip/detail/socket_option.hpp | 536 +++ libtorrent/include/asio/ip/host_name.hpp | 62 + libtorrent/include/asio/ip/multicast.hpp | 181 ++ .../include/asio/ip/resolver_query_base.hpp | 107 + .../include/asio/ip/resolver_service.hpp | 140 + libtorrent/include/asio/ip/tcp.hpp | 158 + libtorrent/include/asio/ip/udp.hpp | 116 + libtorrent/include/asio/ip/unicast.hpp | 70 + libtorrent/include/asio/ip/v6_only.hpp | 68 + libtorrent/include/asio/is_read_buffered.hpp | 62 + libtorrent/include/asio/is_write_buffered.hpp | 62 + libtorrent/include/asio/placeholders.hpp | 107 + libtorrent/include/asio/read.hpp | 516 +++ libtorrent/include/asio/read_until.hpp | 452 +++ .../include/asio/socket_acceptor_service.hpp | 222 ++ libtorrent/include/asio/socket_base.hpp | 515 +++ libtorrent/include/asio/ssl.hpp | 26 + libtorrent/include/asio/ssl/CVS/Entries | 8 + libtorrent/include/asio/ssl/CVS/Repository | 1 + libtorrent/include/asio/ssl/CVS/Root | 1 + libtorrent/include/asio/ssl/basic_context.hpp | 434 +++ libtorrent/include/asio/ssl/context.hpp | 35 + libtorrent/include/asio/ssl/context_base.hpp | 164 + .../include/asio/ssl/context_service.hpp | 175 + .../include/asio/ssl/detail/CVS/Entries | 6 + .../include/asio/ssl/detail/CVS/Repository | 1 + libtorrent/include/asio/ssl/detail/CVS/Root | 1 + .../ssl/detail/openssl_context_service.hpp | 379 +++ .../include/asio/ssl/detail/openssl_init.hpp | 127 + .../asio/ssl/detail/openssl_operation.hpp | 482 +++ .../ssl/detail/openssl_stream_service.hpp | 504 +++ .../include/asio/ssl/detail/openssl_types.hpp | 29 + libtorrent/include/asio/ssl/stream.hpp | 490 +++ libtorrent/include/asio/ssl/stream_base.hpp | 60 + .../include/asio/ssl/stream_service.hpp | 186 ++ libtorrent/include/asio/strand.hpp | 172 + .../include/asio/stream_socket_service.hpp | 283 ++ libtorrent/include/asio/streambuf.hpp | 31 + libtorrent/include/asio/system_error.hpp | 117 + libtorrent/include/asio/thread.hpp | 91 + libtorrent/include/asio/time_traits.hpp | 78 + libtorrent/include/asio/version.hpp | 23 + libtorrent/include/asio/write.hpp | 515 +++ libtorrent/include/libtorrent/alert.hpp | 164 + libtorrent/include/libtorrent/alert_types.hpp | 318 ++ .../include/libtorrent/allocate_resources.hpp | 78 + .../aux_/allocate_resources_impl.hpp | 328 ++ .../include/libtorrent/aux_/session_impl.hpp | 562 ++++ .../include/libtorrent/bandwidth_manager.hpp | 449 +++ libtorrent/include/libtorrent/bencode.hpp | 299 ++ .../include/libtorrent/bt_peer_connection.hpp | 379 +++ libtorrent/include/libtorrent/buffer.hpp | 453 +++ libtorrent/include/libtorrent/config.hpp | 66 + .../include/libtorrent/connection_queue.hpp | 96 + libtorrent/include/libtorrent/debug.hpp | 82 + .../include/libtorrent/disk_io_thread.hpp | 119 + libtorrent/include/libtorrent/entry.hpp | 278 ++ .../include/libtorrent/escape_string.hpp | 46 + libtorrent/include/libtorrent/extensions.hpp | 176 + .../include/libtorrent/extensions/logger.hpp | 54 + .../extensions/metadata_transfer.hpp | 55 + .../include/libtorrent/extensions/ut_pex.hpp | 54 + libtorrent/include/libtorrent/file.hpp | 132 + libtorrent/include/libtorrent/file_pool.hpp | 103 + libtorrent/include/libtorrent/fingerprint.hpp | 93 + libtorrent/include/libtorrent/hasher.hpp | 122 + .../include/libtorrent/http_connection.hpp | 154 + libtorrent/include/libtorrent/http_stream.hpp | 102 + .../libtorrent/http_tracker_connection.hpp | 178 + .../include/libtorrent/identify_client.hpp | 58 + .../libtorrent/instantiate_connection.hpp | 49 + .../include/libtorrent/intrusive_ptr_base.hpp | 71 + .../include/libtorrent/invariant_check.hpp | 78 + libtorrent/include/libtorrent/io.hpp | 153 + libtorrent/include/libtorrent/ip_filter.hpp | 314 ++ .../libtorrent/kademlia/closest_nodes.hpp | 117 + .../libtorrent/kademlia/dht_tracker.hpp | 159 + .../include/libtorrent/kademlia/find_data.hpp | 128 + .../include/libtorrent/kademlia/logging.hpp | 146 + .../include/libtorrent/kademlia/msg.hpp | 102 + .../include/libtorrent/kademlia/node.hpp | 259 ++ .../libtorrent/kademlia/node_entry.hpp | 63 + .../include/libtorrent/kademlia/node_id.hpp | 60 + .../include/libtorrent/kademlia/observer.hpp | 92 + .../libtorrent/kademlia/packet_iterator.hpp | 95 + .../include/libtorrent/kademlia/refresh.hpp | 214 ++ .../libtorrent/kademlia/routing_table.hpp | 248 ++ .../libtorrent/kademlia/rpc_manager.hpp | 140 + .../kademlia/traversal_algorithm.hpp | 162 + libtorrent/include/libtorrent/lsd.hpp | 105 + libtorrent/include/libtorrent/natpmp.hpp | 150 + libtorrent/include/libtorrent/pch.hpp | 96 + libtorrent/include/libtorrent/pe_crypto.hpp | 125 + libtorrent/include/libtorrent/peer.hpp | 63 + .../include/libtorrent/peer_connection.hpp | 707 ++++ libtorrent/include/libtorrent/peer_id.hpp | 192 ++ libtorrent/include/libtorrent/peer_info.hpp | 151 + .../include/libtorrent/peer_request.hpp | 49 + .../libtorrent/piece_block_progress.hpp | 57 + .../include/libtorrent/piece_picker.hpp | 425 +++ libtorrent/include/libtorrent/policy.hpp | 263 ++ libtorrent/include/libtorrent/proxy_base.hpp | 183 ++ .../include/libtorrent/random_sample.hpp | 74 + .../include/libtorrent/resource_request.hpp | 99 + libtorrent/include/libtorrent/session.hpp | 287 ++ .../include/libtorrent/session_settings.hpp | 322 ++ .../include/libtorrent/session_status.hpp | 69 + libtorrent/include/libtorrent/size_type.hpp | 52 + libtorrent/include/libtorrent/socket.hpp | 162 + libtorrent/include/libtorrent/socket_type.hpp | 51 + .../include/libtorrent/socks4_stream.hpp | 91 + .../include/libtorrent/socks5_stream.hpp | 103 + libtorrent/include/libtorrent/stat.hpp | 193 ++ libtorrent/include/libtorrent/storage.hpp | 359 +++ libtorrent/include/libtorrent/time.hpp | 388 +++ libtorrent/include/libtorrent/torrent.hpp | 801 +++++ .../include/libtorrent/torrent_handle.hpp | 387 +++ .../include/libtorrent/torrent_info.hpp | 273 ++ .../include/libtorrent/tracker_manager.hpp | 253 ++ .../libtorrent/udp_tracker_connection.hpp | 122 + libtorrent/include/libtorrent/upnp.hpp | 237 ++ libtorrent/include/libtorrent/utf8.hpp | 161 + .../include/libtorrent/variant_stream.hpp | 716 +++++ libtorrent/include/libtorrent/version.hpp | 41 + .../libtorrent/web_peer_connection.hpp | 179 ++ libtorrent/include/libtorrent/xml_parse.hpp | 99 + libtorrent/src/Makefile.am | 100 + libtorrent/src/alert.cpp | 126 + libtorrent/src/allocate_resources.cpp | 225 ++ libtorrent/src/bt_peer_connection.cpp | 2288 +++++++++++++ libtorrent/src/connection_queue.cpp | 169 + libtorrent/src/deluge_core.cpp | 1439 +++++++++ libtorrent/src/disk_io_thread.cpp | 245 ++ libtorrent/src/entry.cpp | 350 ++ libtorrent/src/escape_string.cpp | 150 + libtorrent/src/file.cpp | 347 ++ libtorrent/src/file_pool.cpp | 134 + libtorrent/src/file_win.cpp | 366 +++ libtorrent/src/http_connection.cpp | 384 +++ libtorrent/src/http_stream.cpp | 162 + libtorrent/src/http_tracker_connection.cpp | 931 ++++++ libtorrent/src/identify_client.cpp | 351 ++ libtorrent/src/instantiate_connection.cpp | 84 + libtorrent/src/ip_filter.cpp | 91 + libtorrent/src/kademlia/closest_nodes.cpp | 132 + libtorrent/src/kademlia/dht_tracker.cpp | 974 ++++++ libtorrent/src/kademlia/find_data.cpp | 142 + libtorrent/src/kademlia/node.cpp | 497 +++ libtorrent/src/kademlia/node_id.cpp | 99 + libtorrent/src/kademlia/refresh.cpp | 174 + libtorrent/src/kademlia/routing_table.cpp | 448 +++ libtorrent/src/kademlia/rpc_manager.cpp | 430 +++ .../src/kademlia/traversal_algorithm.cpp | 184 ++ libtorrent/src/logger.cpp | 233 ++ libtorrent/src/lsd.cpp | 250 ++ libtorrent/src/metadata_transfer.cpp | 566 ++++ libtorrent/src/natpmp.cpp | 393 +++ libtorrent/src/pe_crypto.cpp | 122 + libtorrent/src/peer_connection.cpp | 2701 ++++++++++++++++ libtorrent/src/piece_picker.cpp | 1543 +++++++++ libtorrent/src/policy.cpp | 1488 +++++++++ libtorrent/src/session.cpp | 420 +++ libtorrent/src/session_impl.cpp | 2180 +++++++++++++ libtorrent/src/sha1.cpp | 317 ++ libtorrent/src/socks4_stream.cpp | 147 + libtorrent/src/socks5_stream.cpp | 315 ++ libtorrent/src/stat.cpp | 93 + libtorrent/src/storage.cpp | 2238 +++++++++++++ libtorrent/src/torrent.cpp | 2859 +++++++++++++++++ libtorrent/src/torrent_handle.cpp | 786 +++++ libtorrent/src/torrent_info.cpp | 878 +++++ libtorrent/src/tracker_manager.cpp | 587 ++++ libtorrent/src/udp_tracker_connection.cpp | 555 ++++ libtorrent/src/upnp.cpp | 1038 ++++++ libtorrent/src/ut_pex.cpp | 359 +++ libtorrent/src/web_peer_connection.cpp | 639 ++++ setup.py | 115 + 362 files changed, 90926 insertions(+) create mode 100644 deluge/glade/aboutdialog.glade create mode 100644 deluge/glade/delugegtk.glade create mode 100644 deluge/glade/dgtkpopups.glade create mode 100644 deluge/glade/edit_trackers.glade create mode 100644 deluge/glade/file_tab_menu.glade create mode 100644 deluge/glade/plugin_dialog.glade create mode 100644 deluge/glade/preferences_dialog.glade create mode 100644 deluge/glade/torrent_menu.glade create mode 100644 deluge/glade/tray_menu.glade create mode 100644 deluge/pixmaps/deluge-about.png create mode 100644 deluge/pixmaps/deluge128.png create mode 100644 deluge/pixmaps/deluge192.png create mode 100644 deluge/pixmaps/deluge22.png create mode 100644 deluge/pixmaps/deluge256.png create mode 100644 deluge/pixmaps/deluge32.png create mode 100644 deluge/pixmaps/downloading16.png create mode 100644 deluge/pixmaps/inactive16.png create mode 100644 deluge/pixmaps/seeding16.png create mode 100644 deluge/share/applications/deluge.desktop create mode 100644 deluge/share/pixmaps/deluge.xpm create mode 100644 deluge/src/__init__.py create mode 100644 deluge/src/core.py create mode 100644 deluge/src/daemon.py create mode 100644 deluge/src/main.py create mode 100644 deluge/src/ui.py create mode 100644 ez_setup.py create mode 100644 libtorrent/AUTHORS create mode 100644 libtorrent/COPYING create mode 100644 libtorrent/ChangeLog create mode 100644 libtorrent/NEWS create mode 100644 libtorrent/README create mode 100644 libtorrent/bindings/README.txt create mode 100755 libtorrent/bindings/python/Jamfile create mode 100755 libtorrent/bindings/python/client.py create mode 100755 libtorrent/bindings/python/simple_client.py create mode 100755 libtorrent/bindings/python/src/alert.cpp create mode 100755 libtorrent/bindings/python/src/big_number.cpp create mode 100755 libtorrent/bindings/python/src/converters.cpp create mode 100755 libtorrent/bindings/python/src/datetime.cpp create mode 100755 libtorrent/bindings/python/src/docstrings.cpp create mode 100755 libtorrent/bindings/python/src/entry.cpp create mode 100755 libtorrent/bindings/python/src/extensions.cpp create mode 100755 libtorrent/bindings/python/src/filesystem.cpp create mode 100755 libtorrent/bindings/python/src/fingerprint.cpp create mode 100755 libtorrent/bindings/python/src/gil.hpp create mode 100755 libtorrent/bindings/python/src/module.cpp create mode 100755 libtorrent/bindings/python/src/optional.hpp create mode 100755 libtorrent/bindings/python/src/peer_info.cpp create mode 100755 libtorrent/bindings/python/src/peer_plugin.cpp create mode 100755 libtorrent/bindings/python/src/session.cpp create mode 100755 libtorrent/bindings/python/src/session_settings.cpp create mode 100755 libtorrent/bindings/python/src/torrent.cpp create mode 100755 libtorrent/bindings/python/src/torrent_handle.cpp create mode 100755 libtorrent/bindings/python/src/torrent_info.cpp create mode 100755 libtorrent/bindings/python/src/torrent_status.cpp create mode 100755 libtorrent/bindings/python/src/utility.cpp create mode 100755 libtorrent/bindings/python/src/version.cpp create mode 100644 libtorrent/include/Makefile.am create mode 100644 libtorrent/include/asio.hpp create mode 100644 libtorrent/include/asio/CVS/Entries create mode 100644 libtorrent/include/asio/CVS/Repository create mode 100644 libtorrent/include/asio/CVS/Root create mode 100644 libtorrent/include/asio/basic_datagram_socket.hpp create mode 100644 libtorrent/include/asio/basic_deadline_timer.hpp create mode 100644 libtorrent/include/asio/basic_io_object.hpp create mode 100644 libtorrent/include/asio/basic_socket.hpp create mode 100644 libtorrent/include/asio/basic_socket_acceptor.hpp create mode 100644 libtorrent/include/asio/basic_socket_iostream.hpp create mode 100644 libtorrent/include/asio/basic_socket_streambuf.hpp create mode 100644 libtorrent/include/asio/basic_stream_socket.hpp create mode 100644 libtorrent/include/asio/basic_streambuf.hpp create mode 100644 libtorrent/include/asio/buffer.hpp create mode 100644 libtorrent/include/asio/buffered_read_stream.hpp create mode 100644 libtorrent/include/asio/buffered_read_stream_fwd.hpp create mode 100644 libtorrent/include/asio/buffered_stream.hpp create mode 100644 libtorrent/include/asio/buffered_stream_fwd.hpp create mode 100644 libtorrent/include/asio/buffered_write_stream.hpp create mode 100644 libtorrent/include/asio/buffered_write_stream_fwd.hpp create mode 100644 libtorrent/include/asio/completion_condition.hpp create mode 100644 libtorrent/include/asio/datagram_socket_service.hpp create mode 100644 libtorrent/include/asio/deadline_timer.hpp create mode 100644 libtorrent/include/asio/deadline_timer_service.hpp create mode 100644 libtorrent/include/asio/detail/CVS/Entries create mode 100644 libtorrent/include/asio/detail/CVS/Repository create mode 100644 libtorrent/include/asio/detail/CVS/Root create mode 100644 libtorrent/include/asio/detail/bind_handler.hpp create mode 100644 libtorrent/include/asio/detail/buffer_resize_guard.hpp create mode 100644 libtorrent/include/asio/detail/buffered_stream_storage.hpp create mode 100644 libtorrent/include/asio/detail/call_stack.hpp create mode 100644 libtorrent/include/asio/detail/const_buffers_iterator.hpp create mode 100644 libtorrent/include/asio/detail/consuming_buffers.hpp create mode 100644 libtorrent/include/asio/detail/deadline_timer_service.hpp create mode 100644 libtorrent/include/asio/detail/epoll_reactor.hpp create mode 100644 libtorrent/include/asio/detail/epoll_reactor_fwd.hpp create mode 100644 libtorrent/include/asio/detail/event.hpp create mode 100644 libtorrent/include/asio/detail/fd_set_adapter.hpp create mode 100644 libtorrent/include/asio/detail/handler_alloc_helpers.hpp create mode 100644 libtorrent/include/asio/detail/handler_invoke_helpers.hpp create mode 100644 libtorrent/include/asio/detail/hash_map.hpp create mode 100644 libtorrent/include/asio/detail/io_control.hpp create mode 100644 libtorrent/include/asio/detail/kqueue_reactor.hpp create mode 100644 libtorrent/include/asio/detail/kqueue_reactor_fwd.hpp create mode 100644 libtorrent/include/asio/detail/local_free_on_block_exit.hpp create mode 100644 libtorrent/include/asio/detail/mutex.hpp create mode 100644 libtorrent/include/asio/detail/noncopyable.hpp create mode 100644 libtorrent/include/asio/detail/null_event.hpp create mode 100644 libtorrent/include/asio/detail/null_mutex.hpp create mode 100644 libtorrent/include/asio/detail/null_signal_blocker.hpp create mode 100644 libtorrent/include/asio/detail/null_thread.hpp create mode 100644 libtorrent/include/asio/detail/null_tss_ptr.hpp create mode 100644 libtorrent/include/asio/detail/old_win_sdk_compat.hpp create mode 100644 libtorrent/include/asio/detail/pipe_select_interrupter.hpp create mode 100644 libtorrent/include/asio/detail/pop_options.hpp create mode 100644 libtorrent/include/asio/detail/posix_event.hpp create mode 100644 libtorrent/include/asio/detail/posix_fd_set_adapter.hpp create mode 100644 libtorrent/include/asio/detail/posix_mutex.hpp create mode 100644 libtorrent/include/asio/detail/posix_signal_blocker.hpp create mode 100644 libtorrent/include/asio/detail/posix_thread.hpp create mode 100644 libtorrent/include/asio/detail/posix_tss_ptr.hpp create mode 100644 libtorrent/include/asio/detail/push_options.hpp create mode 100644 libtorrent/include/asio/detail/reactive_socket_service.hpp create mode 100644 libtorrent/include/asio/detail/reactor_op_queue.hpp create mode 100644 libtorrent/include/asio/detail/resolver_service.hpp create mode 100644 libtorrent/include/asio/detail/scoped_lock.hpp create mode 100644 libtorrent/include/asio/detail/select_interrupter.hpp create mode 100644 libtorrent/include/asio/detail/select_reactor.hpp create mode 100644 libtorrent/include/asio/detail/select_reactor_fwd.hpp create mode 100644 libtorrent/include/asio/detail/service_base.hpp create mode 100644 libtorrent/include/asio/detail/service_id.hpp create mode 100644 libtorrent/include/asio/detail/service_registry.hpp create mode 100644 libtorrent/include/asio/detail/service_registry_fwd.hpp create mode 100644 libtorrent/include/asio/detail/signal_blocker.hpp create mode 100644 libtorrent/include/asio/detail/signal_init.hpp create mode 100644 libtorrent/include/asio/detail/socket_holder.hpp create mode 100644 libtorrent/include/asio/detail/socket_ops.hpp create mode 100644 libtorrent/include/asio/detail/socket_option.hpp create mode 100644 libtorrent/include/asio/detail/socket_select_interrupter.hpp create mode 100644 libtorrent/include/asio/detail/socket_types.hpp create mode 100644 libtorrent/include/asio/detail/strand_service.hpp create mode 100644 libtorrent/include/asio/detail/task_io_service.hpp create mode 100644 libtorrent/include/asio/detail/task_io_service_fwd.hpp create mode 100644 libtorrent/include/asio/detail/thread.hpp create mode 100644 libtorrent/include/asio/detail/throw_error.hpp create mode 100644 libtorrent/include/asio/detail/timer_queue.hpp create mode 100644 libtorrent/include/asio/detail/timer_queue_base.hpp create mode 100644 libtorrent/include/asio/detail/tss_ptr.hpp create mode 100644 libtorrent/include/asio/detail/win_event.hpp create mode 100644 libtorrent/include/asio/detail/win_fd_set_adapter.hpp create mode 100644 libtorrent/include/asio/detail/win_iocp_io_service.hpp create mode 100644 libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp create mode 100644 libtorrent/include/asio/detail/win_iocp_operation.hpp create mode 100644 libtorrent/include/asio/detail/win_iocp_socket_service.hpp create mode 100644 libtorrent/include/asio/detail/win_mutex.hpp create mode 100644 libtorrent/include/asio/detail/win_signal_blocker.hpp create mode 100644 libtorrent/include/asio/detail/win_thread.hpp create mode 100644 libtorrent/include/asio/detail/win_tss_ptr.hpp create mode 100644 libtorrent/include/asio/detail/winsock_init.hpp create mode 100644 libtorrent/include/asio/detail/wrapped_handler.hpp create mode 100644 libtorrent/include/asio/error.hpp create mode 100644 libtorrent/include/asio/error_code.hpp create mode 100644 libtorrent/include/asio/handler_alloc_hook.hpp create mode 100644 libtorrent/include/asio/handler_invoke_hook.hpp create mode 100644 libtorrent/include/asio/impl/CVS/Entries create mode 100644 libtorrent/include/asio/impl/CVS/Repository create mode 100644 libtorrent/include/asio/impl/CVS/Root create mode 100644 libtorrent/include/asio/impl/error_code.ipp create mode 100644 libtorrent/include/asio/impl/io_service.ipp create mode 100644 libtorrent/include/asio/impl/read.ipp create mode 100644 libtorrent/include/asio/impl/read_until.ipp create mode 100644 libtorrent/include/asio/impl/write.ipp create mode 100644 libtorrent/include/asio/io_service.hpp create mode 100644 libtorrent/include/asio/ip/CVS/Entries create mode 100644 libtorrent/include/asio/ip/CVS/Repository create mode 100644 libtorrent/include/asio/ip/CVS/Root create mode 100644 libtorrent/include/asio/ip/address.hpp create mode 100644 libtorrent/include/asio/ip/address_v4.hpp create mode 100644 libtorrent/include/asio/ip/address_v6.hpp create mode 100644 libtorrent/include/asio/ip/basic_endpoint.hpp create mode 100644 libtorrent/include/asio/ip/basic_resolver.hpp create mode 100644 libtorrent/include/asio/ip/basic_resolver_entry.hpp create mode 100644 libtorrent/include/asio/ip/basic_resolver_iterator.hpp create mode 100644 libtorrent/include/asio/ip/basic_resolver_query.hpp create mode 100644 libtorrent/include/asio/ip/detail/CVS/Entries create mode 100644 libtorrent/include/asio/ip/detail/CVS/Repository create mode 100644 libtorrent/include/asio/ip/detail/CVS/Root create mode 100644 libtorrent/include/asio/ip/detail/socket_option.hpp create mode 100644 libtorrent/include/asio/ip/host_name.hpp create mode 100644 libtorrent/include/asio/ip/multicast.hpp create mode 100644 libtorrent/include/asio/ip/resolver_query_base.hpp create mode 100644 libtorrent/include/asio/ip/resolver_service.hpp create mode 100644 libtorrent/include/asio/ip/tcp.hpp create mode 100644 libtorrent/include/asio/ip/udp.hpp create mode 100644 libtorrent/include/asio/ip/unicast.hpp create mode 100644 libtorrent/include/asio/ip/v6_only.hpp create mode 100644 libtorrent/include/asio/is_read_buffered.hpp create mode 100644 libtorrent/include/asio/is_write_buffered.hpp create mode 100644 libtorrent/include/asio/placeholders.hpp create mode 100644 libtorrent/include/asio/read.hpp create mode 100644 libtorrent/include/asio/read_until.hpp create mode 100644 libtorrent/include/asio/socket_acceptor_service.hpp create mode 100644 libtorrent/include/asio/socket_base.hpp create mode 100644 libtorrent/include/asio/ssl.hpp create mode 100644 libtorrent/include/asio/ssl/CVS/Entries create mode 100644 libtorrent/include/asio/ssl/CVS/Repository create mode 100644 libtorrent/include/asio/ssl/CVS/Root create mode 100755 libtorrent/include/asio/ssl/basic_context.hpp create mode 100644 libtorrent/include/asio/ssl/context.hpp create mode 100755 libtorrent/include/asio/ssl/context_base.hpp create mode 100755 libtorrent/include/asio/ssl/context_service.hpp create mode 100644 libtorrent/include/asio/ssl/detail/CVS/Entries create mode 100644 libtorrent/include/asio/ssl/detail/CVS/Repository create mode 100644 libtorrent/include/asio/ssl/detail/CVS/Root create mode 100755 libtorrent/include/asio/ssl/detail/openssl_context_service.hpp create mode 100755 libtorrent/include/asio/ssl/detail/openssl_init.hpp create mode 100755 libtorrent/include/asio/ssl/detail/openssl_operation.hpp create mode 100644 libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp create mode 100755 libtorrent/include/asio/ssl/detail/openssl_types.hpp create mode 100644 libtorrent/include/asio/ssl/stream.hpp create mode 100755 libtorrent/include/asio/ssl/stream_base.hpp create mode 100644 libtorrent/include/asio/ssl/stream_service.hpp create mode 100644 libtorrent/include/asio/strand.hpp create mode 100644 libtorrent/include/asio/stream_socket_service.hpp create mode 100644 libtorrent/include/asio/streambuf.hpp create mode 100644 libtorrent/include/asio/system_error.hpp create mode 100644 libtorrent/include/asio/thread.hpp create mode 100644 libtorrent/include/asio/time_traits.hpp create mode 100644 libtorrent/include/asio/version.hpp create mode 100644 libtorrent/include/asio/write.hpp create mode 100755 libtorrent/include/libtorrent/alert.hpp create mode 100755 libtorrent/include/libtorrent/alert_types.hpp create mode 100644 libtorrent/include/libtorrent/allocate_resources.hpp create mode 100644 libtorrent/include/libtorrent/aux_/allocate_resources_impl.hpp create mode 100644 libtorrent/include/libtorrent/aux_/session_impl.hpp create mode 100644 libtorrent/include/libtorrent/bandwidth_manager.hpp create mode 100755 libtorrent/include/libtorrent/bencode.hpp create mode 100755 libtorrent/include/libtorrent/bt_peer_connection.hpp create mode 100644 libtorrent/include/libtorrent/buffer.hpp create mode 100755 libtorrent/include/libtorrent/config.hpp create mode 100644 libtorrent/include/libtorrent/connection_queue.hpp create mode 100755 libtorrent/include/libtorrent/debug.hpp create mode 100644 libtorrent/include/libtorrent/disk_io_thread.hpp create mode 100755 libtorrent/include/libtorrent/entry.hpp create mode 100755 libtorrent/include/libtorrent/escape_string.hpp create mode 100644 libtorrent/include/libtorrent/extensions.hpp create mode 100644 libtorrent/include/libtorrent/extensions/logger.hpp create mode 100644 libtorrent/include/libtorrent/extensions/metadata_transfer.hpp create mode 100644 libtorrent/include/libtorrent/extensions/ut_pex.hpp create mode 100755 libtorrent/include/libtorrent/file.hpp create mode 100644 libtorrent/include/libtorrent/file_pool.hpp create mode 100755 libtorrent/include/libtorrent/fingerprint.hpp create mode 100755 libtorrent/include/libtorrent/hasher.hpp create mode 100644 libtorrent/include/libtorrent/http_connection.hpp create mode 100644 libtorrent/include/libtorrent/http_stream.hpp create mode 100755 libtorrent/include/libtorrent/http_tracker_connection.hpp create mode 100755 libtorrent/include/libtorrent/identify_client.hpp create mode 100644 libtorrent/include/libtorrent/instantiate_connection.hpp create mode 100644 libtorrent/include/libtorrent/intrusive_ptr_base.hpp create mode 100755 libtorrent/include/libtorrent/invariant_check.hpp create mode 100755 libtorrent/include/libtorrent/io.hpp create mode 100644 libtorrent/include/libtorrent/ip_filter.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/closest_nodes.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/dht_tracker.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/find_data.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/logging.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/msg.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/node.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/node_entry.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/node_id.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/observer.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/packet_iterator.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/refresh.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/routing_table.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/rpc_manager.hpp create mode 100644 libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp create mode 100644 libtorrent/include/libtorrent/lsd.hpp create mode 100644 libtorrent/include/libtorrent/natpmp.hpp create mode 100644 libtorrent/include/libtorrent/pch.hpp create mode 100644 libtorrent/include/libtorrent/pe_crypto.hpp create mode 100755 libtorrent/include/libtorrent/peer.hpp create mode 100755 libtorrent/include/libtorrent/peer_connection.hpp create mode 100755 libtorrent/include/libtorrent/peer_id.hpp create mode 100755 libtorrent/include/libtorrent/peer_info.hpp create mode 100644 libtorrent/include/libtorrent/peer_request.hpp create mode 100644 libtorrent/include/libtorrent/piece_block_progress.hpp create mode 100755 libtorrent/include/libtorrent/piece_picker.hpp create mode 100755 libtorrent/include/libtorrent/policy.hpp create mode 100644 libtorrent/include/libtorrent/proxy_base.hpp create mode 100644 libtorrent/include/libtorrent/random_sample.hpp create mode 100755 libtorrent/include/libtorrent/resource_request.hpp create mode 100755 libtorrent/include/libtorrent/session.hpp create mode 100644 libtorrent/include/libtorrent/session_settings.hpp create mode 100644 libtorrent/include/libtorrent/session_status.hpp create mode 100755 libtorrent/include/libtorrent/size_type.hpp create mode 100755 libtorrent/include/libtorrent/socket.hpp create mode 100644 libtorrent/include/libtorrent/socket_type.hpp create mode 100644 libtorrent/include/libtorrent/socks4_stream.hpp create mode 100644 libtorrent/include/libtorrent/socks5_stream.hpp create mode 100755 libtorrent/include/libtorrent/stat.hpp create mode 100755 libtorrent/include/libtorrent/storage.hpp create mode 100644 libtorrent/include/libtorrent/time.hpp create mode 100755 libtorrent/include/libtorrent/torrent.hpp create mode 100755 libtorrent/include/libtorrent/torrent_handle.hpp create mode 100755 libtorrent/include/libtorrent/torrent_info.hpp create mode 100755 libtorrent/include/libtorrent/tracker_manager.hpp create mode 100755 libtorrent/include/libtorrent/udp_tracker_connection.hpp create mode 100644 libtorrent/include/libtorrent/upnp.hpp create mode 100644 libtorrent/include/libtorrent/utf8.hpp create mode 100644 libtorrent/include/libtorrent/variant_stream.hpp create mode 100755 libtorrent/include/libtorrent/version.hpp create mode 100755 libtorrent/include/libtorrent/web_peer_connection.hpp create mode 100644 libtorrent/include/libtorrent/xml_parse.hpp create mode 100644 libtorrent/src/Makefile.am create mode 100755 libtorrent/src/alert.cpp create mode 100644 libtorrent/src/allocate_resources.cpp create mode 100755 libtorrent/src/bt_peer_connection.cpp create mode 100644 libtorrent/src/connection_queue.cpp create mode 100644 libtorrent/src/deluge_core.cpp create mode 100644 libtorrent/src/disk_io_thread.cpp create mode 100755 libtorrent/src/entry.cpp create mode 100755 libtorrent/src/escape_string.cpp create mode 100755 libtorrent/src/file.cpp create mode 100644 libtorrent/src/file_pool.cpp create mode 100644 libtorrent/src/file_win.cpp create mode 100644 libtorrent/src/http_connection.cpp create mode 100644 libtorrent/src/http_stream.cpp create mode 100755 libtorrent/src/http_tracker_connection.cpp create mode 100755 libtorrent/src/identify_client.cpp create mode 100644 libtorrent/src/instantiate_connection.cpp create mode 100644 libtorrent/src/ip_filter.cpp create mode 100644 libtorrent/src/kademlia/closest_nodes.cpp create mode 100644 libtorrent/src/kademlia/dht_tracker.cpp create mode 100644 libtorrent/src/kademlia/find_data.cpp create mode 100644 libtorrent/src/kademlia/node.cpp create mode 100644 libtorrent/src/kademlia/node_id.cpp create mode 100644 libtorrent/src/kademlia/refresh.cpp create mode 100644 libtorrent/src/kademlia/routing_table.cpp create mode 100644 libtorrent/src/kademlia/rpc_manager.cpp create mode 100644 libtorrent/src/kademlia/traversal_algorithm.cpp create mode 100644 libtorrent/src/logger.cpp create mode 100644 libtorrent/src/lsd.cpp create mode 100644 libtorrent/src/metadata_transfer.cpp create mode 100644 libtorrent/src/natpmp.cpp create mode 100644 libtorrent/src/pe_crypto.cpp create mode 100755 libtorrent/src/peer_connection.cpp create mode 100755 libtorrent/src/piece_picker.cpp create mode 100755 libtorrent/src/policy.cpp create mode 100755 libtorrent/src/session.cpp create mode 100755 libtorrent/src/session_impl.cpp create mode 100755 libtorrent/src/sha1.cpp create mode 100644 libtorrent/src/socks4_stream.cpp create mode 100644 libtorrent/src/socks5_stream.cpp create mode 100755 libtorrent/src/stat.cpp create mode 100755 libtorrent/src/storage.cpp create mode 100755 libtorrent/src/torrent.cpp create mode 100755 libtorrent/src/torrent_handle.cpp create mode 100755 libtorrent/src/torrent_info.cpp create mode 100755 libtorrent/src/tracker_manager.cpp create mode 100755 libtorrent/src/udp_tracker_connection.cpp create mode 100644 libtorrent/src/upnp.cpp create mode 100644 libtorrent/src/ut_pex.cpp create mode 100755 libtorrent/src/web_peer_connection.cpp create mode 100644 setup.py diff --git a/deluge/glade/aboutdialog.glade b/deluge/glade/aboutdialog.glade new file mode 100644 index 000000000..c8799dc0d --- /dev/null +++ b/deluge/glade/aboutdialog.glade @@ -0,0 +1,38 @@ + + + + + + 5 + True + True + True + False + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/glade/delugegtk.glade b/deluge/glade/delugegtk.glade new file mode 100644 index 000000000..e28ae076e --- /dev/null +++ b/deluge/glade/delugegtk.glade @@ -0,0 +1,1140 @@ + + + + + + Deluge + + + + + + True + 4 + 3 + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + + + + True + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + + + + False + False + + + + + 3 + 2 + 3 + + + + + True + + + 3 + 3 + 4 + + + + + + True + + + True + _File + True + + + + + True + _Add Torrent + True + + + + True + gtk-add + 1 + + + + + + + True + Add _URL + True + + + + + + True + _Clear Completed + True + + + + True + gtk-clear + 1 + + + + + + + True + + + + + True + gtk-quit + True + True + + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-preferences + True + True + + + + + + True + Pl_ugins + True + + + + True + gtk-disconnect + 1 + + + + + + + + + + + True + _Torrent + True + + + + + True + _View + True + + + True + + + True + _Toolbar + True + True + + + + + + True + _Details + True + True + + + + + + True + Columns + True + + + True + + + True + Size + True + True + + + + + + True + Status + True + True + + + + + + True + Seeders + True + True + + + + + + True + Peers + True + True + + + + + + True + Download + True + True + + + + + + True + Upload + True + True + + + + + + True + Time Remaining + True + True + + + + + + True + Share Ratio + True + True + + + + + + + + + + + + + + True + _Help + True + + + + + True + gtk-about + True + True + + + + + + + + + + 3 + + + + + + True + False + + + 2 + 3 + 1 + 2 + + GTK_FILL + + + + + True + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + + + True + Add Torrent + Add Torrent + True + gtk-add + + + + False + + + + + True + False + Remove Torrent + Remove Torrent + True + gtk-remove + + + + False + + + + + True + Clear Finished Torrents + Clear Finished + True + gtk-clear + + + + False + + + + + True + + + False + False + + + + + True + False + Start / Pause + Start + True + gtk-media-play + + + + False + + + + + True + False + Queue Torrent Up + Move Up + True + gtk-go-up + + + + False + + + + + True + False + Queue Torrent Down + Move Down + True + gtk-go-down + + + + False + + + + + True + + + False + False + + + + + True + Preferences + Preferences + True + gtk-preferences + + + + False + + + + + True + Plugins + Plugins + True + gtk-disconnect + + + + False + + + + + 1 + 2 + GTK_FILL + + + + + + diff --git a/deluge/glade/dgtkpopups.glade b/deluge/glade/dgtkpopups.glade new file mode 100644 index 000000000..63279d9a9 --- /dev/null +++ b/deluge/glade/dgtkpopups.glade @@ -0,0 +1,406 @@ + + + + + + True + + + True + Size + True + True + + + + + + True + Status + True + True + + + + + + True + Seeders + True + True + + + + + + True + Peers + True + True + + + + + + True + Download Speed + True + True + + + + + + True + Upload Speed + True + True + + + + + + True + Time Remaining + True + True + + + + + + True + Share Ratio + True + True + + + + + + Remove Torrent + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + True + False + + + True + + + True + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-dialog-warning + 6 + + + False + False + 5 + + + + + True + 0 + <span size="large"><b>Are you sure you want to remove the selected torrent(s) from Deluge?</b></span> + True + True + + + 10 + 1 + + + + + False + False + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 20 + + + True + Delete downloaded files + 0 + True + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 20 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete .torrent file + 0 + True + True + + + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + + + + + False + False + 5 + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + gtk-no + True + 0 + + + + + True + gtk-yes + True + 1 + + + 1 + + + + + False + GTK_PACK_END + + + + + + + True + + + True + Show/Hide + True + + + + + + True + Add a Torrent... + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + 1 + + + + + + + True + Clear Finished + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-clear + 1 + + + + + + + True + + + + + True + gtk-preferences + True + True + + + + + + True + Plugins + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-execute + 1 + + + + + + + True + + + + + True + gtk-quit + True + True + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + GTK_WIN_POS_MOUSE + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + False + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Rate: + + + False + + + + + True + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + 1 + 0 -1 10000 1 10 10 + True + + + False + False + 1 + + + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 1 + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/glade/edit_trackers.glade b/deluge/glade/edit_trackers.glade new file mode 100644 index 000000000..32053dcd5 --- /dev/null +++ b/deluge/glade/edit_trackers.glade @@ -0,0 +1,120 @@ + + + + + + 300 + 200 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Edit Trackers + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 36 + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Tracker Editing + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + 1 + + + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + False + False + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 0 + + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + 1 + + + + + + 3 + + + + + False + False + 2 + + + + + + diff --git a/deluge/glade/file_tab_menu.glade b/deluge/glade/file_tab_menu.glade new file mode 100644 index 000000000..8e3f34162 --- /dev/null +++ b/deluge/glade/file_tab_menu.glade @@ -0,0 +1,100 @@ + + + + + + + True + + + + True + Select All + True + + + + + True + gtk-select-all + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Unselect All + True + + + + + True + gtk-file + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + Check Selected + True + + + + + True + gtk-ok + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Uncheck Selected + True + + + + + True + gtk-remove + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + diff --git a/deluge/glade/plugin_dialog.glade b/deluge/glade/plugin_dialog.glade new file mode 100644 index 000000000..68fa97357 --- /dev/null +++ b/deluge/glade/plugin_dialog.glade @@ -0,0 +1,114 @@ + + + + + + 480 + 5 + Plugin Manager + 583 + 431 + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + 2 + + + True + False + + + True + True + + + True + + + + + True + + + True + False + GTK_WRAP_WORD + False + + + 10 + + + + + True + GTK_BUTTONBOX_SPREAD + + + True + False + gtk-preferences + True + + + + + + False + 1 + + + + + 10 + 1 + + + + + False + + + + + True + Plugins + + + tab + False + False + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + GTK_BUTTONBOX_END + + + True + gtk-close + True + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/glade/preferences_dialog.glade b/deluge/glade/preferences_dialog.glade new file mode 100644 index 000000000..4e8eca460 --- /dev/null +++ b/deluge/glade/preferences_dialog.glade @@ -0,0 +1,1461 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Deluge Preferences + GTK_WIN_POS_CENTER_ON_PARENT + 550 + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + True + False + + + True + 1 + + + True + True + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + 2 + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + Ask where to save each download + True + 0 + True + + + + + True + 10 + + + True + Save all downloads to: + True + 0 + True + radio_ask_save + + + False + + + + + True + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + 1 + + + + + 1 + + + + + + + + + True + <b>Download Location</b> + True + + + label_item + + + + + False + False + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 10 + + + True + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 0 + Maximum simultaneous active torrents: + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + True + GTK_UPDATE_IF_VALID + + + False + 2 + 1 + + + + + + + + + True + <b>Torrents</b> + True + + + label_item + + + + + False + False + 2 + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + Compact allocation will only allocate as much storage as it needs to keep the pieces downloaded so far. + Use compact storage allocation + True + 0 + True + + + + + + + True + <b>Compact Allocation</b> + True + + + label_item + + + + + False + False + 2 + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Queue torrents to bottom when they begin seeding + True + 0 + True + + + + + True + 10 + + + True + Stop seeding torrents when their share ratio reaches: + True + 0 + True + + + False + + + + + True + True + 1 + 0 0 10 0.050000000745099998 10 9 + 1 + 2 + True + + + False + 1 + + + + + 1 + + + + + + + + + True + <b>Seeding</b> + True + + + label_item + + + + + False + False + 2 + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 2 + + + True + True + The maximum upload rate for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download rate for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + The maximum number of upload slots. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections allowed. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum number of upload slots. Set -1 for unlimited. + 0 + Upload Slots: + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum download rate for all torrents. Set -1 for unlimited. + 0 + Maximum Download Rate (KiB/s): + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum upload rate for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Rate (KiB/s): + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + <b>Bandwidth Usage</b> + True + + + label_item + + + + + False + False + 2 + 4 + + + + + + + + + False + + + + + True + Downloads + + + tab + False + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + + + True + + + True + 2 + + + True + gtk-dialog-warning + 6 + + + False + False + 10 + + + + + True + 0.20000000298023224 + <b>Please Note - Changes to these settings will only be applied the next time Deluge is restarted.</b> + True + True + 0 + + + False + False + 1 + + + + + False + False + 5 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + + + True + From: + + + False + + + + + True + True + 0 0 65535 1 10 10 + 1 + + + False + 5 + 1 + + + + + True + 5 + To: + + + False + False + 2 + + + + + True + True + 0 0 65535 1 10 10 + 1 + + + False + 5 + 3 + + + + + True + 1 + Active Port: + GTK_JUSTIFY_RIGHT + + + False + 5 + 4 + + + + + True + 0 + 0000 + 5 + + + False + 5 + 5 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Test Active Port + 0 + + + + False + False + 6 + + + + + 5 + + + + + + + + + True + <b>TCP Port</b> + True + + + label_item + + + + + False + 2 + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Distributed hash table may improve the amount of active connections. + Enable Mainline DHT + True + 0 + True + + + + + + + + + + + + True + <b>DHT</b> + True + + + label_item + + + + + False + 2 + 2 + + + + + True + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Universal Plug and Play + UPnP + True + 0 + True + True + + + 2 + + + + + True + True + NAT Port Mapping Protocol + NAT-PMP + True + 0 + True + True + + + 2 + 1 + + + + + True + True + µTorrent Peer-Exchange + µTorrent-PeX + True + 0 + True + True + + + 2 + 2 + + + + + + + + + True + <b>Network Extras</b> + True + + + label_item + + + + + 2 + + + + + False + False + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 2 + + + True + + + True + 1 + Inbound: + + + False + + + + + True + Disabled +Enabled +Forced + + + 5 + 1 + + + + + True + 1 + Outbound: + + + 2 + + + + + True + Disabled +Enabled +Forced + + + 5 + 3 + + + + + + + True + + + True + True + Prefer to encrypt the entire stream + True + 0 + True + + + False + + + + + True + 1 + Level: + + + 1 + + + + + True + Handshake +Either +Full Stream + + + 6 + 2 + + + + + 1 + + + + + + + + + True + <b>Encryption</b> + True + + + label_item + + + + + False + False + 2 + 4 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects regular bittorrent peers + Peer Proxy + 0 + True + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Only affects HTTP tracker connections (UDP tracker connections are affected if the given proxy supports UDP, e.g. SOCKS5). + Tracker Proxy + 0 + True + + + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects the DHT messages. Since they are sent over UDP, it only has any effect if the proxy supports UDP. + DHT Proxy + 0 + True + + + 2 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 4 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Proxy type + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Username + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Password + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + None +Socksv4 +Socksv5 +Socksv5 W/ Auth +HTTP +HTTP W/ Auth + + + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + 1 + 2 + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Server + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port + + + 2 + 3 + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + 4 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 8080 0 10000 1 10 10 + + + 3 + 4 + 1 + 2 + + + + + 1 + + + + + + + True + <b>Proxy</b> + True + + + label_item + + + + + False + 2 + 5 + + + + + + + + + 1 + False + + + + + True + Network + + + tab + 1 + False + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + 2 + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + Enable system tray icon + True + 0 + True + True + + + + + True + 10 + + + True + Minimize to tray on close + True + 0 + True + + + + + 1 + + + + + True + 3 + 10 + + + True + + + True + True + Password protect system tray + True + 0 + True + + + + + True + + + True + 5 + + + True + 0 + Password: + + + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + 16 + + + False + 1 + + + + + 1 + + + + + + + False + 2 + + + + + + + + + True + <b>System Tray</b> + True + + + label_item + + + + + False + 2 + + + + + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 15 + + + True + 0 + GUI update interval (seconds) + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.5 0.5 5 0.5 0.5 1 + 1 + 1 + True + + + False + 1 + + + + + + + + + True + <b>Performance</b> + True + + + label_item + + + + + False + False + 2 + 1 + + + + + + + + + 2 + False + + + + + True + Other + + + tab + 2 + False + False + + + + + 2 + 2 + + + + + True + GTK_BUTTONBOX_END + + + True + gtk-cancel + True + 0 + + + + + True + gtk-ok + True + 1 + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/glade/torrent_menu.glade b/deluge/glade/torrent_menu.glade new file mode 100644 index 000000000..70f9eebf9 --- /dev/null +++ b/deluge/glade/torrent_menu.glade @@ -0,0 +1,165 @@ + + + + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-media-pause + True + True + + + + + + True + _Update Tracker + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-refresh + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Edit Trackers + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-edit + 1 + + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Remove Torrent + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + 1 + + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Queue + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Top + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-top + 1 + + + + + + + True + _Up + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-up + 1 + + + + + + + True + _Down + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-down + 1 + + + + + + + True + _Bottom + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-bottom + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-sort-ascending + 1 + + + + + + diff --git a/deluge/glade/tray_menu.glade b/deluge/glade/tray_menu.glade new file mode 100644 index 000000000..22411c91d --- /dev/null +++ b/deluge/glade/tray_menu.glade @@ -0,0 +1,158 @@ + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Show Deluge + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Add Torrent + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Clear Finished + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-clear + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Download Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Upload Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-preferences + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Plu_gins + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-disconnect + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Quit + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-quit + 1 + + + + + + diff --git a/deluge/pixmaps/deluge-about.png b/deluge/pixmaps/deluge-about.png new file mode 100644 index 0000000000000000000000000000000000000000..63aef4fbb7a260167e05378c74f0415b0de6f414 GIT binary patch literal 9944 zcmV;}CMVg6P)CsvFFZ;w0sVN88H|K)K{mt(MOHMLOLP&o>$*X@<;Fk}xERBD9jno+pH$RIO32 z)i`Wvf-vU0U*4zWx#xaOH~4U*ndIf>A&0F##u$gx&TW1LiL&6DT*wAurV)&#>oaS*)m`NkOHz$XrT%Jn*!d4Bu*4Sx0Q z7u0Lzk8z3D9%^uaRbN5a_PL(7*Z2A9$qq_sk|-n?^wCPa@%b`M@zXBhXu#a^3eR5b z6U8ZC-oJHYVxI%f-A>Ii$@f2Q(P|Hf!+`E#0^qajxe*IPCFL_QuLdLq;bsdUYjS+_s;d&K3AM8 zfulI(cYk=va1;^@2l%}%rr7tM1!)}9Zylnf;>X9^BuRFjmwBzBm89zL;yM(eH61_#9z(H>i3R57yVH*DB2GJ~|wH&^fnvTWs&P$dZJ9 z^YGNF`zH9kE?JVWx!t7Q`LMr}&mHGNV68PjJ>DisQbxTFNqBmb`EAIOI9Ku@=IQhE zQIhKn*=sQGWGpVsurOce!TKtdvdetZz{vEy&e>`Y*xYWCr3r&}^R*D*`(oH_qm|_G z(_Q@F>}r049Opvdr_c8pjv|85kTiJpGg7rwZ=O2xCN47biqzR@V+@v}XYk_c{=GFcf~~za(g-vHg+-KUb`JVjw!`|}RUFsG^LO895-X*7@_Y|t zG(o?2VgDDupNAZuJl{ippqKk|!g~=|O2x*@1GG|v!w(BloE6b%h|DrxZZ$u9Q`+DB z5!-@0_g3*r9*x$3QIw&vP)1Bv-;Zhad@7X^cUG4u*#d@*6VJK5+agIaf_@*ZFOEn? zYexMZX)4)xd3a&3`>Er-2)wn^LMn|v=v|5_>-V~7t$F%<@3S?fhmCC<6)`tA%kt7Z zeweb?8lo*5g+Q5{#L@_)vDj~q2%?OI`B~=YXDE}Rg5wGsMhUxnZL%~WKBrLNS&&8{ zNfdI>?9uJrn9`@gnF$<5344tWS(04H{^%sgEG6i%tP*Wu+}mnfqk zt&OB0EmlEhfkKcd*l6^@viNd+8QZZjK^v_Vt@eQaARrEWRCamqr_s4nG|goGw|0ZK z;!FhY_IW$zHA7^FNE6+EKSd&}&n!RCsXj7Kzk6-|wM?T`X(zINt+Xh5A}K z!O4Gq&|jYI2gI5mOy54uEAN;n*Tl&qZh zMKI`NwC3g3;g$L0`xwZwl!MLZV7vV6t2+qW;pt9~L=}5pVHYY}kayaIKK6Bfd~q-$ zmYTb(^VI4U)^F7*J6GrZQ&N(I0lj{36B~XDPDS8MDz>(pC@G2j(Z{}~Gr<@`I2sbg zDTl58Wna!m!_Kp(Xaf)K-J({hvfK9Q`dO~F)2mR~BSIHnPsnSdi+%Fby&>8NzPh`F z<9Yn*+XuML`8ELf5QM|QM1`;O&G|!cDgtlqG?7XZ46Y_YqQFOM#pZVNTISO>_xJIA zpPAV@ch{Ei6UB?hhzyG`O6hg`v=3WTtI4oGBucYl51f;BY-XE6nZwMmap>c^F5j## zQL2>r_PZ}H`@cJZ)|$v4;Ri9T_VwQ)y${~mQYQ=(_8VQ&Bqoios6)XR!r>6hTA*>* zRH%f6y>LllPW?(E`uCBA)l2ZhDo{q7n2driV1z_tXAZQ(jLGqo}$&%w4WEFrKh z3(K*o&de~kv`o2HM*vtL3PJ0P66)oMrMWU+tS*pg!53fLW^3!@M30L`G#XN=)p@yn z$nw%GmUYvHXBzJ$@Qcj@j4=fLtEs+Y#DPzBW`?bu7HhW_ulQQf3kYE_Mj!-i zHGQgOhdawP`eA}xUt=%~==UyP^^7qD!vVrtpmEr}RyXJKz+3;yyS)*;zE2VcD0%f` zt-)}Jl46^8_7FHoTN0`0}k5*EMVL>P6$B~hcvb}*?#_n zEKLzbTVDh`-5;Q|;b(W}sg+&6{`mvEkJM`TtcU|2rDTx-c$3^QjW-fF4;vj^sj$(z zFuu|3?6p7nES`RUK(pOP%Z%GKLl{Q<^N*XaJx>U59E+0YP%V2@${y9Shg6zocZd)I z*RgQz+|+h#i!6%Re)5R!;UU5TAuKX!c-|OcTNdBmU!>%@eEst;uT1C2a4OC_b`+EKA_I zHsz8_wd7JQyHv{gF(RoQCB73!NTLv#rR?stNz)fM`?Zi(O`^WV{?!Ftu1cPI;(Z*1#c+Ax+%+FL_@0o97f zOs&LRz07RA%xppAxnj-K%4h?$418i2QQyv4EV9+<>8Q$>*ORZmsnlDe6v+%8A%k;?v5DwS2Qo-0&&cQ zR%%LpKe4^w|7+GtQIyUbKe%v4KM zD;~9qN4exsEqOSOO{ecO3Q|1J;okjqR@YWBZkZraliiOo21C9ZDmjAG?~x39$TZ6L z$>dHkV+=Zr=pQziSy|(t8&NO0EY6hqYIT+u+A!+R;twu2J{$}Oc$Eq}yDh%`#r;dY zmdnO51>QgClF6%4Qg$8)hlAYQ+HPL5O&#`Iue55igEza;~fA;Tr__K#Zlt?5vc3~bLTNVgGd3KSRyI(PX z=PRnUDxT}$Iu@SmP;wm7VH=s```u^z15&B^^7b6{ipR>GHSBX9n08iZB}oev4#Ugc zmtQs}1RjT8l#-XyPUT}jW*JEw(e4cKuduoG_{Ya2am25Gevfj=<@=|5#7RcA;!>-6 z)C=XURr8}UnpQu+u`O1X=lRe7=}+*=75bqr_O;f8u#?9{3|NSxg;&8_UdM7>T*sM& zm^{Z~c(9YJGVpA_k8KIQ{bB*vb6CE!cG+(kp`Tj;pK`eHJTM{fUZaDQig0-O>2v3a zaYf746&<9dl;YW=CsfNG_tzHb4gxlIT9it8Vn(&>QLPlK?4q=xKZ+@N4y&t6{Kx0WNnkr3mDMk?+)}<5b}dS-gG)*>>|qJPFvvLU zN6gnsd~s`r`rItl+4|+aZ&{Xaky?G?&$zjv1ykx<)L>pGC@D$fh)#Dz5MI3*JkK6K zBTW*1_Fxqu1dm@H;5Zi5l2dq-9m<|drQ}lb9Q+_BZ?)p_?|$I_Ja$MO zBeQ*^z>brLoP;%w8w*_5qq6oTrP>T7FMpkq>!3z0jFt#Xu+bQjWSXz<%uy{lEUm3# zU2?v8SWE)h-T(Zuv&Jc<7QkVvk5c;D?0sLw_=Dcry?W->)1MwwavdJrU1H!zG+F~n zu0zQ?B5!_l@WTY#u_?I@Uw^gET)n~|*0`>HL|)g%bzfEDoVbo{;aJurrE;91l~**` zc+2Zl>ob!?5yt`<46uYi8^hDR+?4+A!6LTfu(-N>sqdXk(>z>w*!!$zD`~Cy@zFNM zn4G`|%^pf-B+>O8rad;dn(e`r>LuN2wTZ%zhxb>oEsH0`Xc5n`@m#0Kw93hAfGCl8 zu1&30=9{nYgJlt>OvviF$CcM9(kC5zdKJXAL_ywMiEYcm5*DVuoSWNjuIMF-q6Z=9 z`w0jAh=qEI^_4ocnHkEL?A;GXLyR%krhw%%IBfOFomf1@{~-3;Q8JFtqtodGXT^sc`uwLah^I`_;9W}j4^CaS(279 z1|_pg>+yV1$jz;o!)A}<3M9iDW7yf;W^t~H=h*BX=JnRDZDTt*dB>!49h)rEljMv0 z_g1kjiy#$8gq>F3;&-Nrdz`!i+kTzA;2*F`LzuB)XRmOA@j)&ymiu%XNBm+xbmoWpM^$4DDi+NUn+nsS1TmrOJtBSuD)W z;M#fP_KC;2Ah92Q3tyx>B0Z^@7 zE`cGxIZLUp^`wq=CvV;ujfNPL->5z-0z*E^QeOe5N4L{O2*J{Pjs7S?7RoX1RV4}{ z2_Z-`NwEcGVSWbNw$O-)$9cT}b&8PDYi6@$;Y_KoC33=!No-BM*3nVmEvylVD&_iI zan-r4tTnU;F}1Qwz3fshdzW7LpM(KgDR%bSX#MHt@V^P;Tn3}{u{7t;j=*UgA+wCV zM)y*k9=&#l#knfBWzp;&wdyq-FCh^E(o7YP1@i@g4bBOvl6J9AJ|eGEnBvxy(`p?5 zeU-2|2akwoJ1*tL>8vYL?7a*Jy#O%WUaBF4pjvT1-kWtwgrgy8CTU*7_QKs>o;Di! zqhl?0a{^z#8`fJ9jfNfk>%#bg^+jP`S9ozi~r45TSWiWxE`0NxI^7bsb)1z}KP7j$$wAPa*>hY9UTx)|;2Bi(U_!>dcNy8we#I`I- zPTrAYn!Mw*$q9K2wauMXQ% zCf}2VLu*{L;7ww^_QZ6a?03f`7ANd=VX~$%QCTxZUNc^%C{4_dQYma(fH9XIN*W_? z@~_$2xjs$)x5w&x!c>&cmca4Y>9Kq9PLDBxor1t}iojYMGO5XwB9j_bD78}g;~3+~_Pd;{w7@H7=Mhm2rs%Vk6ImAzq}1g6)mMhVV--$Qc`a19KL{9( zLgK(bvAN`DOJF#1dR+8OrK}juI&M*;juBXsNlBJUGO5Tig=1NT`K;M%w6JZ9vL_}v z&f|iG*W$a@*kpdyQFVkP#Wj;-zBsn@C z1Pq3``f7c0#N@LkFvbx1xznTBzPMa)&&wwsj0vn>Q3V9jOJ*X z!}f0Llg-Y0m-@nlz}L&=eip=j?)2E)ZeExQj|m(Wl~MKT@roWP6-lZ{Qc0X-M2W~)VR?pSpvk8|>R zwxC+F=?=rZ6|GcgbA=?B@u(fi_9r-M=QQdIHv|R>6~17n$L!oJ-9g^{Uu;_J$Z~a=;G!u z2@{V8rD0f_ErHSW`aWn)nq5sZH7rK$Tz2a0Suhx$PJQ8qz!QYS0cj>LEIVsqX^~+N zBb9z5fitNHqXa)n@uQS5N|91?Yhi{YP5Gby`)^4?AG-cMB@{BJG2w@NiEGUW`igqpIFjPTqTja6d<+(C4lSEO1g(khi+z5yH=+xaS z)Xd>cI8xs{PvKp-A@CH!;Q%T1g)~W9Tv`P3X$qDQEX-D4dydwIEK~Sl!qATyg$bh| zCvq64%+)KnHvEsj`Wyb?@BbO0Vzkl4JR~z&w?9(Z>zQWM3Jp&v0Q zNIVGgHS{CmR5DX5VWauKfAjzN-9P^ey>=^CV5KK%m`WWbVVH3cSO6G=8LRG*IX!k> zfhm$TH0#TC>SdSRW{)6>K}$wqhS3VGF27aLn=l@Dx8LXzCs*@2&#Jxv_MJce^S{ti zUfYh8XN8gyw^SyGQkEBI&wakIk{L>-Xqn-;Hd~u}WKwZ&Z2`}7*l(VkP@>1?u+m4# z7e%s#DwNikyhEkRQucTEXdN7oW{PsPHc{U3bEh3xlT=Bqr((e4f(nB&8kE8nhVLFM zBD0L&J=sB}34>vPA7w{VXobk9K3qX4DJfTLBuUE3^4zT`8`}(;4V+31yE02pvG5t+6Z%jYYZSQ9q}iPdRb3oWwFk zNlB&b(rC14_k6y*x6JC&EZh6t*U~E|*Jkpr{HauE&1A1DRlYK%$udPI6=^1s%AmE% zhhv8WM(soV;gBRruxtlmWBn z*P1J@Kp-spN)i}y5|`_BhJM8D)rBi@o%@Y0&GvwBIG8l6dl$wEd_&+jK&A=hYHjjC zpT}@(t<*$;$IG<^aU9d@2c%N5zP7;X@+`ZDy*$M81}J4nQi&?atCY^m5Tzo`#v~pS zJ14wS8kuQimXHPkey_!--y=y94B$8}#^@pwEWa5jsrYKO#_h!#eh~8e$2+8PLKFrx z+nwARFo3YYvS506+7*N;g5{RuVOth+v#-bCFA`}c`RU0HGD{itPM?EtL*P?DD}`Gs zqm*W`X#e^_=hG6ca}t~4sI{V2EpgcF(dp;yTkowdaBDFq@!M*Zje#UnBw1d1s8n8l zm?=$~shnI=A(bJ`G^xx_`Nhu2}eHtPMc1%i64Y{=9cB4l;-QTI?JibTk|;1<&8^yv3bB~6fx>{ z(dzV`{2KzF0!n7Mr4nJBv9vIQ=Y3e;C#^L`tDL-AqqV|ljX+Z^J9N4}&F%=tvADOo z!20b)dc%Mye!JIHYeSMLq9h}gIeDd2NS!NkCUf$pS-vuvD|9LqX)5!Ir%VwiDWM2IKiMUT6S5>>Z)cMzPEk7dPal!i#ww0iRe{G)Sw^Xvr=?z& zLu)jOc)rmfjiNJop5ZkDU;oJr?+clxlxuZ@Fk$W1{0Hrs@)Er9Qeg7^sWAr6v#C}b zy8V#D_7JHQcW%w|;LZ|42)cvtoz+sN=u?$RRqTa}eesyI#lARBWI^09p;cjq=MJrV zs|$Sl)f%o)JbSUrPn)eg-#L!h-P#}sBa{L?QC|D_@e1#O$Iwb)I}WKd%+6G)l)Vdm zeWf&i_-TtQlMFlUNuTG_;sgc0A@E5sI`2tkgpKDp%+xFIeJ&xa35hZKSa%C!3?)KISYDjr-s%Eht}i2i!6=$a#5ft_v`kZ^N=l^*HO}|SQsqvn>8d2l60F}| z;8)+=V`;udnks(WXz=X z$l9%i6Vq8g7B;pH==FVqL7yx+``FtX0!kD@d;kCjUP(kjRG$G^l2EGF7>*)VSKfcm zG%5F+5VuIGFiegm&t|Uf5ydG(KVfUHL#7lgb;*L`kE@|J+P)* z>%7v1lH&xgmObvR&GV~o?y@*rBTh3Oy=d^SkGARbe3a5S4z%|-b0wdc%}&0i$SZ6I zVY^peeaA3G?AvlYq*N>|ez>3GH0bqxUc5XYjbnnr`?Yhri36Vzqh6Q!N0mS0;}}8+W@ZI<)+8G*574+g-8|&^c9SpfF7wS7w|H=8i3fLrYK2yzTIMLzk^hHvWM+B zWMPY;KSCLS(gMTSOm++MPOVRfyko?Hk5?+wIE00TI?Ic*ANBR5R6KdMhtV4U3|{9q z;fBDcgiI6SFrXs@hpj%VEA#JtF55Zo49jzmbLAA4O`J;hn!fPju$&E zUhK4(t5>*lYmVD1^QeM|V==I-r;z>`YWx8$)jxG8jd42LX-t0IkM((iTRg z^jh2Kg1FKY&Z-Gv?c>C?FMqts`(o7ZvQS#%>9aj%ep8|JfzHae;n~JMX(sXey?567 zz7@rd`G&w}KrrazmdZSRzDK3(F+1~m1<`3?J8n)`VHL!+uq+#;VQa4smPZs05Vnia z8S$_Q2&%L5M2XF#jRPKS9N;=O^=gTkT6wa{C3iBG)GnqJ#;IiJ_Zf_$Lb3CGSKd}9 zzaGoANroMwQ6GaqYJoC&4U%Q&gymHd+itGLpD1}D_j8Q8UFMdS`RVZvzy9vQq-Xbe zVspDmt34zRgL6w=z9H}#V2okdX)%9ml^-5$@ta>i{2=e8W!ZS;Dl&7h3Sw-%XrPQk z93~hnEW3oU9MI}0)y{OmawwInRO$<)Qn7Q;o9u_jdtckOCPU2f45^%CLgYyswqs+o zB#nIhPA4x{Fcumo&y^mBh%EbvxMNSV?G`5U)eaY(7FiPKL*Rwr@v~jN{rUR2-jo^# zUAA{yWJ!E3rt=s#1U@5_%-#O8ODp{F(-!~cR}VfoKJ2(2w&Sw3w}T&>JV8Qfgi}Hk z#5G1u9#eR-(>SCP)IG`3wNSlAY5g-jx%$e%dP#tmULE?Vc_ zWe{K&FC8P@Cs!7-h zqb6afaY2IC8hx+RYDhTActPL_y%I*^lhX zBZ)$kQp_za^Zk!6S--o&`rYMIJ3x#vZ0)r2K3q~V=(LVyXJSvj7KG*)DhlT{Rj2$ntdv(FU9FlaTISy<$-1!0u%-7oH+x;ESx!;8%W z_8Z;FmP^Tx)3M}6fj`+*>iqs$6ZIV!^}0wY!5IGOU!QXK&N6pz zFXFmyrcIjW)kVWmNE+uw3TI7|JPU3Jd=5}jGH4xAuGXl{&GYE#F0N~{yg19!;tb`I zHwo87amvAAFYl*JuHvKa`!~^>s3=E=$f@O z;kr`EBgSE{r-OE_xqjSIYpFG`2X^)D|DxCU?=1aZ+qXZFMQ>FWT)@rT99u!0=)4p|6yos zaOm@Q-*xCe?DV}{3-T>TfPq^1qhaWe*J_nF-}kfcf6Y$c&rZv?6ajwXU;oy>s#VLc zV3fdPlnxG7KKSN${>H(bzOS8>Zy5r-{%!BOd1R>i?F-q_&a17YA7D5V%59Ym#E1fvw?Qt{CK+ot~ePT%8B$+rdp?mYBc z)pGF=B?OgH0TW|`*tdHeLEwWBf`MA)AH3!M|NJj^`aX9;p7R8F{ab(a`-TUr|CA5{ z$|$C%MnMRH=P~S=8imIwD5DsvSKs%qe&*M|eW&mDYRhw)06*{}KQlZ&H2AxWQ33!{ z6C((X)>A@IDHky@J_Je#eBTQPh6X?NlRx<%2X^|tuckbw2{1K1_3>gM7y*EKt%QMk z83fQ9As~ccbf}Jctqe*CiiKdhG+O=S&ffLamFFx0UU%QGymfe>`f>p9eGj|FN8Edh z%WGI&Z-G)xf~m<7grT+!)k^8*Z+Z9c{KAgi_*ItYBmrLYhF`pOY;54Y3V>1y(~~1$ zi~l-&o3yYFfv?6xl{m@f>08tXXYWLgj5Qr&(4DjJs4If1elr}1)&Vo^Vndm`r$YK z^arMQ_~&f9Jm&~-)AhT5t6DDHsFXq>3@|>TeL4l;{OmG1aRQ|Pr3gBSz}eYFC_ zP^*Tc_uqfNx3j-!d*wMrfFJy^pMTZRK<$l60fYd1rpG`Cg;Ky; zqm6~-29zR@ppXjs{CY>92LTAd#P|@3g#r`^isjNBYu>)!-s#`9o${O^z}>HT+t}zx z{dXxPL@I@e@nICg5Q+eakT`vA5fXp`fmDD5AQXWl1m_o4(P~GA1XSk-d>;TorCfT$ zeee0;kL>Wz+ctU55a8ghk&hJ$;jjWhwOYpL@SryBis1CQCB$56@Qt~bia-IH42t07 zxg{iA0wC~wAG>#r0EB^1Myi#{`+xlBK5)xU|Hkc*=L7+M;C27@?e%*7UI_xmJnY>) zVfuAoVWolP^%f*j;3WVFqAh?q=0f4rxkV@dAV8&D#Mnq3pcIU<@<6Tjhi|(7cS<|@ zL$^Vm^#pjuKl!=ahern9t3aR##h%^c&U|+wfivfqp+KQPAPfoYv9VB(6)->_ zKf3^~;6Mi`r11fS0wgFD(Z>oDN)arsw6M}>JEw5(t`P)*4-krCq44T|{+^G%eW!o- zR?4%Q0OJ$Gzga34Z%~9F2m|cdH3mhr_Rh?$pwW&25DhvA1Yk_QCWOo!r`-Z@YIX%t zBA@_JMzL>dR7Vqppj;}x_x1OG^u;^+!?#eLwFG#@KY7ph*K5@`DncPaaoxUMV2l9( z8tn*Y=GU}o2hEs#3PorNy0R7_^#v56kdolU%rc||KnRM(5K|LFI!VM>P%DQYfBnyX zVsu9vV2k8gNr3PC!FNv72de*#Fb|3lOid1>Qr5A%kigN?ivWmA@JIlXSfJsogbfER zF%(jAv?7jEbE}3p1jB-S9pM1!`p+Q!^cOFMQ% z11_wWZ@hj5of}X#3&o5Pq?*U;@AlScq9AP1VA_TDzIC6GLn|jmdTTQk(0AZs5P)MRH z1Jb0B{X6Q8K{R=e&8+A_BSH`cKK4xx0fYjSkz&#N(2u|OGy8YCZ7x@yH3Yc(hkvnL ztJFTmnC~l44A!f9pob7Z5j=Bh39(SA!)N<+M*@~11%Xl&!jgs9@d4uU0;y#nh`7SB zbE}RR1GN&yhpSMapp*?zpZwt~-~7MBoo=Jck!KA7N~Pj&28HlCMJc?%$94NAoM%6? z*uY9N(m)dmNto7jLNX*s&Al+-E0wVbnQ$?^o;ZhbwUuCIu>oCA1Wb+9Q7sps2m$rj z^Cl_4_8^dki2H%}#>jGpo*gyNxgnKFOMO z`<}4?S-{y|VgYVAg9X=4|A_HqNr1J$Bq|$$H zeF3Qmj-6XYlt_ROc%FyrriUDXnD77k55MPuJ9fIwE=8_s0=)P~e~kuV@L}q+0YxZE z#Uc(&k7^Aj6erHDqS@ibN`;9JSswwNP=f( z)*#HNQMDLi@5BH=CZt56`A7_MNom0)4D1wz^Wke5FK;QMx{)hr!WeGcGlG3nLm)&~^E~v_d32%}QM-dAY9nd4Aw&X7h^zM1;On`71cnf% z9Gnm!2nz_yB@{~)l&TeYe(w}P+dj%v5J~7AA%Xo9HH_3kNCj{q@yOA6v?2~E1Y*wr z&BxyPeZP2>o9&Y2nk2wW-uV71q4Z(qc?TF{7^;=<{F|r1C<6h(-#m2&k3Vq~&GmI8 zT!0G+0zGiUC;_7sj8POqAA{8r7^991gb?Tu-yZAlB!r?;ucJOPib5$r>@T(A15l=v zOqp{MH}4%rF<_7Yn(YJ+A6o$D5>g1kx%losc-QxS@v1i7CCar)fH(cl?-N4)F=Y(C z=ix=S??o~65hpR8ID8Uc{_5kK{wzWWs^tO(szp>vp#vV!7QtRaGS?SMWekjsqFNt- zP8KOZ0a7V#31D8Og6~n>x^D=C5-6!Kx6;DlGpmqNf^#_=w{XX&e&KJQ+u|nLGP&jm zaL+&cz&`?^9|oZyjN*9*Coozops}`w<)vl(>4Q&z3wc?e9rzwbhbkDTmRuK+1zzuH zQx7P3et_|*X;cOV4e)?cQg;PPIZIG42e@I^fIbI>BWG7Jv)F`?3Y?1v=T_VIeCd6! z5nIqaTQ1jB6mZW!|8M(2z4wE9Ak4$qKm}!*VDbDpbeau({jn351iqg1LEte8&!Yeo z7M2@WSY8LE(Eb6@U50+u#~vkMghKKJGbfJY?6ITZNen{Z{0IH2eT&sbjG3hdK(#Y@ zV6uj4DbU>xqj!%C6yCROjdq1{%~*hYe&T~32>mi4^gBQ)7$LZ2A^=rdah^Ici^rZh zd(qFy_Vrf73DrJ+R^v;>0F$G2FiKND09h-bV7hq|4G;`XPGMkVRJ#LGK{$UvK}m%h zcMqak^dX_pi8#J-WC3x)!37XW{@Oo!*Y|#6i<|9=<(jbognI8L)cX!F51n=gyNZzm ze`URe$B)ch@b4KV@H`6NW4SHx83Oa_dsH6>9=MR0U08#V(ybUmw8kR4iU?ywPb-7M z;>;PGdipSuD2B2QB+=wjgy5N(RU}-3fFKM!+&HCu2toj#u>bn%_x#C?+uo3uDA$Aq zcj$442zWF1AVBUv88C^U-gLwHoZaFyV-v8#)XZwQBC~;aI z1`J`~p%5?>0#9$DhcNIE2C3%fEP}@n_#QluVRgL&0;T)@Oc?3*DFr3wm{AZVasJpd zSY6VUM=+KEgkZg$;N)CG-y|W}Gg`q=CDh3#N^bE8{lHae*iFig6M#_mJ4%t==T98R z+WaiO{kHusOgq20hGVDabJtT!5Cjax&_}7@qg)D5E`}%<1C&eVwHTmW2v92cC>8W^ zv7oOn1Ret4ZkiCxBWSiFXZEQj0;8ZzTM}kosX6a4f~7Mjv2<=mTMcJj2+l3Hu(%%C z6eDifJ&1zu!PFo8!&klgv;X31xAR7_qg}wue(K|IkidUE`^;fP%?572aT?d{83O=B z3CHIjd;*=A>l-CN;4yecdp)0N@18NOxr~@Z5Y1%)b>4?K$IgPHY)qf|`L#*`#n1=f z@;;E}0x5OaC!`4jrGgL=TuON58g}hFpv%gnf|L@35ZtnF2nC-)N`sHerg+N6%&*Ptp$sXhIHs$MXm+Lt-A(85!vmm#Et5aLuI#GRl1r5{+l zT5aA>c7y;gd+P%Mq3kP5XHVa`cDFRPt z1_PhM^O)9t6XQFRZ5_gl!$(Z{7qM0V*>tXgP+DuH&?Kl;i_ZCz(pUf!>PZ8zkkSAt zA%%pH3WfSGhIZ{X1W}MmVyIHU^}A~NoKkr5)H2R4G$Dk5kn&HsP=Dw1zx@3>W`=iU zG>}sE3+qb@cP^eg0|0pb&3hgAjaGz54xa&|#P!vMfWbH6TVQ~medNx(n} zbYE@=Af-aH6C;W_xDeo6f(vcReWuCedlbIUz$k^3629j_DFs5b#UM&S5d}gOAfy>R z)QKQL00@Ql>H?NMANA335USr;SnHr%@Gw4D0)fH}Qv+CSL|AD`D3HHR2!0O0&+h~d zzz$e|zx9qk{H{)O{r{YL@-d>6!oFR@c>XPW0cfrK;@6(Sdb5MTV+cb{fY1OR2A($k zK85djPWyevoTFyDfKJ4*+UTI!j?j)0m(VE`ix?gmMx|N-VIG+0fifTMC`PN<24swo zDwLQ2q|kjoOsHqO0I5<7V0-}~6u8vnDvwQ}IyjPA0x5CpzG0LL9;B3Lb~wKJ)OkdS zgp^83sb2Z{Uw-xfxvD@IxvB*CuAlzHK_174k3as%m`Dx@#Yw<0XBww&{*ltNf2V9(xZjEs%I4~vKr ziFT~P1B}CGd;{AWJ|o~!8>F)am9)`mcU<4^NFWrrk|_a%fDjU$gkxy;btsgpkW#8HXb>)sNa`rUP|?CU-*^pzi_r|8|12nfl~0doqqa>F_9zyfEV1h z&w;^YkfBUD|)x-n1iax-- zKlKOI^CynIJ!VK`7@_Jvc9tH1HfRdgq!wHxc448d>ZXIc6s_R^by!>p6??J zS!xLa-(~o?5I8ftf+*oG(^o2nc>eQm!!5U5k1+7C*yLF2NKmJ#nT^_PA92StV-a-D zKs%C@076kD!5~KW97MHNbykD2bj%?8IjXacwZ(ZCff7P+X1UdG*b--TV1dOhIts|lh+YrSVdqM40lQoS@y{o0J5 zN%5IcLaO%(!Fobb7}<@X(GmUG<}*BIUkQBkIqk(6#GQ5~Bn6&2y^5IYDyCBCXyP+MeHNF6U2jB(Se`Sy?j` zzzrm|IlxP!7@eBdSxGM)FCb=Q(T1qa`5Dm6V3d#wPo7@Z^9=!v4HPjwTmb+OkO_eN z{?)(*xC#WAzV{u)_0^Rhu*WxMO#fp?&O!)@(B$NO^P1g!*Z*rP5GN8#t1S}_TC-ok zU3WbX)k+Z_qnKTdAr*1p_L%-I^KUbVHk;^%gKg=U9Z|IZOuAW0f)=e{e0s0eeB&V6 zOr;G|DeoX|HgZ;At(oBD?7ACEx?y?%m7;I!6~FssZ++mk+l>uy6$lU(i$5bc4*+0l zd;rx_0RYhI#5i?s8I+o=p2rY))FotcqjpRm6QUP=a`C?j3GcY$X4GqC_>5wHog)%3 zCf{ydrr(->mIB|c`B`!R*#SS|JV#}^h*eR*=+v~11kAm8T_lKC&pRt%{fblb4XiX` zfDlkdaO3nqT9g9(`;dx$9 ziq|uQ@EF6wa#NRVdFgn-zI{^|8LGo)6zxQ!5vdHQY5&}<{Z8v$zn`fka9bJ{5}Inh zt2ZF}JP5F^$`~Ay zm=K~00OP|o4AykHb(C3##ueUl#xG)6K4O=M}al`fdoVL%c zi<~po?U}PI8LWn9_OsgGLx>FE)R6#Y(|vn~)lnZA%X~jgYZ4hnw6^Gc3Nt!^C=ob4 z-*BPg_0t2X&jI}OOMmiXySEddZ6yHnyq~c9H||dz+oPxEAf(je>ZXBQ?Y=qoGg{6l z#d@>juDAL7n-3k-Swcp!(h{cMPm?+x?FKj71<*cV(;m&wRu`qCj7E#IgHGDMBPk4` zQmZ+CAh)RmGHzw+2Q(Q@%{S0V1VDgt!NZ=>3QVU{0YCsMjkP9~%Z^y73pA!fBcVzZ(EYv=rd( ze!pAeQ*EbRnx82?&5S$>38=~#3SpS~AsOofG1Y-o#}@&_atK>Y}HZL z!Hc~Lf^sdBb1=yzrI57rLK7yn0Lz18Gi$E-b`4idGKv7eYhL;<|NA7iLAHee)Azn( zFO<3qfCC=@&d#o&6X}5ytp-vsLeXv~spoA{tb3-%byq+MBB68jdFL$l@ zpTMIS8+3CNeE@IT4y?Az0yy)ZBf#8pdIfZ z_!u0hIn8b-Nc-AMMz6Qe&t&|%$d>l?+554xj5gBz3+=-JD*YO05T@Tr2$DFqt&npD z0pQ$n3*6Kx3|2x^3J&uL06)0ZKy5hzrtf_R18^q*jE~e@I(2So-OcNB8Uv(d_2%!> zv@6hwMXpgPAs87N&|niiY|FDL-+rTf8S~%U*VA5aKNI4)>1A1G4;KL6nSYXV@{(}S zB$7;Kk$U;xoM}HL70xX+)Ak(8S%H7Hbx3L14hwJyKnVaQMpE-{D|`Xu8dT5OL#BaJ zFa#Gl@F^u28Lm5^Vqv`NY;vbx=+~v~&LU*vd)h^Wt}w6P>>`qJ9c>)C9udhtu%^nopXu;q3T;Q4mPX8r(Reret9=e*y{kZX6F zBtpQwYb;5rQ~)#PITl&%&Sc(bR}#q93cAwq4KjUM67*<)ud`=Oy#qe?+w#9hmWDwH z(25mkA*a#QL`v0}sW}gBgHefZEb}fo&rIr3AII9{Axx_D=6I11f1VDRZ0- zm~bv#!&)g73r;H)LEik+H16JLtiK!ltR=`gdD&2JqkcY}XP7tnPKf&tEGVGOY6!;n z!3!?-w0ec5^^U7i9Ih4IJVgMn*c=R-lWikFy;5}Gt*rN%3rUDuZDS+J`3%qvTq=gn zqyuT@uRG`8Z>-;W_dOneAJdP1fajgOoF(Y`ZN0x2@M|MrthWl!tw58Jg|$>+hHJ&_ z1=lVCj05;>05E9k{QzKPt(O4GtfrwmY+a06zOKWY{B15$z(lKbY=Ou6aPd9Di zqc}*!GWfowo2{2@L!XQmrb>bvm|^?W-8m78@Vp6J%-?gpe*T?|BrE#N-l4 z=JSm;QHDT@fEcnVyTlOHB(OkI0zLK10dAeTJm{(D_bw#8@2V`noyE&tVVR(mo7@5g z6hoFJ$iWMK@g>N%5x`Ef27pe)doI)_UFuEd382g|A;itXm1b6M!nqrd_o!aJwO9Lz zYe=PtTiy*4P*&>!fatO=qEx28SDF4?b@lZZ2R-}ydiET+nRha?l%0iFw%ro<8O6X) z$)Z#`jrs~@t9<}RfWX(LD@r)Jr=KYcwn(RDOJQf!Dn|;+togR&0Fp$w^sLV=T`y>fY(<@2#I>FHxC4$U24Xh{M{5V(1}V&_L~b@sIx6 zmdqmB76OF6=R&}g#i`ieVZrA@TLLbnn@cC9X1$%3truoch~n4*mapvTb^2H~}dRR|bt*8rt_K<&8%TsD-g zCcq#7cwS0QvBA22R*cM3Yl&~2osfACN^O#z7nZFiKpRNc)dA8RdAG=n1>iz} z3$x~q(1hSzcLk-;Hx@v@x3s*DMzaG#DSWDODc+t!kM8WAP3~mk`>gis8{XtE&4TMr zsjLF0TJM0(o=;w?*|dgDdpocd@|$TCm1;Oxnhu4s;9DO-$;(?Xxl6W%09gw_FVM2e zth~jAK*9wQV*z4r2_V4;LAe+>KY&Y#qbFuegA*{4Z-;)PdRb@B+IwgA`;0i@R>B54iS!ms>zD z*zO&b`Kyd}r<&ho1(4z?GujWOm!h{KM2`<}?OFgKC89(pf5N~+_o56|TsI)4BS4Y} zBwQjfU?)O=0H~Hj-Bn24Rs8u^AJspAD0t{?i}Ya-nS6dfRaeMqb=vD@-|MHveZb5p zXJ4>$fcIW!8%1{^Qco6D3V=kT*~xVUN~vXRfozWt0HE25+(|nfyq`$p-6vckPBaOk zgd>hQ;)Ekkv{k583Q!8#5=ey;r)F{D^c)m`fb`}XfDN3yj963bjx&9+eLt(kS%)sK z`57>=5#Y*c`NqVN*#lH90TgsSg?mqOD0%E6SZqqRngEWK>#cMEsC*Fu*cG?(RzL%9 zErC=DzzXf^Z<$+*m*DBK31mp{S$bheSG}EkG-=$>n~BZngCD0v~8;$yOS5P06^XfL>BzS zY_YZoNh07eg5iO(a~jXhF5%Dq;$Z*;K1q;=bz}Ukr=8Ve^Z}{IRA0!8?_<(s<~n<8 zx;s*7Nb1HMlm>&C2i6lE0s}fn(r(A5E0R!39ljJ$E+kt@fHPnGl@$O-0OU%*vbBNz z#ICz5gmh-#f*vOvaV*^VLg=AdE|`!};-N>L#>0;v1tma8`j0v~lb$u(JzCtSf4)FJ zFLNO0uiXf2doF#?jxCtXBqXTPL>Q?SK#VzBZ$-{B7%TDArDC&@Y{x2LUjuOc!b&>9 z!Oji1kO(P3B?0FIojB3c#28O&_gFe_V4zw6B?OID1Q6g)zw{7>2ddaTH3q3T68p2d z`eB7rVED*&BqDVV~Q6dn<939ggM6p1_a1s30F}U)W2x=34f-#y$*6_PN%X8I{#)mD6xyYYGhd)P|*c z-6>Oul3EK2xTR$QU~X|8dMw-CnEc#Ufwdh3a7&!;_+*j}y zUwsTAVleQ&fLjn{mKd@NY}p06v>LCifi>YkS}8=7)qI0mZ3JX|m5>I63urQnuf??fD( zSX+W9<|z>(a~$jUIhPn7C}VuM0;Pa2fAvv(@_&9Aacd18(Il{Shq=n5ZVm5Cf|MjM zU5VCWmGB}Lm1p%W))$1xbAm>R30 zRxaSk@w52Q|M(L;@%UjBs16Zr>Dk3ekg*WbYP?3xdgd1R^u^9zQb>CP)kOl8sVXKJ z%OIWKvVWqky9A=OztxG1=~qxnoLqbAvriL3e6rCf+a=0YS^&>%e(U^W57=(th*=N< z0QOCdTySh4E9R-2;6kEpNYH9q0wid65_BStb`%?8aI_=sAXdsD_Dl?-S_<)(UwaG> z{LvTCT$)3`1eBCHBG?>+J66g)9mJYm^U&}ONMQT>T&zpQ-$k5TNO z9@+HgS`s8YB|*ExHTW?{+gOH9#}K0vrxqhl5CjanMyl|Lz~{d36@2bfM^6`!B}!@`tUsZ&_7er76y}!J(QKLgzm!mtFSlm? z`XlXj+qJW4_bZYs{s2M>6Z7ZTNF0HAd2Z(_;eT8PQv3X~L(O5*tG`AqUhLdA{W zYcHRU<2bf$tNy-AXtW|!N+F72 zfLpJd!Z)8jv+3^YmE%2Y}LC?S}g znZb$UCowcUgo&vs6sm(745jBKXb$8p0Nno>G*uefrR}0$DYPGoh; zv!+DU_+zh7`w;+m`0#1>RIuyzjN;7fGL}}G8+>f9XopQe#R^hL-IpsJYJ5065<|3qvAG>9)1#G7@{&Xipt;!DD|L}G(g+5VV(pq z`!-vbjsJ5~2MBWW5DcmyW&?nm_6#BLD3lavwmW#{)cKtD^Um5Q*5^+&I-L&95I|kE?1O`iL{UULosMmH3%n)5WyzIT0O#*ywj2SPXC69Vp1AEper5Dc z0PxMH&R}AshGH0?S}NdqH%#N2F#i@C^ZzxDwHmk6W;_dFy*BJd=*N8xh`j|sD? zm~hV)%B~o~?P_4mJh*`;kYWW(>&uXYf`vsCDs>bpHT^R!3yke2HaSF;d0JT44QIki z-M2_UN{rSEIIwF#&ry>K$4<{tai>nQsoLPia63TRONps<2 zOUM4|@gzw|9LHpReH|BR@2g6HO^N8N#_h!;zfl^y>6ZZD{K6WJotejhX+4qnmg{zb z3yCAA<}Ua-8-BjjlPI|`HH5y0M0g0eFprkwX0>q&#uykOU`&{&jp^qGQ=+GqyGmkE zP@SZQo=lh~eBxvkt@TyN*|VUY53f*0p$K10BS20szGl|+1x zfN>8V6JUw80wljGD6s=McAcWMiibc!C;%~sY__mGKL??Jpin}gTtTr~17*H>BB{+d zN+=;PSPgLfuDb3$gur^UgKr!@17Rk^=qH#ajpd_%_3z?NlXp5D6-ALs^pn;$YU9h6 ztssEf%(i~w!QZG~|6L!n$&0T)b^;!wZXwA#Z{7n!367sxxcnPYdSQi_$M6%1*kkZ{ z=J^9mKae6}48ErqoAAsEo-L_J2;0mSC>8xOBVF^+x1f>$n z^+6OWH3&tZq{N=F3Z{ljI^iS)*4r^2K70m(b4Y3WeZku+$G`sWwewG&isM*v&gI(L znk{|aXhY`plH{@<9GS`f`J3H_Z+p$(Ees!gi~S5r3GV*3gBZ^Y6`!43!9$OqM8f;j z_+5rDO3X6{m?MDaGx#)pCV^eG+;#!B@Ptx5&D@4}b`c4sbZ4tF14vS){k$D#unS;R z2M2N2Z3i(tSb-8c9lhFU;gP4#pwo#UIfvj0B#+~j6A!(2?#P4R;G9RTRx4^W8WHC_ z29R8AQ%YTOqg@FBZ1QK*1OVWV-uaK;5Y#4rI0cB}?mG@*EK@bq>csf^V<)h%oK_WG zsaU5^KbFQmc2+-m-1E|B58D+>VWI?^!6SsYwFF%KK-YJ;S^}B&{X_LK4jr6C=zE}y zp-?JesS)Auu~{TMflLxe!NGYfR!=_ss|!cI{9vckiIOCVS65d%n*|?0d^yRWDu*ZYr5l3WtxM$Km7WFDX}WA+e!f`owWFr_j^R zVRmw9Zh=Zh0F}-yly*44%{@RmegNGq6apVN>>I<#VAT;rNQp!HIKXS)E zd|gIR~i^TBg{Pge* zd;xE>`{iA?AFS@Z`=7F~a$ELuCr9fzbYQZlq&(&VXU{KVW`0Fi9Bs0DaMQ*!8t%+} z_f+zprQ=cyVt%vx+|j`brYDCmGEf1K9ZCjf7uWFEGiT9kN9mBWc?}ppyz=M+zsTcO zE720Kt*u2u2;1jp0pDmpQA$O)0@;$s$M@+9`t5h0%bg#%;d@?E96$6r%7W4CXHSgO zaOgn#py{k|Au+eOhI8|)I5)rQ<|FpGzx?truAhe^fHm!QHPP&hV(SY4fbV%29xP*I zpn}n%3PL~423p{k*PD3i_$=lY*PKsatBnXH32UDEz11Us_Gg^)IIH=b^NqmXhya&u z`d!;>1pxp@f{j4$_u9ShQNL6gy7^T<7z|8*KOr{tq4gXT#<)K+aotk3_!UUpi&G`DupN) z1GjE)-+Y?_n(Y`T&n@88xn(2?&&(MxwFOea_i?&&>MwsYT0DA&a~>O$pBR(h5BQDt z?dz6+4{R|3fS&H)VqkZl&s7ywrti9;IDY8metG0YgnB)7<$=#IF;d5_u>p(?RX40R zOhYyeWG>YU_#itrVhKDI)NgGwI#V9Lak1W-!MZ6{!@O?HH|gg!N_z^D7N3ueKcEKpu3Z z0OqrZBRO2IvG)IFEb%M1&fJU|q4G}{qcoj6^p zwVz~2KpBe}m8bd2vCp|v0ib?)v|ieE`>lRycz<0E3kS#guSBL0|B3PkbERNo|FQ`rIFV!b*^a|Aj zLeX)l5Ol}&+3?O1ftU#%e&9>r_aer~Duq~3ygeh@E5|zvPd^?n9-otX;XY|VcUv6C zTnND%jRxnOXTjcx0R60ijq$e~oU29v_l0@?V6(t)3VfucQr)iv0HJ=-4@>pX3rhtr zDEiC`1LhY3<_7`w!hkZ*N77iLo%uN#HzK5^*9w3ngeW0|eHBtlA%u`Z3-5F~f^#l1 zz)SQJ;6g;WYQS5$>I86Kth2ySrB!Y8p!L)^%+2f5!kTvz&(#Ky+c8mb-2GZ){ED?J8du$E(wreK< z;CUV~8|*R6C~qI|73W-X&SjD$GD#9!q#?VuZW448z#hlYqj)X6jD;Y9myMs#s zKMQ;}3DC8Vu6+dnzVBNSs3b|cD{o}izQpE9;2gZIO5$vVTvG(-7Sm1ivj!Iee^c7u zXJ7T%->3wu8~A=Cuz*Vda;G(YBe|vt&@EjS;zC`(W(crBa<}JoGbHHNcy+A;-Yw56 z0&FB1Vq~pBmJGm!&A*jyE085Zw}$KCglBmJJ|_sUS?RN=f<5kOgl9Xjo0V@V0{nmJ amH!8JLiw(f=*ETs0000UYaMD9{L?&?lf?p~&@mVlR+7mMu|dpC1aXG<0*SL>`ZL1F-a0CJLI8s6C_9bVa% zx|*!7hJJZ&=HdGMp4kPoY!ZoVY}oLDC6lH931Ynn;Yvw~N#Y^}O6tSHFq~gX%#{9k zknE<2LKL|TzG4G7HU_)Hax-#sbI;1rQ?@(qSKRIbgHHU{^_kpT6xPG;c3$yE3owoo)-VsW`Wl@dTU~EgXKH zhlcZ!QBbn=+x)26zc+sAe|zn%>a4LheE*zir*!S%Ebs8JI*`#E_IewuX3nl7=z2(- ztkU83#c;SD2Kt#v)yF1(za239&OH+RUZ^DOOcLs37VX1@Fc*uWm*%ys>9e(SFN#Uhq|m-hF9 ztcZLu*kAN!-goV(-1y(izSHfC(bxZV`;j$h_1u5p!sJ{T6AKZ*DkyCWDR?zH$srNL zbx9A%Zt(t5b6lM#@PP0~KVW@6^MB0`-v67(LZFF2SR3G{1putAD05eTM9DBSivH?o zQ#?$!T~hgQ@1Hd~|FV|P{LvgQPmR_8*B7Mi{^VDM0RyVQF@{1s8f6UM419iDMeJrz zW(-_9a!ecJuC>tpVoRre#rd1wH&Vc%{@;Op(EdKcv#*hC5gI=h{FCp*s$reR;6Lez zCzYyH5780P*opP&+i8*K&t3Nc`cIhii6?*w&aR*F<#9`F0(v{C-XoG|8KJrRPh&hb zvYuzB`3KA`aac+;^nRQCn)8mDz-zzi^?HQX2nz%t>toC6W9IL(%gzLRWS7Y=AAj#W zBo00NjMH13WsT?aOu$%X!ZHt|+Y<`tm{soFp+-Q_zhs*K_TJ_9oz$$m_R_g!kM4CI z4h@Oc1NU@*OP|?7T!$DINO8q7r-E6Mpo3}*_kFlq6S#aGem~dz|DEd&bN`h)Xs7sVa%#L=LL=isBQg`0Duh*3xjpGv%(rJ3U6wnvD!-&WGJe9T{r-c z!*z0cq4{$!%QZgRZFxSedB=K5q<}%uO1t$*b+vpeIZ&h#BP=Wc<%`$Uak0e-aIyIUUD4$jVF<;(4PoHLz{Mt@g%H9- z1xFRA&?$J{CyV?FPw<@`dT&mp^Y(fDYUgsYrv+5oF0w^}V;kQ7 zLM?Wbt&)jE@`)XVLc>(Ov!xUTCP0XRjt1Tg)o}L;#proX4 zNm3I~gee1u^B@oS(M0wT*&Zt=0(M?S04 zrvXz?Q`DyFqYp+esrP};bAq?C^q+vAz+#K_&Hb})TlIa}_=G5H`D{IdsC;XdT+E@m ze5)qg$#3v2l`PT-Yn7>~kK#0B!R`GHg7*c;fMB;V`;OVJhfChq%bnf8y$a3uppkp! z$okTEPD=LUB}&c8@^QGBvR@h@CB%L=4V#Zg2!*1k`3#oguU;ThNa8O+ETwEC(Zb0o zZPndEy5H5&6qxJ*M&h_qF989wT>*Dr*MGO{p&1yy!|*!s9h9R7I}6=@{M+(n9%Mn| zXR7Ja=zCtSo>&#PlKMf#56}`3Y)3LN=*2@cf04L2v5aV#U2$wufsuz1DPfv?c&Xs^ zUxZhi&qlvBX+8mbGOed$c?BJ91HuMnT2GZJ2&VoHh{a};x`BZGv0`?%uG-M~B=I(q zs6uFWj*NW%;*j&d9YNq~2>b_z`^MeJFf#fb)=!h#5PCVpQkIC9D@U~Mu-Xv9vlBssX6RybP49b^Ye2dhS4jpo5%k(ED_oya#H1S z&gm`{!nQCUx0YKi%Ku#}qHx-chc9*R=o7L9LyrQ4DZfERUl@XBraWk{BH>@`6yobMKh1kS%YFtu1`$jeoU#xiKvKO4f0J;=pl?;m8<1($arBZn7AbL#pIeC;8O6aT%y>i>Kqe+S{WQfptxn@D-tw z0K!Vf*A(UZt=#^|jbMw=uOH#WLkuqee$|m#qG+M ztA}rhv#v-uQ~@z}-YI&K38OkgFl@uKikJ?9!Eyml`4^164*P(5|5pMvB$A9=nUX`C z{4a&26=9pfUyeOQQNzXEpGW5t-p+Odt+n5CGK6;g@ilb(sOGk_uZS4LZT9EZZ=jLZ zx##Fo{i}{sk*U?d-mT$)am58bBFi=vn0P1ibNVk3Or|uY^G)x66_8LndI4b_39n*@ z7EqX7*~L@2R|Gp@cgonhc@rDu_J!kUg(nVFwa<0m zjMQssz@dV4lueQi7OriOC5^K3n;o#2q7n<`AiuZlU`h$J98c>nNuFt*u=O%=qz26Az)fXAo3|WL^;!woKJ|O`UmSedUoe zNV_-Qf$kG9;W~iPacbGIy2_ejq-akfwa{fx7{~;PzF3%PKidGeDcdkIN0p^_H#`CA zspPezbOzUmr7J97dku=y5n%tkkqgy9waV41`#)$f!krLTFY1W$<^^u;$ZjjM-nX@X zdj~)8CFpSZ&O@_p{SQ@&7T1u^%bKcWoqYlZrWL_FdqZQjy^mr4Kq(6ZpRU0c#v&y* zRvZSO4rGJ+!=R0VK$)#kZDTN6YG0R2lbHBG+E|(T=eimDYY(~4^MH{@hVdaD)x03^ zMa|~+!-pe@s#37XPlsj>5@F#|yxc{?p62`UQl3)*l%Yk`1f=ZQ z^yun9(YIJywE@n3YD66)CsR95v_Jze(h2FYL>Z>fp{jT*=Gz$~`s?U)%NetiaqAvP zC1%_Bv|KBfUJNrcDD5-cNe&?t&B{Y-uVBDB=_uC#5F8tYaGg5K6Yt%Ica3j9^T1ei zgcj%tryJ}IdLu!dT?5fjD$tfQbiQRenY`^lPc>@jRijf53t0soE|+!#yOs^BZ2|x7 zpy#T6t@2&oMOC8rrV;BS&swB) zMY4)SJdlxTI_;h*r~+4}`8x^cvrQ3^fLYyae=^Z43qiz6@VqBDsA)cPbW(1$ z0$s$tosu@m7vF%4qKL3sg1672K^lz3(A3H*TJMvaZ~rkRZ@Nj{K6S^W-Z?ZZ}?>L?RDLRI=wx#7>#*#^di88aQk?GZqCRlr$Y6tLkeShLQ zR4sVyDrSL|{d?jQqQv&N(360nAU((mvuF3cBZ)dbSFg{GD*O@^B2E$GyeipsYw!)R z*S5BMD?#|Ji9D+LfYaeaYYA|XP41&jA{CAyovkfaE`t|k?wybD=Vg@#JrUB#5iK#W z*YumQ7PBrf!Cv01sy%I0OdoRuf|v@gD|`<6eeUEyp9mu1ug$Q3zJ(XZ<^V91nF~_n zYQSg*RjIZ1KV+;E04T-QKO!6nq4Twv^V*!9%z8lb->D156FeLIU%s%9$=iqso)~R0 z^lB10Bmz8j3fkKBT;{$xe__tcb)nP^7<&)zVJ^0K^SWyzS>>4ZN_%<_j*zbD!hgYU_5 z#R^l(PQ}He9I8@ES^_SZ*FDzvI!+m7s*wP}`++siLB6)M2AdJO&Ld53=rQf{Y#45@ zC@j}^RAZlmk73C+)$^*x#!}{vc!M0k#CQGgr6&}KBdDyg>=h4emLmctJ))9PC~(OY zs|V$?DrZLm+YPWV=knVGMZ_iq|Ks-`(NB&foQm@qCuayt>~HbUggCw`$fDs#GbxhG z>mMt(9dF!Mec7__qTE@^7wvsZj!GzdZHW2grTKY*cMU;qga!{3;fJAY?t27_Z*4s( zuQwPLh6%Q%SyK8SMG_uG70(|NPH4DGi42D>8^SgX0kJVjeJkYqh(`GFn-z{+L-cMq ze{UbQU1;dR(J9RKDFrc>`xtrcjx57OnHV9`N01zLWNDi7@LFK6fl%Smf_iu7q>&&; zhs7hV`6MW&A_#pW^H{=p)(^V>+WY2@$L@<^jvDKNN#)#sS*hG5>-Z~5zD^P=!MuOGvl2#`myKgx1dr^=74pVyN!B{F*CvcmWT3X6Y#M4 zq^QzY1)|EvEvElUjifj6eSsVFBGxW46sI@%Hk;KQlZ;z?ct)bsueE zA5}2Qat4q4Z-Odkv(k2q7>dCW2&kBY{aAp2FVHXu=+xzU!W8XJND|Jxq(tnz;hh&0a9QG62LO85fpKn*BIzOn78 zLV;W2c$Fsg{){BZ)dWjXj249y7JNoYgV3fRfrN>ORr<-!Fns;z zk2KftK~0(9-20>ZxR3)QFj`M@mgxbb+)0&G;uPG^I4hLA*WWL8|NXkg#oxK_aHngZ zq5XXdoIIHr^V+Riwv9%Kl(rX&(<~nHb-t3X2D}hvX2b3$p(J`R%#4WI9)rUqowTtK zXzW0a*y3Aj1ADcNnWFLqU`q;%%&D)!o)^o)E^Dm&$lofEfpNdvJe>C`T~ucuU01Sb zz$RC&j|rhFOVvZo+a;pXD0ANR<1#~AyT`+`yY2%Ns_xz(JfSaD)nXaF$qGs6rDol8 zf72~cPCMN|6E}*`kCgBkpADTYqA|tnC@Io;T2eXdO62O{&`m2)!iCTJj=)9AW5m_r z?$9;$HT^1;xb^gNg$Gkrb%nY6ZYC(HD?0&egoF23ZDVHg58u;;1m@Mo7?O&W-rYF(sFOS35{TrC0ir3Vuw75P`5(QW#38 zHJ_!Uqw6(FBM4zb6O<-~#JU^iz77kqu|>=+A(Q!S1QyAGf!uovCqtnuJFe2m^{Goy zr()=_v>%tkM?%oIgTIG%w-`LteEVT)lBB^SM|dq&3U_X zGQTnr+bifast|*;SI6I~lHdG82&CC$BiLF6fBmftvIzX~jgBf@voZDPz+w=vZ+XgT z4?P#+vldO=7!18H=xna8>{|alhQgy~8xdK8PvM3kEUeO_Qssa#l`<>xPpIq8OQeoy z-LCDgCJ11A6^W|*&_kF?u;+3$Dbr1~nbC5Fc6|(-`B@NuWxS0qy)0A0WyhtAHzO&~ z*`ULtOh(3v=|hxyY0-#PnF#s{7ylkrDfXhMA{M;C7`S$Ah2nFcog^>g-^le-o%E9I2HTivZQpBmeM!GYKN# z+is4{199}!*xo@rm}{%h?if155(mRX9o@kJf2f+Z2&YJ`hlEV)BVMaR9OW3OYxR^b z$Rn+jO2pxM%RL`uu_mX_1s4|aH?-7H!e)M+3-Lt#{9(z z?K#E}jcvqx_zj#nHE5S7grV&3ZcMR?#dNbaZEfzfaJ>|GXdKOMvb?)K=Sex--SD`= zhAoZC`540oZ#SEdA-ZrUvvemy6IzJ7Uc@Ce(MgK~?#m5~JzKL+AP`0jEBE{9aSq6{ z&yO<9L@STZyR2_t|001vARLcnrI&A4h6os2zdtBAQni|aoCn5N`$SL3#oPz}-Qeyi zlOW{V@pc{FWvRygiKu=HJjWstX)>92+)Y7OLnAd+xb*1HzNIbUWC^CdI0oFA%k$1Y zg3BhWNXBW3E1`8Ool;(E43Y3SKoCI;}$@jl~j4W=^UQR14<7A^I{*p_4(fAH-aRH?ZWO-}+9 zS7%ReG>WyuGgEtzeEJt99^(^k;aVixk+C^>C?!NjpC3SQmvEckhTZeT4OXMT2)yr6 z4w$C?_bW}YS{B23;8^5If%49Y@w#F1uMBBvSKvKcuKynk70-^Ojp@8DWBTM0MjFi9 z=_EllqFg#P&}GkbFQXc(+@n4U76 z+LA^<_Y0aL7DwZbntqV^CTD}{*&M3yO-fn%UqU~czO1H34|^5@;DdLsGd2EebnE-TI1?iicUjjsrtCJz9XZG zIaaG$e266uJmd3!9~KsT2PQzB3^5)Ks(^@sXd0qhpzmV^7FRD)KuoriASBLftP`av zE~mynqHH-K6)h$#)3XTZXx)W>y}qa{(TuQ)9{-B!<-ga``3beM8q&Ju8^+!rSkv0< zx;pa*LvPnQ_99mQ9&QK&z#tWhYwgoiL?A@QlE9Uce=NMq7Mibv+AdhZn4bOYl9Ty% z2M_(Mzn3M1iCD!njZBqTLc~EoX@_JP$M1t1OCOFyg569Hj`~PQzgF#HaZxM;5<^BB zBMM7|AUPLMVwj$Vvt!B$g(66pyTD(c^r68!icuL?yVGwxQe{xD&pO;*&^78O9X z>V30Qe?+6ALu-D!UOi)mS zH7)EtaH$*748!^k+~-%!IhCnfmElaNN4s3-3X^iY)0wBEP2FsxYJG;HnwP2Ze|uiW zN!#&8?7%|_Z=ioo@>PY*d0R_qU8hwG#;wPukFp46@@QDM$jfu6P7SR|=3Vcv5QeR! zr{kvCIz0B`1m1IJ^SQIIp>DMX8=oLFxPU^VjUj3q z%mGrQo)NOngqNdGb(%@yA~w;5fm;NpF^e*m9mC=GgIKPqJZe}siJNt+Yv^Oq8|`tP zRZdTvxJCdOS(AAa0#7te(w~r|k3_F86ZBl)Mi8#cldm%J9;*n1oOM&oaCTyV?)S*% z#E12AeX|+qWSq@!*)o7Nz`GuZGt?)p@+yKs`e(9?U-^-$##0Lv%&8dKe)r6RZ(_1? z4gQr8y!DDq0@T*e^{#9Y6GvZ5^BB?xcfJxwL`44YI@ENVWbB&5UNU&ksrgRpI@!Qx z-+r*NhI|#6>KCYILSgvLv6`?5-(`f!#Dob|ut@wJTsSGu?!P)pmnvU<)(t<_J=$jf zL-T7nx~O&|a}D*w(4W#ItFdQ9`Yx7m|2>h;ZK<4!#g;MyEOx;BrapYS6ugT7b^7V! zm35Vut>14xVBN#tFhsta{I0WvL%}Xta9c@3k1__D@ns5$6{z3U%uLJT!*@NF$JU%| z8K@^jRQ|jk-xs#b!2q&wwQ9`?r0LO?G14OzyI>z2MZ2^r>kww-RNCGu2;}` z+WuTs6LDO<{8cYVQ60ymHP@^-a1WiAqh%)u`+B4#VyQ$8q(K=ycLYw|vr24KV}(y_ zo*MFjHI4Mf`mMrZIoqHB^}01T@oyQ$C6Z5;i>{^|_Y=nJI{Q}V?xUic)3RJqM+NWb1eoI z6_*}%X(~=7Pk43q(Z7c0e?!b&@nQqSCFtGUUs84KL2`8%O@{O4Cf+&&Y$GoUPjg(G z7sQj{^CmV`Bc=hCSkU>f%w!K^+ahlZ-$kBMo_pu*YT*aJIqx9jk(i+Sar$pUp5 z^`}DJajd@2Q%6&3302>2lSc zHu(AV_Sw;YM8ryW_JSiI8>PNj!$mxZNQaL)F?N6aL)J4}<33Y!r zdKzolOL>dvpmH4Q`nA?OPo7!NEP(O|nb!##bBx3*{I{YPh~JB-+RNVZGeZ5kqCC1T zjh$oo`R=@*Gxri#gOOU&Afp@SD-yAxYJbI2-n4T@Gx!s(p+UTOxztVh9YxeZc&d%+c0}|-j5#cR`{q3 z4WP^KFRnR`r9O;FfX}1Ht8Z0lNLCFw#*i$%%l&+UsAxGvtK}rtytLR&R7>jqU))*9lr3O-9{U{43z<4h$f`K}N z1dBVzQ?VHQl51qqhzpq3u}s81*B<{0h(bv^McCjycPtiUOcpuWlqlhGo4o(%~hz8v_Y&k+Sm8}N4CS*B`?q41*qCB2cE zB~I|-0Eww;Mssa#ilmFY`VKyuB3^SkQhb7UUOTWD_gz4$YtH>S#Et@%D$Tu0rZsc7 zP?;yhAuj8k5eG-dF$tgqb(-yGYQT;%`YY#W83PwLMs{D1U*Hmpz;2_I()UJ@XJ_S( z{iE%fU%Ubq_1ne_H!}lsTB++BH3?{UW)PWxy|lTDjim1o>A9FJtI@i3NA@e?PWEl0b}LMpAJh@* zZj3*8^(l6vN%FsR+)AUO-BmDQ(H0RoyOzyb-7w)Up`u~f70WoIiO})f+yDAfJ+9yvb3VxoF4>=FYnz#vS9|Nt-b@XEF%1>X zCkX3m-`*GYPnESfw$KNgyybNLdl~iZ7+=5ZrEOHtV_o;++Nj5xui|U1*)b~rp0xWM z9Jvt|<(8dkEXFBU)wfTUPbvNLu!Ppc!9liEHs!Y>YcmjrJVKx5XshnMm&v|=s1#rN zEJ2^SH`&b61N^@7@My<~*!?A{@u6?xBC&%uEhPjlr0kgOT$M{JiDwS{83Q9MGM@Fn z$tlH(Cg1sTDFcy7`<6qUtwo6M;-1~$-rr_eDDD>T%k8T9%fYAzrjLU!dc`psW+5bq z+QX?mV;>ZTn^&hWBJ(XXgi6BN0`$jswMa#L&-$57tQj z%TsxzsS?OL#ZsG1T+~E_L9ZPWcYg|oa*DfcT8IXXP)BLt z#6KsDRUG@It#hu#<;tJ@!liqR`xJOq&8h5PD^-6Ausw$G`uj1;-DP@E{>E_&cA|kj z6Y&RIX~6EatZw$hIIw+5FtYZ{pt3F2(JB8VWkN$;o@{Ah0r=f3(D`;FW`j!8)VcIH ztXJc}IWj=@@C|HTFdB3cK8$B+`>jc_jx=1?U>tM4e_~!6g)ZUr`b3rT8dbLeUDde0 z%s?W18fY$gp@ zPuMg&{NWmYK|^%-5XgGa0Adby>C>g^2V}EzjnUL1Qjysr%-H)Cd5xTGF|5OGCCyqX zz|>#1pR5NT9zaf|Gu8_@BMq-sS-V$=)kNr%vd!V;1+rg{iDCk94o**9&ig{3g6DgJ z66TwY!va3+QJ|H}MQ)~Q8qn+0uNcCzl+a{mXkObpRc$&2Z#lw2{}QxsE{9<%a~w;~ z@Kh^*v*L5#DSDdtWi|-!mQ=`GSImLS<2mdDokpp^REtO+cwTij5gFPIv4fPR?98ZT z9+Pjt(3UQI2o9NXDVUY9dC^70&q}>Xi~EKn5Yo*LZgQ$iDp(MSe>be4!;W1&uhL5~ ziUqjLeT6Y#bHM$#Stj*4Jk~&Dt%8=E9pOLNjTYAEErT!zMgZ_G?BZZIkSHTWfI}zc zkJid88=@b#l$lHUGwk4<95(3b#KmP(H7Qw`0nhK62ZMouVdC$a}>iVy< zi555>#ht#k@9`>6raU1#YEH4s#O`=_6nQTA#A9gBBHoM!&=_AtxB%n#EvM_;gvqwW zMCc;IFrppa({fU1sFrqI8RfGx$ma<4sqlxWBUAE*p}#+vD*8pgJFiB04NLjEo-DD` z4M!_E+=f7<-)GkcQ4+670<-cwi@h3Ubc4KiCqSqLTMdFLH*NY3;Fm%irO6Jz?c5e~ zNz5u(4qMo|$Xiz5F_BKd*IUy)IcX9mkI~FTlFPORLHv4*<=~AN8HgwL_8! zSQQH%P##(KJa5kW&$!iGBzyc`(Jzp*F}mLFI1bj9Ce-{N;Cx@rp8W3aa;LBkQy(r% z%lOO9jwmQzYD_zpcMU`=u{DAUs;uVp$3we~$M-K4r4c04?Ij5biI7B1?85)#r;Amx zhRx8*Tuf_h)TuN2Dh5-Up}tp-e~BM0(1IT76lb%)CZ57V(;gU~z0jKQyl>K*U!Oub zq)nVxGTe$+SQ<8e@n%BO>?=NXX<#I?pMyM4$=n8#?2c-elH)0T@TVs;Id@+>PU4%M zm{b;;p{W$LH)C5)uo38u2t4HL&5E+oRt6HJqRfX)6*U*g~0~-_%h^jK-bQvQT3VjNLCCsX~vJvOAAlt26%lnvGoW!^{Kx z46U|^N%gAL%e8?B`M*87BsgW-zHyl1Rg@VV(40^hUwmZ`+rWc{Yz%6BC&@>n*#YOT zYG4dy*2Pg8Klc}Jr`I18fJiOYsSL|90K_=6N(t zc2gf8Ts_={K-Tjw?H(1jvYC&eiNepR-+AY}tgih4eMlAJHulcP@wJG1It%oO`qKZv z{Q(d~0xs>`F6#{e%!$1ATPGU&5+&9p4|s2K=~9W-Of2b1N-M)(F2~X?`cRoZSLy?!%bQ{UTyw zJ`4SmWiA@VJs0Py-Qvi(vt9!NPr)iZb{y{t@0pishj@hiC%%dA|K~dBDQGp&^r+sa zeiumh@A*IjED)$h@x%L8h-DPg?rnIjn8wde`S+{gUI_fR+-l-5z9f8_+iv`rqi&9~ghy8mJ&0>h-(H1e2=p!5NCF zM#+o;Vd({-U3UOQ0FLd35V)e+(zH1G){596i{xkldy9L&lsSu4A05~^7(XzndGVGm z5ju7fE@?6uaq%veRjL~u@ER(OkpGxyy{e#4(2RyY1GaQP%1?SqrXAQH4}N-(-?@If zYZkV7{;+d<4RK~zO*)PQT%KpX-WdHdvXMjAI`m?fj>i5AiszXARde_s5sR&EXuRD95CpP3^ZyP1o^|?*MYnrUDNCd#Q)1E z`eNf&k!aUi)AFHD{KN+{KX)*@e%~(OlkTgMQFqJhTHsJsi)v|{=!Q^@rtejh94+ex z^K(zFPb5&Fc_3}T;B^Cr1zRe5{TCMoJ#6LX7EYr5F0uOfjUAdN_}$gOZczW|6 zZ$#j`ZF5Q-Y})<#&~cs#L-8iuxf2-g-Rr<3<)y2lw9m~;FoGy+ejh3|m+M7nt;B%` z)*K%!LaYYbE1$Yg5w&YJ;WuJ=BXkA-iLgBENZJ&gUP_w0eA<2eq#J~xA&f}kd>u62 zeP8v>51%Bb18VwuC-lfF*fQEa|L)l7qedeMf|x+r*YF=AhR2Rch9@E2-of3Rv7(b3 z%t=-7-P8Y4E;sdR)n%jA8&1_Th^YY*>WNS8k(dkl>6P@bdbY&QrYK-Q6}O;f4rk(9 zDgY}$-(h_#*Md!Le7Ss#EI{um8Y-ak#u?hJYt_&(in0*EZ{N|w(}p3cRSuUs;T>mI z7B`Tky-rhci;qA5JKfYjzkDd-#Aetoq6H7Q`WIvs*cabq+gp6wPB+#PGZBP_i5dV* z*je7e>@y#V2C?$-CQIpdL==lW@`x17woGU3aSG#xb+4qWC4MhAUvT7CZL;CyTaIlUg+s z1D}`&VzI~oM;tS>s(1rnifV@*BHz>=`rkgQ80?;T@ zE$l(N=>STop8l`k(&5H;A>! zbCx71hVJ#>AF52^c}MZEZisf%)jZ^;U~mZQT?&!%28~% zq>JqkeGVS061v`g+?mc3lS(TP2VrL4T06$9@o$?auik?{!2en^K5Fa=b@*vdWyE^z z#KC0J6{2nPrR)sv`;Sz!RY`$#)J?c?pT2X$IlaxQD}eWbv14^JU)vfLx_J6VluS2Tgps!P4DQ ze!U9bal)O<)^dQGGdgE{u3ffjQ;ko(kFZtlc;ZZKic1O?&jdc5n_jut-Lc%NZ@e*m zmu-3566)@=Y3XNTPwT!WBQLXh>-oKdeUYr`ga|fU5fOAzIa~w0&jt@UIC`>i={;Jr=rOd!Snh7=aU`hM3y`U8b{1E&n89s z=&b6Q=R@-;F1W%FG}+cO43m(*aj2y)a~4hm#eAmF4STBK9w-29x>YL`oW+NON7E4p zzzbB%Ev-U#XJbT&*4qB8UaT)wq)b6Nuu#dulmc8%KZRDpmOeD#F7HK&SGgG(5KF63 zV{ebM!uBi~{0iItSYuKOPRYcUJ0*(!jZ#LFpiVBo#3B7yoNUuH1-Z3htQ2IJ@+6;{ z#LoJPu;lt;`YLD3?{i#ah+vCJ#0rVq^4niQL?!DSTRv(bSU33`*f8SA0S{MD$ANPD zQHtJktY1~Gxwhh+?ybA09PEyo`sR+y)M99$wK*B!C%V(B4V#{j{p-{ zYB$h^$nB#QNixbALXhim5f}JE&C=tFYuLtCc?XmRONE{wA2#$LNew4ElaNs4^>Sk6 zZAX!H!j_z=u;-!x6({{MQo`=%aPf&BoZOkZ&IYZ7J}z0&e_3h}7Rs3t;h>qrj3+S8 z(ir(~nHl4=BS&DX2#*g7$h&YRCFtSA+~RN(!cuC_--}Wz+J`iqA4!b_c5aui{`Ck; zRJd}t(;RQ_K7e2sNkpoUoF>Qj_dW9(8vD?BzFghQ={J8*tBpB>`^~}EO{%G^m)n#` z*Gt0k$#FPNXg_k@XR#l3$rZpwIaNA1Ap?#`kqd%>szE!NUIj#{xRc6#C?X_03-o>-m%Cin>F@7x zc>X%(6CDq^oQq2fubG$MJRaq0k53kO{T|5w+Y&Xbg#-vk2W&oC$Ac8CWat|*PIQ&g z9dSRYxTfs05^S5a;hypGcE#p5ryx$ko4*oc7Ekh3nkM zJp|>Jw&1O+l$}m)E;>9duBUz~=(L!U4|RoVd=K*5z2Rs505nUXCZ?fv<}6uPisUB? ze;X`PF))@DB4A-cl%#?1h2moGnw=Djub%9Q%PZtez&K?f(}2y;i|P$u96l?%4_#N} zYcDh=R`-RY`xh&@c@ADAc8vz1>|;%Q(Wt~x<_)^?U7NA`1JC+2jLG- z_-N2=#Bol1-O4~8e4>%p$8+JZ!4yuCQ;xq{pl)H4(z{)nXV;R@I9j%tY$D>1a2z>! zLya;Q*0S100E-QNJPT=-a8*i5Fk)1OL!m&RQ;q*Q3pwlsXmghOA!bcFz@J}c?fd@L zV>c)H$ddWU81PXEE0m+B%u?xcHs&0(Y?CM)DR=+0oQ!~2vpUIOM7hWSEF@J62p#HX zt^2x&mNR#OLu%JVz2#ZC3BxdX&<6!K5J~ySvoh<3YRmv4E-N=t#7J)5-A}jcn&aP5 z-0NH-_D3+tSe$I=ic7nX!WaL%h;lFN8U99y(@pcG2wLlg(eXW8k8138S=U_3drT#J zbK18^Q)y;^uLTZvKx0b5`S14pVO^R;%W@?Mj<`>Vg3hiIC(_tQH5OGSXEy2PI~_JS zRC>f|+WiT;VjfPZ#3)(H>yxl5Wr#Nl;4UfZyQnZDyOYTsHjrtKnf)A+6QpE&eVhZ< zB&zhZluwWdK^ZlO?fAM?m18QQnA*n(I1WcWn*jj13+S)t9pP53Z; z$TOR&i@J1)0%Mbq@6Rnm)wW5O;Jq|tsia>FaFSRk2MM4*1%C!+A3*?YJm>AlVQzDt zqmyC*-jRK(m7spw)Sf&Qk;-qwCLM~Zqb?jw<_VWKW0Pe2VAQzFaqG7x0mqumQZ4`$ z?rIbTjIoi{VNiby9N@xUyMdqrb5=fFpwwdS_{#`1O^$}wYz z^hN}smUmfrP(&lfvZ>*GMI=I%r%^N zL|91ZYG#Jfvr zc`<}*Hin@<{SxrXiluiOcl8#$f63h1vZ8sHDe{?Ht;LT}+eke3G2H1t2Om`62Rd7) z2ID?FG{0F}JzJ|hSy+*i6vxIXQGT3?E{02`0)B427b`MB!Te~avO`H>QMUsSWX4WX~JqciHf32M; z1brkExo`1usoy07fVnvdz&(fReKYJRnZq6J`!B#^5nOm6x|s0y?N2+pK{AoAw_lA1 z9({wy7(aX-!(Wbj6rFwErY|bJdgHX_ z4He%YdRMrsD{qG6T@F^(Hu{E}f$pO|zyyXh(TcQPG}ZPvPd6VG!#z0aCU6@GHeNBW zzfeZG8wwHlN&q;6v_xjk+W!L!7eh6Fv3Yxh|6z)S5g)u?9)8O0X*oH2I@1&88;^X& zrHUBTa6ZC$H!BKE=4Ffr9i)L@ekRvz0_peKR{-7dqtCdefM2izW7BhS=2oMiiYRzn zVihW>zjt8V`M^zDROZM3oq$h&H-~kO?A^5sB4~Ud={E8;5o){AO+5DwdaoI^n=~d z!h+F_Aj6H{K35!6aiIu>7=5t>$Fj$wdLo$o??{w&TTx{>|$F$Mb2-5G2 zRpmo~wK@LDeu`MnmfHMaDX{@-)Tiz*3oVk&U*R$Se8;cpN861`iOF?8VL8JmQ$DS% zUADoJe{NkhiP|0p|9ch#x}6s=fCYH2P0#v1x2r!U{Wnh2Nl<%3tC&<0fsNFtMvBrH zc8g78M2AO(I#bTWXknM!b=Qrx5-=XhM&2dKyfc!YqNKkJqnS7Kqi`%$ZDmlct)8D4 zoY1vj>Dzt!^0gbJQ|wt>x^jlfQbyfTILIK_pnp>f6xsD4_`L+z-VBmA!>Ego$@f}GkieWs53Jb47@Mnoeol2MCS=6qX7<;gHN?J{=$lpvo{-xbWVGEOX`gfoH8jBRfPuhpqf+el@3=o!sWkxz zlX3{NP(l#%ppElz%J?1+e^A$aobw@-c#IKt$Z?FEF3|c3X4o!!xA`>HM6H#SY>=*H zbzha@3beKKY0*tw^CSAbHZv)QH6M4&f8B*n1_BTRPT=RV@n7X|jpOQk!aQSrA^J^^ z7#EOBW{HjMu9Ibkz7nV6SNH5hdS}#IFVp;%a)$AnSLufEgn%RCcUVJV3%b?V!cN2d zhfiu+;HvpQJ8n#TB?UclgcUGy3O^O?;Nq|R#&xGPJuK;c2P zE>K%!$7CT}q>oi2n(@WreqpH5Y1ACMU&9e`s5&4ax3CS@!`^Q)!f}NIN+8%|I#3yU z#@N>0LjY}OE{4YV;4Uw${IjrVMX45B_auR<-f?{z#6G-A@AJ6&j!ohvu7#!n>2Q9% z%*YpK<<2hvKo0-E{{_(8h4*9J5WolMYjZO7;forpBt|jW6R)-jZhBb7n|)-9FWc;&&&hZTVKj?f(nI9z5ZC zf{%RSbGYYgkANUiq6AELyziuxfx_l3cL0A+N-(@)s=t&*4p zh}6gEbParZ;|0S(44|*C6`IVAcmOe+f26-^;{r&DEX}a6xMsP(xd)PRO-lKh3;N&} zAzKCjA|ffWFZa!Tzie%M6W*?q(L?K!>Nk7@g4*CpIkQjj(qRE!t~`>M*yYnL;-xC$YK9ss}q zRRB_c`U@XE*gk~xC6+A#01+_&T%^sf_}fp-x5okdCI>J0Izdif2yOL+jsa3@{gakJ zVqyXf4&x>Kc%fV@VE1SZ-Bq0>5)xne>O=U*CqIX!m35R^ikQ+cXw6w_KOhD=DEHYw z;q6nWFluK`Dm`_Tu9d1siQp-|Wu0Y*Ij_gxiAk;G;XIss#(KfYqeK!o zJ-cF_0(RU=DM1jQ)OvAQ<+5MUC_jK`;ek(nMM`Na#OjS!+srU~#s^S{IWAO8EMNlJ zR+{S6Xoe)wAZR&ilq8-v??Zo&FaPHu z#7I$MsTzw2&Qb^TW+TP7!R(nqp~U;E`88l{xKnfkG%`tk4>YZOSaX^|ocS1I8`q6y7Ma<@+v}b$NOR z1Z%a)fyCu~)Om>j>2>3j+7sCGl7E~5h~e-8!~h9JZ}yJ$BJzU{2|Rjy)_OrQA4f_^ zA-;(DdzY7&mpK4_IYDsQE|gb(^GlYeKj@b`c5(srX4p39`rX6l)z~aA1_TBOi5C=G z5Tq%RM01zc0)L+IN z77G!>_)u>U=&se9I@N7xpy>%FfOPdw>h(HrHk(#2E*A(c%SA|OFVnvO0CV?#_#r7W z+c-{2iANrv363unV@!_JF6thngzX69gTV0pM8^fKmI{2ECR5ZBNHVjW*;v73kP;m{ zmrF4wM{5`v=tjgCzIy+o_{)#{10J~lVN`g60uw)h{&FR+aO`M)vR^G@a22^=!th^`7GM1((6GF5Z$x97_%i#eSIw(CnFsvN?!Y8DZ zwuM=m2|PA68=R!Cr-H%0u8Y1IONTTNnu${CW-CQANfijZHblxR=_GLn~6M=$BgHIK_Hf7`b8lV4wvX)~wP@@PtE0?D;s?f= z5H97JW(ovJ3rVYmW~+r}qU#D$2b83xL7~CZ(%`WBbY>|@G6*42Efp~_(u?7~DvlnV z!ry-KpK$Lz_aIrGLy`NVS-UvFaC>7dmFnauk(Z;FmcNJZ#}9)83S_BM7(D0Mjx-I{ zEY382lD-E~cd&b?8?~yM0iJZMv$kHx>DguYDNkelTlK|1ZZ@09vW%^*tmy^&z*^dcrl+&H>D)e>&LZX92KcPfF!! zxTG-XjP*`qfc1A1LLiGcgy0a7Qdlk|gis?(aK;b`2Ej_NKX-+R8 zpsl~@2q#p{vKqfkYQ+|M*)#zWsFow_AMdl?$OQWj9671ZnzR0?Oi!#n{<%*IA(#+? zNhysk7&Q9Jl8B_tTf|L}OF1;634?q^-w+h@_y6tZ$8LYs3^8se(Ei#(Qy3lWMZ}!} zIIw3JQ>PZrc_#U@NhTD}kRZsGw!jgGfKu`(l4>H3NDYytyaq!SaYRyikVtTFE|3W| znMNc8I57|>a3a-8QqL>E83Pd^jv~~0A}}H>&Ys4J<5TGF=|OF12z^7t$Wp*qdf^3` zPE7$%?{lIno?zPQ=pz}a8FM^O0PP8ae+{n}`f45!4c)N;tyCbpY4@NDw)MUv$7iv; z+OP&iIO+qjw(w{5<>gEW!5WPQWcsSgIf{_n>cZ z5X55`z49%rQ%Zl@RX@x%8f4sqg9W@LSPSiR%%xvFt<#oYO6ir8*gw{bYB@49mqS8L zbpF`%9E8YV)1D^tleLa4KK$8FNGT~v5=bdoy10j?^&J=bA?MR7<#N{}}FjWO~!?SqKS95>itlGxd@X zOwTXCl}F$lGLqmzDL&^68E5Km#=w~X=M0=nFcNxZd4ZG|7y}px#B~#5=;775+Rm5& zh*01Jw3OXa1&WG@N>$Vb22re3Y*4Ij+*o_3=r$>1*PYie{e6W25CNp)3Ce+s z0Aw0;(iQjkyue^@36n!TPQd|WS%&)_IbjuEb$t|o5D+3u*QOshm?UehBuPjJ!J5rx z)@U@K^}r}S<=08`piTSexO5(X&hX1CzulKCO|2(OQ|~VJ?fw=2WM8@OaeULWZwjc; z4SPp0b$U^Ct=lA#5^7E{2*{QmCn!?ZKg%QpXUHOB`8hH!5OIc#b3|M#K4ajS1n~?J zGBp2q8Tgx!z{2zf=BNR}nYw8FchaTmM6kokpXXIO^tAdNtSP z-nV?>o>GdjPgqpE z#XskDyN2)-j|u^bBK@Dc524=Jcuuhke+Jk|@Cp$}6mi6yA&NL6&JafooO48+z`0U% z&KV-E>5id2-ZFu}xckdUgMu;jb(tG&8k!#2Mr$T7DYbS;iDoJhmntY!tLW+-0OQ=& zCqM`GW|~+j!e$jJ6Q&e8oG(H3gEkqmEYlMXW&%=VDjv!*bXQ_Laqobpj*8byDRF9k z8DD#73L;G*rGU&*WLX9&GNg^Q`)BU`$WN`WuO~^8Bw3cF>+9?3>gs9&rP$_|ZnP|= z+*AN;x(A?eFwCPVO@TvgR7r8f#dYISCdO5ml-vRk~5Gc3Ce#fXkX{Q5hn? zsfA@MHiM(6Aao$%GCp197*E_c2u^^^gw67-tT*uBqbC8O<^xuMQI=n(S!3zg-M`Un zHbtr?C8s1wkR(Z7A8?lCO_fWX1-hW+i03@* z3HawPKZK;Usm1|-M~hN5^t#|28CPJ)1Vbhi^+|Ocz=@D@3C;k{SvajKV~8RGF#!$+ z&PmTTrl=5+@Mf7|f%SJ4Qv}z#S*J9GqA4P2pw(E%+Wags5DKL-O66{pdwLNCvvIoo zkR?ohC7h-*rYVM$nSK!hl_JM2`-Z?x{eVtItu+$-=L5&lN;5kv52Tr)Q=+~+{YR^( z9-eBoT1eAWW?6<-tA!*<{Foi*xV&ky=@iIo`p}8?oC__A0PtdMcmKeX{_6*b@t|Yy zU|$!W^9|R7=O)ouSYF3JfBB)dCKQ`3jFF-_hVF2N*rPz4Gh2p{-li=)=Q=yYxSMW? zySaz$1bliMpn_vp1#9|e`3)eHnAW6bhizS|DGiGYD0g+ETVhDN4OAsVLki5pv0)ikgg2YH1 zAO{2+hG)vo1@yZR9XoVYor&1DeQ8R(END$lqh!&tS(Jdh*vc)orl`4wW3Ye3H5Gqt`pwj25roI{GR0#*nP?sjgjUi$YxX>BcS zNGZAJdEE0nztidPAP6`{1U+IO;81Pdqa?u4Hldl!+%Ny$_o$OQoxIM%Yz@Ek`BOb* z(kmOgc;|y_V;>2OnBC+#VGO{KG$OZQNN2f*|lbj|(9J*L8WP z(-}qkKhOj?5co~I^2D#4ubh1KZwMio#HH}~{3KpF|3uF{v|S$;e!hs!#9~|HlrYO1 zI>5H=A4Z9^>BCq!GLUc|wD5EBh+u1erafS4B0^C$rv|v0j1B-q6987~G|IllM+1HU zvELwN7eO+)JPOqsiW47#@I_ucLF zU%dU@-Md$A`@SD&^C#e(^G2g#M}780g8QBTKokZ&lu-ZxtDpPzFBfLdzL{1Ud+M=i zeDU1lJ@=rL!lkRr_;~T|q5r-?Vl+2p1k8ZjVbLg&aYAj+qLBdlNFy@{CQ+t=PmSiM z_M~-P02jFj1X9=Qgd;+jwUWxz@x=cZWsa)11Y4;vDOf6G@yZKlQJl;VW>74Z!5G8FP7{}|uE6*GaP$ydJKFll19$!EPyT%O*87(OEkX$4d7kgOt^xn3 zpieV_4%N0D7Xb)H2mQ$J&A#-zf1D{z{aV^J=cgy|!ugY_%PD#R$9o^$!1DS|%3qF8 zjM)-~J2l{0sL`22K@s{4#F#`RCIY31cB3~Srqm?Jq6m@(IN}YK2~{ zuarW$n8TS2QV4o`14WOL?R&uK<}e$Wrwx;H&Y>_s8I?5 z;^kCgyWepro__oo7G@`+FN+BR1YF?#E4Q$`wgXBTCZ}r1W}OiHf`=wh;59d|zx%ga zi$A@n!5^7EtyU{YF?;qxgt26Sf$e2`UdP>oi3o!57pNq>Q!tak(XEsi-m{8f1pR29+|3@ z@bYt~P|Rmj?@cO&8%rCwvbfT-OXXomQv1iCM!Asg0(_`2m-5l4OYtr)h@(TcEW zLx(-n7wMuPl6LOA)wuef|272tqk?`I_{Wyj96JGkfu_*DK%e%S{MN5h6XobHLQ1S{HL<+5gXOjDJ^Ko__8w&a434;nFeFwQ{uAa`N%oz~@5Z_R1zcSzLh| za9t6auJs8ZGh+J(JD30EAGF3d&aTkTZs2*|FhK8#2;;K*ZMi1|03v^2P-JLipbvY$ zcIJ&=EzCUgJJiVzTup0kx{9-><}g(oeZ3*q3$U@%!fvyJX4}JV%SE&0cGVswft(~j z(g#SsmmCGsG(Q1QtrRd*D`R@HjC!@yQyd6bfCr+4SF`Qm#?l6EEpH+aG8Oo?)~6)6 z<=y!2zw)-1)|FCRN-1=FUl^?~gh+x|;s8Xr7vKl(Apw9?5*%3T_v<4plc}G5#3pA;YlguYUMF zclqK6TI&lbr7?Mg5Q4j|%PsKl2lPjx`HkFD0s#9WLBG$YtumKvQ<^E{YiHkhBVRxH z8-zL&!|rdoUdH^)1Qup$kr}b)-w3U#HISu}dV+m8N|;SMlvy-aJDkSxrj(+rf$p%- z8HT0#yBmE(Kh}=#wlz_2J})py3#UvlHlz} zfRTxDFToES0Re!%KEWVT3<7&!@Y_e0uNEhs`tnzEwZ~qkPCmVGC?SAs8Aw~R54ZwjI^me~eN;y>B_P4M7Gr#lBJ3x<4a<=JZW z)N5bLRu{fRGx^?{>LHTPI;fQLD3|i66!WN*a;TK@DCM(J9N#Wq=#Ji`lR*DTDiM62 zqt)@y>iGKTVPmTWpLbggHsHI?yR<#)FtK}~bK^(C9614i9+POlfFDGFG!k^Z3NvRGigV{*$&_bbrrFXNa)1R{DJ3Bx%6Sy>*=UAX zIPrx=v&0-4&l00%ITaGlCFr;T+8tkm-9@YIkCdFVA7|3!fr89r$SuD4i|>1PF8oY} zB4TCV1$@Cd=bq;Y&bbZTegxPnA&$i4Ny>de03(Uf;6P?hpY}+vO!?SE`NWGaW~&RY zuw3Q*pyb+s-_mXO4~Xe^hc{!pr#s_2+I5rKpE9<~ROjPhYw>OG?)yKdN(6&|ZyK)a z8sOW&?$_Q6Ar5_F`$6QsB7hOAd}6<5PCwA6^^<{pgwo=%XCKc`o;vH4rp`F|>RG}v z^P@m=FC+**pLBXHCtws&0Aa*+h`b&&mwzJeesDp0TRTDsMF=sVmqG|h2r=jvdYO_W z@NJ;muR#PD1pXs8chcm(B!CfJLUB-SKWqd@5`d(%iQRTe)8*3Kb7!3L^l6qYFHom2 zPcylBLY=OvyU4Z&;+b3n5?khP^vAl+d;T|oWDebRs9I6Ny}{e-m;I%SA1c4mkm0Jq zQigF?C8ZPyJ0h*Gon1i)k*@1X&bh7e2Z4SN5&99}$ei3^a$gew=<*MSI<*7N4-Nb& z?datc>V@3Y>EoHw%mT|-k5QJbFqW-ki-kI9rc6LfgwlcpD^jW$Koyiylu#Ac1?jEq z2$hV{z;T#IiE2Ue4eqyAz1G%}Y;CO)Z}YBb-d-hy=tVcf_46&wZq9`vfdM^$Fn!Cp z_bva-*`$`4>Ff#NzzpsIBkpvMA0{UTq-xv5{De;tZFvcij42&_tSh$6p zIoIzKLWrVzs9=nN#QU5o{XbBMr~bn3)8LOtz&VddVBXhSUIjs5w7m`bQ3)^#`0sn* z8+l*|V2kA}?-%&v0DqDINFEtu)N#T&%ZxGNI8Fq9eFOmL{xtBL^DaUNAtGXEEgu2j zXnM}M41ypH><1-5mz{sV+4#fcfh2&DL^AED!9SD$rtLV683kYnVaQ;tBru?VAhdm&JkSKNB{3?D7U3B`_`86gY#Y$q;Ll_-_5)G6 zk3fUl^czA1L7)IA19S`UNw6Q31a=<>ARjbspC%6t0c^3zFo*z$3I0*Ee_A-l?u#CT z2E5(Zb+kZk`i8ysN`ii)zyoLE43meF0JbE|q<)}3DELPyy_AnU+esk=K`aA;p-sBMh#wZq9qOLyYX%8Xldj#t7$s+002ovPDHLkV1fx?1kV5f literal 0 HcmV?d00001 diff --git a/deluge/pixmaps/deluge22.png b/deluge/pixmaps/deluge22.png new file mode 100644 index 0000000000000000000000000000000000000000..29319b3f38e02265d74a2bfe065ec1aec9ce5320 GIT binary patch literal 1103 zcmV-V1hD&wP)T z(@Th3RUF6h@44@rG zl)7=DBDDBeNO57e;$tCg!4ccB_F>IrrcFD|qe&){C+{g132U$r;6+&{3C_rO2HPdvLbJF-0UUaP$zZd^6| z*sD9GyU$>;h zDY4@zs>RckOGOI9Bl$$k*#~UuTSfkXS4NUCYpiskMj;!=HciHFT8ZaHNyQ`ho=YrZ z6OBfR7sl}PF0+++3InmH@7;XxmcEJoc=9=27f7XuIu5yPiu6F5b^s{og_3rs2S6_p zr5Q`3Nr4bxn)*|~D-6_1ecTw<<;Ynxc6j-hKfi43}~ zQEPVbqxOS+6E~WzNXl~vLP;+SsMcC!5)NIZ)9D5Pbb0~0rU3#4NU7-b0yGt-`gYt4 zpx5n$ePK{P3LyX}U93@WcKGJo zBNU581Q1F|5Qg-EkZuqXh9OESQW2eMrT$&tDmu;ikF}xXUenM;ryFqMOc~oW2*abK z(@7FT!z}bPfd`Fu z!8q}Vi|M!ox!-ej>iIRIV~t zZ32++910m9FX|w4K|JE(CwgCf!?-JL^x z^ZwTQ=AS!vt$Xh|JI=HBe)fK+`9hV1kbw{Y0FtLql(Yc=g#8Nwpm^AeiEE(^_JZT4 z@bo1V`}2odMPl#i-IWd8b)0P6z06&$0WU8vf!B`qZkFcG)&fqhwrP9P3;+NJo+`<| z^!~Eb^A$9_`&D{7CgSkf0pA+pS-$-onQ8fB zyI(WG@+R{}cK?{v{w;+<7MILsUmnkn0urI&I~q^KB?D|c)etZz5In&>S-I{dsa@#H zxcokFcfHc_z8o{VzW4go#`b34SsV*s!hRH@(Q>310tslLFd<(7uK$HKXJj~#ZPo+H zsKzyM*^7>l)lo(|a^9P~NOY zyZh?%)&qAsoNmvFeBR2`ABz`5VL5u%mL!(dVE2C9lKt5v}ap>uFZM)0PV-d z#XK*Pp%V?d1F0<1IItD()|OKhCWNPb_bpL`cU4x%f(0hT&QN+1aN^-~*JLj^iyVzV z=8*yFpetO+%M5!PuAccGi|?#JcR0Ql|B4xN&}Ax9IeX?n;UFODD9=er6EV<%Y+8=- zi=y2a+3fR4U;%cV|I>7}pUpR9|9bV~+H@#4%`qK)8Gs{rpsEA~ho=^4RiK{c10rxu z{*|*NE-pDg^V_c(Uo5O}5Aazm*@$x%MOZPW%bbM=Q#MKkZdO^2kh;6Nca z%EV-eGO%C5=#NWBetX_CtPB3P|E=lt-U9m2~iOT1aLiz#~kX2jc@q7bU zcX4g*+qBPgDzIGIlXMeQ}%icYM`DLrhv9|duCTFmEZJtZ*lUS@)U;{2NPQ8v8gg= z8akSN%NvO6v+fU3ya_rVO`eaJp$$T647Z#BW)ZzaL3_J`sTp)Wi71f+1;og@gqTRA z!i&w^xHsjezSeIki|rXHh(l4DYAruZJK9Ho&MIdrQA2U$QLl7z-t-DVBGGgZ1UL1O z{tKkkss8#d*?!;SM6Bq*f1>wz?K5ENVn*$&9{>qakE37m<(F&Ou>E@D;ki8TQ40vLrzu#An-LHciJw4NPY0=>lqlbwG)cnN#jVqlw z9d|PDYt0B|ZvL7itp+a?Zg~hoDT;vRS}0wZ9yb^M3zw|Uict=ISIJ9w*=S^&FVh=Z zpS^UH9CZ1-#c5Mj!}AMP%Zvlh1p^088}cI--hMr7H@a#=%MvGH7M%k=n=(i7*|Tj+)27_Uatr|eDm4dnkvHd?X`DtIuI+%pJyQ!g%^-Vf=CIEjT0@}rcf@$=} zD}QQGbNdg>ljiz!u zCREM{szsO0BQw5AknyO zi-=8~7T=Lfjm%7`vH0ryGQgzGf3Pqdtr2r=C>9@9d;t$vV2geCo2@9ts50@eQ&J1r;|V}QT-?2JeOBRDZe4e$JIV8Z z(hR-vvElE>$Jv9Nmf`$vRXJmp4L%}kbPc#bI_=sD7F z&p#3qJ8>wU726e)c^qvbyNwDa1yQUePhia#A+@5fMvQ}F0W5`tNtyQ^hEY3=8*6g8 z4C7NBKUR+>R?}-uStlhvp*6jk_LIGQwti)Ly0Y^GyYY9k7x7*)_HX;*SSJrP5qtlNQg@xsQ&X!l<*sjH z{EflZSPq-Yr?rJ!w9+T4SNZ{CsOUjawOb-GC zB%a*&LLh3~+(GYBR=q}SO~C9-pN&&i4B{zlj z)HF5h3USig%1%VZiMJz30|5bLHGeak8oFB};gOd#hCoEDVD?nZmqjKDnn}{DLs3Xs z&3Xk*X0Zp0{X_tVz(HXQzMVemPs_R8AtdL8Zk;Z?)!AW-s_)W^%vh4LiJj^GH~OLcS}2=qMh%;nf>x#k zeK5~0|Mt%qz!3VEue-MSsD*_kaUNaGXa7M|4;@ZcDho2NYx_ApDc7?g5y;rjq=3Ly zlJPnScqL!A=hZOK{2gp>=im$#f>!!{+v);EKZ_rdKL30Z&nlgHUe8s4HTf4?pR;H4 zTJ$Uo7ikK@Bf#3_Er_VX%kw2qbNRIbj76pj^}@;?Bu%98`6`}%pOtKHGnjez7{j9= zm$~1K^AA@^^+n0o@ePUfS^;?^TWnEflrs5-tz2vJ^L8--OmQ+(xIERH78-evz=jFEDhMvG>x6N_d+ohYgj@2trF*cl1;CB zZ9#i&@qQHcYVgQ<`qML)*-)^1!-XU#W7UFwXXsCmQ0BM>EeRqoheu9qXy*U761Vu> zYy(opgxULDOwzW#hbxGfzKS6(XF5s?HFh`V5bD{))vf5Pn{B3uv zx0C2zU)V>iCb@jhM&{iv&GPOzj>6KLYlf^uCq-olnIDSwYv2syvdBIZEDdbb>~(`M z!4M*Ri^lE|qkcSy%=_+^xW1Sz6-uroF?Bt_IVv<5@XP=9M$N?rr3hn7BPma%PrJIP zZ`qc4oQTcJrF-2>ulCe~c+OLswX?Ys7RU_lH#&H#5AUjq)#U&2idak1viB_6e<8Bv z=yoe+Bv>jDZIM=GYMoa)vuJQqSJe{|sw6}mveE;VfU44tSDM;0;MO{TxL2wt9|^Bi z<9n2b`Jb(}9PN#??3pZJG8`Vr01{-MhQjw5OA<2I40Wujxq9%lcxdx1px8iTW}OpL zA4XCa5~4n~P}6T!)KWRhsme+;CQo}&h%z@>`mjLy=;#+S*J5x?%~dRmv3&8+gI=g# z^PBp}k8QH+lY5ADNC-d|JqTIjd&H` zq33gI_2nMdh4;!Yi$R8g%=ZoBUwXAviVqwQhAY55Y7MUyw`nFBK$OoSduSSCqpjW9 z(IGv_>Gr|(Eb^hLm4s?x(dV^9ut+UgWPjkH^11(4EW+YqqtF*cpW%@vue$P=*W3QG z8Una9_ZI5hE;C%>mM5N_#`Sw1Y)}}Hi@$DvnK`f3ANt7athxW--++mW{2{l3@O$TXb2a+KP_ ze@@*Kuwbm*R}lVRfi&wY&nf2YY7Kc`{K$!QfQ9Ls#wOu@HH4FT=G_B8=ze$it6$Xb zu9oX@_thm6!=(fH>r#e7Cq)etC%R|dC|##^VjGWSkiO^j*AIL@31H2q z>bg9zaCha3-0aCt{B(1|F}3K-%2 zzJ)JmwqcFu&eUE?crqH+hsq`*j2$lhIv>TKRfu7W)D&11ek5Yn7QN#ooE*?2IDZ_* z`k1xlxyFB$H&d@0^7hsqi_k5!;n{?~hHA{=Y-jr{_>1E!@jTlB8^6Hpq;{-x)hpjx zUJ!H-yp_rN1<&ShdPm^B7eYxj*1uDMzEG72%1JjE`yk(Y(aE8j-1gQN+>J!FD%Fs! zYRifl3xlGA71+)B9%?H^%bJ1xm- zm-fp^hA?x7y687~3-mAS6N8$@+n3^twS8XYohQ(svM==dZcNG&34Q%8Cq@6A8iF z%#h5S%Q$4LujIKXND-43*-L5!9T4U9M#)G5tByrWxw{NO6Sv`@x@RU>bjy@* z!!N9|;~2%N_ui~ph0amxp3oABIjf9MKPVX&uKoTRY?J>@19OCjLzPvRwWxPjf09WB z^EL#za+1|V?OYwqN+n52X_nv^!0`yT_9il<4_}OMy%^JL2F(T<#OsJN-A>49Pye&A)x#ivC=EiCLI(bz(#M6rl zPEkaqBw&B};j1N)G8b)D$`ElO#sVYa4D`^5S5{4(EK{4I;44+2v*)^0rhS+KLj}5U z!OO6P+nI*p%~)>&Y+9EQI(zX=j=Ktek*3Z$==;Z)k1%j*ko)K|y3)T4S5B-YChs)= zrKtf5&aOjkcXNG@&-st-^nJk0t|engA3{3Onr#?fvG_^PXr~KemV+sSmj7OuC})n_ z>+a~$|83`1@G6CB#S96G{~JiQ@x38#2xmWNQwG_5`1H<9=IIl`g)6Jj$q2Wp!Nh!c z#MGy$k8Kp3;h3SHdYbI*HaQ`xF{KJ9xTv{rW8;_aA;R?BRMD-i2po4w@~@tZFs3hV z&{NPlEI0&=*A1?KBVd#J@OXL7WQZk=|3lA0-3@LTp04ktAv3?DcFV`DKQC17<*PxJ z#Kd4O?dTl7I$uv{G*C-Na8F2dH1^b&h>!6BDs;$BD5=ldEi+(qM(%EG0880eQ7w=L*PmxU9{bE( zWE$NX$)hMxYE$r6Va)OCU%Rh!4qyMgQ8;*#uF4X=>GnxiM_cR6n^9PSGhUe!i4e$C zOc~n zb>HP*$tyxbVzL$ab`gBGmF*tEOqR}@SixY`Vd)4HRM<|^VfH@^(0Kav1|=hmrG{;} z#hcc0h?Rme2||qJiPc+ZV?oh$Hr>2+{+#eW$f zRjW-!)g0*|=*oLg7!zOl_^xNVU3Je>Yn&LGnE>deHbK`BaK%dZnBoR*dyX> zTafL`gZ3&g3Dv|P>3`(rGJ%^Ji2k9!yW^|f4z@b!Pm3lGr?0z^-V?C_r_tDBn~N<} zhBY0p{>t4vps85+9W_EH+HcBh<XF=UeL|+IwXt2@ zv%x;A3a&QmVYB;Oj#9iNo)T)UX($$Px47D}Mn{mAn+4X8f_mGO388h1d_y!Xx9`ud zC)*wfmT(nd>DVvm*%PGfSqS{fw%fqY-8c-`3T6Qy6^@;sz&|5jDA ze8T0&o)25HJW@EKS(~Na!7kXd01R&0!`!X#&zuRxEBn|%A%A(>)oETQ@7>D$*rBa- zGH>>98b+%jX)p!&gZ%et?%}Wt9HfU36+W85QE>X3cuZ>Ev#ICXItsKBo+ciGD>ubW9hx!-={@utkJuZhTS$jN883U>8Q z4|uucCXuaWhw`s?aj}n9G}eX2tHzq$u7H~Z7A)7Vucp(X@A=ZaR}Q4tX!L&XDRqBx z9Ugj*_zor%kkjnH7wtL~IQ#m&M9=88H0it-YYeL|Q>o3LjRqb=?Y$Tj9$A$&C|{+u zTlgd+VqLsa&t9G*RBFgunAT#U69sv3W-=w@zxsaR#;e8Gaet@lfefBAgX^*V-M@AD zlh?M18ms|4z(y8VA-9qUzeQe++tiSYNT_E{-2iPGXsI%R7 z(1~5|)}Md?elFwI`X>$(BdoLtq->{d177+csN3KZ=6gubhA}J&Z`?anj$s8w-V!K{xKJajQHO6>GphXP7`KZH%S(MQ zWhD~_r%tlojtbRP}K_yx^&kt5Zk*RxBC_$r^O-*kwi&xcV`%p=_kun_hg>%r&8fW^sJIvvsMt zT)sP4K+gJQjUar5QKg}kUg$};NV>=(o>JW@M{FCzn)1fri&T>4ZKT`vwim0^cK-++ zn-D8K$|{--nljxD6S4Wxu=_zTw4*0kv%FS)HM?7+n+o!)+EZ$=Z4AG1SMn8iE#&mn zL;9=A7?N08!SQ~R~P@40!2CSh`;PnTj&(7WK!IKkjdj$T1{ckZ9f4J z%x|@zGiKYEF6)LdKzk;~uJ@)#DHt@7b?yQE( zxmfV&CI}HVdmXN!2R&hi?Yi>FMYc+j_fK|O$3u!R!w*XSW$p*8JWXH7JEtng>nv(* zWwGM(MfO??%bMU38BE9?qENJpZVa=FEbv5f08d3lSX!7sTa=?i%f0No;eK~w__l<+ zEaT$z)Hf@p7r5ECxAT_I3{1+OO~`NXKYQgXfQW)s#-b+$ zI(&48TPh7ZwhntLyuN<NI|+@pDQ)t`F-PVWvw0}zM)3(f`u%IGFJ9uJ38OVL zi4ymU>mxNXtC?Bp_7!b!tJkIXtH5?iz{wh9g%h#c?~{x};B=rRqLCI1Uz3YK0h9=MA}-UDtG(~Oq0`9V<=Zc9!Qd_gU}p5rqpw;s=k^`?wM{#q^g#M{ z5h4+IA+)1Zpm#a65u`&4Z;@|pH-fkgJlYOKgpmQEY#rDWyXsLW&ib0BgU*p|W5oX= zIwEY*R*OcAoo78c2wzG53y4Vh=B%_P@t2J?<;`&~fq)4L+KF4epGH@iP<+X2hJRs3%~l_!_EwyvMEnkR&R zDGU1lSpY-)qkky)Kp-w`t-|k)8Y*J0o;vsa>w)Xdlsc+lhee4_h$|eRMEGy?^2P|h z{g#y_WCq6(JMuXzY4O=6+LtS3U86|IAi}oW&S90daYvU@>7+4C7qVN7+r8Eb(#xOc ziW&&m4unz7`>Z?Zmb(>}K zOfO=&_4gyG5ZL3z`%nz$05^3NuPN{(IT*+)|GkLxURG^RW%<5|Z9xEwN#X%(K1w@| zyT`T0Z<%Bc0Had|VDZY{=4~qv=Yrpggx>fYV-_S|^YD;yo=vZ{qUf0T%LVOv#ri$} zs#MLuh-{p9*!{z2s^1hk(_!1X6BD(l#c|>kcn*Z3)HC#hQHPaG-qDO!tfUQBn|usv zZ1>Ycayp1X7KX6nWlHL0WGigxAY`GVd+VFKlZCwO9Qf6*d#JxgMA$g&9<5nLDv zDns|5@_8(7w)f-RjpUp-t6A*(>K!Hc4)U~~?@J``z%;cT;1pe{G%B90(JuMw2j z6yID`hG15d`?-bQlBW|ruGWPuktPD$r{@vZTex0?LiNf8tM0?5qdp7wg?OZfg&)d> z^lucpaMZb(=t!2}^AIfdZxZqb82UM^OTV`#!3M}rn|u<^Ndw)-NqJYX3##<1Cih?X}pRSq+R zQ_E=XTD2m0T@gngBtX?_f?SNhD?!iW(|KC68z7E)s-U;Adj<)el1_2BUQOyst7gP(-IpHpo%Xc)}^sR%5u@pG7e>nK82Ew-8$e%=X72RiWlet{E;{@R0n>CnW2<2tH2VQUlpG#rte zPyOf8<5{}L;)oUGAtOrLv-DR?nSB<_V;17j zm)-%-KhrCT@Y;bxv^wSMWe4|Km^yofaAXXgY{`nJhA7B()-;5V)zlL!(oo&3=MWhf zIT@{Gwc%ZFoS3bpvicnp#d`VN6L?&21zH?mxM=?i1Q02OVaqC2mPX`@>e7|10Lr>I z`$}8QsxSDL)9x+{C-MU&>ac{E2!y!uWcrddep*c7Gefn$+i@F~=^q~Qc?5;JbB6@z zBS|U{EU|8<3?kdwcD{1jeU|&)Q*L|@q2H&O79@otRGwvs{yQ^3U0fMwEZhYfJ2jmS z5{Y34XYT!P_X?y+cWRww_K&tCj&3k8kDX)9(gf!LQ2~v-zEc|_B&Hi;qr|XCdxWEI z=>MvhvJlSHH(_DF&1$DiMg_~aDB$$SoS9Oy475We?w&?o(~Fx`@b9mB z+r=O%`^cz20N1Jm!Q3vR$}eC>B{vLqmqi-Dg?12$2jBKdX&22&{mL7sO{!ubPgGiE zk{7GqiG<@5uEj17UrxMvFpKGx{@VLPHHrX6xsO@E&V=kAIQ`1wNw%RuP2xvRdZc?& zs{$k;vgr}L?ARB_r{zUWE6habCRh|v`anlo- z=zT>DTH&Wq?rWm*iBkFFUHT)iFkj}n^YCyI4H5DI;#sKj|7l|G$1))mOaJN(cDvX> zxoNBG94vqrD!&2F*v^^2f3Jct%eI)x>36Pu!l;?RbtDNo73#uqtqBFGn$$1@-_t;tLldIYNyBC`FM>7mBK7s!yg{9JkM%(4a68^rbWk=>W`!^-Qwy$c%Jr@3ckZisk z+8_Aolb4mP<3K6$sr;XI)KCh^t#GX;691Nt4c<#1=5Y|4Tb#Ao?6Z!dgyAd#=ofxm zI{sqKswysMgT-MjrTRAkUZQeY+Kaaqrs73$ zq^zh8d>NoEf~ph>X12O=hV&OQ1ZDd)17>BYitqE-{QJ2&S#oQbsuv6=)ZUZqpD|gw zQmzgqA?3S`*RX%FK;znAX5o890eS$g((=3 z^!d6;FHlFjn7;uByb0Mxz;#klnufmYe_4e%E={^7)L42a2#T_Q?s)_Dhi?H*wbJ=^ zmC|<~e2Iqi7}Fy;wwzy|&H}I0`#R#-Xv2HgtaL5J7};s@sv5=M2q7ht(RFoL3RU_u zp-@8pPyG_m-CCle!FyvVK;mzh34jK(0P0T&M-`ig#-+6c9l!$;JMD!v*$HWe% z5Uo%Ct~a;<+cY@gN1`L=WP=Z1IhHn+;>Vp{ScS8@3254XuQYVBsQ%q)^Xhrh>y&z3 z&zqrOAWQ*dE*9b-7NX9{*Ya0{PBG`VQmLNe+v*sVrkFTT7_oU)f}#N9*SLYn67;sL zM3LmO4fp+;M6~a;i#Z|sdsw)tK`W7F8Gxts|bybZ#u74#^}IneD}9aXBZE4;0J?d zsneLTfysQQ@AGCDm$0(FI?uB_ifFV>pwx(&HawznkZG6THczp;M&S+ z{((0>rS$8yS~xonyK4R1&A-i7ZqxCB-TGH8^B3n#YXNxD6m}l$#QXK&yyj1YEI-lm zi~}2dR?c%7Y($z@M(|dS#Tl~9&VCg>noJlBj9(AaxWkY&+MSk2drK(^_|l@t1M2>~ zG4)t!4e5IueGoFS`&2g&s5?h1^7ISh&)}$&cAKkT-I>$T-LEA^(dUU*AJMfem;;}; zM2k<*Ps^XaZ~1B3^G|I^U4kM-OlDnJ|6NdP@4!MMh5)r&ENqQ5xJMJD9 zSr#~8PCP{LKnjYKP6*B@!X$SQu=DdsGBh<>iSt#RGEn5*;amDab$z_TwuG28IK#*Q zilRG6nA7z?ZlkoTd2CjNhlAL;|EmXdJ;=jS^j=Ni4cTzNRkyDN z#v@t7@@~g&t2vex+1a#!B^U!k5$kJ^PaJy+Ydk?&)AYxx|6Z%KMxt>MS(dbd;I}bhg=fl+Xnp|kLHyIzq`0^=8NuD}A(AFdF z(cFP2f1Dzvr^0m|18t90NVK4AaQrj6w~wm>5-Y&I+Z65lnZ*g_{^VEeg0X6Z5|%_`>js^zZry{vCPqsPZyWfDNp zzh~k0rZR*~&gr#);gyW!SZrF^Lgvo}o2GX`7i;HjxauD@IEl%HdiN3e*CJcSd_gdY zMAWwv@dFc($ZPp>_FuWUeF+yC1|}U_DjlRpzrX6&bdk~J+vW>3*y2q&3~Nc6<@|2* zV z8U7EPiY9-_RiJ+Li)nyKpCYtiuk>pi35n*<&HkRX@q^yOnz|!J=)*AB$Q5QG;P-uU zvN}7^;+fa=ZnfT;M&PnD;pvDC`1+u_Tx>tzOBss3!|Xx~Ov|5sF5mFywCX;JFgH88 z4V)fH0@;hV0RXu{Z^D9L;;UGgl`oTwaLEKiit%h5#7aF|ql)*VaClMb)4{z|d4V*y zGS*24&kF7Bi}KxhHKpmV`$j1=f8z-9(%3C-px_&y@Utt5HH{3Vu_aErbv&}iwXIn; z`;}_t)Sm~aN{EQbX*b*@@Vs7y`xhZ`fqFNS0g0n2Kx0OHhg!KFxMYnTv~+1QKl4B> zqG127%J25x$A3R$kAE+Qr)iAzyZfk0n(OE{t=piT$QoCEY-C#8mY$*$f~5@=RrLCF z2`Wf&e(K3N2Lvr2&k)Ggj8evvtyG5TIyn3oj8xE{wa8prVl(kMU7b$}019hylEi21 zl))$lnYu8#IZuNy=I02Tc%iw_*Rc|?(7+M^wisM74a-u;rS#*qD-$p?50oGN2MLBh zmf(WkV}v{Y2@YP7COA@%km&1Es|V=tCFDO8!AqF@m^l2(aV*iIz3&Dk@Da_17p=c_ zO@wvOFq~0K_{%TLpQqTagBIk>)f%2}QB0Ek>H)6X$Gx1_2bBAO|JZ{Jw~JQAJ0`(n zT+~!wcG(}l2j!0cu(WI|&P}ioLhL1ZATvN*nn$L{X^0v=-Ht}3QNz7Lvz7@Z;?e&Z z4>^tK410G5*)wCUc@f%_yxYCA6U~D^9=LPu%UH9vIrMb$`FD@++w(y-TIHK98lsSf zIU9BnkdO%ujmDSOf7iiJi5R?EvlqU&J-htC2Os2XW%rVzHR}=yFF5a&osLR#wHXi- zNaMD&D6|R)n{RC*8}XwD6{!5vZ@Fy{ynWB*p;KFLk`RJ^!Q^}GG@8(oGV%|< zEamB30i}!o_HB=KrzG5X(2_6^Eb1|?*YET`2IU~?tapK%nNwJnF%J+;SqkHO0 ztEBfy!Ql$Rv9{93_!VCJ0y@@(&)nAaj0A5jw$K0PVW=31ugA)&VcIq9-z%-r1PUUPLso7+peN8M4GSenq-A7;fiAQ@^ zt%Og`J9@?|=I#0(RpF|`v!*jUG7hUC-+kqPtk~JJG-UHxiA>6jrhp`HfzG@;&~Pj% z%N;+tUDWa9pu~HfhE1Gfvc~QDqGUx4IVtw1tD{PmftBx5Y^|wqO^^a38I;)zO@!v) z(*I@W`DIzk?N&LkX;!oVKC#g!d7LD1+dkc>+L$Y`fX* z^JFZ+u_AoI$Ha8{8>9$!wy60anOJeRHK5hIuD-_MoG{{nO2)Rm)T)Qk_rL&sydq}Vli#A(5*YkDx{ zb^~wwOWhz`Zj8W8NP*l95BEwH^xa^=_&Xj`TG3OrO(UdPqE zw+f}g*B+v(AJ+}0>$}3TP!j5RzL&i0SIb<7PaN&*3xUO-$pHru;uGVZJu^g?#AUNm z89z00rn;NRm_F592yd}Ue4iKGI$UU8#>Jd8%R3AL@kw$~xkqkn(YPmkIB$W&D84iE z{rOjKw8`@(LFRcZOPz0?b%U6?G-}QYE-o;J&Ih*lgA20uBoi3TzN^_My>Tnxh2Th% z_bern^2S%EqwWo*Vk}_!E&mX6n3khj6~Jz0Uh{7GS=#)5 zx!{9j66c{EvuH*-pVd;Q%TdAfLr6VjyES3SYE0fIGVbyS9*+XbrQSa3)%|#8_MOq7zd;MlhC9=bc*?sfqXyE*X~ZQn zf>>++6&%0ABIWiCS>st95kh4tz;8d9vVPzVKIn^yeNH-K=ia~>TYMj#ew%^M#*rQM zwprP~8T3nEcbo5((JzXrbjqWP2LgZqJh7hOyT)VjXj0m`EHdP^xLuH!^cSpM`=|{0)f{&4_Yn_h<5%2 zdIL+Nfj1&rb4XHx@QDcGzn(Kp+r(zoU6qsl|El=P5tS+W%PbFJldW0J85bBL5_`A; zC)`1RMW>{r;81`F2Cln1ysw)?Ox7BS3W$erCs)lk^Ok9X4<>5oPkmUoJ?pS?w9?&} z8)0fN1%r@vwd%RH0Q?w+?W_mQ#a{$Y&%Yj%-b}bJxZ$gZWzDwCy>s4K`Nn%yNwXk3 z`11!!NLV;(KyZO?M0Nc9tq0DU-b_r_i>sj`@;huvl+4?p7&Dh;>~AqTX5IO3&6IoS z((|EqVc4yVNh)E7L@sXzGM)E}N@qbWF5@U+h_Z`&R# zeF=GDf)8&XmXP930Hx!QO_t&eP4h@sYu zR)8+{Y7bO*myK>G(_z#;=N@ZUn&-2g^EXqP9fR5qJ1hrVWipxGE*tw7o@6l~3-=n| zaHPuvlH|_bMIU4^f{FrZmE)NwG8OQ};Cv!*^wZj`W_R{(umgXqhw+OtF=vbVHSYQ_ zv_EGnwGQ>v&UJZn(E50T?82Q==HFdTtT;m5^C5MNgo`}M8hK|6CE|y@OFsMAV&ll< z6Om#wkYe;z&m^Fu==Z!UGl~^v9_R!4;Ts%=+BF8w$See8jnvh z26qLeSb%(|20yv=>w@Lo)WBl{OQ zgE)}Cm2~C-)$fdQRt*Ffwq{V~sN(W^6B5DkTLBo%+%fNi&Am@u!Y*N+rI$1_v%BvM z{pfwv<{lhr2(QLMKv~TnSfA@PeDOx@a-45EFHU}rL$?V%JefDZw6?G$lhb9Hg{~bv zr7ZZhUMbLYlSzceup*N}BF3thcrmLw)PkgoC*FTboo-F8iobfL?05sf0cq|v^TeJ; z?JeK72a{y;z0%^Na!A$v22c@G3uD&twFg%OOHQuMo?Q2@O(g@9sfycTEh7U>t;{Ar zuk4oc!lOAFIfp;+!yBoKh9$-)o;hwDwcx?c@xRZ-z)&9_OzEhkqMK zlY0#E%ox=9-*FSQ<7bZPG(5Lbg|wc$_1echcz<~Iic*nogD-c_?XJVs$O+7J-+fdA zR9(I{`tnPPZQF;h@fm-6x`C547Y+V0h|8+&=Wj;#x&p}+e&-3gFK^%>&K9NV6_Z6T zWtD#$I>;@&I!K&8-zT>}Hbi3wKpJm7FZu&`VW^fME((dMUb8oza2y$cnlt`Ob7N93g#rUHFLB)n1z%Ez}=Z!wvFJ82wTYWq_23g0`^!3`Q+8)4?k+ zUhL>DPZ~;5Q5I;OFd^p6qf1lHL4!U~FezupYw@c{oGJ2`gh!N#< z`fBz0`zxLPh*XW)9*xm;_s^{RlnXS~TPj62Vjh3uuZ>PC-)xw+%#QCke|MuMeu-Bi zmH74G^&_&zzrZxfe!^N%PLEX*tL)EDlJC^$U;=6C8e9Q9add2On-=0w^v_@q{=fwc`_3Y{GO~Vl6 zK_W7^)#jO56PtysjP%_?&N&ZR;H*eKPjm6)Tn^?|^4a&E8=rOmND#!LXPinp(6qDn zF*&v(;wFh5IDtP-Shz8^G|PJhYOep1b(29-TOi*=y|mGu|7T`T^s$LTecZdqxoIqnHVRxDz?@>lrE0Pt%vbIf!hgYALSX%AktQ-t)o8cZwUB zg*3?u0^w<;q+3Ht=Zk)7#Z=N}#MRZ5vLEz&%4%?o_9g>0w9Iw<54WFPdLztG-LdqY zrOmYZ&Je%KztMu| zZ72QMEBK2y@WonuUvL|;b2a*A&WL@@SVf#MZopbtSnOZo@*8A89u2*9to~(}4yqvP z789_@`u<^DYju`miv)~rVBhdMcU2=8W$(QkXmr=h-GoQfAQKp4qYCFG&dxG@xg}D1 zQPZ%<_;;dh@Y0OtA@H#U_rD>~DZ7p^f}19dCRb@?Q}==Npmf=}F|j0OeBe**oDAM# zk~e04(|tSWWQCBSG{!KQ3}3X~voveafPAeJO!W-T%oRz!RM<0KbNl+?*iA+%OY%OI zGgT&etNiGBEKh^R@`I4WNyS0hvw%~plLc!*g6&ycsK2IjfTu0%$hR~p#}a8CzVbT_ z2!)ehG%gJF7f;pPj(v&d?WvbTSvflu^S(g_?EViXiJzapZ3Ki7<6&G$?S(0Tlb>S| z&Wr;w5TIAQ)_Wp-AT))cIM>rNBnVZt6{Bp4lXKmD<@=rPld#YL)%Q6?7yOQ|)%A|$9K!44?)OSz;QYkCk zrQ$F*C#c-lrwPi$MLZC!1t%192;))Xd#4} zj>X>oMR9H8Qn~A-d=(`KPJWZg6u;m2e!#aBeQAC7Xz|J%|Ic{xW3v0X6rb^Xi(pD# z_lsI^1swl>7C?NOH0(aE$ck1Fd;3Z!x9e0izQE^zlCiq-9UcMTagW4}aeGv=XQ-$IO6S7tJ4V=ls$a=@G7sF+gatN>QcL ziN0ff#1orops_2W$HUI}Q#Qwt0q@}W6##HJh|>b?R*MplxlRg=qtF3!uE=@Zvh0!EgfG(LjL*c ztfD$kNxk5EGqX<--BJ@(Ix(eeQ)rN%dUMT!jP}AA?XHa^Gu4m1?`a<%dbK({ns{PD zJXLRcQ~b)myme{yDgPZ07dhrEivI#J zT+D~H*936wEH_uc*Ve(2I*ePPvYN9AGh1~`mozORJTMBY=_}$BH|F!_oqrB|PlLRa z=}HLC8FEMccp{`K2i;#D2&YBe7kHS19q&$xGJNWqo`>5%$$`&7Ho)P2y?M((90;*9 zf0zS`hfg^QJ~oC+8|=&B^k;Z0{3-Y)@a8+Ia&U0*4SnrP>g=dHGNM1fL|0^?IJy{< z-QR;1ctl=SY@LoCx zlCQHOIh!g=F7=>!#DwzI-;mijAC>Ok#1Pa`ioT0(L@sD@lX6)rwERNkHr-4=x~|2j zdx9+c0f6~I{T_$7y96IF_$^_1EAy45GvhLZnj9J6ZoFxZRQS1LsQ&Ysr|P?fb|m_b z_nn;j1uRdoCqG*3bI;TD+~=h$QuIB7G!=E{DT|NBr7D%v#41idt3$!G?AINU82G-O zj&38V#ZkDNjG4UNF2r|tv+Kfb4X+Xc$cTQnySqUm2GK}CMREx7@23}z+9RL;Lk9eyynkattdYRjA?*O&I z&2-t(HcB7#nE=ja%8#rtuJyL7pR_Nc=5L~0u6^rd;Zc*)9MCL|-MGFr9Uw99u;BN}(q@)zW3sOr zt)Ah^|6TIv%^~-x2#xs9(XRv^dwT2H1G*BFTFc@RlTD_Z(;62N2m|4K+*!X8Z?e9c z9OnMR^qX{CW(HxgLe0C$wj_%;T2g~3U6q=-32oEs6lMOnIQ5GOfX6=M#x~?8pqyy= zPUqIXq`$Si7<%aI?SLT?YcH^FMNTvQ` zV(d^p`qKV4-^(;%R2ZPSRX*eR^d$gtd>yyfU)W==GRiH>$Y}Z0YzyQ$<-yn~QLCf+ zLpw~nmi?0iHk3I9074MvN^KatCRAg#{gDsNDn~lhngVh%1D>gWbCv(_YR|f05jZ!r z+!f9oK(WUHVqBY1O!cq%=jfL0L{8}=+1an@4^}izOti z@qfx@e?wjLLp|WT&R^4+mmRfnQ6;$B$wWcFjn;{5pgc-FS00g1t|BD^PTA5lMFMo% z3!5_2f}bgbCMmfmo9d(&)1Rm_cb|_y_3R}A^KTCy+?-Md z$=+W{O#XdG#AOuyNp4AKce#X{;xzm`{hONZ+7ao5CXF*jzK@i$`(*;M?RTibW$&kfjAFrG=|h5!33-=u3I3x7LO>z^gpZa_?Ov+1 zB-ZZx8oLKH4t<(y$UdbnRL+k-<7nhhI7O5QUfVq9i_(2;3XsTMu6xppzgp;$yYf0p zDVq4&$z~iZip^KM{0(9xl9$=$HA;*U`fZ~2{wej_&dIp_ih1tAPf89*rVv&G@rkuV!_gh73N$o_V7OMzi7tg zj(x@__mBnjme|Ez`%+%a=}&*d;nzQG?*flWC8rJpo{r=9^Xnwbs5|2_Csav~uy>xb z-Q2R3A-TCs)XV2^K<@(@d+gE6VaW!f8>1(AZYzcoudfU6kf+>qS&TX@NwQf5k3{c> zam40E%s82HZp32}6NAkiYCt){S84t;sct8l z-;(9uVmvw$do%mKq$&tHY>oL4$^v6hyD^HIX=oU38dcpJPHscwySCqfn6a-1 zxp`N>1NAn5Qa&n$=k2uj$>>g$21-+S24l*n>*9S0v2vbhILWD)(8_OoPnXrdo{^aL zDd^`vT0H4i>KsUKyo^IDzIMzv-^>gBx#?Ffc*GPzHl=?MUN*QTaSh^>kD=j#fi1C& z755#uyY5a4XX7*#VG%Lrc@&j*{1|ifj>tGDqSMtEeZ?Js!x*Hib9L(ntx^-Qm}7Ca zJ)LmDi&alZoSO;#>QJSg`-kLC=?|~qj<4tmi9!PsUrB!byGJ3PwdGWLzJYO19dKz} zIHP$QRM#;+ru`o=B2(*e+yeu+n=icH9=PCXc6tS`+p9Dz&*>|3@v>TDB??%k zeE_?ZA-wyN8i(9i=U<}p158MWNMVW5;_QlkE)%sE_ai^0%IcI*vof6?g8#83uh?9+o3(=Qw2ACR(WxI=JnoBP14Dr)ITg0#uY3s$44%7CC?KEw0gKZ!#V&{gJfP+zBADkq$md|E}?_HyteZem50sXFEIWfZyTESGYlV3KT`-JO$0zS|1fV47wOT$fg99y}i z3fjaU4JJO@urzD|;wz0$3**)4%$wR>e3K-5K7tyZ?*kR@n&ehr5O|Zx+Wz955J>mT zw{2eO;>3c{nZnH*-E$s)CmASu=(6`}s*`BQKMjF%luEtX8cqn8#2?bx z!)uH~n^?Be{0<)ZkJ`o4_pf{VXLrdhGB#Bx)?)VBVKl+a_XdkHUY1XbW=5Nfkm#ga7Ll} zkwchz?#STP)gbYzqicY#O~Kw7RrvJ5q70I~q3VxA7zxSDdDQ!tWUT()&Ys$Lec0uH zefteZ@c4R+#DhNH#{K8Nm;*pca&PTkWNYQs%=$?YZF&iYLSP$5TDlMVzSRo4cFg5P*0GdPBJf zLHL#@HGA=szr+uh3Ki4;_U%G;(9;G8J&EwIzy@A4paa~u+XP?$BaD-MW1$&T@h$T_ z_XcLw$BL_+xJV0zhnP8Mg)kKp;@UsbA7zrFz8x_58v6pY?kL*B3r`!)%e5 zNd4)6@QxC{4r)j93bsQ&=I-mV)Gne@CBu2oQ%t$Cp3>{mUOQheKIb7hEWRjj2$Yih zP%DYm7$ML3HmBgpfXa@E9xseW;e7kVl{0dN5rml$gvdFAktCDTj2}8yH0(8hHzID0 zyAfg?&$-NOb>Wi0ur+qN<`QQXUac4pFg8qo`z2MIZk4_LkWgG!rHr>4FE^1`A0-vL zp^7hid&7T?n6pe?i`KY)x9hfhd7hm}7!X9B(7az5@ampofcS+W%|=b|boZzR%jI0A zZ#B7wXAbsQEGUZ+oj%z*BkI$s1(0XQc00U z#;)WZ)|xJ9nQzebe_rXgl`?Bc*Ml1E(tbWI_ajxmdS=Uj5>Gi7-&U#3EspBxntEu~ zckC#@9xQ7bJXxGUI6XWhH98_RFqlKo(57X7(>__BB3un{R$hY7$*0%_V%y=jPNzaqpkkKLHCq&EU8sv*ZosRY4~QujLsp|!&%+U;mw&g zQk{M9=q1LShx1L?Ab5cv56|ZK261T>c6{5rxeq=6%XLg<*zoOyv51DBFX}&tI*P_i zhBDiJ?fmI9^zBw-+~tdLcw&8h0$Fy^U$D@ZU;$tqhD8{!$fGz80U(UVt`!&W7991l zg&v8fs1vL3f*-iDNCT?$KVAn5Sp`BQ<4fk~rZm*gDT1!jX9OSwEyW5o*-%CX4ZdJI^Es`K z-6z(F#;yEUa;184z*`2%Mg>fKi;T=jrqlhyTj+|-@LD_HByyKctBNlZbJ%U85A@>W zm|1nT{+A{|GXY$5Z>V-r#K$DoCtY#8^ER->QM!UMsiZ?JP5GbEYn)WD_f&tZV^1K&j5g?#YBpP=z>;=_x|F3lACB1=77UPZ! zv+Fs0D2iDIDD=GkbwpJOKJV+jE2B4%%D~EJkLpf{!)H}Go0!B z)3tcyemxm|UaAn!ZC8t~$&U8w26Mz^Kfv{!uL(6G9*C)7D7_TY7Wt_et(UZ1K`}$t zKYhR&$0Wa|XI!anoE0Y?J%g5M06-`YE9M7rXGZuD@>qPakB|~+$<+IV&Cuu1A6e?~ zS2;U0E%9KiorrSxd?<*5@3)np?F;c0BZLxyzn=vW!4{uE?pESvg2%Pr>C+NiC~RuS0UK)vfoo> zRl?&KH(8yr!8i7{Tj*mIVJ;9YznaCq-MQk6>J)7}%rYcf@AlP?K#?-KntSxuFT|Y- zZlor!^jnVV*q#NQF6Hbzd=-&w%CUA)us65*FyQO9x z1o*^qD2(4F5c%BciKsTc`{boL^1K!0zo87lGyh6+MZcNREa_=Zh!UnOYCEU7i?UkX)TC%grN&`UEMP`RG1k|h$?cQHV7gYkiX@#%Mx?LRbjtv|5wKeOnTr>D1qXa!0I?M_2 zCZ$QEuBW#R-4!;d`w!V0CF}k8(3qc~2yc{Im&@mS;%vH<+Cu0Zh8ak8#sgOyK?VtR zLgWk4L&-~zYL0)S1E7%U9u4mE%GmqN8-uk~oDtf3>Y?G5jER(N+d z*{cB}j@fEzOttqAPjC<1Of(`C>I`N<2SY)>L|xuL8{nN+HtUxmvI-9F`O2-2WtI7r zOTYN_+d|vuHO7(ntbn8R!<_&h)yXzC=|h8yGLw$!@%i`w#I&}@$I6N$@ntbD4wc|` zKg-;qJ}EI~fEi6vaJ27i>670+cFS+in-vtv zB8XrQeJEg4g7=iD+j<=O3`f|umuFsv6|w0q69^kaIb5I8_041N2*DBwG-czpHgj6p z{?22*!jljCRFP5pr*jLs5I8vaQP0TWg1H&&_?ts$qG9{O$7?HBpGY|o0N8dU6P?TN zeS_SU@3M#gmB3YZF1C2B9_3m()yVQZ|C^^j%63Mm`32#Bv2NenSlo6OjKj$~e$5F{ ze#Z8zg^m*u_%jo5B?0K@p;i4MrVcj9=Ra-O@~L3TuVBnY@1XQ`KRQh+$s{ZB=PQsA z`0}DbH^ms5DV^K#X2;U}U$r30PPyPVmm_0LX+^BZR{eSwu`l-GH*JTJ^9M~eSoh7> zt;{B_QbB%y(nMUB?~`yD(o}ciZd$B@bRIg=2LcZ!S`vx{jzlA|^3)Ag$Tt#fI+WoS z5&5wi#FT}yMDH22C+PqVC0)p_Ll~&87E_voMInL{?_Pl$iA^5md>FEees~BscKdx= z&ezg%UA!T!$shEI1Si8y@Rm!NY`*?=Xw3Hmz$ReF9Bo^ac2 z$A<&a8Vr|jM-(!VC5H8G3N7d>6$`=OjjxnuQm#W4!CVKcE#-HAIqq9y0!>GUr8?}@ z7urIv>TMF`Pa6Z2AE0@D{f9fMgHlUmCmk66K|$KMws>D#8AQ@zf@D=+Mnd@zEM16J z#fEI(4`;kWIflEQfTn5y#MKa2siFfhwi-fDOr{hgTxLSsSqjv&#fOP3x8op>`UVO3 zf)90t?r3TLEj|iU{*M0FF!T@q_MfkjUA_D0z+cF*H?yv1k|N{csg$G>Jc|ZZ@+Bca z;)r60V(A?oT<0K((`&%a3xCN4PYwa%B1He4b<6=n?})X$T1lQr&47i;a}^mMo*w34 zv6IRrV+EIxW%=?6ft}ht1}^c=P0PWz9|!(`<{iiiQ*`9(9B%CP63~88l?0>cS7sXR!Blju#8QtifOXA1VxPz705jx@{{(*HW zm+<_yp?ltYXE-c9{x*vY7KtYVXazTWQJ4E06`iz9 z9_{W7EMF%dFWXcP!!CwB`}p@go>m8lixfy+z#|x9WL$C3t4Mowwm(Ie?Nyo~IEEggVr}EPL)#e~$sT zsi9pAoRwiiRFolOt%X4o4Ke^+6OFy}OW3x^Sv9C5J}MkR0G3thkqtKb$%Fu{Gc97u zNC#j4)LrVjRUnqqlKW~;Tvg{WeRSnkxpVV#cP)wH4fiwujs1DtKAb9_vkzvTbWE6< z;san2mWG3}$H91%=MwL}fzI8u+XiaR`gyO`lGtQ84G|KUDguO0N#GutQ259l)z+vc zNPz=ZPGZyoX#1@s2F3b5t8?WSXJBVBIKayZ+QEIuX*3Ad%;iMJEsWQ_AInsK{Y$qb zctdp~>tMK$Y7Ea`zp&>6-(d5e6ZBgUDHSVOCufQ*6xs&K1Zi)@C%Rf@E;loC28`bu!`B*xj26OTr;2) zzb>OvbS@`tjLyIUu-L(#76LweWA2)ZxQc%1ME4+pk7)+?6E}`02iuEa0NzWWXHCGk z{%=))vZ{nQM3&VJElY6dHO{;Xy1hpMYm)fqa_>v^jv}hK?;^>9(FBkRB{sFyh zKLxL&-)ZXK1UO;?xHJy=ajs*@ht>PiAGnkY|{n@ zS0j?rT zT}~TZ{pPZXQzl=zQyrFJnMa33(gh@~%Pq#noZsJk7CDJc?I>Bszv%kvEny~0@Gm3) z{h7v^x-iAr7N`)BibI}ghn%le1u+--3Ot)Oe9SEM`1hJdo%=a~o5IQ#`=!MXYeVJ< z#KAY*uTTj;#I{|5RXovZ9)N`G_5*LgH}N7Zg+#-wZe!>ml_?3AX{8dzCAF!#=9u zEJ79gs?`yYx+_yC^RKVO^uuu|4oKApINPKwXQ@cuy$BWmSC_@tewG36$4N4ZV4Z<5 zeJY#7V(#xA6lNQ3+PDh4?=o;Ew*M>c-@EKO@C63TscvaCHGfML%7JI%JP45sU3Oa$ zieDBUM9BS*oF3B|-%_mvOH2yGH_>0K%C05^8%PTYhxyiR5PdT7upq&fI4dHr{`JdT znckAIBUl5SBIzuLEj;NP=4j3_Qh()gB6z#GhR8YE!ZBaex9^PdJVs3Y=*q%%ktXKY zYlX#HsWUzdA`YT&!4G|~uKB4sIUpTEm7bp9rS=M|7u^3(qc;(H?)DyVip8!*^fUHh>MwOm=qin`DU4tC#?qO`rUU_&Bx!Ny4x#T z)*8$#U!96ip!r3Di(gEqf5urTL6z@}YM8A3xPKYmLZeT_{_A^r`mq{L%zx^J{({Oc zYm+ywS_8h`d#$J^hUz;3Som&3s+)o}Nf=sZse%vS=VZqu2vEb>3Jwr?c{mm=Ek}L% z0g~1207<$YCs#slDB&7KexNTQ(-ASStz-OY^rQa@g}FTJyL>r+hz9nSG#;~mrFT(o zSWDUktTWZMNSW)(@{wZo$ijR2TFN(nKRLHC$qVI~p<(p$5zsJ7_{P742nYDa7ZJXW zsu1b>KcAy4%uNT0KCgH&qT^HzVeb#9iN+VPc%*`>?mYY~e(f#tw~&T-ajE+-xB9ZK ziXsW)aT?q91TEMF`m}fuvn>}Qn#2O|H?)7YAg|R`T%qP$N!6{D0%ql;Z(=(q^xlMH zUfvPyjy|zm-;iaDbxPnr*k!)J{(mh1^&@~Wc1Q~Tw+nZ({HqjcK;t^A;YtOwUl zB8Ub1 z5hFa2yOaFZ&z-VF9aSFL)_uG$M#mnUYRKXr*RR|Mjk5 zs+p-N^I^z(+`sUFmqTz}GcD+IVKB52zNzobm5R&z+*DtiBh5$4(X&x<)Nt`*sPjXU zDIw6F&eM^dgpnroOAs25hxWQsis>w6TUQ9wr3zEwAXF_Pc+WxDE31pW%lS&3<%xc@ zK3>2cCLsfsRa!`E3q{>ERlau&d?sJkpT`hvhgzC}{19=Pw&H}TpOz>w>NCYd zJ3z6wt_i|h+1*axY~cS`z?eMkUZCKYIVZh0wV{geeh{E3;Zw~h#YT7 z$mdiE&%RkprvuOUBcA@IPnt}Y7(7cEEb2KK&Li`k6p;tTEL=QaJL#UYsvNv?h zr6FvejhDB}dGq|z{S@(hv9sy;ELh6~lIq+}mjcw@EJH0gD@2y))RbSxO(wPRUc^2T zPJW8nN!cZvxU zx$K#N9`)| znL8dW*%*MC97vjuK8*5`Lm+1{0$P#57l(@(C7({mGvhD&BP!p7W4-6Uw-cPNsMOK| zF@hfdQeFR@Hc_BfMSw_Y877!lBB6^*GhsLzr4jdKqS(P!hAUOy4Ig!>nM%f@`EE5@0l`fSCNuz=cs z7C%JhXY4(w`QzU*`3fo@wVpSr(=?JH`tV+W`1(YFcGp!g%(20p3m+du`T!wG7ron} z=g=0UH6if54ClzSi8Rs6JExD(+U;pgQ=h;$IMybO4{vNck$hspVRx)lFj4hdk?SSH z)MGzWf4t`(KVc6_$OiW+J8yfhL(Rtbh!K~vpj--~o>gM(A0fhDPxHb<=X4m2hp~uoWvV@SAC2@*VjEm9zhr zuDCA9y)DgsStTl7Mczk&4W!Yr5lW{nSvwRI&4^f;BTtakOya(hnzskwLRJtaA@=i4 zCUxAjUFjARrE-e*Kk1Ds5L#;*NpLQxN-sE0{fpU;x7DWXpIBCJ_i65@Y}&()S|9Pk zlG$B?iOAKqAWC5%eV2d`E}Xms=!;)+kUDPtUwr*Kl%ehgRKJuZtT`{0`wwof>q{L& zL|^#K`0(O`=*ArV+?Exd&HQLvj{jYGtD3wMKs3Dq(u>4kD~=#^-3n+nmL%a|saQ0b zS`Fb*-ynez{IpTem7@7~b%UG@W&(=25Y578*x%}AhkPf>Yd)v<`yBW57>i)380uHGe+lR+ z;bUdNP2z*s`+}pZqwOc0PaMUIZ*Ud@`oyNC++RR89`;`qxT@VufRO}VOEwcW1^kx3 z0}rG$2+W6!LTcLpCSpCJc)LC(!mXQtF9hciOCguH(E@)z2i7lrv@e_-jNUO11G(YL zs~09W`dff>F1wk8{#r8jrtTdGFE1>-ZXL&nhT@Tc=rQiCP5ORT^pX5j%PI%cN{Q7y zC;yNX>^Vj-t}+Jb|Le*5jB==;CyQcC5_C3~>91!eA*deYkgqP+;Ube;5D|+hAjVDp zoPX$}i+U52+d~HRE{IwmV^bYKpQ^sEy{EcSY9wEf&pc@wdSJ+|6^K6Q83e?I#O;4As`nkuNWE@y7&N94#o)zQ(S zctTGKm6w?X&1r6IAgjP_Qe8Q3R_wPZ#kW0dtgQY-X14*$Ug8Ag=D!{cAHFX6UScW# zHz{W8QIKNDRAtCX{{@Pp^waCh8`1cEDQf*!c_|1TthYbpFp|iRwL%Ue>9vkJ`Bu`X z%h!9(VY?}eVabiY7I?V3$M#ax08)jfrC zasS92snTsPq%;lPqSB+ywFY~K=0aM87~vsE7J7Qn2w}fK0{y!{KBfI6wW?s4rIeEb z-~vVi!N7?W;I()o<-hbWI5Uz3;)16D9id=V`1o|!cyHAfei19oW1QWa^09$WP%;i* z;so}jdaqhyt(7;}wIP&qH8E~DbH}kXptmK>HU%;MuD<9?25mw_dZUfpL#s zaB!g?oUF2(t;B7r9V8~2h!_`RhVE=T84#3p`cK@b)6mFJ@LeeTx?XXF_iSeJTsG{r zS`m8Xn6_Nse_();%f=Ig#JksD7C*C)4^*#^tPfxT@b9HC&zZwwGb;lE|4sBia#(P= z95j&YRR3D)ac)bL!gyMP(bLOK?a!~^P#7argBf1xxwR3x{P^2YM>SE4TaE${(13&} zzhB9cLx<;qc$E=?5%eqZiAXWXHjZZ5j%`86e+r~O>v#Em*0*RD8tV{D7Tw;b(UC6h z)>wNEdCyn|*ab0G`bTK<_Sq;Mu%&-l6%N`nzHzc{D*zQHTtP2}EQzYI)`t8vt@Y~~ z*bW{7j>SR{Ea<9ImT}*S3UNNQK2stl+Nnn&m41d8h?1dKH?C!8oRblL&dS=YIbpksq?|RDLDQKYJ?(m>zwKq z)I_a67a>ak0yrsf$Az%A z(oDW-5JHq3n~Kz9ZhaT?k@gWG&i|g{jHMMAiE@T$S57OUnVd55Fk*Q=jb3 zsLqdq()a31?`2k9Pzd=j?CGtO_DJhyHDLj*UW=0AlglE*7H`$t`C}vF@9{wIUr=<2 z{7^Y1nM*v>63Go%>px4NWbwg)uqFhCX z_5~vZV{pqe5_H7-_J{SeI-E)Cd`a2V7b2r~KEO=i%z2U>U-uQ3=A{t>rtaH73IU6gX_HcHIn5HT+5Tc|FbJC5`Q|>UUk)clp`ssTVieY1uUa z48Yfa`A_qJS61oAXW^}&l9{v{uN{lr7J%~2g2hf%k4E!r|M(NM_MD@XXDR09Xh<{s~)gG zzQIy^(?#fCmmdG7Mp;gmbcZEhkL(Q2vRUD%OS+)&mR0HreT<`(!=VX|E5tjEZ3R}o z{bA^Vf%Iw*@&Y0RnC6C53c^2*Wg(eC&fG-e3CFG&JT}N@-5PZ<1Jy6Vvz;l}+b45_ zycU;h;(DeA2+AP(pBit3M@JdrbA3WDrVJEXS^>9yXKJ%6m&FicU1PTwhUnhIfy3wq zgpD!5eI@d)SyCcS@Q0a!rJJh3Bgc=-ES3vWHSQ;)9Uqvb39EI= zdozZMoALset|a@{bDkrTua2%d-5=J5d(~Q-uEoRW5}q0bS(y7kwkt?J7DFSDts3JkwFmV`YTJ3d$JOz7YA0&hs59@b0*q2gJmg4&l>21jCA zpAy(tSJvG2eF;_CL}Mnp)36JAKqQW85aZVLdkH~?q!SxDuvPbEB-FDRK|nt%B}Hkf zOri9yc_+7vjTMcEBd_1mGLuNDdnWU4?bgV>0AaOSNvLQXVL$&kv)N%qDpplTn-1uX z5bXUVzS*neIWhFzn_bSi9vgjpvEHe7%j}fX61~IJeMgB!p&AL*RMo|lRl?dwlY?dv z#P8Vm5I|X(>7Qn=P`(xdw@|Y)YsUy-X_ghdcMl}_e*ZXN?mI*&YlZ`JXRyJsj0V|B z0(}%%42ldmNWGY;zuZPyZE<-TvLC(&C!DyD3gBb8U^Ah;Ke9?y_XPe7P2Fz6ru%>8v7~*l+>4;%J_JAC8Es~jY`S5va+u*;s1C%~>&dv5t5{%>I zPx|xg>vvz=jS-k=kNcYy>iCBlMU(JIW-#O4rnbpbC$Gu|hV6LGI_wpri`Pj+uv#!d z7J+S*YB7?|*%6~>WdSi4@dm6oqQ9_(#kmy6X%63C0=J0gRY)dR@c4 zJ_xt46b78wxIC#BU~a|WRva>Id1>q7I<8}?tOeMzje{-b|btb zF@3RFZ7d>=6^s7iX_Uki88o8X_+SKl^e`}#0b)aKj&vx z5c{KLg~|{{%;5uR*w{F4ZC?ofJW2`q&x!pVrqnyX$!KU8w9A!r(*Pn@P_QDCm&g3Z zf(;@SDwGl@$Usnw^-D#TGezE{>G+hBdGZ%#X%s@Q%pg@y1s)Wcpk((xo}NL5S%W+L z8W~FL8Lu9a_zrzvWc%yMRM9*=!vudJpfu-0M5Ni5IjGxyb2a6Z-N70bf8Z(pXQH%c0tcD!l^TFuL&nVTMI$%* z&fJ`fwICG8u(>b%+LG*O!5BF4)C{@|Xf`;qp^HR-V7K%0Jk>>DrkQ;{&U9LZ5Bw@~ z#%K=tL@5m`68JR9@U-sVvX^bEM@yTynZ4^964X4aSNZP?`}qrohq|kVyJ8}bcCShj zxmHv#CKQCBTp9PybkMpyv?g_1D<)Uz@&nO-R#d7Yfo>ODv-%8BVp-(X<69DtVMoV5 z{zc;Xk7z3*do(@cS4$Bm$N%sj)|c1JPgZ}N(LVcW69Mis@w=nsG0I$yHeC4n%xM_1 zxd<82j-hjY>fOgzx1OUsAz5^z zE$-*&d(Kbut%WazA8zp9=+h%aj`RcAYwbXA!IvIRfzLFtr)>W&!6)l5#A zNDba$yU$Va4!57Spgu*diN;x#j7P7DlDSa#za<%njm2HQ+vY$RS-~x-jE>3$K}{GV0JI%6ob>0wYHHIE^xo z5Em$eBK$QACM1Q*j2adOoet|PvLoq%LhxZm>{We6bB~Z1Y^ZD!>_g=!MWaS+cp3vS z+{diXqpjIa(dq9=v1z+O(fs&)^?iEcRr@*_|F>9D_bJ^f=k+p8jFgvUa=2P{LaF%9 zVzQF|nJgCH#iU05eBrE?;hu{%&kxFwfzy+KS03=yzL7YmGCAXfv41+5GZY!K_o%%} z)mT2~D=biw$Et32Ke<8WiQmG#E1SxG_yNn9J+f=J2Sqf^`<+8y+|n(D&*+rm5X4aBuq-*( z2t9%>ILK50u}2AZ+>}NZxi%n4CeLI`A{ZpRkKrK&PQ@#62!n*`E&F< zST}hBS#7aP{F3@f@Y(6xmT(76I8fi;UO3v*NtAI1CT1z$=5K; zh9)#whNlq^#!UVkj9&ki4*D-4!&Kaln2K9_nYYa!K?jDb*mGr})Q5Yi23@EP!347O zaTb50rO(*!cY&bN|8|p8)x=RFjUV z*_)K?rqvcc;?T&f;7DrFy2>L)^CsQ8FdH(q7JuUUT0)~>!;Smljca@j+=0q@5iqv8 zgG`!GWbOQS@dgGYVUJn8WcSM5{@ZBjTyXzytF5?swB2+lczWL!$RW z12P%$7Gntfm+sT>Wt$S0>RwxlWh%RK#8oz>VjZ=C5L_0NsZ@9FcX6<%XR@e;NaZ|X z3r~22GNxSb{owV#=qv9)mCM2yielyu>Ir0f3?4rZ4-Zp>syK%)Zd>Q2xXF+9J)w^@wWRicp(xVs_4h6+v2XYV-X`DXL3{|$crea zg|%zUr9(V|Q4U_mwt{0vHF}0&VhA8NK@=8b3quiDIaAX6L&-^vpS>6)eote@rX+B??E|6Q}L;m8=?TI_jm#J72iOh#o;#as8Ay)K8f4J0FwVzlo)kPTzCl&;{OfsGYV*b z3zoW0vGKHg%cZN~5R)e{0pMfn+zJxVW<2XlSp777N;O7B^c2LLW*X~FIzS|LoVs8f z49fJ54n`RP>$9e|m7|p^9D+qbHM0v9=c8GN+Tw@@TUhOZf^_KXJvOOZ+x4f!1(St@ z7jwKy6GC0$fxT?={h`qRE zpn3B+@JPbB0;=fIMn$j*Eix@zFn2D2vQ;?MOrk{%Hg^x0QD=vh_0@dE!ep1@NRt2) z=4<`TmwFkI@E@T>;Cqru%}(o+fu4tAOkw4$rVpVMwPcEGT$+;JQ#S2D+j1a?fskCJ0SdovI8Y5y9Vf~EN* zyiD4_fe+b(q~EQS9kUZrL&20);Lc5QiQ#x~#|PnToM#iKJxVlm-uKLQavsT?+Vefr zwNNPG%eOItFyOS8w`FnPTC!O-;PW~ z`3dtNbBF+-u>H2i*!imM;5sR2k8RMRxp1ST;X0`j~C5CA?{b;+`F(z?zY=tO4W$(o&L1hPEYhmTGx{0gf)`Ku@?rR_#EMJR+|ghk zss2>w&7QpRdfYdy9DNGDib%56*e)^iChem2^mP`3bg@RZ7AIo_$y9PCvPxYkdLiPzK{Hw6wK3jB7_0zuvX(u;><&@U>GBxnOOmOtt+n zxvMv~zmh$87Eie$%RZZXd2eT2+vI*ph4S3l$|NsLX8iO`PxmfbG%j2nm=Rk!^0~*~ z#|NeHtvDtl&E|j6XWjl=Mv0g=orJbk{3JlX{+(39*Ven;D`H6Fe>K#p1;MPMR3Ay# z6-0V`mR_nG0VGy$%rCf)t)%8?#>$9tsC^i@yy)f0=d;W6*` zE+xN?{CIrZigmAX3yt(t zQLWFue!RiIv-&X-^K_G#k3$iw!Ry4LKJE)GdzRv0$fKutm)D3<=h~m*pT71uB785j zw4-H=t!!I6Z0oiTjl5TSk_xH&^pMWrMP)XDM?SV+n!^IYL%}8^Y~hO73_dP5Y80;qSTVR;A0!ceR4y?87+2v$ulCv)LFNj z)zdBHEzaa0vg^nCt-Uc;AMc%8@Q6{?BvFUqD<#I)YB5jc3HtT4j6so{rjt3r+#=Dq zqGT76SM@1h;_lWxTeuJZp3#XO;qUW;a1ya+$j1DJ@ukn1)F-XmQKoz&Nx4LSs^0a- ztqdr}Ih3LUc{II^-VU_nmBR&-v*NO&r;qxl(w~9E*~bGyK;Vx*Bj4WH(C94$t2SvJ zH!F>{cgA@boA|WtvFr5ZNLFs|Q-i%7vWUO6X!NOHLg!~Vxw>z(TNMEF>W_03aHrwD$~^1#p$ z{{IDy4RZ2knIyv1*+rbcGKZD*W?%cdNT~wyf(25| zwa;Ij!})7-5Q4S_zkc?Gkd`(sf9+Gz()HU?$~X)B?RGmhu|N6)^|py&zq~(iFbJSm zD6lJ_&srXNC7A;9Ag9K{32CzAExeOM&+NAq z_`?D=vGmS=S-bM`s|NgD%rCbI{+%FzeFflwAON6?1a<=GTP^374?pxHPuGq<`SUC& zbbW6i%c|uf9=U%CCuhd{N))Dzh_JA-f%(NX%q^~AVR>U9bucIa0LZKb=oJR2eR5)d zB!Ta90OuTIjVi`R>o_t}N26ZW_niMM(%v2bR@R%ibo~yl-C7z5_yAErYcH&w{(t|S zh+?PqrAXKD$8juE;O_NFC!NHH;0{^xz}i=D&NCnjxsse`e=baQXTImezWEe6vp?(Z|GM=f=yw z7B8K@ES1Hckx9GWpnjL-+sC zPluJ!kFl)+UC)MUWgMR##qsH}Ejt=*$ApwXtmTdiX1X`=toHK(Z>0X*!Wu5$T)^Donl8`r z?#KatfS9)y{&?f!pZvaV)RRc`rNQ57sgb?VAFy|V2o4nZfdfhaK+l}Oc7cCa1fYMc zdi2TrhfaL-Z-&n;uS7+_cs*dxm+-Ho*PN!pIeeLySj@KUq{DX!I2NL|i z0Ve>($VeQV;FEuj16IGyn%_aq1?er z^Q;$u(<;Cx0Knx365iZDMq)Xe2r{q7vz7HG=9ktnzqpFU)y>q`e)|KxDc3Q6i+5IE z+`RnUXJmVI!+3lE(jx&O)Oh{I>!&mPJN4^H82iz|XAnE^1OQkP*h>P)J72ooUuHx1 zf9%QX@;(i-L$*Rim?j>zjJnbq+}%~MYm00MC?uE#5vo{6r1`(-I3VZhE9lR^mcfG>^Y z*V%o)MIT{r2w-12_Y7+H1OXV!2?_3o1hyiUOs~JiJS#S4Mu+bIp}$gWOn-uh z?_t0(JXFE(P!+@VGOFbwD&-=os;+gRIdW3$yxTzggnpY*yM(1QoO$)eSl+Sk7EdD&cC@?AV*jEvm(LC+W? zuMdrO9?O{o;7=Q274VSr*7pjZqM1ObX6S7n6&T-Ru(NdRzaf`}BbTODjR+iJeq z!FscWW-A)FDRY+W_sdR^K zX0-=^IXO!v|J3&b4Eo8ItL;;sP03z>^G-poVPiPF3I)V-)h}@gFxDp#>;~v}4E){?{xSF{Uqs z2a*wyPe$Iu13tQez0)MnivaHFtp1?(o+1F>bd}p6VDFa%_8a_}BmgKP z@I1KzII_sV1G6djpIsODfdsIv_c!47mL&j0QACU}ilWE|eJ{Z0m2DUN>FK*x=J&Gq zECCqvRKZp%;2?m%FX``1k+qx!{48S7&nNwYWP(3U3P!$bkehNJ@V0D=-`$cxFYhk_ z(0dX1#@_P;;9E|Ju%F=H%Hwb4^?MQl;J{ue6w>RcP)PK0-ZB)3q9{QE#PC9ZpZzD#Q*(}LDoyLWgei%^ltM3hmwAm?2rKV$UIW$R}o~*r_0ji>9V}# zL2QsbfSKPe2_&iq4*|dXc1QsGY~Bb#+?)0j_wSbXH?~tMAYGP-;SWK+zjjCf2iQDv z=#>!kr|J2uQ51Qg15;2I8X7wnJ#-bI!(-e5nq0%^?-H#@ttv%B+tXYRen4?8rm*WHdzIx!!!iK_k+L2(X853YoZO1Iu zJ8axI@~LnC=y&_xGk`r`dMxW^bHC0H49K(dH9}z#Xp?rVn6Gxx%3^3J|KocP{Qj2r z1VH=AUv1ekS~xXZB@hV$ECS58$Nw(0=+9q@j=qiUS^RoGHD0W_t0CqfgzlZgOM21-E|f>21kO z_NivQ$H+SUhvN7u`L)h)Q~zC)Ha z5-;XGnRxa1H~>mYeAg!JCy1h$PB$cBDH@dp&QFZ9RBh3XG-HFQ{h$5*k-Og&Kqpp* z@`-5MTN5(`-H?2r&z7-%wvG)jQpixqr?G7t*Rk;(8^_R;rzSXeVVP{wkxAeA&F$ZP zv3NC&E7rh{y-)7(MDXW)+Tq{hGYk);8Oos678ij^=5-w zpvjKjz{bHO$KEQvTyM7T_{S4pSX1bV4s@IKCpQmgdE?X!g*e@L@Z2_u8r^7_>P5}FtH2+!${x_Z6e*5!U}6FH#%5`BA0Rwe&V4Q z*4&CU0lHy(e{nFwxeFE2Nr$wbpc{tl+_9NhT9=61xQ>NuTO@1)+frDjh1EAi#EoTAqH)^!2I?jIVKA)U%{1Ar)sQBwA*9a25(J;%baO>EP^G8BfA zC@GObAOImHXwVvJ%?_@uAdYvuQ}42qaD&m>V5p=BqX>Xj5Yp*J2%!nuZPEjIVyy|I z7%2sDtm%XiQVK-RU=3(Av0jZ%7^8%OILfT+KqR9@DFt!we5f@7s`WOeGMJuOAn6#m zj)iR*m`e48SCzE}tu=AH8c`e*MKNI*A&Jp(m|hoP?k_)Sv^!xdyo$~CfShjh}#cTALNpyLYNFeZ#OTQ`rUa3>1cMFXU2`%2lRj7io7w04!6HuuMG1 z#YW$9V`G{I?M|1ug%!#x%^<9u`{TLS4j(R;%k#hzPy*((*3r5E zJqWW0kpWUbN+~P7>#nbVl0@z!jX)O?wyn}BS0QEG4cgUit2WoJOdKtpJocWRiIv7qOv<;CDHT zd(ZiC&-plvh;UT-Qch9C=xZmiZ<*Ob4-?{(4>6uxi_7|wsoz`&rZC)~zhchSriivRpDC8b_Efo%lj z0RRYu1Gg@xm6`YXQZ6wDJlR-Xl?C-$$7#W8^heRNdvJpONli+>IRwP`^0uB-ra!jb zJ!xmXiMix?XfU9`!1iVpkxYn~lsNNc@7?Yt<9bnu^7xV$I+2hh*89N0zP@2j@wu?vxV|#m)1(RPobUh+1o~iR3pN1!0sn&H^`hXPu>b%707*qoM6N<$f~$@n9RL6T literal 0 HcmV?d00001 diff --git a/deluge/pixmaps/inactive16.png b/deluge/pixmaps/inactive16.png new file mode 100644 index 0000000000000000000000000000000000000000..10342be186f9e3f4a2b700a54e6d5346e7b3143a GIT binary patch literal 588 zcmV-S0<-;zP)Pg-{@ty9Tdk*Ko zy_PWshY&(AO6kWu&!0q5l%WJtCKzLHJ&A}=60vkIlYkJy6*e^e*aet09lsNAPBw)A+DS= z#BuzT5YpN0cIV=YqClSKb>H_topF^`t929vK`+ZPr`#6+wAOGO=jM1ko`qp}YEw$@ zYpuP}XoNIPkt7MEl<4>SNYfO<;Sf?vFveaBAqa6@_u+cIM!jA~v)ROCGJ#SGjYb2< z$HyRqz!(Fq^>xQ_9@|Ax+$taMUyxG5b=?;>fc2$n0LB;?V{BsE_AdaK&*w;z1dGK2 zVHjdMoq|$|*=&Ydt%gda0%OciYcLpmUaeN$%WC<)52e&QTT1zcbFTjFKfmkU_1a=zY#Rt#5{x9e7>o(Bb#QQC zGDxFF!(^kziLkgB{sZ@_QKMke&B4VO{{f8z2T2455lUMuExlf^z4q?sV5nG4^t(MT zpFGd=%_F3g99AZWKcus0In3s>SoBHlPsD;eNDMH&XCZ-^{+lkB)41f*g(n|n;=vn>BH;b@3wR{Li&fRs?GqYp=UmB|tL z4Y>Z8n*oifY7?A#0XFiqiYv6cU^}GZA(jpR-{;|l_)vs5JNsy@{cV!oI_J|<=w6Lt zp+MI^NG! yJY;kz!n~?*!SzT0M%UwA+2pyDyv}E70{j42#Jhng#r9qR0000 c #8794A9", +", c #8592AA", +"' c #919FB5", +") c #A5B0C3", +"! c #7688A4", +"~ c #7687A1", +"{ c #8A99B1", +"] c #768AA7", +"^ c #7E92AE", +"/ c #8397B3", +"( c #92A4BD", +"_ c #96A8C0", +": c #5B7295", +"< c #8B9DB7", +"[ c #6A83A4", +"} c #728BAD", +"| c #7791B3", +"1 c #7B95B6", +"2 c #7C96B8", +"3 c #95ABC6", +"4 c #829CBC", +"5 c #45618C", +"6 c #7F96B3", +"7 c #6483A9", +"8 c #6384AC", +"9 c #6B8BB3", +"0 c #7091B8", +"a c #7495BC", +"b c #7596BD", +"c c #7496BD", +"d c #95AECC", +"e c #6F91B9", +"f c #6081A9", +"g c #3C6292", +"h c #365989", +"i c #4B6B97", +"j c #5676A1", +"k c #5F81AB", +"l c #6E93BF", +"m c #7097C2", +"n c #7297C3", +"o c #8FADCE", +"p c #618AB7", +"q c #476D9D", +"r c #5A779F", +"s c #1B4075", +"t c #1F4377", +"u c #365785", +"v c #4A6791", +"w c #4D6B94", +"x c #5D80AA", +"y c #6C93BF", +"z c #779DC6", +"A c #84A6CB", +"B c #5382B6", +"C c #3A659A", +"D c #5C789F", +"E c #224579", +"F c #1E4277", +"G c #274A7C", +"H c #2E4F7F", +"I c #335483", +"J c #44628D", +"K c #557197", +"L c #526E95", +"M c #506F99", +"N c #668FBD", +"O c #7FA3CA", +"P c #7299C4", +"Q c #36639A", +"R c #4F6D97", +"S c #345484", +"T c #305181", +"U c #42618A", +"V c #4E6992", +"W c #4A678F", +"X c #4B6992", +"Y c #4B6994", +"Z c #5F7A9E", +"` c #5E779C", +" . c #597398", +".. c #527099", +"+. c #618ABA", +"@. c #84A6CD", +"#. c #5D89BB", +"$. c #375A89", +"%. c #4C6992", +"&. c #3A5986", +"*. c #536E94", +"=. c #577197", +"-. c #6F92BB", +";. c #759ECA", +">. c #739CCA", +",. c #759DC9", +"'. c #6880A4", +"). c #5C7699", +"!. c #4F6F98", +"~. c #618DBD", +"{. c #81A4CA", +"]. c #4B7CB3", +"^. c #274F84", +"/. c #5A759B", +"(. c #375784", +"_. c #577196", +":. c #5A7498", +"<. c #6F89AB", +"[. c #8BADD1", +"}. c #7EA5D0", +"|. c #7FA6D0", +"1. c #7CA4CF", +"2. c #7CA4CE", +"3. c #789FC7", +"4. c #6984A7", +"5. c #5A7398", +"6. c #5076A5", +"7. c #6791C0", +"8. c #7198C3", +"9. c #2D5081", +"0. c #25477A", +"a. c #4C6890", +"b. c #627A9D", +"c. c #657DA1", +"d. c #A0BBD7", +"e. c #89AED6", +"f. c #8BAFD7", +"g. c #8BB0D7", +"h. c #8AAED6", +"i. c #86ACD4", +"j. c #82A8D2", +"k. c #7CA3CF", +"l. c #749DC8", +"m. c #6984A6", +"n. c #567298", +"o. c #5786BA", +"p. c #779CC6", +"q. c #5583B7", +"r. c #295086", +"s. c #2C4E7F", +"t. c #345483", +"u. c #5B7498", +"v. c #5D7699", +"w. c #93AAC4", +"x. c #9EBDDD", +"y. c #96B8DD", +"z. c #97B9DE", +"A. c #94B7DC", +"B. c #90B3DA", +"C. c #8AAFD6", +"D. c #83A9D2", +"E. c #7AA2CE", +"F. c #6F95BF", +"G. c #667E9F", +"H. c #577FAF", +"I. c #5585B8", +"J. c #759AC5", +"K. c #3F6FA7", +"L. c #254A80", +"M. c #5C769C", +"N. c #3C5B87", +"O. c #60789C", +"P. c #B0C3D8", +"Q. c #A1C0E3", +"R. c #A3C2E4", +"S. c #A3C3E5", +"T. c #A2C2E4", +"U. c #9EBFE2", +"V. c #98BADE", +"W. c #91B4DA", +"X. c #88AED6", +"Y. c #759ECB", +"Z. c #6885AD", +"`. c #5780B0", +" + c #5383B8", +".+ c #7299C3", +"++ c #4675AB", +"@+ c #395A89", +"#+ c #46648F", +"$+ c #3E5D88", +"%+ c #617A9C", +"&+ c #637B9D", +"*+ c #BFD1E4", +"=+ c #ADCBEA", +"-+ c #B0CDEB", +";+ c #AFCCEB", +">+ c #ACCAE9", +",+ c #A7C6E6", +"'+ c #9FC0E2", +")+ c #8DB1D8", +"!+ c #82A9D2", +"~+ c #77A0CC", +"{+ c #6A94C4", +"]+ c #5F8DBF", +"^+ c #5C89BA", +"/+ c #5C85B4", +"(+ c #3F5E8B", +"_+ c #3B5A86", +":+ c #647C9E", +"<+ c #61799D", +"[+ c #C1D2E4", +"}+ c #BBD5F1", +"|+ c #BCD6F2", +"1+ c #BAD5F1", +"2+ c #B5D1EE", +"3+ c #AECBEA", +"4+ c #A5C4E5", +"5+ c #9BBCE0", +"6+ c #84AAD3", +"7+ c #78A0CC", +"8+ c #6B96C6", +"9+ c #5F8CBF", +"0+ c #5282B8", +"a+ c #4F7FB4", +"b+ c #668CB7", +"c+ c #43618D", +"d+ c #325382", +"e+ c #5E779B", +"f+ c #5F789A", +"g+ c #BACADC", +"h+ c #C9E0F9", +"i+ c #C7DFF8", +"j+ c #C3DCF6", +"k+ c #BCD7F2", +"l+ c #B3CFED", +"m+ c #A8C7E7", +"n+ c #9DBEE1", +"o+ c #6A96C5", +"p+ c #5D8BBE", +"q+ c #5081B7", +"r+ c #497BB2", +"s+ c #698DB7", +"t+ c #45638E", +"u+ c #2E5080", +"v+ c #25487B", +"w+ c #506B93", +"x+ c #647B9E", +"y+ c #9DAFC7", +"z+ c #D6E9FD", +"A+ c #D1E8FE", +"B+ c #CAE2FA", +"C+ c #C1DAF5", +"D+ c #A9C8E8", +"E+ c #6894C4", +"F+ c #5A89BC", +"G+ c #4D7EB5", +"H+ c #4577AF", +"I+ c #6B8DB5", +"J+ c #3B6090", +"K+ c #3D5D8A", +"L+ c #3C5B89", +"M+ c #1D4276", +"N+ c #647C9C", +"O+ c #657D9E", +"P+ c #C2D4E6", +"Q+ c #D3E9FF", +"R+ c #CEE5FC", +"S+ c #C2DBF5", +"T+ c #80A7D1", +"U+ c #729CC9", +"V+ c #6491C2", +"W+ c #5686BA", +"X+ c #497BB3", +"Y+ c #5580B3", +"Z+ c #5B7EAA", +"`+ c #335686", +" @ c #4D6993", +".@ c #26497B", +"+@ c #4C6990", +"@@ c #6B82A2", +"#@ c #7D92B0", +"$@ c #C1D4E9", +"%@ c #C0DAF4", +"&@ c #B2CFED", +"*@ c #7BA3CE", +"=@ c #6F99C7", +"-@ c #6390C0", +";@ c #5886B9", +">@ c #4A7BB2", +",@ c #698CB7", +"'@ c #4C6F9C", +")@ c #284C7F", +"!@ c #577299", +"~@ c #2F517F", +"{@ c #536E93", +"]@ c #7086A5", +"^@ c #7B8FAC", +"/@ c #9FB3CD", +"(@ c #B8D2EE", +"_@ c #91B5DB", +":@ c #85ABD4", +"<@ c #7CA3CD", +"[@ c #729AC8", +"}@ c #6791C1", +"|@ c #5A87BA", +"1@ c #5280B4", +"2@ c #7A97BB", +"3@ c #416290", +"4@ c #2F5080", +"5@ c #4F6B91", +"6@ c #687F9F", +"7@ c #7B8FAB", +"8@ c #8396B0", +"9@ c #8AA1BF", +"0@ c #8CACCE", +"a@ c #8FB2D8", +"b@ c #86AAD2", +"c@ c #7DA3CC", +"d@ c #749BC7", +"e@ c #6993C1", +"f@ c #5B88B9", +"g@ c #7999C0", +"h@ c #587AA5", +"i@ c #21416E", +"j@ c #516D96", +"k@ c #415F8A", +"l@ c #567196", +"m@ c #637B9E", +"n@ c #6F84A3", +"o@ c #778BA7", +"p@ c #758BAA", +"q@ c #7490B3", +"r@ c #749BC5", +"s@ c #6992C0", +"t@ c #799DC4", +"u@ c #7291B6", +"v@ c #46658C", +"w@ c #264878", +"x@ c #4F6C94", +"y@ c #42608C", +"z@ c #294B7D", +"A@ c #4A6790", +"B@ c #567095", +"C@ c #597599", +"D@ c #587398", +"E@ c #536F96", +"F@ c #4F6D95", +"G@ c #7690B4", +"H@ c #7090B7", +"I@ c #4A6991", +"J@ c #2B4871", +"K@ c #41608C", +"L@ c #59749A", +"M@ c #234679", +"N@ c #214578", +"O@ c #3A5A87", +"P@ c #47648F", +"Q@ c #667EA2", +"R@ c #7188A9", +"S@ c #4B6892", +"T@ c #395985", +"U@ c #1C314F", +"V@ c #2E4F7D", +"W@ c #395986", +"X@ c #526E96", +"Y@ c #58739A", +"Z@ c #5B759B", +"`@ c #6A82A5", +" # c #627CA0", +".# c #365887", +"+# c #324B69", +"@# c #344B69", +"## c #34537D", +"$# c #294C7C", +"%# c #315383", +"&# c #40618D", +"*# c #40638F", +"=# c #3B5C87", +"-# c #36537A", +";# c #2E4156", +" ", +" . ", +" + @ # ", +" $ % & * = ", +" - ; > , ' ) ! ", +" ~ { ] ^ / ( _ ", +" : < [ } | 1 2 3 4 ", +" 5 6 7 8 9 0 a b c d e ", +" 7 f g h i j k l m n o p ", +" q r s s s t u v w x y z A B ", +" C D E s F G H I J K L M N O P ", +" Q R S s T U V W X Y Z ` ...+.@.#. ", +" $.%.t &.*.=.r -.;.>.,.0 '.).!.~.{.]. ", +" ^./.F (._.:.<.[.}.|.}.1.2.3.4.5.6.7.8. ", +" 9.v 0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q. ", +" r.v s.t.u.v.w.x.y.z.z.A.B.C.D.E.F.G.H.I.J.K. ", +" L.M.s N.b.O.P.Q.R.S.T.U.V.W.X.|.Y.Z.`. +.+++ ", +" @+#+s $+%+&+*+=+-+;+>+,+'+z.)+!+~+{+]+ +^+/+ ", +" (+S s _+:+<+[+}+|+1+2+3+4+5+B.6+7+8+9+0+a+b+ ", +" c+T s d+e+f+g+h+i+j+k+l+m+n+W.6+~+o+p+q+r+s+ ", +" t+u+s v+w+x+y+z+A+B+C+2+D+n+B.D.Y.E+F+G+H+I+J+ ", +" K+L+s M+N.N+O+P+Q+R+S+2+m+5+)+T+U+V+W+X+Y+Z+ ", +" `+ @s s .@+@@@#@$@R+%@&@4+z.e.*@=@-@;@>@,@'@ ", +" )@!@t s s ~@{@]@^@/@(@=+'+_@:@<@[@}@|@1@2@3@ ", +" S #+s s s 4@5@6@7@8@9@0@a@b@c@d@e@f@g@h@ ", +" i@j@u s s s G k@l@m@n@o@p@q@a r@s@t@u@v@ ", +" w@x@y@s s s s z@_+A@B@C@D@E@F@G@H@I@ ", +" J@K@L@y@M@s s N@z@S O@P@Q@R@S@T@ ", +" U@V@W@X@L@Y@Z@L@c.`@ #v .#+# ", +" @###$#%#&#*#=#-#;# ", +" ", +" "}; diff --git a/deluge/src/__init__.py b/deluge/src/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/src/core.py b/deluge/src/core.py new file mode 100644 index 000000000..9e73020df --- /dev/null +++ b/deluge/src/core.py @@ -0,0 +1,43 @@ +# +# core.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +# Instantiate the logger +import logging +log = logging.getLogger("deluge") + +class Core: + def __init__(self): + log.debug("Core init..") + + def test(self): + print "test" diff --git a/deluge/src/daemon.py b/deluge/src/daemon.py new file mode 100644 index 000000000..3158938f0 --- /dev/null +++ b/deluge/src/daemon.py @@ -0,0 +1,61 @@ +# +# daemon.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +# Instantiate the logger +import logging +log = logging.getLogger("deluge") + +import Pyro.core +from deluge.core import Core + +class Daemon: + def __init__(self): + # Instantiate the Manager class + self.core = Core() + # Initialize the Pyro core and daemon + Pyro.core.initServer(banner=0) + log.info("Pyro server initiliazed..") + self.daemon = Pyro.core.Daemon() + # Connect the Manager to the Pyro server + obj = Pyro.core.ObjBase() + obj.delegateTo(self.core) + self.uri = self.daemon.connect(obj, "core") + log.debug("uri: %s", self.uri) + + def start(self): + # Start the main loop for the pyro daemon + self.daemon.requestLoop() + + def getURI(self): + # Return the URI for the Pyro server + return self.uri diff --git a/deluge/src/main.py b/deluge/src/main.py new file mode 100644 index 000000000..13d8b680e --- /dev/null +++ b/deluge/src/main.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# main.py +# +# Copyright (C) Zach Tibbitts 2006 +# Copyright (C) Alon Zakai 2006 +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +# The main starting point for the program. This function is called when the +# user runs the command 'deluge'. + +import os +import signal + +from optparse import OptionParser +import deluge.common +from deluge.daemon import Daemon +from deluge.ui import Ui + +# Setup the logger +import logging +logging.basicConfig( + level=logging.DEBUG, + format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" +) +# Get the logger for deluge +log = logging.getLogger("deluge") + +def main(): + log.info("Starting Deluge..") + + # Setup the argument parser + parser = OptionParser(usage="%prog [options] [actions]", version=deluge.common.PROGRAM_VERSION) + parser.add_option("--daemon", dest="daemon", help="Start Deluge daemon", + metavar="DAEMON", action="store_true", default=False) + parser.add_option("--ui", dest="ui", help="Start Deluge UI", + metavar="UI", action="store_true", default=False) + + # Get the options and args from the OptionParser + (options, args) = parser.parse_args() + + log.debug("options: %s", options) + log.debug("args: %s", args) + + daemon = None + pid = None + uri = None + + # Start the daemon + if options.daemon: + log.info("Starting daemon..") + daemon = Daemon() + uri = daemon.getURI() + # We need to fork() the process to run it in the background... + pid = os.fork() + if not pid: + daemon.start() + + # Start the UI + if options.ui: + log.info("Starting ui..") + ui = Ui(uri) + + # Stop Deluge + log.info ("Stopping Deluge..") + + # Kill the daemon + if pid: + log.info("Killing daemon..") + os.kill(pid, signal.SIGTERM) + diff --git a/deluge/src/ui.py b/deluge/src/ui.py new file mode 100644 index 000000000..ef8113bc2 --- /dev/null +++ b/deluge/src/ui.py @@ -0,0 +1,48 @@ +# +# ui.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +# Instantiate the logger +import logging +log = logging.getLogger("deluge") + +import Pyro.core + +class Ui: + def __init__(self, core_uri): + log.debug("Ui init..") + log.debug("core_uri: %s", core_uri) + # Get the core manager from the Pyro server + self.core = Pyro.core.getProxyForURI(core_uri) + # Test + self.core.test() + diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 000000000..bfda8cd7d --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,231 @@ +#!python +"""Bootstrap setuptools installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from ez_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import sys +DEFAULT_VERSION = "0.6c6" +DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] + +md5_data = { + 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', + 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', + 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', + 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', + 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', + 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', + 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', + 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', + 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', + 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', + 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', + 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', + 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', + 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', + 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', + 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', + 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', + 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', + 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', + 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', + 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', + 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', + 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', + 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', +} + +import sys, os + +def _validate_md5(egg_name, data): + if egg_name in md5_data: + from md5 import md5 + digest = md5(data).hexdigest() + if digest != md5_data[egg_name]: + print >>sys.stderr, ( + "md5 validation of %s failed! (Possible download problem?)" + % egg_name + ) + sys.exit(2) + return data + + +def use_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + download_delay=15 +): + """Automatically find/download setuptools and make it available on sys.path + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end with + a '/'). `to_dir` is the directory where setuptools will be downloaded, if + it is not already available. If `download_delay` is specified, it should + be the number of seconds that will be paused before initiating a download, + should one be required. If an older version of setuptools is installed, + this routine will print a message to ``sys.stderr`` and raise SystemExit in + an attempt to abort the calling script. + """ + try: + import setuptools + if setuptools.__version__ == '0.0.1': + print >>sys.stderr, ( + "You have an obsolete version of setuptools installed. Please\n" + "remove it from your system entirely before rerunning this script." + ) + sys.exit(2) + except ImportError: + egg = download_setuptools(version, download_base, to_dir, download_delay) + sys.path.insert(0, egg) + import setuptools; setuptools.bootstrap_install_from = egg + + import pkg_resources + try: + pkg_resources.require("setuptools>="+version) + + except pkg_resources.VersionConflict, e: + # XXX could we install in a subprocess here? + print >>sys.stderr, ( + "The required version of setuptools (>=%s) is not available, and\n" + "can't be installed while this script is running. Please install\n" + " a more recent version first.\n\n(Currently using %r)" + ) % (version, e.args[0]) + sys.exit(2) + +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + delay = 15 +): + """Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download attempt. + """ + import urllib2, shutil + egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) + url = download_base + egg_name + saveto = os.path.join(to_dir, egg_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + from distutils import log + if delay: + log.warn(""" +--------------------------------------------------------------------------- +This script requires setuptools version %s to run (even to display +help). I will attempt to download it for you (from +%s), but +you may need to enable firewall access for this script first. +I will start the download in %d seconds. + +(Note: if this machine does not have network access, please obtain the file + + %s + +and place it in this directory before rerunning this script.) +---------------------------------------------------------------------------""", + version, download_base, delay, url + ); from time import sleep; sleep(delay) + log.warn("Downloading %s", url) + src = urllib2.urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = _validate_md5(egg_name, src.read()) + dst = open(saveto,"wb"); dst.write(data) + finally: + if src: src.close() + if dst: dst.close() + return os.path.realpath(saveto) + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + + try: + import setuptools + except ImportError: + egg = None + try: + egg = download_setuptools(version, delay=0) + sys.path.insert(0,egg) + from setuptools.command.easy_install import main + return main(list(argv)+[egg]) # we're done here + finally: + if egg and os.path.exists(egg): + os.unlink(egg) + else: + if setuptools.__version__ == '0.0.1': + # tell the user to uninstall obsolete version + use_setuptools(version) + + req = "setuptools>="+version + import pkg_resources + try: + pkg_resources.require(req) + except pkg_resources.VersionConflict: + try: + from setuptools.command.easy_install import main + except ImportError: + from easy_install import main + main(list(argv)+[download_setuptools(delay=0)]) + sys.exit(0) # try to force an exit + else: + if argv: + from setuptools.command.easy_install import main + main(argv) + else: + print "Setuptools version",version,"or greater has been installed." + print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' + + + +def update_md5(filenames): + """Update our built-in md5 registry""" + + import re + from md5 import md5 + + for name in filenames: + base = os.path.basename(name) + f = open(name,'rb') + md5_data[base] = md5(f.read()).hexdigest() + f.close() + + data = [" %r: %r,\n" % it for it in md5_data.items()] + data.sort() + repl = "".join(data) + + import inspect + srcfile = inspect.getsourcefile(sys.modules[__name__]) + f = open(srcfile, 'rb'); src = f.read(); f.close() + + match = re.search("\nmd5_data = {\n([^}]+)}", src) + if not match: + print >>sys.stderr, "Internal error!" + sys.exit(2) + + src = src[:match.start(1)] + repl + src[match.end(1):] + f = open(srcfile,'w') + f.write(src) + f.close() + + +if __name__=='__main__': + if len(sys.argv)>2 and sys.argv[1]=='--md5update': + update_md5(sys.argv[2:]) + else: + main(sys.argv[1:]) + + + + + diff --git a/libtorrent/AUTHORS b/libtorrent/AUTHORS new file mode 100644 index 000000000..dd56b1e89 --- /dev/null +++ b/libtorrent/AUTHORS @@ -0,0 +1,25 @@ + +Written by Arvid Norberg. Copyright (c) 2003-2007 + +Lots of testing, suggestions and contributions by: +Massaroddel +Tianhao Qiu. + +Contributions by: +Shyam +Magnus Jonsson +Daniel Wallin +Cory Nelson +Stas Khirman +Ryan Norton + +Building and maintainance of the autotools scripts: +Michael Wojciechowski +Peter Koeleman + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to University of UmeŒ for providing development and test hardware. + +Project is hosted by sourceforge. + diff --git a/libtorrent/COPYING b/libtorrent/COPYING new file mode 100644 index 000000000..f38f5cb2d --- /dev/null +++ b/libtorrent/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2003 - 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/libtorrent/ChangeLog b/libtorrent/ChangeLog new file mode 100644 index 000000000..3c1c6f14b --- /dev/null +++ b/libtorrent/ChangeLog @@ -0,0 +1,216 @@ + * Fixed bug in URL parser that failed to parse IPv6 addresses + * added peer download rate approximation + * added port filter for outgoing connection (to prevent + triggering firewalls) + * made most parameters configurable via session_settings + * added encryption support + * added parole mode for peers whose data fails the hash check. + * optimized heap usage in piece-picker and web seed downloader. + * fixed bug in DHT where older write tokens weren't accepted. + * added support for sparse files. + * introduced speed categories for peers and pieces, to separate + slow and fast peers. + * added a half-open tcp connection limit that takes all connections + in to account, not just peer connections. + * added alerts for filtered IPs. + * added support for SOCKS4 and 5 proxies and HTTP CONNECT proxies. + * fixed proper distributed copies calculation. + * added option to use openssl for sha-1 calculations. + * optimized the piece picker in the case where a peer is a seed. + * added support for local peer discovery + * removed the dependency on the compiled boost.date_time library + * deprecated torrent_info::print() + * added UPnP support + * fixed problem where peer interested flags were not updated correctly + when pieces were filtered + * improvements to ut_pex messages, including support for seed flag + * prioritizes upload bandwidth to peers that might send back data + * the following functions have been deprecated: + void torrent_handle::filter_piece(int index, bool filter) const; + void torrent_handle::filter_pieces(std::vector const& pieces) const; + bool torrent_handle::is_piece_filtered(int index) const; + std::vector torrent_handle::filtered_pieces() const; + void torrent_handle::filter_files(std::vector const& files) const; + + instead, use the piece_priority functions. + + * added support for NAT-PMP + * added support for piece priorities. Piece filtering is now set as + a priority + +release 0.12 + + * fixes to make the DHT more compatible + * http seed improvements including error reporting and url encoding issues. + * fixed bug where directories would be left behind when moving storage + in some cases. + * fixed crashing bug when restarting or stopping the DHT. + * added python binding, using boost.python + * improved character conversion on windows when strings are not utf-8. + * metadata extension now respects the private flag in the torrent. + * made the DHT to only be used as a fallback to trackers by default. + * added support for HTTP redirection support for web seeds. + * fixed race condition when accessing a torrent that was checking its + fast resume data. + * fixed a bug in the DHT which could be triggered if the network was + dropped or extremely rare cases. + * if the download rate is limited, web seeds will now only use left-over + bandwidth after all bt peers have used up as much bandwidth as they can. + * added the possibility to have libtorrent resolve the countries of + the peers in torrents. + * improved the bandwidth limiter (it now implements a leaky bucket/node bucket). + * improved the HTTP seed downloader to report accurate progress. + * added more client peer-id signatures to be recognized. + * added support for HTTP servers that skip the CR before the NL at line breaks. + * fixed bug in the HTTP code that only accepted headers case sensitive. + * fixed bug where one of the session constructors didn't initialize boost.filesystem. + * fixed bug when the initial checking of a torrent fails with an exception. + * fixed bug in DHT code which would send incorrect announce messages. + * fixed bug where the http header parser was case sensitive to the header + names. + * Implemented an optmization which frees the piece_picker once a torrent + turns into a seed. + * Added support for uT peer exchange extension, implemented by Massaroddel. + * Modified the quota management to offer better bandwidth balancing + between peers. + * logging now supports multiple sessions (different sessions now log + to different directories). + * fixed random number generator seed problem, generating the same + peer-id for sessions constructed the same second. + * added an option to accept multiple connections from the same IP. + * improved tracker logging. + * moved the file_pool into session. The number of open files is now + limited per session. + * fixed uninitialized private flag in torrent_info + * fixed long standing issue with file.cpp on windows. Replaced the low level + io functions used on windows. + * made it possible to associate a name with torrents without metadata. + * improved http-downloading performance by requesting entire pieces via + http. + * added plugin interface for extensions. And changed the interface for + enabling extensions. + +release 0.11 + + * added support for incorrectly encoded paths in torrent files + (assumes Latin-1 encoding and converts to UTF-8). + * added support for destructing session objects asynchronously. + * fixed bug with file_progress() with files = 0 bytes + * fixed a race condition bug in udp_tracker_connection that could + cause a crash. + * fixed bug occuring when increasing the sequenced download threshold + with max availability lower than previous threshold. + * fixed an integer overflow bug occuring when built with gcc 4.1.x + * fixed crasing bug when closing while checking a torrent + * fixed bug causing a crash with a torrent with piece length 0 + * added an extension to the DHT network protocol to support the + exchange of nodes with IPv6 addresses. + * modified the ip_filter api slightly to support IPv6 + * modified the api slightly to make sequenced download threshold + a per torrent-setting. + * changed the address type to support IPv6 + * fixed bug in piece picker which would not behave as + expected with regard to sequenced download threshold. + * fixed bug with file_progress() with files > 2 GB. + * added --enable-examples option to configure script. + * fixed problem with the resource distribution algorithm + (controlling e.g upload/download rates). + * fixed incorrect asserts in storage related to torrents with + zero-sized files. + * added support for trackerless torrents (with kademlia DHT). + * support for torrents with the private flag set. + * support for torrents containing bootstrap nodes for the + DHT network. + * fixed problem with the configure script on FreeBSD. + * limits the pipelining used on url-seeds. + * fixed problem where the shutdown always would delay for + session_settings::stop_tracker_timeout seconds. + * session::listen_on() won't reopen the socket in case the port and + interface is the same as the one currently in use. + * added http proxy support for web seeds. + * fixed problem where upload and download stats could become incorrect + in case of high cpu load. + * added more clients to the identifiable list. + * fixed fingerprint parser to cope with latest Mainline versions. + +release 0.10 + + * fixed a bug where the requested number of peers in a tracker request could + be too big. + * fixed a bug where empty files were not created in full allocation mode. + * fixed a bug in storage that would, in rare cases, fail to do a + complete check. + * exposed more settings for tweaking parameters in the piece-picker, + downloader and uploader (http_settings replaced by session_settings). + * tweaked default settings to improve high bandwidth transfers. + * improved the piece picker performance and made it possible to download + popular pieces in sequence to improve disk performance. + * added the possibility to control upload and download limits per peer. + * fixed problem with re-requesting skipped pieces when peer was sending pieces + out of fifo-order. + * added support for http seeding (the GetRight protocol) + * renamed identifiers called 'id' in the public interface to support linking + with Objective.C++ + * changed the extensions protocol to use the new one, which is also + implemented by uTorrent. + * factorized the peer_connection and added web_peer_connection which is + able to download from http-sources. + * converted the network code to use asio (resulted in slight api changes + dealing with network addresses). + * made libtorrent build in vc7 (patches from Allen Zhao) + * fixed bug caused when binding outgoing connections to a non-local interface. + * add_torrent() will now throw if called while the session object is + being closed. + * added the ability to limit the number of simultaneous half-open + TCP connections. Flags in peer_info has been added. + +release 0.9.1 + + * made the session disable file name checks within the boost.filsystem library + * fixed race condition in the sockets + * strings that are invalid utf-8 strings are now decoded with the + local codepage on windows + * added the ability to build libtorrent both as a shared library + * client_test can now monitor a directory for torrent files and automatically + start and stop downloads while running + * fixed problem with file_size() when building on windows with unicode support + * added a new torrent state, allocating + * added a new alert, metadata_failed_alert + * changed the interface to session::add_torrent for some speed optimizations. + * greatly improved the command line control of the example client_test. + * fixed bug where upload rate limit was not being applied. + * files that are being checked will no longer stall files that don't need + checking. + * changed the way libtorrent identifies support for its excentions + to look for 'ext' at the end of the peer-id. + * improved performance by adding a circle buffer for the send buffer. + * fixed bugs in the http tracker connection when using an http proxy. + * fixed problem with storage's file pool when creating torrents and then + starting to seed them. + * hard limit on remote request queue and timeout on requests (a timeout + triggers rerequests). This makes libtorrent work much better with + "broken" clients like BitComet which may ignore requests. + +Initial release 0.9 + + * multitracker support + * serves multiple torrents on a single port and a single thread + * supports http proxies and proxy authentication + * gzipped tracker-responses + * block level piece picker + * queues torrents for file check, instead of checking all of them in parallel + * uses separate threads for checking files and for main downloader + * upload and download rate limits + * piece-wise, unordered, incremental file allocation + * fast resume support + * supports files > 2 gigabytes + * supports the no_peer_id=1 extension + * support for udp-tracker protocol + * number of connections limit + * delays sending have messages + * can resume pieces downloaded in any order + * adjusts the length of the request queue depending on download rate + * supports compact=1 + * selective downloading + * ip filter + diff --git a/libtorrent/NEWS b/libtorrent/NEWS new file mode 100644 index 000000000..cd9658d6b --- /dev/null +++ b/libtorrent/NEWS @@ -0,0 +1,3 @@ + +initial release of libtorrent 0.9 + diff --git a/libtorrent/README b/libtorrent/README new file mode 100644 index 000000000..0b19bf61f --- /dev/null +++ b/libtorrent/README @@ -0,0 +1,25 @@ +libtorrent is a C++ library that aims to be a good alternative to all the +other bittorrent implementations around. It is a +library and not a full featured client, although it comes with a working +example client. + +The main goals of libtorrent are: + + * to be cpu efficient + * to be memory efficient + * to be very easy to use + +See docs/manual.html for more detailed build and usage instructions. + +To build with autotools, run: + + ./configure + +Followed by + + make + +When libtorrent is built, finish off by running the tests: + + make check + diff --git a/libtorrent/bindings/README.txt b/libtorrent/bindings/README.txt new file mode 100644 index 000000000..977ca8c1c --- /dev/null +++ b/libtorrent/bindings/README.txt @@ -0,0 +1,3 @@ +Documentation covering building and using the python binding for libtorrent +is located in the main doc directory. See docs/python_binding.html + diff --git a/libtorrent/bindings/python/Jamfile b/libtorrent/bindings/python/Jamfile new file mode 100755 index 000000000..afad3a1a7 --- /dev/null +++ b/libtorrent/bindings/python/Jamfile @@ -0,0 +1,29 @@ +import python ; + +use-project /torrent : ../.. ; + +python-extension libtorrent + : src/module.cpp + src/big_number.cpp + src/fingerprint.cpp + src/utility.cpp + src/session.cpp + src/entry.cpp + src/torrent_info.cpp + src/filesystem.cpp + src/torrent_handle.cpp + src/torrent_status.cpp + src/session_settings.cpp + src/version.cpp + src/alert.cpp + src/datetime.cpp + src/extensions.cpp + src/peer_plugin.cpp + src/docstrings.cpp + src/torrent.cpp + src/peer_info.cpp + /torrent//torrent + /boost/python//boost_python + : src + ; + diff --git a/libtorrent/bindings/python/client.py b/libtorrent/bindings/python/client.py new file mode 100755 index 000000000..1e04a9a08 --- /dev/null +++ b/libtorrent/bindings/python/client.py @@ -0,0 +1,355 @@ +#!/usr/bin/python + +# Copyright Daniel Wallin 2006. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +import sys +import libtorrent as lt +import time +import os.path +import sys + +class WindowsConsole: + def __init__(self): + self.console = Console.getconsole() + + def clear(self): + self.console.page() + + def write(self, str): + self.console.write(str) + + def sleep_and_input(self, seconds): + time.sleep(seconds) + if msvcrt.kbhit(): + return msvcrt.getch() + return None + +class UnixConsole: + def __init__(self): + self.fd = sys.stdin + self.old = termios.tcgetattr(self.fd.fileno()) + new = termios.tcgetattr(self.fd.fileno()) + new[3] = new[3] & ~termios.ICANON + new[6][termios.VTIME] = 0 + new[6][termios.VMIN] = 1 + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, new) + + sys.exitfunc = self._onexit + + def _onexit(self): + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, self.old) + + def clear(self): + sys.stdout.write('\033[2J\033[0;0H') + sys.stdout.flush() + + def write(self, str): + sys.stdout.write(str) + sys.stdout.flush() + + def sleep_and_input(self, seconds): + read,_,_ = select.select([self.fd.fileno()], [], [], seconds) + if len(read) > 0: + return self.fd.read(1) + return None + +if os.name == 'nt': + import Console + import msvcrt +else: + import termios + import select + +class PythonExtension(lt.torrent_plugin): + def __init__(self, alerts): + lt.torrent_plugin.__init__(self) + self.alerts = alerts + self.alerts.append('PythonExtension') + self.count = 0 + + def on_piece_pass(self, index): + self.alerts.append('got piece %d' % index) + + def on_piece_failed(self, index): + self.alerts.append('failed piece %d' % index) + + def tick(self): + self.count += 1 + if self.count >= 10: + self.count = 0 + self.alerts.append('PythonExtension tick') + +def write_line(console, line): + console.write(line) + +def add_suffix(val): + prefix = ['B', 'kB', 'MB', 'GB', 'TB'] + for i in range(len(prefix)): + if abs(val) < 1000: + if i == 0: + return '%5.3g%s' % (val, prefix[i]) + else: + return '%4.3g%s' % (val, prefix[i]) + val /= 1000 + + return '%6.3gPB' % val + +def progress_bar(progress, width): + progress_chars = int(progress * width + 0.5) + return progress_chars * '#' + (width - progress_chars) * '-' + +def print_peer_info(console, peers): + + out = ' down (total ) up (total ) q r flags block progress client\n' + + for p in peers: + + out += '%s/s ' % add_suffix(p.down_speed) + out += '(%s) ' % add_suffix(p.total_download) + out += '%s/s ' % add_suffix(p.up_speed) + out += '(%s) ' % add_suffix(p.total_upload) + out += '%2d ' % p.download_queue_length + out += '%2d ' % p.upload_queue_length + + if p.flags & lt.peer_info.interesting: out += 'I' + else: out += '.' + if p.flags & lt.peer_info.choked: out += 'C' + else: out += '.' + if p.flags & lt.peer_info.remote_interested: out += 'i' + else: out += '.' + if p.flags & lt.peer_info.remote_choked: out += 'c' + else: out += '.' + if p.flags & lt.peer_info.supports_extensions: out += 'e' + else: out += '.' + if p.flags & lt.peer_info.local_connection: out += 'l' + else: out += 'r' + out += ' ' + + if p.downloading_piece_index >= 0: + out += progress_bar(float(p.downloading_progress) / p.downloading_total, 15) + else: + out += progress_bar(0, 15) + out += ' ' + + if p.flags & lt.peer_info.handshake: + id = 'waiting for handshake' + elif p.flags & lt.peer_info.connecting: + id = 'connecting to peer' + elif p.flags & lt.peer_info.queued: + id = 'queued' + else: + id = p.client + + out += '%s\n' % id[:10] + + write_line(console, out) + + +def print_download_queue(console, download_queue): + + out = "" + + for e in download_queue: + out += '%4d: [' % e['piece_index']; + for b in e['blocks']: + s = b['state'] + if s == 3: + out += '#' + elif s == 2: + out += '=' + elif s == 1: + out += '-' + else: + out += ' ' + out += ']\n' + + write_line(console, out) + +def main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-p', '--port', + type='int', help='set listening port') + + parser.add_option('-r', '--ratio', + type='float', help='set the preferred upload/download ratio. 0 means infinite. Values smaller than 1 are clamped to 1') + + parser.add_option('-d', '--max-download-rate', + type='float', help='the maximum download rate given in kB/s. 0 means infinite.') + + parser.add_option('-u', '--max-upload-rate', + type='float', help='the maximum upload rate given in kB/s. 0 means infinite.') + + parser.add_option('-s', '--save-path', + type='string', help='the path where the downloaded file/folder should be placed.') + + parser.add_option('-a', '--allocation-mode', + type='string', help='sets mode used for allocating the downloaded files on disk. Possible options are [full | compact]') + + parser.set_defaults( + port=6881 + , ratio=0 + , max_download_rate=0 + , max_upload_rate=0 + , save_path='./' + , allocation_mode='compact' + ) + + (options, args) = parser.parse_args() + + if options.port < 0 or options.port > 65525: + options.port = 6881 + + options.max_upload_rate *= 1000 + options.max_download_rate *= 1000 + + if options.max_upload_rate <= 0: + options.max_upload_rate = -1 + if options.max_download_rate <= 0: + options.max_download_rate = -1 + + compact_allocation = options.allocation_mode == 'compact' + + 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_severity_level(lt.alert.severity_levels.info) +# ses.add_extension(lt.create_ut_pex_plugin); +# ses.add_extension(lt.create_metadata_plugin); + + handles = [] + alerts = [] + + # Extensions + # ses.add_extension(lambda x: PythonExtension(alerts)) + + for f in args: + e = lt.bdecode(open(f, 'rb').read()) + info = lt.torrent_info(e) + print 'Adding \'%s\'...' % info.name() + + try: + resume_data = lt.bdecode(open( + os.path.join(options.save_path, info.name() + '.fastresume'), 'rb').read()) + except: + resume_data = None + + h = ses.add_torrent(info, options.save_path, + resume_data=resume_data, compact_mode=compact_allocation) + + handles.append(h) + + h.set_max_connections(60) + h.set_max_uploads(-1) + h.set_ratio(options.ratio) + h.set_sequenced_download_threshold(15) + + if os.name == 'nt': + console = WindowsConsole() + else: + console = UnixConsole() + + alive = True + while alive: + console.clear() + + out = '' + + for h in handles: + if h.has_metadata(): + name = h.torrent_info().name()[:40] + else: + name = '-' + out += 'name: %-40s\n' % name + + s = h.status() + + if s.state != lt.torrent_status.seeding: + state_str = ['queued', 'checking', 'connecting', 'downloading metadata', \ + 'downloading', 'finished', 'seeding', 'allocating'] + out += state_str[s.state] + ' ' + + out += '%5.4f%% ' % (s.progress*100) + out += progress_bar(s.progress, 49) + out += '\n' + + out += 'total downloaded: %d Bytes\n' % s.total_done + out += 'peers: %d seeds: %d distributed copies: %d\n' % \ + (s.num_peers, s.num_seeds, s.distributed_copies) + out += '\n' + + out += 'download: %s/s (%s) ' \ + % (add_suffix(s.download_rate), add_suffix(s.total_download)) + out += 'upload: %s/s (%s) ' \ + % (add_suffix(s.upload_rate), add_suffix(s.total_upload)) + out += 'ratio: %s\n' % '0' + + if s.state != lt.torrent_status.seeding: + out += 'info-hash: %s\n' % h.info_hash() + out += 'next announce: %s\n' % s.next_announce + out += 'tracker: %s\n' % s.current_tracker + + write_line(console, out) + + print_peer_info(console, h.get_peer_info()) + print_download_queue(console, h.get_download_queue()) + + if True and s.state != lt.torrent_status.seeding: + out = '\n' + fp = h.file_progress() + ti = h.torrent_info() + for f,p in zip(ti.files(), fp): + out += progress_bar(p, 20) + out += ' ' + f.path + '\n' + write_line(console, out) + + write_line(console, 76 * '-' + '\n') + 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] + + for a in alerts: + if type(a) == str: + write_line(console, a + '\n') + else: + write_line(console, a.msg() + '\n') + + c = console.sleep_and_input(0.5) + + if not c: + continue + + if c == 'r': + for h in handles: h.force_reannounce() + elif c == 'q': + alive = False + elif c == 'p': + for h in handles: h.pause() + elif c == 'u': + for h in handles: h.resume() + + for h in handles: + if not h.is_valid() or not h.has_metadata(): + continue + h.pause() + data = lt.bencode(h.write_resume_data()) + open(os.path.join(options.save_path, h.torrent_info().name() + '.fastresume'), 'wb').write(data) + +main() + diff --git a/libtorrent/bindings/python/simple_client.py b/libtorrent/bindings/python/simple_client.py new file mode 100755 index 000000000..b06aba6bc --- /dev/null +++ b/libtorrent/bindings/python/simple_client.py @@ -0,0 +1,24 @@ +#!/usr/bin/python + +import libtorrent as lt +import time + +ses = lt.session() +ses.listen_on(6881, 6891) + +e = lt.bdecode(open("test.torrent", 'rb').read()) +info = lt.torrent_info(e) + +h = ses.add_torrent(info, "./", compact_mode = True) + +while (not h.is_seed()): + s = h.status() + + state_str = ['queued', 'checking', 'connecting', 'downloading metadata', \ + 'downloading', 'finished', 'seeding', 'allocating'] + print '%.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % \ + (s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, \ + s.num_peers, state_str[s.state]) + + time.sleep(1) + diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp new file mode 100755 index 000000000..f34cf4b5d --- /dev/null +++ b/libtorrent/bindings/python/src/alert.cpp @@ -0,0 +1,162 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +extern char const* alert_doc; +extern char const* alert_msg_doc; +extern char const* alert_severity_doc; +extern char const* listen_failed_alert_doc; +extern char const* file_error_alert_doc; +extern char const* tracker_announce_alert_doc; +extern char const* tracker_alert_doc; +extern char const* tracker_reply_alert_doc; +extern char const* tracker_warning_alert_doc; +extern char const* url_seed_alert_doc; +extern char const* hash_failed_alert_doc; +extern char const* peer_ban_alert_doc; +extern char const* peer_error_alert_doc; +extern char const* invalid_request_alert_doc; +extern char const* peer_request_doc; +extern char const* torrent_finished_alert_doc; +extern char const* metadata_failed_alert_doc; +extern char const* metadata_received_alert_doc; +extern char const* fastresume_rejected_alert_doc; + +void bind_alert() +{ + using boost::noncopyable; + + { + scope alert_scope = class_("alert", alert_doc, no_init) + .def( + "msg", &alert::msg, return_value_policy() + , alert_msg_doc + ) + .def("severity", &alert::severity, alert_severity_doc) + .def( + "__str__", &alert::msg, return_value_policy() + , alert_msg_doc + ) + ; + + enum_("severity_levels") + .value("debug", alert::debug) + .value("info", alert::info) + .value("warning", alert::warning) + .value("critical", alert::critical) + .value("fatal", alert::fatal) + .value("none", alert::none) + ; + } + + class_, noncopyable>( + "listen_failed_alert", listen_failed_alert_doc, no_init + ); + + class_, noncopyable>( + "file_error_alert", file_error_alert_doc, no_init + ) + .def_readonly("handle", &file_error_alert::handle) + ; + + class_, noncopyable>( + "tracker_announce_alert", tracker_announce_alert_doc, no_init + ) + .def_readonly("handle", &tracker_announce_alert::handle) + ; + + class_, noncopyable>( + "tracker_alert", tracker_alert_doc, no_init + ) + .def_readonly("handle", &tracker_alert::handle) + .def_readonly("times_in_row", &tracker_alert::times_in_row) + .def_readonly("status_code", &tracker_alert::status_code) + ; + + class_, noncopyable>( + "tracker_reply_alert", tracker_reply_alert_doc, no_init + ) + .def_readonly("handle", &tracker_reply_alert::handle) + ; + + class_, noncopyable>( + "tracker_warning_alert", tracker_warning_alert_doc, no_init + ) + .def_readonly("handle", &tracker_warning_alert::handle) + ; + + class_, noncopyable>( + "url_seed_alert", url_seed_alert_doc, no_init + ) + .def_readonly("url", &url_seed_alert::url) + ; + + class_, noncopyable>( + "hash_failed_alert", hash_failed_alert_doc, no_init + ) + .def_readonly("handle", &hash_failed_alert::handle) + .def_readonly("piece_index", &hash_failed_alert::piece_index) + ; + + class_, noncopyable>( + "peer_ban_alert", peer_ban_alert_doc, no_init + ) + .def_readonly("ip", &peer_ban_alert::ip) + .def_readonly("handle", &peer_ban_alert::handle) + ; + + class_, noncopyable>( + "peer_error_alert", peer_error_alert_doc, no_init + ) + .def_readonly("ip", &peer_error_alert::ip) + .def_readonly("pid", &peer_error_alert::pid) + ; + + class_, noncopyable>( + "invalid_request_alert", invalid_request_alert_doc, no_init + ) + .def_readonly("handle", &invalid_request_alert::handle) + .def_readonly("ip", &invalid_request_alert::ip) + .def_readonly("request", &invalid_request_alert::request) + .def_readonly("pid", &invalid_request_alert::pid) + ; + + class_("peer_request", peer_request_doc) + .def_readonly("piece", &peer_request::piece) + .def_readonly("start", &peer_request::start) + .def_readonly("length", &peer_request::length) + .def(self == self) + ; + + class_, noncopyable>( + "torrent_finished_alert", torrent_finished_alert_doc, no_init + ) + .def_readonly("handle", &torrent_finished_alert::handle) + ; + + class_, noncopyable>( + "metadata_failed_alert", metadata_failed_alert_doc, no_init + ) + .def_readonly("handle", &metadata_failed_alert::handle) + ; + + class_, noncopyable>( + "metadata_received_alert", metadata_received_alert_doc, no_init + ) + .def_readonly("handle", &metadata_received_alert::handle) + ; + + class_, noncopyable>( + "fastresume_rejected_alert", fastresume_rejected_alert_doc, no_init + ) + .def_readonly("handle", &fastresume_rejected_alert::handle) + ; +} + diff --git a/libtorrent/bindings/python/src/big_number.cpp b/libtorrent/bindings/python/src/big_number.cpp new file mode 100755 index 000000000..4e41df521 --- /dev/null +++ b/libtorrent/bindings/python/src/big_number.cpp @@ -0,0 +1,20 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +void bind_big_number() +{ + using namespace boost::python; + using namespace libtorrent; + + class_("big_number") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + ; +} + diff --git a/libtorrent/bindings/python/src/converters.cpp b/libtorrent/bindings/python/src/converters.cpp new file mode 100755 index 000000000..f684cc4f3 --- /dev/null +++ b/libtorrent/bindings/python/src/converters.cpp @@ -0,0 +1,5 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + diff --git a/libtorrent/bindings/python/src/datetime.cpp b/libtorrent/bindings/python/src/datetime.cpp new file mode 100755 index 000000000..da1347671 --- /dev/null +++ b/libtorrent/bindings/python/src/datetime.cpp @@ -0,0 +1,81 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include "optional.hpp" +#include + +using namespace boost::python; + +#if BOOST_VERSION < 103400 + +// From Boost 1.34 +object import(str name) +{ + // should be 'char const *' but older python versions don't use 'const' yet. + char *n = extract(name); + handle<> module(borrowed(PyImport_ImportModule(n))); + return object(module); +} + +#endif + +object datetime_timedelta; +object datetime_datetime; + +struct time_duration_to_python +{ + static PyObject* convert(boost::posix_time::time_duration const& d) + { + object result = datetime_timedelta( + 0 // days + , 0 // seconds + , d.total_microseconds() + ); + + return incref(result.ptr()); + } +}; + +struct ptime_to_python +{ + static PyObject* convert(boost::posix_time::ptime const& pt) + { + boost::gregorian::date date = pt.date(); + boost::posix_time::time_duration td = pt.time_of_day(); + + object result = datetime_datetime( + (int)date.year() + , (int)date.month() + , (int)date.day() + , td.hours() + , td.minutes() + , td.seconds() + ); + + return incref(result.ptr()); + } +}; + +void bind_datetime() +{ + object datetime = import("datetime").attr("__dict__"); + + datetime_timedelta = datetime["timedelta"]; + datetime_datetime = datetime["datetime"]; + + to_python_converter< + boost::posix_time::time_duration + , time_duration_to_python + >(); + + to_python_converter< + boost::posix_time::ptime + , ptime_to_python + >(); + + optional_to_python(); +} + diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp new file mode 100755 index 000000000..c9a0c8a85 --- /dev/null +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -0,0 +1,266 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// -- torrent_status -------------------------------------------------------- + +char const* torrent_status_doc = + "Represents the current status for a torrent.\n" + "Returned by `torrent_handle.status()`."; + +char const* torrent_status_state_doc = + "The torrents current task. One of `torrent_status.states`."; + +char const* torrent_status_paused_doc = + "Indicates if this torrent is paused or not."; + +char const* torrent_status_progress_doc = + "A value in the range [0, 1], that represents the progress of\n" + "the torrent's current task."; + +char const* torrent_status_next_announce_doc = + "The time until the torrent will announce itself to the\n" + "tracker. An instance of `datetime.timedelta`."; + +char const* torrent_status_announce_interval_doc = + "The interval at which the torrent will reannounce itself to the\n" + "tracker. An instance of `datetime.timedelta`."; + +char const* torrent_status_current_tracker_doc = + "The URL of the last working tracker. If no tracker request has\n" + "been successful yet, it's set to an empty string."; + +char const* torrent_status_total_download_doc = ""; +char const* torrent_status_total_upload_doc = ""; +char const* torrent_status_total_payload_download_doc = ""; +char const* torrent_status_total_payload_upload_doc = ""; +char const* torrent_status_total_failed_bytes_doc = ""; + +// -- session_status -------------------------------------------------------- + +char const* session_status_doc = + ""; +char const* session_status_has_incoming_connections_doc = + ""; +char const* session_status_upload_rate_doc = + ""; +char const* session_status_download_rate_doc = + ""; +char const* session_status_payload_upload_rate_doc = + ""; +char const* session_status_payload_download_rate_doc = + ""; +char const* session_status_total_download_doc = + ""; +char const* session_status_total_upload_doc = + ""; +char const* session_status_total_payload_download_doc = + ""; +char const* session_status_total_payload_upload_doc = + ""; +char const* session_status_num_peers_doc = + ""; +char const* session_status_dht_nodes_doc = + ""; +char const* session_status_cache_nodes_doc = + ""; +char const* session_status_dht_torrents_doc = + ""; + +// -- session --------------------------------------------------------------- + +char const* session_doc = + ""; +char const* session_init_doc = + "The `fingerprint` is a short string that will be used in\n" + "the peer-id to identify the client and the client's version.\n" + "For more details see the `fingerprint` class.\n" + "The constructor that only takes a fingerprint will not open\n" + "a listen port for the session, to get it running you'll have\n" + "to call `session.listen_on()`."; + +char const* session_listen_on_doc = + ""; +char const* session_is_listening_doc = + ""; +char const* session_listen_port_doc = + ""; + +char const* session_status_m_doc = + "Returns an instance of `session_status` with session wide-statistics\n" + "and status"; + +char const* session_start_dht_doc = + ""; +char const* session_stop_dht_doc = + ""; +char const* session_dht_state_doc = + ""; + +char const* session_add_torrent_doc = + "Adds a new torrent to the session. Return a `torrent_handle`.\n" + "\n" + ":Parameters:\n" + " - `torrent_info`: `torrent_info` instance representing the torrent\n" + " you want to add.\n" + " - `save_path`: The path to the directory where files will be saved.\n" + " - `resume_data (optional)`: The resume data for this torrent, as decoded\n" + " with `bdecode()`. This can be acquired from a running torrent with\n" + " `torrent_handle.write_resume_data()`.\n" + " - `compact_mode (optional)`: If set to true (default), the storage\n" + " will grow as more pieces are downloaded, and pieces are rearranged\n" + " to finally be in their correct places once the entire torrent has\n" + " been downloaded. If it is false, the entire storage is allocated\n" + " before download begins. I.e. the files contained in the torrent\n" + " are filled with zeros, and each downloaded piece is put in its\n" + " final place directly when downloaded.\n" + " - `block_size (optional)`: Sets the preferred request size, i.e.\n" + " the number of bytes to request from a peer at a time. This block size\n" + " must be a divisor of the piece size, and since the piece size is an\n" + " even power of 2, so must the block size be. If the block size given\n" + " here turns out to be greater than the piece size, it will simply be\n" + " clamped to the piece size.\n" + "\n" + ":Exceptions:\n" + " - `duplicate_torrent`: If the torrent you are trying to add already\n" + " exists in the session (is either queued for checking, being checked\n" + " or downloading) `add_torrent()` will throw `duplicate_torrent`.\n"; + +char const* session_remove_torrent_doc = + "Close all peer connections associated with the torrent and tell the\n" + "tracker that we've stopped participating in the swarm."; + +char const* session_set_download_rate_limit_doc = + ""; +char const* session_set_upload_rate_limit_doc = + ""; +char const* session_set_max_uploads_doc = + ""; +char const* session_set_max_connections_doc = + ""; +char const* session_set_max_half_open_connections_doc = + "Sets the maximum number of half-open connections libtorrent will\n" + "have when connecting to peers. A half-open connection is one where\n" + "connect() has been called, but the connection still hasn't been\n" + "established (nor failed). Windows XP Service Pack 2 sets a default,\n" + "system wide, limit of the number of half-open connections to 10. So, \n" + "this limit can be used to work nicer together with other network\n" + "applications on that system. The default is to have no limit, and passing\n" + "-1 as the limit, means to have no limit. When limiting the number of\n" + "simultaneous connection attempts, peers will be put in a queue waiting\n" + "for their turn to get connected."; + +char const* session_set_settings_doc = + ""; +char const* session_set_severity_level_doc = + ""; +char const* session_pop_alert_doc = + ""; + +// -- alert ----------------------------------------------------------------- + +char const* alert_doc = + "Base class for all concrete alert classes."; + +char const* alert_msg_doc = + "Returns a string describing this alert."; + +char const* alert_severity_doc = + "Returns the severity level for this alert, one of `alert.severity_levels`."; + +char const* listen_failed_alert_doc = + "This alert is generated when none of the ports, given in the\n" + "port range, to `session` can be opened for listening. This alert\n" + "is generated as severity level `alert.severity_levels.fatal`."; + +char const* file_error_alert_doc = + "If the storage fails to read or write files that it needs access\n" + "to, this alert is generated and the torrent is paused. It is\n" + "generated as severity level `alert.severity_levels.fatal`."; + +char const* tracker_announce_alert_doc = + "This alert is generated each time a tracker announce is sent\n" + "(or attempted to be sent). It is generated at severity level `alert.severity_levels.info`."; + +char const* tracker_alert_doc = + "This alert is generated on tracker time outs, premature\n" + "disconnects, invalid response or a HTTP response other than\n" + "\"200 OK\". From the alert you can get the handle to the torrent\n" + "the tracker belongs to. This alert is generated as severity level\n" + "`alert.severity_levels.warning`."; + +char const* tracker_reply_alert_doc = + "This alert is only for informational purpose. It is generated when\n" + "a tracker announce succeeds. It is generated with severity level\n" + "`alert.severity_levels.info`."; + +char const* tracker_warning_alert_doc = + "This alert is triggered if the tracker reply contains a warning\n" + "field. Usually this means that the tracker announce was successful\n" + ", but the tracker has a message to the client. The message string in\n" + "the alert will contain the warning message from the tracker. It is\n" + "generated with severity level `alert.severity_levels.warning`."; + +char const* url_seed_alert_doc = + "This alert is generated when a HTTP seed name lookup fails. This\n" + "alert is generated as severity level `alert.severity_levels.warning`."; + +char const* hash_failed_alert_doc = + "This alert is generated when a finished piece fails its hash check.\n" + "You can get the handle to the torrent which got the failed piece\n" + "and the index of the piece itself from the alert. This alert is\n" + "generated as severity level `alert.severity_levels.info`."; + +char const* peer_ban_alert_doc = + "This alert is generated when a peer is banned because it has sent\n" + "too many corrupt pieces to us. It is generated at severity level\n" + "`alert.severity_levels.info`. The handle member is a `torrent_handle` to the torrent that\n" + "this peer was a member of."; + +char const* peer_error_alert_doc = + "This alert is generated when a peer sends invalid data over the\n" + "peer-peer protocol. The peer will be disconnected, but you get its\n" + "ip address from the alert, to identify it. This alert is generated\n" + "is severity level `alert.severity_levels.debug`."; + +char const* invalid_request_alert_doc = + "This is a debug alert that is generated by an incoming invalid\n" + "piece request. The handle is a handle to the torrent the peer\n" + "is a member of. Ip is the address of the peer and the request is\n" + "the actual incoming request from the peer. The alert is generated\n" + "as severity level `alert.severity_levels.debug`."; + +char const* peer_request_doc = + "The `peer_request` contains the values the client sent in its\n" + "request message. ``piece`` is the index of the piece it want data\n" + "from, ``start`` is the offset within the piece where the data should be\n" + "read, and ``length`` is the amount of data it wants."; + +char const* torrent_finished_alert_doc = + "This alert is generated when a torrent switches from being a\n" + "downloader to a seed. It will only be generated once per torrent.\n" + "It contains a `torrent_handle` to the torrent in question. This alert\n" + "is generated as severity level `alert.severity_levels.info`."; + +char const* metadata_failed_alert_doc = + "This alert is generated when the metadata has been completely\n" + "received and the info-hash failed to match it. i.e. the\n" + "metadata that was received was corrupt. libtorrent will\n" + "automatically retry to fetch it in this case. This is only\n" + "relevant when running a torrent-less download, with the metadata\n" + "extension provided by libtorrent. It is generated at severity\n" + "level `alert.severity_levels.info`."; + +char const* metadata_received_alert_doc = + "This alert is generated when the metadata has been completely\n" + "received and the torrent can start downloading. It is not generated\n" + "on torrents that are started with metadata, but only those that\n" + "needs to download it from peers (when utilizing the libtorrent\n" + "extension). It is generated at severity level `alert.severity_levels.info`."; + +char const* fastresume_rejected_alert_doc = + "This alert is generated when a fastresume file has been passed\n" + "to `session.add_torrent` but the files on disk did not match the\n" + "fastresume file. The string explains the reason why the resume\n" + "file was rejected. It is generated at severity level `alert.severity_levels.warning`."; + diff --git a/libtorrent/bindings/python/src/entry.cpp b/libtorrent/bindings/python/src/entry.cpp new file mode 100755 index 000000000..b24f6a2cf --- /dev/null +++ b/libtorrent/bindings/python/src/entry.cpp @@ -0,0 +1,132 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +struct entry_to_python +{ + static object convert(entry::list_type const& l) + { + list result; + + for (entry::list_type::const_iterator i(l.begin()), e(l.end()); i != e; ++i) + { + result.append(*i); + } + + return result; + } + + static object convert(entry::dictionary_type const& d) + { + dict result; + + for (entry::dictionary_type::const_iterator i(d.begin()), e(d.end()); i != e; ++i) + result[i->first] = i->second; + + return result; + } + + static object convert0(entry const& e) + { + switch (e.type()) + { + case entry::int_t: + return object(e.integer()); + case entry::string_t: + return object(e.string()); + case entry::list_t: + return convert(e.list()); + case entry::dictionary_t: + return convert(e.dict()); + default: + return object(); + } + } + + static PyObject* convert(entry const& e) + { + return incref(convert0(e).ptr()); + } +}; + +struct entry_from_python +{ + entry_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* e) + { + return e; + } + + static entry construct0(object e) + { + if (extract(e).check()) + { + dict d = extract(e); + list items(d.items()); + std::size_t length = extract(items.attr("__len__")()); + entry result(entry::dictionary_t); + + for (std::size_t i = 0; i < length; ++i) + { + result.dict().insert( + std::make_pair( + extract(items[i][0])() + , construct0(items[i][1]) + ) + ); + } + + return result; + } + else if (extract(e).check()) + { + list l = extract(e); + + std::size_t length = extract(l.attr("__len__")()); + entry result(entry::list_t); + + for (std::size_t i = 0; i < length; ++i) + { + result.list().push_back(construct0(l[i])); + } + + return result; + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + + return entry(); + } + + static void construct(PyObject* e, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + new (storage) entry(construct0(object(borrowed(e)))); + data->convertible = storage; + } +}; + +void bind_entry() +{ + to_python_converter(); + entry_from_python(); +} + diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp new file mode 100755 index 000000000..f8fb30bdf --- /dev/null +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -0,0 +1,148 @@ +// Copyright Daniel Wallin, Arvid Norberg 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + + struct torrent_plugin_wrap : torrent_plugin, wrapper + { + boost::shared_ptr new_connection(peer_connection* p) + { + lock_gil lock; + + if (override f = this->get_override("new_connection")) + return f(ptr(p)); + return torrent_plugin::new_connection(p); + } + + boost::shared_ptr default_new_connection(peer_connection* p) + { + return this->torrent_plugin::new_connection(p); + } + + void on_piece_pass(int index) + { + lock_gil lock; + + if (override f = this->get_override("on_piece_pass")) + f(index); + else + torrent_plugin::on_piece_pass(index); + } + + void default_on_piece_pass(int index) + { + this->torrent_plugin::on_piece_pass(index); + } + + void on_piece_failed(int index) + { + lock_gil lock; + + if (override f = this->get_override("on_piece_failed")) + f(index); + else + torrent_plugin::on_piece_failed(index); + } + + void default_on_piece_failed(int index) + { + return this->torrent_plugin::on_piece_failed(index); + } + + void tick() + { + lock_gil lock; + + if (override f = this->get_override("tick")) + f(); + else + torrent_plugin::tick(); + } + + void default_tick() + { + return this->torrent_plugin::tick(); + } + + bool on_pause() + { + lock_gil lock; + + if (override f = this->get_override("on_pause")) + return f(); + return torrent_plugin::on_pause(); + } + + bool default_on_pause() + { + return this->torrent_plugin::on_pause(); + } + + bool on_resume() + { + lock_gil lock; + + if (override f = this->get_override("on_resume")) + return f(); + return torrent_plugin::on_resume(); + } + + bool default_on_resume() + { + return this->torrent_plugin::on_resume(); + } + }; + +} // namespace unnamed + +void bind_extensions() +{ + class_< + torrent_plugin_wrap, boost::shared_ptr, boost::noncopyable + >("torrent_plugin") + .def( + "new_connection" + , &torrent_plugin::new_connection, &torrent_plugin_wrap::default_new_connection + ) + .def( + "on_piece_pass" + , &torrent_plugin::on_piece_pass, &torrent_plugin_wrap::default_on_piece_pass + ) + .def( + "on_piece_failed" + , &torrent_plugin::on_piece_failed, &torrent_plugin_wrap::default_on_piece_failed + ) + .def( + "tick" + , &torrent_plugin::tick, &torrent_plugin_wrap::default_tick + ) + .def( + "on_pause" + , &torrent_plugin::on_pause, &torrent_plugin_wrap::default_on_pause + ) + .def( + "on_resume" + , &torrent_plugin::on_resume, &torrent_plugin_wrap::default_on_resume + ); + + // TODO move to it's own file + class_("peer_connection", no_init); + + def("create_ut_pex_plugin", create_ut_pex_plugin); + def("create_metadata_plugin", create_metadata_plugin); +} + diff --git a/libtorrent/bindings/python/src/filesystem.cpp b/libtorrent/bindings/python/src/filesystem.cpp new file mode 100755 index 000000000..3c5badb8e --- /dev/null +++ b/libtorrent/bindings/python/src/filesystem.cpp @@ -0,0 +1,51 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; + +struct path_to_python +{ + static PyObject* convert(boost::filesystem::path const& p) + { + return incref(object(p.string()).ptr()); + } +}; + +struct path_from_python +{ + path_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyString_Check(x) ? x : 0; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + boost::filesystem::path + >*)data)->storage.bytes; + new (storage) boost::filesystem::path(PyString_AsString(x)); + data->convertible = storage; + } +}; + +void bind_filesystem() +{ + to_python_converter(); + path_from_python(); + + using namespace boost::filesystem; + if (path::default_name_check_writable()) + path::default_name_check(no_check); +} + diff --git a/libtorrent/bindings/python/src/fingerprint.cpp b/libtorrent/bindings/python/src/fingerprint.cpp new file mode 100755 index 000000000..ca40f3d4d --- /dev/null +++ b/libtorrent/bindings/python/src/fingerprint.cpp @@ -0,0 +1,27 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +void bind_fingerprint() +{ + using namespace boost::python; + using namespace libtorrent; + + class_("fingerprint", no_init) + .def( + init( + (arg("id"), "major", "minor", "revision", "tag") + ) + ) + .def("__str__", &fingerprint::to_string) + .def_readonly("name", &fingerprint::name) + .def_readonly("major_version", &fingerprint::major_version) + .def_readonly("minor_version", &fingerprint::minor_version) + .def_readonly("revision_version", &fingerprint::revision_version) + .def_readonly("tag_version", &fingerprint::tag_version) + ; +} + diff --git a/libtorrent/bindings/python/src/gil.hpp b/libtorrent/bindings/python/src/gil.hpp new file mode 100755 index 000000000..d9534c92c --- /dev/null +++ b/libtorrent/bindings/python/src/gil.hpp @@ -0,0 +1,144 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef GIL_070107_HPP +# define GIL_070107_HPP + +# include +# include +# include +# include + +//namespace libtorrent { namespace python { + +// RAII helper to release GIL. +struct allow_threading_guard +{ + allow_threading_guard() + : save(PyEval_SaveThread()) + {} + + ~allow_threading_guard() + { + PyEval_RestoreThread(save); + } + + PyThreadState* save; +}; + +struct lock_gil +{ + lock_gil() + : state(PyGILState_Ensure()) + {} + + ~lock_gil() + { + PyGILState_Release(state); + } + + PyGILState_STATE state; +}; + +template +struct allow_threading +{ + allow_threading(F fn) + : fn(fn) + {} + + template + R operator()(A0& a0) + { + allow_threading_guard guard; + return (a0.*fn)(); + } + + template + R operator()(A0& a0, A1& a1) + { + allow_threading_guard guard; + return (a0.*fn)(a1); + } + + template + R operator()(A0& a0, A1& a1, A2& a2) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2); + } + + template + R operator()(A0& a0, A1& a1, A2& a2, A3& a3) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2,a3); + } + + template + R operator()(A0& a0, A1& a1, A2& a2, A3& a3, A4& a4) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2,a3,a4); + } + + template + R operator()(A0& a0, A1& a1, A2& a2, A3& a3, A4& a4, A5& a5) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2,a3,a4,a5); + } + + F fn; +}; + +template +struct visitor : boost::python::def_visitor > +{ + visitor(F fn) + : fn(fn) + {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + typedef typename boost::mpl::at_c::type return_type; + + cl.def( + name + , boost::python::make_function( + allow_threading(fn) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +// Member function adaptor that releases and aqcuires the GIL +// around the function call. +template +visitor allow_threads(F fn) +{ + return visitor(fn); +} + +//}} // namespace libtorrent::python + +#endif // GIL_070107_HPP + diff --git a/libtorrent/bindings/python/src/module.cpp b/libtorrent/bindings/python/src/module.cpp new file mode 100755 index 000000000..5c8d891d2 --- /dev/null +++ b/libtorrent/bindings/python/src/module.cpp @@ -0,0 +1,48 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +void bind_utility(); +void bind_fingerprint(); +void bind_big_number(); +void bind_session(); +void bind_entry(); +void bind_torrent_info(); +void bind_filesystem(); +void bind_torrent_handle(); +void bind_torrent_status(); +void bind_session_settings(); +void bind_version(); +void bind_alert(); +void bind_datetime(); +void bind_extensions(); +void bind_peer_plugin(); +void bind_torrent(); +void bind_peer_info(); + +BOOST_PYTHON_MODULE(libtorrent) +{ + Py_Initialize(); + PyEval_InitThreads(); + + bind_utility(); + bind_fingerprint(); + bind_big_number(); + bind_entry(); + bind_session(); + bind_torrent_info(); + bind_filesystem(); + bind_torrent_handle(); + bind_torrent_status(); + bind_session_settings(); + bind_version(); + bind_alert(); + bind_datetime(); + bind_extensions(); + bind_peer_plugin(); + bind_torrent(); + bind_peer_info(); +} + diff --git a/libtorrent/bindings/python/src/optional.hpp b/libtorrent/bindings/python/src/optional.hpp new file mode 100755 index 000000000..63138cc68 --- /dev/null +++ b/libtorrent/bindings/python/src/optional.hpp @@ -0,0 +1,31 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef OPTIONAL_070108_HPP +# define OPTIONAL_070108_HPP + +# include +# include + +template +struct optional_to_python +{ + optional_to_python() + { + boost::python::to_python_converter< + boost::optional, optional_to_python + >(); + } + + static PyObject* convert(boost::optional const& x) + { + if (!x) + return boost::python::incref(Py_None); + + return boost::python::incref(boost::python::object(*x).ptr()); + } +}; + +#endif // OPTIONAL_070108_HPP + diff --git a/libtorrent/bindings/python/src/peer_info.cpp b/libtorrent/bindings/python/src/peer_info.cpp new file mode 100755 index 000000000..49570b795 --- /dev/null +++ b/libtorrent/bindings/python/src/peer_info.cpp @@ -0,0 +1,63 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +void bind_peer_info() +{ + scope pi = class_("peer_info") + .def_readonly("flags", &peer_info::flags) + .def_readonly("ip", &peer_info::ip) + .def_readonly("up_speed", &peer_info::up_speed) + .def_readonly("down_speed", &peer_info::down_speed) + .def_readonly("payload_up_speed", &peer_info::payload_up_speed) + .def_readonly("payload_down_speed", &peer_info::payload_down_speed) + .def_readonly("total_download", &peer_info::total_download) + .def_readonly("total_upload", &peer_info::total_upload) + .def_readonly("pid", &peer_info::pid) + .def_readonly("pieces", &peer_info::pieces) + .def_readonly("upload_limit", &peer_info::upload_limit) + .def_readonly("download_limit", &peer_info::download_limit) + .def_readonly("load_balancing", &peer_info::load_balancing) + .def_readonly("download_queue_length", &peer_info::download_queue_length) + .def_readonly("upload_queue_length", &peer_info::upload_queue_length) + .def_readonly("downloading_piece_index", &peer_info::downloading_piece_index) + .def_readonly("downloading_block_index", &peer_info::downloading_block_index) + .def_readonly("downloading_progress", &peer_info::downloading_progress) + .def_readonly("downloading_total", &peer_info::downloading_total) + .def_readonly("client", &peer_info::client) + .def_readonly("connection_type", &peer_info::connection_type) + .def_readonly("source", &peer_info::source) + ; + + pi.attr("interesting") = (int)peer_info::interesting; + pi.attr("choked") = (int)peer_info::choked; + pi.attr("remote_interested") = (int)peer_info::remote_interested; + pi.attr("remote_choked") = (int)peer_info::remote_choked; + pi.attr("supports_extensions") = (int)peer_info::supports_extensions; + pi.attr("local_connection") = (int)peer_info::local_connection; + pi.attr("handshake") = (int)peer_info::handshake; + pi.attr("connecting") = (int)peer_info::connecting; + pi.attr("queued") = (int)peer_info::queued; + pi.attr("on_parole") = (int)peer_info::on_parole; + pi.attr("seed") = (int)peer_info::seed; +#ifndef TORRENT_DISABLE_ENCRYPTION + pi.attr("rc4_encrypted") = (int)peer_info::rc4_encrypted; + pi.attr("plaintext_encrypted") = (int)peer_info::plaintext_encrypted; +#endif + + pi.attr("standard_bittorrent") = 0; + pi.attr("web_seed") = 1; + + pi.attr("tracker") = 0x1; + pi.attr("dht") = 0x2; + pi.attr("pex") = 0x4; + pi.attr("lsd") = 0x8; + pi.attr("resume_data") = 0x10; +} + diff --git a/libtorrent/bindings/python/src/peer_plugin.cpp b/libtorrent/bindings/python/src/peer_plugin.cpp new file mode 100755 index 000000000..94b5fde66 --- /dev/null +++ b/libtorrent/bindings/python/src/peer_plugin.cpp @@ -0,0 +1,344 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + struct peer_plugin_wrap : peer_plugin, wrapper + { + void add_handshake(entry& e) + { + if (override f = this->get_override("add_handshake")) + e = call(f.ptr(), e); + else + peer_plugin::add_handshake(e); + } + + void default_add_handshake(entry& e) + { + this->peer_plugin::add_handshake(e); + } + + bool on_handshake() + { + if (override f = this->get_override("on_handshake")) + return f(); + else + return peer_plugin::on_handshake(); + } + + bool default_on_handshake() + { + return this->peer_plugin::on_handshake(); + } + + bool on_extension_handshake(entry const& e) + { + if (override f = this->get_override("on_extension_handshake")) + return f(e); + else + return peer_plugin::on_extension_handshake(e); + } + + bool default_on_extension_handshake(entry const& e) + { + return this->peer_plugin::on_extension_handshake(e); + } + + bool on_choke() + { + if (override f = this->get_override("on_choke")) + return f(); + else + return peer_plugin::on_choke(); + } + + bool default_on_choke() + { + return this->peer_plugin::on_choke(); + } + + bool on_unchoke() + { + if (override f = this->get_override("on_unchoke")) + return f(); + else + return peer_plugin::on_unchoke(); + } + + bool default_on_unchoke() + { + return this->peer_plugin::on_unchoke(); + } + + bool on_interested() + { + if (override f = this->get_override("on_interested")) + return f(); + else + return peer_plugin::on_interested(); + } + + bool default_on_interested() + { + return this->peer_plugin::on_interested(); + } + + bool on_not_interested() + { + if (override f = this->get_override("on_not_interested")) + return f(); + else + return peer_plugin::on_not_interested(); + } + + bool default_on_not_interested() + { + return this->peer_plugin::on_not_interested(); + } + + bool on_have(int index) + { + if (override f = this->get_override("on_have")) + return f(index); + else + return peer_plugin::on_have(index); + } + + bool default_on_have(int index) + { + return this->peer_plugin::on_have(index); + } + + bool on_bitfield(std::vector const& bitfield) + { + if (override f = this->get_override("on_bitfield")) + return f(bitfield); + else + return peer_plugin::on_bitfield(bitfield); + } + + bool default_on_bitfield(std::vector const& bitfield) + { + return this->peer_plugin::on_bitfield(bitfield); + } + + bool on_request(peer_request const& req) + { + if (override f = this->get_override("on_request")) + return f(req); + else + return peer_plugin::on_request(req); + } + + bool default_on_request(peer_request const& req) + { + return this->peer_plugin::on_request(req); + } + + bool on_piece(peer_request const& piece, char const* data) + { + if (override f = this->get_override("on_piece")) + return f(piece, data); + else + return peer_plugin::on_piece(piece, data); + } + + bool default_on_piece(peer_request const& piece, char const* data) + { + return this->peer_plugin::on_piece(piece, data); + } + + bool on_cancel(peer_request const& req) + { + if (override f = this->get_override("on_cancel")) + return f(req); + else + return peer_plugin::on_cancel(req); + } + + bool default_on_cancel(peer_request const& req) + { + return this->peer_plugin::on_cancel(req); + } + + bool on_extended(int length, int msg, buffer::const_interval body) + { + if (override f = this->get_override("on_extended")) + return f(length, msg, body); + else + return peer_plugin::on_extended(length, msg, body); + } + + bool default_on_extended(int length, int msg, buffer::const_interval body) + { + return this->peer_plugin::on_extended(length, msg, body); + } + + bool on_unknown_message(int length, int msg, buffer::const_interval body) + { + if (override f = this->get_override("on_unknown_message")) + return f(length, msg, body); + else + return peer_plugin::on_unknown_message(length, msg, body); + } + + bool default_on_unknown_message(int length, int msg, buffer::const_interval body) + { + return this->peer_plugin::on_unknown_message(length, msg, body); + } + + void on_piece_pass(int index) + { + if (override f = this->get_override("on_piece_pass")) + f(index); + else + peer_plugin::on_piece_pass(index); + } + + void default_on_piece_pass(int index) + { + this->peer_plugin::on_piece_pass(index); + } + + void on_piece_failed(int index) + { + if (override f = this->get_override("on_piece_failed")) + f(index); + else + peer_plugin::on_piece_failed(index); + } + + void default_on_piece_failed(int index) + { + this->peer_plugin::on_piece_failed(index); + } + + void tick() + { + if (override f = this->get_override("tick")) + f(); + else + peer_plugin::tick(); + } + + void default_tick() + { + this->peer_plugin::tick(); + } + + bool write_request(peer_request const& req) + { + if (override f = this->get_override("write_request")) + return f(req); + else + return peer_plugin::write_request(req); + } + + bool default_write_request(peer_request const& req) + { + return this->peer_plugin::write_request(req); + } + }; + + object get_buffer() + { + static char const data[] = "foobar"; + return object(handle<>(PyBuffer_FromMemory((void*)data, 6))); + } + +} // namespace unnamed + +void bind_peer_plugin() +{ + class_< + peer_plugin_wrap, boost::shared_ptr, boost::noncopyable + >("peer_plugin") + .def( + "add_handshake" + , &peer_plugin::add_handshake, &peer_plugin_wrap::default_add_handshake + ) + .def( + "on_handshake" + , &peer_plugin::on_handshake, &peer_plugin_wrap::default_on_handshake + ) + .def( + "on_extension_handshake" + , &peer_plugin::on_extension_handshake + , &peer_plugin_wrap::default_on_extension_handshake + ) + .def( + "on_choke" + , &peer_plugin::on_choke, &peer_plugin_wrap::default_on_choke + ) + .def( + "on_unchoke" + , &peer_plugin::on_unchoke, &peer_plugin_wrap::default_on_unchoke + ) + .def( + "on_interested" + , &peer_plugin::on_interested, &peer_plugin_wrap::default_on_interested + ) + .def( + "on_not_interested" + , &peer_plugin::on_not_interested, &peer_plugin_wrap::default_on_not_interested + ) + .def( + "on_have" + , &peer_plugin::on_have, &peer_plugin_wrap::default_on_have + ) + .def( + "on_bitfield" + , &peer_plugin::on_bitfield, &peer_plugin_wrap::default_on_bitfield + ) + .def( + "on_request" + , &peer_plugin::on_request, &peer_plugin_wrap::default_on_request + ) + .def( + "on_piece" + , &peer_plugin::on_piece, &peer_plugin_wrap::default_on_piece + ) + .def( + "on_cancel" + , &peer_plugin::on_cancel, &peer_plugin_wrap::default_on_cancel + ) + .def( + "on_piece_pass" + , &peer_plugin::on_piece_pass, &peer_plugin_wrap::default_on_piece_pass + ) + .def( + "on_piece_failed" + , &peer_plugin::on_piece_failed, &peer_plugin_wrap::default_on_piece_failed + ) + .def( + "tick" + , &peer_plugin::tick, &peer_plugin_wrap::default_tick + ) + .def( + "write_request" + , &peer_plugin::write_request, &peer_plugin_wrap::default_write_request + ) + // These seem to make VC7.1 freeze. Needs special handling. + + /*.def( + "on_extended" + , &peer_plugin::on_extended, &peer_plugin_wrap::default_on_extended + ) + .def( + "on_unknown_message" + , &peer_plugin::on_unknown_message, &peer_plugin_wrap::default_on_unknown_message + )*/ + ; + + def("get_buffer", &get_buffer); +} + diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp new file mode 100755 index 000000000..e6050d1bb --- /dev/null +++ b/libtorrent/bindings/python/src/session.cpp @@ -0,0 +1,214 @@ +// Copyright Daniel Wallin, Arvid Norberg 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; + +extern char const* session_status_doc; +extern char const* session_status_has_incoming_connections_doc; +extern char const* session_status_upload_rate_doc; +extern char const* session_status_download_rate_doc; +extern char const* session_status_payload_upload_rate_doc; +extern char const* session_status_payload_download_rate_doc; +extern char const* session_status_total_download_doc; +extern char const* session_status_total_upload_doc; +extern char const* session_status_total_payload_download_doc; +extern char const* session_status_total_payload_upload_doc; +extern char const* session_status_num_peers_doc; +extern char const* session_status_dht_nodes_doc; +extern char const* session_status_cache_nodes_doc; +extern char const* session_status_dht_torrents_doc; + +extern char const* session_doc; +extern char const* session_init_doc; +extern char const* session_listen_on_doc; +extern char const* session_is_listening_doc; +extern char const* session_listen_port_doc; +extern char const* session_status_m_doc; +extern char const* session_start_dht_doc; +extern char const* session_stop_dht_doc; +extern char const* session_dht_state_doc; +extern char const* session_add_torrent_doc; +extern char const* session_remove_torrent_doc; +extern char const* session_set_download_rate_limit_doc; +extern char const* session_set_upload_rate_limit_doc; +extern char const* session_set_max_uploads_doc; +extern char const* session_set_max_connections_doc; +extern char const* session_set_max_half_open_connections_doc; +extern char const* session_set_settings_doc; +extern char const* session_set_severity_level_doc; +extern char const* session_pop_alert_doc; + +namespace +{ + + bool listen_on(session& s, int min_, int max_, char const* interface) + { + allow_threading_guard guard; + return s.listen_on(std::make_pair(min_, max_), interface); + } + + struct invoke_extension_factory + { + invoke_extension_factory(object const& callback) + : cb(callback) + {} + + boost::shared_ptr operator()(torrent* t) + { + lock_gil lock; + return extract >(cb(ptr(t)))(); + } + + object cb; + }; + + void add_extension(session& s, object const& e) + { + allow_threading_guard guard; + s.add_extension(invoke_extension_factory(e)); + } + + torrent_handle add_torrent(session& s, torrent_info const& ti + , boost::filesystem::path const& save, entry const& resume + , bool compact, int block_size) + { + allow_threading_guard guard; + return s.add_torrent(ti, save, resume, compact, block_size + , default_storage_constructor); + } + +} // namespace unnamed + +void bind_session() +{ + class_("session_status", session_status_doc) + .def_readonly( + "has_incoming_connections", &session_status::has_incoming_connections + , session_status_has_incoming_connections_doc + ) + .def_readonly( + "upload_rate", &session_status::upload_rate + , session_status_upload_rate_doc + ) + .def_readonly( + "download_rate", &session_status::download_rate + , session_status_download_rate_doc + ) + .def_readonly( + "payload_upload_rate", &session_status::payload_upload_rate + , session_status_payload_upload_rate_doc + ) + .def_readonly( + "payload_download_rate", &session_status::payload_download_rate + , session_status_payload_download_rate_doc + ) + .def_readonly( + "total_download", &session_status::total_download + , session_status_total_download_doc + ) + .def_readonly( + "total_upload", &session_status::total_upload + , session_status_total_upload_doc + ) + .def_readonly( + "total_payload_download", &session_status::total_payload_download + , session_status_total_payload_download_doc + ) + .def_readonly( + "total_payload_upload", &session_status::total_payload_upload + , session_status_total_payload_upload_doc + ) + .def_readonly( + "num_peers", &session_status::num_peers + , session_status_num_peers_doc + ) +#ifndef TORRENT_DISABLE_DHT + .def_readonly( + "dht_nodes", &session_status::dht_nodes + , session_status_dht_nodes_doc + ) + .def_readonly( + "dht_cache_nodes", &session_status::dht_node_cache + , session_status_cache_nodes_doc + ) + .def_readonly( + "dht_torrents", &session_status::dht_torrents + , session_status_dht_torrents_doc + ) +#endif + ; + + class_("session", session_doc, no_init) + .def( + init(arg("fingerprint")=fingerprint("LT",0,1,0,0), session_init_doc) + ) + .def( + "listen_on", &listen_on + , (arg("min"), "max", arg("interface") = (char const*)0) + , session_listen_on_doc + ) + .def("is_listening", allow_threads(&session::is_listening), session_is_listening_doc) + .def("listen_port", allow_threads(&session::listen_port), session_listen_port_doc) + .def("status", allow_threads(&session::status), session_status_m_doc) +#ifndef TORRENT_DISABLE_DHT + .def("start_dht", allow_threads(&session::start_dht), session_start_dht_doc) + .def("stop_dht", allow_threads(&session::stop_dht), session_stop_dht_doc) + .def("dht_state", allow_threads(&session::dht_state), session_dht_state_doc) +#endif + .def( + "add_torrent", &add_torrent + , ( + arg("torrent_info"), "save_path", arg("resume_data") = entry() + , arg("compact_mode") = true, arg("block_size") = 16 * 1024 + ) + , session_add_torrent_doc + ) + .def("remove_torrent", allow_threads(&session::remove_torrent), session_remove_torrent_doc) + .def( + "set_download_rate_limit", allow_threads(&session::set_download_rate_limit) + , session_set_download_rate_limit_doc + ) + .def( + "set_upload_rate_limit", allow_threads(&session::set_upload_rate_limit) + , session_set_upload_rate_limit_doc + ) + .def( + "set_max_uploads", allow_threads(&session::set_max_uploads) + , session_set_max_uploads_doc + ) + .def( + "set_max_connections", allow_threads(&session::set_max_connections) + , session_set_max_connections_doc + ) + .def( + "set_max_half_open_connections", allow_threads(&session::set_max_half_open_connections) + , session_set_max_half_open_connections_doc + ) + .def("set_settings", allow_threads(&session::set_settings), session_set_settings_doc) + .def( + "set_severity_level", allow_threads(&session::set_severity_level) + , session_set_severity_level_doc + ) + .def("pop_alert", allow_threads(&session::pop_alert), session_pop_alert_doc) + .def("add_extension", &add_extension) + .def("set_peer_proxy", allow_threads(&session::set_peer_proxy)) + .def("set_tracker_proxy", allow_threads(&session::set_tracker_proxy)) + .def("set_web_seed_proxy", allow_threads(&session::set_web_seed_proxy)) +#ifndef TORRENT_DISABLE_DHT + .def("set_dht_proxy", allow_threads(&session::set_dht_proxy)) +#endif + ; + + def("supports_sparse_files", &supports_sparse_files); + + register_ptr_to_python >(); +} + diff --git a/libtorrent/bindings/python/src/session_settings.cpp b/libtorrent/bindings/python/src/session_settings.cpp new file mode 100755 index 000000000..2c7474c21 --- /dev/null +++ b/libtorrent/bindings/python/src/session_settings.cpp @@ -0,0 +1,77 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +void bind_session_settings() +{ + class_("session_settings") + .def_readwrite("user_agent", &session_settings::user_agent) + .def_readwrite("tracker_completion_timeout", &session_settings::tracker_completion_timeout) + .def_readwrite("tracker_receive_timeout", &session_settings::tracker_receive_timeout) + .def_readwrite("tracker_maximum_response_length", &session_settings::tracker_maximum_response_length) + .def_readwrite("piece_timeout", &session_settings::piece_timeout) + .def_readwrite("request_queue_time", &session_settings::request_queue_time) + .def_readwrite("max_allowed_in_request_queue", &session_settings::max_allowed_in_request_queue) + .def_readwrite("max_out_request_queue", &session_settings::max_out_request_queue) + .def_readwrite("whole_pieces_threshold", &session_settings::whole_pieces_threshold) + .def_readwrite("peer_timeout", &session_settings::peer_timeout) + .def_readwrite("urlseed_timeout", &session_settings::urlseed_timeout) + .def_readwrite("urlseed_pipeline_size", &session_settings::urlseed_pipeline_size) + .def_readwrite("file_pool_size", &session_settings::file_pool_size) + .def_readwrite("allow_multiple_connections_per_ip", &session_settings::allow_multiple_connections_per_ip) + .def_readwrite("max_failcount", &session_settings::max_failcount) + .def_readwrite("min_reconnect_time", &session_settings::min_reconnect_time) + .def_readwrite("peer_connect_timeout", &session_settings::peer_connect_timeout) + .def_readwrite("ignore_limits_on_local_network", &session_settings::ignore_limits_on_local_network) + .def_readwrite("connection_speed", &session_settings::connection_speed) + .def_readwrite("send_redundant_have", &session_settings::send_redundant_have) + .def_readwrite("lazy_bitfields", &session_settings::lazy_bitfields) + .def_readwrite("inactivity_timeout", &session_settings::inactivity_timeout) + .def_readwrite("unchoke_interval", &session_settings::unchoke_interval) +#ifndef TORRENT_DISABLE_DHT + .def_readwrite("use_dht_as_fallback", &session_settings::use_dht_as_fallback) +#endif + ; + + enum_("proxy_type") + .value("none", proxy_settings::none) + .value("socks4", proxy_settings::socks4) + .value("socks5", proxy_settings::socks5) + .value("socks5_pw", proxy_settings::socks5_pw) + .value("http", proxy_settings::http) + .value("http_pw", proxy_settings::http_pw) + ; + scope ps = class_("proxy_settings") + .def_readwrite("hostname", &proxy_settings::hostname) + .def_readwrite("port", &proxy_settings::port) + .def_readwrite("password", &proxy_settings::password) + .def_readwrite("username", &proxy_settings::username) + .def_readwrite("type", &proxy_settings::type) + ; + + enum_("enc_policy") + .value("forced", pe_settings::forced) + .value("enabled", pe_settings::enabled) + .value("disabled", pe_settings::disabled) + ; + + enum_("enc_level") + .value("rc4", pe_settings::rc4) + .value("plaintext", pe_settings::plaintext) + ; + + scope pes = class_("pe_settings") + .def_readwrite("out_enc_policy", &pe_settings::out_enc_policy) + .def_readwrite("in_enc_policy", &pe_settings::in_enc_policy) + .def_readwrite("allowed_enc_level", &pe_settings::allowed_enc_level) + .def_readwrite("prefer_rc4", &pe_settings::prefer_rc4) + ; + +} + diff --git a/libtorrent/bindings/python/src/torrent.cpp b/libtorrent/bindings/python/src/torrent.cpp new file mode 100755 index 000000000..7b2ba76b4 --- /dev/null +++ b/libtorrent/bindings/python/src/torrent.cpp @@ -0,0 +1,15 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +void bind_torrent() +{ + class_("torrent", no_init); +} + diff --git a/libtorrent/bindings/python/src/torrent_handle.cpp b/libtorrent/bindings/python/src/torrent_handle.cpp new file mode 100755 index 000000000..6e0bc5034 --- /dev/null +++ b/libtorrent/bindings/python/src/torrent_handle.cpp @@ -0,0 +1,163 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + + std::vector::const_iterator begin_trackers(torrent_handle& i) + { + allow_threading_guard guard; + return i.trackers().begin(); + } + + + std::vector::const_iterator end_trackers(torrent_handle& i) + { + allow_threading_guard guard; + return i.trackers().end(); + } + +} // namespace unnamed + +list file_progress(torrent_handle& handle) +{ + std::vector p; + + { + allow_threading_guard guard; + p.reserve(handle.get_torrent_info().num_files()); + handle.file_progress(p); + } + + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; +} + +list get_peer_info(torrent_handle const& handle) +{ + std::vector pi; + + { + allow_threading_guard guard; + handle.get_peer_info(pi); + } + + list result; + + for (std::vector::iterator i = pi.begin(); i != pi.end(); ++i) + { + result.append(*i); + } + + return result; +} + +void replace_trackers(torrent_handle& info, object trackers) +{ + object iter(trackers.attr("__iter__")()); + + std::vector result; + + for (;;) + { + handle<> entry(allow_null(PyIter_Next(iter.ptr()))); + + if (entry == handle<>()) + break; + + result.push_back(extract(object(entry))); + } + + allow_threading_guard guard; + info.replace_trackers(result); +} + +list get_download_queue(torrent_handle& handle) +{ + list ret; + + std::vector downloading; + + { + allow_threading_guard guard; + handle.get_download_queue(downloading); + } + + for (std::vector::iterator i = downloading.begin() + , end(downloading.end()); i != end; ++i) + { + dict partial_piece; + partial_piece["piece_index"] = i->piece_index; + partial_piece["blocks_in_piece"] = i->blocks_in_piece; + list block_list; + for (int k = 0; k < i->blocks_in_piece; ++k) + { + dict block_info; + block_info["state"] = i->blocks[k].state; + block_info["num_downloads"] = i->blocks[k].num_downloads; +// block_info["peer"] = i->info[k].peer; + block_list.append(block_info); + } + partial_piece["blocks"] = block_list; + + ret.append(partial_piece); + } + + return ret; +} + +void bind_torrent_handle() +{ + void (torrent_handle::*force_reannounce0)() const = &torrent_handle::force_reannounce; + void (torrent_handle::*force_reannounce1)(boost::posix_time::time_duration) const + = &torrent_handle::force_reannounce; + + return_value_policy copy; + +#define _ allow_threads + + class_("torrent_handle") + .def("status", _(&torrent_handle::status)) + .def("torrent_info", _(&torrent_handle::get_torrent_info), return_internal_reference<>()) + .def("is_valid", _(&torrent_handle::is_valid)) + .def("write_resume_data", _(&torrent_handle::write_resume_data)) + .def("force_reannounce", _(force_reannounce0)) + .def("force_reannounce", _(force_reannounce1)) + .def("set_tracker_login", _(&torrent_handle::set_tracker_login)) + .def("add_url_seed", _(&torrent_handle::add_url_seed)) + .def("set_ratio", _(&torrent_handle::set_ratio)) + .def("set_max_uploads", _(&torrent_handle::set_max_uploads)) + .def("set_max_connections", _(&torrent_handle::set_max_connections)) + .def("set_upload_limit", _(&torrent_handle::set_upload_limit)) + .def("set_download_limit", _(&torrent_handle::set_download_limit)) + .def("set_sequenced_download_threshold", _(&torrent_handle::set_sequenced_download_threshold)) + .def("pause", _(&torrent_handle::pause)) + .def("resume", _(&torrent_handle::resume)) + .def("is_paused", _(&torrent_handle::is_paused)) + .def("is_seed", _(&torrent_handle::is_seed)) + .def("filter_piece", _(&torrent_handle::filter_piece)) + .def("is_piece_filtered", _(&torrent_handle::is_piece_filtered)) + .def("has_metadata", _(&torrent_handle::has_metadata)) + .def("save_path", _(&torrent_handle::save_path)) + .def("move_storage", _(&torrent_handle::move_storage)) + .def("info_hash", _(&torrent_handle::info_hash), copy) + .def("file_progress", file_progress) + .def("trackers", range(begin_trackers, end_trackers)) + .def("replace_trackers", replace_trackers) + .def("get_peer_info", get_peer_info) + .def("get_download_queue", get_download_queue) + ; +} + diff --git a/libtorrent/bindings/python/src/torrent_info.cpp b/libtorrent/bindings/python/src/torrent_info.cpp new file mode 100755 index 000000000..1c31ec185 --- /dev/null +++ b/libtorrent/bindings/python/src/torrent_info.cpp @@ -0,0 +1,103 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + + std::vector::const_iterator begin_trackers(torrent_info& i) + { + return i.trackers().begin(); + } + + + std::vector::const_iterator end_trackers(torrent_info& i) + { + return i.trackers().end(); + } + + void add_node(torrent_info& ti, char const* hostname, int port) + { + ti.add_node(std::make_pair(hostname, port)); + } + + list nodes(torrent_info const& ti) + { + list result; + + typedef std::vector > list_type; + + for (list_type::const_iterator i = ti.nodes().begin(); i != ti.nodes().end(); ++i) + { + result.append(make_tuple(i->first, i->second)); + } + + return result; + } + +} // namespace unnamed + +void bind_torrent_info() +{ + return_value_policy copy; + + class_("torrent_info") + .def(init()) + .def(init()) + + .def("create_torrent", &torrent_info::create_torrent) + .def("set_comment", &torrent_info::set_comment) + .def("set_piece_size", &torrent_info::set_piece_size) + .def("set_creator", &torrent_info::set_creator) + .def("set_hash", &torrent_info::set_hash) + .def("add_tracker", &torrent_info::add_tracker, (arg("url"), arg("tier")=0)) + .def("add_file", &torrent_info::add_file) + .def("add_url_seed", &torrent_info::add_url_seed) + + .def("name", &torrent_info::name, copy) + .def("comment", &torrent_info::comment, copy) + .def("creator", &torrent_info::creator, copy) + .def("total_size", &torrent_info::total_size) + .def("piece_length", &torrent_info::piece_length) + .def("num_pieces", &torrent_info::num_pieces) + .def("info_hash", &torrent_info::info_hash, copy) + + .def("hash_for_piece", &torrent_info::hash_for_piece, copy) + .def("piece_size", &torrent_info::piece_size) + + .def("file_at", &torrent_info::file_at, return_internal_reference<>()) + .def("files", range(&torrent_info::begin_files, &torrent_info::end_files)) + + .def("priv", &torrent_info::priv) + .def("set_priv", &torrent_info::set_priv) + .def("trackers", range(begin_trackers, end_trackers)) + + .def("creation_date", &torrent_info::creation_date) + + .def("add_node", &add_node) + .def("nodes", &nodes) + ; + + class_("file_entry") + .add_property( + "path" + , make_getter( + &file_entry::path, return_value_policy() + ) + ) + .def_readonly("offset", &file_entry::offset) + .def_readonly("size", &file_entry::size) + ; + + class_("announce_entry", init()) + .def_readwrite("url", &announce_entry::url) + .def_readwrite("tier", &announce_entry::tier) + ; +} + diff --git a/libtorrent/bindings/python/src/torrent_status.cpp b/libtorrent/bindings/python/src/torrent_status.cpp new file mode 100755 index 000000000..c321f8bb2 --- /dev/null +++ b/libtorrent/bindings/python/src/torrent_status.cpp @@ -0,0 +1,107 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +object pieces(torrent_status const& s) +{ + list result; + + for (std::vector::const_iterator i(s.pieces->begin()), e(s.pieces->end()); i != e; ++i) + result.append(*i); + + return result; +} + +extern char const* torrent_status_doc; +extern char const* torrent_status_state_doc; +extern char const* torrent_status_paused_doc; +extern char const* torrent_status_progress_doc; +extern char const* torrent_status_next_announce_doc; +extern char const* torrent_status_announce_interval_doc; +extern char const* torrent_status_current_tracker_doc; +extern char const* torrent_status_total_download_doc; +extern char const* torrent_status_total_upload_doc; +extern char const* torrent_status_total_payload_download_doc; +extern char const* torrent_status_total_payload_upload_doc; +extern char const* torrent_status_total_failed_bytes_doc; + +void bind_torrent_status() +{ + scope status = class_("torrent_status", torrent_status_doc) + .def_readonly("state", &torrent_status::state, torrent_status_state_doc) + .def_readonly("paused", &torrent_status::paused, torrent_status_paused_doc) + .def_readonly("progress", &torrent_status::progress, torrent_status_progress_doc) + .add_property( + "next_announce" + , make_getter( + &torrent_status::next_announce, return_value_policy() + ) + , torrent_status_next_announce_doc + ) + .add_property( + "announce_interval" + , make_getter( + &torrent_status::announce_interval, return_value_policy() + ) + , torrent_status_announce_interval_doc + ) + .def_readonly( + "current_tracker", &torrent_status::current_tracker + , torrent_status_current_tracker_doc + ) + .def_readonly( + "total_download", &torrent_status::total_download + , torrent_status_total_download_doc + ) + .def_readonly( + "total_upload", &torrent_status::total_upload + , torrent_status_total_upload_doc + ) + .def_readonly( + "total_payload_download", &torrent_status::total_payload_download + , torrent_status_total_payload_download_doc + ) + .def_readonly( + "total_payload_upload", &torrent_status::total_payload_upload + , torrent_status_total_payload_upload_doc + ) + .def_readonly( + "total_failed_bytes", &torrent_status::total_failed_bytes + , torrent_status_total_failed_bytes_doc + ) + .def_readonly("total_redundant_bytes", &torrent_status::total_redundant_bytes) + .def_readonly("download_rate", &torrent_status::download_rate) + .def_readonly("upload_rate", &torrent_status::upload_rate) + .def_readonly("download_payload_rate", &torrent_status::download_payload_rate) + .def_readonly("upload_payload_rate", &torrent_status::upload_payload_rate) + .def_readonly("num_peers", &torrent_status::num_peers) + .def_readonly("num_complete", &torrent_status::num_complete) + .def_readonly("num_incomplete", &torrent_status::num_incomplete) + .add_property("pieces", pieces) + .def_readonly("num_pieces", &torrent_status::num_pieces) + .def_readonly("total_done", &torrent_status::total_done) + .def_readonly("total_wanted_done", &torrent_status::total_wanted_done) + .def_readonly("total_wanted", &torrent_status::total_wanted) + .def_readonly("num_seeds", &torrent_status::num_seeds) + .def_readonly("distributed_copies", &torrent_status::distributed_copies) + .def_readonly("block_size", &torrent_status::block_size) + ; + + enum_("states") + .value("queued_for_checking", torrent_status::queued_for_checking) + .value("checking_files", torrent_status::checking_files) + .value("connecting_to_tracker", torrent_status::connecting_to_tracker) + .value("downloading", torrent_status::downloading) + .value("finished", torrent_status::finished) + .value("seeding", torrent_status::seeding) + .value("allocating", torrent_status::allocating) + .export_values() + ; +} + diff --git a/libtorrent/bindings/python/src/utility.cpp b/libtorrent/bindings/python/src/utility.cpp new file mode 100755 index 000000000..9ae2d2a93 --- /dev/null +++ b/libtorrent/bindings/python/src/utility.cpp @@ -0,0 +1,37 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +object client_fingerprint_(peer_id const& id) +{ + boost::optional result = client_fingerprint(id); + return result ? object(*result) : object(); +} + +entry bdecode_(std::string const& data) +{ + return bdecode(data.begin(), data.end()); +} + +std::string bencode_(entry const& e) +{ + std::string result; + bencode(std::back_inserter(result), e); + return result; +} + +void bind_utility() +{ + def("identify_client", &libtorrent::identify_client); + def("client_fingerprint", &client_fingerprint_); + def("bdecode", &bdecode_); + def("bencode", &bencode_); +} + diff --git a/libtorrent/bindings/python/src/version.cpp b/libtorrent/bindings/python/src/version.cpp new file mode 100755 index 000000000..aaeafd900 --- /dev/null +++ b/libtorrent/bindings/python/src/version.cpp @@ -0,0 +1,16 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; + +void bind_version() +{ + scope().attr("version") = LIBTORRENT_VERSION; + scope().attr("version_major") = LIBTORRENT_VERSION_MAJOR; + scope().attr("version_minor") = LIBTORRENT_VERSION_MINOR; +} + diff --git a/libtorrent/include/Makefile.am b/libtorrent/include/Makefile.am new file mode 100644 index 000000000..010a2b181 --- /dev/null +++ b/libtorrent/include/Makefile.am @@ -0,0 +1,232 @@ +nobase_include_HEADERS = libtorrent/alert.hpp \ +libtorrent/alert_types.hpp \ +libtorrent/allocate_resources.hpp \ +libtorrent/bandwidth_manager.hpp \ +libtorrent/bencode.hpp \ +libtorrent/buffer.hpp \ +libtorrent/connection_queue.hpp \ +libtorrent/config.hpp \ +libtorrent/debug.hpp \ +libtorrent/disk_io_thread.hpp \ +libtorrent/entry.hpp \ +libtorrent/escape_string.hpp \ +libtorrent/extensions.hpp \ +libtorrent/file.hpp \ +libtorrent/file_pool.hpp \ +libtorrent/fingerprint.hpp \ +libtorrent/hasher.hpp \ +libtorrent/http_connection.hpp \ +libtorrent/http_stream.hpp \ +libtorrent/http_tracker_connection.hpp \ +libtorrent/identify_client.hpp \ +libtorrent/instantiate_connection.hpp \ +libtorrent/intrusive_ptr_base.hpp \ +libtorrent/invariant_check.hpp \ +libtorrent/io.hpp \ +libtorrent/ip_filter.hpp \ +libtorrent/lsd.hpp \ +libtorrent/peer.hpp \ +libtorrent/peer_connection.hpp \ +libtorrent/bt_peer_connection.hpp \ +libtorrent/web_peer_connection.hpp \ +libtorrent/pe_crypto.hpp \ +libtorrent/natpmp.hpp \ +libtorrent/pch.hpp \ +libtorrent/peer_id.hpp \ +libtorrent/peer_info.hpp \ +libtorrent/peer_request.hpp \ +libtorrent/piece_block_progress.hpp \ +libtorrent/piece_picker.hpp \ +libtorrent/policy.hpp \ +libtorrent/proxy_base.hpp \ +libtorrent/random_sample.hpp \ +libtorrent/resource_request.hpp \ +libtorrent/session.hpp \ +libtorrent/session_settings.hpp \ +libtorrent/session_status.hpp \ +libtorrent/size_type.hpp \ +libtorrent/socket.hpp \ +libtorrent/socket_type.hpp \ +libtorrent/socks4_stream.hpp \ +libtorrent/socks5_stream.hpp \ +libtorrent/stat.hpp \ +libtorrent/storage.hpp \ +libtorrent/time.hpp \ +libtorrent/torrent.hpp \ +libtorrent/torrent_handle.hpp \ +libtorrent/torrent_info.hpp \ +libtorrent/tracker_manager.hpp \ +libtorrent/udp_tracker_connection.hpp \ +libtorrent/utf8.hpp \ +libtorrent/xml_parse.hpp \ +libtorrent/variant_stream.hpp \ +libtorrent/version.hpp \ +libtorrent/time.hpp \ +libtorrent/aux_/allocate_resources_impl.hpp \ +libtorrent/aux_/session_impl.hpp \ +libtorrent/extensions/metadata_transfer.hpp \ +libtorrent/extensions/ut_pex.hpp \ +libtorrent/extensions/logger.hpp \ +\ +libtorrent/kademlia/closest_nodes.hpp \ +libtorrent/kademlia/dht_tracker.hpp \ +libtorrent/kademlia/find_data.hpp \ +libtorrent/kademlia/logging.hpp \ +libtorrent/kademlia/msg.hpp \ +libtorrent/kademlia/node.hpp \ +libtorrent/kademlia/node_entry.hpp \ +libtorrent/kademlia/node_id.hpp \ +libtorrent/kademlia/observer.hpp \ +libtorrent/kademlia/packet_iterator.hpp \ +libtorrent/kademlia/refresh.hpp \ +libtorrent/kademlia/routing_table.hpp \ +libtorrent/kademlia/rpc_manager.hpp \ +libtorrent/kademlia/traversal_algorithm.hpp \ +\ +libtorrent/asio.hpp \ +libtorrent/asio/basic_datagram_socket.hpp \ +libtorrent/asio/basic_deadline_timer.hpp \ +libtorrent/asio/basic_io_object.hpp \ +libtorrent/asio/basic_socket.hpp \ +libtorrent/asio/basic_socket_acceptor.hpp \ +libtorrent/asio/basic_socket_iostream.hpp \ +libtorrent/asio/basic_socket_streambuf.hpp \ +libtorrent/asio/basic_stream_socket.hpp \ +libtorrent/asio/basic_streambuf.hpp \ +libtorrent/asio/buffer.hpp \ +libtorrent/asio/buffered_read_stream.hpp \ +libtorrent/asio/buffered_read_stream_fwd.hpp \ +libtorrent/asio/buffered_stream.hpp \ +libtorrent/asio/buffered_stream_fwd.hpp \ +libtorrent/asio/buffered_write_stream.hpp \ +libtorrent/asio/buffered_write_stream_fwd.hpp \ +libtorrent/asio/completion_condition.hpp \ +libtorrent/asio/datagram_socket_service.hpp \ +libtorrent/asio/deadline_timer.hpp \ +libtorrent/asio/deadline_timer_service.hpp \ +libtorrent/asio/detail/bind_handler.hpp \ +libtorrent/asio/detail/buffer_resize_guard.hpp \ +libtorrent/asio/detail/buffered_stream_storage.hpp \ +libtorrent/asio/detail/call_stack.hpp \ +libtorrent/asio/detail/const_buffers_iterator.hpp \ +libtorrent/asio/detail/consuming_buffers.hpp \ +libtorrent/asio/detail/deadline_timer_service.hpp \ +libtorrent/asio/detail/epoll_reactor.hpp \ +libtorrent/asio/detail/epoll_reactor_fwd.hpp \ +libtorrent/asio/detail/event.hpp \ +libtorrent/asio/detail/fd_set_adapter.hpp \ +libtorrent/asio/detail/handler_alloc_helpers.hpp \ +libtorrent/asio/detail/handler_invoke_helpers.hpp \ +libtorrent/asio/detail/hash_map.hpp \ +libtorrent/asio/detail/io_control.hpp \ +libtorrent/asio/detail/kqueue_reactor.hpp \ +libtorrent/asio/detail/kqueue_reactor_fwd.hpp \ +libtorrent/asio/detail/local_free_on_block_exit.hpp \ +libtorrent/asio/detail/mutex.hpp \ +libtorrent/asio/detail/noncopyable.hpp \ +libtorrent/asio/detail/null_event.hpp \ +libtorrent/asio/detail/null_mutex.hpp \ +libtorrent/asio/detail/null_signal_blocker.hpp \ +libtorrent/asio/detail/null_thread.hpp \ +libtorrent/asio/detail/null_tss_ptr.hpp \ +libtorrent/asio/detail/old_win_sdk_compat.hpp \ +libtorrent/asio/detail/pipe_select_interrupter.hpp \ +libtorrent/asio/detail/pop_options.hpp \ +libtorrent/asio/detail/posix_event.hpp \ +libtorrent/asio/detail/posix_fd_set_adapter.hpp \ +libtorrent/asio/detail/posix_mutex.hpp \ +libtorrent/asio/detail/posix_signal_blocker.hpp \ +libtorrent/asio/detail/posix_thread.hpp \ +libtorrent/asio/detail/posix_tss_ptr.hpp \ +libtorrent/asio/detail/push_options.hpp \ +libtorrent/asio/detail/reactive_socket_service.hpp \ +libtorrent/asio/detail/reactor_op_queue.hpp \ +libtorrent/asio/detail/resolver_service.hpp \ +libtorrent/asio/detail/scoped_lock.hpp \ +libtorrent/asio/detail/select_interrupter.hpp \ +libtorrent/asio/detail/select_reactor.hpp \ +libtorrent/asio/detail/select_reactor_fwd.hpp \ +libtorrent/asio/detail/service_registry.hpp \ +libtorrent/asio/detail/service_registry_fwd.hpp \ +libtorrent/asio/detail/service_base.hpp \ +libtorrent/asio/detail/service_id.hpp \ +libtorrent/asio/detail/signal_blocker.hpp \ +libtorrent/asio/detail/signal_init.hpp \ +libtorrent/asio/detail/socket_holder.hpp \ +libtorrent/asio/detail/socket_ops.hpp \ +libtorrent/asio/detail/socket_option.hpp \ +libtorrent/asio/detail/socket_select_interrupter.hpp \ +libtorrent/asio/detail/socket_types.hpp \ +libtorrent/asio/detail/strand_service.hpp \ +libtorrent/asio/detail/task_io_service.hpp \ +libtorrent/asio/detail/task_io_service_fwd.hpp \ +libtorrent/asio/detail/thread.hpp \ +libtorrent/asio/detail/throw_error.hpp \ +libtorrent/asio/detail/timer_queue.hpp \ +libtorrent/asio/detail/timer_queue_base.hpp \ +libtorrent/asio/detail/tss_ptr.hpp \ +libtorrent/asio/detail/win_event.hpp \ +libtorrent/asio/detail/win_fd_set_adapter.hpp \ +libtorrent/asio/detail/win_iocp_io_service.hpp \ +libtorrent/asio/detail/win_iocp_io_service_fwd.hpp \ +libtorrent/asio/detail/win_iocp_operation.hpp \ +libtorrent/asio/detail/win_iocp_socket_service.hpp \ +libtorrent/asio/detail/win_mutex.hpp \ +libtorrent/asio/detail/win_signal_blocker.hpp \ +libtorrent/asio/detail/win_thread.hpp \ +libtorrent/asio/detail/win_tss_ptr.hpp \ +libtorrent/asio/detail/winsock_init.hpp \ +libtorrent/asio/detail/wrapped_handler.hpp \ +libtorrent/asio/error.hpp \ +libtorrent/asio/error_code.hpp \ +libtorrent/asio/handler_alloc_hook.hpp \ +libtorrent/asio/handler_invoke_hook.hpp \ +libtorrent/asio/impl/error_code.ipp \ +libtorrent/asio/impl/io_service.ipp \ +libtorrent/asio/impl/read.ipp \ +libtorrent/asio/impl/read_until.ipp \ +libtorrent/asio/impl/write.ipp \ +libtorrent/asio/io_service.hpp \ +libtorrent/asio/ip/address.hpp \ +libtorrent/asio/ip/address_v4.hpp \ +libtorrent/asio/ip/address_v6.hpp \ +libtorrent/asio/ip/basic_endpoint.hpp \ +libtorrent/asio/ip/basic_resolver.hpp \ +libtorrent/asio/ip/basic_resolver_entry.hpp \ +libtorrent/asio/ip/basic_resolver_iterator.hpp \ +libtorrent/asio/ip/basic_resolver_query.hpp \ +libtorrent/asio/ip/detail/socket_option.hpp \ +libtorrent/asio/ip/host_name.hpp \ +libtorrent/asio/ip/multicast.hpp \ +libtorrent/asio/ip/resolver_query_base.hpp \ +libtorrent/asio/ip/resolver_service.hpp \ +libtorrent/asio/ip/tcp.hpp \ +libtorrent/asio/ip/udp.hpp \ +libtorrent/asio/is_read_buffered.hpp \ +libtorrent/asio/is_write_buffered.hpp \ +libtorrent/asio/placeholders.hpp \ +libtorrent/asio/read.hpp \ +libtorrent/asio/read_until.hpp \ +libtorrent/asio/socket_acceptor_service.hpp \ +libtorrent/asio/socket_base.hpp \ +libtorrent/asio/ssl/basic_context.hpp \ +libtorrent/asio/ssl/context.hpp \ +libtorrent/asio/ssl/context_base.hpp \ +libtorrent/asio/ssl/context_service.hpp \ +libtorrent/asio/ssl/detail/openssl_context_service.hpp \ +libtorrent/asio/ssl/detail/openssl_init.hpp \ +libtorrent/asio/ssl/detail/openssl_operation.hpp \ +libtorrent/asio/ssl/detail/openssl_stream_service.hpp \ +libtorrent/asio/ssl/detail/openssl_types.hpp \ +libtorrent/asio/ssl/stream.hpp \ +libtorrent/asio/ssl/stream_base.hpp \ +libtorrent/asio/ssl/stream_service.hpp \ +libtorrent/asio/ssl.hpp \ +libtorrent/asio/strand.hpp \ +libtorrent/asio/stream_socket_service.hpp \ +libtorrent/asio/streambuf.hpp \ +libtorrent/asio/system_error.hpp \ +libtorrent/asio/thread.hpp \ +libtorrent/asio/time_traits.hpp \ +libtorrent/asio/write.hpp + diff --git a/libtorrent/include/asio.hpp b/libtorrent/include/asio.hpp new file mode 100644 index 000000000..ae6455bdb --- /dev/null +++ b/libtorrent/include/asio.hpp @@ -0,0 +1,74 @@ +// +// asio.hpp +// ~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_HPP +#define ASIO_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/basic_datagram_socket.hpp" +#include "asio/basic_deadline_timer.hpp" +#include "asio/basic_io_object.hpp" +#include "asio/basic_socket_acceptor.hpp" +#include "asio/basic_socket_iostream.hpp" +#include "asio/basic_socket_streambuf.hpp" +#include "asio/basic_stream_socket.hpp" +#include "asio/basic_streambuf.hpp" +#include "asio/buffer.hpp" +#include "asio/buffered_read_stream_fwd.hpp" +#include "asio/buffered_read_stream.hpp" +#include "asio/buffered_stream_fwd.hpp" +#include "asio/buffered_stream.hpp" +#include "asio/buffered_write_stream_fwd.hpp" +#include "asio/buffered_write_stream.hpp" +#include "asio/completion_condition.hpp" +#include "asio/datagram_socket_service.hpp" +#include "asio/deadline_timer_service.hpp" +#include "asio/deadline_timer.hpp" +#include "asio/error.hpp" +#include "asio/error_code.hpp" +#include "asio/handler_alloc_hook.hpp" +#include "asio/handler_invoke_hook.hpp" +#include "asio/io_service.hpp" +#include "asio/ip/address.hpp" +#include "asio/ip/address_v4.hpp" +#include "asio/ip/address_v6.hpp" +#include "asio/ip/basic_endpoint.hpp" +#include "asio/ip/basic_resolver.hpp" +#include "asio/ip/basic_resolver_entry.hpp" +#include "asio/ip/basic_resolver_iterator.hpp" +#include "asio/ip/basic_resolver_query.hpp" +#include "asio/ip/host_name.hpp" +#include "asio/ip/multicast.hpp" +#include "asio/ip/resolver_query_base.hpp" +#include "asio/ip/resolver_service.hpp" +#include "asio/ip/tcp.hpp" +#include "asio/ip/udp.hpp" +#include "asio/ip/unicast.hpp" +#include "asio/ip/v6_only.hpp" +#include "asio/is_read_buffered.hpp" +#include "asio/is_write_buffered.hpp" +#include "asio/placeholders.hpp" +#include "asio/read.hpp" +#include "asio/read_until.hpp" +#include "asio/socket_acceptor_service.hpp" +#include "asio/socket_base.hpp" +#include "asio/strand.hpp" +#include "asio/stream_socket_service.hpp" +#include "asio/streambuf.hpp" +#include "asio/system_error.hpp" +#include "asio/thread.hpp" +#include "asio/time_traits.hpp" +#include "asio/version.hpp" +#include "asio/write.hpp" + +#endif // ASIO_HPP diff --git a/libtorrent/include/asio/CVS/Entries b/libtorrent/include/asio/CVS/Entries new file mode 100644 index 000000000..43749a985 --- /dev/null +++ b/libtorrent/include/asio/CVS/Entries @@ -0,0 +1,45 @@ +/basic_datagram_socket.hpp/1.40/Thu Jan 4 05:44:43 2007// +/basic_deadline_timer.hpp/1.23/Sun May 20 00:49:02 2007// +/basic_io_object.hpp/1.8/Thu Jan 4 05:44:43 2007// +/basic_socket.hpp/1.16/Mon Jan 8 22:12:45 2007// +/basic_socket_acceptor.hpp/1.58/Fri Feb 9 05:47:48 2007// +/basic_socket_iostream.hpp/1.8/Thu Jan 18 11:41:36 2007// +/basic_socket_streambuf.hpp/1.6/Thu Jan 18 11:41:36 2007// +/basic_stream_socket.hpp/1.69/Mon Jan 8 22:12:45 2007// +/basic_streambuf.hpp/1.12/Thu Jan 4 10:23:31 2007// +/buffer.hpp/1.23/Thu Jun 21 14:03:36 2007// +/buffered_read_stream.hpp/1.17/Thu Jan 4 05:44:43 2007// +/buffered_read_stream_fwd.hpp/1.5/Thu Jan 4 05:44:43 2007// +/buffered_stream.hpp/1.32/Thu Jan 4 05:44:43 2007// +/buffered_stream_fwd.hpp/1.9/Thu Jan 4 05:44:43 2007// +/buffered_write_stream.hpp/1.17/Thu Jan 4 05:44:43 2007// +/buffered_write_stream_fwd.hpp/1.5/Thu Jan 4 05:44:43 2007// +/completion_condition.hpp/1.5/Thu Jan 4 05:44:43 2007// +/datagram_socket_service.hpp/1.34/Mon Jan 8 22:12:45 2007// +/deadline_timer.hpp/1.6/Thu Jan 4 05:44:43 2007// +/deadline_timer_service.hpp/1.29/Mon Jan 8 02:47:13 2007// +/error.hpp/1.39/Mon Jan 8 22:12:45 2007// +/error_code.hpp/1.4/Mon Jan 8 22:12:45 2007// +/handler_alloc_hook.hpp/1.11/Thu Jan 4 05:44:43 2007// +/handler_invoke_hook.hpp/1.3/Thu Jan 4 05:44:43 2007// +/io_service.hpp/1.24/Sun May 20 00:49:02 2007// +/is_read_buffered.hpp/1.6/Thu Jan 4 05:44:43 2007// +/is_write_buffered.hpp/1.6/Thu Jan 4 05:44:43 2007// +/placeholders.hpp/1.10/Thu Jan 4 05:44:43 2007// +/read.hpp/1.22/Thu Jan 4 05:44:43 2007// +/read_until.hpp/1.8/Thu Jan 4 05:44:44 2007// +/socket_acceptor_service.hpp/1.34/Fri Feb 9 05:47:48 2007// +/socket_base.hpp/1.23/Mon Jan 8 23:45:36 2007// +/ssl.hpp/1.4/Thu Jan 4 05:44:44 2007// +/strand.hpp/1.6/Tue May 8 13:13:55 2007// +/stream_socket_service.hpp/1.35/Mon Jan 8 22:12:46 2007// +/streambuf.hpp/1.3/Thu Jan 4 05:44:44 2007// +/system_error.hpp/1.3/Thu Jan 4 05:44:44 2007// +/thread.hpp/1.15/Thu Jan 4 05:44:44 2007// +/time_traits.hpp/1.9/Thu Jan 4 05:44:44 2007// +/version.hpp/1.1/Tue May 8 12:17:36 2007// +/write.hpp/1.21/Thu Jan 4 05:44:44 2007// +D/detail//// +D/impl//// +D/ip//// +D/ssl//// diff --git a/libtorrent/include/asio/CVS/Repository b/libtorrent/include/asio/CVS/Repository new file mode 100644 index 000000000..2a32176a2 --- /dev/null +++ b/libtorrent/include/asio/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio diff --git a/libtorrent/include/asio/CVS/Root b/libtorrent/include/asio/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/basic_datagram_socket.hpp b/libtorrent/include/asio/basic_datagram_socket.hpp new file mode 100644 index 000000000..1a521628f --- /dev/null +++ b/libtorrent/include/asio/basic_datagram_socket.hpp @@ -0,0 +1,803 @@ +// +// basic_datagram_socket.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_DATAGRAM_SOCKET_HPP +#define ASIO_BASIC_DATAGRAM_SOCKET_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_socket.hpp" +#include "asio/datagram_socket_service.hpp" +#include "asio/error.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +/// Provides datagram-oriented socket functionality. +/** + * The basic_datagram_socket class template provides asynchronous and blocking + * datagram-oriented socket functionality. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +template > +class basic_datagram_socket + : public basic_socket +{ +public: + /// The native representation of a socket. + typedef typename DatagramSocketService::native_type native_type; + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// Construct a basic_datagram_socket without opening it. + /** + * This constructor creates a datagram socket without opening it. The open() + * function must be called before data can be sent or received on the socket. + * + * @param io_service The io_service object that the datagram socket will use + * to dispatch handlers for any asynchronous operations performed on the + * socket. + */ + explicit basic_datagram_socket(asio::io_service& io_service) + : basic_socket(io_service) + { + } + + /// Construct and open a basic_datagram_socket. + /** + * This constructor creates and opens a datagram socket. + * + * @param io_service The io_service object that the datagram socket will use + * to dispatch handlers for any asynchronous operations performed on the + * socket. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @throws asio::system_error Thrown on failure. + */ + basic_datagram_socket(asio::io_service& io_service, + const protocol_type& protocol) + : basic_socket(io_service, protocol) + { + } + + /// Construct a basic_datagram_socket, opening it and binding it to the given + /// local endpoint. + /** + * This constructor creates a datagram socket and automatically opens it bound + * to the specified endpoint on the local machine. The protocol used is the + * protocol associated with the given endpoint. + * + * @param io_service The io_service object that the datagram socket will use + * to dispatch handlers for any asynchronous operations performed on the + * socket. + * + * @param endpoint An endpoint on the local machine to which the datagram + * socket will be bound. + * + * @throws asio::system_error Thrown on failure. + */ + basic_datagram_socket(asio::io_service& io_service, + const endpoint_type& endpoint) + : basic_socket(io_service, endpoint) + { + } + + /// Construct a basic_datagram_socket on an existing native socket. + /** + * This constructor creates a datagram socket object to hold an existing + * native socket. + * + * @param io_service The io_service object that the datagram socket will use + * to dispatch handlers for any asynchronous operations performed on the + * socket. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @param native_socket The new underlying socket implementation. + * + * @throws asio::system_error Thrown on failure. + */ + basic_datagram_socket(asio::io_service& io_service, + const protocol_type& protocol, const native_type& native_socket) + : basic_socket( + io_service, protocol, native_socket) + { + } + + /// Send some data on a connected socket. + /** + * This function is used to send data on the datagram socket. The function + * call will block until the data has been sent successfully or an error + * occurs. + * + * @param buffers One ore more data buffers to be sent on the socket. + * + * @returns The number of bytes sent. + * + * @throws asio::system_error Thrown on failure. + * + * @note The send operation can only be used with a connected socket. Use + * the send_to function to send data on an unconnected datagram socket. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code socket.send(asio::buffer(data, size)); @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t send(const ConstBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = this->service.send(this->implementation, buffers, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Send some data on a connected socket. + /** + * This function is used to send data on the datagram socket. The function + * call will block until the data has been sent successfully or an error + * occurs. + * + * @param buffers One ore more data buffers to be sent on the socket. + * + * @param flags Flags specifying how the send call is to be made. + * + * @returns The number of bytes sent. + * + * @throws asio::system_error Thrown on failure. + * + * @note The send operation can only be used with a connected socket. Use + * the send_to function to send data on an unconnected datagram socket. + */ + template + std::size_t send(const ConstBufferSequence& buffers, + socket_base::message_flags flags) + { + asio::error_code ec; + std::size_t s = this->service.send( + this->implementation, buffers, flags, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Send some data on a connected socket. + /** + * This function is used to send data on the datagram socket. The function + * call will block until the data has been sent successfully or an error + * occurs. + * + * @param buffers One or more data buffers to be sent on the socket. + * + * @param flags Flags specifying how the send call is to be made. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes sent. + * + * @note The send operation can only be used with a connected socket. Use + * the send_to function to send data on an unconnected datagram socket. + */ + template + std::size_t send(const ConstBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return this->service.send(this->implementation, buffers, flags, ec); + } + + /// Start an asynchronous send on a connected socket. + /** + * This function is used to send data on the datagram socket. The function + * call will block until the data has been sent successfully or an error + * occurs. + * + * @param buffers One or more data buffers to be sent on the socket. Although + * the buffers object may be copied as necessary, ownership of the underlying + * memory blocks is retained by the caller, which must guarantee that they + * remain valid until the handler is called. + * + * @param handler The handler to be called when the send operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes sent. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The async_send operation can only be used with a connected socket. + * Use the async_send_to function to send data on an unconnected datagram + * socket. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * socket.async_send(asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_send(const ConstBufferSequence& buffers, WriteHandler handler) + { + this->service.async_send(this->implementation, buffers, 0, handler); + } + + /// Start an asynchronous send on a connected socket. + /** + * This function is used to send data on the datagram socket. The function + * call will block until the data has been sent successfully or an error + * occurs. + * + * @param buffers One or more data buffers to be sent on the socket. Although + * the buffers object may be copied as necessary, ownership of the underlying + * memory blocks is retained by the caller, which must guarantee that they + * remain valid until the handler is called. + * + * @param flags Flags specifying how the send call is to be made. + * + * @param handler The handler to be called when the send operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes sent. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The async_send operation can only be used with a connected socket. + * Use the async_send_to function to send data on an unconnected datagram + * socket. + */ + template + void async_send(const ConstBufferSequence& buffers, + socket_base::message_flags flags, WriteHandler handler) + { + this->service.async_send(this->implementation, buffers, flags, handler); + } + + /// Send a datagram to the specified endpoint. + /** + * This function is used to send a datagram to the specified remote endpoint. + * The function call will block until the data has been sent successfully or + * an error occurs. + * + * @param buffers One or more data buffers to be sent to the remote endpoint. + * + * @param destination The remote endpoint to which the data will be sent. + * + * @returns The number of bytes sent. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * asio::ip::udp::endpoint destination( + * asio::ip::address::from_string("1.2.3.4"), 12345); + * socket.send_to(asio::buffer(data, size), destination); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t send_to(const ConstBufferSequence& buffers, + const endpoint_type& destination) + { + asio::error_code ec; + std::size_t s = this->service.send_to( + this->implementation, buffers, destination, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Send a datagram to the specified endpoint. + /** + * This function is used to send a datagram to the specified remote endpoint. + * The function call will block until the data has been sent successfully or + * an error occurs. + * + * @param buffers One or more data buffers to be sent to the remote endpoint. + * + * @param destination The remote endpoint to which the data will be sent. + * + * @param flags Flags specifying how the send call is to be made. + * + * @returns The number of bytes sent. + * + * @throws asio::system_error Thrown on failure. + */ + template + std::size_t send_to(const ConstBufferSequence& buffers, + const endpoint_type& destination, socket_base::message_flags flags) + { + asio::error_code ec; + std::size_t s = this->service.send_to( + this->implementation, buffers, destination, flags, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Send a datagram to the specified endpoint. + /** + * This function is used to send a datagram to the specified remote endpoint. + * The function call will block until the data has been sent successfully or + * an error occurs. + * + * @param buffers One or more data buffers to be sent to the remote endpoint. + * + * @param destination The remote endpoint to which the data will be sent. + * + * @param flags Flags specifying how the send call is to be made. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes sent. + */ + template + std::size_t send_to(const ConstBufferSequence& buffers, + const endpoint_type& destination, socket_base::message_flags flags, + asio::error_code& ec) + { + return this->service.send_to(this->implementation, + buffers, destination, flags, ec); + } + + /// Start an asynchronous send. + /** + * This function is used to asynchronously send a datagram to the specified + * remote endpoint. The function call always returns immediately. + * + * @param buffers One or more data buffers to be sent to the remote endpoint. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param destination The remote endpoint to which the data will be sent. + * Copies will be made of the endpoint as required. + * + * @param handler The handler to be called when the send operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes sent. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * asio::ip::udp::endpoint destination( + * asio::ip::address::from_string("1.2.3.4"), 12345); + * socket.async_send_to( + * asio::buffer(data, size), destination, handler); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_send_to(const ConstBufferSequence& buffers, + const endpoint_type& destination, WriteHandler handler) + { + this->service.async_send_to(this->implementation, buffers, destination, 0, + handler); + } + + /// Start an asynchronous send. + /** + * This function is used to asynchronously send a datagram to the specified + * remote endpoint. The function call always returns immediately. + * + * @param buffers One or more data buffers to be sent to the remote endpoint. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param flags Flags specifying how the send call is to be made. + * + * @param destination The remote endpoint to which the data will be sent. + * Copies will be made of the endpoint as required. + * + * @param handler The handler to be called when the send operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes sent. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ + template + void async_send_to(const ConstBufferSequence& buffers, + const endpoint_type& destination, socket_base::message_flags flags, + WriteHandler handler) + { + this->service.async_send_to(this->implementation, buffers, destination, + flags, handler); + } + + /// Receive some data on a connected socket. + /** + * This function is used to receive data on the datagram socket. The function + * call will block until data has been received successfully or an error + * occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @returns The number of bytes received. + * + * @throws asio::system_error Thrown on failure. + * + * @note The receive operation can only be used with a connected socket. Use + * the receive_from function to receive data on an unconnected datagram + * socket. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code socket.receive(asio::buffer(data, size)); @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t receive(const MutableBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = this->service.receive( + this->implementation, buffers, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Receive some data on a connected socket. + /** + * This function is used to receive data on the datagram socket. The function + * call will block until data has been received successfully or an error + * occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @returns The number of bytes received. + * + * @throws asio::system_error Thrown on failure. + * + * @note The receive operation can only be used with a connected socket. Use + * the receive_from function to receive data on an unconnected datagram + * socket. + */ + template + std::size_t receive(const MutableBufferSequence& buffers, + socket_base::message_flags flags) + { + asio::error_code ec; + std::size_t s = this->service.receive( + this->implementation, buffers, flags, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Receive some data on a connected socket. + /** + * This function is used to receive data on the datagram socket. The function + * call will block until data has been received successfully or an error + * occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes received. + * + * @note The receive operation can only be used with a connected socket. Use + * the receive_from function to receive data on an unconnected datagram + * socket. + */ + template + std::size_t receive(const MutableBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return this->service.receive(this->implementation, buffers, flags, ec); + } + + /// Start an asynchronous receive on a connected socket. + /** + * This function is used to asynchronously receive data from the datagram + * socket. The function call always returns immediately. + * + * @param buffers One or more buffers into which the data will be received. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param handler The handler to be called when the receive operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The async_receive operation can only be used with a connected socket. + * Use the async_receive_from function to receive data on an unconnected + * datagram socket. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code + * socket.async_receive(asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_receive(const MutableBufferSequence& buffers, ReadHandler handler) + { + this->service.async_receive(this->implementation, buffers, 0, handler); + } + + /// Start an asynchronous receive on a connected socket. + /** + * This function is used to asynchronously receive data from the datagram + * socket. The function call always returns immediately. + * + * @param buffers One or more buffers into which the data will be received. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @param handler The handler to be called when the receive operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The async_receive operation can only be used with a connected socket. + * Use the async_receive_from function to receive data on an unconnected + * datagram socket. + */ + template + void async_receive(const MutableBufferSequence& buffers, + socket_base::message_flags flags, ReadHandler handler) + { + this->service.async_receive(this->implementation, buffers, flags, handler); + } + + /// Receive a datagram with the endpoint of the sender. + /** + * This function is used to receive a datagram. The function call will block + * until data has been received successfully or an error occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param sender_endpoint An endpoint object that receives the endpoint of + * the remote sender of the datagram. + * + * @returns The number of bytes received. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code + * asio::ip::udp::endpoint sender_endpoint; + * socket.receive_from( + * asio::buffer(data, size), sender_endpoint); + * @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t receive_from(const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint) + { + asio::error_code ec; + std::size_t s = this->service.receive_from( + this->implementation, buffers, sender_endpoint, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Receive a datagram with the endpoint of the sender. + /** + * This function is used to receive a datagram. The function call will block + * until data has been received successfully or an error occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param sender_endpoint An endpoint object that receives the endpoint of + * the remote sender of the datagram. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @returns The number of bytes received. + * + * @throws asio::system_error Thrown on failure. + */ + template + std::size_t receive_from(const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint, socket_base::message_flags flags) + { + asio::error_code ec; + std::size_t s = this->service.receive_from( + this->implementation, buffers, sender_endpoint, flags, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Receive a datagram with the endpoint of the sender. + /** + * This function is used to receive a datagram. The function call will block + * until data has been received successfully or an error occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param sender_endpoint An endpoint object that receives the endpoint of + * the remote sender of the datagram. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes received. + */ + template + std::size_t receive_from(const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint, socket_base::message_flags flags, + asio::error_code& ec) + { + return this->service.receive_from(this->implementation, buffers, + sender_endpoint, flags, ec); + } + + /// Start an asynchronous receive. + /** + * This function is used to asynchronously receive a datagram. The function + * call always returns immediately. + * + * @param buffers One or more buffers into which the data will be received. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param sender_endpoint An endpoint object that receives the endpoint of + * the remote sender of the datagram. Ownership of the sender_endpoint object + * is retained by the caller, which must guarantee that it is valid until the + * handler is called. + * + * @param handler The handler to be called when the receive operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::system_error& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code socket.async_receive_from( + * asio::buffer(data, size), 0, sender_endpoint, handler); @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_receive_from(const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint, ReadHandler handler) + { + this->service.async_receive_from(this->implementation, buffers, + sender_endpoint, 0, handler); + } + + /// Start an asynchronous receive. + /** + * This function is used to asynchronously receive a datagram. The function + * call always returns immediately. + * + * @param buffers One or more buffers into which the data will be received. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param sender_endpoint An endpoint object that receives the endpoint of + * the remote sender of the datagram. Ownership of the sender_endpoint object + * is retained by the caller, which must guarantee that it is valid until the + * handler is called. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @param handler The handler to be called when the receive operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ + template + void async_receive_from(const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint, socket_base::message_flags flags, + ReadHandler handler) + { + this->service.async_receive_from(this->implementation, buffers, + sender_endpoint, flags, handler); + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_DATAGRAM_SOCKET_HPP diff --git a/libtorrent/include/asio/basic_deadline_timer.hpp b/libtorrent/include/asio/basic_deadline_timer.hpp new file mode 100644 index 000000000..d881f6643 --- /dev/null +++ b/libtorrent/include/asio/basic_deadline_timer.hpp @@ -0,0 +1,381 @@ +// +// basic_deadline_timer.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_DEADLINE_TIMER_HPP +#define ASIO_BASIC_DEADLINE_TIMER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_io_object.hpp" +#include "asio/deadline_timer_service.hpp" +#include "asio/error.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +/// Provides waitable timer functionality. +/** + * The basic_deadline_timer class template provides the ability to perform a + * blocking or asynchronous wait for a timer to expire. + * + * Most applications will use the asio::deadline_timer typedef. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Examples + * Performing a blocking wait: + * @code + * // Construct a timer without setting an expiry time. + * asio::deadline_timer timer(io_service); + * + * // Set an expiry time relative to now. + * timer.expires_from_now(boost::posix_time::seconds(5)); + * + * // Wait for the timer to expire. + * timer.wait(); + * @endcode + * + * @par + * Performing an asynchronous wait: + * @code + * void handler(const asio::error_code& error) + * { + * if (!error) + * { + * // Timer expired. + * } + * } + * + * ... + * + * // Construct a timer with an absolute expiry time. + * asio::deadline_timer timer(io_service, + * boost::posix_time::time_from_string("2005-12-07 23:59:59.000")); + * + * // Start an asynchronous wait. + * timer.async_wait(handler); + * @endcode + * + * @par Changing an active deadline_timer's expiry time + * + * Changing the expiry time of a timer while there are pending asynchronous + * waits causes those wait operations to be cancelled. To ensure that the action + * associated with the timer is performed only once, use something like this: + * used: + * + * @code + * void on_some_event() + * { + * if (my_timer.expires_from_now(seconds(5)) > 0) + * { + * // We managed to cancel the timer. Start new asynchronous wait. + * my_timer.async_wait(on_timeout); + * } + * else + * { + * // Too late, timer has already expired! + * } + * } + * + * void on_timeout(const asio::error_code& e) + * { + * if (e != asio::error::operation_aborted) + * { + * // Timer was not cancelled, take necessary action. + * } + * } + * @endcode + * + * @li The asio::basic_deadline_timer::expires_from_now() function + * cancels any pending asynchronous waits, and returns the number of + * asynchronous waits that were cancelled. If it returns 0 then you were too + * late and the wait handler has already been executed, or will soon be + * executed. If it returns 1 then the wait handler was successfully cancelled. + * + * @li If a wait handler is cancelled, the asio::error_code passed to + * it contains the value asio::error::operation_aborted. + */ +template , + typename TimerService = deadline_timer_service > +class basic_deadline_timer + : public basic_io_object +{ +public: + /// The time traits type. + typedef TimeTraits traits_type; + + /// The time type. + typedef typename traits_type::time_type time_type; + + /// The duration type. + typedef typename traits_type::duration_type duration_type; + + /// Constructor. + /** + * This constructor creates a timer without setting an expiry time. The + * expires_at() or expires_from_now() functions must be called to set an + * expiry time before the timer can be waited on. + * + * @param io_service The io_service object that the timer will use to dispatch + * handlers for any asynchronous operations performed on the timer. + */ + explicit basic_deadline_timer(asio::io_service& io_service) + : basic_io_object(io_service) + { + } + + /// Constructor to set a particular expiry time as an absolute time. + /** + * This constructor creates a timer and sets the expiry time. + * + * @param io_service The io_service object that the timer will use to dispatch + * handlers for any asynchronous operations performed on the timer. + * + * @param expiry_time The expiry time to be used for the timer, expressed + * as an absolute time. + */ + basic_deadline_timer(asio::io_service& io_service, + const time_type& expiry_time) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.expires_at(this->implementation, expiry_time, ec); + asio::detail::throw_error(ec); + } + + /// Constructor to set a particular expiry time relative to now. + /** + * This constructor creates a timer and sets the expiry time. + * + * @param io_service The io_service object that the timer will use to dispatch + * handlers for any asynchronous operations performed on the timer. + * + * @param expiry_time The expiry time to be used for the timer, relative to + * now. + */ + basic_deadline_timer(asio::io_service& io_service, + const duration_type& expiry_time) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.expires_from_now(this->implementation, expiry_time, ec); + asio::detail::throw_error(ec); + } + + /// Cancel any asynchronous operations that are waiting on the timer. + /** + * This function forces the completion of any pending asynchronous wait + * operations against the timer. The handler for each cancelled operation will + * be invoked with the asio::error::operation_aborted error code. + * + * Cancelling the timer does not change the expiry time. + * + * @return The number of asynchronous operations that were cancelled. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t cancel() + { + asio::error_code ec; + std::size_t s = this->service.cancel(this->implementation, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Cancel any asynchronous operations that are waiting on the timer. + /** + * This function forces the completion of any pending asynchronous wait + * operations against the timer. The handler for each cancelled operation will + * be invoked with the asio::error::operation_aborted error code. + * + * Cancelling the timer does not change the expiry time. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of asynchronous operations that were cancelled. + */ + std::size_t cancel(asio::error_code& ec) + { + return this->service.cancel(this->implementation, ec); + } + + /// Get the timer's expiry time as an absolute time. + /** + * This function may be used to obtain the timer's current expiry time. + * Whether the timer has expired or not does not affect this value. + */ + time_type expires_at() const + { + return this->service.expires_at(this->implementation); + } + + /// Set the timer's expiry time as an absolute time. + /** + * This function sets the expiry time. Any pending asynchronous wait + * operations will be cancelled. The handler for each cancelled operation will + * be invoked with the asio::error::operation_aborted error code. + * + * @param expiry_time The expiry time to be used for the timer. + * + * @return The number of asynchronous operations that were cancelled. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t expires_at(const time_type& expiry_time) + { + asio::error_code ec; + std::size_t s = this->service.expires_at( + this->implementation, expiry_time, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Set the timer's expiry time as an absolute time. + /** + * This function sets the expiry time. Any pending asynchronous wait + * operations will be cancelled. The handler for each cancelled operation will + * be invoked with the asio::error::operation_aborted error code. + * + * @param expiry_time The expiry time to be used for the timer. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of asynchronous operations that were cancelled. + */ + std::size_t expires_at(const time_type& expiry_time, + asio::error_code& ec) + { + return this->service.expires_at(this->implementation, expiry_time, ec); + } + + /// Get the timer's expiry time relative to now. + /** + * This function may be used to obtain the timer's current expiry time. + * Whether the timer has expired or not does not affect this value. + */ + duration_type expires_from_now() const + { + return this->service.expires_from_now(this->implementation); + } + + /// Set the timer's expiry time relative to now. + /** + * This function sets the expiry time. Any pending asynchronous wait + * operations will be cancelled. The handler for each cancelled operation will + * be invoked with the asio::error::operation_aborted error code. + * + * @param expiry_time The expiry time to be used for the timer. + * + * @return The number of asynchronous operations that were cancelled. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t expires_from_now(const duration_type& expiry_time) + { + asio::error_code ec; + std::size_t s = this->service.expires_from_now( + this->implementation, expiry_time, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Set the timer's expiry time relative to now. + /** + * This function sets the expiry time. Any pending asynchronous wait + * operations will be cancelled. The handler for each cancelled operation will + * be invoked with the asio::error::operation_aborted error code. + * + * @param expiry_time The expiry time to be used for the timer. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of asynchronous operations that were cancelled. + */ + std::size_t expires_from_now(const duration_type& expiry_time, + asio::error_code& ec) + { + return this->service.expires_from_now( + this->implementation, expiry_time, ec); + } + + /// Perform a blocking wait on the timer. + /** + * This function is used to wait for the timer to expire. This function + * blocks and does not return until the timer has expired. + * + * @throws asio::system_error Thrown on failure. + */ + void wait() + { + asio::error_code ec; + this->service.wait(this->implementation, ec); + asio::detail::throw_error(ec); + } + + /// Perform a blocking wait on the timer. + /** + * This function is used to wait for the timer to expire. This function + * blocks and does not return until the timer has expired. + * + * @param ec Set to indicate what error occurred, if any. + */ + void wait(asio::error_code& ec) + { + this->service.wait(this->implementation, ec); + } + + /// Start an asynchronous wait on the timer. + /** + * This function may be used to initiate an asynchronous wait against the + * timer. It always returns immediately. + * + * For each call to async_wait(), the supplied handler will be called exactly + * once. The handler will be called when: + * + * @li The timer has expired. + * + * @li The timer was cancelled, in which case the handler is passed the error + * code asio::error::operation_aborted. + * + * @param handler The handler to be called when the timer expires. Copies + * will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error // Result of operation. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ + template + void async_wait(WaitHandler handler) + { + this->service.async_wait(this->implementation, handler); + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_DEADLINE_TIMER_HPP diff --git a/libtorrent/include/asio/basic_io_object.hpp b/libtorrent/include/asio/basic_io_object.hpp new file mode 100644 index 000000000..9291ff5da --- /dev/null +++ b/libtorrent/include/asio/basic_io_object.hpp @@ -0,0 +1,75 @@ +// +// basic_io_object.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_IO_OBJECT_HPP +#define ASIO_BASIC_IO_OBJECT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { + +/// Base class for all I/O objects. +template +class basic_io_object + : private noncopyable +{ +public: + /// The type of the service that will be used to provide I/O operations. + typedef IoObjectService service_type; + + /// The underlying implementation type of I/O object. + typedef typename service_type::implementation_type implementation_type; + + /// Get the io_service associated with the object. + /** + * This function may be used to obtain the io_service object that the I/O + * object uses to dispatch handlers for asynchronous operations. + * + * @return A reference to the io_service object that the I/O object will use + * to dispatch handlers. Ownership is not transferred to the caller. + */ + asio::io_service& io_service() + { + return service.io_service(); + } + +protected: + /// Construct a basic_io_object. + explicit basic_io_object(asio::io_service& io_service) + : service(asio::use_service(io_service)) + { + service.construct(implementation); + } + + /// Protected destructor to prevent deletion through this type. + ~basic_io_object() + { + service.destroy(implementation); + } + + // The backend service implementation. + service_type& service; + + // The underlying native implementation. + implementation_type implementation; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_IO_OBJECT_HPP diff --git a/libtorrent/include/asio/basic_socket.hpp b/libtorrent/include/asio/basic_socket.hpp new file mode 100644 index 000000000..b0dc52e48 --- /dev/null +++ b/libtorrent/include/asio/basic_socket.hpp @@ -0,0 +1,972 @@ +// +// basic_socket.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_SOCKET_HPP +#define ASIO_BASIC_SOCKET_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_io_object.hpp" +#include "asio/error.hpp" +#include "asio/socket_base.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +/// Provides socket functionality. +/** + * The basic_socket class template provides functionality that is common to both + * stream-oriented and datagram-oriented sockets. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +template +class basic_socket + : public basic_io_object, + public socket_base +{ +public: + /// The native representation of a socket. + typedef typename SocketService::native_type native_type; + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// A basic_socket is always the lowest layer. + typedef basic_socket lowest_layer_type; + + /// Construct a basic_socket without opening it. + /** + * This constructor creates a socket without opening it. + * + * @param io_service The io_service object that the socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + */ + explicit basic_socket(asio::io_service& io_service) + : basic_io_object(io_service) + { + } + + /// Construct and open a basic_socket. + /** + * This constructor creates and opens a socket. + * + * @param io_service The io_service object that the socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @throws asio::system_error Thrown on failure. + */ + basic_socket(asio::io_service& io_service, + const protocol_type& protocol) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.open(this->implementation, protocol, ec); + asio::detail::throw_error(ec); + } + + /// Construct a basic_socket, opening it and binding it to the given local + /// endpoint. + /** + * This constructor creates a socket and automatically opens it bound to the + * specified endpoint on the local machine. The protocol used is the protocol + * associated with the given endpoint. + * + * @param io_service The io_service object that the socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + * + * @param endpoint An endpoint on the local machine to which the socket will + * be bound. + * + * @throws asio::system_error Thrown on failure. + */ + basic_socket(asio::io_service& io_service, + const endpoint_type& endpoint) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.open(this->implementation, endpoint.protocol(), ec); + asio::detail::throw_error(ec); + this->service.bind(this->implementation, endpoint, ec); + asio::detail::throw_error(ec); + } + + /// Construct a basic_socket on an existing native socket. + /** + * This constructor creates a socket object to hold an existing native socket. + * + * @param io_service The io_service object that the socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @param native_socket A native socket. + * + * @throws asio::system_error Thrown on failure. + */ + basic_socket(asio::io_service& io_service, + const protocol_type& protocol, const native_type& native_socket) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.assign(this->implementation, protocol, native_socket, ec); + asio::detail::throw_error(ec); + } + + /// Get a reference to the lowest layer. + /** + * This function returns a reference to the lowest layer in a stack of + * layers. Since a basic_socket cannot contain any further layers, it simply + * returns a reference to itself. + * + * @return A reference to the lowest layer in the stack of layers. Ownership + * is not transferred to the caller. + */ + lowest_layer_type& lowest_layer() + { + return *this; + } + + /// Open the socket using the specified protocol. + /** + * This function opens the socket so that it will use the specified protocol. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * socket.open(asio::ip::tcp::v4()); + * @endcode + */ + void open(const protocol_type& protocol = protocol_type()) + { + asio::error_code ec; + this->service.open(this->implementation, protocol, ec); + asio::detail::throw_error(ec); + } + + /// Open the socket using the specified protocol. + /** + * This function opens the socket so that it will use the specified protocol. + * + * @param protocol An object specifying which protocol is to be used. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * asio::error_code ec; + * socket.open(asio::ip::tcp::v4(), ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code open(const protocol_type& protocol, + asio::error_code& ec) + { + return this->service.open(this->implementation, protocol, ec); + } + + /// Assign an existing native socket to the socket. + /* + * This function opens the socket to hold an existing native socket. + * + * @param protocol An object specifying which protocol is to be used. + * + * @param native_socket A native socket. + * + * @throws asio::system_error Thrown on failure. + */ + void assign(const protocol_type& protocol, const native_type& native_socket) + { + asio::error_code ec; + this->service.assign(this->implementation, protocol, native_socket, ec); + asio::detail::throw_error(ec); + } + + /// Assign an existing native socket to the socket. + /* + * This function opens the socket to hold an existing native socket. + * + * @param protocol An object specifying which protocol is to be used. + * + * @param native_socket A native socket. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code assign(const protocol_type& protocol, + const native_type& native_socket, asio::error_code& ec) + { + return this->service.assign(this->implementation, + protocol, native_socket, ec); + } + + /// Determine whether the socket is open. + bool is_open() const + { + return this->service.is_open(this->implementation); + } + + /// Close the socket. + /** + * This function is used to close the socket. Any asynchronous send, receive + * or connect operations will be cancelled immediately, and will complete + * with the asio::error::operation_aborted error. + * + * @throws asio::system_error Thrown on failure. + */ + void close() + { + asio::error_code ec; + this->service.close(this->implementation, ec); + asio::detail::throw_error(ec); + } + + /// Close the socket. + /** + * This function is used to close the socket. Any asynchronous send, receive + * or connect operations will be cancelled immediately, and will complete + * with the asio::error::operation_aborted error. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::error_code ec; + * socket.close(ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code close(asio::error_code& ec) + { + return this->service.close(this->implementation, ec); + } + + /// Get the native socket representation. + /** + * This function may be used to obtain the underlying representation of the + * socket. This is intended to allow access to native socket functionality + * that is not otherwise provided. + */ + native_type native() + { + return this->service.native(this->implementation); + } + + /// Cancel all asynchronous operations associated with the socket. + /** + * This function causes all outstanding asynchronous connect, send and receive + * operations to finish immediately, and the handlers for cancelled operations + * will be passed the asio::error::operation_aborted error. + * + * @throws asio::system_error Thrown on failure. + */ + void cancel() + { + asio::error_code ec; + this->service.cancel(this->implementation, ec); + asio::detail::throw_error(ec); + } + + /// Cancel all asynchronous operations associated with the socket. + /** + * This function causes all outstanding asynchronous connect, send and receive + * operations to finish immediately, and the handlers for cancelled operations + * will be passed the asio::error::operation_aborted error. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code cancel(asio::error_code& ec) + { + return this->service.cancel(this->implementation, ec); + } + + /// Determine whether the socket is at the out-of-band data mark. + /** + * This function is used to check whether the socket input is currently + * positioned at the out-of-band data mark. + * + * @return A bool indicating whether the socket is at the out-of-band data + * mark. + * + * @throws asio::system_error Thrown on failure. + */ + bool at_mark() const + { + asio::error_code ec; + bool b = this->service.at_mark(this->implementation, ec); + asio::detail::throw_error(ec); + return b; + } + + /// Determine whether the socket is at the out-of-band data mark. + /** + * This function is used to check whether the socket input is currently + * positioned at the out-of-band data mark. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return A bool indicating whether the socket is at the out-of-band data + * mark. + */ + bool at_mark(asio::error_code& ec) const + { + return this->service.at_mark(this->implementation, ec); + } + + /// Determine the number of bytes available for reading. + /** + * This function is used to determine the number of bytes that may be read + * without blocking. + * + * @return The number of bytes that may be read without blocking, or 0 if an + * error occurs. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t available() const + { + asio::error_code ec; + std::size_t s = this->service.available(this->implementation, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Determine the number of bytes available for reading. + /** + * This function is used to determine the number of bytes that may be read + * without blocking. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of bytes that may be read without blocking, or 0 if an + * error occurs. + */ + std::size_t available(asio::error_code& ec) const + { + return this->service.available(this->implementation, ec); + } + + /// Bind the socket to the given local endpoint. + /** + * This function binds the socket to the specified endpoint on the local + * machine. + * + * @param endpoint An endpoint on the local machine to which the socket will + * be bound. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * socket.open(asio::ip::tcp::v4()); + * socket.bind(asio::ip::tcp::endpoint( + * asio::ip::tcp::v4(), 12345)); + * @endcode + */ + void bind(const endpoint_type& endpoint) + { + asio::error_code ec; + this->service.bind(this->implementation, endpoint, ec); + asio::detail::throw_error(ec); + } + + /// Bind the socket to the given local endpoint. + /** + * This function binds the socket to the specified endpoint on the local + * machine. + * + * @param endpoint An endpoint on the local machine to which the socket will + * be bound. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * socket.open(asio::ip::tcp::v4()); + * asio::error_code ec; + * socket.bind(asio::ip::tcp::endpoint( + * asio::ip::tcp::v4(), 12345), ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code bind(const endpoint_type& endpoint, + asio::error_code& ec) + { + return this->service.bind(this->implementation, endpoint, ec); + } + + /// Connect the socket to the specified endpoint. + /** + * This function is used to connect a socket to the specified remote endpoint. + * The function call will block until the connection is successfully made or + * an error occurs. + * + * The socket is automatically opened if it is not already open. If the + * connect fails, and the socket was automatically opened, the socket is + * returned to the closed state. + * + * @param peer_endpoint The remote endpoint to which the socket will be + * connected. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * asio::ip::tcp::endpoint endpoint( + * asio::ip::address::from_string("1.2.3.4"), 12345); + * socket.connect(endpoint); + * @endcode + */ + void connect(const endpoint_type& peer_endpoint) + { + asio::error_code ec; + if (!is_open()) + { + this->service.open(this->implementation, peer_endpoint.protocol(), ec); + asio::detail::throw_error(ec); + } + this->service.connect(this->implementation, peer_endpoint, ec); + asio::detail::throw_error(ec); + } + + /// Connect the socket to the specified endpoint. + /** + * This function is used to connect a socket to the specified remote endpoint. + * The function call will block until the connection is successfully made or + * an error occurs. + * + * The socket is automatically opened if it is not already open. If the + * connect fails, and the socket was automatically opened, the socket is + * returned to the closed state. + * + * @param peer_endpoint The remote endpoint to which the socket will be + * connected. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * asio::ip::tcp::endpoint endpoint( + * asio::ip::address::from_string("1.2.3.4"), 12345); + * asio::error_code ec; + * socket.connect(endpoint, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code connect(const endpoint_type& peer_endpoint, + asio::error_code& ec) + { + if (!is_open()) + { + if (this->service.open(this->implementation, + peer_endpoint.protocol(), ec)) + { + return ec; + } + } + + return this->service.connect(this->implementation, peer_endpoint, ec); + } + + /// Start an asynchronous connect. + /** + * This function is used to asynchronously connect a socket to the specified + * remote endpoint. The function call always returns immediately. + * + * The socket is automatically opened if it is not already open. If the + * connect fails, and the socket was automatically opened, the socket is + * returned to the closed state. + * + * @param peer_endpoint The remote endpoint to which the socket will be + * connected. Copies will be made of the endpoint object as required. + * + * @param handler The handler to be called when the connection operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error // Result of operation + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * @code + * void connect_handler(const asio::error_code& error) + * { + * if (!error) + * { + * // Connect succeeded. + * } + * } + * + * ... + * + * asio::ip::tcp::socket socket(io_service); + * asio::ip::tcp::endpoint endpoint( + * asio::ip::address::from_string("1.2.3.4"), 12345); + * socket.async_connect(endpoint, connect_handler); + * @endcode + */ + template + void async_connect(const endpoint_type& peer_endpoint, ConnectHandler handler) + { + if (!is_open()) + { + asio::error_code ec; + if (this->service.open(this->implementation, + peer_endpoint.protocol(), ec)) + { + this->io_service().post(asio::detail::bind_handler(handler, ec)); + return; + } + } + + this->service.async_connect(this->implementation, peer_endpoint, handler); + } + + /// Set an option on the socket. + /** + * This function is used to set an option on the socket. + * + * @param option The new option value to be set on the socket. + * + * @throws asio::system_error Thrown on failure. + * + * @sa SettableSocketOption @n + * asio::socket_base::broadcast @n + * asio::socket_base::do_not_route @n + * asio::socket_base::keep_alive @n + * asio::socket_base::linger @n + * asio::socket_base::receive_buffer_size @n + * asio::socket_base::receive_low_watermark @n + * asio::socket_base::reuse_address @n + * asio::socket_base::send_buffer_size @n + * asio::socket_base::send_low_watermark @n + * asio::ip::multicast::join_group @n + * asio::ip::multicast::leave_group @n + * asio::ip::multicast::enable_loopback @n + * asio::ip::multicast::outbound_interface @n + * asio::ip::multicast::hops @n + * asio::ip::tcp::no_delay + * + * @par Example + * Setting the IPPROTO_TCP/TCP_NODELAY option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::no_delay option(true); + * socket.set_option(option); + * @endcode + */ + template + void set_option(const SettableSocketOption& option) + { + asio::error_code ec; + this->service.set_option(this->implementation, option, ec); + asio::detail::throw_error(ec); + } + + /// Set an option on the socket. + /** + * This function is used to set an option on the socket. + * + * @param option The new option value to be set on the socket. + * + * @param ec Set to indicate what error occurred, if any. + * + * @sa SettableSocketOption @n + * asio::socket_base::broadcast @n + * asio::socket_base::do_not_route @n + * asio::socket_base::keep_alive @n + * asio::socket_base::linger @n + * asio::socket_base::receive_buffer_size @n + * asio::socket_base::receive_low_watermark @n + * asio::socket_base::reuse_address @n + * asio::socket_base::send_buffer_size @n + * asio::socket_base::send_low_watermark @n + * asio::ip::multicast::join_group @n + * asio::ip::multicast::leave_group @n + * asio::ip::multicast::enable_loopback @n + * asio::ip::multicast::outbound_interface @n + * asio::ip::multicast::hops @n + * asio::ip::tcp::no_delay + * + * @par Example + * Setting the IPPROTO_TCP/TCP_NODELAY option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::no_delay option(true); + * asio::error_code ec; + * socket.set_option(option, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + template + asio::error_code set_option(const SettableSocketOption& option, + asio::error_code& ec) + { + return this->service.set_option(this->implementation, option, ec); + } + + /// Get an option from the socket. + /** + * This function is used to get the current value of an option on the socket. + * + * @param option The option value to be obtained from the socket. + * + * @throws asio::system_error Thrown on failure. + * + * @sa GettableSocketOption @n + * asio::socket_base::broadcast @n + * asio::socket_base::do_not_route @n + * asio::socket_base::keep_alive @n + * asio::socket_base::linger @n + * asio::socket_base::receive_buffer_size @n + * asio::socket_base::receive_low_watermark @n + * asio::socket_base::reuse_address @n + * asio::socket_base::send_buffer_size @n + * asio::socket_base::send_low_watermark @n + * asio::ip::multicast::join_group @n + * asio::ip::multicast::leave_group @n + * asio::ip::multicast::enable_loopback @n + * asio::ip::multicast::outbound_interface @n + * asio::ip::multicast::hops @n + * asio::ip::tcp::no_delay + * + * @par Example + * Getting the value of the SOL_SOCKET/SO_KEEPALIVE option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::socket::keep_alive option; + * socket.get_option(option); + * bool is_set = option.get(); + * @endcode + */ + template + void get_option(GettableSocketOption& option) const + { + asio::error_code ec; + this->service.get_option(this->implementation, option, ec); + asio::detail::throw_error(ec); + } + + /// Get an option from the socket. + /** + * This function is used to get the current value of an option on the socket. + * + * @param option The option value to be obtained from the socket. + * + * @param ec Set to indicate what error occurred, if any. + * + * @sa GettableSocketOption @n + * asio::socket_base::broadcast @n + * asio::socket_base::do_not_route @n + * asio::socket_base::keep_alive @n + * asio::socket_base::linger @n + * asio::socket_base::receive_buffer_size @n + * asio::socket_base::receive_low_watermark @n + * asio::socket_base::reuse_address @n + * asio::socket_base::send_buffer_size @n + * asio::socket_base::send_low_watermark @n + * asio::ip::multicast::join_group @n + * asio::ip::multicast::leave_group @n + * asio::ip::multicast::enable_loopback @n + * asio::ip::multicast::outbound_interface @n + * asio::ip::multicast::hops @n + * asio::ip::tcp::no_delay + * + * @par Example + * Getting the value of the SOL_SOCKET/SO_KEEPALIVE option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::socket::keep_alive option; + * asio::error_code ec; + * socket.get_option(option, ec); + * if (ec) + * { + * // An error occurred. + * } + * bool is_set = option.get(); + * @endcode + */ + template + asio::error_code get_option(GettableSocketOption& option, + asio::error_code& ec) const + { + return this->service.get_option(this->implementation, option, ec); + } + + /// Perform an IO control command on the socket. + /** + * This function is used to execute an IO control command on the socket. + * + * @param command The IO control command to be performed on the socket. + * + * @throws asio::system_error Thrown on failure. + * + * @sa IoControlCommand @n + * asio::socket_base::bytes_readable @n + * asio::socket_base::non_blocking_io + * + * @par Example + * Getting the number of bytes ready to read: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::socket::bytes_readable command; + * socket.io_control(command); + * std::size_t bytes_readable = command.get(); + * @endcode + */ + template + void io_control(IoControlCommand& command) + { + asio::error_code ec; + this->service.io_control(this->implementation, command, ec); + asio::detail::throw_error(ec); + } + + /// Perform an IO control command on the socket. + /** + * This function is used to execute an IO control command on the socket. + * + * @param command The IO control command to be performed on the socket. + * + * @param ec Set to indicate what error occurred, if any. + * + * @sa IoControlCommand @n + * asio::socket_base::bytes_readable @n + * asio::socket_base::non_blocking_io + * + * @par Example + * Getting the number of bytes ready to read: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::socket::bytes_readable command; + * asio::error_code ec; + * socket.io_control(command, ec); + * if (ec) + * { + * // An error occurred. + * } + * std::size_t bytes_readable = command.get(); + * @endcode + */ + template + asio::error_code io_control(IoControlCommand& command, + asio::error_code& ec) + { + return this->service.io_control(this->implementation, command, ec); + } + + /// Get the local endpoint of the socket. + /** + * This function is used to obtain the locally bound endpoint of the socket. + * + * @returns An object that represents the local endpoint of the socket. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::endpoint endpoint = socket.local_endpoint(); + * @endcode + */ + endpoint_type local_endpoint() const + { + asio::error_code ec; + endpoint_type ep = this->service.local_endpoint(this->implementation, ec); + asio::detail::throw_error(ec); + return ep; + } + + /// Get the local endpoint of the socket. + /** + * This function is used to obtain the locally bound endpoint of the socket. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns An object that represents the local endpoint of the socket. + * Returns a default-constructed endpoint object if an error occurred. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::error_code ec; + * asio::ip::tcp::endpoint endpoint = socket.local_endpoint(ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + endpoint_type local_endpoint(asio::error_code& ec) const + { + return this->service.local_endpoint(this->implementation, ec); + } + + /// Get the remote endpoint of the socket. + /** + * This function is used to obtain the remote endpoint of the socket. + * + * @returns An object that represents the remote endpoint of the socket. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(); + * @endcode + */ + endpoint_type remote_endpoint() const + { + asio::error_code ec; + endpoint_type ep = this->service.remote_endpoint(this->implementation, ec); + asio::detail::throw_error(ec); + return ep; + } + + /// Get the remote endpoint of the socket. + /** + * This function is used to obtain the remote endpoint of the socket. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns An object that represents the remote endpoint of the socket. + * Returns a default-constructed endpoint object if an error occurred. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::error_code ec; + * asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + endpoint_type remote_endpoint(asio::error_code& ec) const + { + return this->service.remote_endpoint(this->implementation, ec); + } + + /// Disable sends or receives on the socket. + /** + * This function is used to disable send operations, receive operations, or + * both. + * + * @param what Determines what types of operation will no longer be allowed. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * Shutting down the send side of the socket: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * socket.shutdown(asio::ip::tcp::socket::shutdown_send); + * @endcode + */ + void shutdown(shutdown_type what) + { + asio::error_code ec; + this->service.shutdown(this->implementation, what, ec); + asio::detail::throw_error(ec); + } + + /// Disable sends or receives on the socket. + /** + * This function is used to disable send operations, receive operations, or + * both. + * + * @param what Determines what types of operation will no longer be allowed. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * Shutting down the send side of the socket: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::error_code ec; + * socket.shutdown(asio::ip::tcp::socket::shutdown_send, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code shutdown(shutdown_type what, + asio::error_code& ec) + { + return this->service.shutdown(this->implementation, what, ec); + } + +protected: + /// Protected destructor to prevent deletion through this type. + ~basic_socket() + { + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_SOCKET_HPP diff --git a/libtorrent/include/asio/basic_socket_acceptor.hpp b/libtorrent/include/asio/basic_socket_acceptor.hpp new file mode 100644 index 000000000..a2d6a0356 --- /dev/null +++ b/libtorrent/include/asio/basic_socket_acceptor.hpp @@ -0,0 +1,824 @@ +// +// basic_socket_acceptor.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_SOCKET_ACCEPTOR_HPP +#define ASIO_BASIC_SOCKET_ACCEPTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_io_object.hpp" +#include "asio/basic_socket.hpp" +#include "asio/error.hpp" +#include "asio/socket_acceptor_service.hpp" +#include "asio/socket_base.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +/// Provides the ability to accept new connections. +/** + * The basic_socket_acceptor class template is used for accepting new socket + * connections. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Example + * Opening a socket acceptor with the SO_REUSEADDR option enabled: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), port); + * acceptor.open(endpoint.protocol()); + * acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); + * acceptor.bind(endpoint); + * acceptor.listen(); + * @endcode + */ +template > +class basic_socket_acceptor + : public basic_io_object, + public socket_base +{ +public: + /// The native representation of an acceptor. + typedef typename SocketAcceptorService::native_type native_type; + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// Construct an acceptor without opening it. + /** + * This constructor creates an acceptor without opening it to listen for new + * connections. The open() function must be called before the acceptor can + * accept new socket connections. + * + * @param io_service The io_service object that the acceptor will use to + * dispatch handlers for any asynchronous operations performed on the + * acceptor. + */ + explicit basic_socket_acceptor(asio::io_service& io_service) + : basic_io_object(io_service) + { + } + + /// Construct an open acceptor. + /** + * This constructor creates an acceptor and automatically opens it. + * + * @param io_service The io_service object that the acceptor will use to + * dispatch handlers for any asynchronous operations performed on the + * acceptor. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @throws asio::system_error Thrown on failure. + */ + basic_socket_acceptor(asio::io_service& io_service, + const protocol_type& protocol) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.open(this->implementation, protocol, ec); + asio::detail::throw_error(ec); + } + + /// Construct an acceptor opened on the given endpoint. + /** + * This constructor creates an acceptor and automatically opens it to listen + * for new connections on the specified endpoint. + * + * @param io_service The io_service object that the acceptor will use to + * dispatch handlers for any asynchronous operations performed on the + * acceptor. + * + * @param endpoint An endpoint on the local machine on which the acceptor + * will listen for new connections. + * + * @param reuse_addr Whether the constructor should set the socket option + * socket_base::reuse_address. + * + * @throws asio::system_error Thrown on failure. + * + * @note This constructor is equivalent to the following code: + * @code + * basic_socket_acceptor acceptor(io_service); + * acceptor.open(endpoint.protocol()); + * if (reuse_addr) + * acceptor.set_option(socket_base::reuse_address(true)); + * acceptor.bind(endpoint); + * acceptor.listen(listen_backlog); + * @endcode + */ + basic_socket_acceptor(asio::io_service& io_service, + const endpoint_type& endpoint, bool reuse_addr = true) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.open(this->implementation, endpoint.protocol(), ec); + asio::detail::throw_error(ec); + if (reuse_addr) + { + this->service.set_option(this->implementation, + socket_base::reuse_address(true), ec); + asio::detail::throw_error(ec); + } + this->service.bind(this->implementation, endpoint, ec); + asio::detail::throw_error(ec); + this->service.listen(this->implementation, + socket_base::max_connections, ec); + asio::detail::throw_error(ec); + } + + /// Construct a basic_socket_acceptor on an existing native acceptor. + /** + * This constructor creates an acceptor object to hold an existing native + * acceptor. + * + * @param io_service The io_service object that the acceptor will use to + * dispatch handlers for any asynchronous operations performed on the + * acceptor. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @param native_acceptor A native acceptor. + * + * @throws asio::system_error Thrown on failure. + */ + basic_socket_acceptor(asio::io_service& io_service, + const protocol_type& protocol, const native_type& native_acceptor) + : basic_io_object(io_service) + { + asio::error_code ec; + this->service.assign(this->implementation, protocol, native_acceptor, ec); + asio::detail::throw_error(ec); + } + + /// Open the acceptor using the specified protocol. + /** + * This function opens the socket acceptor so that it will use the specified + * protocol. + * + * @param protocol An object specifying which protocol is to be used. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * acceptor.open(asio::ip::tcp::v4()); + * @endcode + */ + void open(const protocol_type& protocol = protocol_type()) + { + asio::error_code ec; + this->service.open(this->implementation, protocol, ec); + asio::detail::throw_error(ec); + } + + /// Open the acceptor using the specified protocol. + /** + * This function opens the socket acceptor so that it will use the specified + * protocol. + * + * @param protocol An object specifying which protocol is to be used. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * asio::error_code ec; + * acceptor.open(asio::ip::tcp::v4(), ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code open(const protocol_type& protocol, + asio::error_code& ec) + { + return this->service.open(this->implementation, protocol, ec); + } + + /// Assigns an existing native acceptor to the acceptor. + /* + * This function opens the acceptor to hold an existing native acceptor. + * + * @param protocol An object specifying which protocol is to be used. + * + * @param native_acceptor A native acceptor. + * + * @throws asio::system_error Thrown on failure. + */ + void assign(const protocol_type& protocol, const native_type& native_acceptor) + { + asio::error_code ec; + this->service.assign(this->implementation, protocol, native_acceptor, ec); + asio::detail::throw_error(ec); + } + + /// Assigns an existing native acceptor to the acceptor. + /* + * This function opens the acceptor to hold an existing native acceptor. + * + * @param protocol An object specifying which protocol is to be used. + * + * @param native_acceptor A native acceptor. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code assign(const protocol_type& protocol, + const native_type& native_acceptor, asio::error_code& ec) + { + return this->service.assign(this->implementation, + protocol, native_acceptor, ec); + } + + /// Determine whether the acceptor is open. + bool is_open() const + { + return this->service.is_open(this->implementation); + } + + /// Bind the acceptor to the given local endpoint. + /** + * This function binds the socket acceptor to the specified endpoint on the + * local machine. + * + * @param endpoint An endpoint on the local machine to which the socket + * acceptor will be bound. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * acceptor.open(asio::ip::tcp::v4()); + * acceptor.bind(asio::ip::tcp::endpoint(12345)); + * @endcode + */ + void bind(const endpoint_type& endpoint) + { + asio::error_code ec; + this->service.bind(this->implementation, endpoint, ec); + asio::detail::throw_error(ec); + } + + /// Bind the acceptor to the given local endpoint. + /** + * This function binds the socket acceptor to the specified endpoint on the + * local machine. + * + * @param endpoint An endpoint on the local machine to which the socket + * acceptor will be bound. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * acceptor.open(asio::ip::tcp::v4()); + * asio::error_code ec; + * acceptor.bind(asio::ip::tcp::endpoint(12345), ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code bind(const endpoint_type& endpoint, + asio::error_code& ec) + { + return this->service.bind(this->implementation, endpoint, ec); + } + + /// Place the acceptor into the state where it will listen for new + /// connections. + /** + * This function puts the socket acceptor into the state where it may accept + * new connections. + * + * @param backlog The maximum length of the queue of pending connections. + * + * @throws asio::system_error Thrown on failure. + */ + void listen(int backlog = socket_base::max_connections) + { + asio::error_code ec; + this->service.listen(this->implementation, backlog, ec); + asio::detail::throw_error(ec); + } + + /// Place the acceptor into the state where it will listen for new + /// connections. + /** + * This function puts the socket acceptor into the state where it may accept + * new connections. + * + * @param backlog The maximum length of the queue of pending connections. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::error_code ec; + * acceptor.listen(asio::socket_base::max_connections, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code listen(int backlog, asio::error_code& ec) + { + return this->service.listen(this->implementation, backlog, ec); + } + + /// Close the acceptor. + /** + * This function is used to close the acceptor. Any asynchronous accept + * operations will be cancelled immediately. + * + * A subsequent call to open() is required before the acceptor can again be + * used to again perform socket accept operations. + * + * @throws asio::system_error Thrown on failure. + */ + void close() + { + asio::error_code ec; + this->service.close(this->implementation, ec); + asio::detail::throw_error(ec); + } + + /// Close the acceptor. + /** + * This function is used to close the acceptor. Any asynchronous accept + * operations will be cancelled immediately. + * + * A subsequent call to open() is required before the acceptor can again be + * used to again perform socket accept operations. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::error_code ec; + * acceptor.close(ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + asio::error_code close(asio::error_code& ec) + { + return this->service.close(this->implementation, ec); + } + + /// Get the native acceptor representation. + /** + * This function may be used to obtain the underlying representation of the + * acceptor. This is intended to allow access to native acceptor functionality + * that is not otherwise provided. + */ + native_type native() + { + return this->service.native(this->implementation); + } + + /// Cancel all asynchronous operations associated with the acceptor. + /** + * This function causes all outstanding asynchronous connect, send and receive + * operations to finish immediately, and the handlers for cancelled operations + * will be passed the asio::error::operation_aborted error. + * + * @throws asio::system_error Thrown on failure. + */ + void cancel() + { + asio::error_code ec; + this->service.cancel(this->implementation, ec); + asio::detail::throw_error(ec); + } + + /// Cancel all asynchronous operations associated with the acceptor. + /** + * This function causes all outstanding asynchronous connect, send and receive + * operations to finish immediately, and the handlers for cancelled operations + * will be passed the asio::error::operation_aborted error. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code cancel(asio::error_code& ec) + { + return this->service.cancel(this->implementation, ec); + } + + /// Set an option on the acceptor. + /** + * This function is used to set an option on the acceptor. + * + * @param option The new option value to be set on the acceptor. + * + * @throws asio::system_error Thrown on failure. + * + * @sa SettableSocketOption @n + * asio::socket_base::reuse_address + * asio::socket_base::enable_connection_aborted + * + * @par Example + * Setting the SOL_SOCKET/SO_REUSEADDR option: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::acceptor::reuse_address option(true); + * acceptor.set_option(option); + * @endcode + */ + template + void set_option(const SettableSocketOption& option) + { + asio::error_code ec; + this->service.set_option(this->implementation, option, ec); + asio::detail::throw_error(ec); + } + + /// Set an option on the acceptor. + /** + * This function is used to set an option on the acceptor. + * + * @param option The new option value to be set on the acceptor. + * + * @param ec Set to indicate what error occurred, if any. + * + * @sa SettableSocketOption @n + * asio::socket_base::reuse_address + * asio::socket_base::enable_connection_aborted + * + * @par Example + * Setting the SOL_SOCKET/SO_REUSEADDR option: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::acceptor::reuse_address option(true); + * asio::error_code ec; + * acceptor.set_option(option, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + template + asio::error_code set_option(const SettableSocketOption& option, + asio::error_code& ec) + { + return this->service.set_option(this->implementation, option, ec); + } + + /// Get an option from the acceptor. + /** + * This function is used to get the current value of an option on the + * acceptor. + * + * @param option The option value to be obtained from the acceptor. + * + * @throws asio::system_error Thrown on failure. + * + * @sa GettableSocketOption @n + * asio::socket_base::reuse_address + * + * @par Example + * Getting the value of the SOL_SOCKET/SO_REUSEADDR option: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::acceptor::reuse_address option; + * acceptor.get_option(option); + * bool is_set = option.get(); + * @endcode + */ + template + void get_option(GettableSocketOption& option) + { + asio::error_code ec; + this->service.get_option(this->implementation, option, ec); + asio::detail::throw_error(ec); + } + + /// Get an option from the acceptor. + /** + * This function is used to get the current value of an option on the + * acceptor. + * + * @param option The option value to be obtained from the acceptor. + * + * @param ec Set to indicate what error occurred, if any. + * + * @sa GettableSocketOption @n + * asio::socket_base::reuse_address + * + * @par Example + * Getting the value of the SOL_SOCKET/SO_REUSEADDR option: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::acceptor::reuse_address option; + * asio::error_code ec; + * acceptor.get_option(option, ec); + * if (ec) + * { + * // An error occurred. + * } + * bool is_set = option.get(); + * @endcode + */ + template + asio::error_code get_option(GettableSocketOption& option, + asio::error_code& ec) + { + return this->service.get_option(this->implementation, option, ec); + } + + /// Get the local endpoint of the acceptor. + /** + * This function is used to obtain the locally bound endpoint of the acceptor. + * + * @returns An object that represents the local endpoint of the acceptor. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::endpoint endpoint = acceptor.local_endpoint(); + * @endcode + */ + endpoint_type local_endpoint() const + { + asio::error_code ec; + endpoint_type ep = this->service.local_endpoint(this->implementation, ec); + asio::detail::throw_error(ec); + return ep; + } + + /// Get the local endpoint of the acceptor. + /** + * This function is used to obtain the locally bound endpoint of the acceptor. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns An object that represents the local endpoint of the acceptor. + * Returns a default-constructed endpoint object if an error occurred and the + * error handler did not throw an exception. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::error_code ec; + * asio::ip::tcp::endpoint endpoint = acceptor.local_endpoint(ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + endpoint_type local_endpoint(asio::error_code& ec) const + { + return this->service.local_endpoint(this->implementation, ec); + } + + /// Accept a new connection. + /** + * This function is used to accept a new connection from a peer into the + * given socket. The function call will block until a new connection has been + * accepted successfully or an error occurs. + * + * @param peer The socket into which the new connection will be accepted. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::socket socket(io_service); + * acceptor.accept(socket); + * @endcode + */ + template + void accept(basic_socket& peer) + { + asio::error_code ec; + this->service.accept(this->implementation, peer, 0, ec); + asio::detail::throw_error(ec); + } + + /// Accept a new connection. + /** + * This function is used to accept a new connection from a peer into the + * given socket. The function call will block until a new connection has been + * accepted successfully or an error occurs. + * + * @param peer The socket into which the new connection will be accepted. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::soocket socket(io_service); + * asio::error_code ec; + * acceptor.accept(socket, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + template + asio::error_code accept( + basic_socket& peer, + asio::error_code& ec) + { + return this->service.accept(this->implementation, peer, 0, ec); + } + + /// Start an asynchronous accept. + /** + * This function is used to asynchronously accept a new connection into a + * socket. The function call always returns immediately. + * + * @param peer The socket into which the new connection will be accepted. + * Ownership of the peer object is retained by the caller, which must + * guarantee that it is valid until the handler is called. + * + * @param handler The handler to be called when the accept operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error // Result of operation. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * @code + * void accept_handler(const asio::error_code& error) + * { + * if (!error) + * { + * // Accept succeeded. + * } + * } + * + * ... + * + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::socket socket(io_service); + * acceptor.async_accept(socket, accept_handler); + * @endcode + */ + template + void async_accept(basic_socket& peer, + AcceptHandler handler) + { + this->service.async_accept(this->implementation, peer, 0, handler); + } + + /// Accept a new connection and obtain the endpoint of the peer + /** + * This function is used to accept a new connection from a peer into the + * given socket, and additionally provide the endpoint of the remote peer. + * The function call will block until a new connection has been accepted + * successfully or an error occurs. + * + * @param peer The socket into which the new connection will be accepted. + * + * @param peer_endpoint An endpoint object which will receive the endpoint of + * the remote peer. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::socket socket(io_service); + * asio::ip::tcp::endpoint endpoint; + * acceptor.accept(socket, endpoint); + * @endcode + */ + template + void accept(basic_socket& peer, + endpoint_type& peer_endpoint) + { + asio::error_code ec; + this->service.accept(this->implementation, peer, &peer_endpoint, ec); + asio::detail::throw_error(ec); + } + + /// Accept a new connection and obtain the endpoint of the peer + /** + * This function is used to accept a new connection from a peer into the + * given socket, and additionally provide the endpoint of the remote peer. + * The function call will block until a new connection has been accepted + * successfully or an error occurs. + * + * @param peer The socket into which the new connection will be accepted. + * + * @param peer_endpoint An endpoint object which will receive the endpoint of + * the remote peer. + * + * @param ec Set to indicate what error occurred, if any. + * + * @par Example + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::ip::tcp::socket socket(io_service); + * asio::ip::tcp::endpoint endpoint; + * asio::error_code ec; + * acceptor.accept(socket, endpoint, ec); + * if (ec) + * { + * // An error occurred. + * } + * @endcode + */ + template + asio::error_code accept( + basic_socket& peer, + endpoint_type& peer_endpoint, asio::error_code& ec) + { + return this->service.accept(this->implementation, peer, &peer_endpoint, ec); + } + + /// Start an asynchronous accept. + /** + * This function is used to asynchronously accept a new connection into a + * socket, and additionally obtain the endpoint of the remote peer. The + * function call always returns immediately. + * + * @param peer The socket into which the new connection will be accepted. + * Ownership of the peer object is retained by the caller, which must + * guarantee that it is valid until the handler is called. + * + * @param peer_endpoint An endpoint object into which the endpoint of the + * remote peer will be written. Ownership of the peer_endpoint object is + * retained by the caller, which must guarantee that it is valid until the + * handler is called. + * + * @param handler The handler to be called when the accept operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error // Result of operation. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ + template + void async_accept(basic_socket& peer, + endpoint_type& peer_endpoint, AcceptHandler handler) + { + this->service.async_accept(this->implementation, + peer, &peer_endpoint, handler); + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_SOCKET_ACCEPTOR_HPP diff --git a/libtorrent/include/asio/basic_socket_iostream.hpp b/libtorrent/include/asio/basic_socket_iostream.hpp new file mode 100644 index 000000000..c48da7b62 --- /dev/null +++ b/libtorrent/include/asio/basic_socket_iostream.hpp @@ -0,0 +1,146 @@ +// +// basic_socket_iostream.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_SOCKET_IOSTREAM_HPP +#define ASIO_BASIC_SOCKET_IOSTREAM_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_socket_streambuf.hpp" +#include "asio/stream_socket_service.hpp" + +#if !defined(ASIO_SOCKET_IOSTREAM_MAX_ARITY) +#define ASIO_SOCKET_IOSTREAM_MAX_ARITY 5 +#endif // !defined(ASIO_SOCKET_IOSTREAM_MAX_ARITY) + +// A macro that should expand to: +// template +// explicit basic_socket_iostream(T1 x1, ..., Tn xn) +// : basic_iostream(&this->boost::base_from_member< +// basic_socket_streambuf >::member) +// { +// if (rdbuf()->connect(x1, ..., xn) == 0) +// this->setstate(std::ios_base::failbit); +// } +// This macro should only persist within this file. + +#define ASIO_PRIVATE_CTR_DEF(z, n, data) \ + template \ + explicit basic_socket_iostream(BOOST_PP_ENUM_BINARY_PARAMS(n, T, x)) \ + : std::basic_iostream(&this->boost::base_from_member< \ + basic_socket_streambuf >::member) \ + { \ + if (rdbuf()->connect(BOOST_PP_ENUM_PARAMS(n, x)) == 0) \ + this->setstate(std::ios_base::failbit); \ + } \ + /**/ + +// A macro that should expand to: +// template +// void connect(T1 x1, ..., Tn xn) +// { +// if (rdbuf()->connect(x1, ..., xn) == 0) +// this->setstate(std::ios_base::failbit); +// } +// This macro should only persist within this file. + +#define ASIO_PRIVATE_CONNECT_DEF(z, n, data) \ + template \ + void connect(BOOST_PP_ENUM_BINARY_PARAMS(n, T, x)) \ + { \ + if (rdbuf()->connect(BOOST_PP_ENUM_PARAMS(n, x)) == 0) \ + this->setstate(std::ios_base::failbit); \ + } \ + /**/ + +namespace asio { + +/// Iostream interface for a socket. +template > +class basic_socket_iostream + : public boost::base_from_member< + basic_socket_streambuf >, + public std::basic_iostream +{ +public: + /// Construct a basic_socket_iostream without establishing a connection. + basic_socket_iostream() + : std::basic_iostream(&this->boost::base_from_member< + basic_socket_streambuf >::member) + { + } + +#if defined(GENERATING_DOCUMENTATION) + /// Establish a connection to an endpoint corresponding to a resolver query. + /** + * This constructor automatically establishes a connection based on the + * supplied resolver query parameters. The arguments are used to construct + * a resolver query object. + */ + template + explicit basic_socket_iostream(T1 t1, ..., TN tn); +#else + BOOST_PP_REPEAT_FROM_TO( + 1, BOOST_PP_INC(ASIO_SOCKET_IOSTREAM_MAX_ARITY), + ASIO_PRIVATE_CTR_DEF, _ ) +#endif + +#if defined(GENERATING_DOCUMENTATION) + /// Establish a connection to an endpoint corresponding to a resolver query. + /** + * This function automatically establishes a connection based on the supplied + * resolver query parameters. The arguments are used to construct a resolver + * query object. + */ + template + void connect(T1 t1, ..., TN tn); +#else + BOOST_PP_REPEAT_FROM_TO( + 1, BOOST_PP_INC(ASIO_SOCKET_IOSTREAM_MAX_ARITY), + ASIO_PRIVATE_CONNECT_DEF, _ ) +#endif + + /// Close the connection. + void close() + { + if (rdbuf()->close() == 0) + this->setstate(std::ios_base::failbit); + } + + /// Return a pointer to the underlying streambuf. + basic_socket_streambuf* rdbuf() const + { + return const_cast*>( + &this->boost::base_from_member< + basic_socket_streambuf >::member); + } +}; + +} // namespace asio + +#undef ASIO_PRIVATE_CTR_DEF +#undef ASIO_PRIVATE_CONNECT_DEF + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_SOCKET_IOSTREAM_HPP diff --git a/libtorrent/include/asio/basic_socket_streambuf.hpp b/libtorrent/include/asio/basic_socket_streambuf.hpp new file mode 100644 index 000000000..2c4189c52 --- /dev/null +++ b/libtorrent/include/asio/basic_socket_streambuf.hpp @@ -0,0 +1,284 @@ +// +// basic_socket_streambuf.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_SOCKET_STREAMBUF_HPP +#define ASIO_BASIC_SOCKET_STREAMBUF_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_socket.hpp" +#include "asio/io_service.hpp" +#include "asio/stream_socket_service.hpp" +#include "asio/detail/throw_error.hpp" + +#if !defined(ASIO_SOCKET_STREAMBUF_MAX_ARITY) +#define ASIO_SOCKET_STREAMBUF_MAX_ARITY 5 +#endif // !defined(ASIO_SOCKET_STREAMBUF_MAX_ARITY) + +// A macro that should expand to: +// template +// basic_socket_streambuf* connect( +// T1 x1, ..., Tn xn) +// { +// init_buffers(); +// asio::error_code ec; +// this->basic_socket::close(ec); +// typedef typename Protocol::resolver_query resolver_query; +// resolver_query query(x1, ..., xn); +// resolve_and_connect(query, ec); +// return !ec ? this : 0; +// } +// This macro should only persist within this file. + +#define ASIO_PRIVATE_CONNECT_DEF( z, n, data ) \ + template \ + basic_socket_streambuf* connect( \ + BOOST_PP_ENUM_BINARY_PARAMS(n, T, x)) \ + { \ + init_buffers(); \ + asio::error_code ec; \ + this->basic_socket::close(ec); \ + typedef typename Protocol::resolver_query resolver_query; \ + resolver_query query(BOOST_PP_ENUM_PARAMS(n, x)); \ + resolve_and_connect(query, ec); \ + return !ec ? this : 0; \ + } \ + /**/ + +namespace asio { + +/// Iostream streambuf for a socket. +template > +class basic_socket_streambuf + : public std::streambuf, + private boost::base_from_member, + public basic_socket +{ +public: + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// Construct a basic_socket_streambuf without establishing a connection. + basic_socket_streambuf() + : basic_socket( + boost::base_from_member::member), + unbuffered_(false) + { + init_buffers(); + } + + /// Destructor flushes buffered data. + virtual ~basic_socket_streambuf() + { + if (pptr() != pbase()) + overflow(traits_type::eof()); + } + + /// Establish a connection. + /** + * This function establishes a connection to the specified endpoint. + * + * @return \c this if a connection was successfully established, a null + * pointer otherwise. + */ + basic_socket_streambuf* connect( + const endpoint_type& endpoint) + { + init_buffers(); + asio::error_code ec; + this->basic_socket::close(ec); + this->basic_socket::connect(endpoint, ec); + return !ec ? this : 0; + } + +#if defined(GENERATING_DOCUMENTATION) + /// Establish a connection. + /** + * This function automatically establishes a connection based on the supplied + * resolver query parameters. The arguments are used to construct a resolver + * query object. + * + * @return \c this if a connection was successfully established, a null + * pointer otherwise. + */ + template + basic_socket_streambuf* connect( + T1 t1, ..., TN tn); +#else + BOOST_PP_REPEAT_FROM_TO( + 1, BOOST_PP_INC(ASIO_SOCKET_STREAMBUF_MAX_ARITY), + ASIO_PRIVATE_CONNECT_DEF, _ ) +#endif + + /// Close the connection. + /** + * @return \c this if a connection was successfully established, a null + * pointer otherwise. + */ + basic_socket_streambuf* close() + { + asio::error_code ec; + sync(); + this->basic_socket::close(ec); + if (!ec) + init_buffers(); + return !ec ? this : 0; + } + +protected: + int_type underflow() + { + if (gptr() == egptr()) + { + asio::error_code ec; + std::size_t bytes_transferred = this->service.receive( + this->implementation, + asio::buffer(asio::buffer(get_buffer_) + putback_max), + 0, ec); + if (ec) + return traits_type::eof(); + setg(get_buffer_.begin(), get_buffer_.begin() + putback_max, + get_buffer_.begin() + putback_max + bytes_transferred); + return traits_type::to_int_type(*gptr()); + } + else + { + return traits_type::eof(); + } + } + + int_type overflow(int_type c) + { + if (unbuffered_) + { + if (traits_type::eq_int_type(c, traits_type::eof())) + { + // Nothing to do. + return traits_type::not_eof(c); + } + else + { + // Send the single character immediately. + asio::error_code ec; + char_type ch = traits_type::to_char_type(c); + this->service.send(this->implementation, + asio::buffer(&ch, sizeof(char_type)), 0, ec); + if (ec) + return traits_type::eof(); + return c; + } + } + else + { + // Send all data in the output buffer. + asio::const_buffer buffer = + asio::buffer(pbase(), pptr() - pbase()); + while (asio::buffer_size(buffer) > 0) + { + asio::error_code ec; + std::size_t bytes_transferred = this->service.send( + this->implementation, asio::buffer(buffer), + 0, ec); + if (ec) + return traits_type::eof(); + buffer = buffer + bytes_transferred; + } + setp(put_buffer_.begin(), put_buffer_.end()); + + // If the new character is eof then our work here is done. + if (traits_type::eq_int_type(c, traits_type::eof())) + return traits_type::not_eof(c); + + // Add the new character to the output buffer. + *pptr() = traits_type::to_char_type(c); + pbump(1); + return c; + } + } + + int sync() + { + return overflow(traits_type::eof()); + } + + std::streambuf* setbuf(char_type* s, std::streamsize n) + { + if (pptr() == pbase() && s == 0 && n == 0) + { + unbuffered_ = true; + setp(0, 0); + return this; + } + + return 0; + } + +private: + void init_buffers() + { + setg(get_buffer_.begin(), + get_buffer_.begin() + putback_max, + get_buffer_.begin() + putback_max); + if (unbuffered_) + setp(0, 0); + else + setp(put_buffer_.begin(), put_buffer_.end()); + } + + void resolve_and_connect(const typename Protocol::resolver_query& query, + asio::error_code& ec) + { + typedef typename Protocol::resolver resolver_type; + typedef typename Protocol::resolver_iterator iterator_type; + resolver_type resolver( + boost::base_from_member::member); + iterator_type i = resolver.resolve(query, ec); + if (!ec) + { + iterator_type end; + ec = asio::error::host_not_found; + while (ec && i != end) + { + this->basic_socket::close(); + this->basic_socket::connect(*i, ec); + ++i; + } + } + } + + enum { putback_max = 8 }; + enum { buffer_size = 512 }; + boost::array get_buffer_; + boost::array put_buffer_; + bool unbuffered_; +}; + +} // namespace asio + +#undef ASIO_PRIVATE_CONNECT_DEF + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_SOCKET_STREAMBUF_HPP diff --git a/libtorrent/include/asio/basic_stream_socket.hpp b/libtorrent/include/asio/basic_stream_socket.hpp new file mode 100644 index 000000000..59889dc33 --- /dev/null +++ b/libtorrent/include/asio/basic_stream_socket.hpp @@ -0,0 +1,718 @@ +// +// basic_stream_socket.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_STREAM_SOCKET_HPP +#define ASIO_BASIC_STREAM_SOCKET_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_socket.hpp" +#include "asio/error.hpp" +#include "asio/stream_socket_service.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +/// Provides stream-oriented socket functionality. +/** + * The basic_stream_socket class template provides asynchronous and blocking + * stream-oriented socket functionality. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Concepts: + * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. + */ +template > +class basic_stream_socket + : public basic_socket +{ +public: + /// The native representation of a socket. + typedef typename StreamSocketService::native_type native_type; + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// Construct a basic_stream_socket without opening it. + /** + * This constructor creates a stream socket without opening it. The socket + * needs to be opened and then connected or accepted before data can be sent + * or received on it. + * + * @param io_service The io_service object that the stream socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + */ + explicit basic_stream_socket(asio::io_service& io_service) + : basic_socket(io_service) + { + } + + /// Construct and open a basic_stream_socket. + /** + * This constructor creates and opens a stream socket. The socket needs to be + * connected or accepted before data can be sent or received on it. + * + * @param io_service The io_service object that the stream socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @throws asio::system_error Thrown on failure. + */ + basic_stream_socket(asio::io_service& io_service, + const protocol_type& protocol) + : basic_socket(io_service, protocol) + { + } + + /// Construct a basic_stream_socket, opening it and binding it to the given + /// local endpoint. + /** + * This constructor creates a stream socket and automatically opens it bound + * to the specified endpoint on the local machine. The protocol used is the + * protocol associated with the given endpoint. + * + * @param io_service The io_service object that the stream socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + * + * @param endpoint An endpoint on the local machine to which the stream + * socket will be bound. + * + * @throws asio::system_error Thrown on failure. + */ + basic_stream_socket(asio::io_service& io_service, + const endpoint_type& endpoint) + : basic_socket(io_service, endpoint) + { + } + + /// Construct a basic_stream_socket on an existing native socket. + /** + * This constructor creates a stream socket object to hold an existing native + * socket. + * + * @param io_service The io_service object that the stream socket will use to + * dispatch handlers for any asynchronous operations performed on the socket. + * + * @param protocol An object specifying protocol parameters to be used. + * + * @param native_socket The new underlying socket implementation. + * + * @throws asio::system_error Thrown on failure. + */ + basic_stream_socket(asio::io_service& io_service, + const protocol_type& protocol, const native_type& native_socket) + : basic_socket( + io_service, protocol, native_socket) + { + } + + /// Send some data on the socket. + /** + * This function is used to send data on the stream socket. The function + * call will block until one or more bytes of the data has been sent + * successfully, or an until error occurs. + * + * @param buffers One or more data buffers to be sent on the socket. + * + * @returns The number of bytes sent. + * + * @throws asio::system_error Thrown on failure. + * + * @note The send operation may not transmit all of the data to the peer. + * Consider using the @ref write function if you need to ensure that all data + * is written before the blocking operation completes. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * socket.send(asio::buffer(data, size)); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t send(const ConstBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = this->service.send( + this->implementation, buffers, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Send some data on the socket. + /** + * This function is used to send data on the stream socket. The function + * call will block until one or more bytes of the data has been sent + * successfully, or an until error occurs. + * + * @param buffers One or more data buffers to be sent on the socket. + * + * @param flags Flags specifying how the send call is to be made. + * + * @returns The number of bytes sent. + * + * @throws asio::system_error Thrown on failure. + * + * @note The send operation may not transmit all of the data to the peer. + * Consider using the @ref write function if you need to ensure that all data + * is written before the blocking operation completes. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * socket.send(asio::buffer(data, size), 0); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t send(const ConstBufferSequence& buffers, + socket_base::message_flags flags) + { + asio::error_code ec; + std::size_t s = this->service.send( + this->implementation, buffers, flags, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Send some data on the socket. + /** + * This function is used to send data on the stream socket. The function + * call will block until one or more bytes of the data has been sent + * successfully, or an until error occurs. + * + * @param buffers One or more data buffers to be sent on the socket. + * + * @param flags Flags specifying how the send call is to be made. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes sent. Returns 0 if an error occurred. + * + * @note The send operation may not transmit all of the data to the peer. + * Consider using the @ref write function if you need to ensure that all data + * is written before the blocking operation completes. + */ + template + std::size_t send(const ConstBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return this->service.send(this->implementation, buffers, flags, ec); + } + + /// Start an asynchronous send. + /** + * This function is used to asynchronously send data on the stream socket. + * The function call always returns immediately. + * + * @param buffers One or more data buffers to be sent on the socket. Although + * the buffers object may be copied as necessary, ownership of the underlying + * memory blocks is retained by the caller, which must guarantee that they + * remain valid until the handler is called. + * + * @param handler The handler to be called when the send operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes sent. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The send operation may not transmit all of the data to the peer. + * Consider using the @ref async_write function if you need to ensure that all + * data is written before the asynchronous operation completes. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * socket.async_send(asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_send(const ConstBufferSequence& buffers, WriteHandler handler) + { + this->service.async_send(this->implementation, buffers, 0, handler); + } + + /// Start an asynchronous send. + /** + * This function is used to asynchronously send data on the stream socket. + * The function call always returns immediately. + * + * @param buffers One or more data buffers to be sent on the socket. Although + * the buffers object may be copied as necessary, ownership of the underlying + * memory blocks is retained by the caller, which must guarantee that they + * remain valid until the handler is called. + * + * @param flags Flags specifying how the send call is to be made. + * + * @param handler The handler to be called when the send operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes sent. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The send operation may not transmit all of the data to the peer. + * Consider using the @ref async_write function if you need to ensure that all + * data is written before the asynchronous operation completes. + * + * @par Example + * To send a single data buffer use the @ref buffer function as follows: + * @code + * socket.async_send(asio::buffer(data, size), 0, handler); + * @endcode + * See the @ref buffer documentation for information on sending multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_send(const ConstBufferSequence& buffers, + socket_base::message_flags flags, WriteHandler handler) + { + this->service.async_send(this->implementation, buffers, flags, handler); + } + + /// Receive some data on the socket. + /** + * This function is used to receive data on the stream socket. The function + * call will block until one or more bytes of data has been received + * successfully, or until an error occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @returns The number of bytes received. + * + * @throws asio::system_error Thrown on failure. An error code of + * asio::error::eof indicates that the connection was closed by the + * peer. + * + * @note The receive operation may not receive all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that the + * requested amount of data is read before the blocking operation completes. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code + * socket.receive(asio::buffer(data, size)); + * @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t receive(const MutableBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = this->service.receive(this->implementation, buffers, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Receive some data on the socket. + /** + * This function is used to receive data on the stream socket. The function + * call will block until one or more bytes of data has been received + * successfully, or until an error occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @returns The number of bytes received. + * + * @throws asio::system_error Thrown on failure. An error code of + * asio::error::eof indicates that the connection was closed by the + * peer. + * + * @note The receive operation may not receive all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that the + * requested amount of data is read before the blocking operation completes. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code + * socket.receive(asio::buffer(data, size), 0); + * @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t receive(const MutableBufferSequence& buffers, + socket_base::message_flags flags) + { + asio::error_code ec; + std::size_t s = this->service.receive( + this->implementation, buffers, flags, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Receive some data on a connected socket. + /** + * This function is used to receive data on the stream socket. The function + * call will block until one or more bytes of data has been received + * successfully, or until an error occurs. + * + * @param buffers One or more buffers into which the data will be received. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes received. Returns 0 if an error occurred. + * + * @note The receive operation may not receive all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that the + * requested amount of data is read before the blocking operation completes. + */ + template + std::size_t receive(const MutableBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return this->service.receive(this->implementation, buffers, flags, ec); + } + + /// Start an asynchronous receive. + /** + * This function is used to asynchronously receive data from the stream + * socket. The function call always returns immediately. + * + * @param buffers One or more buffers into which the data will be received. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param handler The handler to be called when the receive operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The receive operation may not receive all of the requested number of + * bytes. Consider using the @ref async_read function if you need to ensure + * that the requested amount of data is received before the asynchronous + * operation completes. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code + * socket.async_receive(asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_receive(const MutableBufferSequence& buffers, ReadHandler handler) + { + this->service.async_receive(this->implementation, buffers, 0, handler); + } + + /// Start an asynchronous receive. + /** + * This function is used to asynchronously receive data from the stream + * socket. The function call always returns immediately. + * + * @param buffers One or more buffers into which the data will be received. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param flags Flags specifying how the receive call is to be made. + * + * @param handler The handler to be called when the receive operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The receive operation may not receive all of the requested number of + * bytes. Consider using the @ref async_read function if you need to ensure + * that the requested amount of data is received before the asynchronous + * operation completes. + * + * @par Example + * To receive into a single data buffer use the @ref buffer function as + * follows: + * @code + * socket.async_receive(asio::buffer(data, size), 0, handler); + * @endcode + * See the @ref buffer documentation for information on receiving into + * multiple buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_receive(const MutableBufferSequence& buffers, + socket_base::message_flags flags, ReadHandler handler) + { + this->service.async_receive(this->implementation, buffers, flags, handler); + } + + /// Write some data to the socket. + /** + * This function is used to write data to the stream socket. The function call + * will block until one or more bytes of the data has been written + * successfully, or until an error occurs. + * + * @param buffers One or more data buffers to be written to the socket. + * + * @returns The number of bytes written. + * + * @throws asio::system_error Thrown on failure. An error code of + * asio::error::eof indicates that the connection was closed by the + * peer. + * + * @note The write_some operation may not transmit all of the data to the + * peer. Consider using the @ref write function if you need to ensure that + * all data is written before the blocking operation completes. + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code + * socket.write_some(asio::buffer(data, size)); + * @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = this->service.send(this->implementation, buffers, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Write some data to the socket. + /** + * This function is used to write data to the stream socket. The function call + * will block until one or more bytes of the data has been written + * successfully, or until an error occurs. + * + * @param buffers One or more data buffers to be written to the socket. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes written. Returns 0 if an error occurred. + * + * @note The write_some operation may not transmit all of the data to the + * peer. Consider using the @ref write function if you need to ensure that + * all data is written before the blocking operation completes. + */ + template + std::size_t write_some(const ConstBufferSequence& buffers, + asio::error_code& ec) + { + return this->service.send(this->implementation, buffers, 0, ec); + } + + /// Start an asynchronous write. + /** + * This function is used to asynchronously write data to the stream socket. + * The function call always returns immediately. + * + * @param buffers One or more data buffers to be written to the socket. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param handler The handler to be called when the write operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes written. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The write operation may not transmit all of the data to the peer. + * Consider using the @ref async_write function if you need to ensure that all + * data is written before the asynchronous operation completes. + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code + * socket.async_write_some(asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_write_some(const ConstBufferSequence& buffers, + WriteHandler handler) + { + this->service.async_send(this->implementation, buffers, 0, handler); + } + + /// Read some data from the socket. + /** + * This function is used to read data from the stream socket. The function + * call will block until one or more bytes of data has been read successfully, + * or until an error occurs. + * + * @param buffers One or more buffers into which the data will be read. + * + * @returns The number of bytes read. + * + * @throws asio::system_error Thrown on failure. An error code of + * asio::error::eof indicates that the connection was closed by the + * peer. + * + * @note The read_some operation may not read all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that + * the requested amount of data is read before the blocking operation + * completes. + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code + * socket.read_some(asio::buffer(data, size)); + * @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t read_some(const MutableBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = this->service.receive(this->implementation, buffers, 0, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Read some data from the socket. + /** + * This function is used to read data from the stream socket. The function + * call will block until one or more bytes of data has been read successfully, + * or until an error occurs. + * + * @param buffers One or more buffers into which the data will be read. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes read. Returns 0 if an error occurred. + * + * @note The read_some operation may not read all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that + * the requested amount of data is read before the blocking operation + * completes. + */ + template + std::size_t read_some(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return this->service.receive(this->implementation, buffers, 0, ec); + } + + /// Start an asynchronous read. + /** + * This function is used to asynchronously read data from the stream socket. + * The function call always returns immediately. + * + * @param buffers One or more buffers into which the data will be read. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes read. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note The read operation may not read all of the requested number of bytes. + * Consider using the @ref async_read function if you need to ensure that the + * requested amount of data is read before the asynchronous operation + * completes. + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code + * socket.async_read_some(asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + void async_read_some(const MutableBufferSequence& buffers, + ReadHandler handler) + { + this->service.async_receive(this->implementation, buffers, 0, handler); + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_STREAM_SOCKET_HPP diff --git a/libtorrent/include/asio/basic_streambuf.hpp b/libtorrent/include/asio/basic_streambuf.hpp new file mode 100644 index 000000000..016445e0f --- /dev/null +++ b/libtorrent/include/asio/basic_streambuf.hpp @@ -0,0 +1,200 @@ +// +// basic_streambuf.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_STREAMBUF_HPP +#define ASIO_BASIC_STREAMBUF_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { + +/// Automatically resizable buffer class based on std::streambuf. +template > +class basic_streambuf + : public std::streambuf, + private noncopyable +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The type used to represent the get area as a list of buffers. + typedef implementation_defined const_buffers_type; + + /// The type used to represent the put area as a list of buffers. + typedef implementation_defined mutable_buffers_type; +#else + typedef asio::const_buffers_1 const_buffers_type; + typedef asio::mutable_buffers_1 mutable_buffers_type; +#endif + + /// Construct a buffer with a specified maximum size. + explicit basic_streambuf( + std::size_t max_size = (std::numeric_limits::max)(), + const Allocator& allocator = Allocator()) + : max_size_(max_size), + buffer_(allocator) + { + std::size_t pend = (std::min)(max_size_, buffer_delta); + buffer_.resize((std::max)(pend, 1)); + setg(&buffer_[0], &buffer_[0], &buffer_[0]); + setp(&buffer_[0], &buffer_[0] + pend); + } + + /// Return the size of the get area in characters. + std::size_t size() const + { + return pptr() - gptr(); + } + + /// Return the maximum size of the buffer. + std::size_t max_size() const + { + return max_size_; + } + + /// Get a list of buffers that represents the get area. + const_buffers_type data() const + { + return asio::buffer(asio::const_buffer(gptr(), + (pptr() - gptr()) * sizeof(char_type))); + } + + /// Get a list of buffers that represents the put area, with the given size. + mutable_buffers_type prepare(std::size_t size) + { + reserve(size); + return asio::buffer(asio::mutable_buffer( + pptr(), size * sizeof(char_type))); + } + + /// Move the start of the put area by the specified number of characters. + void commit(std::size_t n) + { + if (pptr() + n > epptr()) + n = epptr() - pptr(); + pbump(static_cast(n)); + } + + /// Move the start of the get area by the specified number of characters. + void consume(std::size_t n) + { + while (n > 0) + { + sbumpc(); + --n; + } + } + +protected: + enum { buffer_delta = 128 }; + + int_type underflow() + { + if (gptr() < pptr()) + { + setg(&buffer_[0], gptr(), pptr()); + return traits_type::to_int_type(*gptr()); + } + else + { + return traits_type::eof(); + } + } + + int_type overflow(int_type c) + { + if (!traits_type::eq_int_type(c, traits_type::eof())) + { + if (pptr() == epptr()) + { + std::size_t buffer_size = pptr() - gptr(); + if (buffer_size < max_size_ && max_size_ - buffer_size < buffer_delta) + { + reserve(max_size_ - buffer_size); + } + else + { + reserve(buffer_delta); + } + } + + *pptr() = traits_type::to_char_type(c); + pbump(1); + return c; + } + + return traits_type::not_eof(c); + } + + void reserve(std::size_t n) + { + // Get current stream positions as offsets. + std::size_t gnext = gptr() - &buffer_[0]; + std::size_t gend = egptr() - &buffer_[0]; + std::size_t pnext = pptr() - &buffer_[0]; + std::size_t pend = epptr() - &buffer_[0]; + + // Check if there is already enough space in the put area. + if (n <= pend - pnext) + { + return; + } + + // Shift existing contents of get area to start of buffer. + if (gnext > 0) + { + std::rotate(&buffer_[0], &buffer_[0] + gnext, &buffer_[0] + pend); + gend -= gnext; + pnext -= gnext; + } + + // Ensure buffer is large enough to hold at least the specified size. + if (n > pend - pnext) + { + if (n <= max_size_ && pnext <= max_size_ - n) + { + buffer_.resize((std::max)(pnext + n, 1)); + } + else + { + throw std::length_error("asio::streambuf too long"); + } + } + + // Update stream positions. + setg(&buffer_[0], &buffer_[0], &buffer_[0] + gend); + setp(&buffer_[0] + pnext, &buffer_[0] + pnext + n); + } + +private: + std::size_t max_size_; + std::vector buffer_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_STREAMBUF_HPP diff --git a/libtorrent/include/asio/buffer.hpp b/libtorrent/include/asio/buffer.hpp new file mode 100644 index 000000000..7e5dc76c8 --- /dev/null +++ b/libtorrent/include/asio/buffer.hpp @@ -0,0 +1,790 @@ +// +// buffer.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFER_HPP +#define ASIO_BUFFER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_MSVC) +# if defined(_HAS_ITERATOR_DEBUGGING) +# if !defined(ASIO_DISABLE_BUFFER_DEBUGGING) +# define ASIO_ENABLE_BUFFER_DEBUGGING +# endif // !defined(ASIO_DISABLE_BUFFER_DEBUGGING) +# endif // defined(_HAS_ITERATOR_DEBUGGING) +#endif // defined(BOOST_MSVC) + +#if defined(__GNUC__) +# if defined(_GLIBCXX_DEBUG) +# if !defined(ASIO_DISABLE_BUFFER_DEBUGGING) +# define ASIO_ENABLE_BUFFER_DEBUGGING +# endif // !defined(ASIO_DISABLE_BUFFER_DEBUGGING) +# endif // defined(_GLIBCXX_DEBUG) +#endif // defined(__GNUC__) + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) +# include "asio/detail/push_options.hpp" +# include +# include "asio/detail/pop_options.hpp" +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + +namespace asio { + +class mutable_buffer; +class const_buffer; + +namespace detail { +void* buffer_cast_helper(const mutable_buffer&); +const void* buffer_cast_helper(const const_buffer&); +std::size_t buffer_size_helper(const mutable_buffer&); +std::size_t buffer_size_helper(const const_buffer&); +} // namespace detail + +/// Holds a buffer that can be modified. +/** + * The mutable_buffer class provides a safe representation of a buffer that can + * be modified. It does not own the underlying data, and so is cheap to copy or + * assign. + */ +class mutable_buffer +{ +public: + /// Construct an empty buffer. + mutable_buffer() + : data_(0), + size_(0) + { + } + + /// Construct a buffer to represent a given memory range. + mutable_buffer(void* data, std::size_t size) + : data_(data), + size_(size) + { + } + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + mutable_buffer(void* data, std::size_t size, + boost::function debug_check) + : data_(data), + size_(size), + debug_check_(debug_check) + { + } + + const boost::function& get_debug_check() const + { + return debug_check_; + } +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + +private: + friend void* asio::detail::buffer_cast_helper( + const mutable_buffer& b); + friend std::size_t asio::detail::buffer_size_helper( + const mutable_buffer& b); + + void* data_; + std::size_t size_; + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + boost::function debug_check_; +#endif // ASIO_ENABLE_BUFFER_DEBUGGING +}; + +namespace detail { + +inline void* buffer_cast_helper(const mutable_buffer& b) +{ +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + if (b.size_ && b.debug_check_) + b.debug_check_(); +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + return b.data_; +} + +inline std::size_t buffer_size_helper(const mutable_buffer& b) +{ + return b.size_; +} + +} // namespace detail + +/// Cast a non-modifiable buffer to a specified pointer to POD type. +/** + * @relates mutable_buffer + */ +template +inline PointerToPodType buffer_cast(const mutable_buffer& b) +{ + return static_cast(detail::buffer_cast_helper(b)); +} + +/// Get the number of bytes in a non-modifiable buffer. +/** + * @relates mutable_buffer + */ +inline std::size_t buffer_size(const mutable_buffer& b) +{ + return detail::buffer_size_helper(b); +} + +/// Create a new modifiable buffer that is offset from the start of another. +/** + * @relates mutable_buffer + */ +inline mutable_buffer operator+(const mutable_buffer& b, std::size_t start) +{ + if (start > buffer_size(b)) + return mutable_buffer(); + char* new_data = buffer_cast(b) + start; + std::size_t new_size = buffer_size(b) - start; + return mutable_buffer(new_data, new_size +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , b.get_debug_check() +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + ); +} + +/// Create a new modifiable buffer that is offset from the start of another. +/** + * @relates mutable_buffer + */ +inline mutable_buffer operator+(std::size_t start, const mutable_buffer& b) +{ + if (start > buffer_size(b)) + return mutable_buffer(); + char* new_data = buffer_cast(b) + start; + std::size_t new_size = buffer_size(b) - start; + return mutable_buffer(new_data, new_size +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , b.get_debug_check() +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + ); +} + +/// Adapts a single modifiable buffer so that it meets the requirements of the +/// MutableBufferSequence concept. +class mutable_buffers_1 + : public mutable_buffer +{ +public: + /// The type for each element in the list of buffers. + typedef mutable_buffer value_type; + + /// A random-access iterator type that may be used to read elements. + typedef const mutable_buffer* const_iterator; + + /// Construct to represent a single modifiable buffer. + explicit mutable_buffers_1(const mutable_buffer& b) + : mutable_buffer(b) + { + } + + /// Get a random-access iterator to the first element. + const_iterator begin() const + { + return this; + } + + /// Get a random-access iterator for one past the last element. + const_iterator end() const + { + return begin() + 1; + } +}; + +/// Holds a buffer that cannot be modified. +/** + * The const_buffer class provides a safe representation of a buffer that cannot + * be modified. It does not own the underlying data, and so is cheap to copy or + * assign. + */ +class const_buffer +{ +public: + /// Construct an empty buffer. + const_buffer() + : data_(0), + size_(0) + { + } + + /// Construct a buffer to represent a given memory range. + const_buffer(const void* data, std::size_t size) + : data_(data), + size_(size) + { + } + + /// Construct a non-modifiable buffer from a modifiable one. + const_buffer(const mutable_buffer& b) + : data_(asio::detail::buffer_cast_helper(b)), + size_(asio::detail::buffer_size_helper(b)) +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , debug_check_(b.get_debug_check()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + { + } + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + const_buffer(const void* data, std::size_t size, + boost::function debug_check) + : data_(data), + size_(size), + debug_check_(debug_check) + { + } + + const boost::function& get_debug_check() const + { + return debug_check_; + } +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + +private: + friend const void* asio::detail::buffer_cast_helper( + const const_buffer& b); + friend std::size_t asio::detail::buffer_size_helper( + const const_buffer& b); + + const void* data_; + std::size_t size_; + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + boost::function debug_check_; +#endif // ASIO_ENABLE_BUFFER_DEBUGGING +}; + +namespace detail { + +inline const void* buffer_cast_helper(const const_buffer& b) +{ +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + if (b.size_ && b.debug_check_) + b.debug_check_(); +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + return b.data_; +} + +inline std::size_t buffer_size_helper(const const_buffer& b) +{ + return b.size_; +} + +} // namespace detail + +/// Cast a non-modifiable buffer to a specified pointer to POD type. +/** + * @relates const_buffer + */ +template +inline PointerToPodType buffer_cast(const const_buffer& b) +{ + return static_cast(detail::buffer_cast_helper(b)); +} + +/// Get the number of bytes in a non-modifiable buffer. +/** + * @relates const_buffer + */ +inline std::size_t buffer_size(const const_buffer& b) +{ + return detail::buffer_size_helper(b); +} + +/// Create a new non-modifiable buffer that is offset from the start of another. +/** + * @relates const_buffer + */ +inline const_buffer operator+(const const_buffer& b, std::size_t start) +{ + if (start > buffer_size(b)) + return const_buffer(); + const char* new_data = buffer_cast(b) + start; + std::size_t new_size = buffer_size(b) - start; + return const_buffer(new_data, new_size +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , b.get_debug_check() +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + ); +} + +/// Create a new non-modifiable buffer that is offset from the start of another. +/** + * @relates const_buffer + */ +inline const_buffer operator+(std::size_t start, const const_buffer& b) +{ + if (start > buffer_size(b)) + return const_buffer(); + const char* new_data = buffer_cast(b) + start; + std::size_t new_size = buffer_size(b) - start; + return const_buffer(new_data, new_size +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , b.get_debug_check() +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + ); +} + +/// Adapts a single non-modifiable buffer so that it meets the requirements of +/// the ConstBufferSequence concept. +class const_buffers_1 + : public const_buffer +{ +public: + /// The type for each element in the list of buffers. + typedef const_buffer value_type; + + /// A random-access iterator type that may be used to read elements. + typedef const const_buffer* const_iterator; + + /// Construct to represent a single non-modifiable buffer. + explicit const_buffers_1(const const_buffer& b) + : const_buffer(b) + { + } + + /// Get a random-access iterator to the first element. + const_iterator begin() const + { + return this; + } + + /// Get a random-access iterator for one past the last element. + const_iterator end() const + { + return begin() + 1; + } +}; + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) +namespace detail { + +template +class buffer_debug_check +{ +public: + buffer_debug_check(Iterator iter) + : iter_(iter) + { + } + + void operator()() + { + *iter_; + } + +private: + Iterator iter_; +}; + +} // namespace detail +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + +/** @defgroup buffer asio::buffer + * + * @brief The asio::buffer function is used to create a buffer object to + * represent raw memory, an array of POD elements, or a vector of POD elements. + * + * The simplest use case involves reading or writing a single buffer of a + * specified size: + * + * @code sock.write(asio::buffer(data, size)); @endcode + * + * In the above example, the return value of asio::buffer meets the + * requirements of the ConstBufferSequence concept so that it may be directly + * passed to the socket's write function. A buffer created for modifiable + * memory also meets the requirements of the MutableBufferSequence concept. + * + * An individual buffer may be created from a builtin array, std::vector or + * boost::array of POD elements. This helps prevent buffer overruns by + * automatically determining the size of the buffer: + * + * @code char d1[128]; + * size_t bytes_transferred = sock.read(asio::buffer(d1)); + * + * std::vector d2(128); + * bytes_transferred = sock.read(asio::buffer(d2)); + * + * boost::array d3; + * bytes_transferred = sock.read(asio::buffer(d3)); @endcode + * + * To read or write using multiple buffers (i.e. scatter-gather I/O), multiple + * buffer objects may be assigned into a container that supports the + * MutableBufferSequence (for read) or ConstBufferSequence (for write) concepts: + * + * @code + * char d1[128]; + * std::vector d2(128); + * boost::array d3; + * + * boost::array bufs1 = { + * asio::buffer(d1), + * asio::buffer(d2), + * asio::buffer(d3) }; + * bytes_transferred = sock.read(bufs1); + * + * std::vector bufs2; + * bufs2.push_back(asio::buffer(d1)); + * bufs2.push_back(asio::buffer(d2)); + * bufs2.push_back(asio::buffer(d3)); + * bytes_transferred = sock.write(bufs2); @endcode + */ +/*@{*/ + +/// Create a new modifiable buffer from an existing buffer. +inline mutable_buffers_1 buffer(const mutable_buffer& b) +{ + return mutable_buffers_1(b); +} + +/// Create a new modifiable buffer from an existing buffer. +inline mutable_buffers_1 buffer(const mutable_buffer& b, + std::size_t max_size_in_bytes) +{ + return mutable_buffers_1( + mutable_buffer(buffer_cast(b), + buffer_size(b) < max_size_in_bytes + ? buffer_size(b) : max_size_in_bytes +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , b.get_debug_check() +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new non-modifiable buffer from an existing buffer. +inline const_buffers_1 buffer(const const_buffer& b) +{ + return const_buffers_1(b); +} + +/// Create a new non-modifiable buffer from an existing buffer. +inline const_buffers_1 buffer(const const_buffer& b, + std::size_t max_size_in_bytes) +{ + return const_buffers_1( + const_buffer(buffer_cast(b), + buffer_size(b) < max_size_in_bytes + ? buffer_size(b) : max_size_in_bytes +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , b.get_debug_check() +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new modifiable buffer that represents the given memory range. +inline mutable_buffers_1 buffer(void* data, std::size_t size_in_bytes) +{ + return mutable_buffers_1(mutable_buffer(data, size_in_bytes)); +} + +/// Create a new non-modifiable buffer that represents the given memory range. +inline const_buffers_1 buffer(const void* data, + std::size_t size_in_bytes) +{ + return const_buffers_1(const_buffer(data, size_in_bytes)); +} + +/// Create a new modifiable buffer that represents the given POD array. +template +inline mutable_buffers_1 buffer(PodType (&data)[N]) +{ + return mutable_buffers_1(mutable_buffer(data, N * sizeof(PodType))); +} + +/// Create a new modifiable buffer that represents the given POD array. +template +inline mutable_buffers_1 buffer(PodType (&data)[N], + std::size_t max_size_in_bytes) +{ + return mutable_buffers_1( + mutable_buffer(data, + N * sizeof(PodType) < max_size_in_bytes + ? N * sizeof(PodType) : max_size_in_bytes)); +} + +/// Create a new non-modifiable buffer that represents the given POD array. +template +inline const_buffers_1 buffer(const PodType (&data)[N]) +{ + return const_buffers_1(const_buffer(data, N * sizeof(PodType))); +} + +/// Create a new non-modifiable buffer that represents the given POD array. +template +inline const_buffers_1 buffer(const PodType (&data)[N], + std::size_t max_size_in_bytes) +{ + return const_buffers_1( + const_buffer(data, + N * sizeof(PodType) < max_size_in_bytes + ? N * sizeof(PodType) : max_size_in_bytes)); +} + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + +// Borland C++ thinks the overloads: +// +// unspecified buffer(boost::array& array ...); +// +// and +// +// unspecified buffer(boost::array& array ...); +// +// are ambiguous. This will be worked around by using a buffer_types traits +// class that contains typedefs for the appropriate buffer and container +// classes, based on whether PodType is const or non-const. + +namespace detail { + +template +struct buffer_types_base; + +template <> +struct buffer_types_base +{ + typedef mutable_buffer buffer_type; + typedef mutable_buffers_1 container_type; +}; + +template <> +struct buffer_types_base +{ + typedef const_buffer buffer_type; + typedef const_buffers_1 container_type; +}; + +template +struct buffer_types + : public buffer_types_base::value> +{ +}; + +} // namespace detail + +template +inline typename detail::buffer_types::container_type +buffer(boost::array& data) +{ + typedef typename asio::detail::buffer_types::buffer_type + buffer_type; + typedef typename asio::detail::buffer_types::container_type + container_type; + return container_type( + buffer_type(data.c_array(), data.size() * sizeof(PodType))); +} + +template +inline typename detail::buffer_types::container_type +buffer(boost::array& data, std::size_t max_size_in_bytes) +{ + typedef typename asio::detail::buffer_types::buffer_type + buffer_type; + typedef typename asio::detail::buffer_types::container_type + container_type; + return container_type( + buffer_type(data.c_array(), + data.size() * sizeof(PodType) < max_size_in_bytes + ? data.size() * sizeof(PodType) : max_size_in_bytes)); +} + +#else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + +/// Create a new modifiable buffer that represents the given POD array. +template +inline mutable_buffers_1 buffer(boost::array& data) +{ + return mutable_buffers_1( + mutable_buffer(data.c_array(), data.size() * sizeof(PodType))); +} + +/// Create a new modifiable buffer that represents the given POD array. +template +inline mutable_buffers_1 buffer(boost::array& data, + std::size_t max_size_in_bytes) +{ + return mutable_buffers_1( + mutable_buffer(data.c_array(), + data.size() * sizeof(PodType) < max_size_in_bytes + ? data.size() * sizeof(PodType) : max_size_in_bytes)); +} + +/// Create a new non-modifiable buffer that represents the given POD array. +template +inline const_buffers_1 buffer(boost::array& data) +{ + return const_buffers_1( + const_buffer(data.data(), data.size() * sizeof(PodType))); +} + +/// Create a new non-modifiable buffer that represents the given POD array. +template +inline const_buffers_1 buffer(boost::array& data, + std::size_t max_size_in_bytes) +{ + return const_buffers_1( + const_buffer(data.data(), + data.size() * sizeof(PodType) < max_size_in_bytes + ? data.size() * sizeof(PodType) : max_size_in_bytes)); +} + +#endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + +/// Create a new non-modifiable buffer that represents the given POD array. +template +inline const_buffers_1 buffer(const boost::array& data) +{ + return const_buffers_1( + const_buffer(data.data(), data.size() * sizeof(PodType))); +} + +/// Create a new non-modifiable buffer that represents the given POD array. +template +inline const_buffers_1 buffer(const boost::array& data, + std::size_t max_size_in_bytes) +{ + return const_buffers_1( + const_buffer(data.data(), + data.size() * sizeof(PodType) < max_size_in_bytes + ? data.size() * sizeof(PodType) : max_size_in_bytes)); +} + +/// Create a new modifiable buffer that represents the given POD vector. +/** + * @note The buffer is invalidated by any vector operation that would also + * invalidate iterators. + */ +template +inline mutable_buffers_1 buffer(std::vector& data) +{ + return mutable_buffers_1( + mutable_buffer(&data[0], data.size() * sizeof(PodType) +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , detail::buffer_debug_check< + typename std::vector::iterator + >(data.begin()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new modifiable buffer that represents the given POD vector. +/** + * @note The buffer is invalidated by any vector operation that would also + * invalidate iterators. + */ +template +inline mutable_buffers_1 buffer(std::vector& data, + std::size_t max_size_in_bytes) +{ + return mutable_buffers_1( + mutable_buffer(&data[0], + data.size() * sizeof(PodType) < max_size_in_bytes + ? data.size() * sizeof(PodType) : max_size_in_bytes +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , detail::buffer_debug_check< + typename std::vector::iterator + >(data.begin()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new non-modifiable buffer that represents the given POD vector. +/** + * @note The buffer is invalidated by any vector operation that would also + * invalidate iterators. + */ +template +inline const_buffers_1 buffer( + const std::vector& data) +{ + return const_buffers_1( + const_buffer(&data[0], data.size() * sizeof(PodType) +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , detail::buffer_debug_check< + typename std::vector::const_iterator + >(data.begin()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new non-modifiable buffer that represents the given POD vector. +/** + * @note The buffer is invalidated by any vector operation that would also + * invalidate iterators. + */ +template +inline const_buffers_1 buffer( + const std::vector& data, std::size_t max_size_in_bytes) +{ + return const_buffers_1( + const_buffer(&data[0], + data.size() * sizeof(PodType) < max_size_in_bytes + ? data.size() * sizeof(PodType) : max_size_in_bytes +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , detail::buffer_debug_check< + typename std::vector::const_iterator + >(data.begin()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new non-modifiable buffer that represents the given string. +/** + * @note The buffer is invalidated by any non-const operation called on the + * given string object. + */ +inline const_buffers_1 buffer(const std::string& data) +{ + return const_buffers_1(const_buffer(data.data(), data.size() +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , detail::buffer_debug_check(data.begin()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/// Create a new non-modifiable buffer that represents the given string. +/** + * @note The buffer is invalidated by any non-const operation called on the + * given string object. + */ +inline const_buffers_1 buffer(const std::string& data, + std::size_t max_size_in_bytes) +{ + return const_buffers_1( + const_buffer(data.data(), + data.size() < max_size_in_bytes + ? data.size() : max_size_in_bytes +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + , detail::buffer_debug_check(data.begin()) +#endif // ASIO_ENABLE_BUFFER_DEBUGGING + )); +} + +/*@}*/ + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFER_HPP diff --git a/libtorrent/include/asio/buffered_read_stream.hpp b/libtorrent/include/asio/buffered_read_stream.hpp new file mode 100644 index 000000000..5cf5d688e --- /dev/null +++ b/libtorrent/include/asio/buffered_read_stream.hpp @@ -0,0 +1,407 @@ +// +// buffered_read_stream.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFERED_READ_STREAM_HPP +#define ASIO_BUFFERED_READ_STREAM_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffered_read_stream_fwd.hpp" +#include "asio/buffer.hpp" +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/buffer_resize_guard.hpp" +#include "asio/detail/buffered_stream_storage.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { + +/// Adds buffering to the read-related operations of a stream. +/** + * The buffered_read_stream class template can be used to add buffering to the + * synchronous and asynchronous read operations of a stream. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Concepts: + * AsyncReadStream, AsyncWriteStream, Stream, Sync_Read_Stream, SyncWriteStream. + */ +template +class buffered_read_stream + : private noncopyable +{ +public: + /// The type of the next layer. + typedef typename boost::remove_reference::type next_layer_type; + + /// The type of the lowest layer. + typedef typename next_layer_type::lowest_layer_type lowest_layer_type; + +#if defined(GENERATING_DOCUMENTATION) + /// The default buffer size. + static const std::size_t default_buffer_size = implementation_defined; +#else + BOOST_STATIC_CONSTANT(std::size_t, default_buffer_size = 1024); +#endif + + /// Construct, passing the specified argument to initialise the next layer. + template + explicit buffered_read_stream(Arg& a) + : next_layer_(a), + storage_(default_buffer_size) + { + } + + /// Construct, passing the specified argument to initialise the next layer. + template + buffered_read_stream(Arg& a, std::size_t buffer_size) + : next_layer_(a), + storage_(buffer_size) + { + } + + /// Get a reference to the next layer. + next_layer_type& next_layer() + { + return next_layer_; + } + + /// Get a reference to the lowest layer. + lowest_layer_type& lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /// Get the io_service associated with the object. + asio::io_service& io_service() + { + return next_layer_.io_service(); + } + + /// Close the stream. + void close() + { + next_layer_.close(); + } + + /// Close the stream. + asio::error_code close(asio::error_code& ec) + { + return next_layer_.close(ec); + } + + /// Write the given data to the stream. Returns the number of bytes written. + /// Throws an exception on failure. + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + return next_layer_.write_some(buffers); + } + + /// Write the given data to the stream. Returns the number of bytes written, + /// or 0 if an error occurred. + template + std::size_t write_some(const ConstBufferSequence& buffers, + asio::error_code& ec) + { + return next_layer_.write_some(buffers, ec); + } + + /// Start an asynchronous write. The data being written must be valid for the + /// lifetime of the asynchronous operation. + template + void async_write_some(const ConstBufferSequence& buffers, + WriteHandler handler) + { + next_layer_.async_write_some(buffers, handler); + } + + /// Fill the buffer with some data. Returns the number of bytes placed in the + /// buffer as a result of the operation. Throws an exception on failure. + std::size_t fill() + { + detail::buffer_resize_guard + resize_guard(storage_); + std::size_t previous_size = storage_.size(); + storage_.resize(storage_.capacity()); + storage_.resize(previous_size + next_layer_.read_some(buffer( + storage_.data() + previous_size, + storage_.size() - previous_size))); + resize_guard.commit(); + return storage_.size() - previous_size; + } + + /// Fill the buffer with some data. Returns the number of bytes placed in the + /// buffer as a result of the operation, or 0 if an error occurred. + std::size_t fill(asio::error_code& ec) + { + detail::buffer_resize_guard + resize_guard(storage_); + std::size_t previous_size = storage_.size(); + storage_.resize(storage_.capacity()); + storage_.resize(previous_size + next_layer_.read_some(buffer( + storage_.data() + previous_size, + storage_.size() - previous_size), + ec)); + resize_guard.commit(); + return storage_.size() - previous_size; + } + + template + class fill_handler + { + public: + fill_handler(asio::io_service& io_service, + detail::buffered_stream_storage& storage, + std::size_t previous_size, ReadHandler handler) + : io_service_(io_service), + storage_(storage), + previous_size_(previous_size), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + storage_.resize(previous_size_ + bytes_transferred); + io_service_.dispatch(detail::bind_handler( + handler_, ec, bytes_transferred)); + } + + private: + asio::io_service& io_service_; + detail::buffered_stream_storage& storage_; + std::size_t previous_size_; + ReadHandler handler_; + }; + + /// Start an asynchronous fill. + template + void async_fill(ReadHandler handler) + { + std::size_t previous_size = storage_.size(); + storage_.resize(storage_.capacity()); + next_layer_.async_read_some( + buffer( + storage_.data() + previous_size, + storage_.size() - previous_size), + fill_handler(io_service(), + storage_, previous_size, handler)); + } + + /// Read some data from the stream. Returns the number of bytes read. Throws + /// an exception on failure. + template + std::size_t read_some(const MutableBufferSequence& buffers) + { + if (storage_.empty()) + fill(); + return copy(buffers); + } + + /// Read some data from the stream. Returns the number of bytes read or 0 if + /// an error occurred. + template + std::size_t read_some(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + ec = asio::error_code(); + if (storage_.empty() && !fill(ec)) + return 0; + return copy(buffers); + } + + template + class read_some_handler + { + public: + read_some_handler(asio::io_service& io_service, + detail::buffered_stream_storage& storage, + const MutableBufferSequence& buffers, ReadHandler handler) + : io_service_(io_service), + storage_(storage), + buffers_(buffers), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, std::size_t) + { + if (ec || storage_.empty()) + { + std::size_t length = 0; + io_service_.dispatch(detail::bind_handler(handler_, ec, length)); + } + else + { + using namespace std; // For memcpy. + + std::size_t bytes_avail = storage_.size(); + std::size_t bytes_copied = 0; + + typename MutableBufferSequence::const_iterator iter = buffers_.begin(); + typename MutableBufferSequence::const_iterator end = buffers_.end(); + for (; iter != end && bytes_avail > 0; ++iter) + { + std::size_t max_length = buffer_size(*iter); + std::size_t length = (max_length < bytes_avail) + ? max_length : bytes_avail; + memcpy(buffer_cast(*iter), + storage_.data() + bytes_copied, length); + bytes_copied += length; + bytes_avail -= length; + } + + storage_.consume(bytes_copied); + io_service_.dispatch(detail::bind_handler(handler_, ec, bytes_copied)); + } + } + + private: + asio::io_service& io_service_; + detail::buffered_stream_storage& storage_; + MutableBufferSequence buffers_; + ReadHandler handler_; + }; + + /// Start an asynchronous read. The buffer into which the data will be read + /// must be valid for the lifetime of the asynchronous operation. + template + void async_read_some(const MutableBufferSequence& buffers, + ReadHandler handler) + { + if (storage_.empty()) + { + async_fill(read_some_handler( + io_service(), storage_, buffers, handler)); + } + else + { + std::size_t length = copy(buffers); + io_service().post(detail::bind_handler( + handler, asio::error_code(), length)); + } + } + + /// Peek at the incoming data on the stream. Returns the number of bytes read. + /// Throws an exception on failure. + template + std::size_t peek(const MutableBufferSequence& buffers) + { + if (storage_.empty()) + fill(); + return peek_copy(buffers); + } + + /// Peek at the incoming data on the stream. Returns the number of bytes read, + /// or 0 if an error occurred. + template + std::size_t peek(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + ec = asio::error_code(); + if (storage_.empty() && !fill(ec)) + return 0; + return peek_copy(buffers); + } + + /// Determine the amount of data that may be read without blocking. + std::size_t in_avail() + { + return storage_.size(); + } + + /// Determine the amount of data that may be read without blocking. + std::size_t in_avail(asio::error_code& ec) + { + ec = asio::error_code(); + return storage_.size(); + } + +private: + /// Copy data out of the internal buffer to the specified target buffer. + /// Returns the number of bytes copied. + template + std::size_t copy(const MutableBufferSequence& buffers) + { + using namespace std; // For memcpy. + + std::size_t bytes_avail = storage_.size(); + std::size_t bytes_copied = 0; + + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + for (; iter != end && bytes_avail > 0; ++iter) + { + std::size_t max_length = buffer_size(*iter); + std::size_t length = (max_length < bytes_avail) + ? max_length : bytes_avail; + memcpy(buffer_cast(*iter), storage_.data() + bytes_copied, length); + bytes_copied += length; + bytes_avail -= length; + } + + storage_.consume(bytes_copied); + return bytes_copied; + } + + /// Copy data from the internal buffer to the specified target buffer, without + /// removing the data from the internal buffer. Returns the number of bytes + /// copied. + template + std::size_t peek_copy(const MutableBufferSequence& buffers) + { + using namespace std; // For memcpy. + + std::size_t bytes_avail = storage_.size(); + std::size_t bytes_copied = 0; + + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + for (; iter != end && bytes_avail > 0; ++iter) + { + std::size_t max_length = buffer_size(*iter); + std::size_t length = (max_length < bytes_avail) + ? max_length : bytes_avail; + memcpy(buffer_cast(*iter), storage_.data() + bytes_copied, length); + bytes_copied += length; + bytes_avail -= length; + } + + return bytes_copied; + } + + /// The next layer. + Stream next_layer_; + + // The data in the buffer. + detail::buffered_stream_storage storage_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFERED_READ_STREAM_HPP diff --git a/libtorrent/include/asio/buffered_read_stream_fwd.hpp b/libtorrent/include/asio/buffered_read_stream_fwd.hpp new file mode 100644 index 000000000..df4270366 --- /dev/null +++ b/libtorrent/include/asio/buffered_read_stream_fwd.hpp @@ -0,0 +1,29 @@ +// +// buffered_read_stream_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFERED_READ_STREAM_FWD_HPP +#define ASIO_BUFFERED_READ_STREAM_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { + +template +class buffered_read_stream; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFERED_READ_STREAM_FWD_HPP diff --git a/libtorrent/include/asio/buffered_stream.hpp b/libtorrent/include/asio/buffered_stream.hpp new file mode 100644 index 000000000..b6901a6d4 --- /dev/null +++ b/libtorrent/include/asio/buffered_stream.hpp @@ -0,0 +1,243 @@ +// +// buffered_stream.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFERED_STREAM_HPP +#define ASIO_BUFFERED_STREAM_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffered_read_stream.hpp" +#include "asio/buffered_write_stream.hpp" +#include "asio/buffered_stream_fwd.hpp" +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { + +/// Adds buffering to the read- and write-related operations of a stream. +/** + * The buffered_stream class template can be used to add buffering to the + * synchronous and asynchronous read and write operations of a stream. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Concepts: + * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. + */ +template +class buffered_stream + : private noncopyable +{ +public: + /// The type of the next layer. + typedef typename boost::remove_reference::type next_layer_type; + + /// The type of the lowest layer. + typedef typename next_layer_type::lowest_layer_type lowest_layer_type; + + /// Construct, passing the specified argument to initialise the next layer. + template + explicit buffered_stream(Arg& a) + : inner_stream_impl_(a), + stream_impl_(inner_stream_impl_) + { + } + + /// Construct, passing the specified argument to initialise the next layer. + template + explicit buffered_stream(Arg& a, std::size_t read_buffer_size, + std::size_t write_buffer_size) + : inner_stream_impl_(a, write_buffer_size), + stream_impl_(inner_stream_impl_, read_buffer_size) + { + } + + /// Get a reference to the next layer. + next_layer_type& next_layer() + { + return stream_impl_.next_layer().next_layer(); + } + + /// Get a reference to the lowest layer. + lowest_layer_type& lowest_layer() + { + return stream_impl_.lowest_layer(); + } + + /// Get the io_service associated with the object. + asio::io_service& io_service() + { + return stream_impl_.io_service(); + } + + /// Close the stream. + void close() + { + stream_impl_.close(); + } + + /// Close the stream. + asio::error_code close(asio::error_code& ec) + { + return stream_impl_.close(ec); + } + + /// Flush all data from the buffer to the next layer. Returns the number of + /// bytes written to the next layer on the last write operation. Throws an + /// exception on failure. + std::size_t flush() + { + return stream_impl_.next_layer().flush(); + } + + /// Flush all data from the buffer to the next layer. Returns the number of + /// bytes written to the next layer on the last write operation, or 0 if an + /// error occurred. + std::size_t flush(asio::error_code& ec) + { + return stream_impl_.next_layer().flush(ec); + } + + /// Start an asynchronous flush. + template + void async_flush(WriteHandler handler) + { + return stream_impl_.next_layer().async_flush(handler); + } + + /// Write the given data to the stream. Returns the number of bytes written. + /// Throws an exception on failure. + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + return stream_impl_.write_some(buffers); + } + + /// Write the given data to the stream. Returns the number of bytes written, + /// or 0 if an error occurred. + template + std::size_t write_some(const ConstBufferSequence& buffers, + asio::error_code& ec) + { + return stream_impl_.write_some(buffers, ec); + } + + /// Start an asynchronous write. The data being written must be valid for the + /// lifetime of the asynchronous operation. + template + void async_write_some(const ConstBufferSequence& buffers, + WriteHandler handler) + { + stream_impl_.async_write_some(buffers, handler); + } + + /// Fill the buffer with some data. Returns the number of bytes placed in the + /// buffer as a result of the operation. Throws an exception on failure. + std::size_t fill() + { + return stream_impl_.fill(); + } + + /// Fill the buffer with some data. Returns the number of bytes placed in the + /// buffer as a result of the operation, or 0 if an error occurred. + std::size_t fill(asio::error_code& ec) + { + return stream_impl_.fill(ec); + } + + /// Start an asynchronous fill. + template + void async_fill(ReadHandler handler) + { + stream_impl_.async_fill(handler); + } + + /// Read some data from the stream. Returns the number of bytes read. Throws + /// an exception on failure. + template + std::size_t read_some(const MutableBufferSequence& buffers) + { + return stream_impl_.read_some(buffers); + } + + /// Read some data from the stream. Returns the number of bytes read or 0 if + /// an error occurred. + template + std::size_t read_some(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return stream_impl_.read_some(buffers, ec); + } + + /// Start an asynchronous read. The buffer into which the data will be read + /// must be valid for the lifetime of the asynchronous operation. + template + void async_read_some(const MutableBufferSequence& buffers, + ReadHandler handler) + { + stream_impl_.async_read_some(buffers, handler); + } + + /// Peek at the incoming data on the stream. Returns the number of bytes read. + /// Throws an exception on failure. + template + std::size_t peek(const MutableBufferSequence& buffers) + { + return stream_impl_.peek(buffers); + } + + /// Peek at the incoming data on the stream. Returns the number of bytes read, + /// or 0 if an error occurred. + template + std::size_t peek(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return stream_impl_.peek(buffers, ec); + } + + /// Determine the amount of data that may be read without blocking. + std::size_t in_avail() + { + return stream_impl_.in_avail(); + } + + /// Determine the amount of data that may be read without blocking. + std::size_t in_avail(asio::error_code& ec) + { + return stream_impl_.in_avail(ec); + } + +private: + // The buffered write stream. + typedef buffered_write_stream write_stream_type; + write_stream_type inner_stream_impl_; + + // The buffered read stream. + typedef buffered_read_stream read_stream_type; + read_stream_type stream_impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFERED_STREAM_HPP diff --git a/libtorrent/include/asio/buffered_stream_fwd.hpp b/libtorrent/include/asio/buffered_stream_fwd.hpp new file mode 100644 index 000000000..10d1c384e --- /dev/null +++ b/libtorrent/include/asio/buffered_stream_fwd.hpp @@ -0,0 +1,29 @@ +// +// buffered_stream_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFERED_STREAM_FWD_HPP +#define ASIO_BUFFERED_STREAM_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { + +template +class buffered_stream; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFERED_STREAM_FWD_HPP diff --git a/libtorrent/include/asio/buffered_write_stream.hpp b/libtorrent/include/asio/buffered_write_stream.hpp new file mode 100644 index 000000000..ffe3a2ee7 --- /dev/null +++ b/libtorrent/include/asio/buffered_write_stream.hpp @@ -0,0 +1,361 @@ +// +// buffered_write_stream.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFERED_WRITE_STREAM_HPP +#define ASIO_BUFFERED_WRITE_STREAM_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffered_write_stream_fwd.hpp" +#include "asio/buffer.hpp" +#include "asio/completion_condition.hpp" +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/write.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/buffered_stream_storage.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { + +/// Adds buffering to the write-related operations of a stream. +/** + * The buffered_write_stream class template can be used to add buffering to the + * synchronous and asynchronous write operations of a stream. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Concepts: + * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. + */ +template +class buffered_write_stream + : private noncopyable +{ +public: + /// The type of the next layer. + typedef typename boost::remove_reference::type next_layer_type; + + /// The type of the lowest layer. + typedef typename next_layer_type::lowest_layer_type lowest_layer_type; + +#if defined(GENERATING_DOCUMENTATION) + /// The default buffer size. + static const std::size_t default_buffer_size = implementation_defined; +#else + BOOST_STATIC_CONSTANT(std::size_t, default_buffer_size = 1024); +#endif + + /// Construct, passing the specified argument to initialise the next layer. + template + explicit buffered_write_stream(Arg& a) + : next_layer_(a), + storage_(default_buffer_size) + { + } + + /// Construct, passing the specified argument to initialise the next layer. + template + buffered_write_stream(Arg& a, std::size_t buffer_size) + : next_layer_(a), + storage_(buffer_size) + { + } + + /// Get a reference to the next layer. + next_layer_type& next_layer() + { + return next_layer_; + } + + /// Get a reference to the lowest layer. + lowest_layer_type& lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /// Get the io_service associated with the object. + asio::io_service& io_service() + { + return next_layer_.io_service(); + } + + /// Close the stream. + void close() + { + next_layer_.close(); + } + + /// Close the stream. + asio::error_code close(asio::error_code& ec) + { + return next_layer_.close(ec); + } + + /// Flush all data from the buffer to the next layer. Returns the number of + /// bytes written to the next layer on the last write operation. Throws an + /// exception on failure. + std::size_t flush() + { + std::size_t bytes_written = write(next_layer_, + buffer(storage_.data(), storage_.size())); + storage_.consume(bytes_written); + return bytes_written; + } + + /// Flush all data from the buffer to the next layer. Returns the number of + /// bytes written to the next layer on the last write operation, or 0 if an + /// error occurred. + std::size_t flush(asio::error_code& ec) + { + std::size_t bytes_written = write(next_layer_, + buffer(storage_.data(), storage_.size()), + transfer_all(), ec); + storage_.consume(bytes_written); + return bytes_written; + } + + template + class flush_handler + { + public: + flush_handler(asio::io_service& io_service, + detail::buffered_stream_storage& storage, WriteHandler handler) + : io_service_(io_service), + storage_(storage), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_written) + { + storage_.consume(bytes_written); + io_service_.dispatch(detail::bind_handler(handler_, ec, bytes_written)); + } + + private: + asio::io_service& io_service_; + detail::buffered_stream_storage& storage_; + WriteHandler handler_; + }; + + /// Start an asynchronous flush. + template + void async_flush(WriteHandler handler) + { + async_write(next_layer_, buffer(storage_.data(), storage_.size()), + flush_handler(io_service(), storage_, handler)); + } + + /// Write the given data to the stream. Returns the number of bytes written. + /// Throws an exception on failure. + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + if (storage_.size() == storage_.capacity()) + flush(); + return copy(buffers); + } + + /// Write the given data to the stream. Returns the number of bytes written, + /// or 0 if an error occurred and the error handler did not throw. + template + std::size_t write_some(const ConstBufferSequence& buffers, + asio::error_code& ec) + { + ec = asio::error_code(); + if (storage_.size() == storage_.capacity() && !flush(ec)) + return 0; + return copy(buffers); + } + + template + class write_some_handler + { + public: + write_some_handler(asio::io_service& io_service, + detail::buffered_stream_storage& storage, + const ConstBufferSequence& buffers, WriteHandler handler) + : io_service_(io_service), + storage_(storage), + buffers_(buffers), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, std::size_t) + { + if (ec) + { + std::size_t length = 0; + io_service_.dispatch(detail::bind_handler(handler_, ec, length)); + } + else + { + using namespace std; // For memcpy. + + std::size_t orig_size = storage_.size(); + std::size_t space_avail = storage_.capacity() - orig_size; + std::size_t bytes_copied = 0; + + typename ConstBufferSequence::const_iterator iter = buffers_.begin(); + typename ConstBufferSequence::const_iterator end = buffers_.end(); + for (; iter != end && space_avail > 0; ++iter) + { + std::size_t bytes_avail = buffer_size(*iter); + std::size_t length = (bytes_avail < space_avail) + ? bytes_avail : space_avail; + storage_.resize(orig_size + bytes_copied + length); + memcpy(storage_.data() + orig_size + bytes_copied, + buffer_cast(*iter), length); + bytes_copied += length; + space_avail -= length; + } + + io_service_.dispatch(detail::bind_handler(handler_, ec, bytes_copied)); + } + } + + private: + asio::io_service& io_service_; + detail::buffered_stream_storage& storage_; + ConstBufferSequence buffers_; + WriteHandler handler_; + }; + + /// Start an asynchronous write. The data being written must be valid for the + /// lifetime of the asynchronous operation. + template + void async_write_some(const ConstBufferSequence& buffers, + WriteHandler handler) + { + if (storage_.size() == storage_.capacity()) + { + async_flush(write_some_handler( + io_service(), storage_, buffers, handler)); + } + else + { + std::size_t bytes_copied = copy(buffers); + io_service().post(detail::bind_handler( + handler, asio::error_code(), bytes_copied)); + } + } + + /// Read some data from the stream. Returns the number of bytes read. Throws + /// an exception on failure. + template + std::size_t read_some(const MutableBufferSequence& buffers) + { + return next_layer_.read_some(buffers); + } + + /// Read some data from the stream. Returns the number of bytes read or 0 if + /// an error occurred. + template + std::size_t read_some(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return next_layer_.read_some(buffers, ec); + } + + /// Start an asynchronous read. The buffer into which the data will be read + /// must be valid for the lifetime of the asynchronous operation. + template + void async_read_some(const MutableBufferSequence& buffers, + ReadHandler handler) + { + next_layer_.async_read_some(buffers, handler); + } + + /// Peek at the incoming data on the stream. Returns the number of bytes read. + /// Throws an exception on failure. + template + std::size_t peek(const MutableBufferSequence& buffers) + { + return next_layer_.peek(buffers); + } + + /// Peek at the incoming data on the stream. Returns the number of bytes read, + /// or 0 if an error occurred. + template + std::size_t peek(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return next_layer_.peek(buffers, ec); + } + + /// Determine the amount of data that may be read without blocking. + std::size_t in_avail() + { + return next_layer_.in_avail(); + } + + /// Determine the amount of data that may be read without blocking. + std::size_t in_avail(asio::error_code& ec) + { + return next_layer_.in_avail(ec); + } + +private: + /// Copy data into the internal buffer from the specified source buffer. + /// Returns the number of bytes copied. + template + std::size_t copy(const ConstBufferSequence& buffers) + { + using namespace std; // For memcpy. + + std::size_t orig_size = storage_.size(); + std::size_t space_avail = storage_.capacity() - orig_size; + std::size_t bytes_copied = 0; + + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + for (; iter != end && space_avail > 0; ++iter) + { + std::size_t bytes_avail = buffer_size(*iter); + std::size_t length = (bytes_avail < space_avail) + ? bytes_avail : space_avail; + storage_.resize(orig_size + bytes_copied + length); + memcpy(storage_.data() + orig_size + bytes_copied, + buffer_cast(*iter), length); + bytes_copied += length; + space_avail -= length; + } + + return bytes_copied; + } + + /// The next layer. + Stream next_layer_; + + // The data in the buffer. + detail::buffered_stream_storage storage_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFERED_WRITE_STREAM_HPP diff --git a/libtorrent/include/asio/buffered_write_stream_fwd.hpp b/libtorrent/include/asio/buffered_write_stream_fwd.hpp new file mode 100644 index 000000000..84cf36e3a --- /dev/null +++ b/libtorrent/include/asio/buffered_write_stream_fwd.hpp @@ -0,0 +1,29 @@ +// +// buffered_write_stream_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BUFFERED_WRITE_STREAM_FWD_HPP +#define ASIO_BUFFERED_WRITE_STREAM_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { + +template +class buffered_write_stream; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BUFFERED_WRITE_STREAM_FWD_HPP diff --git a/libtorrent/include/asio/completion_condition.hpp b/libtorrent/include/asio/completion_condition.hpp new file mode 100644 index 000000000..42696d599 --- /dev/null +++ b/libtorrent/include/asio/completion_condition.hpp @@ -0,0 +1,101 @@ +// +// completion_condition.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_COMPLETION_CONDITION_HPP +#define ASIO_COMPLETION_CONDITION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { + +namespace detail { + +class transfer_all_t +{ +public: + typedef bool result_type; + + template + bool operator()(const Error& err, std::size_t) + { + return !!err; + } +}; + +class transfer_at_least_t +{ +public: + typedef bool result_type; + + explicit transfer_at_least_t(std::size_t minimum) + : minimum_(minimum) + { + } + + template + bool operator()(const Error& err, std::size_t bytes_transferred) + { + return !!err || bytes_transferred >= minimum_; + } + +private: + std::size_t minimum_; +}; + +} // namespace detail + +/** + * @defgroup completion_condition Completion Condition Function Objects + * + * Function objects used for determining when a read or write operation should + * complete. + */ +/*@{*/ + +/// Return a completion condition function object that indicates that a read or +/// write operation should continue until all of the data has been transferred, +/// or until an error occurs. +#if defined(GENERATING_DOCUMENTATION) +unspecified transfer_all(); +#else +inline detail::transfer_all_t transfer_all() +{ + return detail::transfer_all_t(); +} +#endif + +/// Return a completion condition function object that indicates that a read or +/// write operation should continue until a minimum number of bytes has been +/// transferred, or until an error occurs. +#if defined(GENERATING_DOCUMENTATION) +unspecified transfer_at_least(std::size_t minimum); +#else +inline detail::transfer_at_least_t transfer_at_least(std::size_t minimum) +{ + return detail::transfer_at_least_t(minimum); +} +#endif + +/*@}*/ + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_COMPLETION_CONDITION_HPP diff --git a/libtorrent/include/asio/datagram_socket_service.hpp b/libtorrent/include/asio/datagram_socket_service.hpp new file mode 100644 index 000000000..1f858de61 --- /dev/null +++ b/libtorrent/include/asio/datagram_socket_service.hpp @@ -0,0 +1,320 @@ +// +// datagram_socket_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DATAGRAM_SOCKET_SERVICE_HPP +#define ASIO_DATAGRAM_SOCKET_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/epoll_reactor.hpp" +#include "asio/detail/kqueue_reactor.hpp" +#include "asio/detail/select_reactor.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/reactive_socket_service.hpp" +#include "asio/detail/win_iocp_socket_service.hpp" + +namespace asio { + +/// Default service implementation for a datagram socket. +template +class datagram_socket_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base > +#endif +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + +private: + // The type of the platform-specific implementation. +#if defined(ASIO_HAS_IOCP) + typedef detail::win_iocp_socket_service service_impl_type; +#elif defined(ASIO_HAS_EPOLL) + typedef detail::reactive_socket_service< + Protocol, detail::epoll_reactor > service_impl_type; +#elif defined(ASIO_HAS_KQUEUE) + typedef detail::reactive_socket_service< + Protocol, detail::kqueue_reactor > service_impl_type; +#else + typedef detail::reactive_socket_service< + Protocol, detail::select_reactor > service_impl_type; +#endif + +public: + /// The type of a datagram socket. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined implementation_type; +#else + typedef typename service_impl_type::implementation_type implementation_type; +#endif + + /// The native socket type. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined native_type; +#else + typedef typename service_impl_type::native_type native_type; +#endif + + /// Construct a new datagram socket service for the specified io_service. + explicit datagram_socket_service(asio::io_service& io_service) + : asio::detail::service_base< + datagram_socket_service >(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Construct a new datagram socket implementation. + void construct(implementation_type& impl) + { + service_impl_.construct(impl); + } + + /// Destroy a datagram socket implementation. + void destroy(implementation_type& impl) + { + service_impl_.destroy(impl); + } + + // Open a new datagram socket implementation. + asio::error_code open(implementation_type& impl, + const protocol_type& protocol, asio::error_code& ec) + { + if (protocol.type() == SOCK_DGRAM) + service_impl_.open(impl, protocol, ec); + else + ec = asio::error::invalid_argument; + return ec; + } + + /// Assign an existing native socket to a datagram socket. + asio::error_code assign(implementation_type& impl, + const protocol_type& protocol, const native_type& native_socket, + asio::error_code& ec) + { + return service_impl_.assign(impl, protocol, native_socket, ec); + } + + /// Determine whether the socket is open. + bool is_open(const implementation_type& impl) const + { + return service_impl_.is_open(impl); + } + + /// Close a datagram socket implementation. + asio::error_code close(implementation_type& impl, + asio::error_code& ec) + { + return service_impl_.close(impl, ec); + } + + /// Get the native socket implementation. + native_type native(implementation_type& impl) + { + return service_impl_.native(impl); + } + + /// Cancel all asynchronous operations associated with the socket. + asio::error_code cancel(implementation_type& impl, + asio::error_code& ec) + { + return service_impl_.cancel(impl, ec); + } + + /// Determine whether the socket is at the out-of-band data mark. + bool at_mark(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.at_mark(impl, ec); + } + + /// Determine the number of bytes available for reading. + std::size_t available(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.available(impl, ec); + } + + // Bind the datagram socket to the specified local endpoint. + asio::error_code bind(implementation_type& impl, + const endpoint_type& endpoint, asio::error_code& ec) + { + return service_impl_.bind(impl, endpoint, ec); + } + + /// Connect the datagram socket to the specified endpoint. + asio::error_code connect(implementation_type& impl, + const endpoint_type& peer_endpoint, asio::error_code& ec) + { + return service_impl_.connect(impl, peer_endpoint, ec); + } + + /// Start an asynchronous connect. + template + void async_connect(implementation_type& impl, + const endpoint_type& peer_endpoint, ConnectHandler handler) + { + service_impl_.async_connect(impl, peer_endpoint, handler); + } + + /// Set a socket option. + template + asio::error_code set_option(implementation_type& impl, + const SettableSocketOption& option, asio::error_code& ec) + { + return service_impl_.set_option(impl, option, ec); + } + + /// Get a socket option. + template + asio::error_code get_option(const implementation_type& impl, + GettableSocketOption& option, asio::error_code& ec) const + { + return service_impl_.get_option(impl, option, ec); + } + + /// Perform an IO control command on the socket. + template + asio::error_code io_control(implementation_type& impl, + IoControlCommand& command, asio::error_code& ec) + { + return service_impl_.io_control(impl, command, ec); + } + + /// Get the local endpoint. + endpoint_type local_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.local_endpoint(impl, ec); + } + + /// Get the remote endpoint. + endpoint_type remote_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.remote_endpoint(impl, ec); + } + + /// Disable sends or receives on the socket. + asio::error_code shutdown(implementation_type& impl, + socket_base::shutdown_type what, asio::error_code& ec) + { + return service_impl_.shutdown(impl, what, ec); + } + + /// Send the given data to the peer. + template + std::size_t send(implementation_type& impl, + const ConstBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return service_impl_.send(impl, buffers, flags, ec); + } + + /// Start an asynchronous send. + template + void async_send(implementation_type& impl, const ConstBufferSequence& buffers, + socket_base::message_flags flags, WriteHandler handler) + { + service_impl_.async_send(impl, buffers, flags, handler); + } + + /// Send a datagram to the specified endpoint. + template + std::size_t send_to(implementation_type& impl, + const ConstBufferSequence& buffers, const endpoint_type& destination, + socket_base::message_flags flags, asio::error_code& ec) + { + return service_impl_.send_to(impl, buffers, destination, flags, ec); + } + + /// Start an asynchronous send. + template + void async_send_to(implementation_type& impl, + const ConstBufferSequence& buffers, const endpoint_type& destination, + socket_base::message_flags flags, WriteHandler handler) + { + service_impl_.async_send_to(impl, buffers, destination, flags, handler); + } + + /// Receive some data from the peer. + template + std::size_t receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return service_impl_.receive(impl, buffers, flags, ec); + } + + /// Start an asynchronous receive. + template + void async_receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, ReadHandler handler) + { + service_impl_.async_receive(impl, buffers, flags, handler); + } + + /// Receive a datagram with the endpoint of the sender. + template + std::size_t receive_from(implementation_type& impl, + const MutableBufferSequence& buffers, endpoint_type& sender_endpoint, + socket_base::message_flags flags, asio::error_code& ec) + { + return service_impl_.receive_from(impl, buffers, sender_endpoint, flags, + ec); + } + + /// Start an asynchronous receive that will get the endpoint of the sender. + template + void async_receive_from(implementation_type& impl, + const MutableBufferSequence& buffers, endpoint_type& sender_endpoint, + socket_base::message_flags flags, ReadHandler handler) + { + service_impl_.async_receive_from(impl, buffers, sender_endpoint, flags, + handler); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DATAGRAM_SOCKET_SERVICE_HPP diff --git a/libtorrent/include/asio/deadline_timer.hpp b/libtorrent/include/asio/deadline_timer.hpp new file mode 100644 index 000000000..2079c5d61 --- /dev/null +++ b/libtorrent/include/asio/deadline_timer.hpp @@ -0,0 +1,37 @@ +// +// deadline_timer.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DEADLINE_TIMER_HPP +#define ASIO_DEADLINE_TIMER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_types.hpp" // Must come before posix_time. + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_deadline_timer.hpp" + +namespace asio { + +/// Typedef for the typical usage of timer. +typedef basic_deadline_timer deadline_timer; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DEADLINE_TIMER_HPP diff --git a/libtorrent/include/asio/deadline_timer_service.hpp b/libtorrent/include/asio/deadline_timer_service.hpp new file mode 100644 index 000000000..17b97350b --- /dev/null +++ b/libtorrent/include/asio/deadline_timer_service.hpp @@ -0,0 +1,164 @@ +// +// deadline_timer_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DEADLINE_TIMER_SERVICE_HPP +#define ASIO_DEADLINE_TIMER_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/time_traits.hpp" +#include "asio/detail/deadline_timer_service.hpp" +#include "asio/detail/epoll_reactor.hpp" +#include "asio/detail/kqueue_reactor.hpp" +#include "asio/detail/select_reactor.hpp" +#include "asio/detail/service_base.hpp" + +namespace asio { + +/// Default service implementation for a timer. +template > +class deadline_timer_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base< + deadline_timer_service > +#endif +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The time traits type. + typedef TimeTraits traits_type; + + /// The time type. + typedef typename traits_type::time_type time_type; + + /// The duration type. + typedef typename traits_type::duration_type duration_type; + +private: + // The type of the platform-specific implementation. +#if defined(ASIO_HAS_IOCP) + typedef detail::deadline_timer_service< + traits_type, detail::select_reactor > service_impl_type; +#elif defined(ASIO_HAS_EPOLL) + typedef detail::deadline_timer_service< + traits_type, detail::epoll_reactor > service_impl_type; +#elif defined(ASIO_HAS_KQUEUE) + typedef detail::deadline_timer_service< + traits_type, detail::kqueue_reactor > service_impl_type; +#else + typedef detail::deadline_timer_service< + traits_type, detail::select_reactor > service_impl_type; +#endif + +public: + /// The implementation type of the deadline timer. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined implementation_type; +#else + typedef typename service_impl_type::implementation_type implementation_type; +#endif + + /// Construct a new timer service for the specified io_service. + explicit deadline_timer_service(asio::io_service& io_service) + : asio::detail::service_base< + deadline_timer_service >(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Construct a new timer implementation. + void construct(implementation_type& impl) + { + service_impl_.construct(impl); + } + + /// Destroy a timer implementation. + void destroy(implementation_type& impl) + { + service_impl_.destroy(impl); + } + + /// Cancel any asynchronous wait operations associated with the timer. + std::size_t cancel(implementation_type& impl, asio::error_code& ec) + { + return service_impl_.cancel(impl, ec); + } + + /// Get the expiry time for the timer as an absolute time. + time_type expires_at(const implementation_type& impl) const + { + return service_impl_.expires_at(impl); + } + + /// Set the expiry time for the timer as an absolute time. + std::size_t expires_at(implementation_type& impl, + const time_type& expiry_time, asio::error_code& ec) + { + return service_impl_.expires_at(impl, expiry_time, ec); + } + + /// Get the expiry time for the timer relative to now. + duration_type expires_from_now(const implementation_type& impl) const + { + return service_impl_.expires_from_now(impl); + } + + /// Set the expiry time for the timer relative to now. + std::size_t expires_from_now(implementation_type& impl, + const duration_type& expiry_time, asio::error_code& ec) + { + return service_impl_.expires_from_now(impl, expiry_time, ec); + } + + // Perform a blocking wait on the timer. + void wait(implementation_type& impl, asio::error_code& ec) + { + service_impl_.wait(impl, ec); + } + + // Start an asynchronous wait on the timer. + template + void async_wait(implementation_type& impl, WaitHandler handler) + { + service_impl_.async_wait(impl, handler); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DEADLINE_TIMER_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/CVS/Entries b/libtorrent/include/asio/detail/CVS/Entries new file mode 100644 index 000000000..e6deb1bfd --- /dev/null +++ b/libtorrent/include/asio/detail/CVS/Entries @@ -0,0 +1,74 @@ +/bind_handler.hpp/1.18/Thu Jan 4 05:44:44 2007// +/buffer_resize_guard.hpp/1.9/Thu Jan 4 05:44:44 2007// +/buffered_stream_storage.hpp/1.5/Thu Jan 4 05:44:44 2007// +/call_stack.hpp/1.3/Thu Jan 4 05:44:44 2007// +/const_buffers_iterator.hpp/1.3/Thu Jan 4 05:44:44 2007// +/consuming_buffers.hpp/1.7/Sat Jan 13 13:41:09 2007// +/deadline_timer_service.hpp/1.7/Mon Jan 8 02:47:13 2007// +/epoll_reactor.hpp/1.40/Thu Jan 4 05:44:44 2007// +/epoll_reactor_fwd.hpp/1.3/Thu Jan 4 05:44:44 2007// +/event.hpp/1.13/Thu Jan 4 05:44:44 2007// +/fd_set_adapter.hpp/1.5/Thu Jan 4 05:44:44 2007// +/handler_alloc_helpers.hpp/1.8/Thu Jan 4 05:44:44 2007// +/handler_invoke_helpers.hpp/1.2/Thu Jan 4 05:44:44 2007// +/hash_map.hpp/1.19/Thu Mar 22 21:13:13 2007// +/io_control.hpp/1.5/Thu Jan 4 05:44:44 2007// +/kqueue_reactor.hpp/1.30/Thu Mar 22 21:08:02 2007// +/kqueue_reactor_fwd.hpp/1.3/Thu Jan 4 05:44:44 2007// +/local_free_on_block_exit.hpp/1.2/Thu Jan 4 05:44:44 2007// +/mutex.hpp/1.13/Thu Jan 4 05:44:44 2007// +/noncopyable.hpp/1.3/Thu Jan 4 05:44:44 2007// +/null_event.hpp/1.3/Thu Jan 4 05:44:44 2007// +/null_mutex.hpp/1.3/Thu Jan 4 05:44:44 2007// +/null_signal_blocker.hpp/1.3/Thu Jan 4 05:44:44 2007// +/null_thread.hpp/1.5/Mon Jan 8 22:12:46 2007// +/null_tss_ptr.hpp/1.3/Thu Jan 4 05:44:44 2007// +/old_win_sdk_compat.hpp/1.5/Sat May 12 08:16:25 2007// +/pipe_select_interrupter.hpp/1.11/Thu Jan 4 05:44:44 2007// +/pop_options.hpp/1.10/Thu Jan 4 05:44:44 2007// +/posix_event.hpp/1.16/Thu Jan 4 05:44:44 2007// +/posix_fd_set_adapter.hpp/1.4/Tue Feb 13 07:13:29 2007// +/posix_mutex.hpp/1.15/Thu Jan 4 05:44:44 2007// +/posix_signal_blocker.hpp/1.10/Sat Feb 17 22:57:37 2007// +/posix_thread.hpp/1.17/Thu Jan 4 05:44:45 2007// +/posix_tss_ptr.hpp/1.10/Thu Jan 4 05:44:45 2007// +/push_options.hpp/1.16/Thu Jan 4 05:44:45 2007// +/reactive_socket_service.hpp/1.59/Sun May 13 23:00:01 2007// +/reactor_op_queue.hpp/1.24/Thu Jan 4 05:44:45 2007// +/resolver_service.hpp/1.11/Thu Jan 4 09:06:56 2007// +/scoped_lock.hpp/1.9/Thu Jan 4 05:44:45 2007// +/select_interrupter.hpp/1.10/Thu Jan 4 05:44:45 2007// +/select_reactor.hpp/1.49/Thu Jan 4 05:44:45 2007// +/select_reactor_fwd.hpp/1.2/Thu Jan 4 05:44:45 2007// +/service_base.hpp/1.2/Thu Jan 4 05:44:45 2007// +/service_id.hpp/1.2/Thu Jan 4 05:44:45 2007// +/service_registry.hpp/1.19/Tue Feb 13 12:06:43 2007// +/service_registry_fwd.hpp/1.2/Thu Jan 4 05:44:45 2007// +/signal_blocker.hpp/1.10/Thu Jan 4 05:44:45 2007// +/signal_init.hpp/1.11/Thu Jan 4 05:44:45 2007// +/socket_holder.hpp/1.10/Thu Jan 4 05:44:45 2007// +/socket_ops.hpp/1.74/Mon May 21 12:34:39 2007// +/socket_option.hpp/1.7/Sat Feb 17 22:57:37 2007// +/socket_select_interrupter.hpp/1.15/Thu May 10 23:48:52 2007// +/socket_types.hpp/1.41/Sun May 13 07:59:21 2007// +/strand_service.hpp/1.15/Thu Jan 4 05:44:45 2007// +/task_io_service.hpp/1.18/Wed Feb 14 13:26:21 2007// +/task_io_service_fwd.hpp/1.2/Thu Jan 4 05:44:45 2007// +/thread.hpp/1.13/Thu Jan 4 05:44:45 2007// +/throw_error.hpp/1.3/Thu Jan 4 05:44:45 2007// +/timer_queue.hpp/1.6/Sun Apr 22 07:07:15 2007// +/timer_queue_base.hpp/1.2/Thu Jan 4 05:44:45 2007// +/tss_ptr.hpp/1.8/Thu Jan 4 05:44:45 2007// +/win_event.hpp/1.14/Thu Jan 4 05:44:45 2007// +/win_fd_set_adapter.hpp/1.4/Thu Jan 4 05:44:45 2007// +/win_iocp_io_service.hpp/1.24/Mon Jan 8 01:09:14 2007// +/win_iocp_io_service_fwd.hpp/1.4/Thu Jan 4 05:44:45 2007// +/win_iocp_operation.hpp/1.16/Thu Jan 4 05:44:45 2007// +/win_iocp_socket_service.hpp/1.75/Sat May 12 09:07:32 2007// +/win_mutex.hpp/1.16/Thu Jan 4 05:44:45 2007// +/win_signal_blocker.hpp/1.9/Thu Jan 4 05:44:45 2007// +/win_thread.hpp/1.20/Thu Jan 4 05:44:45 2007// +/win_tss_ptr.hpp/1.10/Thu Jan 4 05:44:45 2007// +/winsock_init.hpp/1.16/Thu Jan 4 05:44:45 2007// +/wrapped_handler.hpp/1.11/Thu Jan 4 05:44:45 2007// +D diff --git a/libtorrent/include/asio/detail/CVS/Repository b/libtorrent/include/asio/detail/CVS/Repository new file mode 100644 index 000000000..711cf30c4 --- /dev/null +++ b/libtorrent/include/asio/detail/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio/detail diff --git a/libtorrent/include/asio/detail/CVS/Root b/libtorrent/include/asio/detail/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/detail/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/detail/bind_handler.hpp b/libtorrent/include/asio/detail/bind_handler.hpp new file mode 100644 index 000000000..497bcfcc2 --- /dev/null +++ b/libtorrent/include/asio/detail/bind_handler.hpp @@ -0,0 +1,349 @@ +// +// bind_handler.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_BIND_HANDLER_HPP +#define ASIO_DETAIL_BIND_HANDLER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" + +namespace asio { +namespace detail { + +template +class binder1 +{ +public: + binder1(const Handler& handler, const Arg1& arg1) + : handler_(handler), + arg1_(arg1) + { + } + + void operator()() + { + handler_(arg1_); + } + + void operator()() const + { + handler_(arg1_); + } + +//private: + Handler handler_; + Arg1 arg1_; +}; + +template +inline void* asio_handler_allocate(std::size_t size, + binder1* this_handler) +{ + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); +} + +template +inline void asio_handler_deallocate(void* pointer, std::size_t size, + binder1* this_handler) +{ + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); +} + +template +inline void asio_handler_invoke(const Function& function, + binder1* this_handler) +{ + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); +} + +template +inline binder1 bind_handler(const Handler& handler, + const Arg1& arg1) +{ + return binder1(handler, arg1); +} + +template +class binder2 +{ +public: + binder2(const Handler& handler, const Arg1& arg1, const Arg2& arg2) + : handler_(handler), + arg1_(arg1), + arg2_(arg2) + { + } + + void operator()() + { + handler_(arg1_, arg2_); + } + + void operator()() const + { + handler_(arg1_, arg2_); + } + +//private: + Handler handler_; + Arg1 arg1_; + Arg2 arg2_; +}; + +template +inline void* asio_handler_allocate(std::size_t size, + binder2* this_handler) +{ + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); +} + +template +inline void asio_handler_deallocate(void* pointer, std::size_t size, + binder2* this_handler) +{ + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); +} + +template +inline void asio_handler_invoke(const Function& function, + binder2* this_handler) +{ + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); +} + +template +inline binder2 bind_handler(const Handler& handler, + const Arg1& arg1, const Arg2& arg2) +{ + return binder2(handler, arg1, arg2); +} + +template +class binder3 +{ +public: + binder3(const Handler& handler, const Arg1& arg1, const Arg2& arg2, + const Arg3& arg3) + : handler_(handler), + arg1_(arg1), + arg2_(arg2), + arg3_(arg3) + { + } + + void operator()() + { + handler_(arg1_, arg2_, arg3_); + } + + void operator()() const + { + handler_(arg1_, arg2_, arg3_); + } + +//private: + Handler handler_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; +}; + +template +inline void* asio_handler_allocate(std::size_t size, + binder3* this_handler) +{ + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); +} + +template +inline void asio_handler_deallocate(void* pointer, std::size_t size, + binder3* this_handler) +{ + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); +} + +template +inline void asio_handler_invoke(const Function& function, + binder3* this_handler) +{ + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); +} + +template +inline binder3 bind_handler(const Handler& handler, + const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) +{ + return binder3(handler, arg1, arg2, arg3); +} + +template +class binder4 +{ +public: + binder4(const Handler& handler, const Arg1& arg1, const Arg2& arg2, + const Arg3& arg3, const Arg4& arg4) + : handler_(handler), + arg1_(arg1), + arg2_(arg2), + arg3_(arg3), + arg4_(arg4) + { + } + + void operator()() + { + handler_(arg1_, arg2_, arg3_, arg4_); + } + + void operator()() const + { + handler_(arg1_, arg2_, arg3_, arg4_); + } + +//private: + Handler handler_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; +}; + +template +inline void* asio_handler_allocate(std::size_t size, + binder4* this_handler) +{ + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); +} + +template +inline void asio_handler_deallocate(void* pointer, std::size_t size, + binder4* this_handler) +{ + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); +} + +template +inline void asio_handler_invoke(const Function& function, + binder4* this_handler) +{ + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); +} + +template +inline binder4 bind_handler( + const Handler& handler, const Arg1& arg1, const Arg2& arg2, + const Arg3& arg3, const Arg4& arg4) +{ + return binder4(handler, arg1, arg2, arg3, + arg4); +} + +template +class binder5 +{ +public: + binder5(const Handler& handler, const Arg1& arg1, const Arg2& arg2, + const Arg3& arg3, const Arg4& arg4, const Arg5& arg5) + : handler_(handler), + arg1_(arg1), + arg2_(arg2), + arg3_(arg3), + arg4_(arg4), + arg5_(arg5) + { + } + + void operator()() + { + handler_(arg1_, arg2_, arg3_, arg4_, arg5_); + } + + void operator()() const + { + handler_(arg1_, arg2_, arg3_, arg4_, arg5_); + } + +//private: + Handler handler_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; +}; + +template +inline void* asio_handler_allocate(std::size_t size, + binder5* this_handler) +{ + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); +} + +template +inline void asio_handler_deallocate(void* pointer, std::size_t size, + binder5* this_handler) +{ + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); +} + +template +inline void asio_handler_invoke(const Function& function, + binder5* this_handler) +{ + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); +} + +template +inline binder5 bind_handler( + const Handler& handler, const Arg1& arg1, const Arg2& arg2, + const Arg3& arg3, const Arg4& arg4, const Arg5& arg5) +{ + return binder5(handler, arg1, arg2, + arg3, arg4, arg5); +} + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_BIND_HANDLER_HPP diff --git a/libtorrent/include/asio/detail/buffer_resize_guard.hpp b/libtorrent/include/asio/detail/buffer_resize_guard.hpp new file mode 100644 index 000000000..0dcbe6956 --- /dev/null +++ b/libtorrent/include/asio/detail/buffer_resize_guard.hpp @@ -0,0 +1,70 @@ +// +// buffer_resize_guard.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_BUFFER_RESIZE_GUARD_HPP +#define ASIO_DETAIL_BUFFER_RESIZE_GUARD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +// Helper class to manage buffer resizing in an exception safe way. +template +class buffer_resize_guard +{ +public: + // Constructor. + buffer_resize_guard(Buffer& buffer) + : buffer_(buffer), + old_size_(buffer.size()) + { + } + + // Destructor rolls back the buffer resize unless commit was called. + ~buffer_resize_guard() + { + if (old_size_ + != std::numeric_limits::max BOOST_PREVENT_MACRO_SUBSTITUTION()) + { + buffer_.resize(old_size_); + } + } + + // Commit the resize transaction. + void commit() + { + old_size_ + = std::numeric_limits::max BOOST_PREVENT_MACRO_SUBSTITUTION(); + } + +private: + // The buffer being managed. + Buffer& buffer_; + + // The size of the buffer at the time the guard was constructed. + size_t old_size_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_BUFFER_RESIZE_GUARD_HPP diff --git a/libtorrent/include/asio/detail/buffered_stream_storage.hpp b/libtorrent/include/asio/detail/buffered_stream_storage.hpp new file mode 100644 index 000000000..2a84d876d --- /dev/null +++ b/libtorrent/include/asio/detail/buffered_stream_storage.hpp @@ -0,0 +1,127 @@ +// +// buffered_stream_storage.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_BUFFERED_STREAM_STORAGE_HPP +#define ASIO_DETAIL_BUFFERED_STREAM_STORAGE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +class buffered_stream_storage +{ +public: + // The type of the bytes stored in the buffer. + typedef unsigned char byte_type; + + // The type used for offsets into the buffer. + typedef std::size_t size_type; + + // Constructor. + explicit buffered_stream_storage(std::size_t capacity) + : begin_offset_(0), + end_offset_(0), + buffer_(capacity) + { + } + + /// Clear the buffer. + void clear() + { + begin_offset_ = 0; + end_offset_ = 0; + } + + // Return a pointer to the beginning of the unread data. + byte_type* data() + { + return &buffer_[0] + begin_offset_; + } + + // Return a pointer to the beginning of the unread data. + const byte_type* data() const + { + return &buffer_[0] + begin_offset_; + } + + // Is there no unread data in the buffer. + bool empty() const + { + return begin_offset_ == end_offset_; + } + + // Return the amount of unread data the is in the buffer. + size_type size() const + { + return end_offset_ - begin_offset_; + } + + // Resize the buffer to the specified length. + void resize(size_type length) + { + assert(length <= capacity()); + if (begin_offset_ + length <= capacity()) + { + end_offset_ = begin_offset_ + length; + } + else + { + using namespace std; // For memmove. + memmove(&buffer_[0], &buffer_[0] + begin_offset_, size()); + end_offset_ = length; + begin_offset_ = 0; + } + } + + // Return the maximum size for data in the buffer. + size_type capacity() const + { + return buffer_.size(); + } + + // Consume multiple bytes from the beginning of the buffer. + void consume(size_type count) + { + assert(begin_offset_ + count <= end_offset_); + begin_offset_ += count; + if (empty()) + clear(); + } + +private: + // The offset to the beginning of the unread data. + size_type begin_offset_; + + // The offset to the end of the unread data. + size_type end_offset_; + + // The data in the buffer. + std::vector buffer_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_BUFFERED_STREAM_STORAGE_HPP diff --git a/libtorrent/include/asio/detail/call_stack.hpp b/libtorrent/include/asio/detail/call_stack.hpp new file mode 100644 index 000000000..1373f46c7 --- /dev/null +++ b/libtorrent/include/asio/detail/call_stack.hpp @@ -0,0 +1,90 @@ +// +// call_stack.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_CALL_STACK_HPP +#define ASIO_DETAIL_CALL_STACK_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/tss_ptr.hpp" + +namespace asio { +namespace detail { + +// Helper class to determine whether or not the current thread is inside an +// invocation of io_service::run() for a specified io_service object. +template +class call_stack +{ +public: + // Context class automatically pushes an owner on to the stack. + class context + : private noncopyable + { + public: + // Push the owner on to the stack. + explicit context(Owner* d) + : owner_(d), + next_(call_stack::top_) + { + call_stack::top_ = this; + } + + // Pop the owner from the stack. + ~context() + { + call_stack::top_ = next_; + } + + private: + friend class call_stack; + + // The owner associated with the context. + Owner* owner_; + + // The next element in the stack. + context* next_; + }; + + friend class context; + + // Determine whether the specified owner is on the stack. + static bool contains(Owner* d) + { + context* elem = top_; + while (elem) + { + if (elem->owner_ == d) + return true; + elem = elem->next_; + } + return false; + } + +private: + // The top of the stack of calls for the current thread. + static tss_ptr top_; +}; + +template +tss_ptr::context> +call_stack::top_; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_CALL_STACK_HPP diff --git a/libtorrent/include/asio/detail/const_buffers_iterator.hpp b/libtorrent/include/asio/detail/const_buffers_iterator.hpp new file mode 100644 index 000000000..964294865 --- /dev/null +++ b/libtorrent/include/asio/detail/const_buffers_iterator.hpp @@ -0,0 +1,151 @@ +// +// const_buffers_iterator.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_CONST_BUFFERS_ITERATOR_HPP +#define ASIO_DETAIL_CONST_BUFFERS_ITERATOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" + +namespace asio { +namespace detail { + +// A proxy iterator for a sub-range in a list of buffers. +template +class const_buffers_iterator + : public boost::iterator_facade, + const char, boost::bidirectional_traversal_tag> +{ +public: + // Default constructor creates an iterator in an undefined state. + const_buffers_iterator() + { + } + + // Create an iterator for the specified position. + const_buffers_iterator(const ConstBufferSequence& buffers, + std::size_t position) + : begin_(buffers.begin()), + current_(buffers.begin()), + end_(buffers.end()), + position_(0) + { + while (current_ != end_) + { + current_buffer_ = *current_; + std::size_t buffer_size = asio::buffer_size(current_buffer_); + if (position - position_ < buffer_size) + { + current_buffer_position_ = position - position_; + position_ = position; + return; + } + position_ += buffer_size; + ++current_; + } + current_buffer_ = asio::const_buffer(); + current_buffer_position_ = 0; + } + + std::size_t position() const + { + return position_; + } + +private: + friend class boost::iterator_core_access; + + void increment() + { + if (current_ == end_) + return; + + ++position_; + + ++current_buffer_position_; + if (current_buffer_position_ != asio::buffer_size(current_buffer_)) + return; + + ++current_; + current_buffer_position_ = 0; + while (current_ != end_) + { + current_buffer_ = *current_; + if (asio::buffer_size(current_buffer_) > 0) + return; + ++current_; + } + } + + void decrement() + { + if (position_ == 0) + return; + + --position_; + + if (current_buffer_position_ != 0) + { + --current_buffer_position_; + return; + } + + typename ConstBufferSequence::const_iterator iter = current_; + while (iter != begin_) + { + --iter; + asio::const_buffer buffer = *iter; + std::size_t buffer_size = asio::buffer_size(buffer); + if (buffer_size > 0) + { + current_ = iter; + current_buffer_ = buffer; + current_buffer_position_ = buffer_size - 1; + return; + } + } + } + + bool equal(const const_buffers_iterator& other) const + { + return position_ == other.position_; + } + + const char& dereference() const + { + return asio::buffer_cast( + current_buffer_)[current_buffer_position_]; + } + + asio::const_buffer current_buffer_; + std::size_t current_buffer_position_; + typename ConstBufferSequence::const_iterator begin_; + typename ConstBufferSequence::const_iterator current_; + typename ConstBufferSequence::const_iterator end_; + std::size_t position_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_CONST_BUFFERS_ITERATOR_HPP diff --git a/libtorrent/include/asio/detail/consuming_buffers.hpp b/libtorrent/include/asio/detail/consuming_buffers.hpp new file mode 100644 index 000000000..cbe38a926 --- /dev/null +++ b/libtorrent/include/asio/detail/consuming_buffers.hpp @@ -0,0 +1,205 @@ +// +// consuming_buffers.hpp +// ~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_CONSUMING_BUFFERS_HPP +#define ASIO_DETAIL_CONSUMING_BUFFERS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +// A proxy iterator for a sub-range in a list of buffers. +template +class consuming_buffers_iterator + : public boost::iterator_facade< + consuming_buffers_iterator, + const Buffer, boost::forward_traversal_tag> +{ +public: + // Default constructor creates an end iterator. + consuming_buffers_iterator() + : at_end_(true) + { + } + + // Construct with a buffer for the first entry and an iterator + // range for the remaining entries. + consuming_buffers_iterator(bool at_end, const Buffer& first, + Buffer_Iterator begin_remainder, Buffer_Iterator end_remainder) + : at_end_(at_end), + first_(buffer(first, max_size)), + begin_remainder_(begin_remainder), + end_remainder_(end_remainder), + offset_(0) + { + } + +private: + friend class boost::iterator_core_access; + + enum { max_size = 65536 }; + + void increment() + { + if (!at_end_) + { + if (begin_remainder_ == end_remainder_ + || offset_ + buffer_size(first_) >= max_size) + { + at_end_ = true; + } + else + { + offset_ += buffer_size(first_); + first_ = buffer(*begin_remainder_++, max_size - offset_); + } + } + } + + bool equal(const consuming_buffers_iterator& other) const + { + if (at_end_ && other.at_end_) + return true; + return !at_end_ && !other.at_end_ + && buffer_cast(first_) + == buffer_cast(other.first_) + && buffer_size(first_) == buffer_size(other.first_) + && begin_remainder_ == other.begin_remainder_ + && end_remainder_ == other.end_remainder_; + } + + const Buffer& dereference() const + { + return first_; + } + + bool at_end_; + Buffer first_; + Buffer_Iterator begin_remainder_; + Buffer_Iterator end_remainder_; + std::size_t offset_; +}; + +// A proxy for a sub-range in a list of buffers. +template +class consuming_buffers +{ +public: + // The type for each element in the list of buffers. + typedef Buffer value_type; + + // A forward-only iterator type that may be used to read elements. + typedef consuming_buffers_iterator + const_iterator; + + // Construct to represent the entire list of buffers. + consuming_buffers(const Buffers& buffers) + : buffers_(buffers), + at_end_(buffers_.begin() == buffers_.end()), + first_(*buffers_.begin()), + begin_remainder_(buffers_.begin()) + { + if (!at_end_) + ++begin_remainder_; + } + + // Copy constructor. + consuming_buffers(const consuming_buffers& other) + : buffers_(other.buffers_), + at_end_(other.at_end_), + first_(other.first_), + begin_remainder_(buffers_.begin()) + { + typename Buffers::const_iterator first = other.buffers_.begin(); + typename Buffers::const_iterator second = other.begin_remainder_; + std::advance(begin_remainder_, std::distance(first, second)); + } + + // Assignment operator. + consuming_buffers& operator=(const consuming_buffers& other) + { + buffers_ = other.buffers_; + at_end_ = other.at_end_; + first_ = other.first_; + begin_remainder_ = buffers_.begin(); + typename Buffers::const_iterator first = other.buffers_.begin(); + typename Buffers::const_iterator second = other.begin_remainder_; + std::advance(begin_remainder_, std::distance(first, second)); + return *this; + } + + // Get a forward-only iterator to the first element. + const_iterator begin() const + { + return const_iterator(at_end_, first_, begin_remainder_, buffers_.end()); + } + + // Get a forward-only iterator for one past the last element. + const_iterator end() const + { + return const_iterator(); + } + + // Consume the specified number of bytes from the buffers. + void consume(std::size_t size) + { + // Remove buffers from the start until the specified size is reached. + while (size > 0 && !at_end_) + { + if (buffer_size(first_) <= size) + { + size -= buffer_size(first_); + if (begin_remainder_ == buffers_.end()) + at_end_ = true; + else + first_ = *begin_remainder_++; + } + else + { + first_ = first_ + size; + size = 0; + } + } + + // Remove any more empty buffers at the start. + while (!at_end_ && buffer_size(first_) == 0) + { + if (begin_remainder_ == buffers_.end()) + at_end_ = true; + else + first_ = *begin_remainder_++; + } + } + +private: + Buffers buffers_; + bool at_end_; + Buffer first_; + typename Buffers::const_iterator begin_remainder_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_CONSUMING_BUFFERS_HPP diff --git a/libtorrent/include/asio/detail/deadline_timer_service.hpp b/libtorrent/include/asio/detail/deadline_timer_service.hpp new file mode 100644 index 000000000..c22c5a7b7 --- /dev/null +++ b/libtorrent/include/asio/detail/deadline_timer_service.hpp @@ -0,0 +1,199 @@ +// +// deadline_timer_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_DEADLINE_TIMER_SERVICE_HPP +#define ASIO_DETAIL_DEADLINE_TIMER_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/timer_queue.hpp" + +namespace asio { +namespace detail { + +template +class deadline_timer_service + : public asio::detail::service_base< + deadline_timer_service > +{ +public: + // The time type. + typedef typename Time_Traits::time_type time_type; + + // The duration type. + typedef typename Time_Traits::duration_type duration_type; + + // The implementation type of the timer. This type is dependent on the + // underlying implementation of the timer service. + struct implementation_type + : private asio::detail::noncopyable + { + time_type expiry; + bool might_have_pending_waits; + }; + + // Constructor. + deadline_timer_service(asio::io_service& io_service) + : asio::detail::service_base< + deadline_timer_service >(io_service), + scheduler_(asio::use_service(io_service)) + { + scheduler_.add_timer_queue(timer_queue_); + } + + // Destructor. + ~deadline_timer_service() + { + scheduler_.remove_timer_queue(timer_queue_); + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + // Construct a new timer implementation. + void construct(implementation_type& impl) + { + impl.expiry = time_type(); + impl.might_have_pending_waits = false; + } + + // Destroy a timer implementation. + void destroy(implementation_type& impl) + { + asio::error_code ec; + cancel(impl, ec); + } + + // Cancel any asynchronous wait operations associated with the timer. + std::size_t cancel(implementation_type& impl, asio::error_code& ec) + { + if (!impl.might_have_pending_waits) + { + ec = asio::error_code(); + return 0; + } + std::size_t count = scheduler_.cancel_timer(timer_queue_, &impl); + impl.might_have_pending_waits = false; + ec = asio::error_code(); + return count; + } + + // Get the expiry time for the timer as an absolute time. + time_type expires_at(const implementation_type& impl) const + { + return impl.expiry; + } + + // Set the expiry time for the timer as an absolute time. + std::size_t expires_at(implementation_type& impl, + const time_type& expiry_time, asio::error_code& ec) + { + std::size_t count = cancel(impl, ec); + impl.expiry = expiry_time; + ec = asio::error_code(); + return count; + } + + // Get the expiry time for the timer relative to now. + duration_type expires_from_now(const implementation_type& impl) const + { + return Time_Traits::subtract(expires_at(impl), Time_Traits::now()); + } + + // Set the expiry time for the timer relative to now. + std::size_t expires_from_now(implementation_type& impl, + const duration_type& expiry_time, asio::error_code& ec) + { + return expires_at(impl, + Time_Traits::add(Time_Traits::now(), expiry_time), ec); + } + + // Perform a blocking wait on the timer. + void wait(implementation_type& impl, asio::error_code& ec) + { + time_type now = Time_Traits::now(); + while (Time_Traits::less_than(now, impl.expiry)) + { + boost::posix_time::time_duration timeout = + Time_Traits::to_posix_duration(Time_Traits::subtract(impl.expiry, now)); + ::timeval tv; + tv.tv_sec = timeout.total_seconds(); + tv.tv_usec = timeout.total_microseconds() % 1000000; + asio::error_code ec; + socket_ops::select(0, 0, 0, 0, &tv, ec); + now = Time_Traits::now(); + } + ec = asio::error_code(); + } + + template + class wait_handler + { + public: + wait_handler(asio::io_service& io_service, Handler handler) + : io_service_(io_service), + work_(io_service), + handler_(handler) + { + } + + void operator()(const asio::error_code& result) + { + io_service_.post(detail::bind_handler(handler_, result)); + } + + private: + asio::io_service& io_service_; + asio::io_service::work work_; + Handler handler_; + }; + + // Start an asynchronous wait on the timer. + template + void async_wait(implementation_type& impl, Handler handler) + { + impl.might_have_pending_waits = true; + scheduler_.schedule_timer(timer_queue_, impl.expiry, + wait_handler(this->io_service(), handler), &impl); + } + +private: + // The queue of timers. + timer_queue timer_queue_; + + // The object that schedules and executes timers. Usually a reactor. + Timer_Scheduler& scheduler_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_DEADLINE_TIMER_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/epoll_reactor.hpp b/libtorrent/include/asio/detail/epoll_reactor.hpp new file mode 100644 index 000000000..d55e86454 --- /dev/null +++ b/libtorrent/include/asio/detail/epoll_reactor.hpp @@ -0,0 +1,613 @@ +// +// epoll_reactor.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_EPOLL_REACTOR_HPP +#define ASIO_DETAIL_EPOLL_REACTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/epoll_reactor_fwd.hpp" + +#if defined(ASIO_HAS_EPOLL) + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/hash_map.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/task_io_service.hpp" +#include "asio/detail/thread.hpp" +#include "asio/detail/reactor_op_queue.hpp" +#include "asio/detail/select_interrupter.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/signal_blocker.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/timer_queue.hpp" + +namespace asio { +namespace detail { + +template +class epoll_reactor + : public asio::detail::service_base > +{ +public: + // Constructor. + epoll_reactor(asio::io_service& io_service) + : asio::detail::service_base >(io_service), + mutex_(), + epoll_fd_(do_epoll_create()), + wait_in_progress_(false), + interrupter_(), + read_op_queue_(), + write_op_queue_(), + except_op_queue_(), + pending_cancellations_(), + stop_thread_(false), + thread_(0), + shutdown_(false) + { + // Start the reactor's internal thread only if needed. + if (Own_Thread) + { + asio::detail::signal_blocker sb; + thread_ = new asio::detail::thread( + bind_handler(&epoll_reactor::call_run_thread, this)); + } + + // Add the interrupter's descriptor to epoll. + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLIN | EPOLLERR; + ev.data.fd = interrupter_.read_descriptor(); + epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, interrupter_.read_descriptor(), &ev); + } + + // Destructor. + ~epoll_reactor() + { + shutdown_service(); + close(epoll_fd_); + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + asio::detail::mutex::scoped_lock lock(mutex_); + shutdown_ = true; + stop_thread_ = true; + lock.unlock(); + + if (thread_) + { + interrupter_.interrupt(); + thread_->join(); + delete thread_; + thread_ = 0; + } + + read_op_queue_.destroy_operations(); + write_op_queue_.destroy_operations(); + except_op_queue_.destroy_operations(); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->destroy_timers(); + timer_queues_.clear(); + } + + // Register a socket with the reactor. Returns 0 on success, system error + // code on failure. + int register_descriptor(socket_type descriptor) + { + // No need to lock according to epoll documentation. + + epoll_event ev = { 0, { 0 } }; + ev.events = 0; + ev.data.fd = descriptor; + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev); + if (result != 0) + return errno; + return 0; + } + + // Start a new read operation. The handler object will be invoked when the + // given descriptor is ready to be read, or an error has occurred. + template + void start_read_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (!read_op_queue_.has_operation(descriptor)) + if (handler(asio::error_code())) + return; + + if (read_op_queue_.enqueue_operation(descriptor, handler)) + { + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLIN | EPOLLERR | EPOLLHUP; + if (write_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLOUT; + if (except_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) + { + asio::error_code ec(errno, asio::native_ecat); + read_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Start a new write operation. The handler object will be invoked when the + // given descriptor is ready to be written, or an error has occurred. + template + void start_write_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (!write_op_queue_.has_operation(descriptor)) + if (handler(asio::error_code())) + return; + + if (write_op_queue_.enqueue_operation(descriptor, handler)) + { + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLOUT | EPOLLERR | EPOLLHUP; + if (read_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLIN; + if (except_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) + { + asio::error_code ec(errno, asio::native_ecat); + write_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Start a new exception operation. The handler object will be invoked when + // the given descriptor has exception information, or an error has occurred. + template + void start_except_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (except_op_queue_.enqueue_operation(descriptor, handler)) + { + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLPRI | EPOLLERR | EPOLLHUP; + if (read_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLIN; + if (write_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLOUT; + ev.data.fd = descriptor; + + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) + { + asio::error_code ec(errno, asio::native_ecat); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Start new write and exception operations. The handler object will be + // invoked when the given descriptor is ready for writing or has exception + // information available, or an error has occurred. + template + void start_write_and_except_ops(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + bool need_mod = write_op_queue_.enqueue_operation(descriptor, handler); + need_mod = except_op_queue_.enqueue_operation(descriptor, handler) + && need_mod; + if (need_mod) + { + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLOUT | EPOLLPRI | EPOLLERR | EPOLLHUP; + if (read_op_queue_.has_operation(descriptor)) + ev.events |= EPOLLIN; + ev.data.fd = descriptor; + + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) + { + asio::error_code ec(errno, asio::native_ecat); + write_op_queue_.dispatch_all_operations(descriptor, ec); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Cancel all operations associated with the given descriptor. The + // handlers associated with the descriptor will be invoked with the + // operation_aborted error. + void cancel_ops(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + cancel_ops_unlocked(descriptor); + } + + // Enqueue cancellation of all operations associated with the given + // descriptor. The handlers associated with the descriptor will be invoked + // with the operation_aborted error. This function does not acquire the + // epoll_reactor's mutex, and so should only be used from within a reactor + // handler. + void enqueue_cancel_ops_unlocked(socket_type descriptor) + { + pending_cancellations_.push_back(descriptor); + } + + // Cancel any operations that are running against the descriptor and remove + // its registration from the reactor. + void close_descriptor(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Remove the descriptor from epoll. + epoll_event ev = { 0, { 0 } }; + epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, descriptor, &ev); + + // Cancel any outstanding operations associated with the descriptor. + cancel_ops_unlocked(descriptor); + } + + // Add a new timer queue to the reactor. + template + void add_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + timer_queues_.push_back(&timer_queue); + } + + // Remove a timer queue from the reactor. + template + void remove_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + if (timer_queues_[i] == &timer_queue) + { + timer_queues_.erase(timer_queues_.begin() + i); + return; + } + } + } + + // Schedule a timer in the given timer queue to expire at the specified + // absolute time. The handler object will be invoked when the timer expires. + template + void schedule_timer(timer_queue& timer_queue, + const typename Time_Traits::time_type& time, Handler handler, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (timer_queue.enqueue_timer(time, handler, token)) + interrupter_.interrupt(); + } + + // Cancel the timer associated with the given token. Returns the number of + // handlers that have been posted or dispatched. + template + std::size_t cancel_timer(timer_queue& timer_queue, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + return timer_queue.cancel_timer(token); + } + +private: + friend class task_io_service >; + + // Run epoll once until interrupted or events are ready to be dispatched. + void run(bool block) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Dispatch any operation cancellations that were made while the select + // loop was not running. + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + + // Check if the thread is supposed to stop. + if (stop_thread_) + { + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + return; + } + + // We can return immediately if there's no work to do and the reactor is + // not supposed to block. + if (!block && read_op_queue_.empty() && write_op_queue_.empty() + && except_op_queue_.empty() && all_timer_queues_are_empty()) + { + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + return; + } + + int timeout = block ? get_timeout() : 0; + wait_in_progress_ = true; + lock.unlock(); + + // Block on the epoll descriptor. + epoll_event events[128]; + int num_events = epoll_wait(epoll_fd_, events, 128, timeout); + + lock.lock(); + wait_in_progress_ = false; + + // Block signals while dispatching operations. + asio::detail::signal_blocker sb; + + // Dispatch the waiting events. + for (int i = 0; i < num_events; ++i) + { + int descriptor = events[i].data.fd; + if (descriptor == interrupter_.read_descriptor()) + { + interrupter_.reset(); + } + else + { + if (events[i].events & (EPOLLERR | EPOLLHUP)) + { + asio::error_code ec; + except_op_queue_.dispatch_all_operations(descriptor, ec); + read_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + + epoll_event ev = { 0, { 0 } }; + ev.events = 0; + ev.data.fd = descriptor; + epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + } + else + { + bool more_reads = false; + bool more_writes = false; + bool more_except = false; + asio::error_code ec; + + // Exception operations must be processed first to ensure that any + // out-of-band data is read before normal data. + if (events[i].events & EPOLLPRI) + more_except = except_op_queue_.dispatch_operation(descriptor, ec); + else + more_except = except_op_queue_.has_operation(descriptor); + + if (events[i].events & EPOLLIN) + more_reads = read_op_queue_.dispatch_operation(descriptor, ec); + else + more_reads = read_op_queue_.has_operation(descriptor); + + if (events[i].events & EPOLLOUT) + more_writes = write_op_queue_.dispatch_operation(descriptor, ec); + else + more_writes = write_op_queue_.has_operation(descriptor); + + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLERR | EPOLLHUP; + if (more_reads) + ev.events |= EPOLLIN; + if (more_writes) + ev.events |= EPOLLOUT; + if (more_except) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) + { + ec = asio::error_code(errno, asio::native_ecat); + read_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + } + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_timers(); + + // Issue any pending cancellations. + for (size_t i = 0; i < pending_cancellations_.size(); ++i) + cancel_ops_unlocked(pending_cancellations_[i]); + pending_cancellations_.clear(); + + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + } + + // Run the select loop in the thread. + void run_thread() + { + asio::detail::mutex::scoped_lock lock(mutex_); + while (!stop_thread_) + { + lock.unlock(); + run(true); + lock.lock(); + } + } + + // Entry point for the select loop thread. + static void call_run_thread(epoll_reactor* reactor) + { + reactor->run_thread(); + } + + // Interrupt the select loop. + void interrupt() + { + interrupter_.interrupt(); + } + + // The hint to pass to epoll_create to size its data structures. + enum { epoll_size = 20000 }; + + // Create the epoll file descriptor. Throws an exception if the descriptor + // cannot be created. + static int do_epoll_create() + { + int fd = epoll_create(epoll_size); + if (fd == -1) + { + boost::throw_exception(asio::system_error( + asio::error_code(errno, asio::native_ecat), + "epoll")); + } + return fd; + } + + // Check if all timer queues are empty. + bool all_timer_queues_are_empty() const + { + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + if (!timer_queues_[i]->empty()) + return false; + return true; + } + + // Get the timeout value for the epoll_wait call. The timeout value is + // returned as a number of milliseconds. A return value of -1 indicates + // that epoll_wait should block indefinitely. + int get_timeout() + { + if (all_timer_queues_are_empty()) + return -1; + + // By default we will wait no longer than 5 minutes. This will ensure that + // any changes to the system clock are detected after no longer than this. + boost::posix_time::time_duration minimum_wait_duration + = boost::posix_time::minutes(5); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + boost::posix_time::time_duration wait_duration + = timer_queues_[i]->wait_duration(); + if (wait_duration < minimum_wait_duration) + minimum_wait_duration = wait_duration; + } + + if (minimum_wait_duration > boost::posix_time::time_duration()) + { + return minimum_wait_duration.total_milliseconds(); + } + else + { + return 0; + } + } + + // Cancel all operations associated with the given descriptor. The do_cancel + // function of the handler objects will be invoked. This function does not + // acquire the epoll_reactor's mutex. + void cancel_ops_unlocked(socket_type descriptor) + { + bool interrupt = read_op_queue_.cancel_operations(descriptor); + interrupt = write_op_queue_.cancel_operations(descriptor) || interrupt; + interrupt = except_op_queue_.cancel_operations(descriptor) || interrupt; + if (interrupt) + interrupter_.interrupt(); + } + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // The epoll file descriptor. + int epoll_fd_; + + // Whether the epoll_wait call is currently in progress + bool wait_in_progress_; + + // The interrupter is used to break a blocking epoll_wait call. + select_interrupter interrupter_; + + // The queue of read operations. + reactor_op_queue read_op_queue_; + + // The queue of write operations. + reactor_op_queue write_op_queue_; + + // The queue of except operations. + reactor_op_queue except_op_queue_; + + // The timer queues. + std::vector timer_queues_; + + // The descriptors that are pending cancellation. + std::vector pending_cancellations_; + + // Does the reactor loop thread need to stop. + bool stop_thread_; + + // The thread that is running the reactor loop. + asio::detail::thread* thread_; + + // Whether the service has been shut down. + bool shutdown_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_EPOLL) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_EPOLL_REACTOR_HPP diff --git a/libtorrent/include/asio/detail/epoll_reactor_fwd.hpp b/libtorrent/include/asio/detail/epoll_reactor_fwd.hpp new file mode 100644 index 000000000..87fad6325 --- /dev/null +++ b/libtorrent/include/asio/detail/epoll_reactor_fwd.hpp @@ -0,0 +1,47 @@ +// +// epoll_reactor_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_EPOLL_REACTOR_FWD_HPP +#define ASIO_DETAIL_EPOLL_REACTOR_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#if !defined(ASIO_DISABLE_EPOLL) +#if defined(__linux__) // This service is only supported on Linux. + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION (2,5,45) // Only kernels >= 2.5.45. + +// Define this to indicate that epoll is supported on the target platform. +#define ASIO_HAS_EPOLL 1 + +namespace asio { +namespace detail { + +template +class epoll_reactor; + +} // namespace detail +} // namespace asio + +#endif // LINUX_VERSION_CODE >= KERNEL_VERSION (2,5,45) +#endif // defined(__linux__) +#endif // !defined(ASIO_DISABLE_EPOLL) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_EPOLL_REACTOR_FWD_HPP diff --git a/libtorrent/include/asio/detail/event.hpp b/libtorrent/include/asio/detail/event.hpp new file mode 100644 index 000000000..766f51716 --- /dev/null +++ b/libtorrent/include/asio/detail/event.hpp @@ -0,0 +1,50 @@ +// +// event.hpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_EVENT_HPP +#define ASIO_DETAIL_EVENT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) +# include "asio/detail/null_event.hpp" +#elif defined(BOOST_WINDOWS) +# include "asio/detail/win_event.hpp" +#elif defined(BOOST_HAS_PTHREADS) +# include "asio/detail/posix_event.hpp" +#else +# error Only Windows and POSIX are supported! +#endif + +namespace asio { +namespace detail { + +#if !defined(BOOST_HAS_THREADS) +typedef null_event event; +#elif defined(BOOST_WINDOWS) +typedef win_event event; +#elif defined(BOOST_HAS_PTHREADS) +typedef posix_event event; +#endif + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_EVENT_HPP diff --git a/libtorrent/include/asio/detail/fd_set_adapter.hpp b/libtorrent/include/asio/detail/fd_set_adapter.hpp new file mode 100644 index 000000000..1d01dc5fb --- /dev/null +++ b/libtorrent/include/asio/detail/fd_set_adapter.hpp @@ -0,0 +1,41 @@ +// +// fd_set_adapter.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_FD_SET_ADAPTER_HPP +#define ASIO_DETAIL_FD_SET_ADAPTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/posix_fd_set_adapter.hpp" +#include "asio/detail/win_fd_set_adapter.hpp" + +namespace asio { +namespace detail { + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +typedef win_fd_set_adapter fd_set_adapter; +#else +typedef posix_fd_set_adapter fd_set_adapter; +#endif + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_FD_SET_ADAPTER_HPP diff --git a/libtorrent/include/asio/detail/handler_alloc_helpers.hpp b/libtorrent/include/asio/detail/handler_alloc_helpers.hpp new file mode 100644 index 000000000..68e7cb15d --- /dev/null +++ b/libtorrent/include/asio/detail/handler_alloc_helpers.hpp @@ -0,0 +1,256 @@ +// +// handler_alloc_helpers.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_HANDLER_ALLOC_HELPERS_HPP +#define ASIO_DETAIL_HANDLER_ALLOC_HELPERS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/handler_alloc_hook.hpp" +#include "asio/detail/noncopyable.hpp" + +// Calls to asio_handler_allocate and asio_handler_deallocate must be made from +// a namespace that does not contain any overloads of these functions. The +// asio_handler_alloc_helpers namespace is defined here for that purpose. +namespace asio_handler_alloc_helpers { + +template +inline void* allocate(std::size_t s, Handler* h) +{ +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) + return ::operator new(s); +#else + using namespace asio; + return asio_handler_allocate(s, h); +#endif +} + +template +inline void deallocate(void* p, std::size_t s, Handler* h) +{ +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) + ::operator delete(p); +#else + using namespace asio; + asio_handler_deallocate(p, s, h); +#endif +} + +} // namespace asio_handler_alloc_helpers + +namespace asio { +namespace detail { + +// Traits for handler allocation. +template +struct handler_alloc_traits +{ + typedef Handler handler_type; + typedef Object value_type; + typedef Object* pointer_type; + BOOST_STATIC_CONSTANT(std::size_t, value_size = sizeof(Object)); +}; + +template +class handler_ptr; + +// Helper class to provide RAII on uninitialised handler memory. +template +class raw_handler_ptr + : private noncopyable +{ +public: + typedef typename Alloc_Traits::handler_type handler_type; + typedef typename Alloc_Traits::value_type value_type; + typedef typename Alloc_Traits::pointer_type pointer_type; + BOOST_STATIC_CONSTANT(std::size_t, value_size = Alloc_Traits::value_size); + + // Constructor allocates the memory. + raw_handler_ptr(handler_type& handler) + : handler_(handler), + pointer_(static_cast( + asio_handler_alloc_helpers::allocate(value_size, &handler_))) + { + } + + // Destructor automatically deallocates memory, unless it has been stolen by + // a handler_ptr object. + ~raw_handler_ptr() + { + if (pointer_) + asio_handler_alloc_helpers::deallocate( + pointer_, value_size, &handler_); + } + +private: + friend class handler_ptr; + handler_type& handler_; + pointer_type pointer_; +}; + +// Helper class to provide RAII on uninitialised handler memory. +template +class handler_ptr + : private noncopyable +{ +public: + typedef typename Alloc_Traits::handler_type handler_type; + typedef typename Alloc_Traits::value_type value_type; + typedef typename Alloc_Traits::pointer_type pointer_type; + BOOST_STATIC_CONSTANT(std::size_t, value_size = Alloc_Traits::value_size); + typedef raw_handler_ptr raw_ptr_type; + + // Take ownership of existing memory. + handler_ptr(handler_type& handler, pointer_type pointer) + : handler_(handler), + pointer_(pointer) + { + } + + // Construct object in raw memory and take ownership if construction succeeds. + handler_ptr(raw_ptr_type& raw_ptr) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1, a2)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2, Arg3& a3) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1, a2, a3)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2, Arg3& a3, Arg4& a4) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1, a2, a3, a4)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2, Arg3& a3, Arg4& a4, + Arg5& a5) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1, a2, a3, a4, a5)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2, Arg3& a3, Arg4& a4, + Arg5& a5, Arg6& a6) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1, a2, a3, a4, a5, a6)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2, Arg3& a3, Arg4& a4, + Arg5& a5, Arg6& a6, Arg7& a7) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type(a1, a2, a3, a4, a5, a6, a7)) + { + raw_ptr.pointer_ = 0; + } + + // Construct object in raw memory and take ownership if construction succeeds. + template + handler_ptr(raw_ptr_type& raw_ptr, Arg1& a1, Arg2& a2, Arg3& a3, Arg4& a4, + Arg5& a5, Arg6& a6, Arg7& a7, Arg8& a8) + : handler_(raw_ptr.handler_), + pointer_(new (raw_ptr.pointer_) value_type( + a1, a2, a3, a4, a5, a6, a7, a8)) + { + raw_ptr.pointer_ = 0; + } + + // Destructor automatically deallocates memory, unless it has been released. + ~handler_ptr() + { + reset(); + } + + // Get the memory. + pointer_type get() const + { + return pointer_; + } + + // Release ownership of the memory. + pointer_type release() + { + pointer_type tmp = pointer_; + pointer_ = 0; + return tmp; + } + + // Explicitly destroy and deallocate the memory. + void reset() + { + if (pointer_) + { + pointer_->value_type::~value_type(); + asio_handler_alloc_helpers::deallocate( + pointer_, value_size, &handler_); + pointer_ = 0; + } + } + +private: + handler_type& handler_; + pointer_type pointer_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_HANDLER_ALLOC_HELPERS_HPP diff --git a/libtorrent/include/asio/detail/handler_invoke_helpers.hpp b/libtorrent/include/asio/detail/handler_invoke_helpers.hpp new file mode 100644 index 000000000..b260c426c --- /dev/null +++ b/libtorrent/include/asio/detail/handler_invoke_helpers.hpp @@ -0,0 +1,47 @@ +// +// handler_invoke_helpers.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_HANDLER_INVOKE_HELPERS_HPP +#define ASIO_DETAIL_HANDLER_INVOKE_HELPERS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/handler_invoke_hook.hpp" + +// Calls to asio_handler_invoke must be made from a namespace that does not +// contain overloads of this function. The asio_handler_invoke_helpers +// namespace is defined here for that purpose. +namespace asio_handler_invoke_helpers { + +template +inline void invoke(const Function& function, Context* context) +{ +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) + Function tmp(function); + tmp(); +#else + using namespace asio; + asio_handler_invoke(function, context); +#endif +} + +} // namespace asio_handler_invoke_helpers + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_HANDLER_INVOKE_HELPERS_HPP diff --git a/libtorrent/include/asio/detail/hash_map.hpp b/libtorrent/include/asio/detail/hash_map.hpp new file mode 100644 index 000000000..05cebdf58 --- /dev/null +++ b/libtorrent/include/asio/detail/hash_map.hpp @@ -0,0 +1,209 @@ +// +// hash_map.hpp +// ~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_HASH_MAP_HPP +#define ASIO_DETAIL_HASH_MAP_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +template +inline std::size_t calculate_hash_value(const T& t) +{ + return boost::hash_value(t); +} + +#if defined(_WIN64) +inline std::size_t calculate_hash_value(SOCKET s) +{ + return static_cast(s); +} +#endif // defined(_WIN64) + +template +class hash_map + : private noncopyable +{ +public: + // The type of a value in the map. + typedef std::pair value_type; + + // The type of a non-const iterator over the hash map. + typedef typename std::list::iterator iterator; + + // The type of a const iterator over the hash map. + typedef typename std::list::const_iterator const_iterator; + + // Constructor. + hash_map() + { + // Initialise all buckets to empty. + for (size_t i = 0; i < num_buckets; ++i) + buckets_[i].first = buckets_[i].last = values_.end(); + } + + // Get an iterator for the beginning of the map. + iterator begin() + { + return values_.begin(); + } + + // Get an iterator for the beginning of the map. + const_iterator begin() const + { + return values_.begin(); + } + + // Get an iterator for the end of the map. + iterator end() + { + return values_.end(); + } + + // Get an iterator for the end of the map. + const_iterator end() const + { + return values_.end(); + } + + // Check whether the map is empty. + bool empty() const + { + return values_.empty(); + } + + // Find an entry in the map. + iterator find(const K& k) + { + size_t bucket = calculate_hash_value(k) % num_buckets; + iterator it = buckets_[bucket].first; + if (it == values_.end()) + return values_.end(); + iterator end = buckets_[bucket].last; + ++end; + while (it != end) + { + if (it->first == k) + return it; + ++it; + } + return values_.end(); + } + + // Find an entry in the map. + const_iterator find(const K& k) const + { + size_t bucket = calculate_hash_value(k) % num_buckets; + const_iterator it = buckets_[bucket].first; + if (it == values_.end()) + return it; + const_iterator end = buckets_[bucket].last; + ++end; + while (it != end) + { + if (it->first == k) + return it; + ++it; + } + return values_.end(); + } + + // Insert a new entry into the map. + std::pair insert(const value_type& v) + { + size_t bucket = calculate_hash_value(v.first) % num_buckets; + iterator it = buckets_[bucket].first; + if (it == values_.end()) + { + buckets_[bucket].first = buckets_[bucket].last = + values_.insert(values_.end(), v); + return std::pair(buckets_[bucket].last, true); + } + iterator end = buckets_[bucket].last; + ++end; + while (it != end) + { + if (it->first == v.first) + return std::pair(it, false); + ++it; + } + buckets_[bucket].last = values_.insert(end, v); + return std::pair(buckets_[bucket].last, true); + } + + // Erase an entry from the map. + void erase(iterator it) + { + assert(it != values_.end()); + + size_t bucket = calculate_hash_value(it->first) % num_buckets; + bool is_first = (it == buckets_[bucket].first); + bool is_last = (it == buckets_[bucket].last); + if (is_first && is_last) + buckets_[bucket].first = buckets_[bucket].last = values_.end(); + else if (is_first) + ++buckets_[bucket].first; + else if (is_last) + --buckets_[bucket].last; + + values_.erase(it); + } + + // Remove all entries from the map. + void clear() + { + // Clear the values. + values_.clear(); + + // Initialise all buckets to empty. + for (size_t i = 0; i < num_buckets; ++i) + buckets_[i].first = buckets_[i].last = values_.end(); + } + +private: + // The list of all values in the hash map. + std::list values_; + + // The type for a bucket in the hash table. + struct bucket_type + { + iterator first; + iterator last; + }; + + // The number of buckets in the hash. + enum { num_buckets = 1021 }; + + // The buckets in the hash. + bucket_type buckets_[num_buckets]; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_HASH_MAP_HPP diff --git a/libtorrent/include/asio/detail/io_control.hpp b/libtorrent/include/asio/detail/io_control.hpp new file mode 100644 index 000000000..feb02d9d5 --- /dev/null +++ b/libtorrent/include/asio/detail/io_control.hpp @@ -0,0 +1,137 @@ +// +// io_control.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_IO_CONTROL_HPP +#define ASIO_DETAIL_IO_CONTROL_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { +namespace io_control { + +// IO control command for non-blocking I/O. +class non_blocking_io +{ +public: + // Default constructor. + non_blocking_io() + : value_(0) + { + } + + // Construct with a specific command value. + non_blocking_io(bool value) + : value_(value ? 1 : 0) + { + } + + // Get the name of the IO control command. + int name() const + { + return FIONBIO; + } + + // Set the value of the I/O control command. + void set(bool value) + { + value_ = value ? 1 : 0; + } + + // Get the current value of the I/O control command. + bool get() const + { + return value_ != 0; + } + + // Get the address of the command data. + detail::ioctl_arg_type* data() + { + return &value_; + } + + // Get the address of the command data. + const detail::ioctl_arg_type* data() const + { + return &value_; + } + +private: + detail::ioctl_arg_type value_; +}; + +// I/O control command for getting number of bytes available. +class bytes_readable +{ +public: + // Default constructor. + bytes_readable() + : value_(0) + { + } + + // Construct with a specific command value. + bytes_readable(std::size_t value) + : value_(static_cast(value)) + { + } + + // Get the name of the IO control command. + int name() const + { + return FIONREAD; + } + + // Set the value of the I/O control command. + void set(std::size_t value) + { + value_ = static_cast(value); + } + + // Get the current value of the I/O control command. + std::size_t get() const + { + return static_cast(value_); + } + + // Get the address of the command data. + detail::ioctl_arg_type* data() + { + return &value_; + } + + // Get the address of the command data. + const detail::ioctl_arg_type* data() const + { + return &value_; + } + +private: + detail::ioctl_arg_type value_; +}; + +} // namespace io_control +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_IO_CONTROL_HPP diff --git a/libtorrent/include/asio/detail/kqueue_reactor.hpp b/libtorrent/include/asio/detail/kqueue_reactor.hpp new file mode 100644 index 000000000..6628803af --- /dev/null +++ b/libtorrent/include/asio/detail/kqueue_reactor.hpp @@ -0,0 +1,620 @@ +// +// kqueue_reactor.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// Copyright (c) 2005 Stefan Arentz (stefan at soze dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_KQUEUE_REACTOR_HPP +#define ASIO_DETAIL_KQUEUE_REACTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/kqueue_reactor_fwd.hpp" + +#if defined(ASIO_HAS_KQUEUE) + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/task_io_service.hpp" +#include "asio/detail/thread.hpp" +#include "asio/detail/reactor_op_queue.hpp" +#include "asio/detail/select_interrupter.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/signal_blocker.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/timer_queue.hpp" + +// Older versions of Mac OS X may not define EV_OOBAND. +#if !defined(EV_OOBAND) +# define EV_OOBAND EV_FLAG1 +#endif // !defined(EV_OOBAND) + +namespace asio { +namespace detail { + +template +class kqueue_reactor + : public asio::detail::service_base > +{ +public: + // Constructor. + kqueue_reactor(asio::io_service& io_service) + : asio::detail::service_base< + kqueue_reactor >(io_service), + mutex_(), + kqueue_fd_(do_kqueue_create()), + wait_in_progress_(false), + interrupter_(), + read_op_queue_(), + write_op_queue_(), + except_op_queue_(), + pending_cancellations_(), + stop_thread_(false), + thread_(0), + shutdown_(false) + { + // Start the reactor's internal thread only if needed. + if (Own_Thread) + { + asio::detail::signal_blocker sb; + thread_ = new asio::detail::thread( + bind_handler(&kqueue_reactor::call_run_thread, this)); + } + + // Add the interrupter's descriptor to the kqueue. + struct kevent event; + EV_SET(&event, interrupter_.read_descriptor(), + EVFILT_READ, EV_ADD, 0, 0, 0); + ::kevent(kqueue_fd_, &event, 1, 0, 0, 0); + } + + // Destructor. + ~kqueue_reactor() + { + shutdown_service(); + close(kqueue_fd_); + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + asio::detail::mutex::scoped_lock lock(mutex_); + shutdown_ = true; + stop_thread_ = true; + lock.unlock(); + + if (thread_) + { + interrupter_.interrupt(); + thread_->join(); + delete thread_; + thread_ = 0; + } + + read_op_queue_.destroy_operations(); + write_op_queue_.destroy_operations(); + except_op_queue_.destroy_operations(); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->destroy_timers(); + timer_queues_.clear(); + } + + // Register a socket with the reactor. Returns 0 on success, system error + // code on failure. + int register_descriptor(socket_type) + { + return 0; + } + + // Start a new read operation. The handler object will be invoked when the + // given descriptor is ready to be read, or an error has occurred. + template + void start_read_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (!read_op_queue_.has_operation(descriptor)) + if (handler(asio::error_code())) + return; + + if (read_op_queue_.enqueue_operation(descriptor, handler)) + { + struct kevent event; + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code ec(errno, asio::native_ecat); + read_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Start a new write operation. The handler object will be invoked when the + // given descriptor is ready to be written, or an error has occurred. + template + void start_write_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (!write_op_queue_.has_operation(descriptor)) + if (handler(asio::error_code())) + return; + + if (write_op_queue_.enqueue_operation(descriptor, handler)) + { + struct kevent event; + EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code ec(errno, asio::native_ecat); + write_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Start a new exception operation. The handler object will be invoked when + // the given descriptor has exception information, or an error has occurred. + template + void start_except_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (except_op_queue_.enqueue_operation(descriptor, handler)) + { + struct kevent event; + if (read_op_queue_.has_operation(descriptor)) + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); + else + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code ec(errno, asio::native_ecat); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Start new write and exception operations. The handler object will be + // invoked when the given descriptor is ready for writing or has exception + // information available, or an error has occurred. + template + void start_write_and_except_ops(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (write_op_queue_.enqueue_operation(descriptor, handler)) + { + struct kevent event; + EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code ec(errno, asio::native_ecat); + write_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + + if (except_op_queue_.enqueue_operation(descriptor, handler)) + { + struct kevent event; + if (read_op_queue_.has_operation(descriptor)) + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); + else + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code ec(errno, asio::native_ecat); + except_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + + // Cancel all operations associated with the given descriptor. The + // handlers associated with the descriptor will be invoked with the + // operation_aborted error. + void cancel_ops(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + cancel_ops_unlocked(descriptor); + } + + // Enqueue cancellation of all operations associated with the given + // descriptor. The handlers associated with the descriptor will be invoked + // with the operation_aborted error. This function does not acquire the + // kqueue_reactor's mutex, and so should only be used from within a reactor + // handler. + void enqueue_cancel_ops_unlocked(socket_type descriptor) + { + pending_cancellations_.push_back(descriptor); + } + + // Cancel any operations that are running against the descriptor and remove + // its registration from the reactor. + void close_descriptor(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Remove the descriptor from kqueue. + struct kevent event[2]; + EV_SET(&event[0], descriptor, EVFILT_READ, EV_DELETE, 0, 0, 0); + EV_SET(&event[1], descriptor, EVFILT_WRITE, EV_DELETE, 0, 0, 0); + ::kevent(kqueue_fd_, event, 2, 0, 0, 0); + + // Cancel any outstanding operations associated with the descriptor. + cancel_ops_unlocked(descriptor); + } + + // Add a new timer queue to the reactor. + template + void add_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + timer_queues_.push_back(&timer_queue); + } + + // Remove a timer queue from the reactor. + template + void remove_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + if (timer_queues_[i] == &timer_queue) + { + timer_queues_.erase(timer_queues_.begin() + i); + return; + } + } + } + + // Schedule a timer in the given timer queue to expire at the specified + // absolute time. The handler object will be invoked when the timer expires. + template + void schedule_timer(timer_queue& timer_queue, + const typename Time_Traits::time_type& time, Handler handler, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (timer_queue.enqueue_timer(time, handler, token)) + interrupter_.interrupt(); + } + + // Cancel the timer associated with the given token. Returns the number of + // handlers that have been posted or dispatched. + template + std::size_t cancel_timer(timer_queue& timer_queue, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + return timer_queue.cancel_timer(token); + } + +private: + friend class task_io_service >; + + // Run the kqueue loop. + void run(bool block) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Dispatch any operation cancellations that were made while the select + // loop was not running. + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + + // Check if the thread is supposed to stop. + if (stop_thread_) + { + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + return; + } + + // We can return immediately if there's no work to do and the reactor is + // not supposed to block. + if (!block && read_op_queue_.empty() && write_op_queue_.empty() + && except_op_queue_.empty() && all_timer_queues_are_empty()) + { + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + return; + } + + // Determine how long to block while waiting for events. + timespec timeout_buf = { 0, 0 }; + timespec* timeout = block ? get_timeout(timeout_buf) : &timeout_buf; + + wait_in_progress_ = true; + lock.unlock(); + + // Block on the kqueue descriptor. + struct kevent events[128]; + int num_events = kevent(kqueue_fd_, 0, 0, events, 128, timeout); + + lock.lock(); + wait_in_progress_ = false; + + // Block signals while dispatching operations. + asio::detail::signal_blocker sb; + + // Dispatch the waiting events. + for (int i = 0; i < num_events; ++i) + { + int descriptor = events[i].ident; + if (descriptor == interrupter_.read_descriptor()) + { + interrupter_.reset(); + } + else if (events[i].filter == EVFILT_READ) + { + // Dispatch operations associated with the descriptor. + bool more_reads = false; + bool more_except = false; + if (events[i].flags & EV_ERROR) + { + asio::error_code error( + events[i].data, asio::native_ecat); + except_op_queue_.dispatch_all_operations(descriptor, error); + read_op_queue_.dispatch_all_operations(descriptor, error); + } + else if (events[i].flags & EV_OOBAND) + { + asio::error_code error; + more_except = except_op_queue_.dispatch_operation(descriptor, error); + if (events[i].data > 0) + more_reads = read_op_queue_.dispatch_operation(descriptor, error); + else + more_reads = read_op_queue_.has_operation(descriptor); + } + else + { + asio::error_code error; + more_reads = read_op_queue_.dispatch_operation(descriptor, error); + more_except = except_op_queue_.has_operation(descriptor); + } + + // Update the descriptor in the kqueue. + struct kevent event; + if (more_reads) + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); + else if (more_except) + EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); + else + EV_SET(&event, descriptor, EVFILT_READ, EV_DELETE, 0, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code error(errno, asio::native_ecat); + except_op_queue_.dispatch_all_operations(descriptor, error); + read_op_queue_.dispatch_all_operations(descriptor, error); + } + } + else if (events[i].filter == EVFILT_WRITE) + { + // Dispatch operations associated with the descriptor. + bool more_writes = false; + if (events[i].flags & EV_ERROR) + { + asio::error_code error( + events[i].data, asio::native_ecat); + write_op_queue_.dispatch_all_operations(descriptor, error); + } + else + { + asio::error_code error; + more_writes = write_op_queue_.dispatch_operation(descriptor, error); + } + + // Update the descriptor in the kqueue. + struct kevent event; + if (more_writes) + EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); + else + EV_SET(&event, descriptor, EVFILT_WRITE, EV_DELETE, 0, 0, 0); + if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) + { + asio::error_code error(errno, asio::native_ecat); + write_op_queue_.dispatch_all_operations(descriptor, error); + } + } + } + + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_timers(); + + // Issue any pending cancellations. + for (std::size_t i = 0; i < pending_cancellations_.size(); ++i) + cancel_ops_unlocked(pending_cancellations_[i]); + pending_cancellations_.clear(); + + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + } + + // Run the select loop in the thread. + void run_thread() + { + asio::detail::mutex::scoped_lock lock(mutex_); + while (!stop_thread_) + { + lock.unlock(); + run(true); + lock.lock(); + } + } + + // Entry point for the select loop thread. + static void call_run_thread(kqueue_reactor* reactor) + { + reactor->run_thread(); + } + + // Interrupt the select loop. + void interrupt() + { + interrupter_.interrupt(); + } + + // Create the kqueue file descriptor. Throws an exception if the descriptor + // cannot be created. + static int do_kqueue_create() + { + int fd = kqueue(); + if (fd == -1) + { + boost::throw_exception(asio::system_error( + asio::error_code(errno, asio::native_ecat), + "kqueue")); + } + return fd; + } + + // Check if all timer queues are empty. + bool all_timer_queues_are_empty() const + { + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + if (!timer_queues_[i]->empty()) + return false; + return true; + } + + // Get the timeout value for the kevent call. + timespec* get_timeout(timespec& ts) + { + if (all_timer_queues_are_empty()) + return 0; + + // By default we will wait no longer than 5 minutes. This will ensure that + // any changes to the system clock are detected after no longer than this. + boost::posix_time::time_duration minimum_wait_duration + = boost::posix_time::minutes(5); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + boost::posix_time::time_duration wait_duration + = timer_queues_[i]->wait_duration(); + if (wait_duration < minimum_wait_duration) + minimum_wait_duration = wait_duration; + } + + if (minimum_wait_duration > boost::posix_time::time_duration()) + { + ts.tv_sec = minimum_wait_duration.total_seconds(); + ts.tv_nsec = minimum_wait_duration.total_nanoseconds() % 1000000000; + } + else + { + ts.tv_sec = 0; + ts.tv_nsec = 0; + } + + return &ts; + } + + // Cancel all operations associated with the given descriptor. The do_cancel + // function of the handler objects will be invoked. This function does not + // acquire the kqueue_reactor's mutex. + void cancel_ops_unlocked(socket_type descriptor) + { + bool interrupt = read_op_queue_.cancel_operations(descriptor); + interrupt = write_op_queue_.cancel_operations(descriptor) || interrupt; + interrupt = except_op_queue_.cancel_operations(descriptor) || interrupt; + if (interrupt) + interrupter_.interrupt(); + } + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // The kqueue file descriptor. + int kqueue_fd_; + + // Whether the kqueue wait call is currently in progress + bool wait_in_progress_; + + // The interrupter is used to break a blocking kevent call. + select_interrupter interrupter_; + + // The queue of read operations. + reactor_op_queue read_op_queue_; + + // The queue of write operations. + reactor_op_queue write_op_queue_; + + // The queue of except operations. + reactor_op_queue except_op_queue_; + + // The timer queues. + std::vector timer_queues_; + + // The descriptors that are pending cancellation. + std::vector pending_cancellations_; + + // Does the reactor loop thread need to stop. + bool stop_thread_; + + // The thread that is running the reactor loop. + asio::detail::thread* thread_; + + // Whether the service has been shut down. + bool shutdown_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_KQUEUE) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_KQUEUE_REACTOR_HPP diff --git a/libtorrent/include/asio/detail/kqueue_reactor_fwd.hpp b/libtorrent/include/asio/detail/kqueue_reactor_fwd.hpp new file mode 100644 index 000000000..5171f4215 --- /dev/null +++ b/libtorrent/include/asio/detail/kqueue_reactor_fwd.hpp @@ -0,0 +1,41 @@ +// +// kqueue_reactor_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// Copyright (c) 2005 Stefan Arentz (stefan at soze dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_KQUEUE_REACTOR_FWD_HPP +#define ASIO_DETAIL_KQUEUE_REACTOR_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#if !defined(ASIO_DISABLE_KQUEUE) +#if defined(__MACH__) && defined(__APPLE__) + +// Define this to indicate that epoll is supported on the target platform. +#define ASIO_HAS_KQUEUE 1 + +namespace asio { +namespace detail { + +template +class kqueue_reactor; + +} // namespace detail +} // namespace asio + +#endif // defined(__MACH__) && defined(__APPLE__) +#endif // !defined(ASIO_DISABLE_KQUEUE) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_KQUEUE_REACTOR_FWD_HPP diff --git a/libtorrent/include/asio/detail/local_free_on_block_exit.hpp b/libtorrent/include/asio/detail/local_free_on_block_exit.hpp new file mode 100644 index 000000000..8e1f6088d --- /dev/null +++ b/libtorrent/include/asio/detail/local_free_on_block_exit.hpp @@ -0,0 +1,59 @@ +// +// local_free_on_block_exit.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_LOCAL_FREE_ON_BLOCK_EXIT_HPP +#define ASIO_DETAIL_LOCAL_FREE_ON_BLOCK_EXIT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +class local_free_on_block_exit + : private noncopyable +{ +public: + // Constructor blocks all signals for the calling thread. + explicit local_free_on_block_exit(void* p) + : p_(p) + { + } + + // Destructor restores the previous signal mask. + ~local_free_on_block_exit() + { + ::LocalFree(p_); + } + +private: + void* p_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_LOCAL_FREE_ON_BLOCK_EXIT_HPP diff --git a/libtorrent/include/asio/detail/mutex.hpp b/libtorrent/include/asio/detail/mutex.hpp new file mode 100644 index 000000000..87d32ba3c --- /dev/null +++ b/libtorrent/include/asio/detail/mutex.hpp @@ -0,0 +1,50 @@ +// +// mutex.hpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_MUTEX_HPP +#define ASIO_DETAIL_MUTEX_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) +# include "asio/detail/null_mutex.hpp" +#elif defined(BOOST_WINDOWS) +# include "asio/detail/win_mutex.hpp" +#elif defined(BOOST_HAS_PTHREADS) +# include "asio/detail/posix_mutex.hpp" +#else +# error Only Windows and POSIX are supported! +#endif + +namespace asio { +namespace detail { + +#if !defined(BOOST_HAS_THREADS) +typedef null_mutex mutex; +#elif defined(BOOST_WINDOWS) +typedef win_mutex mutex; +#elif defined(BOOST_HAS_PTHREADS) +typedef posix_mutex mutex; +#endif + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_MUTEX_HPP diff --git a/libtorrent/include/asio/detail/noncopyable.hpp b/libtorrent/include/asio/detail/noncopyable.hpp new file mode 100644 index 000000000..8e9d54683 --- /dev/null +++ b/libtorrent/include/asio/detail/noncopyable.hpp @@ -0,0 +1,55 @@ +// +// noncopyable.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_NONCOPYABLE_HPP +#define ASIO_DETAIL_NONCOPYABLE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +// Redefine the noncopyable class for Borland C++ since that compiler does not +// apply the empty base optimisation unless the base class contains a dummy +// char data member. +class noncopyable +{ +protected: + noncopyable() {} + ~noncopyable() {} +private: + noncopyable(const noncopyable&); + const noncopyable& operator=(const noncopyable&); + char dummy_; +}; +#else +using boost::noncopyable; +#endif + +} // namespace detail + +using asio::detail::noncopyable; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_NONCOPYABLE_HPP diff --git a/libtorrent/include/asio/detail/null_event.hpp b/libtorrent/include/asio/detail/null_event.hpp new file mode 100644 index 000000000..df522ce0f --- /dev/null +++ b/libtorrent/include/asio/detail/null_event.hpp @@ -0,0 +1,68 @@ +// +// null_event.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_NULL_EVENT_HPP +#define ASIO_DETAIL_NULL_EVENT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class null_event + : private noncopyable +{ +public: + // Constructor. + null_event() + { + } + + // Destructor. + ~null_event() + { + } + + // Signal the event. + void signal() + { + } + + // Reset the event. + void clear() + { + } + + // Wait for the event to become signalled. + void wait() + { + } +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_HAS_THREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_NULL_EVENT_HPP diff --git a/libtorrent/include/asio/detail/null_mutex.hpp b/libtorrent/include/asio/detail/null_mutex.hpp new file mode 100644 index 000000000..2f6a72084 --- /dev/null +++ b/libtorrent/include/asio/detail/null_mutex.hpp @@ -0,0 +1,66 @@ +// +// null_mutex.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_NULL_MUTEX_HPP +#define ASIO_DETAIL_NULL_MUTEX_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/scoped_lock.hpp" + +namespace asio { +namespace detail { + +class null_mutex + : private noncopyable +{ +public: + typedef asio::detail::scoped_lock scoped_lock; + + // Constructor. + null_mutex() + { + } + + // Destructor. + ~null_mutex() + { + } + + // Lock the mutex. + void lock() + { + } + + // Unlock the mutex. + void unlock() + { + } +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_HAS_THREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_NULL_MUTEX_HPP diff --git a/libtorrent/include/asio/detail/null_signal_blocker.hpp b/libtorrent/include/asio/detail/null_signal_blocker.hpp new file mode 100644 index 000000000..c10017d98 --- /dev/null +++ b/libtorrent/include/asio/detail/null_signal_blocker.hpp @@ -0,0 +1,63 @@ +// +// null_signal_blocker.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_NULL_SIGNAL_BLOCKER_HPP +#define ASIO_DETAIL_NULL_SIGNAL_BLOCKER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class null_signal_blocker + : private noncopyable +{ +public: + // Constructor blocks all signals for the calling thread. + null_signal_blocker() + { + } + + // Destructor restores the previous signal mask. + ~null_signal_blocker() + { + } + + // Block all signals for the calling thread. + void block() + { + } + + // Restore the previous signal mask. + void unblock() + { + } +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_HAS_THREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_NULL_SIGNAL_BLOCKER_HPP diff --git a/libtorrent/include/asio/detail/null_thread.hpp b/libtorrent/include/asio/detail/null_thread.hpp new file mode 100644 index 000000000..c013a7701 --- /dev/null +++ b/libtorrent/include/asio/detail/null_thread.hpp @@ -0,0 +1,68 @@ +// +// null_thread.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_NULL_THREAD_HPP +#define ASIO_DETAIL_NULL_THREAD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class null_thread + : private noncopyable +{ +public: + // Constructor. + template + null_thread(Function f) + { + asio::system_error e( + asio::error::operation_not_supported, "thread"); + boost::throw_exception(e); + } + + // Destructor. + ~null_thread() + { + } + + // Wait for the thread to exit. + void join() + { + } +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_HAS_THREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_NULL_THREAD_HPP diff --git a/libtorrent/include/asio/detail/null_tss_ptr.hpp b/libtorrent/include/asio/detail/null_tss_ptr.hpp new file mode 100644 index 000000000..9f3b0ad0c --- /dev/null +++ b/libtorrent/include/asio/detail/null_tss_ptr.hpp @@ -0,0 +1,70 @@ +// +// null_tss_ptr.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_NULL_TSS_PTR_HPP +#define ASIO_DETAIL_NULL_TSS_PTR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +template +class null_tss_ptr + : private noncopyable +{ +public: + // Constructor. + null_tss_ptr() + : value_(0) + { + } + + // Destructor. + ~null_tss_ptr() + { + } + + // Get the value. + operator T*() const + { + return value_; + } + + // Set the value. + void operator=(T* value) + { + value_ = value; + } + +private: + T* value_; +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_HAS_THREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_NULL_TSS_PTR_HPP diff --git a/libtorrent/include/asio/detail/old_win_sdk_compat.hpp b/libtorrent/include/asio/detail/old_win_sdk_compat.hpp new file mode 100644 index 000000000..2fb957aa9 --- /dev/null +++ b/libtorrent/include/asio/detail/old_win_sdk_compat.hpp @@ -0,0 +1,321 @@ +// +// old_win_sdk_compat.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_OLD_WIN_SDK_COMPAT_HPP +#define ASIO_DETAIL_OLD_WIN_SDK_COMPAT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +// Guess whether we are building against on old Platform SDK. +#if !defined(IN6ADDR_ANY_INIT) +#define ASIO_HAS_OLD_WIN_SDK 1 +#endif // !defined(IN6ADDR_ANY_INIT) + +#if defined(ASIO_HAS_OLD_WIN_SDK) + +// Emulation of types that are missing from old Platform SDKs. + +namespace asio { +namespace detail { + +enum +{ + sockaddr_storage_maxsize = 128, // Maximum size. + sockaddr_storage_alignsize = (sizeof(__int64)), // Desired alignment. + sockaddr_storage_pad1size = (sockaddr_storage_alignsize - sizeof(short)), + sockaddr_storage_pad2size = (sockaddr_storage_maxsize - + (sizeof(short) + sockaddr_storage_pad1size + sockaddr_storage_alignsize)) +}; + +struct sockaddr_storage_emulation +{ + short ss_family; + char __ss_pad1[sockaddr_storage_pad1size]; + __int64 __ss_align; + char __ss_pad2[sockaddr_storage_pad2size]; +}; + +struct in6_addr_emulation +{ + u_char s6_addr[16]; +}; + +struct sockaddr_in6_emulation +{ + short sin6_family; + u_short sin6_port; + u_long sin6_flowinfo; + in6_addr_emulation sin6_addr; + u_long sin6_scope_id; +}; + +struct ipv6_mreq_emulation +{ + in6_addr_emulation ipv6mr_multiaddr; + unsigned int ipv6mr_interface; +}; + +#if !defined(IN6ADDR_ANY_INIT) +# define IN6ADDR_ANY_INIT { 0 } +#endif + +#if !defined(IN6ADDR_LOOPBACK_INIT) +# define IN6ADDR_LOOPBACK_INIT { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } +#endif + +struct addrinfo_emulation +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + char* ai_canonname; + sockaddr* ai_addr; + addrinfo_emulation* ai_next; +}; + +#if !defined(AI_PASSIVE) +# define AI_PASSIVE 0x1 +#endif + +#if !defined(AI_CANONNAME) +# define AI_CANONNAME 0x2 +#endif + +#if !defined(AI_NUMERICHOST) +# define AI_NUMERICHOST 0x4 +#endif + +#if !defined(EAI_AGAIN) +# define EAI_AGAIN WSATRY_AGAIN +#endif + +#if !defined(EAI_BADFLAGS) +# define EAI_BADFLAGS WSAEINVAL +#endif + +#if !defined(EAI_FAIL) +# define EAI_FAIL WSANO_RECOVERY +#endif + +#if !defined(EAI_FAMILY) +# define EAI_FAMILY WSAEAFNOSUPPORT +#endif + +#if !defined(EAI_MEMORY) +# define EAI_MEMORY WSA_NOT_ENOUGH_MEMORY +#endif + +#if !defined(EAI_NODATA) +# define EAI_NODATA WSANO_DATA +#endif + +#if !defined(EAI_NONAME) +# define EAI_NONAME WSAHOST_NOT_FOUND +#endif + +#if !defined(EAI_SERVICE) +# define EAI_SERVICE WSATYPE_NOT_FOUND +#endif + +#if !defined(EAI_SOCKTYPE) +# define EAI_SOCKTYPE WSAESOCKTNOSUPPORT +#endif + +#if !defined(NI_NOFQDN) +# define NI_NOFQDN 0x01 +#endif + +#if !defined(NI_NUMERICHOST) +# define NI_NUMERICHOST 0x02 +#endif + +#if !defined(NI_NAMEREQD) +# define NI_NAMEREQD 0x04 +#endif + +#if !defined(NI_NUMERICSERV) +# define NI_NUMERICSERV 0x08 +#endif + +#if !defined(NI_DGRAM) +# define NI_DGRAM 0x10 +#endif + +#if !defined(IPPROTO_IPV6) +# define IPPROTO_IPV6 41 +#endif + +#if !defined(IPV6_UNICAST_HOPS) +# define IPV6_UNICAST_HOPS 4 +#endif + +#if !defined(IPV6_MULTICAST_IF) +# define IPV6_MULTICAST_IF 9 +#endif + +#if !defined(IPV6_MULTICAST_HOPS) +# define IPV6_MULTICAST_HOPS 10 +#endif + +#if !defined(IPV6_MULTICAST_LOOP) +# define IPV6_MULTICAST_LOOP 11 +#endif + +#if !defined(IPV6_JOIN_GROUP) +# define IPV6_JOIN_GROUP 12 +#endif + +#if !defined(IPV6_LEAVE_GROUP) +# define IPV6_LEAVE_GROUP 13 +#endif + +inline int IN6_IS_ADDR_UNSPECIFIED(const in6_addr_emulation* a) +{ + return ((a->s6_addr[0] == 0) + && (a->s6_addr[1] == 0) + && (a->s6_addr[2] == 0) + && (a->s6_addr[3] == 0) + && (a->s6_addr[4] == 0) + && (a->s6_addr[5] == 0) + && (a->s6_addr[6] == 0) + && (a->s6_addr[7] == 0) + && (a->s6_addr[8] == 0) + && (a->s6_addr[9] == 0) + && (a->s6_addr[10] == 0) + && (a->s6_addr[11] == 0) + && (a->s6_addr[12] == 0) + && (a->s6_addr[13] == 0) + && (a->s6_addr[14] == 0) + && (a->s6_addr[15] == 0)); +} + +inline int IN6_IS_ADDR_LOOPBACK(const in6_addr_emulation* a) +{ + return ((a->s6_addr[0] == 0) + && (a->s6_addr[1] == 0) + && (a->s6_addr[2] == 0) + && (a->s6_addr[3] == 0) + && (a->s6_addr[4] == 0) + && (a->s6_addr[5] == 0) + && (a->s6_addr[6] == 0) + && (a->s6_addr[7] == 0) + && (a->s6_addr[8] == 0) + && (a->s6_addr[9] == 0) + && (a->s6_addr[10] == 0) + && (a->s6_addr[11] == 0) + && (a->s6_addr[12] == 0) + && (a->s6_addr[13] == 0) + && (a->s6_addr[14] == 0) + && (a->s6_addr[15] == 1)); +} + +inline int IN6_IS_ADDR_MULTICAST(const in6_addr_emulation* a) +{ + return (a->s6_addr[0] == 0xff); +} + +inline int IN6_IS_ADDR_LINKLOCAL(const in6_addr_emulation* a) +{ + return ((a->s6_addr[0] == 0xfe) && ((a->s6_addr[1] & 0xc0) == 0x80)); +} + +inline int IN6_IS_ADDR_SITELOCAL(const in6_addr_emulation* a) +{ + return ((a->s6_addr[0] == 0xfe) && ((a->s6_addr[1] & 0xc0) == 0xc0)); +} + +inline int IN6_IS_ADDR_V4MAPPED(const in6_addr_emulation* a) +{ + return ((a->s6_addr[0] == 0) + && (a->s6_addr[1] == 0) + && (a->s6_addr[2] == 0) + && (a->s6_addr[3] == 0) + && (a->s6_addr[4] == 0) + && (a->s6_addr[5] == 0) + && (a->s6_addr[6] == 0) + && (a->s6_addr[7] == 0) + && (a->s6_addr[8] == 0) + && (a->s6_addr[9] == 0) + && (a->s6_addr[10] == 0xff) + && (a->s6_addr[11] == 0xff)); +} + +inline int IN6_IS_ADDR_V4COMPAT(const in6_addr_emulation* a) +{ + return ((a->s6_addr[0] == 0) + && (a->s6_addr[1] == 0) + && (a->s6_addr[2] == 0) + && (a->s6_addr[3] == 0) + && (a->s6_addr[4] == 0) + && (a->s6_addr[5] == 0) + && (a->s6_addr[6] == 0) + && (a->s6_addr[7] == 0) + && (a->s6_addr[8] == 0) + && (a->s6_addr[9] == 0) + && (a->s6_addr[10] == 0xff) + && (a->s6_addr[11] == 0xff) + && !((a->s6_addr[12] == 0) + && (a->s6_addr[13] == 0) + && (a->s6_addr[14] == 0) + && ((a->s6_addr[15] == 0) || (a->s6_addr[15] == 1)))); +} + +inline int IN6_IS_ADDR_MC_NODELOCAL(const in6_addr_emulation* a) +{ + return IN6_IS_ADDR_MULTICAST(a) && ((a->s6_addr[1] & 0xf) == 1); +} + +inline int IN6_IS_ADDR_MC_LINKLOCAL(const in6_addr_emulation* a) +{ + return IN6_IS_ADDR_MULTICAST(a) && ((a->s6_addr[1] & 0xf) == 2); +} + +inline int IN6_IS_ADDR_MC_SITELOCAL(const in6_addr_emulation* a) +{ + return IN6_IS_ADDR_MULTICAST(a) && ((a->s6_addr[1] & 0xf) == 5); +} + +inline int IN6_IS_ADDR_MC_ORGLOCAL(const in6_addr_emulation* a) +{ + return IN6_IS_ADDR_MULTICAST(a) && ((a->s6_addr[1] & 0xf) == 8); +} + +inline int IN6_IS_ADDR_MC_GLOBAL(const in6_addr_emulation* a) +{ + return IN6_IS_ADDR_MULTICAST(a) && ((a->s6_addr[1] & 0xf) == 0xe); +} + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_OLD_WIN_SDK) + +// Even newer Platform SDKs that support IPv6 may not define IPV6_V6ONLY. +#if !defined(IPV6_V6ONLY) +# define IPV6_V6ONLY 27 +#endif + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_OLD_WIN_SDK_COMPAT_HPP diff --git a/libtorrent/include/asio/detail/pipe_select_interrupter.hpp b/libtorrent/include/asio/detail/pipe_select_interrupter.hpp new file mode 100644 index 000000000..e203669cb --- /dev/null +++ b/libtorrent/include/asio/detail/pipe_select_interrupter.hpp @@ -0,0 +1,104 @@ +// +// pipe_select_interrupter.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_PIPE_SELECT_INTERRUPTER_HPP +#define ASIO_DETAIL_PIPE_SELECT_INTERRUPTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +class pipe_select_interrupter +{ +public: + // Constructor. + pipe_select_interrupter() + { + int pipe_fds[2]; + if (pipe(pipe_fds) == 0) + { + read_descriptor_ = pipe_fds[0]; + ::fcntl(read_descriptor_, F_SETFL, O_NONBLOCK); + write_descriptor_ = pipe_fds[1]; + ::fcntl(write_descriptor_, F_SETFL, O_NONBLOCK); + } + } + + // Destructor. + ~pipe_select_interrupter() + { + if (read_descriptor_ != -1) + ::close(read_descriptor_); + if (write_descriptor_ != -1) + ::close(write_descriptor_); + } + + // Interrupt the select call. + void interrupt() + { + char byte = 0; + ::write(write_descriptor_, &byte, 1); + } + + // Reset the select interrupt. Returns true if the call was interrupted. + bool reset() + { + char data[1024]; + int bytes_read = ::read(read_descriptor_, data, sizeof(data)); + bool was_interrupted = (bytes_read > 0); + while (bytes_read == sizeof(data)) + bytes_read = ::read(read_descriptor_, data, sizeof(data)); + return was_interrupted; + } + + // Get the read descriptor to be passed to select. + int read_descriptor() const + { + return read_descriptor_; + } + +private: + // The read end of a connection used to interrupt the select call. This file + // descriptor is passed to select such that when it is time to stop, a single + // byte will be written on the other end of the connection and this + // descriptor will become readable. + int read_descriptor_; + + // The write end of a connection used to interrupt the select call. A single + // byte may be written to this to wake up the select which is waiting for the + // other end to become readable. + int write_descriptor_; +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_PIPE_SELECT_INTERRUPTER_HPP diff --git a/libtorrent/include/asio/detail/pop_options.hpp b/libtorrent/include/asio/detail/pop_options.hpp new file mode 100644 index 000000000..c8b0ed93b --- /dev/null +++ b/libtorrent/include/asio/detail/pop_options.hpp @@ -0,0 +1,88 @@ +// +// pop_options.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// No header guard + +#if defined(__COMO__) + +// Comeau C++ + +#elif defined(__DMC__) + +// Digital Mars C++ + +#elif defined(__INTEL_COMPILER) || defined(__ICL) \ + || defined(__ICC) || defined(__ECC) + +// Intel C++ + +#elif defined(__GNUC__) + +// GNU C++ + +# if defined(__MINGW32__) || defined(__CYGWIN__) +# pragma pack (pop) +# endif + +#elif defined(__KCC) + +// Kai C++ + +#elif defined(__sgi) + +// SGI MIPSpro C++ + +#elif defined(__DECCXX) + +// Compaq Tru64 Unix cxx + +#elif defined(__ghs) + +// Greenhills C++ + +#elif defined(__BORLANDC__) + +// Borland C++ + +# pragma option pop +# pragma nopushoptwarn +# pragma nopackwarning + +#elif defined(__MWERKS__) + +// Metrowerks CodeWarrior + +#elif defined(__SUNPRO_CC) + +// Sun Workshop Compiler C++ + +#elif defined(__HP_aCC) + +// HP aCC + +#elif defined(__MRC__) || defined(__SC__) + +// MPW MrCpp or SCpp + +#elif defined(__IBMCPP__) + +// IBM Visual Age + +#elif defined(_MSC_VER) + +// Microsoft Visual C++ +// +// Must remain the last #elif since some other vendors (Metrowerks, for example) +// also #define _MSC_VER + +# pragma warning (pop) +# pragma pack (pop) + +#endif diff --git a/libtorrent/include/asio/detail/posix_event.hpp b/libtorrent/include/asio/detail/posix_event.hpp new file mode 100644 index 000000000..408c23bb9 --- /dev/null +++ b/libtorrent/include/asio/detail/posix_event.hpp @@ -0,0 +1,111 @@ +// +// posix_event.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_POSIX_EVENT_HPP +#define ASIO_DETAIL_POSIX_EVENT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class posix_event + : private noncopyable +{ +public: + // Constructor. + posix_event() + : signalled_(false) + { + int error = ::pthread_mutex_init(&mutex_, 0); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "event"); + boost::throw_exception(e); + } + + error = ::pthread_cond_init(&cond_, 0); + if (error != 0) + { + ::pthread_mutex_destroy(&mutex_); + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "event"); + boost::throw_exception(e); + } + } + + // Destructor. + ~posix_event() + { + ::pthread_cond_destroy(&cond_); + ::pthread_mutex_destroy(&mutex_); + } + + // Signal the event. + void signal() + { + ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + signalled_ = true; + ::pthread_cond_signal(&cond_); // Ignore EINVAL. + ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. + } + + // Reset the event. + void clear() + { + ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + signalled_ = false; + ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. + } + + // Wait for the event to become signalled. + void wait() + { + ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + while (!signalled_) + ::pthread_cond_wait(&cond_, &mutex_); // Ignore EINVAL. + ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. + } + +private: + ::pthread_mutex_t mutex_; + ::pthread_cond_t cond_; + bool signalled_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_POSIX_EVENT_HPP diff --git a/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp b/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp new file mode 100644 index 000000000..ae5bf6642 --- /dev/null +++ b/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp @@ -0,0 +1,72 @@ +// +// posix_fd_set_adapter.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_POSIX_FD_SET_ADAPTER_HPP +#define ASIO_DETAIL_POSIX_FD_SET_ADAPTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_types.hpp" + +#if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) + +namespace asio { +namespace detail { + +// Adapts the FD_SET type to meet the Descriptor_Set concept's requirements. +class posix_fd_set_adapter +{ +public: + posix_fd_set_adapter() + : max_descriptor_(invalid_socket) + { + using namespace std; // Needed for memset on Solaris. + FD_ZERO(&fd_set_); + } + + void set(socket_type descriptor) + { + if (max_descriptor_ == invalid_socket || descriptor > max_descriptor_) + max_descriptor_ = descriptor; + FD_SET(descriptor, &fd_set_); + } + + bool is_set(socket_type descriptor) const + { + return FD_ISSET(descriptor, &fd_set_) != 0; + } + + operator fd_set*() + { + return &fd_set_; + } + + socket_type max_descriptor() const + { + return max_descriptor_; + } + +private: + fd_set fd_set_; + socket_type max_descriptor_; +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_POSIX_FD_SET_ADAPTER_HPP diff --git a/libtorrent/include/asio/detail/posix_mutex.hpp b/libtorrent/include/asio/detail/posix_mutex.hpp new file mode 100644 index 000000000..154089f3c --- /dev/null +++ b/libtorrent/include/asio/detail/posix_mutex.hpp @@ -0,0 +1,100 @@ +// +// posix_mutex.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_POSIX_MUTEX_HPP +#define ASIO_DETAIL_POSIX_MUTEX_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/scoped_lock.hpp" + +namespace asio { +namespace detail { + +class posix_mutex + : private noncopyable +{ +public: + typedef asio::detail::scoped_lock scoped_lock; + + // Constructor. + posix_mutex() + { + int error = ::pthread_mutex_init(&mutex_, 0); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "mutex"); + boost::throw_exception(e); + } + } + + // Destructor. + ~posix_mutex() + { + ::pthread_mutex_destroy(&mutex_); + } + + // Lock the mutex. + void lock() + { + int error = ::pthread_mutex_lock(&mutex_); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "mutex"); + boost::throw_exception(e); + } + } + + // Unlock the mutex. + void unlock() + { + int error = ::pthread_mutex_unlock(&mutex_); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "mutex"); + boost::throw_exception(e); + } + } + +private: + ::pthread_mutex_t mutex_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_POSIX_MUTEX_HPP diff --git a/libtorrent/include/asio/detail/posix_signal_blocker.hpp b/libtorrent/include/asio/detail/posix_signal_blocker.hpp new file mode 100644 index 000000000..73d0581e1 --- /dev/null +++ b/libtorrent/include/asio/detail/posix_signal_blocker.hpp @@ -0,0 +1,90 @@ +// +// posix_signal_blocker.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_POSIX_SIGNAL_BLOCKER_HPP +#define ASIO_DETAIL_POSIX_SIGNAL_BLOCKER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class posix_signal_blocker + : private noncopyable +{ +public: + // Constructor blocks all signals for the calling thread. + posix_signal_blocker() + : blocked_(false) + { + sigset_t new_mask; + sigfillset(&new_mask); + blocked_ = (pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask_) == 0); + } + + // Destructor restores the previous signal mask. + ~posix_signal_blocker() + { + if (blocked_) + pthread_sigmask(SIG_SETMASK, &old_mask_, 0); + } + + // Block all signals for the calling thread. + void block() + { + if (!blocked_) + { + sigset_t new_mask; + sigfillset(&new_mask); + blocked_ = (pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask_) == 0); + } + } + + // Restore the previous signal mask. + void unblock() + { + if (blocked_) + blocked_ = (pthread_sigmask(SIG_SETMASK, &old_mask_, 0) != 0); + } + +private: + // Have signals been blocked. + bool blocked_; + + // The previous signal mask. + sigset_t old_mask_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_POSIX_SIGNAL_BLOCKER_HPP diff --git a/libtorrent/include/asio/detail/posix_thread.hpp b/libtorrent/include/asio/detail/posix_thread.hpp new file mode 100644 index 000000000..f01b40428 --- /dev/null +++ b/libtorrent/include/asio/detail/posix_thread.hpp @@ -0,0 +1,127 @@ +// +// posix_thread.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_POSIX_THREAD_HPP +#define ASIO_DETAIL_POSIX_THREAD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +extern "C" void* asio_detail_posix_thread_function(void* arg); + +class posix_thread + : private noncopyable +{ +public: + // Constructor. + template + posix_thread(Function f) + : joined_(false) + { + std::auto_ptr arg(new func(f)); + int error = ::pthread_create(&thread_, 0, + asio_detail_posix_thread_function, arg.get()); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "thread"); + boost::throw_exception(e); + } + arg.release(); + } + + // Destructor. + ~posix_thread() + { + if (!joined_) + ::pthread_detach(thread_); + } + + // Wait for the thread to exit. + void join() + { + if (!joined_) + { + ::pthread_join(thread_, 0); + joined_ = true; + } + } + +private: + friend void* asio_detail_posix_thread_function(void* arg); + + class func_base + { + public: + virtual ~func_base() {} + virtual void run() = 0; + }; + + template + class func + : public func_base + { + public: + func(Function f) + : f_(f) + { + } + + virtual void run() + { + f_(); + } + + private: + Function f_; + }; + + ::pthread_t thread_; + bool joined_; +}; + +inline void* asio_detail_posix_thread_function(void* arg) +{ + std::auto_ptr f( + static_cast(arg)); + f->run(); + return 0; +} + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_POSIX_THREAD_HPP diff --git a/libtorrent/include/asio/detail/posix_tss_ptr.hpp b/libtorrent/include/asio/detail/posix_tss_ptr.hpp new file mode 100644 index 000000000..93fce3479 --- /dev/null +++ b/libtorrent/include/asio/detail/posix_tss_ptr.hpp @@ -0,0 +1,86 @@ +// +// posix_tss_ptr.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_POSIX_TSS_PTR_HPP +#define ASIO_DETAIL_POSIX_TSS_PTR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +template +class posix_tss_ptr + : private noncopyable +{ +public: + // Constructor. + posix_tss_ptr() + { + int error = ::pthread_key_create(&tss_key_, 0); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "tss"); + boost::throw_exception(e); + } + } + + // Destructor. + ~posix_tss_ptr() + { + ::pthread_key_delete(tss_key_); + } + + // Get the value. + operator T*() const + { + return static_cast(::pthread_getspecific(tss_key_)); + } + + // Set the value. + void operator=(T* value) + { + ::pthread_setspecific(tss_key_, value); + } + +private: + // Thread-specific storage to allow unlocked access to determine whether a + // thread is a member of the pool. + pthread_key_t tss_key_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_HAS_PTHREADS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_POSIX_TSS_PTR_HPP diff --git a/libtorrent/include/asio/detail/push_options.hpp b/libtorrent/include/asio/detail/push_options.hpp new file mode 100644 index 000000000..0b68d2933 --- /dev/null +++ b/libtorrent/include/asio/detail/push_options.hpp @@ -0,0 +1,106 @@ +// +// push_options.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// No header guard + +#if defined(__COMO__) + +// Comeau C++ + +#elif defined(__DMC__) + +// Digital Mars C++ + +#elif defined(__INTEL_COMPILER) || defined(__ICL) \ + || defined(__ICC) || defined(__ECC) + +// Intel C++ + +#elif defined(__GNUC__) + +// GNU C++ + +# if defined(__MINGW32__) || defined(__CYGWIN__) +# pragma pack (push, 8) +# endif + +#elif defined(__KCC) + +// Kai C++ + +#elif defined(__sgi) + +// SGI MIPSpro C++ + +#elif defined(__DECCXX) + +// Compaq Tru64 Unix cxx + +#elif defined(__ghs) + +// Greenhills C++ + +#elif defined(__BORLANDC__) + +// Borland C++ + +# pragma option push -a8 -b -Ve- -Vx- -w-inl -vi- +# pragma nopushoptwarn +# pragma nopackwarning +# if !defined(__MT__) +# error Multithreaded RTL must be selected. +# endif // !defined(__MT__) + +#elif defined(__MWERKS__) + +// Metrowerks CodeWarrior + +#elif defined(__SUNPRO_CC) + +// Sun Workshop Compiler C++ + +#elif defined(__HP_aCC) + +// HP aCC + +#elif defined(__MRC__) || defined(__SC__) + +// MPW MrCpp or SCpp + +#elif defined(__IBMCPP__) + +// IBM Visual Age + +#elif defined(_MSC_VER) + +// Microsoft Visual C++ +// +// Must remain the last #elif since some other vendors (Metrowerks, for example) +// also #define _MSC_VER + +# pragma warning (disable:4103) +# pragma warning (push) +# pragma warning (disable:4244) +# pragma warning (disable:4355) +# pragma warning (disable:4675) +# pragma pack (push, 8) +// Note that if the /Og optimisation flag is enabled with MSVC6, the compiler +// has a tendency to incorrectly optimise away some calls to member template +// functions, even though those functions contain code that should not be +// optimised away! Therefore we will always disable this optimisation option +// for the MSVC6 compiler. +# if (_MSC_VER < 1300) +# pragma optimize ("g", off) +# endif +# if !defined(_MT) +# error Multithreaded RTL must be selected. +# endif // !defined(_MT) + +#endif diff --git a/libtorrent/include/asio/detail/reactive_socket_service.hpp b/libtorrent/include/asio/detail/reactive_socket_service.hpp new file mode 100644 index 000000000..d5b8e4fc8 --- /dev/null +++ b/libtorrent/include/asio/detail/reactive_socket_service.hpp @@ -0,0 +1,1574 @@ +// +// reactive_socket_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_REACTIVE_SOCKET_SERVICE_HPP +#define ASIO_DETAIL_REACTIVE_SOCKET_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/socket_base.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/socket_holder.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +template +class reactive_socket_service + : public asio::detail::service_base< + reactive_socket_service > +{ +public: + // The protocol type. + typedef Protocol protocol_type; + + // The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + // The native type of a socket. + typedef socket_type native_type; + + // The implementation type of the socket. + class implementation_type + : private asio::detail::noncopyable + { + public: + // Default constructor. + implementation_type() + : socket_(invalid_socket), + flags_(0), + protocol_(endpoint_type().protocol()) + { + } + + private: + // Only this service will have access to the internal values. + friend class reactive_socket_service; + + // The native socket representation. + socket_type socket_; + + enum + { + user_set_non_blocking = 1, // The user wants a non-blocking socket. + internal_non_blocking = 2, // The socket has been set non-blocking. + enable_connection_aborted = 4, // User wants connection_aborted errors. + user_set_linger = 8 // The user set the linger option. + }; + + // Flags indicating the current state of the socket. + unsigned char flags_; + + // The protocol associated with the socket. + protocol_type protocol_; + }; + + // The maximum number of buffers to support in a single operation. + enum { max_buffers = 16 }; + + // Constructor. + reactive_socket_service(asio::io_service& io_service) + : asio::detail::service_base< + reactive_socket_service >(io_service), + reactor_(asio::use_service(io_service)) + { + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + // Construct a new socket implementation. + void construct(implementation_type& impl) + { + impl.socket_ = invalid_socket; + impl.flags_ = 0; + } + + // Destroy a socket implementation. + void destroy(implementation_type& impl) + { + if (impl.socket_ != invalid_socket) + { + reactor_.close_descriptor(impl.socket_); + + if (impl.flags_ & implementation_type::internal_non_blocking) + { + ioctl_arg_type non_blocking = 0; + asio::error_code ignored_ec; + socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ignored_ec); + impl.flags_ &= ~implementation_type::internal_non_blocking; + } + + if (impl.flags_ & implementation_type::user_set_linger) + { + ::linger opt; + opt.l_onoff = 0; + opt.l_linger = 0; + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, + SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); + } + + asio::error_code ignored_ec; + socket_ops::close(impl.socket_, ignored_ec); + + impl.socket_ = invalid_socket; + } + } + + // Open a new socket implementation. + asio::error_code open(implementation_type& impl, + const protocol_type& protocol, asio::error_code& ec) + { + if (is_open(impl)) + { + ec = asio::error::already_open; + return ec; + } + + socket_holder sock(socket_ops::socket(protocol.family(), + protocol.type(), protocol.protocol(), ec)); + if (sock.get() == invalid_socket) + return ec; + + if (int err = reactor_.register_descriptor(sock.get())) + { + ec = asio::error_code(err, asio::native_ecat); + return ec; + } + + impl.socket_ = sock.release(); + impl.flags_ = 0; + impl.protocol_ = protocol; + ec = asio::error_code(); + return ec; + } + + // Assign a native socket to a socket implementation. + asio::error_code assign(implementation_type& impl, + const protocol_type& protocol, const native_type& native_socket, + asio::error_code& ec) + { + if (is_open(impl)) + { + ec = asio::error::already_open; + return ec; + } + + if (int err = reactor_.register_descriptor(native_socket)) + { + ec = asio::error_code(err, asio::native_ecat); + return ec; + } + + impl.socket_ = native_socket; + impl.flags_ = 0; + impl.protocol_ = protocol; + ec = asio::error_code(); + return ec; + } + + // Determine whether the socket is open. + bool is_open(const implementation_type& impl) const + { + return impl.socket_ != invalid_socket; + } + + // Destroy a socket implementation. + asio::error_code close(implementation_type& impl, + asio::error_code& ec) + { + if (is_open(impl)) + { + reactor_.close_descriptor(impl.socket_); + + if (impl.flags_ & implementation_type::internal_non_blocking) + { + ioctl_arg_type non_blocking = 0; + asio::error_code ignored_ec; + socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ignored_ec); + impl.flags_ &= ~implementation_type::internal_non_blocking; + } + + if (socket_ops::close(impl.socket_, ec) == socket_error_retval) + return ec; + + impl.socket_ = invalid_socket; + } + + ec = asio::error_code(); + return ec; + } + + // Get the native socket representation. + native_type native(implementation_type& impl) + { + return impl.socket_; + } + + // Cancel all operations associated with the socket. + asio::error_code cancel(implementation_type& impl, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + reactor_.cancel_ops(impl.socket_); + ec = asio::error_code(); + return ec; + } + + // Determine whether the socket is at the out-of-band data mark. + bool at_mark(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return false; + } + + asio::detail::ioctl_arg_type value = 0; + socket_ops::ioctl(impl.socket_, SIOCATMARK, &value, ec); +#if defined(ENOTTY) + if (ec.value() == ENOTTY) + ec = asio::error::not_socket; +#endif // defined(ENOTTY) + return ec ? false : value != 0; + } + + // Determine the number of bytes available for reading. + std::size_t available(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + asio::detail::ioctl_arg_type value = 0; + socket_ops::ioctl(impl.socket_, FIONREAD, &value, ec); +#if defined(ENOTTY) + if (ec.value() == ENOTTY) + ec = asio::error::not_socket; +#endif // defined(ENOTTY) + return ec ? static_cast(0) : static_cast(value); + } + + // Bind the socket to the specified local endpoint. + asio::error_code bind(implementation_type& impl, + const endpoint_type& endpoint, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::bind(impl.socket_, endpoint.data(), endpoint.size(), ec); + return ec; + } + + // Place the socket into the state where it will listen for new connections. + asio::error_code listen(implementation_type& impl, int backlog, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::listen(impl.socket_, backlog, ec); + return ec; + } + + // Set a socket option. + template + asio::error_code set_option(implementation_type& impl, + const Option& option, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + if (option.level(impl.protocol_) == custom_socket_option_level + && option.name(impl.protocol_) == enable_connection_aborted_option) + { + if (option.size(impl.protocol_) != sizeof(int)) + { + ec = asio::error::invalid_argument; + } + else + { + if (*reinterpret_cast(option.data(impl.protocol_))) + impl.flags_ |= implementation_type::enable_connection_aborted; + else + impl.flags_ &= ~implementation_type::enable_connection_aborted; + ec = asio::error_code(); + } + return ec; + } + else + { + if (option.level(impl.protocol_) == SOL_SOCKET + && option.name(impl.protocol_) == SO_LINGER) + { + impl.flags_ |= implementation_type::user_set_linger; + } + + socket_ops::setsockopt(impl.socket_, + option.level(impl.protocol_), option.name(impl.protocol_), + option.data(impl.protocol_), option.size(impl.protocol_), ec); + +#if defined(__MACH__) && defined(__APPLE__) \ +|| defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) + // To implement portable behaviour for SO_REUSEADDR with UDP sockets we + // need to also set SO_REUSEPORT on BSD-based platforms. + if (!ec && impl.protocol_.type() == SOCK_DGRAM + && option.level(impl.protocol_) == SOL_SOCKET + && option.name(impl.protocol_) == SO_REUSEADDR) + { + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, SOL_SOCKET, SO_REUSEPORT, + option.data(impl.protocol_), option.size(impl.protocol_), + ignored_ec); + } +#endif + + return ec; + } + } + + // Set a socket option. + template + asio::error_code get_option(const implementation_type& impl, + Option& option, asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + if (option.level(impl.protocol_) == custom_socket_option_level + && option.name(impl.protocol_) == enable_connection_aborted_option) + { + if (option.size(impl.protocol_) != sizeof(int)) + { + ec = asio::error::invalid_argument; + } + else + { + int* target = reinterpret_cast(option.data(impl.protocol_)); + if (impl.flags_ & implementation_type::enable_connection_aborted) + *target = 1; + else + *target = 0; + option.resize(impl.protocol_, sizeof(int)); + ec = asio::error_code(); + } + return ec; + } + else + { + size_t size = option.size(impl.protocol_); + socket_ops::getsockopt(impl.socket_, + option.level(impl.protocol_), option.name(impl.protocol_), + option.data(impl.protocol_), &size, ec); + if (!ec) + option.resize(impl.protocol_, size); + return ec; + } + } + + // Perform an IO control command on the socket. + template + asio::error_code io_control(implementation_type& impl, + IO_Control_Command& command, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + if (command.name() == static_cast(FIONBIO)) + { + if (command.get()) + impl.flags_ |= implementation_type::user_set_non_blocking; + else + impl.flags_ &= ~implementation_type::user_set_non_blocking; + ec = asio::error_code(); + } + else + { + socket_ops::ioctl(impl.socket_, command.name(), + static_cast(command.data()), ec); + } + return ec; + } + + // Get the local endpoint. + endpoint_type local_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return endpoint_type(); + } + + endpoint_type endpoint; + socket_addr_len_type addr_len = endpoint.capacity(); + if (socket_ops::getsockname(impl.socket_, endpoint.data(), &addr_len, ec)) + return endpoint_type(); + endpoint.resize(addr_len); + return endpoint; + } + + // Get the remote endpoint. + endpoint_type remote_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return endpoint_type(); + } + + endpoint_type endpoint; + socket_addr_len_type addr_len = endpoint.capacity(); + if (socket_ops::getpeername(impl.socket_, endpoint.data(), &addr_len, ec)) + return endpoint_type(); + endpoint.resize(addr_len); + return endpoint; + } + + /// Disable sends or receives on the socket. + asio::error_code shutdown(implementation_type& impl, + socket_base::shutdown_type what, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::shutdown(impl.socket_, what, ec); + return ec; + } + + // Send the given data to the peer. + template + size_t send(implementation_type& impl, const ConstBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + size_t i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) + { + ec = asio::error_code(); + return 0; + } + + // Make socket non-blocking if user wants non-blocking. + if (impl.flags_ & implementation_type::user_set_non_blocking) + { + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + return 0; + impl.flags_ |= implementation_type::internal_non_blocking; + } + } + + // Send the data. + for (;;) + { + // Try to complete the operation without blocking. + int bytes_sent = socket_ops::send(impl.socket_, bufs, i, flags, ec); + + // Check if operation succeeded. + if (bytes_sent >= 0) + return bytes_sent; + + // Operation failed. + if ((impl.flags_ & implementation_type::user_set_non_blocking) + || (ec != asio::error::would_block + && ec != asio::error::try_again)) + return 0; + + // Wait for socket to become ready. + if (socket_ops::poll_write(impl.socket_, ec) < 0) + return 0; + } + } + + template + class send_handler + { + public: + send_handler(socket_type socket, asio::io_service& io_service, + const ConstBufferSequence& buffers, socket_base::message_flags flags, + Handler handler) + : socket_(socket), + io_service_(io_service), + work_(io_service), + buffers_(buffers), + flags_(flags), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether the operation was successful. + if (result) + { + io_service_.post(bind_handler(handler_, result, 0)); + return true; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers_.begin(); + typename ConstBufferSequence::const_iterator end = buffers_.end(); + size_t i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + } + + // Send the data. + asio::error_code ec; + int bytes = socket_ops::send(socket_, bufs, i, flags_, ec); + + // Check if we need to run the operation again. + if (ec == asio::error::would_block + || ec == asio::error::try_again) + return false; + + io_service_.post(bind_handler(handler_, ec, bytes < 0 ? 0 : bytes)); + return true; + } + + private: + socket_type socket_; + asio::io_service& io_service_; + asio::io_service::work work_; + ConstBufferSequence buffers_; + socket_base::message_flags flags_; + Handler handler_; + }; + + // Start an asynchronous send. The data being sent must be valid for the + // lifetime of the asynchronous operation. + template + void async_send(implementation_type& impl, const ConstBufferSequence& buffers, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + } + else + { + if (impl.protocol_.type() == SOCK_STREAM) + { + // Determine total size of buffers. + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + size_t i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (total_buffer_size == 0) + { + this->io_service().post(bind_handler(handler, + asio::error_code(), 0)); + return; + } + } + + // Make socket non-blocking. + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec, 0)); + return; + } + impl.flags_ |= implementation_type::internal_non_blocking; + } + + reactor_.start_write_op(impl.socket_, + send_handler( + impl.socket_, this->io_service(), buffers, flags, handler)); + } + } + + // Send a datagram to the specified endpoint. Returns the number of bytes + // sent. + template + size_t send_to(implementation_type& impl, const ConstBufferSequence& buffers, + const endpoint_type& destination, socket_base::message_flags flags, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + size_t i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + } + + // Make socket non-blocking if user wants non-blocking. + if (impl.flags_ & implementation_type::user_set_non_blocking) + { + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + return 0; + impl.flags_ |= implementation_type::internal_non_blocking; + } + } + + // Send the data. + for (;;) + { + // Try to complete the operation without blocking. + int bytes_sent = socket_ops::sendto(impl.socket_, bufs, i, flags, + destination.data(), destination.size(), ec); + + // Check if operation succeeded. + if (bytes_sent >= 0) + return bytes_sent; + + // Operation failed. + if ((impl.flags_ & implementation_type::user_set_non_blocking) + || (ec != asio::error::would_block + && ec != asio::error::try_again)) + return 0; + + // Wait for socket to become ready. + if (socket_ops::poll_write(impl.socket_, ec) < 0) + return 0; + } + } + + template + class send_to_handler + { + public: + send_to_handler(socket_type socket, asio::io_service& io_service, + const ConstBufferSequence& buffers, const endpoint_type& endpoint, + socket_base::message_flags flags, Handler handler) + : socket_(socket), + io_service_(io_service), + work_(io_service), + buffers_(buffers), + destination_(endpoint), + flags_(flags), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether the operation was successful. + if (result) + { + io_service_.post(bind_handler(handler_, result, 0)); + return true; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers_.begin(); + typename ConstBufferSequence::const_iterator end = buffers_.end(); + size_t i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + } + + // Send the data. + asio::error_code ec; + int bytes = socket_ops::sendto(socket_, bufs, i, flags_, + destination_.data(), destination_.size(), ec); + + // Check if we need to run the operation again. + if (ec == asio::error::would_block + || ec == asio::error::try_again) + return false; + + io_service_.post(bind_handler(handler_, ec, bytes < 0 ? 0 : bytes)); + return true; + } + + private: + socket_type socket_; + asio::io_service& io_service_; + asio::io_service::work work_; + ConstBufferSequence buffers_; + endpoint_type destination_; + socket_base::message_flags flags_; + Handler handler_; + }; + + // Start an asynchronous send. The data being sent must be valid for the + // lifetime of the asynchronous operation. + template + void async_send_to(implementation_type& impl, + const ConstBufferSequence& buffers, + const endpoint_type& destination, socket_base::message_flags flags, + Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + } + else + { + // Make socket non-blocking. + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec, 0)); + return; + } + impl.flags_ |= implementation_type::internal_non_blocking; + } + + reactor_.start_write_op(impl.socket_, + send_to_handler( + impl.socket_, this->io_service(), buffers, + destination, flags, handler)); + } + } + + // Receive some data from the peer. Returns the number of bytes received. + template + size_t receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + size_t i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) + { + ec = asio::error_code(); + return 0; + } + + // Make socket non-blocking if user wants non-blocking. + if (impl.flags_ & implementation_type::user_set_non_blocking) + { + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + return 0; + impl.flags_ |= implementation_type::internal_non_blocking; + } + } + + // Receive some data. + for (;;) + { + // Try to complete the operation without blocking. + int bytes_recvd = socket_ops::recv(impl.socket_, bufs, i, flags, ec); + + // Check if operation succeeded. + if (bytes_recvd > 0) + return bytes_recvd; + + // Check for EOF. + if (bytes_recvd == 0) + { + ec = asio::error::eof; + return 0; + } + + // Operation failed. + if ((impl.flags_ & implementation_type::user_set_non_blocking) + || (ec != asio::error::would_block + && ec != asio::error::try_again)) + return 0; + + // Wait for socket to become ready. + if (socket_ops::poll_read(impl.socket_, ec) < 0) + return 0; + } + } + + template + class receive_handler + { + public: + receive_handler(socket_type socket, asio::io_service& io_service, + const MutableBufferSequence& buffers, socket_base::message_flags flags, + Handler handler) + : socket_(socket), + io_service_(io_service), + work_(io_service), + buffers_(buffers), + flags_(flags), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether the operation was successful. + if (result) + { + io_service_.post(bind_handler(handler_, result, 0)); + return true; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers_.begin(); + typename MutableBufferSequence::const_iterator end = buffers_.end(); + size_t i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + } + + // Receive some data. + asio::error_code ec; + int bytes = socket_ops::recv(socket_, bufs, i, flags_, ec); + if (bytes == 0) + ec = asio::error::eof; + + // Check if we need to run the operation again. + if (ec == asio::error::would_block + || ec == asio::error::try_again) + return false; + + io_service_.post(bind_handler(handler_, ec, bytes < 0 ? 0 : bytes)); + return true; + } + + private: + socket_type socket_; + asio::io_service& io_service_; + asio::io_service::work work_; + MutableBufferSequence buffers_; + socket_base::message_flags flags_; + Handler handler_; + }; + + // Start an asynchronous receive. The buffer for the data being received + // must be valid for the lifetime of the asynchronous operation. + template + void async_receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + } + else + { + if (impl.protocol_.type() == SOCK_STREAM) + { + // Determine total size of buffers. + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + size_t i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (total_buffer_size == 0) + { + this->io_service().post(bind_handler(handler, + asio::error_code(), 0)); + return; + } + } + + // Make socket non-blocking. + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec, 0)); + return; + } + impl.flags_ |= implementation_type::internal_non_blocking; + } + + if (flags & socket_base::message_out_of_band) + { + reactor_.start_except_op(impl.socket_, + receive_handler( + impl.socket_, this->io_service(), buffers, flags, handler)); + } + else + { + reactor_.start_read_op(impl.socket_, + receive_handler( + impl.socket_, this->io_service(), buffers, flags, handler)); + } + } + } + + // Receive a datagram with the endpoint of the sender. Returns the number of + // bytes received. + template + size_t receive_from(implementation_type& impl, + const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint, socket_base::message_flags flags, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + size_t i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + } + + // Make socket non-blocking if user wants non-blocking. + if (impl.flags_ & implementation_type::user_set_non_blocking) + { + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + return 0; + impl.flags_ |= implementation_type::internal_non_blocking; + } + } + + // Receive some data. + for (;;) + { + // Try to complete the operation without blocking. + socket_addr_len_type addr_len = sender_endpoint.capacity(); + int bytes_recvd = socket_ops::recvfrom(impl.socket_, bufs, i, flags, + sender_endpoint.data(), &addr_len, ec); + + // Check if operation succeeded. + if (bytes_recvd > 0) + { + sender_endpoint.resize(addr_len); + return bytes_recvd; + } + + // Check for EOF. + if (bytes_recvd == 0) + { + ec = asio::error::eof; + return 0; + } + + // Operation failed. + if ((impl.flags_ & implementation_type::user_set_non_blocking) + || (ec != asio::error::would_block + && ec != asio::error::try_again)) + return 0; + + // Wait for socket to become ready. + if (socket_ops::poll_read(impl.socket_, ec) < 0) + return 0; + } + } + + template + class receive_from_handler + { + public: + receive_from_handler(socket_type socket, + asio::io_service& io_service, + const MutableBufferSequence& buffers, endpoint_type& endpoint, + socket_base::message_flags flags, Handler handler) + : socket_(socket), + io_service_(io_service), + work_(io_service), + buffers_(buffers), + sender_endpoint_(endpoint), + flags_(flags), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether the operation was successful. + if (result != 0) + { + io_service_.post(bind_handler(handler_, result, 0)); + return true; + } + + // Copy buffers into array. + socket_ops::buf bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers_.begin(); + typename MutableBufferSequence::const_iterator end = buffers_.end(); + size_t i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + socket_ops::init_buf(bufs[i], + asio::buffer_cast(buffer), + asio::buffer_size(buffer)); + } + + // Receive some data. + socket_addr_len_type addr_len = sender_endpoint_.capacity(); + asio::error_code ec; + int bytes = socket_ops::recvfrom(socket_, bufs, i, flags_, + sender_endpoint_.data(), &addr_len, ec); + if (bytes == 0) + ec = asio::error::eof; + + // Check if we need to run the operation again. + if (ec == asio::error::would_block + || ec == asio::error::try_again) + return false; + + sender_endpoint_.resize(addr_len); + io_service_.post(bind_handler(handler_, ec, bytes < 0 ? 0 : bytes)); + return true; + } + + private: + socket_type socket_; + asio::io_service& io_service_; + asio::io_service::work work_; + MutableBufferSequence buffers_; + endpoint_type& sender_endpoint_; + socket_base::message_flags flags_; + Handler handler_; + }; + + // Start an asynchronous receive. The buffer for the data being received and + // the sender_endpoint object must both be valid for the lifetime of the + // asynchronous operation. + template + void async_receive_from(implementation_type& impl, + const MutableBufferSequence& buffers, endpoint_type& sender_endpoint, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + } + else + { + // Make socket non-blocking. + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec, 0)); + return; + } + impl.flags_ |= implementation_type::internal_non_blocking; + } + + reactor_.start_read_op(impl.socket_, + receive_from_handler( + impl.socket_, this->io_service(), buffers, + sender_endpoint, flags, handler)); + } + } + + // Accept a new connection. + template + asio::error_code accept(implementation_type& impl, + Socket& peer, endpoint_type* peer_endpoint, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + // We cannot accept a socket that is already open. + if (peer.is_open()) + { + ec = asio::error::already_open; + return ec; + } + + // Make socket non-blocking if user wants non-blocking. + if (impl.flags_ & implementation_type::user_set_non_blocking) + { + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + return ec; + impl.flags_ |= implementation_type::internal_non_blocking; + } + } + + // Accept a socket. + for (;;) + { + // Try to complete the operation without blocking. + asio::error_code ec; + socket_holder new_socket; + socket_addr_len_type addr_len = 0; + if (peer_endpoint) + { + addr_len = peer_endpoint->capacity(); + new_socket.reset(socket_ops::accept(impl.socket_, + peer_endpoint->data(), &addr_len, ec)); + } + else + { + new_socket.reset(socket_ops::accept(impl.socket_, 0, 0, ec)); + } + + // Check if operation succeeded. + if (new_socket.get() >= 0) + { + if (peer_endpoint) + peer_endpoint->resize(addr_len); + peer.assign(impl.protocol_, new_socket.get(), ec); + if (!ec) + new_socket.release(); + return ec; + } + + // Operation failed. + if (ec == asio::error::would_block + || ec == asio::error::try_again) + { + if (impl.flags_ & implementation_type::user_set_non_blocking) + return ec; + // Fall through to retry operation. + } + else if (ec == asio::error::connection_aborted) + { + if (impl.flags_ & implementation_type::enable_connection_aborted) + return ec; + // Fall through to retry operation. + } +#if defined(EPROTO) + else if (ec.value() == EPROTO) + { + if (impl.flags_ & implementation_type::enable_connection_aborted) + return ec; + // Fall through to retry operation. + } +#endif // defined(EPROTO) + else + return ec; + + // Wait for socket to become ready. + if (socket_ops::poll_read(impl.socket_, ec) < 0) + return ec; + } + } + + template + class accept_handler + { + public: + accept_handler(socket_type socket, asio::io_service& io_service, + Socket& peer, const protocol_type& protocol, + endpoint_type* peer_endpoint, bool enable_connection_aborted, + Handler handler) + : socket_(socket), + io_service_(io_service), + work_(io_service), + peer_(peer), + protocol_(protocol), + peer_endpoint_(peer_endpoint), + enable_connection_aborted_(enable_connection_aborted), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether the operation was successful. + if (result) + { + io_service_.post(bind_handler(handler_, result)); + return true; + } + + // Accept the waiting connection. + asio::error_code ec; + socket_holder new_socket; + socket_addr_len_type addr_len = 0; + if (peer_endpoint_) + { + addr_len = peer_endpoint_->capacity(); + new_socket.reset(socket_ops::accept(socket_, + peer_endpoint_->data(), &addr_len, ec)); + } + else + { + new_socket.reset(socket_ops::accept(socket_, 0, 0, ec)); + } + + // Check if we need to run the operation again. + if (ec == asio::error::would_block + || ec == asio::error::try_again) + return false; + if (ec == asio::error::connection_aborted + && !enable_connection_aborted_) + return false; +#if defined(EPROTO) + if (ec.value() == EPROTO && !enable_connection_aborted_) + return false; +#endif // defined(EPROTO) + + // Transfer ownership of the new socket to the peer object. + if (!ec) + { + if (peer_endpoint_) + peer_endpoint_->resize(addr_len); + peer_.assign(protocol_, new_socket.get(), ec); + if (!ec) + new_socket.release(); + } + + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + private: + socket_type socket_; + asio::io_service& io_service_; + asio::io_service::work work_; + Socket& peer_; + protocol_type protocol_; + endpoint_type* peer_endpoint_; + bool enable_connection_aborted_; + Handler handler_; + }; + + // Start an asynchronous accept. The peer and peer_endpoint objects + // must be valid until the accept's handler is invoked. + template + void async_accept(implementation_type& impl, Socket& peer, + endpoint_type* peer_endpoint, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor)); + } + else if (peer.is_open()) + { + this->io_service().post(bind_handler(handler, + asio::error::already_open)); + } + else + { + // Make socket non-blocking. + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec)); + return; + } + impl.flags_ |= implementation_type::internal_non_blocking; + } + + reactor_.start_read_op(impl.socket_, + accept_handler( + impl.socket_, this->io_service(), + peer, impl.protocol_, peer_endpoint, + (impl.flags_ & implementation_type::enable_connection_aborted) != 0, + handler)); + } + } + + // Connect the socket to the specified endpoint. + asio::error_code connect(implementation_type& impl, + const endpoint_type& peer_endpoint, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + if (impl.flags_ & implementation_type::internal_non_blocking) + { + // Mark the socket as blocking while we perform the connect. + ioctl_arg_type non_blocking = 0; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + return ec; + impl.flags_ &= ~implementation_type::internal_non_blocking; + } + + // Perform the connect operation. + socket_ops::connect(impl.socket_, + peer_endpoint.data(), peer_endpoint.size(), ec); + return ec; + } + + template + class connect_handler + { + public: + connect_handler(socket_type socket, boost::shared_ptr completed, + asio::io_service& io_service, Reactor& reactor, Handler handler) + : socket_(socket), + completed_(completed), + io_service_(io_service), + work_(io_service), + reactor_(reactor), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether a handler has already been called for the connection. + // If it has, then we don't want to do anything in this handler. + if (*completed_) + return true; + + // Cancel the other reactor operation for the connection. + *completed_ = true; + reactor_.enqueue_cancel_ops_unlocked(socket_); + + // Check whether the operation was successful. + if (result) + { + io_service_.post(bind_handler(handler_, result)); + return true; + } + + // Get the error code from the connect operation. + int connect_error = 0; + size_t connect_error_len = sizeof(connect_error); + asio::error_code ec; + if (socket_ops::getsockopt(socket_, SOL_SOCKET, SO_ERROR, + &connect_error, &connect_error_len, ec) == socket_error_retval) + { + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + // If connection failed then post the handler with the error code. + if (connect_error) + { + ec = asio::error_code(connect_error, + asio::native_ecat); + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + // Post the result of the successful connection operation. + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + private: + socket_type socket_; + boost::shared_ptr completed_; + asio::io_service& io_service_; + asio::io_service::work work_; + Reactor& reactor_; + Handler handler_; + }; + + // Start an asynchronous connect. + template + void async_connect(implementation_type& impl, + const endpoint_type& peer_endpoint, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor)); + return; + } + + // Make socket non-blocking. + if (!(impl.flags_ & implementation_type::internal_non_blocking)) + { + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec)); + return; + } + impl.flags_ |= implementation_type::internal_non_blocking; + } + + // Start the connect operation. The socket is already marked as non-blocking + // so the connection will take place asynchronously. + asio::error_code ec; + if (socket_ops::connect(impl.socket_, peer_endpoint.data(), + peer_endpoint.size(), ec) == 0) + { + // The connect operation has finished successfully so we need to post the + // handler immediately. + this->io_service().post(bind_handler(handler, + asio::error_code())); + } + else if (ec == asio::error::in_progress + || ec == asio::error::would_block) + { + // The connection is happening in the background, and we need to wait + // until the socket becomes writeable. + boost::shared_ptr completed(new bool(false)); + reactor_.start_write_and_except_ops(impl.socket_, + connect_handler( + impl.socket_, completed, this->io_service(), reactor_, handler)); + } + else + { + // The connect operation has failed, so post the handler immediately. + this->io_service().post(bind_handler(handler, ec)); + } + } + +private: + // The selector that performs event demultiplexing for the service. + Reactor& reactor_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_REACTIVE_SOCKET_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/reactor_op_queue.hpp b/libtorrent/include/asio/detail/reactor_op_queue.hpp new file mode 100644 index 000000000..b2d1054c6 --- /dev/null +++ b/libtorrent/include/asio/detail/reactor_op_queue.hpp @@ -0,0 +1,384 @@ +// +// reactor_op_queue.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_REACTOR_OP_QUEUE_HPP +#define ASIO_DETAIL_REACTOR_OP_QUEUE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/hash_map.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +template +class reactor_op_queue + : private noncopyable +{ +public: + // Constructor. + reactor_op_queue() + : operations_(), + cancelled_operations_(0), + cleanup_operations_(0) + { + } + + // Add a new operation to the queue. Returns true if this is the only + // operation for the given descriptor, in which case the reactor's event + // demultiplexing function call may need to be interrupted and restarted. + template + bool enqueue_operation(Descriptor descriptor, Handler handler) + { + op_base* new_op = new op(descriptor, handler); + + typedef typename operation_map::iterator iterator; + typedef typename operation_map::value_type value_type; + std::pair entry = + operations_.insert(value_type(descriptor, new_op)); + if (entry.second) + return true; + + op_base* current_op = entry.first->second; + while (current_op->next_) + current_op = current_op->next_; + current_op->next_ = new_op; + + return false; + } + + // Cancel all operations associated with the descriptor. Any operations + // pending for the descriptor will be notified that they have been cancelled + // next time dispatch_cancellations is called. Returns true if any operations + // were cancelled, in which case the reactor's event demultiplexing function + // may need to be interrupted and restarted. + bool cancel_operations(Descriptor descriptor) + { + typename operation_map::iterator i = operations_.find(descriptor); + if (i != operations_.end()) + { + op_base* last_op = i->second; + while (last_op->next_) + last_op = last_op->next_; + last_op->next_ = cancelled_operations_; + cancelled_operations_ = i->second; + operations_.erase(i); + return true; + } + + return false; + } + + // Whether there are no operations in the queue. + bool empty() const + { + return operations_.empty(); + } + + // Determine whether there are any operations associated with the descriptor. + bool has_operation(Descriptor descriptor) const + { + return operations_.find(descriptor) != operations_.end(); + } + + // Dispatch the first operation corresponding to the descriptor. Returns true + // if there are more operations queued for the descriptor. + bool dispatch_operation(Descriptor descriptor, + const asio::error_code& result) + { + typename operation_map::iterator i = operations_.find(descriptor); + if (i != operations_.end()) + { + op_base* this_op = i->second; + i->second = this_op->next_; + this_op->next_ = cleanup_operations_; + cleanup_operations_ = this_op; + bool done = this_op->invoke(result); + if (done) + { + // Operation has finished. + if (i->second) + { + return true; + } + else + { + operations_.erase(i); + return false; + } + } + else + { + // Operation wants to be called again. Leave it at the front of the + // queue for this descriptor, and remove from the cleanup list. + cleanup_operations_ = this_op->next_; + this_op->next_ = i->second; + i->second = this_op; + return true; + } + } + return false; + } + + // Dispatch all operations corresponding to the descriptor. + void dispatch_all_operations(Descriptor descriptor, + const asio::error_code& result) + { + typename operation_map::iterator i = operations_.find(descriptor); + if (i != operations_.end()) + { + while (i->second) + { + op_base* this_op = i->second; + i->second = this_op->next_; + this_op->next_ = cleanup_operations_; + cleanup_operations_ = this_op; + bool done = this_op->invoke(result); + if (!done) + { + // Operation has not finished yet, so leave at front of queue, and + // remove from the cleanup list. + cleanup_operations_ = this_op->next_; + this_op->next_ = i->second; + i->second = this_op; + return; + } + } + operations_.erase(i); + } + } + + // Fill a descriptor set with the descriptors corresponding to each active + // operation. + template + void get_descriptors(Descriptor_Set& descriptors) + { + typename operation_map::iterator i = operations_.begin(); + while (i != operations_.end()) + { + descriptors.set(i->first); + ++i; + } + } + + // Dispatch the operations corresponding to the ready file descriptors + // contained in the given descriptor set. + template + void dispatch_descriptors(const Descriptor_Set& descriptors, + const asio::error_code& result) + { + typename operation_map::iterator i = operations_.begin(); + while (i != operations_.end()) + { + typename operation_map::iterator op_iter = i++; + if (descriptors.is_set(op_iter->first)) + { + op_base* this_op = op_iter->second; + op_iter->second = this_op->next_; + this_op->next_ = cleanup_operations_; + cleanup_operations_ = this_op; + bool done = this_op->invoke(result); + if (done) + { + if (!op_iter->second) + operations_.erase(op_iter); + } + else + { + // Operation has not finished yet, so leave at front of queue, and + // remove from the cleanup list. + cleanup_operations_ = this_op->next_; + this_op->next_ = op_iter->second; + op_iter->second = this_op; + } + } + } + } + + // Dispatch any pending cancels for operations. + void dispatch_cancellations() + { + while (cancelled_operations_) + { + op_base* this_op = cancelled_operations_; + cancelled_operations_ = this_op->next_; + this_op->next_ = cleanup_operations_; + cleanup_operations_ = this_op; + this_op->invoke(asio::error::operation_aborted); + } + } + + // Destroy operations that are waiting to be cleaned up. + void cleanup_operations() + { + while (cleanup_operations_) + { + op_base* next_op = cleanup_operations_->next_; + cleanup_operations_->next_ = 0; + cleanup_operations_->destroy(); + cleanup_operations_ = next_op; + } + } + + // Destroy all operations owned by the queue. + void destroy_operations() + { + while (cancelled_operations_) + { + op_base* next_op = cancelled_operations_->next_; + cancelled_operations_->next_ = 0; + cancelled_operations_->destroy(); + cancelled_operations_ = next_op; + } + + while (cleanup_operations_) + { + op_base* next_op = cleanup_operations_->next_; + cleanup_operations_->next_ = 0; + cleanup_operations_->destroy(); + cleanup_operations_ = next_op; + } + + typename operation_map::iterator i = operations_.begin(); + while (i != operations_.end()) + { + typename operation_map::iterator op_iter = i++; + op_base* curr_op = op_iter->second; + operations_.erase(op_iter); + while (curr_op) + { + op_base* next_op = curr_op->next_; + curr_op->next_ = 0; + curr_op->destroy(); + curr_op = next_op; + } + } + } + +private: + // Base class for reactor operations. A function pointer is used instead of + // virtual functions to avoid the associated overhead. + class op_base + { + public: + // Get the descriptor associated with the operation. + Descriptor descriptor() const + { + return descriptor_; + } + + // Perform the operation. + bool invoke(const asio::error_code& result) + { + return invoke_func_(this, result); + } + + // Destroy the operation. + void destroy() + { + return destroy_func_(this); + } + + protected: + typedef bool (*invoke_func_type)(op_base*, + const asio::error_code&); + typedef void (*destroy_func_type)(op_base*); + + // Construct an operation for the given descriptor. + op_base(invoke_func_type invoke_func, + destroy_func_type destroy_func, Descriptor descriptor) + : invoke_func_(invoke_func), + destroy_func_(destroy_func), + descriptor_(descriptor), + next_(0) + { + } + + // Prevent deletion through this type. + ~op_base() + { + } + + private: + friend class reactor_op_queue; + + // The function to be called to dispatch the handler. + invoke_func_type invoke_func_; + + // The function to be called to delete the handler. + destroy_func_type destroy_func_; + + // The descriptor associated with the operation. + Descriptor descriptor_; + + // The next operation for the same file descriptor. + op_base* next_; + }; + + // Adaptor class template for using handlers in operations. + template + class op + : public op_base + { + public: + // Constructor. + op(Descriptor descriptor, Handler handler) + : op_base(&op::invoke_handler, + &op::destroy_handler, descriptor), + handler_(handler) + { + } + + // Invoke the handler. + static bool invoke_handler(op_base* base, + const asio::error_code& result) + { + return static_cast*>(base)->handler_(result); + } + + // Delete the handler. + static void destroy_handler(op_base* base) + { + delete static_cast*>(base); + } + + private: + Handler handler_; + }; + + // The type for a map of operations. + typedef hash_map operation_map; + + // The operations that are currently executing asynchronously. + operation_map operations_; + + // The list of operations that have been cancelled. + op_base* cancelled_operations_; + + // The list of operations to be destroyed. + op_base* cleanup_operations_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_REACTOR_OP_QUEUE_HPP diff --git a/libtorrent/include/asio/detail/resolver_service.hpp b/libtorrent/include/asio/detail/resolver_service.hpp new file mode 100644 index 000000000..c820b75f3 --- /dev/null +++ b/libtorrent/include/asio/detail/resolver_service.hpp @@ -0,0 +1,357 @@ +// +// resolver_service.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_RESOLVER_SERVICE_HPP +#define ASIO_DETAIL_RESOLVER_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/thread.hpp" + +namespace asio { +namespace detail { + +template +class resolver_service + : public asio::detail::service_base > +{ +private: + // Helper class to perform exception-safe cleanup of addrinfo objects. + class auto_addrinfo + : private asio::detail::noncopyable + { + public: + explicit auto_addrinfo(asio::detail::addrinfo_type* ai) + : ai_(ai) + { + } + + ~auto_addrinfo() + { + if (ai_) + socket_ops::freeaddrinfo(ai_); + } + + operator asio::detail::addrinfo_type*() + { + return ai_; + } + + private: + asio::detail::addrinfo_type* ai_; + }; + +public: + // The implementation type of the resolver. The shared pointer is used as a + // cancellation token to indicate to the background thread that the operation + // has been cancelled. + typedef boost::shared_ptr implementation_type; + struct noop_deleter { void operator()(void*) {} }; + + // The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + // The query type. + typedef typename Protocol::resolver_query query_type; + + // The iterator type. + typedef typename Protocol::resolver_iterator iterator_type; + + // Constructor. + resolver_service(asio::io_service& io_service) + : asio::detail::service_base< + resolver_service >(io_service), + mutex_(), + work_io_service_(new asio::io_service), + work_(new asio::io_service::work(*work_io_service_)), + work_thread_(0) + { + } + + // Destructor. + ~resolver_service() + { + shutdown_service(); + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + work_.reset(); + if (work_io_service_) + { + work_io_service_->stop(); + if (work_thread_) + { + work_thread_->join(); + work_thread_.reset(); + } + work_io_service_.reset(); + } + } + + // Construct a new resolver implementation. + void construct(implementation_type& impl) + { + impl.reset(static_cast(0), noop_deleter()); + } + + // Destroy a resolver implementation. + void destroy(implementation_type&) + { + } + + // Cancel pending asynchronous operations. + void cancel(implementation_type& impl) + { + impl.reset(static_cast(0), noop_deleter()); + } + + // Resolve a query to a list of entries. + iterator_type resolve(implementation_type&, const query_type& query, + asio::error_code& ec) + { + asio::detail::addrinfo_type* address_info = 0; + std::string host_name = query.host_name(); + std::string service_name = query.service_name(); + asio::detail::addrinfo_type hints = query.hints(); + + socket_ops::getaddrinfo(host_name.length() ? host_name.c_str() : 0, + service_name.c_str(), &hints, &address_info, ec); + auto_addrinfo auto_address_info(address_info); + + if (ec) + return iterator_type(); + + return iterator_type::create(address_info, host_name, service_name); + } + + template + class resolve_query_handler + { + public: + resolve_query_handler(implementation_type impl, const query_type& query, + asio::io_service& io_service, Handler handler) + : impl_(impl), + query_(query), + io_service_(io_service), + work_(io_service), + handler_(handler) + { + } + + void operator()() + { + // Check if the operation has been cancelled. + if (impl_.expired()) + { + iterator_type iterator; + io_service_.post(asio::detail::bind_handler(handler_, + asio::error::operation_aborted, iterator)); + return; + } + + // Perform the blocking host resolution operation. + asio::detail::addrinfo_type* address_info = 0; + std::string host_name = query_.host_name(); + std::string service_name = query_.service_name(); + asio::detail::addrinfo_type hints = query_.hints(); + asio::error_code ec; + socket_ops::getaddrinfo(host_name.length() ? host_name.c_str() : 0, + service_name.c_str(), &hints, &address_info, ec); + auto_addrinfo auto_address_info(address_info); + + // Invoke the handler and pass the result. + iterator_type iterator; + if (!ec) + iterator = iterator_type::create(address_info, host_name, service_name); + io_service_.post(asio::detail::bind_handler( + handler_, ec, iterator)); + } + + private: + boost::weak_ptr impl_; + query_type query_; + asio::io_service& io_service_; + asio::io_service::work work_; + Handler handler_; + }; + + // Asynchronously resolve a query to a list of entries. + template + void async_resolve(implementation_type& impl, const query_type& query, + Handler handler) + { + if (work_io_service_) + { + start_work_thread(); + work_io_service_->post( + resolve_query_handler( + impl, query, this->io_service(), handler)); + } + } + + // Resolve an endpoint to a list of entries. + iterator_type resolve(implementation_type&, + const endpoint_type& endpoint, asio::error_code& ec) + { + // First try resolving with the service name. If that fails try resolving + // but allow the service to be returned as a number. + char host_name[NI_MAXHOST]; + char service_name[NI_MAXSERV]; + int flags = endpoint.protocol().type() == SOCK_DGRAM ? NI_DGRAM : 0; + socket_ops::getnameinfo(endpoint.data(), endpoint.size(), + host_name, NI_MAXHOST, service_name, NI_MAXSERV, flags, ec); + if (ec) + { + flags |= NI_NUMERICSERV; + socket_ops::getnameinfo(endpoint.data(), endpoint.size(), + host_name, NI_MAXHOST, service_name, NI_MAXSERV, flags, ec); + } + + if (ec) + return iterator_type(); + + return iterator_type::create(endpoint, host_name, service_name); + } + + template + class resolve_endpoint_handler + { + public: + resolve_endpoint_handler(implementation_type impl, + const endpoint_type& endpoint, asio::io_service& io_service, + Handler handler) + : impl_(impl), + endpoint_(endpoint), + io_service_(io_service), + work_(io_service), + handler_(handler) + { + } + + void operator()() + { + // Check if the operation has been cancelled. + if (impl_.expired()) + { + iterator_type iterator; + io_service_.post(asio::detail::bind_handler(handler_, + asio::error::operation_aborted, iterator)); + return; + } + + + // First try resolving with the service name. If that fails try resolving + // but allow the service to be returned as a number. + char host_name[NI_MAXHOST]; + char service_name[NI_MAXSERV]; + int flags = endpoint_.protocol().type() == SOCK_DGRAM ? NI_DGRAM : 0; + asio::error_code ec; + socket_ops::getnameinfo(endpoint_.data(), endpoint_.size(), + host_name, NI_MAXHOST, service_name, NI_MAXSERV, flags, ec); + if (ec) + { + flags |= NI_NUMERICSERV; + socket_ops::getnameinfo(endpoint_.data(), endpoint_.size(), + host_name, NI_MAXHOST, service_name, NI_MAXSERV, flags, ec); + } + + // Invoke the handler and pass the result. + iterator_type iterator; + if (!ec) + iterator = iterator_type::create(endpoint_, host_name, service_name); + io_service_.post(asio::detail::bind_handler( + handler_, ec, iterator)); + } + + private: + boost::weak_ptr impl_; + endpoint_type endpoint_; + asio::io_service& io_service_; + asio::io_service::work work_; + Handler handler_; + }; + + // Asynchronously resolve an endpoint to a list of entries. + template + void async_resolve(implementation_type& impl, const endpoint_type& endpoint, + Handler handler) + { + if (work_io_service_) + { + start_work_thread(); + work_io_service_->post( + resolve_endpoint_handler( + impl, endpoint, this->io_service(), handler)); + } + } + +private: + // Helper class to run the work io_service in a thread. + class work_io_service_runner + { + public: + work_io_service_runner(asio::io_service& io_service) + : io_service_(io_service) {} + void operator()() { io_service_.run(); } + private: + asio::io_service& io_service_; + }; + + // Start the work thread if it's not already running. + void start_work_thread() + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (work_thread_ == 0) + { + work_thread_.reset(new asio::detail::thread( + work_io_service_runner(*work_io_service_))); + } + } + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // Private io_service used for performing asynchronous host resolution. + boost::scoped_ptr work_io_service_; + + // Work for the private io_service to perform. + boost::scoped_ptr work_; + + // Thread used for running the work io_service's run loop. + boost::scoped_ptr work_thread_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_RESOLVER_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/scoped_lock.hpp b/libtorrent/include/asio/detail/scoped_lock.hpp new file mode 100644 index 000000000..64c77cbab --- /dev/null +++ b/libtorrent/include/asio/detail/scoped_lock.hpp @@ -0,0 +1,79 @@ +// +// scoped_lock.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SCOPED_LOCK_HPP +#define ASIO_DETAIL_SCOPED_LOCK_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +// Helper class to lock and unlock a mutex automatically. +template +class scoped_lock + : private noncopyable +{ +public: + // Constructor acquires the lock. + scoped_lock(Mutex& m) + : mutex_(m) + { + mutex_.lock(); + locked_ = true; + } + + // Destructor releases the lock. + ~scoped_lock() + { + if (locked_) + mutex_.unlock(); + } + + // Explicitly acquire the lock. + void lock() + { + if (!locked_) + { + mutex_.lock(); + locked_ = true; + } + } + + // Explicitly release the lock. + void unlock() + { + if (locked_) + { + mutex_.unlock(); + locked_ = false; + } + } + +private: + // The underlying mutex. + Mutex& mutex_; + + // Whether the mutex is currently locked or unlocked. + bool locked_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SCOPED_LOCK_HPP diff --git a/libtorrent/include/asio/detail/select_interrupter.hpp b/libtorrent/include/asio/detail/select_interrupter.hpp new file mode 100644 index 000000000..de7fc9611 --- /dev/null +++ b/libtorrent/include/asio/detail/select_interrupter.hpp @@ -0,0 +1,41 @@ +// +// select_interrupter.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SELECT_INTERRUPTER_HPP +#define ASIO_DETAIL_SELECT_INTERRUPTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/pipe_select_interrupter.hpp" +#include "asio/detail/socket_select_interrupter.hpp" + +namespace asio { +namespace detail { + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +typedef socket_select_interrupter select_interrupter; +#else +typedef pipe_select_interrupter select_interrupter; +#endif + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SELECT_INTERRUPTER_HPP diff --git a/libtorrent/include/asio/detail/select_reactor.hpp b/libtorrent/include/asio/detail/select_reactor.hpp new file mode 100644 index 000000000..83f093ae6 --- /dev/null +++ b/libtorrent/include/asio/detail/select_reactor.hpp @@ -0,0 +1,456 @@ +// +// select_reactor.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SELECT_REACTOR_HPP +#define ASIO_DETAIL_SELECT_REACTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_types.hpp" // Must come before posix_time. + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/fd_set_adapter.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/reactor_op_queue.hpp" +#include "asio/detail/select_interrupter.hpp" +#include "asio/detail/select_reactor_fwd.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/signal_blocker.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/task_io_service.hpp" +#include "asio/detail/thread.hpp" +#include "asio/detail/timer_queue.hpp" + +namespace asio { +namespace detail { + +template +class select_reactor + : public asio::detail::service_base > +{ +public: + // Constructor. + select_reactor(asio::io_service& io_service) + : asio::detail::service_base< + select_reactor >(io_service), + mutex_(), + select_in_progress_(false), + interrupter_(), + read_op_queue_(), + write_op_queue_(), + except_op_queue_(), + pending_cancellations_(), + stop_thread_(false), + thread_(0), + shutdown_(false) + { + if (Own_Thread) + { + asio::detail::signal_blocker sb; + thread_ = new asio::detail::thread( + bind_handler(&select_reactor::call_run_thread, this)); + } + } + + // Destructor. + ~select_reactor() + { + shutdown_service(); + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + asio::detail::mutex::scoped_lock lock(mutex_); + shutdown_ = true; + stop_thread_ = true; + lock.unlock(); + + if (thread_) + { + interrupter_.interrupt(); + thread_->join(); + delete thread_; + thread_ = 0; + } + + read_op_queue_.destroy_operations(); + write_op_queue_.destroy_operations(); + except_op_queue_.destroy_operations(); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->destroy_timers(); + timer_queues_.clear(); + } + + // Register a socket with the reactor. Returns 0 on success, system error + // code on failure. + int register_descriptor(socket_type descriptor) + { + return 0; + } + + // Start a new read operation. The handler object will be invoked when the + // given descriptor is ready to be read, or an error has occurred. + template + void start_read_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (read_op_queue_.enqueue_operation(descriptor, handler)) + interrupter_.interrupt(); + } + + // Start a new write operation. The handler object will be invoked when the + // given descriptor is ready to be written, or an error has occurred. + template + void start_write_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (write_op_queue_.enqueue_operation(descriptor, handler)) + interrupter_.interrupt(); + } + + // Start a new exception operation. The handler object will be invoked when + // the given descriptor has exception information, or an error has occurred. + template + void start_except_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (except_op_queue_.enqueue_operation(descriptor, handler)) + interrupter_.interrupt(); + } + + // Start new write and exception operations. The handler object will be + // invoked when the given descriptor is ready for writing or has exception + // information available, or an error has occurred. + template + void start_write_and_except_ops(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + { + bool interrupt = write_op_queue_.enqueue_operation(descriptor, handler); + interrupt = except_op_queue_.enqueue_operation(descriptor, handler) + || interrupt; + if (interrupt) + interrupter_.interrupt(); + } + } + + // Cancel all operations associated with the given descriptor. The + // handlers associated with the descriptor will be invoked with the + // operation_aborted error. + void cancel_ops(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + cancel_ops_unlocked(descriptor); + } + + // Enqueue cancellation of all operations associated with the given + // descriptor. The handlers associated with the descriptor will be invoked + // with the operation_aborted error. This function does not acquire the + // select_reactor's mutex, and so should only be used from within a reactor + // handler. + void enqueue_cancel_ops_unlocked(socket_type descriptor) + { + pending_cancellations_.push_back(descriptor); + } + + // Cancel any operations that are running against the descriptor and remove + // its registration from the reactor. + void close_descriptor(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + cancel_ops_unlocked(descriptor); + } + + // Add a new timer queue to the reactor. + template + void add_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + timer_queues_.push_back(&timer_queue); + } + + // Remove a timer queue from the reactor. + template + void remove_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + if (timer_queues_[i] == &timer_queue) + { + timer_queues_.erase(timer_queues_.begin() + i); + return; + } + } + } + + // Schedule a timer in the given timer queue to expire at the specified + // absolute time. The handler object will be invoked when the timer expires. + template + void schedule_timer(timer_queue& timer_queue, + const typename Time_Traits::time_type& time, Handler handler, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (timer_queue.enqueue_timer(time, handler, token)) + interrupter_.interrupt(); + } + + // Cancel the timer associated with the given token. Returns the number of + // handlers that have been posted or dispatched. + template + std::size_t cancel_timer(timer_queue& timer_queue, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + return timer_queue.cancel_timer(token); + } + +private: + friend class task_io_service >; + + // Run select once until interrupted or events are ready to be dispatched. + void run(bool block) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Dispatch any operation cancellations that were made while the select + // loop was not running. + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + + // Check if the thread is supposed to stop. + if (stop_thread_) + { + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + return; + } + + // We can return immediately if there's no work to do and the reactor is + // not supposed to block. + if (!block && read_op_queue_.empty() && write_op_queue_.empty() + && except_op_queue_.empty() && all_timer_queues_are_empty()) + { + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + return; + } + + // Set up the descriptor sets. + fd_set_adapter read_fds; + read_fds.set(interrupter_.read_descriptor()); + read_op_queue_.get_descriptors(read_fds); + fd_set_adapter write_fds; + write_op_queue_.get_descriptors(write_fds); + fd_set_adapter except_fds; + except_op_queue_.get_descriptors(except_fds); + socket_type max_fd = read_fds.max_descriptor(); + if (write_fds.max_descriptor() > max_fd) + max_fd = write_fds.max_descriptor(); + if (except_fds.max_descriptor() > max_fd) + max_fd = except_fds.max_descriptor(); + + // Block on the select call without holding the lock so that new + // operations can be started while the call is executing. + timeval tv_buf = { 0, 0 }; + timeval* tv = block ? get_timeout(tv_buf) : &tv_buf; + select_in_progress_ = true; + lock.unlock(); + asio::error_code ec; + int retval = socket_ops::select(static_cast(max_fd + 1), + read_fds, write_fds, except_fds, tv, ec); + lock.lock(); + select_in_progress_ = false; + + // Block signals while dispatching operations. + asio::detail::signal_blocker sb; + + // Reset the interrupter. + if (retval > 0 && read_fds.is_set(interrupter_.read_descriptor())) + interrupter_.reset(); + + // Dispatch all ready operations. + if (retval > 0) + { + // Exception operations must be processed first to ensure that any + // out-of-band data is read before normal data. + except_op_queue_.dispatch_descriptors(except_fds, + asio::error_code()); + read_op_queue_.dispatch_descriptors(read_fds, + asio::error_code()); + write_op_queue_.dispatch_descriptors(write_fds, + asio::error_code()); + except_op_queue_.dispatch_cancellations(); + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + } + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_timers(); + + // Issue any pending cancellations. + for (size_t i = 0; i < pending_cancellations_.size(); ++i) + cancel_ops_unlocked(pending_cancellations_[i]); + pending_cancellations_.clear(); + + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + } + + // Run the select loop in the thread. + void run_thread() + { + asio::detail::mutex::scoped_lock lock(mutex_); + while (!stop_thread_) + { + lock.unlock(); + run(true); + lock.lock(); + } + } + + // Entry point for the select loop thread. + static void call_run_thread(select_reactor* reactor) + { + reactor->run_thread(); + } + + // Interrupt the select loop. + void interrupt() + { + interrupter_.interrupt(); + } + + // Check if all timer queues are empty. + bool all_timer_queues_are_empty() const + { + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + if (!timer_queues_[i]->empty()) + return false; + return true; + } + + // Get the timeout value for the select call. + timeval* get_timeout(timeval& tv) + { + if (all_timer_queues_are_empty()) + return 0; + + // By default we will wait no longer than 5 minutes. This will ensure that + // any changes to the system clock are detected after no longer than this. + boost::posix_time::time_duration minimum_wait_duration + = boost::posix_time::minutes(5); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + boost::posix_time::time_duration wait_duration + = timer_queues_[i]->wait_duration(); + if (wait_duration < minimum_wait_duration) + minimum_wait_duration = wait_duration; + } + + if (minimum_wait_duration > boost::posix_time::time_duration()) + { + tv.tv_sec = minimum_wait_duration.total_seconds(); + tv.tv_usec = minimum_wait_duration.total_microseconds() % 1000000; + } + else + { + tv.tv_sec = 0; + tv.tv_usec = 0; + } + + return &tv; + } + + // Cancel all operations associated with the given descriptor. The do_cancel + // function of the handler objects will be invoked. This function does not + // acquire the select_reactor's mutex. + void cancel_ops_unlocked(socket_type descriptor) + { + bool interrupt = read_op_queue_.cancel_operations(descriptor); + interrupt = write_op_queue_.cancel_operations(descriptor) || interrupt; + interrupt = except_op_queue_.cancel_operations(descriptor) || interrupt; + if (interrupt) + interrupter_.interrupt(); + } + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // Whether the select loop is currently running or not. + bool select_in_progress_; + + // The interrupter is used to break a blocking select call. + select_interrupter interrupter_; + + // The queue of read operations. + reactor_op_queue read_op_queue_; + + // The queue of write operations. + reactor_op_queue write_op_queue_; + + // The queue of exception operations. + reactor_op_queue except_op_queue_; + + // The timer queues. + std::vector timer_queues_; + + // The descriptors that are pending cancellation. + std::vector pending_cancellations_; + + // Does the reactor loop thread need to stop. + bool stop_thread_; + + // The thread that is running the reactor loop. + asio::detail::thread* thread_; + + // Whether the service has been shut down. + bool shutdown_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SELECT_REACTOR_HPP diff --git a/libtorrent/include/asio/detail/select_reactor_fwd.hpp b/libtorrent/include/asio/detail/select_reactor_fwd.hpp new file mode 100644 index 000000000..3e1687eae --- /dev/null +++ b/libtorrent/include/asio/detail/select_reactor_fwd.hpp @@ -0,0 +1,31 @@ +// +// select_reactor_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SELECT_REACTOR_FWD_HPP +#define ASIO_DETAIL_SELECT_REACTOR_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace detail { + +template +class select_reactor; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SELECT_REACTOR_FWD_HPP diff --git a/libtorrent/include/asio/detail/service_base.hpp b/libtorrent/include/asio/detail/service_base.hpp new file mode 100644 index 000000000..4f375c921 --- /dev/null +++ b/libtorrent/include/asio/detail/service_base.hpp @@ -0,0 +1,49 @@ +// +// service_base.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SERVICE_BASE_HPP +#define ASIO_DETAIL_SERVICE_BASE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/service_id.hpp" + +namespace asio { +namespace detail { + +// Special service base class to keep classes header-file only. +template +class service_base + : public asio::io_service::service +{ +public: + static asio::detail::service_id id; + + // Constructor. + service_base(asio::io_service& io_service) + : asio::io_service::service(io_service) + { + } +}; + +template +asio::detail::service_id service_base::id; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SERVICE_BASE_HPP diff --git a/libtorrent/include/asio/detail/service_id.hpp b/libtorrent/include/asio/detail/service_id.hpp new file mode 100644 index 000000000..8ff19097b --- /dev/null +++ b/libtorrent/include/asio/detail/service_id.hpp @@ -0,0 +1,37 @@ +// +// service_id.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SERVICE_ID_HPP +#define ASIO_DETAIL_SERVICE_ID_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/io_service.hpp" + +namespace asio { +namespace detail { + +// Special derived service id type to keep classes header-file only. +template +class service_id + : public asio::io_service::id +{ +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SERVICE_ID_HPP diff --git a/libtorrent/include/asio/detail/service_registry.hpp b/libtorrent/include/asio/detail/service_registry.hpp new file mode 100644 index 000000000..bd1c3ea5b --- /dev/null +++ b/libtorrent/include/asio/detail/service_registry.hpp @@ -0,0 +1,198 @@ +// +// service_registry.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SERVICE_REGISTRY_HPP +#define ASIO_DETAIL_SERVICE_REGISTRY_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/service_id.hpp" + +namespace asio { +namespace detail { + +class service_registry + : private noncopyable +{ +public: + // Constructor. + service_registry(asio::io_service& o) + : owner_(o), + first_service_(0) + { + } + + // Destructor. + ~service_registry() + { + // Shutdown all services. This must be done in a separate loop before the + // services are destroyed since the destructors of user-defined handler + // objects may try to access other service objects. + asio::io_service::service* service = first_service_; + while (service) + { + service->shutdown_service(); + service = service->next_; + } + + // Destroy all services. + while (first_service_) + { + asio::io_service::service* next_service = first_service_->next_; + delete first_service_; + first_service_ = next_service; + } + } + + // Get the service object corresponding to the specified service type. Will + // create a new service object automatically if no such object already + // exists. Ownership of the service object is not transferred to the caller. + template + Service& use_service() + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // First see if there is an existing service object for the given type. + asio::io_service::service* service = first_service_; + while (service) + { + if (service_id_matches(*service, Service::id)) + return *static_cast(service); + service = service->next_; + } + + // Create a new service object. The service registry's mutex is not locked + // at this time to allow for nested calls into this function from the new + // service's constructor. + lock.unlock(); + std::auto_ptr new_service(new Service(owner_)); + init_service_id(*new_service, Service::id); + Service& new_service_ref = *new_service; + lock.lock(); + + // Check that nobody else created another service object of the same type + // while the lock was released. + service = first_service_; + while (service) + { + if (service_id_matches(*service, Service::id)) + return *static_cast(service); + service = service->next_; + } + + // Service was successfully initialised, pass ownership to registry. + new_service->next_ = first_service_; + first_service_ = new_service.release(); + + return new_service_ref; + } + + // Add a service object. Returns false on error, in which case ownership of + // the object is retained by the caller. + template + bool add_service(Service* new_service) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Check if there is an existing service object for the given type. + asio::io_service::service* service = first_service_; + while (service) + { + if (service_id_matches(*service, Service::id)) + return false; + service = service->next_; + } + + // Take ownership of the service object. + init_service_id(*new_service, Service::id); + new_service->next_ = first_service_; + first_service_ = new_service; + + return true; + } + + // Check whether a service object of the specified type already exists. + template + bool has_service() const + { + asio::detail::mutex::scoped_lock lock(mutex_); + + asio::io_service::service* service = first_service_; + while (service) + { + if (service_id_matches(*service, Service::id)) + return true; + service = service->next_; + } + + return false; + } + +private: + // Set a service's id. + void init_service_id(asio::io_service::service& service, + const asio::io_service::id& id) + { + service.type_info_ = 0; + service.id_ = &id; + } + + // Set a service's id. + template + void init_service_id(asio::io_service::service& service, + const asio::detail::service_id& /*id*/) + { + service.type_info_ = &typeid(Service); + service.id_ = 0; + } + + // Check if a service matches the given id. + bool service_id_matches(const asio::io_service::service& service, + const asio::io_service::id& id) + { + return service.id_ == &id; + } + + // Check if a service matches the given id. + template + bool service_id_matches(const asio::io_service::service& service, + const asio::detail::service_id& /*id*/) + { + return service.type_info_ != 0 && *service.type_info_ == typeid(Service); + } + + // Mutex to protect access to internal data. + mutable asio::detail::mutex mutex_; + + // The owner of this service registry and the services it contains. + asio::io_service& owner_; + + // The first service in the list of contained services. + asio::io_service::service* first_service_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SERVICE_REGISTRY_HPP diff --git a/libtorrent/include/asio/detail/service_registry_fwd.hpp b/libtorrent/include/asio/detail/service_registry_fwd.hpp new file mode 100644 index 000000000..596c24be3 --- /dev/null +++ b/libtorrent/include/asio/detail/service_registry_fwd.hpp @@ -0,0 +1,30 @@ +// +// service_registry_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SERVICE_REGISTRY_FWD_HPP +#define ASIO_DETAIL_SERVICE_REGISTRY_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace detail { + +class service_registry; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SERVICE_REGISTRY_FWD_HPP diff --git a/libtorrent/include/asio/detail/signal_blocker.hpp b/libtorrent/include/asio/detail/signal_blocker.hpp new file mode 100644 index 000000000..3eb3ba495 --- /dev/null +++ b/libtorrent/include/asio/detail/signal_blocker.hpp @@ -0,0 +1,50 @@ +// +// signal_blocker.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SIGNAL_BLOCKER_HPP +#define ASIO_DETAIL_SIGNAL_BLOCKER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) +# include "asio/detail/null_signal_blocker.hpp" +#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# include "asio/detail/win_signal_blocker.hpp" +#elif defined(BOOST_HAS_PTHREADS) +# include "asio/detail/posix_signal_blocker.hpp" +#else +# error Only Windows and POSIX are supported! +#endif + +namespace asio { +namespace detail { + +#if !defined(BOOST_HAS_THREADS) +typedef null_signal_blocker signal_blocker; +#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) +typedef win_signal_blocker signal_blocker; +#elif defined(BOOST_HAS_PTHREADS) +typedef posix_signal_blocker signal_blocker; +#endif + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SIGNAL_BLOCKER_HPP diff --git a/libtorrent/include/asio/detail/signal_init.hpp b/libtorrent/include/asio/detail/signal_init.hpp new file mode 100644 index 000000000..3df395dc3 --- /dev/null +++ b/libtorrent/include/asio/detail/signal_init.hpp @@ -0,0 +1,51 @@ +// +// signal_init.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SIGNAL_INIT_HPP +#define ASIO_DETAIL_SIGNAL_INIT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +template +class signal_init +{ +public: + // Constructor. + signal_init() + { + std::signal(Signal, SIG_IGN); + } +}; + +} // namespace detail +} // namespace asio + +#endif // !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SIGNAL_INIT_HPP diff --git a/libtorrent/include/asio/detail/socket_holder.hpp b/libtorrent/include/asio/detail/socket_holder.hpp new file mode 100644 index 000000000..e45f4fd61 --- /dev/null +++ b/libtorrent/include/asio/detail/socket_holder.hpp @@ -0,0 +1,95 @@ +// +// socket_holder.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SOCKET_HOLDER_HPP +#define ASIO_DETAIL_SOCKET_HOLDER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_ops.hpp" + +namespace asio { +namespace detail { + +// Implement the resource acquisition is initialisation idiom for sockets. +class socket_holder + : private noncopyable +{ +public: + // Construct as an uninitialised socket. + socket_holder() + : socket_(invalid_socket) + { + } + + // Construct to take ownership of the specified socket. + explicit socket_holder(socket_type s) + : socket_(s) + { + } + + // Destructor. + ~socket_holder() + { + if (socket_ != invalid_socket) + { + asio::error_code ec; + socket_ops::close(socket_, ec); + } + } + + // Get the underlying socket. + socket_type get() const + { + return socket_; + } + + // Reset to an uninitialised socket. + void reset() + { + if (socket_ != invalid_socket) + { + asio::error_code ec; + socket_ops::close(socket_, ec); + socket_ = invalid_socket; + } + } + + // Reset to take ownership of the specified socket. + void reset(socket_type s) + { + reset(); + socket_ = s; + } + + // Release ownership of the socket. + socket_type release() + { + socket_type tmp = socket_; + socket_ = invalid_socket; + return tmp; + } + +private: + // The underlying socket. + socket_type socket_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SOCKET_HOLDER_HPP diff --git a/libtorrent/include/asio/detail/socket_ops.hpp b/libtorrent/include/asio/detail/socket_ops.hpp new file mode 100644 index 000000000..4b38c6ee8 --- /dev/null +++ b/libtorrent/include/asio/detail/socket_ops.hpp @@ -0,0 +1,1653 @@ +// +// socket_ops.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SOCKET_OPS_HPP +#define ASIO_DETAIL_SOCKET_OPS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__MACH__) && defined(__APPLE__) +# include +#endif // defined(__MACH__) && defined(__APPLE__) +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { +namespace socket_ops { + +inline void clear_error(asio::error_code& ec) +{ + errno = 0; +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + WSASetLastError(0); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + ec = asio::error_code(); +} + +template +inline ReturnType error_wrapper(ReturnType return_value, + asio::error_code& ec) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + ec = asio::error_code(WSAGetLastError(), asio::native_ecat); +#else + ec = asio::error_code(errno, asio::native_ecat); +#endif + return return_value; +} + +inline socket_type accept(socket_type s, socket_addr_type* addr, + socket_addr_len_type* addrlen, asio::error_code& ec) +{ + clear_error(ec); +#if defined(__MACH__) && defined(__APPLE__) || defined(__FreeBSD__) + socket_type new_s = error_wrapper(::accept(s, addr, addrlen), ec); + if (new_s == invalid_socket) + return new_s; + + int optval = 1; + int result = error_wrapper(::setsockopt(new_s, + SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)), ec); + if (result != 0) + { + ::close(new_s); + return invalid_socket; + } + + return new_s; +#else + return error_wrapper(::accept(s, addr, addrlen), ec); +#endif +} + +inline int bind(socket_type s, const socket_addr_type* addr, + socket_addr_len_type addrlen, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::bind(s, addr, addrlen), ec); +} + +inline int close(socket_type s, asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + return error_wrapper(::closesocket(s), ec); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + return error_wrapper(::close(s), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int shutdown(socket_type s, int what, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::shutdown(s, what), ec); +} + +inline int connect(socket_type s, const socket_addr_type* addr, + socket_addr_len_type addrlen, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::connect(s, addr, addrlen), ec); +} + +inline int listen(socket_type s, int backlog, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::listen(s, backlog), ec); +} + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +typedef WSABUF buf; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +typedef iovec buf; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +inline void init_buf(buf& b, void* data, size_t size) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + b.buf = static_cast(data); + b.len = static_cast(size); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + b.iov_base = data; + b.iov_len = size; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline void init_buf(buf& b, const void* data, size_t size) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + b.buf = static_cast(const_cast(data)); + b.len = static_cast(size); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + b.iov_base = const_cast(data); + b.iov_len = size; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int recv(socket_type s, buf* bufs, size_t count, int flags, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + // Receive some data. + DWORD recv_buf_count = static_cast(count); + DWORD bytes_transferred = 0; + DWORD recv_flags = flags; + int result = error_wrapper(::WSARecv(s, bufs, + recv_buf_count, &bytes_transferred, &recv_flags, 0, 0), ec); + if (result != 0) + return -1; + return bytes_transferred; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + msghdr msg; + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = bufs; + msg.msg_iovlen = count; + msg.msg_control = 0; + msg.msg_controllen = 0; + msg.msg_flags = 0; + return error_wrapper(::recvmsg(s, &msg, flags), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int recvfrom(socket_type s, buf* bufs, size_t count, int flags, + socket_addr_type* addr, socket_addr_len_type* addrlen, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + // Receive some data. + DWORD recv_buf_count = static_cast(count); + DWORD bytes_transferred = 0; + DWORD recv_flags = flags; + int result = error_wrapper(::WSARecvFrom(s, bufs, recv_buf_count, + &bytes_transferred, &recv_flags, addr, addrlen, 0, 0), ec); + if (result != 0) + return -1; + return bytes_transferred; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + msghdr msg; +#if defined(__MACH__) && defined(__APPLE__) \ + && (MAC_OS_X_VERSION_MAX_ALLOWED < 1040) + msg.msg_name = reinterpret_cast(addr); +#else + msg.msg_name = addr; +#endif + msg.msg_namelen = *addrlen; + msg.msg_iov = bufs; + msg.msg_iovlen = count; + msg.msg_control = 0; + msg.msg_controllen = 0; + msg.msg_flags = 0; + int result = error_wrapper(::recvmsg(s, &msg, flags), ec); + *addrlen = msg.msg_namelen; + return result; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int send(socket_type s, const buf* bufs, size_t count, int flags, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + // Send the data. + DWORD send_buf_count = static_cast(count); + DWORD bytes_transferred = 0; + DWORD send_flags = flags; + int result = error_wrapper(::WSASend(s, const_cast(bufs), + send_buf_count, &bytes_transferred, send_flags, 0, 0), ec); + if (result != 0) + return -1; + return bytes_transferred; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + msghdr msg; + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = const_cast(bufs); + msg.msg_iovlen = count; + msg.msg_control = 0; + msg.msg_controllen = 0; + msg.msg_flags = 0; +#if defined(__linux__) + flags |= MSG_NOSIGNAL; +#endif // defined(__linux__) + return error_wrapper(::sendmsg(s, &msg, flags), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int sendto(socket_type s, const buf* bufs, size_t count, int flags, + const socket_addr_type* addr, socket_addr_len_type addrlen, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + // Send the data. + DWORD send_buf_count = static_cast(count); + DWORD bytes_transferred = 0; + int result = error_wrapper(::WSASendTo(s, const_cast(bufs), + send_buf_count, &bytes_transferred, flags, addr, addrlen, 0, 0), ec); + if (result != 0) + return -1; + return bytes_transferred; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + msghdr msg; +#if defined(__MACH__) && defined(__APPLE__) \ + && (MAC_OS_X_VERSION_MAX_ALLOWED < 1040) + msg.msg_name = reinterpret_cast(const_cast(addr)); +#else + msg.msg_name = const_cast(addr); +#endif + msg.msg_namelen = addrlen; + msg.msg_iov = const_cast(bufs); + msg.msg_iovlen = count; + msg.msg_control = 0; + msg.msg_controllen = 0; + msg.msg_flags = 0; +#if defined(__linux__) + flags |= MSG_NOSIGNAL; +#endif // defined(__linux__) + return error_wrapper(::sendmsg(s, &msg, flags), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline socket_type socket(int af, int type, int protocol, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + socket_type s = error_wrapper(::WSASocket(af, type, protocol, 0, 0, + WSA_FLAG_OVERLAPPED), ec); + if (s == invalid_socket) + return s; + + if (af == AF_INET6) + { + // Try to enable the POSIX default behaviour of having IPV6_V6ONLY set to + // false. This will only succeed on Windows Vista and later versions of + // Windows, where a dual-stack IPv4/v6 implementation is available. + DWORD optval = 0; + ::setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&optval), sizeof(optval)); + } + + return s; +#elif defined(__MACH__) && defined(__APPLE__) || defined(__FreeBSD__) + socket_type s = error_wrapper(::socket(af, type, protocol), ec); + if (s == invalid_socket) + return s; + + int optval = 1; + int result = error_wrapper(::setsockopt(s, + SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)), ec); + if (result != 0) + { + ::close(s); + return invalid_socket; + } + + return s; +#else + return error_wrapper(::socket(af, type, protocol), ec); +#endif +} + +inline int setsockopt(socket_type s, int level, int optname, + const void* optval, size_t optlen, asio::error_code& ec) +{ + if (level == custom_socket_option_level && optname == always_fail_option) + { + ec = asio::error::invalid_argument; + return -1; + } + +#if defined(__BORLANDC__) + // Mysteriously, using the getsockopt and setsockopt functions directly with + // Borland C++ results in incorrect values being set and read. The bug can be + // worked around by using function addresses resolved with GetProcAddress. + if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) + { + typedef int (WSAAPI *sso_t)(SOCKET, int, int, const char*, int); + if (sso_t sso = (sso_t)::GetProcAddress(winsock_module, "setsockopt")) + { + clear_error(ec); + return error_wrapper(sso(s, level, optname, + reinterpret_cast(optval), + static_cast(optlen)), ec); + } + } + ec = asio::error::fault; + return -1; +#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) + clear_error(ec); + return error_wrapper(::setsockopt(s, level, optname, + reinterpret_cast(optval), static_cast(optlen)), ec); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + clear_error(ec); + return error_wrapper(::setsockopt(s, level, optname, optval, + static_cast(optlen)), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int getsockopt(socket_type s, int level, int optname, void* optval, + size_t* optlen, asio::error_code& ec) +{ + if (level == custom_socket_option_level && optname == always_fail_option) + { + ec = asio::error::invalid_argument; + return -1; + } + +#if defined(__BORLANDC__) + // Mysteriously, using the getsockopt and setsockopt functions directly with + // Borland C++ results in incorrect values being set and read. The bug can be + // worked around by using function addresses resolved with GetProcAddress. + if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) + { + typedef int (WSAAPI *gso_t)(SOCKET, int, int, char*, int*); + if (gso_t gso = (gso_t)::GetProcAddress(winsock_module, "getsockopt")) + { + clear_error(ec); + int tmp_optlen = static_cast(*optlen); + int result = error_wrapper(gso(s, level, optname, + reinterpret_cast(optval), &tmp_optlen), ec); + *optlen = static_cast(tmp_optlen); + if (result != 0 && level == IPPROTO_IPV6 && optname == IPV6_V6ONLY + && ec.value() == WSAENOPROTOOPT && *optlen == sizeof(DWORD)) + { + // Dual-stack IPv4/v6 sockets, and the IPV6_V6ONLY socket option, are + // only supported on Windows Vista and later. To simplify program logic + // we will fake success of getting this option and specify that the + // value is non-zero (i.e. true). This corresponds to the behavior of + // IPv6 sockets on Windows platforms pre-Vista. + *static_cast(optval) = 1; + clear_error(ec); + } + return result; + } + } + ec = asio::error::fault; + return -1; +#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) + clear_error(ec); + int tmp_optlen = static_cast(*optlen); + int result = error_wrapper(::getsockopt(s, level, optname, + reinterpret_cast(optval), &tmp_optlen), ec); + *optlen = static_cast(tmp_optlen); + if (result != 0 && level == IPPROTO_IPV6 && optname == IPV6_V6ONLY + && ec.value() == WSAENOPROTOOPT && *optlen == sizeof(DWORD)) + { + // Dual-stack IPv4/v6 sockets, and the IPV6_V6ONLY socket option, are only + // supported on Windows Vista and later. To simplify program logic we will + // fake success of getting this option and specify that the value is + // non-zero (i.e. true). This corresponds to the behavior of IPv6 sockets + // on Windows platforms pre-Vista. + *static_cast(optval) = 1; + clear_error(ec); + } + return result; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + clear_error(ec); + socklen_t tmp_optlen = static_cast(*optlen); + int result = error_wrapper(::getsockopt(s, level, optname, + optval, &tmp_optlen), ec); + *optlen = static_cast(tmp_optlen); +#if defined(__linux__) + if (result == 0 && level == SOL_SOCKET && *optlen == sizeof(int) + && (optname == SO_SNDBUF || optname == SO_RCVBUF)) + { + // On Linux, setting SO_SNDBUF or SO_RCVBUF to N actually causes the kernel + // to set the buffer size to N*2. Linux puts additional stuff into the + // buffers so that only about half is actually available to the application. + // The retrieved value is divided by 2 here to make it appear as though the + // correct value has been set. + *static_cast(optval) /= 2; + } +#endif // defined(__linux__) + return result; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int getpeername(socket_type s, socket_addr_type* addr, + socket_addr_len_type* addrlen, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::getpeername(s, addr, addrlen), ec); +} + +inline int getsockname(socket_type s, socket_addr_type* addr, + socket_addr_len_type* addrlen, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::getsockname(s, addr, addrlen), ec); +} + +inline int ioctl(socket_type s, long cmd, ioctl_arg_type* arg, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + return error_wrapper(::ioctlsocket(s, cmd, arg), ec); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + return error_wrapper(::ioctl(s, cmd, arg), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int select(int nfds, fd_set* readfds, fd_set* writefds, + fd_set* exceptfds, timeval* timeout, asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + if (!readfds && !writefds && !exceptfds && timeout) + { + DWORD milliseconds = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + if (milliseconds == 0) + milliseconds = 1; // Force context switch. + ::Sleep(milliseconds); + ec = asio::error_code(); + return 0; + } + + // The select() call allows timeout values measured in microseconds, but the + // system clock (as wrapped by boost::posix_time::microsec_clock) typically + // has a resolution of 10 milliseconds. This can lead to a spinning select + // reactor, meaning increased CPU usage, when waiting for the earliest + // scheduled timeout if it's less than 10 milliseconds away. To avoid a tight + // spin we'll use a minimum timeout of 1 millisecond. + if (timeout && timeout->tv_sec == 0 + && timeout->tv_usec > 0 && timeout->tv_usec < 1000) + timeout->tv_usec = 1000; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + return error_wrapper(::select(nfds, readfds, + writefds, exceptfds, timeout), ec); +} + +inline int poll_read(socket_type s, asio::error_code& ec) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + FD_SET fds; + FD_ZERO(&fds); + FD_SET(s, &fds); + clear_error(ec); + return error_wrapper(::select(s, &fds, 0, 0, 0), ec); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + pollfd fds; + fds.fd = s; + fds.events = POLLIN; + fds.revents = 0; + clear_error(ec); + return error_wrapper(::poll(&fds, 1, -1), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int poll_write(socket_type s, asio::error_code& ec) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + FD_SET fds; + FD_ZERO(&fds); + FD_SET(s, &fds); + clear_error(ec); + return error_wrapper(::select(s, 0, &fds, 0, 0), ec); +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + pollfd fds; + fds.fd = s; + fds.events = POLLOUT; + fds.revents = 0; + clear_error(ec); + return error_wrapper(::poll(&fds, 1, -1), ec); +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline const char* inet_ntop(int af, const void* src, char* dest, size_t length, + unsigned long scope_id, asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + using namespace std; // For memcpy. + + if (af != AF_INET && af != AF_INET6) + { + ec = asio::error::address_family_not_supported; + return 0; + } + + sockaddr_storage_type address; + DWORD address_length; + if (af == AF_INET) + { + address_length = sizeof(sockaddr_in4_type); + sockaddr_in4_type* ipv4_address = + reinterpret_cast(&address); + ipv4_address->sin_family = AF_INET; + ipv4_address->sin_port = 0; + memcpy(&ipv4_address->sin_addr, src, sizeof(in4_addr_type)); + } + else // AF_INET6 + { + address_length = sizeof(sockaddr_in6_type); + sockaddr_in6_type* ipv6_address = + reinterpret_cast(&address); + ipv6_address->sin6_family = AF_INET6; + ipv6_address->sin6_port = 0; + ipv6_address->sin6_flowinfo = 0; + ipv6_address->sin6_scope_id = scope_id; + memcpy(&ipv6_address->sin6_addr, src, sizeof(in6_addr_type)); + } + + DWORD string_length = static_cast(length); + int result = error_wrapper(::WSAAddressToStringA( + reinterpret_cast(&address), + address_length, 0, dest, &string_length), ec); + + // Windows may set error code on success. + if (result != socket_error_retval) + clear_error(ec); + + // Windows may not set an error code on failure. + else if (result == socket_error_retval && !ec) + ec = asio::error::invalid_argument; + + return result == socket_error_retval ? 0 : dest; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + const char* result = error_wrapper(::inet_ntop(af, src, dest, length), ec); + if (result == 0 && !ec) + ec = asio::error::invalid_argument; + if (result != 0 && af == AF_INET6 && scope_id != 0) + { + using namespace std; // For strcat and sprintf. + char if_name[IF_NAMESIZE + 1] = "%"; + const in6_addr_type* ipv6_address = static_cast(src); + bool is_link_local = IN6_IS_ADDR_LINKLOCAL(ipv6_address); + if (!is_link_local || if_indextoname(scope_id, if_name + 1) == 0) + sprintf(if_name + 1, "%lu", scope_id); + strcat(dest, if_name); + } + return result; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int inet_pton(int af, const char* src, void* dest, + unsigned long* scope_id, asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + using namespace std; // For memcpy and strcmp. + + if (af != AF_INET && af != AF_INET6) + { + ec = asio::error::address_family_not_supported; + return -1; + } + + sockaddr_storage_type address; + int address_length = sizeof(sockaddr_storage_type); + int result = error_wrapper(::WSAStringToAddressA( + const_cast(src), af, 0, + reinterpret_cast(&address), + &address_length), ec); + + if (af == AF_INET) + { + if (result != socket_error_retval) + { + sockaddr_in4_type* ipv4_address = + reinterpret_cast(&address); + memcpy(dest, &ipv4_address->sin_addr, sizeof(in4_addr_type)); + clear_error(ec); + } + else if (strcmp(src, "255.255.255.255") == 0) + { + static_cast(dest)->s_addr = INADDR_NONE; + clear_error(ec); + } + } + else // AF_INET6 + { + if (result != socket_error_retval) + { + sockaddr_in6_type* ipv6_address = + reinterpret_cast(&address); + memcpy(dest, &ipv6_address->sin6_addr, sizeof(in6_addr_type)); + if (scope_id) + *scope_id = ipv6_address->sin6_scope_id; + clear_error(ec); + } + } + + // Windows may not set an error code on failure. + if (result == socket_error_retval && !ec) + ec = asio::error::invalid_argument; + + return result == socket_error_retval ? -1 : 1; +#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + int result = error_wrapper(::inet_pton(af, src, dest), ec); + if (result <= 0 && !ec) + ec = asio::error::invalid_argument; + if (result > 0 && af == AF_INET6 && scope_id) + { + using namespace std; // For strchr and atoi. + *scope_id = 0; + if (const char* if_name = strchr(src, '%')) + { + in6_addr_type* ipv6_address = static_cast(dest); + bool is_link_local = IN6_IS_ADDR_LINKLOCAL(ipv6_address); + if (is_link_local) + *scope_id = if_nametoindex(if_name + 1); + if (*scope_id == 0) + *scope_id = atoi(if_name + 1); + } + } + return result; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +} + +inline int gethostname(char* name, int namelen, asio::error_code& ec) +{ + clear_error(ec); + return error_wrapper(::gethostname(name, namelen), ec); +} + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) \ + || defined(__MACH__) && defined(__APPLE__) + +// The following functions are only needed for emulation of getaddrinfo and +// getnameinfo. + +inline asio::error_code translate_netdb_error(int error) +{ + switch (error) + { + case 0: + return asio::error_code(); + case HOST_NOT_FOUND: + return asio::error::host_not_found; + case TRY_AGAIN: + return asio::error::host_not_found_try_again; + case NO_RECOVERY: + return asio::error::no_recovery; + case NO_DATA: + return asio::error::no_data; + default: + BOOST_ASSERT(false); + return asio::error::invalid_argument; + } +} + +inline hostent* gethostbyaddr(const char* addr, int length, int af, + hostent* result, char* buffer, int buflength, asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + (void)(buffer); + (void)(buflength); + hostent* retval = error_wrapper(::gethostbyaddr(addr, length, af), ec); + if (!retval) + return 0; + *result = *retval; + return retval; +#elif defined(__sun) || defined(__QNX__) + int error = 0; + hostent* retval = error_wrapper(::gethostbyaddr_r(addr, length, af, result, + buffer, buflength, &error), ec); + if (error) + ec = translate_netdb_error(error); + return retval; +#elif defined(__MACH__) && defined(__APPLE__) + (void)(buffer); + (void)(buflength); + int error = 0; + hostent* retval = error_wrapper(::getipnodebyaddr( + addr, length, af, &error), ec); + if (error) + ec = translate_netdb_error(error); + if (!retval) + return 0; + *result = *retval; + return retval; +#else + hostent* retval = 0; + int error = 0; + error_wrapper(::gethostbyaddr_r(addr, length, af, result, buffer, + buflength, &retval, &error), ec); + if (error) + ec = translate_netdb_error(error); + return retval; +#endif +} + +inline hostent* gethostbyname(const char* name, int af, struct hostent* result, + char* buffer, int buflength, int ai_flags, asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + (void)(buffer); + (void)(buflength); + (void)(ai_flags); + if (af != AF_INET) + { + ec = asio::error::address_family_not_supported; + return 0; + } + hostent* retval = error_wrapper(::gethostbyname(name), ec); + if (!retval) + return 0; + *result = *retval; + return result; +#elif defined(__sun) || defined(__QNX__) + (void)(ai_flags); + if (af != AF_INET) + { + ec = asio::error::address_family_not_supported; + return 0; + } + int error = 0; + hostent* retval = error_wrapper(::gethostbyname_r(name, result, buffer, + buflength, &error), ec); + if (error) + ec = translate_netdb_error(error); + return retval; +#elif defined(__MACH__) && defined(__APPLE__) + (void)(buffer); + (void)(buflength); + int error = 0; + hostent* retval = error_wrapper(::getipnodebyname( + name, af, ai_flags, &error), ec); + if (error) + ec = translate_netdb_error(error); + if (!retval) + return 0; + *result = *retval; + return retval; +#else + (void)(ai_flags); + if (af != AF_INET) + { + ec = asio::error::address_family_not_supported; + return 0; + } + hostent* retval = 0; + int error = 0; + error_wrapper(::gethostbyname_r(name, result, + buffer, buflength, &retval, &error), ec); + if (error) + ec = translate_netdb_error(error); + return retval; +#endif +} + +inline void freehostent(hostent* h) +{ +#if defined(__MACH__) && defined(__APPLE__) + if (h) + ::freehostent(h); +#else + (void)(h); +#endif +} + +// Emulation of getaddrinfo based on implementation in: +// Stevens, W. R., UNIX Network Programming Vol. 1, 2nd Ed., Prentice-Hall 1998. + +struct gai_search +{ + const char* host; + int family; +}; + +inline int gai_nsearch(const char* host, + const addrinfo_type* hints, gai_search (&search)[2]) +{ + int search_count = 0; + if (host == 0 || host[0] == '\0') + { + if (hints->ai_flags & AI_PASSIVE) + { + // No host and AI_PASSIVE implies wildcard bind. + switch (hints->ai_family) + { + case AF_INET: + search[search_count].host = "0.0.0.0"; + search[search_count].family = AF_INET; + ++search_count; + break; + case AF_INET6: + search[search_count].host = "0::0"; + search[search_count].family = AF_INET6; + ++search_count; + break; + case AF_UNSPEC: + search[search_count].host = "0::0"; + search[search_count].family = AF_INET6; + ++search_count; + search[search_count].host = "0.0.0.0"; + search[search_count].family = AF_INET; + ++search_count; + break; + default: + break; + } + } + else + { + // No host and not AI_PASSIVE means connect to local host. + switch (hints->ai_family) + { + case AF_INET: + search[search_count].host = "localhost"; + search[search_count].family = AF_INET; + ++search_count; + break; + case AF_INET6: + search[search_count].host = "localhost"; + search[search_count].family = AF_INET6; + ++search_count; + break; + case AF_UNSPEC: + search[search_count].host = "localhost"; + search[search_count].family = AF_INET6; + ++search_count; + search[search_count].host = "localhost"; + search[search_count].family = AF_INET; + ++search_count; + break; + default: + break; + } + } + } + else + { + // Host is specified. + switch (hints->ai_family) + { + case AF_INET: + search[search_count].host = host; + search[search_count].family = AF_INET; + ++search_count; + break; + case AF_INET6: + search[search_count].host = host; + search[search_count].family = AF_INET6; + ++search_count; + break; + case AF_UNSPEC: + search[search_count].host = host; + search[search_count].family = AF_INET6; + ++search_count; + search[search_count].host = host; + search[search_count].family = AF_INET; + ++search_count; + break; + default: + break; + } + } + return search_count; +} + +template +inline T* gai_alloc(std::size_t size = sizeof(T)) +{ + using namespace std; + T* p = static_cast(::operator new(size, std::nothrow)); + if (p) + memset(p, 0, size); + return p; +} + +inline void gai_free(void* p) +{ + ::operator delete(p); +} + +enum { gai_clone_flag = 1 << 30 }; + +inline int gai_aistruct(addrinfo_type*** next, const addrinfo_type* hints, + const void* addr, int family) +{ + using namespace std; + + addrinfo_type* ai = gai_alloc(); + if (ai == 0) + return EAI_MEMORY; + + ai->ai_next = 0; + **next = ai; + *next = &ai->ai_next; + + ai->ai_canonname = 0; + ai->ai_socktype = hints->ai_socktype; + if (ai->ai_socktype == 0) + ai->ai_flags |= gai_clone_flag; + ai->ai_protocol = hints->ai_protocol; + ai->ai_family = family; + + switch (ai->ai_family) + { + case AF_INET: + { + sockaddr_in4_type* sinptr = gai_alloc(); + if (sinptr == 0) + return EAI_MEMORY; + sinptr->sin_family = AF_INET; + memcpy(&sinptr->sin_addr, addr, sizeof(in4_addr_type)); + ai->ai_addr = reinterpret_cast(sinptr); + ai->ai_addrlen = sizeof(sockaddr_in4_type); + break; + } + case AF_INET6: + { + sockaddr_in6_type* sin6ptr = gai_alloc(); + if (sin6ptr == 0) + return EAI_MEMORY; + sin6ptr->sin6_family = AF_INET6; + memcpy(&sin6ptr->sin6_addr, addr, sizeof(in6_addr_type)); + ai->ai_addr = reinterpret_cast(sin6ptr); + ai->ai_addrlen = sizeof(sockaddr_in6_type); + break; + } + default: + break; + } + + return 0; +} + +inline addrinfo_type* gai_clone(addrinfo_type* ai) +{ + using namespace std; + + addrinfo_type* new_ai = gai_alloc(); + if (new_ai == 0) + return new_ai; + + new_ai->ai_next = ai->ai_next; + ai->ai_next = new_ai; + + new_ai->ai_flags = 0; + new_ai->ai_family = ai->ai_family; + new_ai->ai_socktype = ai->ai_socktype; + new_ai->ai_protocol = ai->ai_protocol; + new_ai->ai_canonname = 0; + new_ai->ai_addrlen = ai->ai_addrlen; + new_ai->ai_addr = gai_alloc(ai->ai_addrlen); + memcpy(new_ai->ai_addr, ai->ai_addr, ai->ai_addrlen); + + return new_ai; +} + +inline int gai_port(addrinfo_type* aihead, int port, int socktype) +{ + int num_found = 0; + + for (addrinfo_type* ai = aihead; ai; ai = ai->ai_next) + { + if (ai->ai_flags & gai_clone_flag) + { + if (ai->ai_socktype != 0) + { + ai = gai_clone(ai); + if (ai == 0) + return -1; + // ai now points to newly cloned entry. + } + } + else if (ai->ai_socktype != socktype) + { + // Ignore if mismatch on socket type. + continue; + } + + ai->ai_socktype = socktype; + + switch (ai->ai_family) + { + case AF_INET: + { + sockaddr_in4_type* sinptr = + reinterpret_cast(ai->ai_addr); + sinptr->sin_port = port; + ++num_found; + break; + } + case AF_INET6: + { + sockaddr_in6_type* sin6ptr = + reinterpret_cast(ai->ai_addr); + sin6ptr->sin6_port = port; + ++num_found; + break; + } + default: + break; + } + } + + return num_found; +} + +inline int gai_serv(addrinfo_type* aihead, + const addrinfo_type* hints, const char* serv) +{ + using namespace std; + + int num_found = 0; + + if ( +#if defined(AI_NUMERICSERV) + (hints->ai_flags & AI_NUMERICSERV) || +#endif + isdigit(serv[0])) + { + int port = htons(atoi(serv)); + if (hints->ai_socktype) + { + // Caller specifies socket type. + int rc = gai_port(aihead, port, hints->ai_socktype); + if (rc < 0) + return EAI_MEMORY; + num_found += rc; + } + else + { + // Caller does not specify socket type. + int rc = gai_port(aihead, port, SOCK_STREAM); + if (rc < 0) + return EAI_MEMORY; + num_found += rc; + rc = gai_port(aihead, port, SOCK_DGRAM); + if (rc < 0) + return EAI_MEMORY; + num_found += rc; + } + } + else + { + // Try service name with TCP first, then UDP. + if (hints->ai_socktype == 0 || hints->ai_socktype == SOCK_STREAM) + { + servent* sptr = getservbyname(serv, "tcp"); + if (sptr != 0) + { + int rc = gai_port(aihead, sptr->s_port, SOCK_STREAM); + if (rc < 0) + return EAI_MEMORY; + num_found += rc; + } + } + if (hints->ai_socktype == 0 || hints->ai_socktype == SOCK_DGRAM) + { + servent* sptr = getservbyname(serv, "udp"); + if (sptr != 0) + { + int rc = gai_port(aihead, sptr->s_port, SOCK_DGRAM); + if (rc < 0) + return EAI_MEMORY; + num_found += rc; + } + } + } + + if (num_found == 0) + { + if (hints->ai_socktype == 0) + { + // All calls to getservbyname() failed. + return EAI_NONAME; + } + else + { + // Service not supported for socket type. + return EAI_SERVICE; + } + } + + return 0; +} + +inline int gai_echeck(const char* host, const char* service, + int flags, int family, int socktype, int protocol) +{ + (void)(flags); + (void)(protocol); + + // Host or service must be specified. + if (host == 0 || host[0] == '\0') + if (service == 0 || service[0] == '\0') + return EAI_NONAME; + + // Check combination of family and socket type. + switch (family) + { + case AF_UNSPEC: + break; + case AF_INET: + case AF_INET6: + if (socktype != 0 && socktype != SOCK_STREAM && socktype != SOCK_DGRAM) + return EAI_SOCKTYPE; + break; + default: + return EAI_FAMILY; + } + + return 0; +} + +inline void freeaddrinfo_emulation(addrinfo_type* aihead) +{ + addrinfo_type* ai = aihead; + while (ai) + { + gai_free(ai->ai_addr); + gai_free(ai->ai_canonname); + addrinfo_type* ainext = ai->ai_next; + gai_free(ai); + ai = ainext; + } +} + +inline int getaddrinfo_emulation(const char* host, const char* service, + const addrinfo_type* hintsp, addrinfo_type** result) +{ + // Set up linked list of addrinfo structures. + addrinfo_type* aihead = 0; + addrinfo_type** ainext = &aihead; + char* canon = 0; + + // Supply default hints if not specified by caller. + addrinfo_type hints = addrinfo_type(); + hints.ai_family = AF_UNSPEC; + if (hintsp) + hints = *hintsp; + + // If the resolution is not specifically for AF_INET6, remove the AI_V4MAPPED + // and AI_ALL flags. +#if defined(AI_V4MAPPED) + if (hints.ai_family != AF_INET6) + hints.ai_flags &= ~AI_V4MAPPED; +#endif +#if defined(AI_ALL) + if (hints.ai_family != AF_INET6) + hints.ai_flags &= ~AI_ALL; +#endif + + // Basic error checking. + int rc = gai_echeck(host, service, hints.ai_flags, hints.ai_family, + hints.ai_socktype, hints.ai_protocol); + if (rc != 0) + { + freeaddrinfo_emulation(aihead); + return rc; + } + + gai_search search[2]; + int search_count = gai_nsearch(host, &hints, search); + for (gai_search* sptr = search; sptr < search + search_count; ++sptr) + { + // Check for IPv4 dotted decimal string. + in4_addr_type inaddr; + asio::error_code ec; + if (socket_ops::inet_pton(AF_INET, sptr->host, &inaddr, 0, ec) == 1) + { + if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET) + { + freeaddrinfo_emulation(aihead); + gai_free(canon); + return EAI_FAMILY; + } + if (sptr->family == AF_INET) + { + rc = gai_aistruct(&ainext, &hints, &inaddr, AF_INET); + if (rc != 0) + { + freeaddrinfo_emulation(aihead); + gai_free(canon); + return rc; + } + } + continue; + } + + // Check for IPv6 hex string. + in6_addr_type in6addr; + if (socket_ops::inet_pton(AF_INET6, sptr->host, &in6addr, 0, ec) == 1) + { + if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET6) + { + freeaddrinfo_emulation(aihead); + gai_free(canon); + return EAI_FAMILY; + } + if (sptr->family == AF_INET6) + { + rc = gai_aistruct(&ainext, &hints, &in6addr, AF_INET6); + if (rc != 0) + { + freeaddrinfo_emulation(aihead); + gai_free(canon); + return rc; + } + } + continue; + } + + // Look up hostname. + hostent hent; + char hbuf[8192] = ""; + hostent* hptr = socket_ops::gethostbyname(sptr->host, + sptr->family, &hent, hbuf, sizeof(hbuf), hints.ai_flags, ec); + if (hptr == 0) + { + if (search_count == 2) + { + // Failure is OK if there are multiple searches. + continue; + } + freeaddrinfo_emulation(aihead); + gai_free(canon); + if (ec == asio::error::host_not_found) + return EAI_NONAME; + if (ec == asio::error::host_not_found_try_again) + return EAI_AGAIN; + if (ec == asio::error::no_recovery) + return EAI_FAIL; + if (ec == asio::error::no_data) + return EAI_NONAME; + return EAI_NONAME; + } + + // Check for address family mismatch if one was specified. + if (hints.ai_family != AF_UNSPEC && hints.ai_family != hptr->h_addrtype) + { + freeaddrinfo_emulation(aihead); + gai_free(canon); + socket_ops::freehostent(hptr); + return EAI_FAMILY; + } + + // Save canonical name first time. + if (host != 0 && host[0] != '\0' && hptr->h_name && hptr->h_name[0] + && (hints.ai_flags & AI_CANONNAME) && canon == 0) + { + canon = gai_alloc(strlen(hptr->h_name) + 1); + if (canon == 0) + { + freeaddrinfo_emulation(aihead); + socket_ops::freehostent(hptr); + return EAI_MEMORY; + } + strcpy(canon, hptr->h_name); + } + + // Create an addrinfo structure for each returned address. + for (char** ap = hptr->h_addr_list; *ap; ++ap) + { + rc = gai_aistruct(&ainext, &hints, *ap, hptr->h_addrtype); + if (rc != 0) + { + freeaddrinfo_emulation(aihead); + gai_free(canon); + socket_ops::freehostent(hptr); + return EAI_FAMILY; + } + } + + socket_ops::freehostent(hptr); + } + + // Check if we found anything. + if (aihead == 0) + { + gai_free(canon); + return EAI_NONAME; + } + + // Return canonical name in first entry. + if (host != 0 && host[0] != '\0' && (hints.ai_flags & AI_CANONNAME)) + { + if (canon) + { + aihead->ai_canonname = canon; + canon = 0; + } + else + { + aihead->ai_canonname = gai_alloc(strlen(search[0].host) + 1); + if (aihead->ai_canonname == 0) + { + freeaddrinfo_emulation(aihead); + return EAI_MEMORY; + } + strcpy(aihead->ai_canonname, search[0].host); + } + } + gai_free(canon); + + // Process the service name. + if (service != 0 && service[0] != '\0') + { + rc = gai_serv(aihead, &hints, service); + if (rc != 0) + { + freeaddrinfo_emulation(aihead); + return rc; + } + } + + // Return result to caller. + *result = aihead; + return 0; +} + +inline asio::error_code getnameinfo_emulation( + const socket_addr_type* sa, socket_addr_len_type salen, char* host, + std::size_t hostlen, char* serv, std::size_t servlen, int flags, + asio::error_code& ec) +{ + using namespace std; + + const char* addr; + size_t addr_len; + unsigned short port; + switch (sa->sa_family) + { + case AF_INET: + if (salen != sizeof(sockaddr_in4_type)) + { + return ec = asio::error::invalid_argument; + } + addr = reinterpret_cast( + &reinterpret_cast(sa)->sin_addr); + addr_len = sizeof(in4_addr_type); + port = reinterpret_cast(sa)->sin_port; + break; + case AF_INET6: + if (salen != sizeof(sockaddr_in6_type)) + { + return ec = asio::error::invalid_argument; + } + addr = reinterpret_cast( + &reinterpret_cast(sa)->sin6_addr); + addr_len = sizeof(in6_addr_type); + port = reinterpret_cast(sa)->sin6_port; + break; + default: + return ec = asio::error::address_family_not_supported; + } + + if (host && hostlen > 0) + { + if (flags & NI_NUMERICHOST) + { + if (socket_ops::inet_ntop(sa->sa_family, addr, host, hostlen, 0, ec) == 0) + { + return ec; + } + } + else + { + hostent hent; + char hbuf[8192] = ""; + hostent* hptr = socket_ops::gethostbyaddr(addr, + static_cast(addr_len), sa->sa_family, + &hent, hbuf, sizeof(hbuf), ec); + if (hptr && hptr->h_name && hptr->h_name[0] != '\0') + { + if (flags & NI_NOFQDN) + { + char* dot = strchr(hptr->h_name, '.'); + if (dot) + { + *dot = 0; + } + } + *host = '\0'; + strncat(host, hptr->h_name, hostlen); + socket_ops::freehostent(hptr); + } + else + { + socket_ops::freehostent(hptr); + if (flags & NI_NAMEREQD) + { + return ec = asio::error::host_not_found; + } + if (socket_ops::inet_ntop(sa->sa_family, + addr, host, hostlen, 0, ec) == 0) + { + return ec; + } + } + } + } + + if (serv && servlen > 0) + { + if (flags & NI_NUMERICSERV) + { + if (servlen < 6) + { + return ec = asio::error::no_buffer_space; + } + sprintf(serv, "%u", ntohs(port)); + } + else + { +#if defined(BOOST_HAS_THREADS) && defined(BOOST_HAS_PTHREADS) + static ::pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + ::pthread_mutex_lock(&mutex); +#endif // defined(BOOST_HAS_THREADS) && defined(BOOST_HAS_PTHREADS) + servent* sptr = ::getservbyport(port, (flags & NI_DGRAM) ? "udp" : 0); + if (sptr && sptr->s_name && sptr->s_name[0] != '\0') + { + *serv = '\0'; + strncat(serv, sptr->s_name, servlen); + } + else + { + if (servlen < 6) + { + return ec = asio::error::no_buffer_space; + } + sprintf(serv, "%u", ntohs(port)); + } +#if defined(BOOST_HAS_THREADS) && defined(BOOST_HAS_PTHREADS) + ::pthread_mutex_unlock(&mutex); +#endif // defined(BOOST_HAS_THREADS) && defined(BOOST_HAS_PTHREADS) + } + } + + clear_error(ec); + return ec; +} + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + // || defined(__MACH__) && defined(__APPLE__) + +inline asio::error_code translate_addrinfo_error(int error) +{ + switch (error) + { + case 0: + return asio::error_code(); + case EAI_AGAIN: + return asio::error::host_not_found_try_again; + case EAI_BADFLAGS: + return asio::error::invalid_argument; + case EAI_FAIL: + return asio::error::no_recovery; + case EAI_FAMILY: + return asio::error::address_family_not_supported; + case EAI_MEMORY: + return asio::error::no_memory; + case EAI_NONAME: + return asio::error::host_not_found; + case EAI_SERVICE: + return asio::error::service_not_found; + case EAI_SOCKTYPE: + return asio::error::socket_type_not_supported; + default: // Possibly the non-portable EAI_SYSTEM. +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + return asio::error_code( + WSAGetLastError(), asio::native_ecat); +#else + return asio::error_code( + errno, asio::native_ecat); +#endif + } +} + +inline asio::error_code getaddrinfo(const char* host, + const char* service, const addrinfo_type* hints, addrinfo_type** result, + asio::error_code& ec) +{ + clear_error(ec); +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) + // Building for Windows XP, Windows Server 2003, or later. + int error = ::getaddrinfo(host, service, hints, result); + return ec = translate_addrinfo_error(error); +# else + // Building for Windows 2000 or earlier. + typedef int (WSAAPI *gai_t)(const char*, + const char*, const addrinfo_type*, addrinfo_type**); + if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) + { + if (gai_t gai = (gai_t)::GetProcAddress(winsock_module, "getaddrinfo")) + { + int error = gai(host, service, hints, result); + return ec = translate_addrinfo_error(error); + } + } + int error = getaddrinfo_emulation(host, service, hints, result); + return ec = translate_addrinfo_error(error); +# endif +#elif defined(__MACH__) && defined(__APPLE__) + int error = getaddrinfo_emulation(host, service, hints, result); + return ec = translate_addrinfo_error(error); +#else + int error = ::getaddrinfo(host, service, hints, result); + return ec = translate_addrinfo_error(error); +#endif +} + +inline void freeaddrinfo(addrinfo_type* ai) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) + // Building for Windows XP, Windows Server 2003, or later. + ::freeaddrinfo(ai); +# else + // Building for Windows 2000 or earlier. + typedef int (WSAAPI *fai_t)(addrinfo_type*); + if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) + { + if (fai_t fai = (fai_t)::GetProcAddress(winsock_module, "freeaddrinfo")) + { + fai(ai); + return; + } + } + freeaddrinfo_emulation(ai); +# endif +#elif defined(__MACH__) && defined(__APPLE__) + freeaddrinfo_emulation(ai); +#else + ::freeaddrinfo(ai); +#endif +} + +inline asio::error_code getnameinfo(const socket_addr_type* addr, + socket_addr_len_type addrlen, char* host, std::size_t hostlen, + char* serv, std::size_t servlen, int flags, asio::error_code& ec) +{ +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) + // Building for Windows XP, Windows Server 2003, or later. + clear_error(ec); + int error = ::getnameinfo(addr, addrlen, host, static_cast(hostlen), + serv, static_cast(servlen), flags); + return ec = translate_addrinfo_error(error); +# else + // Building for Windows 2000 or earlier. + typedef int (WSAAPI *gni_t)(const socket_addr_type*, + socket_addr_len_type, char*, std::size_t, char*, std::size_t, int); + if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) + { + if (gni_t gni = (gni_t)::GetProcAddress(winsock_module, "getnameinfo")) + { + clear_error(ec); + int error = gni(addr, addrlen, host, hostlen, serv, servlen, flags); + return ec = translate_addrinfo_error(error); + } + } + clear_error(ec); + return getnameinfo_emulation(addr, addrlen, + host, hostlen, serv, servlen, flags, ec); +# endif +#elif defined(__MACH__) && defined(__APPLE__) + using namespace std; // For memcpy. + sockaddr_storage_type tmp_addr; + memcpy(&tmp_addr, addr, addrlen); + tmp_addr.ss_len = addrlen; + addr = reinterpret_cast(&tmp_addr); + clear_error(ec); + return getnameinfo_emulation(addr, addrlen, + host, hostlen, serv, servlen, flags, ec); +#else + clear_error(ec); + int error = ::getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); + return ec = translate_addrinfo_error(error); +#endif +} + +inline u_long_type network_to_host_long(u_long_type value) +{ + return ntohl(value); +} + +inline u_long_type host_to_network_long(u_long_type value) +{ + return htonl(value); +} + +inline u_short_type network_to_host_short(u_short_type value) +{ + return ntohs(value); +} + +inline u_short_type host_to_network_short(u_short_type value) +{ + return htons(value); +} + +} // namespace socket_ops +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SOCKET_OPS_HPP diff --git a/libtorrent/include/asio/detail/socket_option.hpp b/libtorrent/include/asio/detail/socket_option.hpp new file mode 100644 index 000000000..ee867e6b2 --- /dev/null +++ b/libtorrent/include/asio/detail/socket_option.hpp @@ -0,0 +1,298 @@ +// +// socket_option.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SOCKET_OPTION_HPP +#define ASIO_DETAIL_SOCKET_OPTION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { +namespace socket_option { + +// Helper template for implementing boolean-based options. +template +class boolean +{ +public: + // Default constructor. + boolean() + : value_(0) + { + } + + // Construct with a specific option value. + explicit boolean(bool v) + : value_(v ? 1 : 0) + { + } + + // Set the current value of the boolean. + boolean& operator=(bool v) + { + value_ = v ? 1 : 0; + return *this; + } + + // Get the current value of the boolean. + bool value() const + { + return !!value_; + } + + // Convert to bool. + operator bool() const + { + return !!value_; + } + + // Test for false. + bool operator!() const + { + return !value_; + } + + // Get the level of the socket option. + template + int level(const Protocol&) const + { + return Level; + } + + // Get the name of the socket option. + template + int name(const Protocol&) const + { + return Name; + } + + // Get the address of the boolean data. + template + int* data(const Protocol&) + { + return &value_; + } + + // Get the address of the boolean data. + template + const int* data(const Protocol&) const + { + return &value_; + } + + // Get the size of the boolean data. + template + std::size_t size(const Protocol&) const + { + return sizeof(value_); + } + + // Set the size of the boolean data. + template + void resize(const Protocol&, std::size_t s) + { + if (s != sizeof(value_)) + throw std::length_error("boolean socket option resize"); + } + +private: + int value_; +}; + +// Helper template for implementing integer options. +template +class integer +{ +public: + // Default constructor. + integer() + : value_(0) + { + } + + // Construct with a specific option value. + explicit integer(int v) + : value_(v) + { + } + + // Set the value of the int option. + integer& operator=(int v) + { + value_ = v; + return *this; + } + + // Get the current value of the int option. + int value() const + { + return value_; + } + + // Get the level of the socket option. + template + int level(const Protocol&) const + { + return Level; + } + + // Get the name of the socket option. + template + int name(const Protocol&) const + { + return Name; + } + + // Get the address of the int data. + template + int* data(const Protocol&) + { + return &value_; + } + + // Get the address of the int data. + template + const int* data(const Protocol&) const + { + return &value_; + } + + // Get the size of the int data. + template + std::size_t size(const Protocol&) const + { + return sizeof(value_); + } + + // Set the size of the int data. + template + void resize(const Protocol&, std::size_t s) + { + if (s != sizeof(value_)) + throw std::length_error("integer socket option resize"); + } + +private: + int value_; +}; + +// Helper template for implementing linger options. +template +class linger +{ +public: + // Default constructor. + linger() + { + value_.l_onoff = 0; + value_.l_linger = 0; + } + + // Construct with specific option values. + linger(bool e, int t) + { + enabled(e); + timeout(t); + } + + // Set the value for whether linger is enabled. + void enabled(bool value) + { + value_.l_onoff = value ? 1 : 0; + } + + // Get the value for whether linger is enabled. + bool enabled() const + { + return value_.l_onoff != 0; + } + + // Set the value for the linger timeout. + void timeout(int value) + { +#if defined(WIN32) + value_.l_linger = static_cast(value); +#else + value_.l_linger = value; +#endif + } + + // Get the value for the linger timeout. + int timeout() const + { + return static_cast(value_.l_linger); + } + + // Get the level of the socket option. + template + int level(const Protocol&) const + { + return Level; + } + + // Get the name of the socket option. + template + int name(const Protocol&) const + { + return Name; + } + + // Get the address of the linger data. + template + ::linger* data(const Protocol&) + { + return &value_; + } + + // Get the address of the linger data. + template + const ::linger* data(const Protocol&) const + { + return &value_; + } + + // Get the size of the linger data. + template + std::size_t size(const Protocol&) const + { + return sizeof(value_); + } + + // Set the size of the int data. + template + void resize(const Protocol&, std::size_t s) + { + if (s != sizeof(value_)) + throw std::length_error("linger socket option resize"); + } + +private: + ::linger value_; +}; + +} // namespace socket_option +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SOCKET_OPTION_HPP diff --git a/libtorrent/include/asio/detail/socket_select_interrupter.hpp b/libtorrent/include/asio/detail/socket_select_interrupter.hpp new file mode 100644 index 000000000..ba62b12d6 --- /dev/null +++ b/libtorrent/include/asio/detail/socket_select_interrupter.hpp @@ -0,0 +1,187 @@ +// +// socket_select_interrupter.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SOCKET_SELECT_INTERRUPTER_HPP +#define ASIO_DETAIL_SOCKET_SELECT_INTERRUPTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/socket_holder.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +class socket_select_interrupter +{ +public: + // Constructor. + socket_select_interrupter() + { + asio::error_code ec; + socket_holder acceptor(socket_ops::socket( + AF_INET, SOCK_STREAM, IPPROTO_TCP, ec)); + if (acceptor.get() == invalid_socket) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + int opt = 1; + socket_ops::setsockopt(acceptor.get(), + SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt), ec); + + using namespace std; // For memset. + sockaddr_in4_type addr; + socket_addr_len_type addr_len = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = 0; + if (socket_ops::bind(acceptor.get(), (const socket_addr_type*)&addr, + addr_len, ec) == socket_error_retval) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + if (socket_ops::getsockname(acceptor.get(), (socket_addr_type*)&addr, + &addr_len, ec) == socket_error_retval) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + if (socket_ops::listen(acceptor.get(), + SOMAXCONN, ec) == socket_error_retval) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + socket_holder client(socket_ops::socket( + AF_INET, SOCK_STREAM, IPPROTO_TCP, ec)); + if (client.get() == invalid_socket) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + if (socket_ops::connect(client.get(), (const socket_addr_type*)&addr, + addr_len, ec) == socket_error_retval) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + socket_holder server(socket_ops::accept(acceptor.get(), 0, 0, ec)); + if (server.get() == invalid_socket) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + ioctl_arg_type non_blocking = 1; + if (socket_ops::ioctl(client.get(), FIONBIO, &non_blocking, ec)) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + opt = 1; + socket_ops::setsockopt(client.get(), + IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt), ec); + + non_blocking = 1; + if (socket_ops::ioctl(server.get(), FIONBIO, &non_blocking, ec)) + { + asio::system_error e(ec, "socket_select_interrupter"); + boost::throw_exception(e); + } + + opt = 1; + socket_ops::setsockopt(server.get(), + IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt), ec); + + read_descriptor_ = server.release(); + write_descriptor_ = client.release(); + } + + // Destructor. + ~socket_select_interrupter() + { + asio::error_code ec; + if (read_descriptor_ != invalid_socket) + socket_ops::close(read_descriptor_, ec); + if (write_descriptor_ != invalid_socket) + socket_ops::close(write_descriptor_, ec); + } + + // Interrupt the select call. + void interrupt() + { + char byte = 0; + socket_ops::buf b; + socket_ops::init_buf(b, &byte, 1); + asio::error_code ec; + socket_ops::send(write_descriptor_, &b, 1, 0, ec); + } + + // Reset the select interrupt. Returns true if the call was interrupted. + bool reset() + { + char data[1024]; + socket_ops::buf b; + socket_ops::init_buf(b, data, sizeof(data)); + asio::error_code ec; + int bytes_read = socket_ops::recv(read_descriptor_, &b, 1, 0, ec); + bool was_interrupted = (bytes_read > 0); + while (bytes_read == sizeof(data)) + bytes_read = socket_ops::recv(read_descriptor_, &b, 1, 0, ec); + return was_interrupted; + } + + // Get the read descriptor to be passed to select. + socket_type read_descriptor() const + { + return read_descriptor_; + } + +private: + // The read end of a connection used to interrupt the select call. This file + // descriptor is passed to select such that when it is time to stop, a single + // byte will be written on the other end of the connection and this + // descriptor will become readable. + socket_type read_descriptor_; + + // The write end of a connection used to interrupt the select call. A single + // byte may be written to this to wake up the select which is waiting for the + // other end to become readable. + socket_type write_descriptor_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SOCKET_SELECT_INTERRUPTER_HPP diff --git a/libtorrent/include/asio/detail/socket_types.hpp b/libtorrent/include/asio/detail/socket_types.hpp new file mode 100644 index 000000000..49d1c7fc2 --- /dev/null +++ b/libtorrent/include/asio/detail/socket_types.hpp @@ -0,0 +1,179 @@ +// +// socket_types.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_SOCKET_TYPES_HPP +#define ASIO_DETAIL_SOCKET_TYPES_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/push_options.hpp" +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# if defined(_WINSOCKAPI_) && !defined(_WINSOCK2API_) +# error WinSock.h has already been included +# endif // defined(_WINSOCKAPI_) && !defined(_WINSOCK2API_) +# if !defined(_WIN32_WINNT) && !defined(_WIN32_WINDOWS) +# if defined(_MSC_VER) || defined(__BORLANDC__) +# pragma message("Please define _WIN32_WINNT or _WIN32_WINDOWS appropriately") +# pragma message("Assuming _WIN32_WINNT=0x0500 (i.e. Windows 2000 target)") +# else // defined(_MSC_VER) || defined(__BORLANDC__) +# warning Please define _WIN32_WINNT or _WIN32_WINDOWS appropriately +# warning Assuming _WIN32_WINNT=0x0500 (i.e. Windows 2000 target) +# endif // defined(_MSC_VER) || defined(__BORLANDC__) +# define _WIN32_WINNT 0x0500 +# endif // !defined(_WIN32_WINNT) && !defined(_WIN32_WINDOWS) +# if defined(_MSC_VER) +# if defined(_WIN32) && !defined(WIN32) +# if !defined(_WINSOCK2API_) +# define WIN32 // Needed for correct types in winsock2.h +# else // !defined(_WINSOCK2API_) +# error Please define the macro WIN32 in your compiler options +# endif // !defined(_WINSOCK2API_) +# endif // defined(_WIN32) && !defined(WIN32) +# endif // defined(_MSC_VER) +# if defined(__BORLANDC__) +# include // Needed for __errno +# if defined(__WIN32__) && !defined(WIN32) +# if !defined(_WINSOCK2API_) +# define WIN32 // Needed for correct types in winsock2.h +# else // !defined(_WINSOCK2API_) +# error Please define the macro WIN32 in your compiler options +# endif // !defined(_WINSOCK2API_) +# endif // defined(__WIN32__) && !defined(WIN32) +# if !defined(_WSPIAPI_H_) +# define _WSPIAPI_H_ +# define ASIO_WSPIAPI_H_DEFINED +# endif // !defined(_WSPIAPI_H_) +# endif // defined(__BORLANDC__) +# if !defined(ASIO_NO_WIN32_LEAN_AND_MEAN) +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif // !defined(WIN32_LEAN_AND_MEAN) +# endif // !defined(ASIO_NO_WIN32_LEAN_AND_MEAN) +# if defined(__CYGWIN__) +# if !defined(__USE_W32_SOCKETS) +# error You must add -D__USE_W32_SOCKETS to your compiler options. +# endif // !defined(__USE_W32_SOCKETS) +# if !defined(NOMINMAX) +# define NOMINMAX 1 +# endif // !defined(NOMINMAX) +# endif // defined(__CYGWIN__) +# include +# include +# include +# if defined(ASIO_WSPIAPI_H_DEFINED) +# undef _WSPIAPI_H_ +# undef ASIO_WSPIAPI_H_DEFINED +# endif // defined(ASIO_WSPIAPI_H_DEFINED) +# if !defined(ASIO_NO_DEFAULT_LINKED_LIBS) +# if defined(_MSC_VER) || defined(__BORLANDC__) +# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "mswsock.lib") +# endif // defined(_MSC_VER) || defined(__BORLANDC__) +# endif // !defined(ASIO_NO_DEFAULT_LINKED_LIBS) +# include "asio/detail/old_win_sdk_compat.hpp" +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# if defined(__sun) +# include +# include +# endif +#endif +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +typedef SOCKET socket_type; +const SOCKET invalid_socket = INVALID_SOCKET; +const int socket_error_retval = SOCKET_ERROR; +const int max_addr_v4_str_len = 256; +const int max_addr_v6_str_len = 256; +typedef sockaddr socket_addr_type; +typedef int socket_addr_len_type; +typedef in_addr in4_addr_type; +typedef ip_mreq in4_mreq_type; +typedef sockaddr_in sockaddr_in4_type; +# if defined(ASIO_HAS_OLD_WIN_SDK) +typedef in6_addr_emulation in6_addr_type; +typedef ipv6_mreq_emulation in6_mreq_type; +typedef sockaddr_in6_emulation sockaddr_in6_type; +typedef sockaddr_storage_emulation sockaddr_storage_type; +typedef addrinfo_emulation addrinfo_type; +# else +typedef in6_addr in6_addr_type; +typedef ipv6_mreq in6_mreq_type; +typedef sockaddr_in6 sockaddr_in6_type; +typedef sockaddr_storage sockaddr_storage_type; +typedef addrinfo addrinfo_type; +# endif +typedef unsigned long ioctl_arg_type; +typedef u_long u_long_type; +typedef u_short u_short_type; +const int shutdown_receive = SD_RECEIVE; +const int shutdown_send = SD_SEND; +const int shutdown_both = SD_BOTH; +const int message_peek = MSG_PEEK; +const int message_out_of_band = MSG_OOB; +const int message_do_not_route = MSG_DONTROUTE; +#else +typedef int socket_type; +const int invalid_socket = -1; +const int socket_error_retval = -1; +const int max_addr_v4_str_len = INET_ADDRSTRLEN; +const int max_addr_v6_str_len = INET6_ADDRSTRLEN + 1 + IF_NAMESIZE; +typedef sockaddr socket_addr_type; +typedef socklen_t socket_addr_len_type; +typedef in_addr in4_addr_type; +typedef ip_mreq in4_mreq_type; +typedef sockaddr_in sockaddr_in4_type; +typedef in6_addr in6_addr_type; +typedef ipv6_mreq in6_mreq_type; +typedef sockaddr_in6 sockaddr_in6_type; +typedef sockaddr_storage sockaddr_storage_type; +typedef addrinfo addrinfo_type; +typedef int ioctl_arg_type; +typedef uint32_t u_long_type; +typedef uint16_t u_short_type; +const int shutdown_receive = SHUT_RD; +const int shutdown_send = SHUT_WR; +const int shutdown_both = SHUT_RDWR; +const int message_peek = MSG_PEEK; +const int message_out_of_band = MSG_OOB; +const int message_do_not_route = MSG_DONTROUTE; +#endif +const int custom_socket_option_level = 0xA5100000; +const int enable_connection_aborted_option = 1; +const int always_fail_option = 2; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_SOCKET_TYPES_HPP diff --git a/libtorrent/include/asio/detail/strand_service.hpp b/libtorrent/include/asio/detail/strand_service.hpp new file mode 100644 index 000000000..f10289090 --- /dev/null +++ b/libtorrent/include/asio/detail/strand_service.hpp @@ -0,0 +1,526 @@ +// +// strand_service.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_STRAND_SERVICE_HPP +#define ASIO_DETAIL_STRAND_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/call_stack.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/service_base.hpp" + +namespace asio { +namespace detail { + +// Default service implementation for a strand. +class strand_service + : public asio::detail::service_base +{ +public: + class handler_base; + class invoke_current_handler; + class post_next_waiter_on_exit; + + // The underlying implementation of a strand. + class strand_impl + { +#if defined (__BORLANDC__) + public: +#else + private: +#endif + void add_ref() + { + asio::detail::mutex::scoped_lock lock(mutex_); + ++ref_count_; + } + + void release() + { + asio::detail::mutex::scoped_lock lock(mutex_); + --ref_count_; + if (ref_count_ == 0) + { + lock.unlock(); + delete this; + } + } + + private: + // Only this service will have access to the internal values. + friend class strand_service; + friend class post_next_waiter_on_exit; + friend class invoke_current_handler; + + strand_impl(strand_service& owner) + : owner_(owner), + current_handler_(0), + first_waiter_(0), + last_waiter_(0), + ref_count_(0) + { + // Insert implementation into linked list of all implementations. + asio::detail::mutex::scoped_lock lock(owner_.mutex_); + next_ = owner_.impl_list_; + prev_ = 0; + if (owner_.impl_list_) + owner_.impl_list_->prev_ = this; + owner_.impl_list_ = this; + } + + ~strand_impl() + { + // Remove implementation from linked list of all implementations. + asio::detail::mutex::scoped_lock lock(owner_.mutex_); + if (owner_.impl_list_ == this) + owner_.impl_list_ = next_; + if (prev_) + prev_->next_ = next_; + if (next_) + next_->prev_= prev_; + next_ = 0; + prev_ = 0; + lock.unlock(); + + if (current_handler_) + { + current_handler_->destroy(); + } + + while (first_waiter_) + { + handler_base* next = first_waiter_->next_; + first_waiter_->destroy(); + first_waiter_ = next; + } + } + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // The service that owns this implementation. + strand_service& owner_; + + // The handler that is ready to execute. If this pointer is non-null then it + // indicates that a handler holds the lock. + handler_base* current_handler_; + + // The start of the list of waiting handlers for the strand. + handler_base* first_waiter_; + + // The end of the list of waiting handlers for the strand. + handler_base* last_waiter_; + + // Storage for posted handlers. + typedef boost::aligned_storage<64> handler_storage_type; +#if defined(__BORLANDC__) + boost::aligned_storage<64> handler_storage_; +#else + handler_storage_type handler_storage_; +#endif + + // Pointers to adjacent socket implementations in linked list. + strand_impl* next_; + strand_impl* prev_; + + // The reference count on the strand implementation. + size_t ref_count_; + +#if !defined(__BORLANDC__) + friend void intrusive_ptr_add_ref(strand_impl* p) + { + p->add_ref(); + } + + friend void intrusive_ptr_release(strand_impl* p) + { + p->release(); + } +#endif + }; + + friend class strand_impl; + + typedef boost::intrusive_ptr implementation_type; + + // Base class for all handler types. + class handler_base + { + public: + typedef void (*invoke_func_type)(handler_base*, + strand_service&, implementation_type&); + typedef void (*destroy_func_type)(handler_base*); + + handler_base(invoke_func_type invoke_func, destroy_func_type destroy_func) + : next_(0), + invoke_func_(invoke_func), + destroy_func_(destroy_func) + { + } + + void invoke(strand_service& service_impl, implementation_type& impl) + { + invoke_func_(this, service_impl, impl); + } + + void destroy() + { + destroy_func_(this); + } + + protected: + ~handler_base() + { + } + + private: + friend class strand_service; + friend class strand_impl; + friend class post_next_waiter_on_exit; + handler_base* next_; + invoke_func_type invoke_func_; + destroy_func_type destroy_func_; + }; + + // Helper class to allow handlers to be dispatched. + class invoke_current_handler + { + public: + invoke_current_handler(strand_service& service_impl, + const implementation_type& impl) + : service_impl_(service_impl), + impl_(impl) + { + } + + void operator()() + { + impl_->current_handler_->invoke(service_impl_, impl_); + } + + friend void* asio_handler_allocate(std::size_t size, + invoke_current_handler* this_handler) + { + return this_handler->do_handler_allocate(size); + } + + friend void asio_handler_deallocate(void*, std::size_t, + invoke_current_handler*) + { + } + + void* do_handler_allocate(std::size_t size) + { +#if defined(__BORLANDC__) + BOOST_ASSERT(size <= boost::aligned_storage<64>::size); +#else + BOOST_ASSERT(size <= strand_impl::handler_storage_type::size); +#endif + return impl_->handler_storage_.address(); + } + + // The asio_handler_invoke hook is not defined here since the default one + // provides the correct behaviour, and including it here breaks MSVC 7.1 + // in some situations. + + private: + strand_service& service_impl_; + implementation_type impl_; + }; + + // Helper class to automatically enqueue next waiter on block exit. + class post_next_waiter_on_exit + { + public: + post_next_waiter_on_exit(strand_service& service_impl, + implementation_type& impl) + : service_impl_(service_impl), + impl_(impl), + cancelled_(false) + { + } + + ~post_next_waiter_on_exit() + { + if (!cancelled_) + { + asio::detail::mutex::scoped_lock lock(impl_->mutex_); + impl_->current_handler_ = impl_->first_waiter_; + if (impl_->current_handler_) + { + impl_->first_waiter_ = impl_->first_waiter_->next_; + if (impl_->first_waiter_ == 0) + impl_->last_waiter_ = 0; + lock.unlock(); + service_impl_.io_service().post( + invoke_current_handler(service_impl_, impl_)); + } + } + } + + void cancel() + { + cancelled_ = true; + } + + private: + strand_service& service_impl_; + implementation_type& impl_; + bool cancelled_; + }; + + // Class template for a waiter. + template + class handler_wrapper + : public handler_base + { + public: + handler_wrapper(Handler handler) + : handler_base(&handler_wrapper::do_invoke, + &handler_wrapper::do_destroy), + handler_(handler) + { + } + + static void do_invoke(handler_base* base, + strand_service& service_impl, implementation_type& impl) + { + // Take ownership of the handler object. + typedef handler_wrapper this_type; + this_type* h(static_cast(base)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(h->handler_, h); + + post_next_waiter_on_exit p1(service_impl, impl); + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(h->handler_); + + // A handler object must still be valid when the next waiter is posted + // since destroying the last handler might cause the strand object to be + // destroyed. Therefore we create a second post_next_waiter_on_exit object + // that will be destroyed before the handler object. + p1.cancel(); + post_next_waiter_on_exit p2(service_impl, impl); + + // Free the memory associated with the handler. + ptr.reset(); + + // Indicate that this strand is executing on the current thread. + call_stack::context ctx(impl.get()); + + // Make the upcall. + asio_handler_invoke_helpers::invoke(handler, &handler); + } + + static void do_destroy(handler_base* base) + { + // Take ownership of the handler object. + typedef handler_wrapper this_type; + this_type* h(static_cast(base)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(h->handler_, h); + } + + private: + Handler handler_; + }; + + // Construct a new strand service for the specified io_service. + explicit strand_service(asio::io_service& io_service) + : asio::detail::service_base(io_service), + mutex_(), + impl_list_(0) + { + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + // Construct a list of all handlers to be destroyed. + asio::detail::mutex::scoped_lock lock(mutex_); + strand_impl* impl = impl_list_; + handler_base* first_handler = 0; + while (impl) + { + if (impl->current_handler_) + { + impl->current_handler_->next_ = first_handler; + first_handler = impl->current_handler_; + impl->current_handler_ = 0; + } + if (impl->first_waiter_) + { + impl->last_waiter_->next_ = first_handler; + first_handler = impl->first_waiter_; + impl->first_waiter_ = 0; + impl->last_waiter_ = 0; + } + impl = impl->next_; + } + + // Destroy all handlers without holding the lock. + lock.unlock(); + while (first_handler) + { + handler_base* next = first_handler->next_; + first_handler->destroy(); + first_handler = next; + } + } + + // Construct a new strand implementation. + void construct(implementation_type& impl) + { + impl = implementation_type(new strand_impl(*this)); + } + + // Destroy a strand implementation. + void destroy(implementation_type& impl) + { + implementation_type().swap(impl); + } + + // Request the io_service to invoke the given handler. + template + void dispatch(implementation_type& impl, Handler handler) + { + if (call_stack::contains(impl.get())) + { + asio_handler_invoke_helpers::invoke(handler, &handler); + } + else + { + asio::detail::mutex::scoped_lock lock(impl->mutex_); + + // Allocate and construct an object to wrap the handler. + typedef handler_wrapper value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, handler); + + if (impl->current_handler_ == 0) + { + // This handler now has the lock, so can be dispatched immediately. + impl->current_handler_ = ptr.get(); + lock.unlock(); + this->io_service().dispatch(invoke_current_handler(*this, impl)); + ptr.release(); + } + else + { + // Another handler already holds the lock, so this handler must join + // the list of waiters. The handler will be posted automatically when + // its turn comes. + if (impl->last_waiter_) + { + impl->last_waiter_->next_ = ptr.get(); + impl->last_waiter_ = impl->last_waiter_->next_; + } + else + { + impl->first_waiter_ = ptr.get(); + impl->last_waiter_ = ptr.get(); + } + ptr.release(); + } + } + } + + // Request the io_service to invoke the given handler and return immediately. + template + void post(implementation_type& impl, Handler handler) + { + asio::detail::mutex::scoped_lock lock(impl->mutex_); + + // Allocate and construct an object to wrap the handler. + typedef handler_wrapper value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, handler); + + if (impl->current_handler_ == 0) + { + // This handler now has the lock, so can be dispatched immediately. + impl->current_handler_ = ptr.get(); + lock.unlock(); + this->io_service().post(invoke_current_handler(*this, impl)); + ptr.release(); + } + else + { + // Another handler already holds the lock, so this handler must join the + // list of waiters. The handler will be posted automatically when its turn + // comes. + if (impl->last_waiter_) + { + impl->last_waiter_->next_ = ptr.get(); + impl->last_waiter_ = impl->last_waiter_->next_; + } + else + { + impl->first_waiter_ = ptr.get(); + impl->last_waiter_ = ptr.get(); + } + ptr.release(); + } + } + +private: + // Mutex to protect access to the linked list of implementations. + asio::detail::mutex mutex_; + + // The head of a linked list of all implementations. + strand_impl* impl_list_; +}; + +} // namespace detail +} // namespace asio + +#if defined(__BORLANDC__) + +namespace boost { + +inline void intrusive_ptr_add_ref( + asio::detail::strand_service::strand_impl* p) +{ + p->add_ref(); +} + +inline void intrusive_ptr_release( + asio::detail::strand_service::strand_impl* p) +{ + p->release(); +} + +} // namespace boost + +#endif // defined(__BORLANDC__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_STRAND_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/task_io_service.hpp b/libtorrent/include/asio/detail/task_io_service.hpp new file mode 100644 index 000000000..07df1c18a --- /dev/null +++ b/libtorrent/include/asio/detail/task_io_service.hpp @@ -0,0 +1,538 @@ +// +// task_io_service.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_TASK_IO_SERVICE_HPP +#define ASIO_DETAIL_TASK_IO_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/error_code.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/call_stack.hpp" +#include "asio/detail/event.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/task_io_service_fwd.hpp" + +namespace asio { +namespace detail { + +template +class task_io_service + : public asio::detail::service_base > +{ +public: + // Constructor. + task_io_service(asio::io_service& io_service) + : asio::detail::service_base >(io_service), + mutex_(), + task_(use_service(io_service)), + outstanding_work_(0), + handler_queue_(&task_handler_), + handler_queue_end_(&task_handler_), + stopped_(false), + shutdown_(false), + first_idle_thread_(0) + { + } + + void init(size_t /*concurrency_hint*/) + { + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + asio::detail::mutex::scoped_lock lock(mutex_); + shutdown_ = true; + lock.unlock(); + + // Destroy handler objects. + while (handler_queue_) + { + handler_base* h = handler_queue_; + handler_queue_ = h->next_; + if (h != &task_handler_) + h->destroy(); + } + + // Reset handler queue to initial state. + handler_queue_ = &task_handler_; + handler_queue_end_ = &task_handler_; + } + + // Run the event loop until interrupted or no more work. + size_t run(asio::error_code& ec) + { + typename call_stack::context ctx(this); + + idle_thread_info this_idle_thread; + this_idle_thread.prev = &this_idle_thread; + this_idle_thread.next = &this_idle_thread; + + asio::detail::mutex::scoped_lock lock(mutex_); + + size_t n = 0; + while (do_one(lock, &this_idle_thread, ec)) + if (n != (std::numeric_limits::max)()) + ++n; + return n; + } + + // Run until interrupted or one operation is performed. + size_t run_one(asio::error_code& ec) + { + typename call_stack::context ctx(this); + + idle_thread_info this_idle_thread; + this_idle_thread.prev = &this_idle_thread; + this_idle_thread.next = &this_idle_thread; + + asio::detail::mutex::scoped_lock lock(mutex_); + + return do_one(lock, &this_idle_thread, ec); + } + + // Poll for operations without blocking. + size_t poll(asio::error_code& ec) + { + typename call_stack::context ctx(this); + + asio::detail::mutex::scoped_lock lock(mutex_); + + size_t n = 0; + while (do_one(lock, 0, ec)) + if (n != (std::numeric_limits::max)()) + ++n; + return n; + } + + // Poll for one operation without blocking. + size_t poll_one(asio::error_code& ec) + { + typename call_stack::context ctx(this); + + asio::detail::mutex::scoped_lock lock(mutex_); + + return do_one(lock, 0, ec); + } + + // Interrupt the event processing loop. + void stop() + { + asio::detail::mutex::scoped_lock lock(mutex_); + stop_all_threads(); + } + + // Reset in preparation for a subsequent run invocation. + void reset() + { + asio::detail::mutex::scoped_lock lock(mutex_); + stopped_ = false; + } + + // Notify that some work has started. + void work_started() + { + asio::detail::mutex::scoped_lock lock(mutex_); + ++outstanding_work_; + } + + // Notify that some work has finished. + void work_finished() + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (--outstanding_work_ == 0) + stop_all_threads(); + } + + // Request invocation of the given handler. + template + void dispatch(Handler handler) + { + if (call_stack::contains(this)) + asio_handler_invoke_helpers::invoke(handler, &handler); + else + post(handler); + } + + // Request invocation of the given handler and return immediately. + template + void post(Handler handler) + { + // Allocate and construct an operation to wrap the handler. + typedef handler_wrapper value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, handler); + + asio::detail::mutex::scoped_lock lock(mutex_); + + // If the service has been shut down we silently discard the handler. + if (shutdown_) + return; + + // Add the handler to the end of the queue. + if (handler_queue_end_) + { + handler_queue_end_->next_ = ptr.get(); + handler_queue_end_ = ptr.get(); + } + else + { + handler_queue_ = handler_queue_end_ = ptr.get(); + } + ptr.release(); + + // An undelivered handler is treated as unfinished work. + ++outstanding_work_; + + // Wake up a thread to execute the handler. + if (!interrupt_one_idle_thread()) + if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) + task_.interrupt(); + } + +private: + struct idle_thread_info; + + size_t do_one(asio::detail::mutex::scoped_lock& lock, + idle_thread_info* this_idle_thread, asio::error_code& ec) + { + if (outstanding_work_ == 0 && !stopped_) + { + stop_all_threads(); + ec = asio::error_code(); + return 0; + } + + bool polling = !this_idle_thread; + bool task_has_run = false; + while (!stopped_) + { + if (handler_queue_) + { + // Prepare to execute first handler from queue. + handler_base* h = handler_queue_; + handler_queue_ = h->next_; + if (handler_queue_ == 0) + handler_queue_end_ = 0; + bool more_handlers = (handler_queue_ != 0); + lock.unlock(); + + if (h == &task_handler_) + { + // If the task has already run and we're polling then we're done. + if (task_has_run && polling) + { + ec = asio::error_code(); + return 0; + } + task_has_run = true; + + task_cleanup c(lock, *this); + + // Run the task. May throw an exception. Only block if the handler + // queue is empty and we have an idle_thread_info object, otherwise + // we want to return as soon as possible. + task_.run(!more_handlers && !polling); + } + else + { + handler_cleanup c(lock, *this); + + // Invoke the handler. May throw an exception. + h->call(); // call() deletes the handler object + + ec = asio::error_code(); + return 1; + } + } + else if (this_idle_thread) + { + // Nothing to run right now, so just wait for work to do. + if (first_idle_thread_) + { + this_idle_thread->next = first_idle_thread_; + this_idle_thread->prev = first_idle_thread_->prev; + first_idle_thread_->prev->next = this_idle_thread; + first_idle_thread_->prev = this_idle_thread; + } + first_idle_thread_ = this_idle_thread; + this_idle_thread->wakeup_event.clear(); + lock.unlock(); + this_idle_thread->wakeup_event.wait(); + lock.lock(); + if (this_idle_thread->next == this_idle_thread) + { + first_idle_thread_ = 0; + } + else + { + if (first_idle_thread_ == this_idle_thread) + first_idle_thread_ = this_idle_thread->next; + this_idle_thread->next->prev = this_idle_thread->prev; + this_idle_thread->prev->next = this_idle_thread->next; + this_idle_thread->next = this_idle_thread; + this_idle_thread->prev = this_idle_thread; + } + } + else + { + ec = asio::error_code(); + return 0; + } + } + + ec = asio::error_code(); + return 0; + } + + // Stop the task and all idle threads. + void stop_all_threads() + { + stopped_ = true; + interrupt_all_idle_threads(); + if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) + task_.interrupt(); + } + + // Interrupt a single idle thread. Returns true if a thread was interrupted, + // false if no running thread could be found to interrupt. + bool interrupt_one_idle_thread() + { + if (first_idle_thread_) + { + first_idle_thread_->wakeup_event.signal(); + first_idle_thread_ = first_idle_thread_->next; + return true; + } + return false; + } + + // Interrupt all idle threads. + void interrupt_all_idle_threads() + { + if (first_idle_thread_) + { + first_idle_thread_->wakeup_event.signal(); + idle_thread_info* current_idle_thread = first_idle_thread_->next; + while (current_idle_thread != first_idle_thread_) + { + current_idle_thread->wakeup_event.signal(); + current_idle_thread = current_idle_thread->next; + } + } + } + + class task_cleanup; + friend class task_cleanup; + + // The base class for all handler wrappers. A function pointer is used + // instead of virtual functions to avoid the associated overhead. + class handler_base + { + public: + typedef void (*call_func_type)(handler_base*); + typedef void (*destroy_func_type)(handler_base*); + + handler_base(call_func_type call_func, destroy_func_type destroy_func) + : next_(0), + call_func_(call_func), + destroy_func_(destroy_func) + { + } + + void call() + { + call_func_(this); + } + + void destroy() + { + destroy_func_(this); + } + + protected: + // Prevent deletion through this type. + ~handler_base() + { + } + + private: + friend class task_io_service; + friend class task_cleanup; + handler_base* next_; + call_func_type call_func_; + destroy_func_type destroy_func_; + }; + + // Template wrapper for handlers. + template + class handler_wrapper + : public handler_base + { + public: + handler_wrapper(Handler handler) + : handler_base(&handler_wrapper::do_call, + &handler_wrapper::do_destroy), + handler_(handler) + { + } + + static void do_call(handler_base* base) + { + // Take ownership of the handler object. + typedef handler_wrapper this_type; + this_type* h(static_cast(base)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(h->handler_, h); + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(h->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Make the upcall. + asio_handler_invoke_helpers::invoke(handler, &handler); + } + + static void do_destroy(handler_base* base) + { + // Take ownership of the handler object. + typedef handler_wrapper this_type; + this_type* h(static_cast(base)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(h->handler_, h); + } + + private: + Handler handler_; + }; + + // Helper class to perform task-related operations on block exit. + class task_cleanup + { + public: + task_cleanup(asio::detail::mutex::scoped_lock& lock, + task_io_service& task_io_svc) + : lock_(lock), + task_io_service_(task_io_svc) + { + } + + ~task_cleanup() + { + // Reinsert the task at the end of the handler queue. + lock_.lock(); + task_io_service_.task_handler_.next_ = 0; + if (task_io_service_.handler_queue_end_) + { + task_io_service_.handler_queue_end_->next_ + = &task_io_service_.task_handler_; + task_io_service_.handler_queue_end_ + = &task_io_service_.task_handler_; + } + else + { + task_io_service_.handler_queue_ + = task_io_service_.handler_queue_end_ + = &task_io_service_.task_handler_; + } + } + + private: + asio::detail::mutex::scoped_lock& lock_; + task_io_service& task_io_service_; + }; + + // Helper class to perform handler-related operations on block exit. + class handler_cleanup; + friend class handler_cleanup; + class handler_cleanup + { + public: + handler_cleanup(asio::detail::mutex::scoped_lock& lock, + task_io_service& task_io_svc) + : lock_(lock), + task_io_service_(task_io_svc) + { + } + + ~handler_cleanup() + { + lock_.lock(); + if (--task_io_service_.outstanding_work_ == 0) + task_io_service_.stop_all_threads(); + } + + private: + asio::detail::mutex::scoped_lock& lock_; + task_io_service& task_io_service_; + }; + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // The task to be run by this service. + Task& task_; + + // Handler object to represent the position of the task in the queue. + class task_handler + : public handler_base + { + public: + task_handler() + : handler_base(0, 0) + { + } + } task_handler_; + + // The count of unfinished work. + int outstanding_work_; + + // The start of a linked list of handlers that are ready to be delivered. + handler_base* handler_queue_; + + // The end of a linked list of handlers that are ready to be delivered. + handler_base* handler_queue_end_; + + // Flag to indicate that the dispatcher has been stopped. + bool stopped_; + + // Flag to indicate that the dispatcher has been shut down. + bool shutdown_; + + // Structure containing information about an idle thread. + struct idle_thread_info + { + event wakeup_event; + idle_thread_info* prev; + idle_thread_info* next; + }; + + // The number of threads that are currently idle. + idle_thread_info* first_idle_thread_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_TASK_IO_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/task_io_service_fwd.hpp b/libtorrent/include/asio/detail/task_io_service_fwd.hpp new file mode 100644 index 000000000..208a9d3ca --- /dev/null +++ b/libtorrent/include/asio/detail/task_io_service_fwd.hpp @@ -0,0 +1,31 @@ +// +// task_io_service_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_TASK_IO_SERVICE_FWD_HPP +#define ASIO_DETAIL_TASK_IO_SERVICE_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace detail { + +template +class task_io_service; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_TASK_IO_SERVICE_FWD_HPP diff --git a/libtorrent/include/asio/detail/thread.hpp b/libtorrent/include/asio/detail/thread.hpp new file mode 100644 index 000000000..b334c38af --- /dev/null +++ b/libtorrent/include/asio/detail/thread.hpp @@ -0,0 +1,50 @@ +// +// thread.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_THREAD_HPP +#define ASIO_DETAIL_THREAD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) +# include "asio/detail/null_thread.hpp" +#elif defined(BOOST_WINDOWS) +# include "asio/detail/win_thread.hpp" +#elif defined(BOOST_HAS_PTHREADS) +# include "asio/detail/posix_thread.hpp" +#else +# error Only Windows and POSIX are supported! +#endif + +namespace asio { +namespace detail { + +#if !defined(BOOST_HAS_THREADS) +typedef null_thread thread; +#elif defined(BOOST_WINDOWS) +typedef win_thread thread; +#elif defined(BOOST_HAS_PTHREADS) +typedef posix_thread thread; +#endif + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_THREAD_HPP diff --git a/libtorrent/include/asio/detail/throw_error.hpp b/libtorrent/include/asio/detail/throw_error.hpp new file mode 100644 index 000000000..d2ad09a2e --- /dev/null +++ b/libtorrent/include/asio/detail/throw_error.hpp @@ -0,0 +1,44 @@ +// +// throw_error.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_THROW_ERROR_HPP +#define ASIO_DETAIL_THROW_ERROR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error_code.hpp" +#include "asio/system_error.hpp" + +namespace asio { +namespace detail { + +inline void throw_error(const asio::error_code& err) +{ + if (err) + { + asio::system_error e(err); + boost::throw_exception(e); + } +} + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_THROW_ERROR_HPP diff --git a/libtorrent/include/asio/detail/timer_queue.hpp b/libtorrent/include/asio/detail/timer_queue.hpp new file mode 100644 index 000000000..af1e36bd5 --- /dev/null +++ b/libtorrent/include/asio/detail/timer_queue.hpp @@ -0,0 +1,348 @@ +// +// timer_queue.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_TIMER_QUEUE_HPP +#define ASIO_DETAIL_TIMER_QUEUE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/hash_map.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/timer_queue_base.hpp" + +namespace asio { +namespace detail { + +template +class timer_queue + : public timer_queue_base +{ +public: + // The time type. + typedef typename Time_Traits::time_type time_type; + + // The duration type. + typedef typename Time_Traits::duration_type duration_type; + + // Constructor. + timer_queue() + : timers_(), + heap_() + { + } + + // Add a new timer to the queue. Returns true if this is the timer that is + // earliest in the queue, in which case the reactor's event demultiplexing + // function call may need to be interrupted and restarted. + template + bool enqueue_timer(const time_type& time, Handler handler, void* token) + { + // Ensure that there is space for the timer in the heap. We reserve here so + // that the push_back below will not throw due to a reallocation failure. + heap_.reserve(heap_.size() + 1); + + // Create a new timer object. + std::auto_ptr > new_timer( + new timer(time, handler, token)); + + // Insert the new timer into the hash. + typedef typename hash_map::iterator iterator; + typedef typename hash_map::value_type value_type; + std::pair result = + timers_.insert(value_type(token, new_timer.get())); + if (!result.second) + { + result.first->second->prev_ = new_timer.get(); + new_timer->next_ = result.first->second; + result.first->second = new_timer.get(); + } + + // Put the timer at the correct position in the heap. + new_timer->heap_index_ = heap_.size(); + heap_.push_back(new_timer.get()); + up_heap(heap_.size() - 1); + bool is_first = (heap_[0] == new_timer.get()); + + // Ownership of the timer is transferred to the timer queue. + new_timer.release(); + + return is_first; + } + + // Whether there are no timers in the queue. + virtual bool empty() const + { + return heap_.empty(); + } + + // Get the time for the timer that is earliest in the queue. + virtual boost::posix_time::time_duration wait_duration() const + { + return Time_Traits::to_posix_duration( + Time_Traits::subtract(heap_[0]->time_, Time_Traits::now())); + } + + // Dispatch the timers that are earlier than the specified time. + virtual void dispatch_timers() + { + const time_type now = Time_Traits::now(); + while (!heap_.empty() && !Time_Traits::less_than(now, heap_[0]->time_)) + { + timer_base* t = heap_[0]; + remove_timer(t); + t->invoke(asio::error_code()); + } + } + + // Cancel the timer with the given token. The handler will be invoked + // immediately with the result operation_aborted. + std::size_t cancel_timer(void* timer_token) + { + std::size_t num_cancelled = 0; + typedef typename hash_map::iterator iterator; + iterator it = timers_.find(timer_token); + if (it != timers_.end()) + { + timer_base* t = it->second; + while (t) + { + timer_base* next = t->next_; + remove_timer(t); + t->invoke(asio::error::operation_aborted); + t = next; + ++num_cancelled; + } + } + return num_cancelled; + } + + // Destroy all timers. + virtual void destroy_timers() + { + typename hash_map::iterator i = timers_.begin(); + typename hash_map::iterator end = timers_.end(); + while (i != end) + { + timer_base* t = i->second; + typename hash_map::iterator old_i = i++; + timers_.erase(old_i); + t->destroy(); + } + heap_.clear(); + timers_.clear(); + } + +private: + // Base class for timer operations. Function pointers are used instead of + // virtual functions to avoid the associated overhead. + class timer_base + { + public: + // Perform the timer operation and then destroy. + void invoke(const asio::error_code& result) + { + invoke_func_(this, result); + } + + // Destroy the timer operation. + void destroy() + { + destroy_func_(this); + } + + protected: + typedef void (*invoke_func_type)(timer_base*, + const asio::error_code&); + typedef void (*destroy_func_type)(timer_base*); + + // Constructor. + timer_base(invoke_func_type invoke_func, destroy_func_type destroy_func, + const time_type& time, void* token) + : invoke_func_(invoke_func), + destroy_func_(destroy_func), + time_(time), + token_(token), + next_(0), + prev_(0), + heap_index_( + std::numeric_limits::max BOOST_PREVENT_MACRO_SUBSTITUTION()) + { + } + + // Prevent deletion through this type. + ~timer_base() + { + } + + private: + friend class timer_queue; + + // The function to be called to dispatch the handler. + invoke_func_type invoke_func_; + + // The function to be called to destroy the handler. + destroy_func_type destroy_func_; + + // The time when the operation should fire. + time_type time_; + + // The token associated with the timer. + void* token_; + + // The next timer known to the queue. + timer_base* next_; + + // The previous timer known to the queue. + timer_base* prev_; + + // The index of the timer in the heap. + size_t heap_index_; + }; + + // Adaptor class template for using handlers in timers. + template + class timer + : public timer_base + { + public: + // Constructor. + timer(const time_type& time, Handler handler, void* token) + : timer_base(&timer::invoke_handler, + &timer::destroy_handler, time, token), + handler_(handler) + { + } + + // Invoke the handler and then destroy it. + static void invoke_handler(timer_base* base, + const asio::error_code& result) + { + std::auto_ptr > t(static_cast*>(base)); + t->handler_(result); + } + + // Destroy the handler. + static void destroy_handler(timer_base* base) + { + delete static_cast*>(base); + } + + private: + Handler handler_; + }; + + // Move the item at the given index up the heap to its correct position. + void up_heap(size_t index) + { + size_t parent = (index - 1) / 2; + while (index > 0 + && Time_Traits::less_than(heap_[index]->time_, heap_[parent]->time_)) + { + swap_heap(index, parent); + index = parent; + parent = (index - 1) / 2; + } + } + + // Move the item at the given index down the heap to its correct position. + void down_heap(size_t index) + { + size_t child = index * 2 + 1; + while (child < heap_.size()) + { + size_t min_child = (child + 1 == heap_.size() + || Time_Traits::less_than( + heap_[child]->time_, heap_[child + 1]->time_)) + ? child : child + 1; + if (Time_Traits::less_than(heap_[index]->time_, heap_[min_child]->time_)) + break; + swap_heap(index, min_child); + index = min_child; + child = index * 2 + 1; + } + } + + // Swap two entries in the heap. + void swap_heap(size_t index1, size_t index2) + { + timer_base* tmp = heap_[index1]; + heap_[index1] = heap_[index2]; + heap_[index2] = tmp; + heap_[index1]->heap_index_ = index1; + heap_[index2]->heap_index_ = index2; + } + + // Remove a timer from the heap and list of timers. + void remove_timer(timer_base* t) + { + // Remove the timer from the heap. + size_t index = t->heap_index_; + if (!heap_.empty() && index < heap_.size()) + { + if (index == heap_.size() - 1) + { + heap_.pop_back(); + } + else + { + swap_heap(index, heap_.size() - 1); + heap_.pop_back(); + size_t parent = (index - 1) / 2; + if (index > 0 && Time_Traits::less_than( + heap_[index]->time_, heap_[parent]->time_)) + up_heap(index); + else + down_heap(index); + } + } + + // Remove the timer from the hash. + typedef typename hash_map::iterator iterator; + iterator it = timers_.find(t->token_); + if (it != timers_.end()) + { + if (it->second == t) + it->second = t->next_; + if (t->prev_) + t->prev_->next_ = t->next_; + if (t->next_) + t->next_->prev_ = t->prev_; + if (it->second == 0) + timers_.erase(it); + } + } + + // A hash of timer token to linked lists of timers. + hash_map timers_; + + // The heap of timers, with the earliest timer at the front. + std::vector heap_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_TIMER_QUEUE_HPP diff --git a/libtorrent/include/asio/detail/timer_queue_base.hpp b/libtorrent/include/asio/detail/timer_queue_base.hpp new file mode 100644 index 000000000..c8be49748 --- /dev/null +++ b/libtorrent/include/asio/detail/timer_queue_base.hpp @@ -0,0 +1,56 @@ +// +// timer_queue_base.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_TIMER_QUEUE_BASE_HPP +#define ASIO_DETAIL_TIMER_QUEUE_BASE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_types.hpp" // Must come before posix_time. + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class timer_queue_base + : private noncopyable +{ +public: + // Destructor. + virtual ~timer_queue_base() {} + + // Whether there are no timers in the queue. + virtual bool empty() const = 0; + + // Get the time to wait until the next timer. + virtual boost::posix_time::time_duration wait_duration() const = 0; + + // Dispatch all ready timers. + virtual void dispatch_timers() = 0; + + // Destroy all timers. + virtual void destroy_timers() = 0; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_TIMER_QUEUE_BASE_HPP diff --git a/libtorrent/include/asio/detail/tss_ptr.hpp b/libtorrent/include/asio/detail/tss_ptr.hpp new file mode 100644 index 000000000..8a860a68f --- /dev/null +++ b/libtorrent/include/asio/detail/tss_ptr.hpp @@ -0,0 +1,65 @@ +// +// tss_ptr.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_TSS_PTR_HPP +#define ASIO_DETAIL_TSS_PTR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if !defined(BOOST_HAS_THREADS) +# include "asio/detail/null_tss_ptr.hpp" +#elif defined(BOOST_WINDOWS) +# include "asio/detail/win_tss_ptr.hpp" +#elif defined(BOOST_HAS_PTHREADS) +# include "asio/detail/posix_tss_ptr.hpp" +#else +# error Only Windows and POSIX are supported! +#endif + +namespace asio { +namespace detail { + +template +class tss_ptr +#if !defined(BOOST_HAS_THREADS) + : public null_tss_ptr +#elif defined(BOOST_WINDOWS) + : public win_tss_ptr +#elif defined(BOOST_HAS_PTHREADS) + : public posix_tss_ptr +#endif +{ +public: + void operator=(T* value) + { +#if !defined(BOOST_HAS_THREADS) + null_tss_ptr::operator=(value); +#elif defined(BOOST_WINDOWS) + win_tss_ptr::operator=(value); +#elif defined(BOOST_HAS_PTHREADS) + posix_tss_ptr::operator=(value); +#endif + } +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_TSS_PTR_HPP diff --git a/libtorrent/include/asio/detail/win_event.hpp b/libtorrent/include/asio/detail/win_event.hpp new file mode 100644 index 000000000..8de9383da --- /dev/null +++ b/libtorrent/include/asio/detail/win_event.hpp @@ -0,0 +1,90 @@ +// +// win_event.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_EVENT_HPP +#define ASIO_DETAIL_WIN_EVENT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +class win_event + : private noncopyable +{ +public: + // Constructor. + win_event() + : event_(::CreateEvent(0, true, false, 0)) + { + if (!event_) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "event"); + boost::throw_exception(e); + } + } + + // Destructor. + ~win_event() + { + ::CloseHandle(event_); + } + + // Signal the event. + void signal() + { + ::SetEvent(event_); + } + + // Reset the event. + void clear() + { + ::ResetEvent(event_); + } + + // Wait for the event to become signalled. + void wait() + { + ::WaitForSingleObject(event_, INFINITE); + } + +private: + HANDLE event_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_EVENT_HPP diff --git a/libtorrent/include/asio/detail/win_fd_set_adapter.hpp b/libtorrent/include/asio/detail/win_fd_set_adapter.hpp new file mode 100644 index 000000000..f2632c4d0 --- /dev/null +++ b/libtorrent/include/asio/detail/win_fd_set_adapter.hpp @@ -0,0 +1,84 @@ +// +// win_fd_set_adapter.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_FD_SET_ADAPTER_HPP +#define ASIO_DETAIL_WIN_FD_SET_ADAPTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_types.hpp" + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +namespace asio { +namespace detail { + +// Adapts the FD_SET type to meet the Descriptor_Set concept's requirements. +class win_fd_set_adapter +{ +public: + enum { win_fd_set_size = 1024 }; + + win_fd_set_adapter() + : max_descriptor_(invalid_socket) + { + fd_set_.fd_count = 0; + } + + void set(socket_type descriptor) + { + for (u_int i = 0; i < fd_set_.fd_count; ++i) + if (fd_set_.fd_array[i] == descriptor) + return; + if (fd_set_.fd_count < win_fd_set_size) + fd_set_.fd_array[fd_set_.fd_count++] = descriptor; + } + + bool is_set(socket_type descriptor) const + { + return !!__WSAFDIsSet(descriptor, + const_cast(reinterpret_cast(&fd_set_))); + } + + operator fd_set*() + { + return reinterpret_cast(&fd_set_); + } + + socket_type max_descriptor() const + { + return max_descriptor_; + } + +private: + // This structure is defined to be compatible with the Windows API fd_set + // structure, but without being dependent on the value of FD_SETSIZE. + struct win_fd_set + { + u_int fd_count; + SOCKET fd_array[win_fd_set_size]; + }; + + win_fd_set fd_set_; + socket_type max_descriptor_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_FD_SET_ADAPTER_HPP diff --git a/libtorrent/include/asio/detail/win_iocp_io_service.hpp b/libtorrent/include/asio/detail/win_iocp_io_service.hpp new file mode 100644 index 000000000..4957fb01a --- /dev/null +++ b/libtorrent/include/asio/detail/win_iocp_io_service.hpp @@ -0,0 +1,424 @@ +// +// win_iocp_io_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_IOCP_IO_SERVICE_HPP +#define ASIO_DETAIL_WIN_IOCP_IO_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/win_iocp_io_service_fwd.hpp" + +#if defined(ASIO_HAS_IOCP) + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/call_stack.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/win_iocp_operation.hpp" + +namespace asio { +namespace detail { + +class win_iocp_io_service + : public asio::detail::service_base +{ +public: + // Base class for all operations. + typedef win_iocp_operation operation; + + // Constructor. + win_iocp_io_service(asio::io_service& io_service) + : asio::detail::service_base(io_service), + iocp_(), + outstanding_work_(0), + stopped_(0), + shutdown_(0) + { + } + + void init(size_t concurrency_hint) + { + iocp_.handle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, + static_cast((std::min)(concurrency_hint, DWORD(~0)))); + if (!iocp_.handle) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "iocp"); + boost::throw_exception(e); + } + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + ::InterlockedExchange(&shutdown_, 1); + + for (;;) + { + DWORD bytes_transferred = 0; +#if (WINVER < 0x0500) + DWORD completion_key = 0; +#else + DWORD_PTR completion_key = 0; +#endif + LPOVERLAPPED overlapped = 0; + ::SetLastError(0); + BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, + &bytes_transferred, &completion_key, &overlapped, 0); + DWORD last_error = ::GetLastError(); + if (!ok && overlapped == 0 && last_error == WAIT_TIMEOUT) + break; + if (overlapped) + static_cast(overlapped)->destroy(); + } + } + + // Register a handle with the IO completion port. + void register_handle(HANDLE handle) + { + ::CreateIoCompletionPort(handle, iocp_.handle, 0, 0); + } + + // Run the event loop until stopped or no more work. + size_t run(asio::error_code& ec) + { + if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) + { + ec = asio::error_code(); + return 0; + } + + call_stack::context ctx(this); + + size_t n = 0; + while (do_one(true, ec)) + if (n != (std::numeric_limits::max)()) + ++n; + return n; + } + + // Run until stopped or one operation is performed. + size_t run_one(asio::error_code& ec) + { + if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) + { + ec = asio::error_code(); + return 0; + } + + call_stack::context ctx(this); + + return do_one(true, ec); + } + + // Poll for operations without blocking. + size_t poll(asio::error_code& ec) + { + if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) + { + ec = asio::error_code(); + return 0; + } + + call_stack::context ctx(this); + + size_t n = 0; + while (do_one(false, ec)) + if (n != (std::numeric_limits::max)()) + ++n; + return n; + } + + // Poll for one operation without blocking. + size_t poll_one(asio::error_code& ec) + { + if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) + { + ec = asio::error_code(); + return 0; + } + + call_stack::context ctx(this); + + return do_one(false, ec); + } + + // Stop the event processing loop. + void stop() + { + if (::InterlockedExchange(&stopped_, 1) == 0) + { + if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "pqcs"); + boost::throw_exception(e); + } + } + } + + // Reset in preparation for a subsequent run invocation. + void reset() + { + ::InterlockedExchange(&stopped_, 0); + } + + // Notify that some work has started. + void work_started() + { + ::InterlockedIncrement(&outstanding_work_); + } + + // Notify that some work has finished. + void work_finished() + { + if (::InterlockedDecrement(&outstanding_work_) == 0) + stop(); + } + + // Request invocation of the given handler. + template + void dispatch(Handler handler) + { + if (call_stack::contains(this)) + asio_handler_invoke_helpers::invoke(handler, &handler); + else + post(handler); + } + + // Request invocation of the given handler and return immediately. + template + void post(Handler handler) + { + // If the service has been shut down we silently discard the handler. + if (::InterlockedExchangeAdd(&shutdown_, 0) != 0) + return; + + // Allocate and construct an operation to wrap the handler. + typedef handler_operation value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, *this, handler); + + // Enqueue the operation on the I/O completion port. + if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, ptr.get())) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "pqcs"); + boost::throw_exception(e); + } + + // Operation has been successfully posted. + ptr.release(); + } + + // Request invocation of the given OVERLAPPED-derived operation. + void post_completion(win_iocp_operation* op, DWORD op_last_error, + DWORD bytes_transferred) + { + // Enqueue the operation on the I/O completion port. + if (!::PostQueuedCompletionStatus(iocp_.handle, + bytes_transferred, op_last_error, op)) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "pqcs"); + boost::throw_exception(e); + } + } + +private: + // Dequeues at most one operation from the I/O completion port, and then + // executes it. Returns the number of operations that were dequeued (i.e. + // either 0 or 1). + size_t do_one(bool block, asio::error_code& ec) + { + for (;;) + { + // Get the next operation from the queue. + DWORD bytes_transferred = 0; +#if (WINVER < 0x0500) + DWORD completion_key = 0; +#else + DWORD_PTR completion_key = 0; +#endif + LPOVERLAPPED overlapped = 0; + ::SetLastError(0); + BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, &bytes_transferred, + &completion_key, &overlapped, block ? 1000 : 0); + DWORD last_error = ::GetLastError(); + + if (!ok && overlapped == 0) + { + if (block && last_error == WAIT_TIMEOUT) + continue; + ec = asio::error_code(); + return 0; + } + + if (overlapped) + { + // We may have been passed a last_error value in the completion_key. + if (last_error == 0) + { + last_error = completion_key; + } + + // Ensure that the io_service does not exit due to running out of work + // while we make the upcall. + auto_work work(*this); + + // Dispatch the operation. + operation* op = static_cast(overlapped); + op->do_completion(last_error, bytes_transferred); + + ec = asio::error_code(); + return 1; + } + else + { + // The stopped_ flag is always checked to ensure that any leftover + // interrupts from a previous run invocation are ignored. + if (::InterlockedExchangeAdd(&stopped_, 0) != 0) + { + // Wake up next thread that is blocked on GetQueuedCompletionStatus. + if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) + { + DWORD last_error = ::GetLastError(); + ec = asio::error_code(last_error, + asio::native_ecat); + return 0; + } + + ec = asio::error_code(); + return 0; + } + } + } + } + + struct auto_work + { + auto_work(win_iocp_io_service& io_service) + : io_service_(io_service) + { + io_service_.work_started(); + } + + ~auto_work() + { + io_service_.work_finished(); + } + + private: + win_iocp_io_service& io_service_; + }; + + template + struct handler_operation + : public operation + { + handler_operation(win_iocp_io_service& io_service, + Handler handler) + : operation(&handler_operation::do_completion_impl, + &handler_operation::destroy_impl), + io_service_(io_service), + handler_(handler) + { + io_service_.work_started(); + } + + ~handler_operation() + { + io_service_.work_finished(); + } + + private: + // Prevent copying and assignment. + handler_operation(const handler_operation&); + void operator=(const handler_operation&); + + static void do_completion_impl(operation* op, DWORD, size_t) + { + // Take ownership of the operation object. + typedef handler_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(handler_op->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Make the upcall. + asio_handler_invoke_helpers::invoke(handler, &handler); + } + + static void destroy_impl(operation* op) + { + // Take ownership of the operation object. + typedef handler_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + } + + win_iocp_io_service& io_service_; + Handler handler_; + }; + + // The IO completion port used for queueing operations. + struct iocp_holder + { + HANDLE handle; + iocp_holder() : handle(0) {} + ~iocp_holder() { if (handle) ::CloseHandle(handle); } + } iocp_; + + // The count of unfinished work. + long outstanding_work_; + + // Flag to indicate whether the event loop has been stopped. + long stopped_; + + // Flag to indicate whether the service has been shut down. + long shutdown_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_IOCP) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_IOCP_IO_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp new file mode 100644 index 000000000..184fdfa18 --- /dev/null +++ b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp @@ -0,0 +1,46 @@ +// +// win_iocp_io_service_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_IOCP_IO_SERVICE_FWD_HPP +#define ASIO_DETAIL_WIN_IOCP_IO_SERVICE_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +// This service is only supported on Win32 (NT4 and later). +#if !defined(ASIO_DISABLE_IOCP) +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400) + +// Define this to indicate that IOCP is supported on the target platform. +#define ASIO_HAS_IOCP 1 + +namespace asio { +namespace detail { + +class win_iocp_io_service; + +} // namespace detail +} // namespace asio + +#endif // defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400) +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +#endif // !defined(ASIO_DISABLE_IOCP) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_IOCP_IO_SERVICE_FWD_HPP diff --git a/libtorrent/include/asio/detail/win_iocp_operation.hpp b/libtorrent/include/asio/detail/win_iocp_operation.hpp new file mode 100644 index 000000000..34b0fad90 --- /dev/null +++ b/libtorrent/include/asio/detail/win_iocp_operation.hpp @@ -0,0 +1,81 @@ +// +// win_iocp_operation.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_IOCP_OPERATION_HPP +#define ASIO_DETAIL_WIN_IOCP_OPERATION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/win_iocp_io_service_fwd.hpp" + +#if defined(ASIO_HAS_IOCP) + +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +// Base class for all IOCP operations. A function pointer is used instead of +// virtual functions to avoid the associated overhead. +// +// This class inherits from OVERLAPPED so that we can downcast to get back to +// the win_iocp_operation pointer from the LPOVERLAPPED out parameter of +// GetQueuedCompletionStatus. +struct win_iocp_operation + : public OVERLAPPED +{ + typedef void (*invoke_func_type)(win_iocp_operation*, DWORD, size_t); + typedef void (*destroy_func_type)(win_iocp_operation*); + + win_iocp_operation(invoke_func_type invoke_func, + destroy_func_type destroy_func) + : invoke_func_(invoke_func), + destroy_func_(destroy_func) + { + Internal = 0; + InternalHigh = 0; + Offset = 0; + OffsetHigh = 0; + hEvent = 0; + } + + void do_completion(DWORD last_error, size_t bytes_transferred) + { + invoke_func_(this, last_error, bytes_transferred); + } + + void destroy() + { + destroy_func_(this); + } + +protected: + // Prevent deletion through this type. + ~win_iocp_operation() + { + } + +private: + invoke_func_type invoke_func_; + destroy_func_type destroy_func_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_IOCP) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_IOCP_OPERATION_HPP diff --git a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp new file mode 100644 index 000000000..007286e8d --- /dev/null +++ b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp @@ -0,0 +1,2000 @@ +// +// win_iocp_socket_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_IOCP_SOCKET_SERVICE_HPP +#define ASIO_DETAIL_WIN_IOCP_SOCKET_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/win_iocp_io_service_fwd.hpp" + +#if defined(ASIO_HAS_IOCP) + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/socket_base.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/select_reactor.hpp" +#include "asio/detail/socket_holder.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/win_iocp_io_service.hpp" + +namespace asio { +namespace detail { + +template +class win_iocp_socket_service + : public asio::detail::service_base > +{ +public: + // The protocol type. + typedef Protocol protocol_type; + + // The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + // Base class for all operations. + typedef win_iocp_operation operation; + + struct noop_deleter { void operator()(void*) {} }; + typedef boost::shared_ptr shared_cancel_token_type; + typedef boost::weak_ptr weak_cancel_token_type; + + // The native type of a socket. + class native_type + { + public: + native_type(socket_type s) + : socket_(s), + have_remote_endpoint_(false) + { + } + + native_type(socket_type s, const endpoint_type& ep) + : socket_(s), + have_remote_endpoint_(true), + remote_endpoint_(ep) + { + } + + void operator=(socket_type s) + { + socket_ = s; + have_remote_endpoint_ = false; + remote_endpoint_ = endpoint_type(); + } + + operator socket_type() const + { + return socket_; + } + + HANDLE as_handle() const + { + return reinterpret_cast(socket_); + } + + bool have_remote_endpoint() const + { + return have_remote_endpoint_; + } + + endpoint_type remote_endpoint() const + { + return remote_endpoint_; + } + + private: + socket_type socket_; + bool have_remote_endpoint_; + endpoint_type remote_endpoint_; + }; + + // The implementation type of the socket. + class implementation_type + { + public: + // Default constructor. + implementation_type() + : socket_(invalid_socket), + flags_(0), + cancel_token_(), + protocol_(endpoint_type().protocol()), + next_(0), + prev_(0) + { + } + + private: + // Only this service will have access to the internal values. + friend class win_iocp_socket_service; + + // The native socket representation. + native_type socket_; + + enum + { + enable_connection_aborted = 1, // User wants connection_aborted errors. + user_set_linger = 2, // The user set the linger option. + user_set_non_blocking = 4 // The user wants a non-blocking socket. + }; + + // Flags indicating the current state of the socket. + unsigned char flags_; + + // We use a shared pointer as a cancellation token here to work around the + // broken Windows support for cancellation. MSDN says that when you call + // closesocket any outstanding WSARecv or WSASend operations will complete + // with the error ERROR_OPERATION_ABORTED. In practice they complete with + // ERROR_NETNAME_DELETED, which means you can't tell the difference between + // a local cancellation and the socket being hard-closed by the peer. + shared_cancel_token_type cancel_token_; + + // The protocol associated with the socket. + protocol_type protocol_; + + // The ID of the thread from which it is safe to cancel asynchronous + // operations. 0 means no asynchronous operations have been started yet. + // ~0 means asynchronous operations have been started from more than one + // thread, and cancellation is not supported for the socket. + DWORD safe_cancellation_thread_id_; + + // Pointers to adjacent socket implementations in linked list. + implementation_type* next_; + implementation_type* prev_; + }; + + // The type of the reactor used for connect operations. + typedef detail::select_reactor reactor_type; + + // The maximum number of buffers to support in a single operation. + enum { max_buffers = 16 }; + + // Constructor. + win_iocp_socket_service(asio::io_service& io_service) + : asio::detail::service_base< + win_iocp_socket_service >(io_service), + iocp_service_(asio::use_service(io_service)), + reactor_(0), + mutex_(), + impl_list_(0) + { + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + // Close all implementations, causing all operations to complete. + asio::detail::mutex::scoped_lock lock(mutex_); + implementation_type* impl = impl_list_; + while (impl) + { + asio::error_code ignored_ec; + close(*impl, ignored_ec); + impl = impl->next_; + } + } + + // Construct a new socket implementation. + void construct(implementation_type& impl) + { + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + + // Insert implementation into linked list of all implementations. + asio::detail::mutex::scoped_lock lock(mutex_); + impl.next_ = impl_list_; + impl.prev_ = 0; + if (impl_list_) + impl_list_->prev_ = &impl; + impl_list_ = &impl; + } + + // Destroy a socket implementation. + void destroy(implementation_type& impl) + { + if (impl.socket_ != invalid_socket) + { + // Check if the reactor was created, in which case we need to close the + // socket on the reactor as well to cancel any operations that might be + // running there. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (reactor) + reactor->close_descriptor(impl.socket_); + + if (impl.flags_ & implementation_type::user_set_linger) + { + ::linger opt; + opt.l_onoff = 0; + opt.l_linger = 0; + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, + SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); + } + + asio::error_code ignored_ec; + socket_ops::close(impl.socket_, ignored_ec); + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + } + + // Remove implementation from linked list of all implementations. + asio::detail::mutex::scoped_lock lock(mutex_); + if (impl_list_ == &impl) + impl_list_ = impl.next_; + if (impl.prev_) + impl.prev_->next_ = impl.next_; + if (impl.next_) + impl.next_->prev_= impl.prev_; + impl.next_ = 0; + impl.prev_ = 0; + } + + // Open a new socket implementation. + asio::error_code open(implementation_type& impl, + const protocol_type& protocol, asio::error_code& ec) + { + if (is_open(impl)) + { + ec = asio::error::already_open; + return ec; + } + + socket_holder sock(socket_ops::socket(protocol.family(), protocol.type(), + protocol.protocol(), ec)); + if (sock.get() == invalid_socket) + return ec; + + HANDLE sock_as_handle = reinterpret_cast(sock.get()); + iocp_service_.register_handle(sock_as_handle); + + impl.socket_ = sock.release(); + impl.flags_ = 0; + impl.cancel_token_.reset(static_cast(0), noop_deleter()); + impl.protocol_ = protocol; + ec = asio::error_code(); + return ec; + } + + // Assign a native socket to a socket implementation. + asio::error_code assign(implementation_type& impl, + const protocol_type& protocol, const native_type& native_socket, + asio::error_code& ec) + { + if (is_open(impl)) + { + ec = asio::error::already_open; + return ec; + } + + iocp_service_.register_handle(native_socket.as_handle()); + + impl.socket_ = native_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(static_cast(0), noop_deleter()); + impl.protocol_ = protocol; + ec = asio::error_code(); + return ec; + } + + // Determine whether the socket is open. + bool is_open(const implementation_type& impl) const + { + return impl.socket_ != invalid_socket; + } + + // Destroy a socket implementation. + asio::error_code close(implementation_type& impl, + asio::error_code& ec) + { + if (is_open(impl)) + { + // Check if the reactor was created, in which case we need to close the + // socket on the reactor as well to cancel any operations that might be + // running there. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (reactor) + reactor->close_descriptor(impl.socket_); + + if (socket_ops::close(impl.socket_, ec) == socket_error_retval) + return ec; + + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + } + + ec = asio::error_code(); + return ec; + } + + // Get the native socket representation. + native_type native(implementation_type& impl) + { + return impl.socket_; + } + + // Cancel all operations associated with the socket. + asio::error_code cancel(implementation_type& impl, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + } + else if (impl.safe_cancellation_thread_id_ == 0) + { + // No operations have been started, so there's nothing to cancel. + ec = asio::error_code(); + } + else if (impl.safe_cancellation_thread_id_ == ::GetCurrentThreadId()) + { + // Asynchronous operations have been started from the current thread only, + // so it is safe to try to cancel them using CancelIo. + socket_type sock = impl.socket_; + HANDLE sock_as_handle = reinterpret_cast(sock); + if (!::CancelIo(sock_as_handle)) + { + DWORD last_error = ::GetLastError(); + ec = asio::error_code(last_error, asio::native_ecat); + } + else + { + ec = asio::error_code(); + } + } + else + { + // Asynchronous operations have been started from more than one thread, + // so cancellation is not safe. + ec = asio::error::operation_not_supported; + } + + return ec; + } + + // Determine whether the socket is at the out-of-band data mark. + bool at_mark(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return false; + } + + asio::detail::ioctl_arg_type value = 0; + socket_ops::ioctl(impl.socket_, SIOCATMARK, &value, ec); + return ec ? false : value != 0; + } + + // Determine the number of bytes available for reading. + std::size_t available(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + asio::detail::ioctl_arg_type value = 0; + socket_ops::ioctl(impl.socket_, FIONREAD, &value, ec); + return ec ? static_cast(0) : static_cast(value); + } + + // Bind the socket to the specified local endpoint. + asio::error_code bind(implementation_type& impl, + const endpoint_type& endpoint, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::bind(impl.socket_, endpoint.data(), endpoint.size(), ec); + return ec; + } + + // Place the socket into the state where it will listen for new connections. + asio::error_code listen(implementation_type& impl, int backlog, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::listen(impl.socket_, backlog, ec); + return ec; + } + + // Set a socket option. + template + asio::error_code set_option(implementation_type& impl, + const Option& option, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + if (option.level(impl.protocol_) == custom_socket_option_level + && option.name(impl.protocol_) == enable_connection_aborted_option) + { + if (option.size(impl.protocol_) != sizeof(int)) + { + ec = asio::error::invalid_argument; + } + else + { + if (*reinterpret_cast(option.data(impl.protocol_))) + impl.flags_ |= implementation_type::enable_connection_aborted; + else + impl.flags_ &= ~implementation_type::enable_connection_aborted; + ec = asio::error_code(); + } + return ec; + } + else + { + if (option.level(impl.protocol_) == SOL_SOCKET + && option.name(impl.protocol_) == SO_LINGER) + { + impl.flags_ |= implementation_type::user_set_linger; + } + + socket_ops::setsockopt(impl.socket_, + option.level(impl.protocol_), option.name(impl.protocol_), + option.data(impl.protocol_), option.size(impl.protocol_), ec); + return ec; + } + } + + // Set a socket option. + template + asio::error_code get_option(const implementation_type& impl, + Option& option, asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + if (option.level(impl.protocol_) == custom_socket_option_level + && option.name(impl.protocol_) == enable_connection_aborted_option) + { + if (option.size(impl.protocol_) != sizeof(int)) + { + ec = asio::error::invalid_argument; + } + else + { + int* target = reinterpret_cast(option.data(impl.protocol_)); + if (impl.flags_ & implementation_type::enable_connection_aborted) + *target = 1; + else + *target = 0; + option.resize(impl.protocol_, sizeof(int)); + ec = asio::error_code(); + } + return ec; + } + else + { + size_t size = option.size(impl.protocol_); + socket_ops::getsockopt(impl.socket_, + option.level(impl.protocol_), option.name(impl.protocol_), + option.data(impl.protocol_), &size, ec); + if (!ec) + option.resize(impl.protocol_, size); + return ec; + } + } + + // Perform an IO control command on the socket. + template + asio::error_code io_control(implementation_type& impl, + IO_Control_Command& command, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::ioctl(impl.socket_, command.name(), + static_cast(command.data()), ec); + + if (!ec && command.name() == static_cast(FIONBIO)) + { + if (command.get()) + impl.flags_ |= implementation_type::user_set_non_blocking; + else + impl.flags_ &= ~implementation_type::user_set_non_blocking; + } + + return ec; + } + + // Get the local endpoint. + endpoint_type local_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return endpoint_type(); + } + + endpoint_type endpoint; + socket_addr_len_type addr_len = endpoint.capacity(); + if (socket_ops::getsockname(impl.socket_, endpoint.data(), &addr_len, ec)) + return endpoint_type(); + endpoint.resize(addr_len); + return endpoint; + } + + // Get the remote endpoint. + endpoint_type remote_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return endpoint_type(); + } + + if (impl.socket_.have_remote_endpoint()) + { + // Check if socket is still connected. + DWORD connect_time = 0; + size_t connect_time_len = sizeof(connect_time); + if (socket_ops::getsockopt(impl.socket_, SOL_SOCKET, SO_CONNECT_TIME, + &connect_time, &connect_time_len, ec) == socket_error_retval) + { + return endpoint_type(); + } + if (connect_time == 0xFFFFFFFF) + { + ec = asio::error::not_connected; + return endpoint_type(); + } + + ec = asio::error_code(); + return impl.socket_.remote_endpoint(); + } + else + { + endpoint_type endpoint; + socket_addr_len_type addr_len = endpoint.capacity(); + if (socket_ops::getpeername(impl.socket_, endpoint.data(), &addr_len, ec)) + return endpoint_type(); + endpoint.resize(addr_len); + return endpoint; + } + } + + /// Disable sends or receives on the socket. + asio::error_code shutdown(implementation_type& impl, + socket_base::shutdown_type what, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + socket_ops::shutdown(impl.socket_, what, ec); + return ec; + } + + // Send the given data to the peer. Returns the number of bytes sent. + template + size_t send(implementation_type& impl, const ConstBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = const_cast( + asio::buffer_cast(buffer)); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) + { + ec = asio::error_code(); + return 0; + } + + // Send the data. + DWORD bytes_transferred = 0; + int result = ::WSASend(impl.socket_, bufs, + i, &bytes_transferred, flags, 0, 0); + if (result != 0) + { + DWORD last_error = ::WSAGetLastError(); + if (last_error == ERROR_NETNAME_DELETED) + last_error = WSAECONNRESET; + else if (last_error == ERROR_PORT_UNREACHABLE) + last_error = WSAECONNREFUSED; + ec = asio::error_code(last_error, asio::native_ecat); + return 0; + } + + ec = asio::error_code(); + return bytes_transferred; + } + + template + class send_operation + : public operation + { + public: + send_operation(asio::io_service& io_service, + weak_cancel_token_type cancel_token, + const ConstBufferSequence& buffers, Handler handler) + : operation( + &send_operation::do_completion_impl, + &send_operation::destroy_impl), + work_(io_service), + cancel_token_(cancel_token), + buffers_(buffers), + handler_(handler) + { + } + + private: + static void do_completion_impl(operation* op, + DWORD last_error, size_t bytes_transferred) + { + // Take ownership of the operation object. + typedef send_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + // Check whether buffers are still valid. + typename ConstBufferSequence::const_iterator iter + = handler_op->buffers_.begin(); + typename ConstBufferSequence::const_iterator end + = handler_op->buffers_.end(); + while (iter != end) + { + asio::const_buffer buffer(*iter); + asio::buffer_cast(buffer); + ++iter; + } +#endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) + + // Map non-portable errors to their portable counterparts. + asio::error_code ec(last_error, asio::native_ecat); + if (ec.value() == ERROR_NETNAME_DELETED) + { + if (handler_op->cancel_token_.expired()) + ec = asio::error::operation_aborted; + else + ec = asio::error::connection_reset; + } + else if (ec.value() == ERROR_PORT_UNREACHABLE) + { + ec = asio::error::connection_refused; + } + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(handler_op->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Call the handler. + asio_handler_invoke_helpers::invoke( + detail::bind_handler(handler, ec, bytes_transferred), &handler); + } + + static void destroy_impl(operation* op) + { + // Take ownership of the operation object. + typedef send_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + } + + asio::io_service::work work_; + weak_cancel_token_type cancel_token_; + ConstBufferSequence buffers_; + Handler handler_; + }; + + // Start an asynchronous send. The data being sent must be valid for the + // lifetime of the asynchronous operation. + template + void async_send(implementation_type& impl, const ConstBufferSequence& buffers, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + return; + } + + // Update the ID of the thread from which cancellation is safe. + if (impl.safe_cancellation_thread_id_ == 0) + impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); + else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) + impl.safe_cancellation_thread_id_ = ~DWORD(0); + + // Allocate and construct an operation to wrap the handler. + typedef send_operation value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, + this->io_service(), impl.cancel_token_, buffers, handler); + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = const_cast( + asio::buffer_cast(buffer)); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code error; + iocp_service_.post(bind_handler(handler, error, 0)); + return; + } + + // Send the data. + DWORD bytes_transferred = 0; + int result = ::WSASend(impl.socket_, bufs, i, + &bytes_transferred, flags, ptr.get(), 0); + DWORD last_error = ::WSAGetLastError(); + + // Check if the operation completed immediately. + if (result != 0 && last_error != WSA_IO_PENDING) + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code ec(last_error, asio::native_ecat); + iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); + } + else + { + ptr.release(); + } + } + + // Send a datagram to the specified endpoint. Returns the number of bytes + // sent. + template + size_t send_to(implementation_type& impl, const ConstBufferSequence& buffers, + const endpoint_type& destination, socket_base::message_flags flags, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = const_cast( + asio::buffer_cast(buffer)); + } + + // Send the data. + DWORD bytes_transferred = 0; + int result = ::WSASendTo(impl.socket_, bufs, i, &bytes_transferred, + flags, destination.data(), destination.size(), 0, 0); + if (result != 0) + { + DWORD last_error = ::WSAGetLastError(); + if (last_error == ERROR_PORT_UNREACHABLE) + last_error = WSAECONNREFUSED; + ec = asio::error_code(last_error, asio::native_ecat); + return 0; + } + + ec = asio::error_code(); + return bytes_transferred; + } + + template + class send_to_operation + : public operation + { + public: + send_to_operation(asio::io_service& io_service, + const ConstBufferSequence& buffers, Handler handler) + : operation( + &send_to_operation::do_completion_impl, + &send_to_operation::destroy_impl), + work_(io_service), + buffers_(buffers), + handler_(handler) + { + } + + private: + static void do_completion_impl(operation* op, + DWORD last_error, size_t bytes_transferred) + { + // Take ownership of the operation object. + typedef send_to_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + // Check whether buffers are still valid. + typename ConstBufferSequence::const_iterator iter + = handler_op->buffers_.begin(); + typename ConstBufferSequence::const_iterator end + = handler_op->buffers_.end(); + while (iter != end) + { + asio::const_buffer buffer(*iter); + asio::buffer_cast(buffer); + ++iter; + } +#endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) + + // Map non-portable errors to their portable counterparts. + asio::error_code ec(last_error, asio::native_ecat); + if (ec.value() == ERROR_PORT_UNREACHABLE) + { + ec = asio::error::connection_refused; + } + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(handler_op->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Call the handler. + asio_handler_invoke_helpers::invoke( + detail::bind_handler(handler, ec, bytes_transferred), &handler); + } + + static void destroy_impl(operation* op) + { + // Take ownership of the operation object. + typedef send_to_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + } + + asio::io_service::work work_; + ConstBufferSequence buffers_; + Handler handler_; + }; + + // Start an asynchronous send. The data being sent must be valid for the + // lifetime of the asynchronous operation. + template + void async_send_to(implementation_type& impl, + const ConstBufferSequence& buffers, const endpoint_type& destination, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + return; + } + + // Update the ID of the thread from which cancellation is safe. + if (impl.safe_cancellation_thread_id_ == 0) + impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); + else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) + impl.safe_cancellation_thread_id_ = ~DWORD(0); + + // Allocate and construct an operation to wrap the handler. + typedef send_to_operation value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, + this->io_service(), buffers, handler); + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename ConstBufferSequence::const_iterator iter = buffers.begin(); + typename ConstBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::const_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = const_cast( + asio::buffer_cast(buffer)); + } + + // Send the data. + DWORD bytes_transferred = 0; + int result = ::WSASendTo(impl.socket_, bufs, i, &bytes_transferred, + flags, destination.data(), destination.size(), ptr.get(), 0); + DWORD last_error = ::WSAGetLastError(); + + // Check if the operation completed immediately. + if (result != 0 && last_error != WSA_IO_PENDING) + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code ec(last_error, asio::native_ecat); + iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); + } + else + { + ptr.release(); + } + } + + // Receive some data from the peer. Returns the number of bytes received. + template + size_t receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = asio::buffer_cast(buffer); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) + { + ec = asio::error_code(); + return 0; + } + + // Receive some data. + DWORD bytes_transferred = 0; + DWORD recv_flags = flags; + int result = ::WSARecv(impl.socket_, bufs, i, + &bytes_transferred, &recv_flags, 0, 0); + if (result != 0) + { + DWORD last_error = ::WSAGetLastError(); + if (last_error == ERROR_NETNAME_DELETED) + last_error = WSAECONNRESET; + else if (last_error == ERROR_PORT_UNREACHABLE) + last_error = WSAECONNREFUSED; + ec = asio::error_code(last_error, asio::native_ecat); + return 0; + } + if (bytes_transferred == 0) + { + ec = asio::error::eof; + return 0; + } + + ec = asio::error_code(); + return bytes_transferred; + } + + template + class receive_operation + : public operation + { + public: + receive_operation(asio::io_service& io_service, + weak_cancel_token_type cancel_token, + const MutableBufferSequence& buffers, Handler handler) + : operation( + &receive_operation< + MutableBufferSequence, Handler>::do_completion_impl, + &receive_operation< + MutableBufferSequence, Handler>::destroy_impl), + work_(io_service), + cancel_token_(cancel_token), + buffers_(buffers), + handler_(handler) + { + } + + private: + static void do_completion_impl(operation* op, + DWORD last_error, size_t bytes_transferred) + { + // Take ownership of the operation object. + typedef receive_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + // Check whether buffers are still valid. + typename MutableBufferSequence::const_iterator iter + = handler_op->buffers_.begin(); + typename MutableBufferSequence::const_iterator end + = handler_op->buffers_.end(); + while (iter != end) + { + asio::mutable_buffer buffer(*iter); + asio::buffer_cast(buffer); + ++iter; + } +#endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) + + // Map non-portable errors to their portable counterparts. + asio::error_code ec(last_error, asio::native_ecat); + if (ec.value() == ERROR_NETNAME_DELETED) + { + if (handler_op->cancel_token_.expired()) + ec = asio::error::operation_aborted; + else + ec = asio::error::connection_reset; + } + else if (ec.value() == ERROR_PORT_UNREACHABLE) + { + ec = asio::error::connection_refused; + } + + // Check for connection closed. + else if (!ec && bytes_transferred == 0) + { + ec = asio::error::eof; + } + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(handler_op->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Call the handler. + asio_handler_invoke_helpers::invoke( + detail::bind_handler(handler, ec, bytes_transferred), &handler); + } + + static void destroy_impl(operation* op) + { + // Take ownership of the operation object. + typedef receive_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + } + + asio::io_service::work work_; + weak_cancel_token_type cancel_token_; + MutableBufferSequence buffers_; + Handler handler_; + }; + + // Start an asynchronous receive. The buffer for the data being received + // must be valid for the lifetime of the asynchronous operation. + template + void async_receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + return; + } + + // Update the ID of the thread from which cancellation is safe. + if (impl.safe_cancellation_thread_id_ == 0) + impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); + else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) + impl.safe_cancellation_thread_id_ = ~DWORD(0); + + // Allocate and construct an operation to wrap the handler. + typedef receive_operation value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, + this->io_service(), impl.cancel_token_, buffers, handler); + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + size_t total_buffer_size = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = asio::buffer_cast(buffer); + total_buffer_size += asio::buffer_size(buffer); + } + + // A request to receive 0 bytes on a stream socket is a no-op. + if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code error; + iocp_service_.post(bind_handler(handler, error, 0)); + return; + } + + // Receive some data. + DWORD bytes_transferred = 0; + DWORD recv_flags = flags; + int result = ::WSARecv(impl.socket_, bufs, i, + &bytes_transferred, &recv_flags, ptr.get(), 0); + DWORD last_error = ::WSAGetLastError(); + if (result != 0 && last_error != WSA_IO_PENDING) + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code ec(last_error, asio::native_ecat); + iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); + } + else + { + ptr.release(); + } + } + + // Receive a datagram with the endpoint of the sender. Returns the number of + // bytes received. + template + size_t receive_from(implementation_type& impl, + const MutableBufferSequence& buffers, + endpoint_type& sender_endpoint, socket_base::message_flags flags, + asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return 0; + } + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = asio::buffer_cast(buffer); + } + + // Receive some data. + DWORD bytes_transferred = 0; + DWORD recv_flags = flags; + int endpoint_size = sender_endpoint.capacity(); + int result = ::WSARecvFrom(impl.socket_, bufs, i, &bytes_transferred, + &recv_flags, sender_endpoint.data(), &endpoint_size, 0, 0); + if (result != 0) + { + DWORD last_error = ::WSAGetLastError(); + if (last_error == ERROR_PORT_UNREACHABLE) + last_error = WSAECONNREFUSED; + ec = asio::error_code(last_error, asio::native_ecat); + return 0; + } + if (bytes_transferred == 0) + { + ec = asio::error::eof; + return 0; + } + + sender_endpoint.resize(endpoint_size); + + ec = asio::error_code(); + return bytes_transferred; + } + + template + class receive_from_operation + : public operation + { + public: + receive_from_operation(asio::io_service& io_service, + endpoint_type& endpoint, const MutableBufferSequence& buffers, + Handler handler) + : operation( + &receive_from_operation< + MutableBufferSequence, Handler>::do_completion_impl, + &receive_from_operation< + MutableBufferSequence, Handler>::destroy_impl), + endpoint_(endpoint), + endpoint_size_(endpoint.capacity()), + work_(io_service), + buffers_(buffers), + handler_(handler) + { + } + + int& endpoint_size() + { + return endpoint_size_; + } + + private: + static void do_completion_impl(operation* op, + DWORD last_error, size_t bytes_transferred) + { + // Take ownership of the operation object. + typedef receive_from_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + +#if defined(ASIO_ENABLE_BUFFER_DEBUGGING) + // Check whether buffers are still valid. + typename MutableBufferSequence::const_iterator iter + = handler_op->buffers_.begin(); + typename MutableBufferSequence::const_iterator end + = handler_op->buffers_.end(); + while (iter != end) + { + asio::mutable_buffer buffer(*iter); + asio::buffer_cast(buffer); + ++iter; + } +#endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) + + // Map non-portable errors to their portable counterparts. + asio::error_code ec(last_error, asio::native_ecat); + if (ec.value() == ERROR_PORT_UNREACHABLE) + { + ec = asio::error::connection_refused; + } + + // Check for connection closed. + if (!ec && bytes_transferred == 0) + { + ec = asio::error::eof; + } + + // Record the size of the endpoint returned by the operation. + handler_op->endpoint_.resize(handler_op->endpoint_size_); + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(handler_op->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Call the handler. + asio_handler_invoke_helpers::invoke( + detail::bind_handler(handler, ec, bytes_transferred), &handler); + } + + static void destroy_impl(operation* op) + { + // Take ownership of the operation object. + typedef receive_from_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + } + + endpoint_type& endpoint_; + int endpoint_size_; + asio::io_service::work work_; + MutableBufferSequence buffers_; + Handler handler_; + }; + + // Start an asynchronous receive. The buffer for the data being received and + // the sender_endpoint object must both be valid for the lifetime of the + // asynchronous operation. + template + void async_receive_from(implementation_type& impl, + const MutableBufferSequence& buffers, endpoint_type& sender_endp, + socket_base::message_flags flags, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor, 0)); + return; + } + + // Update the ID of the thread from which cancellation is safe. + if (impl.safe_cancellation_thread_id_ == 0) + impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); + else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) + impl.safe_cancellation_thread_id_ = ~DWORD(0); + + // Allocate and construct an operation to wrap the handler. + typedef receive_from_operation value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + handler_ptr ptr(raw_ptr, + this->io_service(), sender_endp, buffers, handler); + + // Copy buffers into WSABUF array. + ::WSABUF bufs[max_buffers]; + typename MutableBufferSequence::const_iterator iter = buffers.begin(); + typename MutableBufferSequence::const_iterator end = buffers.end(); + DWORD i = 0; + for (; iter != end && i < max_buffers; ++iter, ++i) + { + asio::mutable_buffer buffer(*iter); + bufs[i].len = static_cast(asio::buffer_size(buffer)); + bufs[i].buf = asio::buffer_cast(buffer); + } + + // Receive some data. + DWORD bytes_transferred = 0; + DWORD recv_flags = flags; + int result = ::WSARecvFrom(impl.socket_, bufs, i, &bytes_transferred, + &recv_flags, sender_endp.data(), &ptr.get()->endpoint_size(), + ptr.get(), 0); + DWORD last_error = ::WSAGetLastError(); + if (result != 0 && last_error != WSA_IO_PENDING) + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code ec(last_error, asio::native_ecat); + iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); + } + else + { + ptr.release(); + } + } + + // Accept a new connection. + template + asio::error_code accept(implementation_type& impl, Socket& peer, + endpoint_type* peer_endpoint, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + // We cannot accept a socket that is already open. + if (peer.is_open()) + { + ec = asio::error::already_open; + return ec; + } + + for (;;) + { + asio::error_code ec; + socket_holder new_socket; + socket_addr_len_type addr_len = 0; + if (peer_endpoint) + { + addr_len = peer_endpoint->capacity(); + new_socket.reset(socket_ops::accept(impl.socket_, + peer_endpoint->data(), &addr_len, ec)); + } + else + { + new_socket.reset(socket_ops::accept(impl.socket_, 0, 0, ec)); + } + + if (ec) + { + if (ec == asio::error::connection_aborted + && !(impl.flags_ & implementation_type::enable_connection_aborted)) + { + // Retry accept operation. + continue; + } + else + { + return ec; + } + } + + if (peer_endpoint) + peer_endpoint->resize(addr_len); + + peer.assign(impl.protocol_, new_socket.get(), ec); + if (!ec) + new_socket.release(); + return ec; + } + } + + template + class accept_operation + : public operation + { + public: + accept_operation(win_iocp_io_service& io_service, + socket_type socket, socket_type new_socket, Socket& peer, + const protocol_type& protocol, endpoint_type* peer_endpoint, + bool enable_connection_aborted, Handler handler) + : operation( + &accept_operation::do_completion_impl, + &accept_operation::destroy_impl), + io_service_(io_service), + socket_(socket), + new_socket_(new_socket), + peer_(peer), + protocol_(protocol), + peer_endpoint_(peer_endpoint), + work_(io_service.io_service()), + enable_connection_aborted_(enable_connection_aborted), + handler_(handler) + { + } + + socket_type new_socket() + { + return new_socket_.get(); + } + + void* output_buffer() + { + return output_buffer_; + } + + DWORD address_length() + { + return sizeof(sockaddr_storage_type) + 16; + } + + private: + static void do_completion_impl(operation* op, + DWORD last_error, size_t bytes_transferred) + { + // Take ownership of the operation object. + typedef accept_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + + // Map Windows error ERROR_NETNAME_DELETED to connection_aborted. + if (last_error == ERROR_NETNAME_DELETED) + { + last_error = WSAECONNABORTED; + } + + // Restart the accept operation if we got the connection_aborted error + // and the enable_connection_aborted socket option is not set. + if (last_error == WSAECONNABORTED + && !ptr.get()->enable_connection_aborted_) + { + // Reset OVERLAPPED structure. + ptr.get()->Internal = 0; + ptr.get()->InternalHigh = 0; + ptr.get()->Offset = 0; + ptr.get()->OffsetHigh = 0; + ptr.get()->hEvent = 0; + + // Create a new socket for the next connection, since the AcceptEx call + // fails with WSAEINVAL if we try to reuse the same socket. + asio::error_code ec; + ptr.get()->new_socket_.reset(); + ptr.get()->new_socket_.reset(socket_ops::socket( + ptr.get()->protocol_.family(), ptr.get()->protocol_.type(), + ptr.get()->protocol_.protocol(), ec)); + if (ptr.get()->new_socket() != invalid_socket) + { + // Accept a connection. + DWORD bytes_read = 0; + BOOL result = ::AcceptEx(ptr.get()->socket_, ptr.get()->new_socket(), + ptr.get()->output_buffer(), 0, ptr.get()->address_length(), + ptr.get()->address_length(), &bytes_read, ptr.get()); + last_error = ::WSAGetLastError(); + + // Check if the operation completed immediately. + if (!result && last_error != WSA_IO_PENDING) + { + if (last_error == ERROR_NETNAME_DELETED + || last_error == WSAECONNABORTED) + { + // Post this handler so that operation will be restarted again. + ptr.get()->io_service_.post_completion(ptr.get(), last_error, 0); + ptr.release(); + return; + } + else + { + // Operation already complete. Continue with rest of this handler. + } + } + else + { + // Asynchronous operation has been successfully restarted. + ptr.release(); + return; + } + } + } + + // Get the address of the peer. + endpoint_type peer_endpoint; + if (last_error == 0) + { + LPSOCKADDR local_addr = 0; + int local_addr_length = 0; + LPSOCKADDR remote_addr = 0; + int remote_addr_length = 0; + GetAcceptExSockaddrs(handler_op->output_buffer(), 0, + handler_op->address_length(), handler_op->address_length(), + &local_addr, &local_addr_length, &remote_addr, &remote_addr_length); + if (remote_addr_length > peer_endpoint.capacity()) + { + last_error = WSAEINVAL; + } + else + { + using namespace std; // For memcpy. + memcpy(peer_endpoint.data(), remote_addr, remote_addr_length); + peer_endpoint.resize(remote_addr_length); + } + } + + // Need to set the SO_UPDATE_ACCEPT_CONTEXT option so that getsockname + // and getpeername will work on the accepted socket. + if (last_error == 0) + { + SOCKET update_ctx_param = handler_op->socket_; + asio::error_code ec; + if (socket_ops::setsockopt(handler_op->new_socket_.get(), + SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, + &update_ctx_param, sizeof(SOCKET), ec) != 0) + { + last_error = ec.value(); + } + } + + // If the socket was successfully accepted, transfer ownership of the + // socket to the peer object. + if (last_error == 0) + { + asio::error_code ec; + handler_op->peer_.assign(handler_op->protocol_, + native_type(handler_op->new_socket_.get(), peer_endpoint), ec); + if (ec) + last_error = ec.value(); + else + handler_op->new_socket_.release(); + } + + // Pass endpoint back to caller. + if (handler_op->peer_endpoint_) + *handler_op->peer_endpoint_ = peer_endpoint; + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(handler_op->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Call the handler. + asio::error_code ec(last_error, asio::native_ecat); + asio_handler_invoke_helpers::invoke( + detail::bind_handler(handler, ec), &handler); + } + + static void destroy_impl(operation* op) + { + // Take ownership of the operation object. + typedef accept_operation op_type; + op_type* handler_op(static_cast(op)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(handler_op->handler_, handler_op); + } + + win_iocp_io_service& io_service_; + socket_type socket_; + socket_holder new_socket_; + Socket& peer_; + protocol_type protocol_; + endpoint_type* peer_endpoint_; + asio::io_service::work work_; + unsigned char output_buffer_[(sizeof(sockaddr_storage_type) + 16) * 2]; + bool enable_connection_aborted_; + Handler handler_; + }; + + // Start an asynchronous accept. The peer and peer_endpoint objects + // must be valid until the accept's handler is invoked. + template + void async_accept(implementation_type& impl, Socket& peer, + endpoint_type* peer_endpoint, Handler handler) + { + // Check whether acceptor has been initialised. + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor)); + return; + } + + // Check that peer socket has not already been opened. + if (peer.is_open()) + { + this->io_service().post(bind_handler(handler, + asio::error::already_open)); + return; + } + + // Update the ID of the thread from which cancellation is safe. + if (impl.safe_cancellation_thread_id_ == 0) + impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); + else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) + impl.safe_cancellation_thread_id_ = ~DWORD(0); + + // Create a new socket for the connection. + asio::error_code ec; + socket_holder sock(socket_ops::socket(impl.protocol_.family(), + impl.protocol_.type(), impl.protocol_.protocol(), ec)); + if (sock.get() == invalid_socket) + { + this->io_service().post(bind_handler(handler, ec)); + return; + } + + // Allocate and construct an operation to wrap the handler. + typedef accept_operation value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(handler); + socket_type new_socket = sock.get(); + bool enable_connection_aborted = + (impl.flags_ & implementation_type::enable_connection_aborted); + handler_ptr ptr(raw_ptr, + iocp_service_, impl.socket_, new_socket, peer, impl.protocol_, + peer_endpoint, enable_connection_aborted, handler); + sock.release(); + + // Accept a connection. + DWORD bytes_read = 0; + BOOL result = ::AcceptEx(impl.socket_, ptr.get()->new_socket(), + ptr.get()->output_buffer(), 0, ptr.get()->address_length(), + ptr.get()->address_length(), &bytes_read, ptr.get()); + DWORD last_error = ::WSAGetLastError(); + + // Check if the operation completed immediately. + if (!result && last_error != WSA_IO_PENDING) + { + if (!enable_connection_aborted + && (last_error == ERROR_NETNAME_DELETED + || last_error == WSAECONNABORTED)) + { + // Post handler so that operation will be restarted again. We do not + // perform the AcceptEx again here to avoid the possibility of starving + // other handlers. + iocp_service_.post_completion(ptr.get(), last_error, 0); + ptr.release(); + } + else + { + asio::io_service::work work(this->io_service()); + ptr.reset(); + asio::error_code ec(last_error, asio::native_ecat); + iocp_service_.post(bind_handler(handler, ec)); + } + } + else + { + ptr.release(); + } + } + + // Connect the socket to the specified endpoint. + asio::error_code connect(implementation_type& impl, + const endpoint_type& peer_endpoint, asio::error_code& ec) + { + if (!is_open(impl)) + { + ec = asio::error::bad_descriptor; + return ec; + } + + // Perform the connect operation. + socket_ops::connect(impl.socket_, + peer_endpoint.data(), peer_endpoint.size(), ec); + return ec; + } + + template + class connect_handler + { + public: + connect_handler(socket_type socket, bool user_set_non_blocking, + boost::shared_ptr completed, + asio::io_service& io_service, + reactor_type& reactor, Handler handler) + : socket_(socket), + user_set_non_blocking_(user_set_non_blocking), + completed_(completed), + io_service_(io_service), + reactor_(reactor), + work_(io_service), + handler_(handler) + { + } + + bool operator()(const asio::error_code& result) + { + // Check whether a handler has already been called for the connection. + // If it has, then we don't want to do anything in this handler. + if (*completed_) + return true; + + // Cancel the other reactor operation for the connection. + *completed_ = true; + reactor_.enqueue_cancel_ops_unlocked(socket_); + + // Check whether the operation was successful. + if (result) + { + io_service_.post(bind_handler(handler_, result)); + return true; + } + + // Get the error code from the connect operation. + int connect_error = 0; + size_t connect_error_len = sizeof(connect_error); + asio::error_code ec; + if (socket_ops::getsockopt(socket_, SOL_SOCKET, SO_ERROR, + &connect_error, &connect_error_len, ec) == socket_error_retval) + { + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + // If connection failed then post the handler with the error code. + if (connect_error) + { + ec = asio::error_code( + connect_error, asio::native_ecat); + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + // Revert socket to blocking mode unless the user requested otherwise. + if (!user_set_non_blocking_) + { + ioctl_arg_type non_blocking = 0; + if (socket_ops::ioctl(socket_, FIONBIO, &non_blocking, ec)) + { + io_service_.post(bind_handler(handler_, ec)); + return true; + } + } + + // Post the result of the successful connection operation. + ec = asio::error_code(); + io_service_.post(bind_handler(handler_, ec)); + return true; + } + + private: + socket_type socket_; + bool user_set_non_blocking_; + boost::shared_ptr completed_; + asio::io_service& io_service_; + reactor_type& reactor_; + asio::io_service::work work_; + Handler handler_; + }; + + // Start an asynchronous connect. + template + void async_connect(implementation_type& impl, + const endpoint_type& peer_endpoint, Handler handler) + { + if (!is_open(impl)) + { + this->io_service().post(bind_handler(handler, + asio::error::bad_descriptor)); + return; + } + + // Update the ID of the thread from which cancellation is safe. + if (impl.safe_cancellation_thread_id_ == 0) + impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); + else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) + impl.safe_cancellation_thread_id_ = ~DWORD(0); + + // Check if the reactor was already obtained from the io_service. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (!reactor) + { + reactor = &(asio::use_service(this->io_service())); + interlocked_exchange_pointer( + reinterpret_cast(&reactor_), reactor); + } + + // Mark the socket as non-blocking so that the connection will take place + // asynchronously. + ioctl_arg_type non_blocking = 1; + asio::error_code ec; + if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) + { + this->io_service().post(bind_handler(handler, ec)); + return; + } + + // Start the connect operation. + if (socket_ops::connect(impl.socket_, peer_endpoint.data(), + peer_endpoint.size(), ec) == 0) + { + // Revert socket to blocking mode unless the user requested otherwise. + if (!(impl.flags_ & implementation_type::user_set_non_blocking)) + { + non_blocking = 0; + socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec); + } + + // The connect operation has finished successfully so we need to post the + // handler immediately. + this->io_service().post(bind_handler(handler, ec)); + } + else if (ec == asio::error::in_progress + || ec == asio::error::would_block) + { + // The connection is happening in the background, and we need to wait + // until the socket becomes writeable. + boost::shared_ptr completed(new bool(false)); + reactor->start_write_and_except_ops(impl.socket_, + connect_handler( + impl.socket_, + (impl.flags_ & implementation_type::user_set_non_blocking) != 0, + completed, this->io_service(), *reactor, handler)); + } + else + { + // Revert socket to blocking mode unless the user requested otherwise. + if (!(impl.flags_ & implementation_type::user_set_non_blocking)) + { + non_blocking = 0; + asio::error_code ignored_ec; + socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ignored_ec); + } + + // The connect operation has failed, so post the handler immediately. + this->io_service().post(bind_handler(handler, ec)); + } + } + +private: + // Helper function to provide InterlockedCompareExchangePointer functionality + // on very old Platform SDKs. + void* interlocked_compare_exchange_pointer(void** dest, void* exch, void* cmp) + { +#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) + return reinterpret_cast(InterlockedCompareExchange( + reinterpret_cast(dest), reinterpret_cast(exch), + reinterpret_cast(cmp))); +#else + return InterlockedCompareExchangePointer(dest, exch, cmp); +#endif + } + + // Helper function to provide InterlockedExchangePointer functionality on very + // old Platform SDKs. + void* interlocked_exchange_pointer(void** dest, void* val) + { +#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) + return reinterpret_cast(InterlockedExchange( + reinterpret_cast(dest), reinterpret_cast(val))); +#else + return InterlockedExchangePointer(dest, val); +#endif + } + + // The IOCP service used for running asynchronous operations and dispatching + // handlers. + win_iocp_io_service& iocp_service_; + + // The reactor used for performing connect operations. This object is created + // only if needed. + reactor_type* reactor_; + + // Mutex to protect access to the linked list of implementations. + asio::detail::mutex mutex_; + + // The head of a linked list of all implementations. + implementation_type* impl_list_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_IOCP) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_IOCP_SOCKET_SERVICE_HPP diff --git a/libtorrent/include/asio/detail/win_mutex.hpp b/libtorrent/include/asio/detail/win_mutex.hpp new file mode 100644 index 000000000..82659831f --- /dev/null +++ b/libtorrent/include/asio/detail/win_mutex.hpp @@ -0,0 +1,146 @@ +// +// win_mutex.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_MUTEX_HPP +#define ASIO_DETAIL_WIN_MUTEX_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/scoped_lock.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +class win_mutex + : private noncopyable +{ +public: + typedef asio::detail::scoped_lock scoped_lock; + + // Constructor. + win_mutex() + { + int error = do_init(); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "mutex"); + boost::throw_exception(e); + } + } + + // Destructor. + ~win_mutex() + { + ::DeleteCriticalSection(&crit_section_); + } + + // Lock the mutex. + void lock() + { + int error = do_lock(); + if (error != 0) + { + asio::system_error e( + asio::error_code(error, asio::native_ecat), + "mutex"); + boost::throw_exception(e); + } + } + + // Unlock the mutex. + void unlock() + { + ::LeaveCriticalSection(&crit_section_); + } + +private: + // Initialisation must be performed in a separate function to the constructor + // since the compiler does not support the use of structured exceptions and + // C++ exceptions in the same function. + int do_init() + { +#if defined(__MINGW32__) + // Not sure if MinGW supports structured exception handling, so for now + // we'll just call the Windows API and hope. + ::InitializeCriticalSection(&crit_section_); + return 0; +#else + __try + { + ::InitializeCriticalSection(&crit_section_); + } + __except(GetExceptionCode() == STATUS_NO_MEMORY + ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) + { + return ERROR_OUTOFMEMORY; + } + + return 0; +#endif + } + + // Locking must be performed in a separate function to lock() since the + // compiler does not support the use of structured exceptions and C++ + // exceptions in the same function. + int do_lock() + { +#if defined(__MINGW32__) + // Not sure if MinGW supports structured exception handling, so for now + // we'll just call the Windows API and hope. + ::EnterCriticalSection(&crit_section_); + return 0; +#else + __try + { + ::EnterCriticalSection(&crit_section_); + } + __except(GetExceptionCode() == STATUS_INVALID_HANDLE + || GetExceptionCode() == STATUS_NO_MEMORY + ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) + { + if (GetExceptionCode() == STATUS_NO_MEMORY) + return ERROR_OUTOFMEMORY; + return ERROR_INVALID_HANDLE; + } + + return 0; +#endif + } + + ::CRITICAL_SECTION crit_section_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_MUTEX_HPP diff --git a/libtorrent/include/asio/detail/win_signal_blocker.hpp b/libtorrent/include/asio/detail/win_signal_blocker.hpp new file mode 100644 index 000000000..892c40f89 --- /dev/null +++ b/libtorrent/include/asio/detail/win_signal_blocker.hpp @@ -0,0 +1,67 @@ +// +// win_signal_blocker.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_SIGNAL_BLOCKER_HPP +#define ASIO_DETAIL_WIN_SIGNAL_BLOCKER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class win_signal_blocker + : private noncopyable +{ +public: + // Constructor blocks all signals for the calling thread. + win_signal_blocker() + { + // No-op. + } + + // Destructor restores the previous signal mask. + ~win_signal_blocker() + { + // No-op. + } + + // Block all signals for the calling thread. + void block() + { + // No-op. + } + + // Restore the previous signal mask. + void unblock() + { + // No-op. + } +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_SIGNAL_BLOCKER_HPP diff --git a/libtorrent/include/asio/detail/win_thread.hpp b/libtorrent/include/asio/detail/win_thread.hpp new file mode 100644 index 000000000..a6c9b15d2 --- /dev/null +++ b/libtorrent/include/asio/detail/win_thread.hpp @@ -0,0 +1,123 @@ +// +// win_thread.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_THREAD_HPP +#define ASIO_DETAIL_WIN_THREAD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +unsigned int __stdcall win_thread_function(void* arg); + +class win_thread + : private noncopyable +{ +public: + // Constructor. + template + win_thread(Function f) + { + std::auto_ptr arg(new func(f)); + unsigned int thread_id = 0; + thread_ = reinterpret_cast(::_beginthreadex(0, 0, + win_thread_function, arg.get(), 0, &thread_id)); + if (!thread_) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "thread"); + boost::throw_exception(e); + } + arg.release(); + } + + // Destructor. + ~win_thread() + { + ::CloseHandle(thread_); + } + + // Wait for the thread to exit. + void join() + { + ::WaitForSingleObject(thread_, INFINITE); + } + +private: + friend unsigned int __stdcall win_thread_function(void* arg); + + class func_base + { + public: + virtual ~func_base() {} + virtual void run() = 0; + }; + + template + class func + : public func_base + { + public: + func(Function f) + : f_(f) + { + } + + virtual void run() + { + f_(); + } + + private: + Function f_; + }; + + ::HANDLE thread_; +}; + +inline unsigned int __stdcall win_thread_function(void* arg) +{ + std::auto_ptr func( + static_cast(arg)); + func->run(); + return 0; +} + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_THREAD_HPP diff --git a/libtorrent/include/asio/detail/win_tss_ptr.hpp b/libtorrent/include/asio/detail/win_tss_ptr.hpp new file mode 100644 index 000000000..d3e2f8161 --- /dev/null +++ b/libtorrent/include/asio/detail/win_tss_ptr.hpp @@ -0,0 +1,87 @@ +// +// win_tss_ptr.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_TSS_PTR_HPP +#define ASIO_DETAIL_WIN_TSS_PTR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +template +class win_tss_ptr + : private noncopyable +{ +public: + // Constructor. + win_tss_ptr() + { + tss_key_ = ::TlsAlloc(); + if (tss_key_ == TLS_OUT_OF_INDEXES) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, asio::native_ecat), + "tss"); + boost::throw_exception(e); + } + } + + // Destructor. + ~win_tss_ptr() + { + ::TlsFree(tss_key_); + } + + // Get the value. + operator T*() const + { + return static_cast(::TlsGetValue(tss_key_)); + } + + // Set the value. + void operator=(T* value) + { + ::TlsSetValue(tss_key_, value); + } + +private: + // Thread-specific storage to allow unlocked access to determine whether a + // thread is a member of the pool. + DWORD tss_key_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_TSS_PTR_HPP diff --git a/libtorrent/include/asio/detail/winsock_init.hpp b/libtorrent/include/asio/detail/winsock_init.hpp new file mode 100644 index 000000000..67c69e8ce --- /dev/null +++ b/libtorrent/include/asio/detail/winsock_init.hpp @@ -0,0 +1,118 @@ +// +// winsock_init.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WINSOCK_INIT_HPP +#define ASIO_DETAIL_WINSOCK_INIT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +template +class winsock_init + : private noncopyable +{ +private: + // Structure to perform the actual initialisation. + struct do_init + { + do_init() + { + WSADATA wsa_data; + result_ = ::WSAStartup(MAKEWORD(Major, Minor), &wsa_data); + } + + ~do_init() + { + ::WSACleanup(); + } + + int result() const + { + return result_; + } + + // Helper function to manage a do_init singleton. The static instance of the + // winsock_init object ensures that this function is always called before + // main, and therefore before any other threads can get started. The do_init + // instance must be static in this function to ensure that it gets + // initialised before any other global objects try to use it. + static boost::shared_ptr instance() + { + static boost::shared_ptr init(new do_init); + return init; + } + + private: + int result_; + }; + +public: + // Constructor. + winsock_init() + : ref_(do_init::instance()) + { + // Check whether winsock was successfully initialised. This check is not + // performed for the global instance since there will be nobody around to + // catch the exception. + if (this != &instance_ && ref_->result() != 0) + { + asio::system_error e( + asio::error_code(ref_->result(), asio::native_ecat), + "winsock"); + boost::throw_exception(e); + } + } + + // Destructor. + ~winsock_init() + { + } + +private: + // Instance to force initialisation of winsock at global scope. + static winsock_init instance_; + + // Reference to singleton do_init object to ensure that winsock does not get + // cleaned up until the last user has finished with it. + boost::shared_ptr ref_; +}; + +template +winsock_init winsock_init::instance_; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WINSOCK_INIT_HPP diff --git a/libtorrent/include/asio/detail/wrapped_handler.hpp b/libtorrent/include/asio/detail/wrapped_handler.hpp new file mode 100644 index 000000000..f757fd3dc --- /dev/null +++ b/libtorrent/include/asio/detail/wrapped_handler.hpp @@ -0,0 +1,187 @@ +// +// wrapped_handler.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WRAPPED_HANDLER_HPP +#define ASIO_DETAIL_WRAPPED_HANDLER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" + +namespace asio { +namespace detail { + +template +class wrapped_handler +{ +public: + typedef void result_type; + + wrapped_handler(Dispatcher& dispatcher, Handler handler) + : dispatcher_(dispatcher), + handler_(handler) + { + } + + void operator()() + { + dispatcher_.dispatch(handler_); + } + + void operator()() const + { + dispatcher_.dispatch(handler_); + } + + template + void operator()(const Arg1& arg1) + { + dispatcher_.dispatch(detail::bind_handler(handler_, arg1)); + } + + template + void operator()(const Arg1& arg1) const + { + dispatcher_.dispatch(detail::bind_handler(handler_, arg1)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2) + { + dispatcher_.dispatch(detail::bind_handler(handler_, arg1, arg2)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2) const + { + dispatcher_.dispatch(detail::bind_handler(handler_, arg1, arg2)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) + { + dispatcher_.dispatch(detail::bind_handler(handler_, arg1, arg2, arg3)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) const + { + dispatcher_.dispatch(detail::bind_handler(handler_, arg1, arg2, arg3)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, + const Arg4& arg4) + { + dispatcher_.dispatch( + detail::bind_handler(handler_, arg1, arg2, arg3, arg4)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, + const Arg4& arg4) const + { + dispatcher_.dispatch( + detail::bind_handler(handler_, arg1, arg2, arg3, arg4)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, + const Arg4& arg4, const Arg5& arg5) + { + dispatcher_.dispatch( + detail::bind_handler(handler_, arg1, arg2, arg3, arg4, arg5)); + } + + template + void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, + const Arg4& arg4, const Arg5& arg5) const + { + dispatcher_.dispatch( + detail::bind_handler(handler_, arg1, arg2, arg3, arg4, arg5)); + } + +//private: + Dispatcher& dispatcher_; + Handler handler_; +}; + +template +inline void* asio_handler_allocate(std::size_t size, + wrapped_handler* this_handler) +{ + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); +} + +template +inline void asio_handler_deallocate(void* pointer, std::size_t size, + wrapped_handler* this_handler) +{ + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); +} + +template +class rewrapped_handler +{ +public: + explicit rewrapped_handler(const Handler& handler, const Context& context) + : handler_(handler), + context_(context) + { + } + + void operator()() + { + handler_(); + } + + void operator()() const + { + handler_(); + } + +//private: + Handler handler_; + Context context_; +}; + +template +inline void asio_handler_invoke(const Function& function, + wrapped_handler* this_handler) +{ + this_handler->dispatcher_.dispatch( + rewrapped_handler( + function, this_handler->handler_)); +} + +template +inline void asio_handler_invoke(const Function& function, + rewrapped_handler* this_handler) +{ + asio_handler_invoke_helpers::invoke( + function, &this_handler->context_); +} + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WRAPPED_HANDLER_HPP diff --git a/libtorrent/include/asio/error.hpp b/libtorrent/include/asio/error.hpp new file mode 100644 index 000000000..935cc6796 --- /dev/null +++ b/libtorrent/include/asio/error.hpp @@ -0,0 +1,367 @@ +// +// error.hpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ERROR_HPP +#define ASIO_ERROR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error_code.hpp" +#include "asio/detail/socket_types.hpp" + +#if defined(GENERATING_DOCUMENTATION) +/// INTERNAL ONLY. +# define ASIO_NATIVE_ERROR(e) implementation_defined +/// INTERNAL ONLY. +# define ASIO_SOCKET_ERROR(e) implementation_defined +/// INTERNAL ONLY. +# define ASIO_NETDB_ERROR(e) implementation_defined +/// INTERNAL ONLY. +# define ASIO_GETADDRINFO_ERROR(e) implementation_defined +/// INTERNAL ONLY. +# define ASIO_WIN_OR_POSIX(e_win, e_posix) implementation_defined +#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# define ASIO_NATIVE_ERROR(e) \ + asio::error_code(e, \ + asio::native_ecat) +# define ASIO_SOCKET_ERROR(e) \ + asio::error_code(WSA ## e, \ + asio::native_ecat) +# define ASIO_NETDB_ERROR(e) \ + asio::error_code(WSA ## e, \ + asio::native_ecat) +# define ASIO_GETADDRINFO_ERROR(e) \ + asio::error_code(WSA ## e, \ + asio::native_ecat) +# define ASIO_MISC_ERROR(e) \ + asio::error_code(e, \ + asio::misc_ecat) +# define ASIO_WIN_OR_POSIX(e_win, e_posix) e_win +#else +# define ASIO_NATIVE_ERROR(e) \ + asio::error_code(e, \ + asio::native_ecat) +# define ASIO_SOCKET_ERROR(e) \ + asio::error_code(e, \ + asio::native_ecat) +# define ASIO_NETDB_ERROR(e) \ + asio::error_code(e, \ + asio::netdb_ecat) +# define ASIO_GETADDRINFO_ERROR(e) \ + asio::error_code(e, \ + asio::addrinfo_ecat) +# define ASIO_MISC_ERROR(e) \ + asio::error_code(e, \ + asio::misc_ecat) +# define ASIO_WIN_OR_POSIX(e_win, e_posix) e_posix +#endif + +namespace asio { + +namespace detail { + +/// Hack to keep asio library header-file-only. +template +class error_base +{ +public: + // boostify: error category declarations go here. + + /// Permission denied. + static const asio::error_code access_denied; + + /// Address family not supported by protocol. + static const asio::error_code address_family_not_supported; + + /// Address already in use. + static const asio::error_code address_in_use; + + /// Transport endpoint is already connected. + static const asio::error_code already_connected; + + /// Already open. + static const asio::error_code already_open; + + /// Operation already in progress. + static const asio::error_code already_started; + + /// A connection has been aborted. + static const asio::error_code connection_aborted; + + /// Connection refused. + static const asio::error_code connection_refused; + + /// Connection reset by peer. + static const asio::error_code connection_reset; + + /// Bad file descriptor. + static const asio::error_code bad_descriptor; + + /// End of file or stream. + static const asio::error_code eof; + + /// Bad address. + static const asio::error_code fault; + + /// Host not found (authoritative). + static const asio::error_code host_not_found; + + /// Host not found (non-authoritative). + static const asio::error_code host_not_found_try_again; + + /// No route to host. + static const asio::error_code host_unreachable; + + /// Operation now in progress. + static const asio::error_code in_progress; + + /// Interrupted system call. + static const asio::error_code interrupted; + + /// Invalid argument. + static const asio::error_code invalid_argument; + + /// Message too long. + static const asio::error_code message_size; + + /// Network is down. + static const asio::error_code network_down; + + /// Network dropped connection on reset. + static const asio::error_code network_reset; + + /// Network is unreachable. + static const asio::error_code network_unreachable; + + /// Too many open files. + static const asio::error_code no_descriptors; + + /// No buffer space available. + static const asio::error_code no_buffer_space; + + /// The query is valid but does not have associated address data. + static const asio::error_code no_data; + + /// Cannot allocate memory. + static const asio::error_code no_memory; + + /// Operation not permitted. + static const asio::error_code no_permission; + + /// Protocol not available. + static const asio::error_code no_protocol_option; + + /// A non-recoverable error occurred. + static const asio::error_code no_recovery; + + /// Transport endpoint is not connected. + static const asio::error_code not_connected; + + /// Element not found. + static const asio::error_code not_found; + + /// Socket operation on non-socket. + static const asio::error_code not_socket; + + /// Operation cancelled. + static const asio::error_code operation_aborted; + + /// Operation not supported. + static const asio::error_code operation_not_supported; + + /// The service is not supported for the given socket type. + static const asio::error_code service_not_found; + + /// The socket type is not supported. + static const asio::error_code socket_type_not_supported; + + /// Cannot send after transport endpoint shutdown. + static const asio::error_code shut_down; + + /// Connection timed out. + static const asio::error_code timed_out; + + /// Resource temporarily unavailable. + static const asio::error_code try_again; + + /// The socket is marked non-blocking and the requested operation would block. + static const asio::error_code would_block; + +private: + error_base(); +}; + +// boostify: error category definitions go here. + +template const asio::error_code +error_base::access_denied = ASIO_SOCKET_ERROR(EACCES); + +template const asio::error_code +error_base::address_family_not_supported = ASIO_SOCKET_ERROR( + EAFNOSUPPORT); + +template const asio::error_code +error_base::address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE); + +template const asio::error_code +error_base::already_connected = ASIO_SOCKET_ERROR(EISCONN); + +template const asio::error_code +error_base::already_open = ASIO_MISC_ERROR(1); + +template const asio::error_code +error_base::already_started = ASIO_SOCKET_ERROR(EALREADY); + +template const asio::error_code +error_base::connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED); + +template const asio::error_code +error_base::connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED); + +template const asio::error_code +error_base::connection_reset = ASIO_SOCKET_ERROR(ECONNRESET); + +template const asio::error_code +error_base::bad_descriptor = ASIO_SOCKET_ERROR(EBADF); + +template const asio::error_code +error_base::eof = ASIO_MISC_ERROR(2); + +template const asio::error_code +error_base::fault = ASIO_SOCKET_ERROR(EFAULT); + +template const asio::error_code +error_base::host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND); + +template const asio::error_code +error_base::host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN); + +template const asio::error_code +error_base::host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH); + +template const asio::error_code +error_base::in_progress = ASIO_SOCKET_ERROR(EINPROGRESS); + +template const asio::error_code +error_base::interrupted = ASIO_SOCKET_ERROR(EINTR); + +template const asio::error_code +error_base::invalid_argument = ASIO_SOCKET_ERROR(EINVAL); + +template const asio::error_code +error_base::message_size = ASIO_SOCKET_ERROR(EMSGSIZE); + +template const asio::error_code +error_base::network_down = ASIO_SOCKET_ERROR(ENETDOWN); + +template const asio::error_code +error_base::network_reset = ASIO_SOCKET_ERROR(ENETRESET); + +template const asio::error_code +error_base::network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH); + +template const asio::error_code +error_base::no_descriptors = ASIO_SOCKET_ERROR(EMFILE); + +template const asio::error_code +error_base::no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS); + +template const asio::error_code +error_base::no_data = ASIO_NETDB_ERROR(NO_DATA); + +template const asio::error_code +error_base::no_memory = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), + ASIO_NATIVE_ERROR(ENOMEM)); + +template const asio::error_code +error_base::no_permission = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), + ASIO_NATIVE_ERROR(EPERM)); + +template const asio::error_code +error_base::no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT); + +template const asio::error_code +error_base::no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY); + +template const asio::error_code +error_base::not_connected = ASIO_SOCKET_ERROR(ENOTCONN); + +template const asio::error_code +error_base::not_found = ASIO_MISC_ERROR(3); + +template const asio::error_code +error_base::not_socket = ASIO_SOCKET_ERROR(ENOTSOCK); + +template const asio::error_code +error_base::operation_aborted = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), + ASIO_NATIVE_ERROR(ECANCELED)); + +template const asio::error_code +error_base::operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP); + +template const asio::error_code +error_base::service_not_found = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), + ASIO_GETADDRINFO_ERROR(EAI_SERVICE)); + +template const asio::error_code +error_base::socket_type_not_supported = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), + ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)); + +template const asio::error_code +error_base::shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN); + +template const asio::error_code +error_base::timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT); + +template const asio::error_code +error_base::try_again = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_RETRY), + ASIO_NATIVE_ERROR(EAGAIN)); + +template const asio::error_code +error_base::would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK); + +} // namespace detail + +/// Contains error constants. +class error : public asio::detail::error_base +{ +private: + error(); +}; + +} // namespace asio + +#undef ASIO_NATIVE_ERROR +#undef ASIO_SOCKET_ERROR +#undef ASIO_NETDB_ERROR +#undef ASIO_GETADDRINFO_ERROR +#undef ASIO_MISC_ERROR +#undef ASIO_WIN_OR_POSIX + +#include "asio/impl/error_code.ipp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ERROR_HPP diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp new file mode 100644 index 000000000..0614490e2 --- /dev/null +++ b/libtorrent/include/asio/error_code.hpp @@ -0,0 +1,139 @@ +// +// error_code.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ERROR_CODE_HPP +#define ASIO_ERROR_CODE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#if defined(GENERATING_DOCUMENTATION) +# define ASIO_WIN_OR_POSIX(e_win, e_posix) implementation_defined +#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) +# define ASIO_WIN_OR_POSIX(e_win, e_posix) e_win +#else +# define ASIO_WIN_OR_POSIX(e_win, e_posix) e_posix +#endif + +namespace asio { + +/// Available error code categories. +enum error_category +{ + /// Native error codes. + native_ecat = ASIO_WIN_OR_POSIX(0, 0), + + /// Error codes from NetDB functions. + netdb_ecat = ASIO_WIN_OR_POSIX(native_ecat, 1), + + /// Error codes from getaddrinfo. + addrinfo_ecat = ASIO_WIN_OR_POSIX(native_ecat, 2), + + /// Miscellaneous error codes. + misc_ecat = ASIO_WIN_OR_POSIX(3, 3), + + /// SSL error codes. + ssl_ecat = ASIO_WIN_OR_POSIX(4, 4) +}; + +/// Class to represent an error code value. +class error_code +{ +public: + /// The underlying representation of an error code. + typedef int value_type; + + /// Default constructor. + error_code() + : value_(0), + category_(native_ecat) + { + } + + /// Construct with specific error code and category. + error_code(value_type v, error_category c) + : value_(v), + category_(c) + { + } + + /// Get the error value. + value_type value() const + { + return value_; + } + + /// Get the error category. + error_category category() const + { + return category_; + } + + /// Get the message associated with the error. + std::string message() const; + + struct unspecified_bool_type_t + { + }; + + typedef unspecified_bool_type_t* unspecified_bool_type; + + /// Operator returns non-null if there is a non-success error code. + operator unspecified_bool_type() const + { + if (value_ == 0) + return 0; + else + return reinterpret_cast(1); + } + + /// Operator to test if the error represents success. + bool operator!() const + { + return value_ == 0; + } + + /// Equality operator to compare two error objects. + friend bool operator==(const error_code& e1, const error_code& e2) + { + return e1.value_ == e2.value_ && e1.category_ == e2.category_; + } + + /// Inequality operator to compare two error objects. + friend bool operator!=(const error_code& e1, const error_code& e2) + { + return e1.value_ != e2.value_ || e1.category_ != e2.category_; + } + +private: + // The value associated with the error code. + value_type value_; + + // The category associated with the error code. + error_category category_; +}; + +} // namespace asio + +#undef ASIO_WIN_OR_POSIX + +#include "asio/error.hpp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ERROR_CODE_HPP diff --git a/libtorrent/include/asio/handler_alloc_hook.hpp b/libtorrent/include/asio/handler_alloc_hook.hpp new file mode 100644 index 000000000..042b1fecd --- /dev/null +++ b/libtorrent/include/asio/handler_alloc_hook.hpp @@ -0,0 +1,88 @@ +// +// handler_alloc_hook.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_HANDLER_ALLOC_HOOK_HPP +#define ASIO_HANDLER_ALLOC_HOOK_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { + +/// Default allocation function for handlers. +/** + * Asynchronous operations may need to allocate temporary objects. Since + * asynchronous operations have a handler function object, these temporary + * objects can be said to be associated with the handler. + * + * Implement asio_handler_allocate and asio_handler_deallocate for your own + * handlers to provide custom allocation for these temporary objects. + * + * This default implementation is simply: + * @code + * return ::operator new(bytes); + * @endcode + * + * @note All temporary objects associated with a handler will be deallocated + * before the upcall to the handler is performed. This allows the same memory to + * be reused for a subsequent asynchronous operation initiated by the handler. + * + * @par Example + * @code + * class my_handler; + * + * void* asio_handler_allocate(std::size_t size, my_handler* context) + * { + * return ::operator new(size); + * } + * + * void asio_handler_deallocate(void* pointer, std::size_t size, + * my_handler* context) + * { + * ::operator delete(pointer); + * } + * @endcode + */ +inline void* asio_handler_allocate(std::size_t size, ...) +{ + return ::operator new(size); +} + +/// Default deallocation function for handlers. +/** + * Implement asio_handler_allocate and asio_handler_deallocate for your own + * handlers to provide custom allocation for the associated temporary objects. + * + * This default implementation is simply: + * @code + * ::operator delete(pointer); + * @endcode + * + * @sa asio_handler_allocate. + */ +inline void asio_handler_deallocate(void* pointer, std::size_t size, ...) +{ + (void)(size); + ::operator delete(pointer); +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_HANDLER_ALLOC_HOOK_HPP diff --git a/libtorrent/include/asio/handler_invoke_hook.hpp b/libtorrent/include/asio/handler_invoke_hook.hpp new file mode 100644 index 000000000..4ba5db329 --- /dev/null +++ b/libtorrent/include/asio/handler_invoke_hook.hpp @@ -0,0 +1,69 @@ +// +// handler_invoke_hook.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_HANDLER_INVOKE_HOOK_HPP +#define ASIO_HANDLER_INVOKE_HOOK_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +namespace asio { + +/// Default invoke function for handlers. +/** + * Completion handlers for asynchronous operations are invoked by the + * io_service associated with the corresponding object (e.g. a socket or + * deadline_timer). Certain guarantees are made on when the handler may be + * invoked, in particular that a handler can only be invoked from a thread that + * is currently calling asio::io_service::run() on the corresponding + * io_service object. Handlers may subsequently be invoked through other + * objects (such as asio::strand objects) that provide additional + * guarantees. + * + * When asynchronous operations are composed from other asynchronous + * operations, all intermediate handlers should be invoked using the same + * method as the final handler. This is required to ensure that user-defined + * objects are not accessed in a way that may violate the guarantees. This + * hooking function ensures that the invoked method used for the final handler + * is accessible at each intermediate step. + * + * Implement asio_handler_invoke for your own handlers to specify a custom + * invocation strategy. + * + * This default implementation is simply: + * @code + * function(); + * @endcode + * + * @par Example + * @code + * class my_handler; + * + * template + * void asio_handler_invoke(Function function, my_handler* context) + * { + * context->strand_.dispatch(function); + * } + * @endcode + */ +template +inline void asio_handler_invoke(Function function, ...) +{ + function(); +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_HANDLER_INVOKE_HOOK_HPP diff --git a/libtorrent/include/asio/impl/CVS/Entries b/libtorrent/include/asio/impl/CVS/Entries new file mode 100644 index 000000000..eadcea08f --- /dev/null +++ b/libtorrent/include/asio/impl/CVS/Entries @@ -0,0 +1,6 @@ +/error_code.ipp/1.6/Sun Mar 25 14:06:36 2007// +/io_service.ipp/1.12/Mon Jan 8 01:04:08 2007// +/read.ipp/1.16/Sat Jan 13 13:30:12 2007// +/read_until.ipp/1.10/Sun Jan 7 08:05:53 2007// +/write.ipp/1.14/Sat Jan 13 13:30:12 2007// +D diff --git a/libtorrent/include/asio/impl/CVS/Repository b/libtorrent/include/asio/impl/CVS/Repository new file mode 100644 index 000000000..c5e1b5327 --- /dev/null +++ b/libtorrent/include/asio/impl/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio/impl diff --git a/libtorrent/include/asio/impl/CVS/Root b/libtorrent/include/asio/impl/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/impl/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/impl/error_code.ipp b/libtorrent/include/asio/impl/error_code.ipp new file mode 100644 index 000000000..da2f98833 --- /dev/null +++ b/libtorrent/include/asio/impl/error_code.ipp @@ -0,0 +1,98 @@ +// +// error_code.ipp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ERROR_CODE_IPP +#define ASIO_ERROR_CODE_IPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/local_free_on_block_exit.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { + +inline std::string error_code::message() const +{ + if (*this == error::already_open) + return "Already open."; + if (*this == error::not_found) + return "Not found."; + if (category_ == ssl_ecat) + return "SSL error."; +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + value_type value = value_; + if (*this == error::eof) + value = ERROR_HANDLE_EOF; + char* msg = 0; + DWORD length = ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, 0, value, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&msg, 0, 0); + detail::local_free_on_block_exit local_free_obj(msg); + if (length && msg[length - 1] == '\n') + msg[--length] = '\0'; + if (length && msg[length - 1] == '\r') + msg[--length] = '\0'; + if (length) + return msg; + else + return "asio error"; +#else // defined(BOOST_WINDOWS) + if (*this == error::eof) + return "End of file."; + if (*this == error::host_not_found) + return "Host not found (authoritative)."; + if (*this == error::host_not_found_try_again) + return "Host not found (non-authoritative), try again later."; + if (*this == error::no_recovery) + return "A non-recoverable error occurred during database lookup."; + if (*this == error::no_data) + return "The query is valid, but it does not have associated data."; + if (*this == error::not_found) + return "Element not found."; +#if !defined(__sun) + if (*this == error::operation_aborted) + return "Operation aborted."; +#endif // !defined(__sun) + if (*this == error::service_not_found) + return "Service not found."; + if (*this == error::socket_type_not_supported) + return "Socket type not supported."; +#if defined(__sun) || defined(__QNX__) + return strerror(value_); +#elif defined(__MACH__) && defined(__APPLE__) \ +|| defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) \ +|| defined(_AIX) + char buf[256] = ""; + strerror_r(value_, buf, sizeof(buf)); + return buf; +#else + char buf[256] = ""; + return strerror_r(value_, buf, sizeof(buf)); +#endif +#endif // defined(BOOST_WINDOWS) +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ERROR_CODE_IPP diff --git a/libtorrent/include/asio/impl/io_service.ipp b/libtorrent/include/asio/impl/io_service.ipp new file mode 100644 index 000000000..e973619d1 --- /dev/null +++ b/libtorrent/include/asio/impl/io_service.ipp @@ -0,0 +1,213 @@ +// +// io_service.ipp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IO_SERVICE_IPP +#define ASIO_IO_SERVICE_IPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/epoll_reactor.hpp" +#include "asio/detail/kqueue_reactor.hpp" +#include "asio/detail/select_reactor.hpp" +#include "asio/detail/service_registry.hpp" +#include "asio/detail/task_io_service.hpp" +#include "asio/detail/throw_error.hpp" +#include "asio/detail/win_iocp_io_service.hpp" + +namespace asio { + +inline io_service::io_service() + : service_registry_(new asio::detail::service_registry(*this)), + impl_(service_registry_->use_service()) +{ + impl_.init((std::numeric_limits::max)()); +} + +inline io_service::io_service(std::size_t concurrency_hint) + : service_registry_(new asio::detail::service_registry(*this)), + impl_(service_registry_->use_service()) +{ + impl_.init(concurrency_hint); +} + +inline io_service::~io_service() +{ + delete service_registry_; +} + +inline std::size_t io_service::run() +{ + asio::error_code ec; + std::size_t s = impl_.run(ec); + asio::detail::throw_error(ec); + return s; +} + +inline std::size_t io_service::run(asio::error_code& ec) +{ + return impl_.run(ec); +} + +inline std::size_t io_service::run_one() +{ + asio::error_code ec; + std::size_t s = impl_.run_one(ec); + asio::detail::throw_error(ec); + return s; +} + +inline std::size_t io_service::run_one(asio::error_code& ec) +{ + return impl_.run_one(ec); +} + +inline std::size_t io_service::poll() +{ + asio::error_code ec; + std::size_t s = impl_.poll(ec); + asio::detail::throw_error(ec); + return s; +} + +inline std::size_t io_service::poll(asio::error_code& ec) +{ + return impl_.poll(ec); +} + +inline std::size_t io_service::poll_one() +{ + asio::error_code ec; + std::size_t s = impl_.poll_one(ec); + asio::detail::throw_error(ec); + return s; +} + +inline std::size_t io_service::poll_one(asio::error_code& ec) +{ + return impl_.poll_one(ec); +} + +inline void io_service::stop() +{ + impl_.stop(); +} + +inline void io_service::reset() +{ + impl_.reset(); +} + +template +inline void io_service::dispatch(Handler handler) +{ + impl_.dispatch(handler); +} + +template +inline void io_service::post(Handler handler) +{ + impl_.post(handler); +} + +template +#if defined(GENERATING_DOCUMENTATION) +unspecified +#else +inline detail::wrapped_handler +#endif +io_service::wrap(Handler handler) +{ + return detail::wrapped_handler(*this, handler); +} + +inline io_service::work::work(asio::io_service& io_service) + : io_service_(io_service) +{ + io_service_.impl_.work_started(); +} + +inline io_service::work::work(const work& other) + : io_service_(other.io_service_) +{ + io_service_.impl_.work_started(); +} + +inline io_service::work::~work() +{ + io_service_.impl_.work_finished(); +} + +inline asio::io_service& io_service::work::io_service() +{ + return io_service_; +} + +inline io_service::service::service(asio::io_service& owner) + : owner_(owner), + type_info_(0), + next_(0) +{ +} + +inline io_service::service::~service() +{ +} + +inline asio::io_service& io_service::service::io_service() +{ + return owner_; +} + +template +inline Service& use_service(io_service& ios) +{ + // Check that Service meets the necessary type requirements. + (void)static_cast(static_cast(0)); + (void)static_cast(&Service::id); + + return ios.service_registry_->template use_service(); +} + +template +void add_service(io_service& ios, Service* svc) +{ + // Check that Service meets the necessary type requirements. + (void)static_cast(static_cast(0)); + (void)static_cast(&Service::id); + + if (&ios != &svc->io_service()) + boost::throw_exception(invalid_service_owner()); + if (!ios.service_registry_->template add_service(svc)) + boost::throw_exception(service_already_exists()); +} + +template +bool has_service(io_service& ios) +{ + // Check that Service meets the necessary type requirements. + (void)static_cast(static_cast(0)); + (void)static_cast(&Service::id); + + return ios.service_registry_->template has_service(); +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IO_SERVICE_IPP diff --git a/libtorrent/include/asio/impl/read.ipp b/libtorrent/include/asio/impl/read.ipp new file mode 100644 index 000000000..f30f50fb0 --- /dev/null +++ b/libtorrent/include/asio/impl/read.ipp @@ -0,0 +1,314 @@ +// +// read.ipp +// ~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_READ_IPP +#define ASIO_READ_IPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/completion_condition.hpp" +#include "asio/error.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/consuming_buffers.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +template +std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers, + CompletionCondition completion_condition, asio::error_code& ec) +{ + asio::detail::consuming_buffers< + mutable_buffer, MutableBufferSequence> tmp(buffers); + std::size_t total_transferred = 0; + while (tmp.begin() != tmp.end()) + { + std::size_t bytes_transferred = s.read_some(tmp, ec); + tmp.consume(bytes_transferred); + total_transferred += bytes_transferred; + if (completion_condition(ec, total_transferred)) + return total_transferred; + } + ec = asio::error_code(); + return total_transferred; +} + +template +inline std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers) +{ + asio::error_code ec; + std::size_t bytes_transferred = read(s, buffers, transfer_all(), ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +inline std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers, + CompletionCondition completion_condition) +{ + asio::error_code ec; + std::size_t bytes_transferred = read(s, buffers, completion_condition, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +std::size_t read(SyncReadStream& s, + asio::basic_streambuf& b, + CompletionCondition completion_condition, asio::error_code& ec) +{ + std::size_t total_transferred = 0; + for (;;) + { + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + std::size_t bytes_transferred = s.read_some(b.prepare(bytes_available), ec); + b.commit(bytes_transferred); + total_transferred += bytes_transferred; + if (b.size() == b.max_size() + || completion_condition(ec, total_transferred)) + return total_transferred; + } +} + +template +inline std::size_t read(SyncReadStream& s, + asio::basic_streambuf& b) +{ + asio::error_code ec; + std::size_t bytes_transferred = read(s, b, transfer_all(), ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +inline std::size_t read(SyncReadStream& s, + asio::basic_streambuf& b, + CompletionCondition completion_condition) +{ + asio::error_code ec; + std::size_t bytes_transferred = read(s, b, completion_condition, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +namespace detail +{ + template + class read_handler + { + public: + typedef asio::detail::consuming_buffers< + mutable_buffer, MutableBufferSequence> buffers_type; + + read_handler(AsyncReadStream& stream, const buffers_type& buffers, + CompletionCondition completion_condition, ReadHandler handler) + : stream_(stream), + buffers_(buffers), + total_transferred_(0), + completion_condition_(completion_condition), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + total_transferred_ += bytes_transferred; + buffers_.consume(bytes_transferred); + if (completion_condition_(ec, total_transferred_) + || buffers_.begin() == buffers_.end()) + { + handler_(ec, total_transferred_); + } + else + { + stream_.async_read_some(buffers_, *this); + } + } + + //private: + AsyncReadStream& stream_; + buffers_type buffers_; + std::size_t total_transferred_; + CompletionCondition completion_condition_; + ReadHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + read_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + read_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + read_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +inline void async_read(AsyncReadStream& s, const MutableBufferSequence& buffers, + CompletionCondition completion_condition, ReadHandler handler) +{ + asio::detail::consuming_buffers< + mutable_buffer, MutableBufferSequence> tmp(buffers); + s.async_read_some(tmp, + detail::read_handler( + s, tmp, completion_condition, handler)); +} + +template +inline void async_read(AsyncReadStream& s, const MutableBufferSequence& buffers, + ReadHandler handler) +{ + async_read(s, buffers, transfer_all(), handler); +} + +namespace detail +{ + template + class read_streambuf_handler + { + public: + read_streambuf_handler(AsyncReadStream& stream, + basic_streambuf& streambuf, + CompletionCondition completion_condition, ReadHandler handler) + : stream_(stream), + streambuf_(streambuf), + total_transferred_(0), + completion_condition_(completion_condition), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + total_transferred_ += bytes_transferred; + streambuf_.commit(bytes_transferred); + if (streambuf_.size() == streambuf_.max_size() + || completion_condition_(ec, total_transferred_)) + { + handler_(ec, total_transferred_); + } + else + { + std::size_t bytes_available = + std::min(512, streambuf_.max_size() - streambuf_.size()); + stream_.async_read_some(streambuf_.prepare(bytes_available), *this); + } + } + + //private: + AsyncReadStream& stream_; + asio::basic_streambuf& streambuf_; + std::size_t total_transferred_; + CompletionCondition completion_condition_; + ReadHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + read_streambuf_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + read_streambuf_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + read_streambuf_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +inline void async_read(AsyncReadStream& s, + asio::basic_streambuf& b, + CompletionCondition completion_condition, ReadHandler handler) +{ + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + s.async_read_some(b.prepare(bytes_available), + detail::read_streambuf_handler( + s, b, completion_condition, handler)); +} + +template +inline void async_read(AsyncReadStream& s, + asio::basic_streambuf& b, ReadHandler handler) +{ + async_read(s, b, transfer_all(), handler); +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_READ_IPP diff --git a/libtorrent/include/asio/impl/read_until.ipp b/libtorrent/include/asio/impl/read_until.ipp new file mode 100644 index 000000000..64c15ec7d --- /dev/null +++ b/libtorrent/include/asio/impl/read_until.ipp @@ -0,0 +1,750 @@ +// +// read_until.ipp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_READ_UNTIL_IPP +#define ASIO_READ_UNTIL_IPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/const_buffers_iterator.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +template +inline std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, char delim) +{ + asio::error_code ec; + std::size_t bytes_transferred = read_until(s, b, delim, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, char delim, + asio::error_code& ec) +{ + std::size_t next_search_start = 0; + for (;;) + { + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = b.data(); + iterator begin(buffers, next_search_start); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + iterator iter = std::find(begin, end, delim); + if (iter != end) + { + // Found a match. We're done. + ec = asio::error_code(); + return iter.position() + 1; + } + else + { + // No match. Next search can start with the new data. + next_search_start = end.position(); + } + + // Check if buffer is full. + if (b.size() == b.max_size()) + { + ec = error::not_found; + return 0; + } + + // Need more data. + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + b.commit(s.read_some(b.prepare(bytes_available), ec)); + if (ec) + return 0; + } +} + +template +inline std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const std::string& delim) +{ + asio::error_code ec; + std::size_t bytes_transferred = read_until(s, b, delim, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +namespace detail +{ + // Algorithm that finds a subsequence of equal values in a sequence. Returns + // (iterator,true) if a full match was found, in which case the iterator + // points to the beginning of the match. Returns (iterator,false) if a + // partial match was found at the end of the first sequence, in which case + // the iterator points to the beginning of the partial match. Returns + // (last1,false) if no full or partial match was found. + template + std::pair partial_search( + Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) + { + for (Iterator1 iter1 = first1; iter1 != last1; ++iter1) + { + Iterator1 test_iter1 = iter1; + Iterator2 test_iter2 = first2; + for (;; ++test_iter1, ++test_iter2) + { + if (test_iter2 == last2) + return std::make_pair(iter1, true); + if (test_iter1 == last1) + { + if (test_iter2 != first2) + return std::make_pair(iter1, false); + else + break; + } + if (*test_iter1 != *test_iter2) + break; + } + } + return std::make_pair(last1, false); + } +} // namespace detail + +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const std::string& delim, + asio::error_code& ec) +{ + std::size_t next_search_start = 0; + for (;;) + { + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = b.data(); + iterator begin(buffers, next_search_start); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + std::pair result = asio::detail::partial_search( + begin, end, delim.begin(), delim.end()); + if (result.first != end) + { + if (result.second) + { + // Full match. We're done. + ec = asio::error_code(); + return result.first.position() + delim.length(); + } + else + { + // Partial match. Next search needs to start from beginning of match. + next_search_start = result.first.position(); + } + } + else + { + // No match. Next search can start with the new data. + next_search_start = end.position(); + } + + // Check if buffer is full. + if (b.size() == b.max_size()) + { + ec = error::not_found; + return 0; + } + + // Need more data. + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + b.commit(s.read_some(b.prepare(bytes_available), ec)); + if (ec) + return 0; + } +} + +template +inline std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const boost::regex& expr) +{ + asio::error_code ec; + std::size_t bytes_transferred = read_until(s, b, expr, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const boost::regex& expr, + asio::error_code& ec) +{ + std::size_t next_search_start = 0; + for (;;) + { + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = b.data(); + iterator begin(buffers, next_search_start); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + boost::match_results match_results; + if (boost::regex_search(begin, end, match_results, expr, + boost::match_default | boost::match_partial)) + { + if (match_results[0].matched) + { + // Full match. We're done. + ec = asio::error_code(); + return match_results[0].second.position(); + } + else + { + // Partial match. Next search needs to start from beginning of match. + next_search_start = match_results[0].first.position(); + } + } + else + { + // No match. Next search can start with the new data. + next_search_start = end.position(); + } + + // Check if buffer is full. + if (b.size() == b.max_size()) + { + ec = error::not_found; + return 0; + } + + // Need more data. + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + b.commit(s.read_some(b.prepare(bytes_available), ec)); + if (ec) + return 0; + } +} + +namespace detail +{ + template + class read_until_delim_handler + { + public: + read_until_delim_handler(AsyncReadStream& stream, + asio::basic_streambuf& streambuf, char delim, + std::size_t next_search_start, ReadHandler handler) + : stream_(stream), + streambuf_(streambuf), + delim_(delim), + next_search_start_(next_search_start), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + // Check for errors. + if (ec) + { + std::size_t bytes = 0; + handler_(ec, bytes); + return; + } + + // Commit received data to streambuf's get area. + streambuf_.commit(bytes_transferred); + + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = streambuf_.data(); + iterator begin(buffers, next_search_start_); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + iterator iter = std::find(begin, end, delim_); + if (iter != end) + { + // Found a match. We're done. + std::size_t bytes = iter.position() + 1; + handler_(ec, bytes); + return; + } + + // No match. Check if buffer is full. + if (streambuf_.size() == streambuf_.max_size()) + { + std::size_t bytes = 0; + handler_(error::not_found, bytes); + return; + } + + // Next search can start with the new data. + next_search_start_ = end.position(); + + // Start a new asynchronous read operation to obtain more data. + std::size_t bytes_available = + std::min(512, streambuf_.max_size() - streambuf_.size()); + stream_.async_read_some(streambuf_.prepare(bytes_available), *this); + } + + //private: + AsyncReadStream& stream_; + asio::basic_streambuf& streambuf_; + char delim_; + std::size_t next_search_start_; + ReadHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + read_until_delim_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + read_until_delim_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + read_until_delim_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +void async_read_until(AsyncReadStream& s, + asio::basic_streambuf& b, char delim, ReadHandler handler) +{ + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = b.data(); + iterator begin(buffers, 0); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + iterator iter = std::find(begin, end, delim); + if (iter != end) + { + // Found a match. We're done. + asio::error_code ec; + std::size_t bytes = iter.position() + 1; + s.io_service().post(detail::bind_handler(handler, ec, bytes)); + return; + } + + // No match. Check if buffer is full. + if (b.size() == b.max_size()) + { + s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + return; + } + + // Start a new asynchronous read operation to obtain more data. + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + s.async_read_some(b.prepare(bytes_available), + detail::read_until_delim_handler( + s, b, delim, end.position(), handler)); +} + +namespace detail +{ + template + class read_until_delim_string_handler + { + public: + read_until_delim_string_handler(AsyncReadStream& stream, + asio::basic_streambuf& streambuf, + const std::string& delim, std::size_t next_search_start, + ReadHandler handler) + : stream_(stream), + streambuf_(streambuf), + delim_(delim), + next_search_start_(next_search_start), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + // Check for errors. + if (ec) + { + std::size_t bytes = 0; + handler_(ec, bytes); + return; + } + + // Commit received data to streambuf's get area. + streambuf_.commit(bytes_transferred); + + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = streambuf_.data(); + iterator begin(buffers, next_search_start_); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + std::pair result = asio::detail::partial_search( + begin, end, delim_.begin(), delim_.end()); + if (result.first != end) + { + if (result.second) + { + // Full match. We're done. + std::size_t bytes = result.first.position() + delim_.length(); + handler_(ec, bytes); + return; + } + else + { + // Partial match. Next search needs to start from beginning of match. + next_search_start_ = result.first.position(); + } + } + else + { + // No match. Next search can start with the new data. + next_search_start_ = end.position(); + } + + // Check if buffer is full. + if (streambuf_.size() == streambuf_.max_size()) + { + std::size_t bytes = 0; + handler_(error::not_found, bytes); + return; + } + + // Start a new asynchronous read operation to obtain more data. + std::size_t bytes_available = + std::min(512, streambuf_.max_size() - streambuf_.size()); + stream_.async_read_some(streambuf_.prepare(bytes_available), *this); + } + + //private: + AsyncReadStream& stream_; + asio::basic_streambuf& streambuf_; + std::string delim_; + std::size_t next_search_start_; + ReadHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + read_until_delim_string_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + read_until_delim_string_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + read_until_delim_string_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +void async_read_until(AsyncReadStream& s, + asio::basic_streambuf& b, const std::string& delim, + ReadHandler handler) +{ + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = b.data(); + iterator begin(buffers, 0); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + std::size_t next_search_start; + std::pair result = asio::detail::partial_search( + begin, end, delim.begin(), delim.end()); + if (result.first != end) + { + if (result.second) + { + // Full match. We're done. + asio::error_code ec; + std::size_t bytes = result.first.position() + delim.length(); + s.io_service().post(detail::bind_handler(handler, ec, bytes)); + return; + } + else + { + // Partial match. Next search needs to start from beginning of match. + next_search_start = result.first.position(); + } + } + else + { + // No match. Next search can start with the new data. + next_search_start = end.position(); + } + + // Check if buffer is full. + if (b.size() == b.max_size()) + { + s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + return; + } + + // Start a new asynchronous read operation to obtain more data. + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + s.async_read_some(b.prepare(bytes_available), + detail::read_until_delim_string_handler< + AsyncReadStream, Allocator, ReadHandler>( + s, b, delim, next_search_start, handler)); +} + +namespace detail +{ + template + class read_until_expr_handler + { + public: + read_until_expr_handler(AsyncReadStream& stream, + asio::basic_streambuf& streambuf, + const boost::regex& expr, std::size_t next_search_start, + ReadHandler handler) + : stream_(stream), + streambuf_(streambuf), + expr_(expr), + next_search_start_(next_search_start), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + // Check for errors. + if (ec) + { + std::size_t bytes = 0; + handler_(ec, bytes); + return; + } + + // Commit received data to streambuf's get area. + streambuf_.commit(bytes_transferred); + + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = streambuf_.data(); + iterator begin(buffers, next_search_start_); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + boost::match_results match_results; + if (boost::regex_search(begin, end, match_results, expr_, + boost::match_default | boost::match_partial)) + { + if (match_results[0].matched) + { + // Full match. We're done. + std::size_t bytes = match_results[0].second.position(); + handler_(ec, bytes); + return; + } + else + { + // Partial match. Next search needs to start from beginning of match. + next_search_start_ = match_results[0].first.position(); + } + } + else + { + // No match. Next search can start with the new data. + next_search_start_ = end.position(); + } + + // Check if buffer is full. + if (streambuf_.size() == streambuf_.max_size()) + { + std::size_t bytes = 0; + handler_(error::not_found, bytes); + return; + } + + // Start a new asynchronous read operation to obtain more data. + std::size_t bytes_available = + std::min(512, streambuf_.max_size() - streambuf_.size()); + stream_.async_read_some(streambuf_.prepare(bytes_available), *this); + } + + //private: + AsyncReadStream& stream_; + asio::basic_streambuf& streambuf_; + boost::regex expr_; + std::size_t next_search_start_; + ReadHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + read_until_expr_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + read_until_expr_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + read_until_expr_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +void async_read_until(AsyncReadStream& s, + asio::basic_streambuf& b, const boost::regex& expr, + ReadHandler handler) +{ + // Determine the range of the data to be searched. + typedef typename asio::basic_streambuf< + Allocator>::const_buffers_type const_buffers_type; + typedef asio::detail::const_buffers_iterator< + const_buffers_type> iterator; + const_buffers_type buffers = b.data(); + iterator begin(buffers, 0); + iterator end(buffers, (std::numeric_limits::max)()); + + // Look for a match. + std::size_t next_search_start; + boost::match_results match_results; + if (boost::regex_search(begin, end, match_results, expr, + boost::match_default | boost::match_partial)) + { + if (match_results[0].matched) + { + // Full match. We're done. + asio::error_code ec; + std::size_t bytes = match_results[0].second.position(); + s.io_service().post(detail::bind_handler(handler, ec, bytes)); + return; + } + else + { + // Partial match. Next search needs to start from beginning of match. + next_search_start = match_results[0].first.position(); + } + } + else + { + // No match. Next search can start with the new data. + next_search_start = end.position(); + } + + // Check if buffer is full. + if (b.size() == b.max_size()) + { + s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + return; + } + + // Start a new asynchronous read operation to obtain more data. + std::size_t bytes_available = + std::min(512, b.max_size() - b.size()); + s.async_read_some(b.prepare(bytes_available), + detail::read_until_expr_handler( + s, b, expr, next_search_start, handler)); +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_READ_UNTIL_IPP diff --git a/libtorrent/include/asio/impl/write.ipp b/libtorrent/include/asio/impl/write.ipp new file mode 100644 index 000000000..8c2d1d33f --- /dev/null +++ b/libtorrent/include/asio/impl/write.ipp @@ -0,0 +1,279 @@ +// +// write.ipp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_WRITE_IPP +#define ASIO_WRITE_IPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/completion_condition.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/consuming_buffers.hpp" +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { + +template +std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers, + CompletionCondition completion_condition, asio::error_code& ec) +{ + asio::detail::consuming_buffers< + const_buffer, ConstBufferSequence> tmp(buffers); + std::size_t total_transferred = 0; + while (tmp.begin() != tmp.end()) + { + std::size_t bytes_transferred = s.write_some(tmp, ec); + tmp.consume(bytes_transferred); + total_transferred += bytes_transferred; + if (completion_condition(ec, total_transferred)) + return total_transferred; + } + ec = asio::error_code(); + return total_transferred; +} + +template +inline std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers) +{ + asio::error_code ec; + std::size_t bytes_transferred = write(s, buffers, transfer_all(), ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +inline std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers, + CompletionCondition completion_condition) +{ + asio::error_code ec; + std::size_t bytes_transferred = write(s, buffers, completion_condition, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +std::size_t write(SyncWriteStream& s, + asio::basic_streambuf& b, + CompletionCondition completion_condition, asio::error_code& ec) +{ + std::size_t bytes_transferred = write(s, b.data(), completion_condition, ec); + b.consume(bytes_transferred); + return bytes_transferred; +} + +template +inline std::size_t write(SyncWriteStream& s, + asio::basic_streambuf& b) +{ + asio::error_code ec; + std::size_t bytes_transferred = write(s, b, transfer_all(), ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +template +inline std::size_t write(SyncWriteStream& s, + asio::basic_streambuf& b, + CompletionCondition completion_condition) +{ + asio::error_code ec; + std::size_t bytes_transferred = write(s, b, completion_condition, ec); + asio::detail::throw_error(ec); + return bytes_transferred; +} + +namespace detail +{ + template + class write_handler + { + public: + typedef asio::detail::consuming_buffers< + const_buffer, ConstBufferSequence> buffers_type; + + write_handler(AsyncWriteStream& stream, const buffers_type& buffers, + CompletionCondition completion_condition, WriteHandler handler) + : stream_(stream), + buffers_(buffers), + total_transferred_(0), + completion_condition_(completion_condition), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + total_transferred_ += bytes_transferred; + buffers_.consume(bytes_transferred); + if (completion_condition_(ec, total_transferred_) + || buffers_.begin() == buffers_.end()) + { + handler_(ec, total_transferred_); + } + else + { + stream_.async_write_some(buffers_, *this); + } + } + + //private: + AsyncWriteStream& stream_; + buffers_type buffers_; + std::size_t total_transferred_; + CompletionCondition completion_condition_; + WriteHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + write_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + write_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + write_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +inline void async_write(AsyncWriteStream& s, const ConstBufferSequence& buffers, + CompletionCondition completion_condition, WriteHandler handler) +{ + asio::detail::consuming_buffers< + const_buffer, ConstBufferSequence> tmp(buffers); + s.async_write_some(tmp, + detail::write_handler( + s, tmp, completion_condition, handler)); +} + +template +inline void async_write(AsyncWriteStream& s, const ConstBufferSequence& buffers, + WriteHandler handler) +{ + async_write(s, buffers, transfer_all(), handler); +} + +namespace detail +{ + template + class write_streambuf_handler + { + public: + write_streambuf_handler(asio::basic_streambuf& streambuf, + WriteHandler handler) + : streambuf_(streambuf), + handler_(handler) + { + } + + void operator()(const asio::error_code& ec, + std::size_t bytes_transferred) + { + streambuf_.consume(bytes_transferred); + handler_(ec, bytes_transferred); + } + + //private: + asio::basic_streambuf& streambuf_; + WriteHandler handler_; + }; + + template + inline void* asio_handler_allocate(std::size_t size, + write_streambuf_handler* this_handler) + { + return asio_handler_alloc_helpers::allocate( + size, &this_handler->handler_); + } + + template + inline void asio_handler_deallocate(void* pointer, std::size_t size, + write_streambuf_handler* this_handler) + { + asio_handler_alloc_helpers::deallocate( + pointer, size, &this_handler->handler_); + } + + template + inline void asio_handler_invoke(const Function& function, + write_streambuf_handler* this_handler) + { + asio_handler_invoke_helpers::invoke( + function, &this_handler->handler_); + } +} // namespace detail + +template +inline void async_write(AsyncWriteStream& s, + asio::basic_streambuf& b, + CompletionCondition completion_condition, WriteHandler handler) +{ + async_write(s, b.data(), completion_condition, + detail::write_streambuf_handler< + AsyncWriteStream, Allocator, WriteHandler>(b, handler)); +} + +template +inline void async_write(AsyncWriteStream& s, + asio::basic_streambuf& b, WriteHandler handler) +{ + async_write(s, b, transfer_all(), handler); +} + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_WRITE_IPP diff --git a/libtorrent/include/asio/io_service.hpp b/libtorrent/include/asio/io_service.hpp new file mode 100644 index 000000000..b694545db --- /dev/null +++ b/libtorrent/include/asio/io_service.hpp @@ -0,0 +1,502 @@ +// +// io_service.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IO_SERVICE_HPP +#define ASIO_IO_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error_code.hpp" +#include "asio/detail/epoll_reactor_fwd.hpp" +#include "asio/detail/kqueue_reactor_fwd.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/select_reactor_fwd.hpp" +#include "asio/detail/service_registry_fwd.hpp" +#include "asio/detail/signal_init.hpp" +#include "asio/detail/task_io_service_fwd.hpp" +#include "asio/detail/win_iocp_io_service_fwd.hpp" +#include "asio/detail/winsock_init.hpp" +#include "asio/detail/wrapped_handler.hpp" + +namespace asio { + +/// Provides core I/O functionality. +/** + * The io_service class provides the core I/O functionality for users of the + * asynchronous I/O objects, including: + * + * @li asio::ip::tcp::socket + * @li asio::ip::tcp::acceptor + * @li asio::ip::udp::socket + * @li asio::deadline_timer. + * + * The io_service class also includes facilities intended for developers of + * custom asynchronous services. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Safe, with the exception that calling reset() + * while there are unfinished run() calls results in undefined behaviour. + * + * @par Concepts: + * Dispatcher. + * + * @par Effect of exceptions thrown from handlers + * + * If an exception is thrown from a handler, the exception is allowed to + * propagate through the throwing thread's invocation of + * asio::io_service::run(), asio::io_service::run_one(), + * asio::io_service::poll() or asio::io_service::poll_one(). + * No other threads that are calling any of these functions are affected. It is + * then the responsibility of the application to catch the exception. + * + * After the exception has been caught, the + * asio::io_service::run(), asio::io_service::run_one(), + * asio::io_service::poll() or asio::io_service::poll_one() + * call may be restarted @em without the need for an intervening call to + * asio::io_service::reset(). This allows the thread to rejoin the + * io_service's thread pool without impacting any other threads in the pool. + * + * For example: + * + * @code + * asio::io_service io_service; + * ... + * for (;;) + * { + * try + * { + * io_service.run(); + * break; // run() exited normally + * } + * catch (my_exception& e) + * { + * // Deal with exception as appropriate. + * } + * } + * @endcode + */ +class io_service + : private noncopyable +{ +private: + // The type of the platform-specific implementation. +#if defined(ASIO_HAS_IOCP) + typedef detail::win_iocp_io_service impl_type; +#elif defined(ASIO_HAS_EPOLL) + typedef detail::task_io_service > impl_type; +#elif defined(ASIO_HAS_KQUEUE) + typedef detail::task_io_service > impl_type; +#else + typedef detail::task_io_service > impl_type; +#endif + +public: + class work; + friend class work; + + class id; + + class service; + + class strand; + + /// Constructor. + io_service(); + + /// Constructor. + /** + * Construct with a hint about the required level of concurrency. + * + * @param concurrency_hint A suggestion to the implementation on how many + * threads it should allow to run simultaneously. + */ + explicit io_service(std::size_t concurrency_hint); + + /// Destructor. + ~io_service(); + + /// Run the io_service's event processing loop. + /** + * The run() function blocks until all work has finished and there are no + * more handlers to be dispatched, or until the io_service has been stopped. + * + * Multiple threads may call the run() function to set up a pool of threads + * from which the io_service may execute handlers. All threads that are + * waiting in the pool are equivalent and the io_service may choose any one + * of them to invoke a handler. + * + * The run() function may be safely called again once it has completed only + * after a call to reset(). + * + * @return The number of handlers that were executed. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t run(); + + /// Run the io_service's event processing loop. + /** + * The run() function blocks until all work has finished and there are no + * more handlers to be dispatched, or until the io_service has been stopped. + * + * Multiple threads may call the run() function to set up a pool of threads + * from which the io_service may execute handlers. All threads that are + * waiting in the pool are equivalent and the io_service may choose any one + * of them to invoke a handler. + * + * The run() function may be safely called again once it has completed only + * after a call to reset(). + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of handlers that were executed. + */ + std::size_t run(asio::error_code& ec); + + /// Run the io_service's event processing loop to execute at most one handler. + /** + * The run_one() function blocks until one handler has been dispatched, or + * until the io_service has been stopped. + * + * @return The number of handlers that were executed. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t run_one(); + + /// Run the io_service's event processing loop to execute at most one handler. + /** + * The run_one() function blocks until one handler has been dispatched, or + * until the io_service has been stopped. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of handlers that were executed. + */ + std::size_t run_one(asio::error_code& ec); + + /// Run the io_service's event processing loop to execute ready handlers. + /** + * The poll() function runs handlers that are ready to run, without blocking, + * until the io_service has been stopped or there are no more ready handlers. + * + * @return The number of handlers that were executed. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t poll(); + + /// Run the io_service's event processing loop to execute ready handlers. + /** + * The poll() function runs handlers that are ready to run, without blocking, + * until the io_service has been stopped or there are no more ready handlers. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of handlers that were executed. + */ + std::size_t poll(asio::error_code& ec); + + /// Run the io_service's event processing loop to execute one ready handler. + /** + * The poll_one() function runs at most one handler that is ready to run, + * without blocking. + * + * @return The number of handlers that were executed. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t poll_one(); + + /// Run the io_service's event processing loop to execute one ready handler. + /** + * The poll_one() function runs at most one handler that is ready to run, + * without blocking. + * + * @param ec Set to indicate what error occurred, if any. + * + * @return The number of handlers that were executed. + */ + std::size_t poll_one(asio::error_code& ec); + + /// Stop the io_service's event processing loop. + /** + * This function does not block, but instead simply signals the io_service to + * stop. All invocations of its run() or run_one() member functions should + * return as soon as possible. Subsequent calls to run(), run_one(), poll() + * or poll_one() will return immediately until reset() is called. + */ + void stop(); + + /// Reset the io_service in preparation for a subsequent run() invocation. + /** + * This function must be called prior to any second or later set of + * invocations of the run(), run_one(), poll() or poll_one() functions when a + * previous invocation of these functions returned due to the io_service + * being stopped or running out of work. This function allows the io_service + * to reset any internal state, such as a "stopped" flag. + * + * This function must not be called while there are any unfinished calls to + * the run(), run_one(), poll() or poll_one() functions. + */ + void reset(); + + /// Request the io_service to invoke the given handler. + /** + * This function is used to ask the io_service to execute the given handler. + * + * The io_service guarantees that the handler will only be called in a thread + * in which the run(), run_one(), poll() or poll_one() member functions is + * currently being invoked. The handler may be executed inside this function + * if the guarantee can be met. + * + * @param handler The handler to be called. The io_service will make + * a copy of the handler object as required. The function signature of the + * handler must be: @code void handler(); @endcode + */ + template + void dispatch(CompletionHandler handler); + + /// Request the io_service to invoke the given handler and return immediately. + /** + * This function is used to ask the io_service to execute the given handler, + * but without allowing the io_service to call the handler from inside this + * function. + * + * The io_service guarantees that the handler will only be called in a thread + * in which the run(), run_one(), poll() or poll_one() member functions is + * currently being invoked. + * + * @param handler The handler to be called. The io_service will make + * a copy of the handler object as required. The function signature of the + * handler must be: @code void handler(); @endcode + */ + template + void post(CompletionHandler handler); + + /// Create a new handler that automatically dispatches the wrapped handler + /// on the io_service. + /** + * This function is used to create a new handler function object that, when + * invoked, will automatically pass the wrapped handler to the io_service's + * dispatch function. + * + * @param handler The handler to be wrapped. The io_service will make a copy + * of the handler object as required. The function signature of the handler + * must be: @code void handler(A1 a1, ... An an); @endcode + * + * @return A function object that, when invoked, passes the wrapped handler to + * the io_service's dispatch function. Given a function object with the + * signature: + * @code R f(A1 a1, ... An an); @endcode + * If this function object is passed to the wrap function like so: + * @code io_service.wrap(f); @endcode + * then the return value is a function object with the signature + * @code void g(A1 a1, ... An an); @endcode + * that, when invoked, executes code equivalent to: + * @code io_service.dispatch(boost::bind(f, a1, ... an)); @endcode + */ + template +#if defined(GENERATING_DOCUMENTATION) + unspecified +#else + detail::wrapped_handler +#endif + wrap(Handler handler); + + /// Obtain the service object corresponding to the given type. + /** + * This function is used to locate a service object that corresponds to + * the given service type. If there is no existing implementation of the + * service, then the io_service will create a new instance of the service. + * + * @param ios The io_service object that owns the service. + * + * @return The service interface implementing the specified service type. + * Ownership of the service interface is not transferred to the caller. + */ + template + friend Service& use_service(io_service& ios); + + /// Add a service object to the io_service. + /** + * This function is used to add a service to the io_service. + * + * @param ios The io_service object that owns the service. + * + * @param svc The service object. On success, ownership of the service object + * is transferred to the io_service. When the io_service object is destroyed, + * it will destroy the service object by performing: + * @code delete static_cast(svc) @endcode + * + * @throws asio::service_already_exists Thrown if a service of the + * given type is already present in the io_service. + * + * @throws asio::invalid_service_owner Thrown if the service's owning + * io_service is not the io_service object specified by the ios parameter. + */ + template + friend void add_service(io_service& ios, Service* svc); + + /// Determine if an io_service contains a specified service type. + /** + * This function is used to determine whether the io_service contains a + * service object corresponding to the given service type. + * + * @param ios The io_service object that owns the service. + * + * @return A boolean indicating whether the io_service contains the service. + */ + template + friend bool has_service(io_service& ios); + +private: +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + detail::winsock_init<> init_; +#elif defined(__sun) || defined(__QNX__) + detail::signal_init<> init_; +#endif + + // The service registry. + asio::detail::service_registry* service_registry_; + + // The implementation. + impl_type& impl_; +}; + +/// Class to inform the io_service when it has work to do. +/** + * The work class is used to inform the io_service when work starts and + * finishes. This ensures that the io_service's run() function will not exit + * while work is underway, and that it does exit when there is no unfinished + * work remaining. + * + * The work class is copy-constructible so that it may be used as a data member + * in a handler class. It is not assignable. + */ +class io_service::work +{ +public: + /// Constructor notifies the io_service that work is starting. + /** + * The constructor is used to inform the io_service that some work has begun. + * This ensures that the io_service's run() function will not exit while the + * work is underway. + */ + explicit work(asio::io_service& io_service); + + /// Copy constructor notifies the io_service that work is starting. + /** + * The constructor is used to inform the io_service that some work has begun. + * This ensures that the io_service's run() function will not exit while the + * work is underway. + */ + work(const work& other); + + /// Destructor notifies the io_service that the work is complete. + /** + * The destructor is used to inform the io_service that some work has + * finished. Once the count of unfinished work reaches zero, the io_service's + * run() function is permitted to exit. + */ + ~work(); + + /// Get the io_service associated with the work. + asio::io_service& io_service(); + +private: + // Prevent assignment. + void operator=(const work& other); + + // The io_service. + asio::io_service& io_service_; +}; + +/// Class used to uniquely identify a service. +class io_service::id + : private noncopyable +{ +public: + /// Constructor. + id() {} +}; + +/// Base class for all io_service services. +class io_service::service + : private noncopyable +{ +public: + /// Get the io_service object that owns the service. + asio::io_service& io_service(); + +protected: + /// Constructor. + /** + * @param owner The io_service object that owns the service. + */ + service(asio::io_service& owner); + + /// Destructor. + virtual ~service(); + +private: + /// Destroy all user-defined handler objects owned by the service. + virtual void shutdown_service() = 0; + + friend class asio::detail::service_registry; + asio::io_service& owner_; + const std::type_info* type_info_; + const asio::io_service::id* id_; + service* next_; +}; + +/// Exception thrown when trying to add a duplicate service to an io_service. +class service_already_exists + : public std::logic_error +{ +public: + service_already_exists() + : std::logic_error("Service already exists.") + { + } +}; + +/// Exception thrown when trying to add a service object to an io_service where +/// the service has a different owner. +class invalid_service_owner + : public std::logic_error +{ +public: + invalid_service_owner() + : std::logic_error("Invalid service owner.") + { + } +}; + +} // namespace asio + +#include "asio/impl/io_service.ipp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IO_SERVICE_HPP diff --git a/libtorrent/include/asio/ip/CVS/Entries b/libtorrent/include/asio/ip/CVS/Entries new file mode 100644 index 000000000..5da45ff93 --- /dev/null +++ b/libtorrent/include/asio/ip/CVS/Entries @@ -0,0 +1,17 @@ +/address.hpp/1.9/Thu Jan 4 05:44:46 2007// +/address_v4.hpp/1.11/Thu Jan 4 05:44:46 2007// +/address_v6.hpp/1.11/Mon Feb 19 01:46:31 2007// +/basic_endpoint.hpp/1.16/Wed Feb 14 13:26:26 2007// +/basic_resolver.hpp/1.5/Thu Jan 4 05:44:46 2007// +/basic_resolver_entry.hpp/1.5/Thu Jan 4 05:44:46 2007// +/basic_resolver_iterator.hpp/1.10/Sun Apr 8 23:47:05 2007// +/basic_resolver_query.hpp/1.12/Thu Jan 4 05:44:46 2007// +/host_name.hpp/1.5/Thu Jan 4 05:44:46 2007// +/multicast.hpp/1.8/Sat Feb 17 22:57:39 2007// +/resolver_query_base.hpp/1.3/Thu Jan 4 05:44:46 2007// +/resolver_service.hpp/1.7/Thu Jan 4 05:44:46 2007// +/tcp.hpp/1.14/Sun May 20 00:49:02 2007// +/udp.hpp/1.12/Sun May 20 00:49:02 2007// +/unicast.hpp/1.2/Sat Feb 17 22:57:39 2007// +/v6_only.hpp/1.2/Sun May 13 07:59:22 2007// +D/detail//// diff --git a/libtorrent/include/asio/ip/CVS/Repository b/libtorrent/include/asio/ip/CVS/Repository new file mode 100644 index 000000000..96de0dd58 --- /dev/null +++ b/libtorrent/include/asio/ip/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio/ip diff --git a/libtorrent/include/asio/ip/CVS/Root b/libtorrent/include/asio/ip/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/ip/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ip/address.hpp b/libtorrent/include/asio/ip/address.hpp new file mode 100644 index 000000000..1b2c548bf --- /dev/null +++ b/libtorrent/include/asio/ip/address.hpp @@ -0,0 +1,277 @@ +// +// address.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_ADDRESS_HPP +#define ASIO_IP_ADDRESS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/ip/address_v4.hpp" +#include "asio/ip/address_v6.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { +namespace ip { + +/// Implements version-independent IP addresses. +/** + * The asio::ip::address class provides the ability to use either IP + * version 4 or version 6 addresses. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +class address +{ +public: + /// Default constructor. + address() + : type_(ipv4), + ipv4_address_(), + ipv6_address_() + { + } + + /// Construct an address from an IPv4 address. + address(const asio::ip::address_v4& ipv4_address) + : type_(ipv4), + ipv4_address_(ipv4_address), + ipv6_address_() + { + } + + /// Construct an address from an IPv6 address. + address(const asio::ip::address_v6& ipv6_address) + : type_(ipv6), + ipv4_address_(), + ipv6_address_(ipv6_address) + { + } + + /// Copy constructor. + address(const address& other) + : type_(other.type_), + ipv4_address_(other.ipv4_address_), + ipv6_address_(other.ipv6_address_) + { + } + + /// Assign from another address. + address& operator=(const address& other) + { + type_ = other.type_; + ipv4_address_ = other.ipv4_address_; + ipv6_address_ = other.ipv6_address_; + return *this; + } + + /// Assign from an IPv4 address. + address& operator=(const asio::ip::address_v4& ipv4_address) + { + type_ = ipv4; + ipv4_address_ = ipv4_address; + ipv6_address_ = asio::ip::address_v6(); + return *this; + } + + /// Assign from an IPv6 address. + address& operator=(const asio::ip::address_v6& ipv6_address) + { + type_ = ipv6; + ipv4_address_ = asio::ip::address_v4(); + ipv6_address_ = ipv6_address; + return *this; + } + + /// Get whether the address is an IP version 4 address. + bool is_v4() const + { + return type_ == ipv4; + } + + /// Get whether the address is an IP version 6 address. + bool is_v6() const + { + return type_ == ipv6; + } + + /// Get the address as an IP version 4 address. + asio::ip::address_v4 to_v4() const + { + if (type_ != ipv4) + { + asio::system_error e( + asio::error::address_family_not_supported); + boost::throw_exception(e); + } + return ipv4_address_; + } + + /// Get the address as an IP version 6 address. + asio::ip::address_v6 to_v6() const + { + if (type_ != ipv6) + { + asio::system_error e( + asio::error::address_family_not_supported); + boost::throw_exception(e); + } + return ipv6_address_; + } + + /// Get the address as a string in dotted decimal format. + std::string to_string() const + { + if (type_ == ipv6) + return ipv6_address_.to_string(); + return ipv4_address_.to_string(); + } + + /// Get the address as a string in dotted decimal format. + std::string to_string(asio::error_code& ec) const + { + if (type_ == ipv6) + return ipv6_address_.to_string(ec); + return ipv4_address_.to_string(ec); + } + + /// Create an address from an IPv4 address string in dotted decimal form, + /// or from an IPv6 address in hexadecimal notation. + static address from_string(const char* str) + { + asio::error_code ec; + address addr = from_string(str, ec); + asio::detail::throw_error(ec); + return addr; + } + + /// Create an address from an IPv4 address string in dotted decimal form, + /// or from an IPv6 address in hexadecimal notation. + static address from_string(const char* str, asio::error_code& ec) + { + asio::ip::address_v6 ipv6_address = + asio::ip::address_v6::from_string(str, ec); + if (!ec) + { + address tmp; + tmp.type_ = ipv6; + tmp.ipv6_address_ = ipv6_address; + return tmp; + } + + asio::ip::address_v4 ipv4_address = + asio::ip::address_v4::from_string(str, ec); + if (!ec) + { + address tmp; + tmp.type_ = ipv4; + tmp.ipv4_address_ = ipv4_address; + return tmp; + } + + return address(); + } + + /// Create an address from an IPv4 address string in dotted decimal form, + /// or from an IPv6 address in hexadecimal notation. + static address from_string(const std::string& str) + { + return from_string(str.c_str()); + } + + /// Create an address from an IPv4 address string in dotted decimal form, + /// or from an IPv6 address in hexadecimal notation. + static address from_string(const std::string& str, + asio::error_code& ec) + { + return from_string(str.c_str(), ec); + } + + /// Compare two addresses for equality. + friend bool operator==(const address& a1, const address& a2) + { + if (a1.type_ != a2.type_) + return false; + if (a1.type_ == ipv6) + return a1.ipv6_address_ == a2.ipv6_address_; + return a1.ipv4_address_ == a2.ipv4_address_; + } + + /// Compare two addresses for inequality. + friend bool operator!=(const address& a1, const address& a2) + { + if (a1.type_ != a2.type_) + return true; + if (a1.type_ == ipv6) + return a1.ipv6_address_ != a2.ipv6_address_; + return a1.ipv4_address_ != a2.ipv4_address_; + } + + /// Compare addresses for ordering. + friend bool operator<(const address& a1, const address& a2) + { + if (a1.type_ < a2.type_) + return true; + if (a1.type_ > a2.type_) + return false; + if (a1.type_ == ipv6) + return a1.ipv6_address_ < a2.ipv6_address_; + return a1.ipv4_address_ < a2.ipv4_address_; + } + +private: + // The type of the address. + enum { ipv4, ipv6 } type_; + + // The underlying IPv4 address. + asio::ip::address_v4 ipv4_address_; + + // The underlying IPv6 address. + asio::ip::address_v6 ipv6_address_; +}; + +/// Output an address as a string. +/** + * Used to output a human-readable string for a specified address. + * + * @param os The output stream to which the string will be written. + * + * @param addr The address to be written. + * + * @return The output stream. + * + * @relates asio::ip::address + */ +template +std::basic_ostream& operator<<( + std::basic_ostream& os, const address& addr) +{ + os << addr.to_string(); + return os; +} + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_ADDRESS_HPP diff --git a/libtorrent/include/asio/ip/address_v4.hpp b/libtorrent/include/asio/ip/address_v4.hpp new file mode 100644 index 000000000..ae3891c95 --- /dev/null +++ b/libtorrent/include/asio/ip/address_v4.hpp @@ -0,0 +1,283 @@ +// +// address_v4.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_ADDRESS_V4_HPP +#define ASIO_IP_ADDRESS_V4_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { +namespace ip { + +/// Implements IP version 4 style addresses. +/** + * The asio::ip::address_v4 class provides the ability to use and + * manipulate IP version 4 addresses. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +class address_v4 +{ +public: + /// The type used to represent an address as an array of bytes. + typedef boost::array bytes_type; + + /// Default constructor. + address_v4() + { + addr_.s_addr = 0; + } + + /// Construct an address from raw bytes. + explicit address_v4(const bytes_type& bytes) + { + using namespace std; // For memcpy. + memcpy(&addr_.s_addr, bytes.elems, 4); + } + + /// Construct an address from a unsigned long in host byte order. + explicit address_v4(unsigned long addr) + { + addr_.s_addr = asio::detail::socket_ops::host_to_network_long(addr); + } + + /// Copy constructor. + address_v4(const address_v4& other) + : addr_(other.addr_) + { + } + + /// Assign from another address. + address_v4& operator=(const address_v4& other) + { + addr_ = other.addr_; + return *this; + } + + /// Get the address in bytes. + bytes_type to_bytes() const + { + using namespace std; // For memcpy. + bytes_type bytes; + memcpy(bytes.elems, &addr_.s_addr, 4); + return bytes; + } + + /// Get the address as an unsigned long in host byte order + unsigned long to_ulong() const + { + return asio::detail::socket_ops::network_to_host_long(addr_.s_addr); + } + + /// Get the address as a string in dotted decimal format. + std::string to_string() const + { + asio::error_code ec; + std::string addr = to_string(ec); + asio::detail::throw_error(ec); + return addr; + } + + /// Get the address as a string in dotted decimal format. + std::string to_string(asio::error_code& ec) const + { + char addr_str[asio::detail::max_addr_v4_str_len]; + const char* addr = + asio::detail::socket_ops::inet_ntop(AF_INET, &addr_, addr_str, + asio::detail::max_addr_v4_str_len, 0, ec); + if (addr == 0) + return std::string(); + return addr; + } + + /// Create an address from an IP address string in dotted decimal form. + static address_v4 from_string(const char* str) + { + asio::error_code ec; + address_v4 addr = from_string(str, ec); + asio::detail::throw_error(ec); + return addr; + } + + /// Create an address from an IP address string in dotted decimal form. + static address_v4 from_string(const char* str, asio::error_code& ec) + { + address_v4 tmp; + if (asio::detail::socket_ops::inet_pton( + AF_INET, str, &tmp.addr_, 0, ec) <= 0) + return address_v4(); + return tmp; + } + + /// Create an address from an IP address string in dotted decimal form. + static address_v4 from_string(const std::string& str) + { + return from_string(str.c_str()); + } + + /// Create an address from an IP address string in dotted decimal form. + static address_v4 from_string(const std::string& str, + asio::error_code& ec) + { + return from_string(str.c_str(), ec); + } + + /// Determine whether the address is a class A address. + bool is_class_a() const + { + return IN_CLASSA(to_ulong()); + } + + /// Determine whether the address is a class B address. + bool is_class_b() const + { + return IN_CLASSB(to_ulong()); + } + + /// Determine whether the address is a class C address. + bool is_class_c() const + { + return IN_CLASSC(to_ulong()); + } + + /// Determine whether the address is a multicast address. + bool is_multicast() const + { + return IN_MULTICAST(to_ulong()); + } + + /// Compare two addresses for equality. + friend bool operator==(const address_v4& a1, const address_v4& a2) + { + return a1.addr_.s_addr == a2.addr_.s_addr; + } + + /// Compare two addresses for inequality. + friend bool operator!=(const address_v4& a1, const address_v4& a2) + { + return a1.addr_.s_addr != a2.addr_.s_addr; + } + + /// Compare addresses for ordering. + friend bool operator<(const address_v4& a1, const address_v4& a2) + { + return a1.to_ulong() < a2.to_ulong(); + } + + /// Compare addresses for ordering. + friend bool operator>(const address_v4& a1, const address_v4& a2) + { + return a1.to_ulong() > a2.to_ulong(); + } + + /// Compare addresses for ordering. + friend bool operator<=(const address_v4& a1, const address_v4& a2) + { + return a1.to_ulong() <= a2.to_ulong(); + } + + /// Compare addresses for ordering. + friend bool operator>=(const address_v4& a1, const address_v4& a2) + { + return a1.to_ulong() >= a2.to_ulong(); + } + + /// Obtain an address object that represents any address. + static address_v4 any() + { + return address_v4(static_cast(INADDR_ANY)); + } + + /// Obtain an address object that represents the loopback address. + static address_v4 loopback() + { + return address_v4(static_cast(INADDR_LOOPBACK)); + } + + /// Obtain an address object that represents the broadcast address. + static address_v4 broadcast() + { + return address_v4(static_cast(INADDR_BROADCAST)); + } + + /// Obtain an address object that represents the broadcast address that + /// corresponds to the specified address and netmask. + static address_v4 broadcast(const address_v4& addr, const address_v4& mask) + { + return address_v4(addr.to_ulong() | ~mask.to_ulong()); + } + + /// Obtain the netmask that corresponds to the address, based on its address + /// class. + static address_v4 netmask(const address_v4& addr) + { + if (addr.is_class_a()) + return address_v4(0xFF000000); + if (addr.is_class_b()) + return address_v4(0xFFFF0000); + if (addr.is_class_c()) + return address_v4(0xFFFFFF00); + return address_v4(0xFFFFFFFF); + } + +private: + // The underlying IPv4 address. + asio::detail::in4_addr_type addr_; +}; + +/// Output an address as a string. +/** + * Used to output a human-readable string for a specified address. + * + * @param os The output stream to which the string will be written. + * + * @param addr The address to be written. + * + * @return The output stream. + * + * @relates asio::ip::address_v4 + */ +template +std::basic_ostream& operator<<( + std::basic_ostream& os, const address_v4& addr) +{ + asio::error_code ec; + std::string s = addr.to_string(ec); + if (ec) + os.setstate(std::ios_base::failbit); + else + for (std::string::iterator i = s.begin(); i != s.end(); ++i) + os << os.widen(*i); + return os; +} + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_ADDRESS_V4_HPP diff --git a/libtorrent/include/asio/ip/address_v6.hpp b/libtorrent/include/asio/ip/address_v6.hpp new file mode 100644 index 000000000..f732955fa --- /dev/null +++ b/libtorrent/include/asio/ip/address_v6.hpp @@ -0,0 +1,401 @@ +// +// address_v6.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_ADDRESS_V6_HPP +#define ASIO_IP_ADDRESS_V6_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/throw_error.hpp" +#include "asio/ip/address_v4.hpp" + +namespace asio { +namespace ip { + +/// Implements IP version 6 style addresses. +/** + * The asio::ip::address_v6 class provides the ability to use and + * manipulate IP version 6 addresses. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +class address_v6 +{ +public: + /// The type used to represent an address as an array of bytes. + typedef boost::array bytes_type; + + /// Default constructor. + address_v6() + : scope_id_(0) + { + asio::detail::in6_addr_type tmp_addr = IN6ADDR_ANY_INIT; + addr_ = tmp_addr; + } + + /// Construct an address from raw bytes and scope ID. + explicit address_v6(const bytes_type& bytes, unsigned long scope_id = 0) + : scope_id_(scope_id) + { + using namespace std; // For memcpy. + memcpy(addr_.s6_addr, bytes.elems, 16); + } + + /// Copy constructor. + address_v6(const address_v6& other) + : addr_(other.addr_), + scope_id_(other.scope_id_) + { + } + + /// Assign from another address. + address_v6& operator=(const address_v6& other) + { + addr_ = other.addr_; + scope_id_ = other.scope_id_; + return *this; + } + + /// The scope ID of the address. + /** + * Returns the scope ID associated with the IPv6 address. + */ + unsigned long scope_id() const + { + return scope_id_; + } + + /// The scope ID of the address. + /** + * Modifies the scope ID associated with the IPv6 address. + */ + void scope_id(unsigned long id) + { + scope_id_ = id; + } + + /// Get the address in bytes. + bytes_type to_bytes() const + { + using namespace std; // For memcpy. + bytes_type bytes; + memcpy(bytes.elems, addr_.s6_addr, 16); + return bytes; + } + + /// Get the address as a string. + std::string to_string() const + { + asio::error_code ec; + std::string addr = to_string(ec); + asio::detail::throw_error(ec); + return addr; + } + + /// Get the address as a string. + std::string to_string(asio::error_code& ec) const + { + char addr_str[asio::detail::max_addr_v6_str_len]; + const char* addr = + asio::detail::socket_ops::inet_ntop(AF_INET6, &addr_, addr_str, + asio::detail::max_addr_v6_str_len, scope_id_, ec); + if (addr == 0) + return std::string(); + return addr; + } + + /// Create an address from an IP address string. + static address_v6 from_string(const char* str) + { + asio::error_code ec; + address_v6 addr = from_string(str, ec); + asio::detail::throw_error(ec); + return addr; + } + + /// Create an address from an IP address string. + static address_v6 from_string(const char* str, asio::error_code& ec) + { + address_v6 tmp; + if (asio::detail::socket_ops::inet_pton( + AF_INET6, str, &tmp.addr_, &tmp.scope_id_, ec) <= 0) + return address_v6(); + return tmp; + } + + /// Create an address from an IP address string. + static address_v6 from_string(const std::string& str) + { + return from_string(str.c_str()); + } + + /// Create an address from an IP address string. + static address_v6 from_string(const std::string& str, + asio::error_code& ec) + { + return from_string(str.c_str(), ec); + } + + /// Converts an IPv4-mapped or IPv4-compatible address to an IPv4 address. + address_v4 to_v4() const + { + if (!is_v4_mapped() && !is_v4_compatible()) + throw std::bad_cast(); + address_v4::bytes_type v4_bytes = { { addr_.s6_addr[12], + addr_.s6_addr[13], addr_.s6_addr[14], addr_.s6_addr[15] } }; + return address_v4(v4_bytes); + } + + /// Determine whether the address is a loopback address. + bool is_loopback() const + { +#if defined(__BORLANDC__) + return ((addr_.s6_addr[0] == 0) && (addr_.s6_addr[1] == 0) + && (addr_.s6_addr[2] == 0) && (addr_.s6_addr[3] == 0) + && (addr_.s6_addr[4] == 0) && (addr_.s6_addr[5] == 0) + && (addr_.s6_addr[6] == 0) && (addr_.s6_addr[7] == 0) + && (addr_.s6_addr[8] == 0) && (addr_.s6_addr[9] == 0) + && (addr_.s6_addr[10] == 0) && (addr_.s6_addr[11] == 0) + && (addr_.s6_addr[12] == 0) && (addr_.s6_addr[13] == 0) + && (addr_.s6_addr[14] == 0) && (addr_.s6_addr[15] == 1)); +#else + using namespace asio::detail; + return IN6_IS_ADDR_LOOPBACK(&addr_) != 0; +#endif + } + + /// Determine whether the address is unspecified. + bool is_unspecified() const + { +#if defined(__BORLANDC__) + return ((addr_.s6_addr[0] == 0) && (addr_.s6_addr[1] == 0) + && (addr_.s6_addr[2] == 0) && (addr_.s6_addr[3] == 0) + && (addr_.s6_addr[4] == 0) && (addr_.s6_addr[5] == 0) + && (addr_.s6_addr[6] == 0) && (addr_.s6_addr[7] == 0) + && (addr_.s6_addr[8] == 0) && (addr_.s6_addr[9] == 0) + && (addr_.s6_addr[10] == 0) && (addr_.s6_addr[11] == 0) + && (addr_.s6_addr[12] == 0) && (addr_.s6_addr[13] == 0) + && (addr_.s6_addr[14] == 0) && (addr_.s6_addr[15] == 0)); +#else + using namespace asio::detail; + return IN6_IS_ADDR_UNSPECIFIED(&addr_) != 0; +#endif + } + + /// Determine whether the address is link local. + bool is_link_local() const + { + using namespace asio::detail; + return IN6_IS_ADDR_LINKLOCAL(&addr_) != 0; + } + + /// Determine whether the address is site local. + bool is_site_local() const + { + using namespace asio::detail; + return IN6_IS_ADDR_SITELOCAL(&addr_) != 0; + } + + /// Determine whether the address is a mapped IPv4 address. + bool is_v4_mapped() const + { + using namespace asio::detail; + return IN6_IS_ADDR_V4MAPPED(&addr_) != 0; + } + + /// Determine whether the address is an IPv4-compatible address. + bool is_v4_compatible() const + { + using namespace asio::detail; + return IN6_IS_ADDR_V4COMPAT(&addr_) != 0; + } + + /// Determine whether the address is a multicast address. + bool is_multicast() const + { + using namespace asio::detail; + return IN6_IS_ADDR_MULTICAST(&addr_) != 0; + } + + /// Determine whether the address is a global multicast address. + bool is_multicast_global() const + { + using namespace asio::detail; + return IN6_IS_ADDR_MC_GLOBAL(&addr_) != 0; + } + + /// Determine whether the address is a link-local multicast address. + bool is_multicast_link_local() const + { + using namespace asio::detail; + return IN6_IS_ADDR_MC_LINKLOCAL(&addr_) != 0; + } + + /// Determine whether the address is a node-local multicast address. + bool is_multicast_node_local() const + { + using namespace asio::detail; + return IN6_IS_ADDR_MC_NODELOCAL(&addr_) != 0; + } + + /// Determine whether the address is a org-local multicast address. + bool is_multicast_org_local() const + { + using namespace asio::detail; + return IN6_IS_ADDR_MC_ORGLOCAL(&addr_) != 0; + } + + /// Determine whether the address is a site-local multicast address. + bool is_multicast_site_local() const + { + using namespace asio::detail; + return IN6_IS_ADDR_MC_SITELOCAL(&addr_) != 0; + } + + /// Compare two addresses for equality. + friend bool operator==(const address_v6& a1, const address_v6& a2) + { + using namespace std; // For memcmp. + return memcmp(&a1.addr_, &a2.addr_, + sizeof(asio::detail::in6_addr_type)) == 0 + && a1.scope_id_ == a2.scope_id_; + } + + /// Compare two addresses for inequality. + friend bool operator!=(const address_v6& a1, const address_v6& a2) + { + using namespace std; // For memcmp. + return memcmp(&a1.addr_, &a2.addr_, + sizeof(asio::detail::in6_addr_type)) != 0 + || a1.scope_id_ != a2.scope_id_; + } + + /// Compare addresses for ordering. + friend bool operator<(const address_v6& a1, const address_v6& a2) + { + using namespace std; // For memcmp. + int memcmp_result = memcmp(&a1.addr_, &a2.addr_, + sizeof(asio::detail::in6_addr_type)) < 0; + if (memcmp_result < 0) + return true; + if (memcmp_result > 0) + return false; + return a1.scope_id_ < a2.scope_id_; + } + + /// Compare addresses for ordering. + friend bool operator>(const address_v6& a1, const address_v6& a2) + { + return a2 < a1; + } + + /// Compare addresses for ordering. + friend bool operator<=(const address_v6& a1, const address_v6& a2) + { + return !(a2 < a1); + } + + /// Compare addresses for ordering. + friend bool operator>=(const address_v6& a1, const address_v6& a2) + { + return !(a1 < a2); + } + + /// Obtain an address object that represents any address. + static address_v6 any() + { + return address_v6(); + } + + /// Obtain an address object that represents the loopback address. + static address_v6 loopback() + { + address_v6 tmp; + asio::detail::in6_addr_type tmp_addr = IN6ADDR_LOOPBACK_INIT; + tmp.addr_ = tmp_addr; + return tmp; + } + + /// Create an IPv4-mapped IPv6 address. + static address_v6 v4_mapped(const address_v4& addr) + { + address_v4::bytes_type v4_bytes = addr.to_bytes(); + bytes_type v6_bytes = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, + v4_bytes[0], v4_bytes[1], v4_bytes[2], v4_bytes[3] } }; + return address_v6(v6_bytes); + } + + /// Create an IPv4-compatible IPv6 address. + static address_v6 v4_compatible(const address_v4& addr) + { + address_v4::bytes_type v4_bytes = addr.to_bytes(); + bytes_type v6_bytes = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + v4_bytes[0], v4_bytes[1], v4_bytes[2], v4_bytes[3] } }; + return address_v6(v6_bytes); + } + +private: + // The underlying IPv6 address. + asio::detail::in6_addr_type addr_; + + // The scope ID associated with the address. + unsigned long scope_id_; +}; + +/// Output an address as a string. +/** + * Used to output a human-readable string for a specified address. + * + * @param os The output stream to which the string will be written. + * + * @param addr The address to be written. + * + * @return The output stream. + * + * @relates asio::ip::address_v6 + */ +template +std::basic_ostream& operator<<( + std::basic_ostream& os, const address_v6& addr) +{ + asio::error_code ec; + std::string s = addr.to_string(ec); + if (ec) + os.setstate(std::ios_base::failbit); + else + for (std::string::iterator i = s.begin(); i != s.end(); ++i) + os << os.widen(*i); + return os; +} + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_ADDRESS_V6_HPP diff --git a/libtorrent/include/asio/ip/basic_endpoint.hpp b/libtorrent/include/asio/ip/basic_endpoint.hpp new file mode 100644 index 000000000..3ca91dc03 --- /dev/null +++ b/libtorrent/include/asio/ip/basic_endpoint.hpp @@ -0,0 +1,368 @@ +// +// basic_endpoint.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_BASIC_ENDPOINT_HPP +#define ASIO_IP_BASIC_ENDPOINT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +# include +#endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/ip/address.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace ip { + +/// Describes an endpoint for a version-independent IP socket. +/** + * The asio::ip::basic_endpoint class template describes an endpoint that + * may be associated with a particular socket. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Concepts: + * Endpoint. + */ +template +class basic_endpoint +{ +public: + /// The protocol type associated with the endpoint. + typedef InternetProtocol protocol_type; + + /// The type of the endpoint structure. This type is dependent on the + /// underlying implementation of the socket layer. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined data_type; +#else + typedef asio::detail::socket_addr_type data_type; +#endif + + /// The type for the size of the endpoint structure. This type is dependent on + /// the underlying implementation of the socket layer. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined size_type; +#else + typedef asio::detail::socket_addr_len_type size_type; +#endif + + /// Default constructor. + basic_endpoint() + : data_() + { + asio::detail::sockaddr_in4_type& data + = reinterpret_cast(data_); + data.sin_family = AF_INET; + data.sin_port = 0; + data.sin_addr.s_addr = INADDR_ANY; + } + + /// Construct an endpoint using a port number, specified in the host's byte + /// order. The IP address will be the any address (i.e. INADDR_ANY or + /// in6addr_any). This constructor would typically be used for accepting new + /// connections. + /** + * @par Examples + * To initialise an IPv4 TCP endpoint for port 1234, use: + * @code + * asio::ip::tcp::endpoint ep(asio::ip::tcp::v4(), 1234); + * @endcode + * + * To specify an IPv6 UDP endpoint for port 9876, use: + * @code + * asio::ip::udp::endpoint ep(asio::ip::udp::v6(), 9876); + * @endcode + */ + basic_endpoint(const InternetProtocol& protocol, unsigned short port_num) + : data_() + { + using namespace std; // For memcpy. + if (protocol.family() == PF_INET) + { + asio::detail::sockaddr_in4_type& data + = reinterpret_cast(data_); + data.sin_family = AF_INET; + data.sin_port = + asio::detail::socket_ops::host_to_network_short(port_num); + data.sin_addr.s_addr = INADDR_ANY; + } + else + { + asio::detail::sockaddr_in6_type& data + = reinterpret_cast(data_); + data.sin6_family = AF_INET6; + data.sin6_port = + asio::detail::socket_ops::host_to_network_short(port_num); + data.sin6_flowinfo = 0; + asio::detail::in6_addr_type tmp_addr = IN6ADDR_ANY_INIT; + data.sin6_addr = tmp_addr; + data.sin6_scope_id = 0; + } + } + + /// Construct an endpoint using a port number and an IP address. This + /// constructor may be used for accepting connections on a specific interface + /// or for making a connection to a remote endpoint. + basic_endpoint(const asio::ip::address& addr, unsigned short port_num) + : data_() + { + using namespace std; // For memcpy. + if (addr.is_v4()) + { + asio::detail::sockaddr_in4_type& data + = reinterpret_cast(data_); + data.sin_family = AF_INET; + data.sin_port = + asio::detail::socket_ops::host_to_network_short(port_num); + data.sin_addr.s_addr = + asio::detail::socket_ops::host_to_network_long( + addr.to_v4().to_ulong()); + } + else + { + asio::detail::sockaddr_in6_type& data + = reinterpret_cast(data_); + data.sin6_family = AF_INET6; + data.sin6_port = + asio::detail::socket_ops::host_to_network_short(port_num); + data.sin6_flowinfo = 0; + asio::ip::address_v6 v6_addr = addr.to_v6(); + asio::ip::address_v6::bytes_type bytes = v6_addr.to_bytes(); + memcpy(data.sin6_addr.s6_addr, bytes.elems, 16); + data.sin6_scope_id = v6_addr.scope_id(); + } + } + + /// Copy constructor. + basic_endpoint(const basic_endpoint& other) + : data_(other.data_) + { + } + + /// Assign from another endpoint. + basic_endpoint& operator=(const basic_endpoint& other) + { + data_ = other.data_; + return *this; + } + + /// The protocol associated with the endpoint. + protocol_type protocol() const + { + if (is_v4()) + return InternetProtocol::v4(); + return InternetProtocol::v6(); + } + + /// Get the underlying endpoint in the native type. + data_type* data() + { + return reinterpret_cast(&data_); + } + + /// Get the underlying endpoint in the native type. + const data_type* data() const + { + return reinterpret_cast(&data_); + } + + /// Get the underlying size of the endpoint in the native type. + size_type size() const + { + if (is_v4()) + return sizeof(asio::detail::sockaddr_in4_type); + else + return sizeof(asio::detail::sockaddr_in6_type); + } + + /// Set the underlying size of the endpoint in the native type. + void resize(size_type size) + { + if (size > size_type(sizeof(data_))) + { + asio::system_error e(asio::error::invalid_argument); + boost::throw_exception(e); + } + } + + /// Get the capacity of the endpoint in the native type. + size_type capacity() const + { + return sizeof(data_); + } + + /// Get the port associated with the endpoint. The port number is always in + /// the host's byte order. + unsigned short port() const + { + if (is_v4()) + { + return asio::detail::socket_ops::network_to_host_short( + reinterpret_cast( + data_).sin_port); + } + else + { + return asio::detail::socket_ops::network_to_host_short( + reinterpret_cast( + data_).sin6_port); + } + } + + /// Set the port associated with the endpoint. The port number is always in + /// the host's byte order. + void port(unsigned short port_num) + { + if (is_v4()) + { + reinterpret_cast(data_).sin_port + = asio::detail::socket_ops::host_to_network_short(port_num); + } + else + { + reinterpret_cast(data_).sin6_port + = asio::detail::socket_ops::host_to_network_short(port_num); + } + } + + /// Get the IP address associated with the endpoint. + asio::ip::address address() const + { + using namespace std; // For memcpy. + if (is_v4()) + { + const asio::detail::sockaddr_in4_type& data + = reinterpret_cast( + data_); + return asio::ip::address_v4( + asio::detail::socket_ops::network_to_host_long( + data.sin_addr.s_addr)); + } + else + { + const asio::detail::sockaddr_in6_type& data + = reinterpret_cast( + data_); + asio::ip::address_v6::bytes_type bytes; + memcpy(bytes.elems, data.sin6_addr.s6_addr, 16); + return asio::ip::address_v6(bytes, data.sin6_scope_id); + } + } + + /// Set the IP address associated with the endpoint. + void address(const asio::ip::address& addr) + { + basic_endpoint tmp_endpoint(addr, port()); + data_ = tmp_endpoint.data_; + } + + /// Compare two endpoints for equality. + friend bool operator==(const basic_endpoint& e1, + const basic_endpoint& e2) + { + return e1.address() == e2.address() && e1.port() == e2.port(); + } + + /// Compare two endpoints for inequality. + friend bool operator!=(const basic_endpoint& e1, + const basic_endpoint& e2) + { + return e1.address() != e2.address() || e1.port() != e2.port(); + } + + /// Compare endpoints for ordering. + friend bool operator<(const basic_endpoint& e1, + const basic_endpoint& e2) + { + if (e1.address() < e2.address()) + return true; + if (e1.address() != e2.address()) + return false; + return e1.port() < e2.port(); + } + +private: + // Helper function to determine whether the endpoint is IPv4. + bool is_v4() const + { +#if defined(_AIX) + return data_.__ss_family == AF_INET; +#else + return data_.ss_family == AF_INET; +#endif + } + + // The underlying IP socket address. + asio::detail::sockaddr_storage_type data_; +}; + +/// Output an endpoint as a string. +/** + * Used to output a human-readable string for a specified endpoint. + * + * @param os The output stream to which the string will be written. + * + * @param endpoint The endpoint to be written. + * + * @return The output stream. + * + * @relates asio::ip::basic_endpoint + */ +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +template +std::ostream& operator<<(std::ostream& os, + const basic_endpoint& endpoint) +{ + const address& addr = endpoint.address(); + if (addr.is_v4()) + os << addr.to_string(); + else + os << '[' << addr.to_string() << ']'; + os << ':' << endpoint.port(); + return os; +} +#else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +template +std::basic_ostream& operator<<( + std::basic_ostream& os, + const basic_endpoint& endpoint) +{ + const address& addr = endpoint.address(); + if (addr.is_v4()) + os << addr.to_string(); + else + os << '[' << addr.to_string() << ']'; + os << ':' << endpoint.port(); + return os; +} +#endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_BASIC_ENDPOINT_HPP diff --git a/libtorrent/include/asio/ip/basic_resolver.hpp b/libtorrent/include/asio/ip/basic_resolver.hpp new file mode 100644 index 000000000..8c9d25486 --- /dev/null +++ b/libtorrent/include/asio/ip/basic_resolver.hpp @@ -0,0 +1,245 @@ +// +// basic_resolver.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_BASIC_RESOLVER_HPP +#define ASIO_IP_BASIC_RESOLVER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_io_object.hpp" +#include "asio/error.hpp" +#include "asio/ip/resolver_service.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { +namespace ip { + +/// Provides endpoint resolution functionality. +/** + * The basic_resolver class template provides the ability to resolve a query + * to a list of endpoints. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +template > +class basic_resolver + : public basic_io_object +{ +public: + /// The protocol type. + typedef InternetProtocol protocol_type; + + /// The endpoint type. + typedef typename InternetProtocol::endpoint endpoint_type; + + /// The query type. + typedef typename InternetProtocol::resolver_query query; + + /// The iterator type. + typedef typename InternetProtocol::resolver_iterator iterator; + + /// Constructor. + /** + * This constructor creates a basic_resolver. + * + * @param io_service The io_service object that the resolver will use to + * dispatch handlers for any asynchronous operations performed on the timer. + */ + explicit basic_resolver(asio::io_service& io_service) + : basic_io_object(io_service) + { + } + + /// Cancel any asynchronous operations that are waiting on the resolver. + /** + * This function forces the completion of any pending asynchronous + * operations on the host resolver. The handler for each cancelled operation + * will be invoked with the asio::error::operation_aborted error code. + */ + void cancel() + { + return this->service.cancel(this->implementation); + } + + /// Resolve a query to a list of entries. + /** + * This function is used to resolve a query into a list of endpoint entries. + * + * @param q A query object that determines what endpoints will be returned. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. + * + * @throws asio::system_error Thrown on failure. + * + * @note A default constructed iterator represents the end of the list. + * + * A successful call to this function is guaranteed to return at least one + * entry. + */ + iterator resolve(const query& q) + { + asio::error_code ec; + iterator i = this->service.resolve(this->implementation, q, ec); + asio::detail::throw_error(ec); + return i; + } + + /// Resolve a query to a list of entries. + /** + * This function is used to resolve a query into a list of endpoint entries. + * + * @param q A query object that determines what endpoints will be returned. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. Returns a default constructed iterator if an error + * occurs. + * + * @note A default constructed iterator represents the end of the list. + * + * A successful call to this function is guaranteed to return at least one + * entry. + */ + iterator resolve(const query& q, asio::error_code& ec) + { + return this->service.resolve(this->implementation, q, ec); + } + + /// Asynchronously resolve a query to a list of entries. + /** + * This function is used to asynchronously resolve a query into a list of + * endpoint entries. + * + * @param q A query object that determines what endpoints will be returned. + * + * @param handler The handler to be called when the resolve operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * resolver::iterator iterator // Forward-only iterator that can + * // be used to traverse the list + * // of endpoint entries. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note A default constructed iterator represents the end of the list. + * + * A successful resolve operation is guaranteed to pass at least one entry to + * the handler. + */ + template + void async_resolve(const query& q, ResolveHandler handler) + { + return this->service.async_resolve(this->implementation, q, handler); + } + + /// Resolve an endpoint to a list of entries. + /** + * This function is used to resolve an endpoint into a list of endpoint + * entries. + * + * @param e An endpoint object that determines what endpoints will be + * returned. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. + * + * @throws asio::system_error Thrown on failure. + * + * @note A default constructed iterator represents the end of the list. + * + * A successful call to this function is guaranteed to return at least one + * entry. + */ + iterator resolve(const endpoint_type& e) + { + asio::error_code ec; + iterator i = this->service.resolve(this->implementation, e, ec); + asio::detail::throw_error(ec); + return i; + } + + /// Resolve an endpoint to a list of entries. + /** + * This function is used to resolve an endpoint into a list of endpoint + * entries. + * + * @param e An endpoint object that determines what endpoints will be + * returned. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. Returns a default constructed iterator if an error + * occurs. + * + * @note A default constructed iterator represents the end of the list. + * + * A successful call to this function is guaranteed to return at least one + * entry. + */ + iterator resolve(const endpoint_type& e, asio::error_code& ec) + { + return this->service.resolve(this->implementation, e, ec); + } + + /// Asynchronously resolve an endpoint to a list of entries. + /** + * This function is used to asynchronously resolve an endpoint into a list of + * endpoint entries. + * + * @param e An endpoint object that determines what endpoints will be + * returned. + * + * @param handler The handler to be called when the resolve operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * resolver::iterator iterator // Forward-only iterator that can + * // be used to traverse the list + * // of endpoint entries. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note A default constructed iterator represents the end of the list. + * + * A successful resolve operation is guaranteed to pass at least one entry to + * the handler. + */ + template + void async_resolve(const endpoint_type& e, ResolveHandler handler) + { + return this->service.async_resolve(this->implementation, e, handler); + } +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_BASIC_RESOLVER_HPP diff --git a/libtorrent/include/asio/ip/basic_resolver_entry.hpp b/libtorrent/include/asio/ip/basic_resolver_entry.hpp new file mode 100644 index 000000000..03184d8f5 --- /dev/null +++ b/libtorrent/include/asio/ip/basic_resolver_entry.hpp @@ -0,0 +1,95 @@ +// +// basic_resolver_entry.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_BASIC_RESOLVER_ENTRY_HPP +#define ASIO_IP_BASIC_RESOLVER_ENTRY_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace ip { + +/// An entry produced by a resolver. +/** + * The asio::ip::basic_resolver_entry class template describes an entry + * as returned by a resolver. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +template +class basic_resolver_entry +{ +public: + /// The protocol type associated with the endpoint entry. + typedef InternetProtocol protocol_type; + + /// The endpoint type associated with the endpoint entry. + typedef typename InternetProtocol::endpoint endpoint_type; + + /// Default constructor. + basic_resolver_entry() + { + } + + /// Construct with specified endpoint, host name and service name. + basic_resolver_entry(const endpoint_type& endpoint, + const std::string& host_name, const std::string& service_name) + : endpoint_(endpoint), + host_name_(host_name), + service_name_(service_name) + { + } + + /// Get the endpoint associated with the entry. + endpoint_type endpoint() const + { + return endpoint_; + } + + /// Convert to the endpoint associated with the entry. + operator endpoint_type() const + { + return endpoint_; + } + + /// Get the host name associated with the entry. + std::string host_name() const + { + return host_name_; + } + + /// Get the service name associated with the entry. + std::string service_name() const + { + return service_name_; + } + +private: + endpoint_type endpoint_; + std::string host_name_; + std::string service_name_; +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_BASIC_RESOLVER_ENTRY_HPP diff --git a/libtorrent/include/asio/ip/basic_resolver_iterator.hpp b/libtorrent/include/asio/ip/basic_resolver_iterator.hpp new file mode 100644 index 000000000..686e4446e --- /dev/null +++ b/libtorrent/include/asio/ip/basic_resolver_iterator.hpp @@ -0,0 +1,156 @@ +// +// basic_resolver_iterator.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_BASIC_RESOLVER_ITERATOR_HPP +#define ASIO_IP_BASIC_RESOLVER_ITERATOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/ip/basic_resolver_entry.hpp" + +namespace asio { +namespace ip { + +/// An iterator over the entries produced by a resolver. +/** + * The asio::ip::basic_resolver_iterator class template is used to define + * iterators over the results returned by a resolver. + * + * The iterator's value_type, obtained when the iterator is dereferenced, is: + * @code const basic_resolver_entry @endcode + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +template +class basic_resolver_iterator + : public boost::iterator_facade< + basic_resolver_iterator, + const basic_resolver_entry, + boost::forward_traversal_tag> +{ +public: + /// Default constructor creates an end iterator. + basic_resolver_iterator() + { + } + + /// Create an iterator from an addrinfo list returned by getaddrinfo. + static basic_resolver_iterator create( + asio::detail::addrinfo_type* address_info, + const std::string& host_name, const std::string& service_name) + { + basic_resolver_iterator iter; + if (!address_info) + return iter; + + std::string actual_host_name = host_name; + if (address_info->ai_canonname) + actual_host_name = address_info->ai_canonname; + + iter.values_.reset(new values_type); + + while (address_info) + { + if (address_info->ai_family == PF_INET + || address_info->ai_family == PF_INET6) + { + using namespace std; // For memcpy. + typename InternetProtocol::endpoint endpoint; + endpoint.resize( + static_cast( + address_info->ai_addrlen)); + memcpy(endpoint.data(), address_info->ai_addr, + address_info->ai_addrlen); + iter.values_->push_back( + basic_resolver_entry(endpoint, + actual_host_name, service_name)); + } + address_info = address_info->ai_next; + } + + if (iter.values_->size()) + iter.iter_ = iter.values_->begin(); + else + iter.values_.reset(); + + return iter; + } + + /// Create an iterator from an endpoint, host name and service name. + static basic_resolver_iterator create( + const typename InternetProtocol::endpoint& endpoint, + const std::string& host_name, const std::string& service_name) + { + basic_resolver_iterator iter; + iter.values_.reset(new values_type); + iter.values_->push_back( + basic_resolver_entry( + endpoint, host_name, service_name)); + iter.iter_ = iter.values_->begin(); + return iter; + } + +private: + friend class boost::iterator_core_access; + + void increment() + { + if (++*iter_ == values_->end()) + { + // Reset state to match a default constructed end iterator. + values_.reset(); + typedef typename values_type::const_iterator values_iterator_type; + iter_.reset(); + } + } + + bool equal(const basic_resolver_iterator& other) const + { + if (!values_ && !other.values_) + return true; + if (values_ != other.values_) + return false; + return *iter_ == *other.iter_; + } + + const basic_resolver_entry& dereference() const + { + return **iter_; + } + + typedef std::vector > values_type; + typedef typename values_type::const_iterator values_iter_type; + boost::shared_ptr values_; + boost::optional iter_; +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_BASIC_RESOLVER_ITERATOR_HPP diff --git a/libtorrent/include/asio/ip/basic_resolver_query.hpp b/libtorrent/include/asio/ip/basic_resolver_query.hpp new file mode 100644 index 000000000..0fdd003fe --- /dev/null +++ b/libtorrent/include/asio/ip/basic_resolver_query.hpp @@ -0,0 +1,149 @@ +// +// basic_resolver_query.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_BASIC_RESOLVER_QUERY_HPP +#define ASIO_IP_BASIC_RESOLVER_QUERY_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/socket_ops.hpp" +#include "asio/ip/resolver_query_base.hpp" + +namespace asio { +namespace ip { + +/// An query to be passed to a resolver. +/** + * The asio::ip::basic_resolver_query class template describes a query + * that can be passed to a resolver. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + */ +template +class basic_resolver_query + : public resolver_query_base +{ +public: + /// The protocol type associated with the endpoint query. + typedef InternetProtocol protocol_type; + + /// Construct with specified service name for any protocol. + basic_resolver_query(const std::string& service_name, + int flags = passive | address_configured) + : hints_(), + host_name_(), + service_name_(service_name) + { + typename InternetProtocol::endpoint endpoint; + hints_.ai_flags = flags; + hints_.ai_family = PF_UNSPEC; + hints_.ai_socktype = endpoint.protocol().type(); + hints_.ai_protocol = endpoint.protocol().protocol(); + hints_.ai_addrlen = 0; + hints_.ai_canonname = 0; + hints_.ai_addr = 0; + hints_.ai_next = 0; + } + + /// Construct with specified service name for a given protocol. + basic_resolver_query(const protocol_type& protocol, + const std::string& service_name, + int flags = passive | address_configured) + : hints_(), + host_name_(), + service_name_(service_name) + { + hints_.ai_flags = flags; + hints_.ai_family = protocol.family(); + hints_.ai_socktype = protocol.type(); + hints_.ai_protocol = protocol.protocol(); + hints_.ai_addrlen = 0; + hints_.ai_canonname = 0; + hints_.ai_addr = 0; + hints_.ai_next = 0; + } + + /// Construct with specified host name and service name for any protocol. + basic_resolver_query(const std::string& host_name, + const std::string& service_name, int flags = address_configured) + : hints_(), + host_name_(host_name), + service_name_(service_name) + { + typename InternetProtocol::endpoint endpoint; + hints_.ai_flags = flags; + hints_.ai_family = PF_UNSPEC; + hints_.ai_socktype = endpoint.protocol().type(); + hints_.ai_protocol = endpoint.protocol().protocol(); + hints_.ai_addrlen = 0; + hints_.ai_canonname = 0; + hints_.ai_addr = 0; + hints_.ai_next = 0; + } + + /// Construct with specified host name and service name for a given protocol. + basic_resolver_query(const protocol_type& protocol, + const std::string& host_name, const std::string& service_name, + int flags = address_configured) + : hints_(), + host_name_(host_name), + service_name_(service_name) + { + hints_.ai_flags = flags; + hints_.ai_family = protocol.family(); + hints_.ai_socktype = protocol.type(); + hints_.ai_protocol = protocol.protocol(); + hints_.ai_addrlen = 0; + hints_.ai_canonname = 0; + hints_.ai_addr = 0; + hints_.ai_next = 0; + } + + /// Get the hints associated with the query. + const asio::detail::addrinfo_type& hints() const + { + return hints_; + } + + /// Get the host name associated with the query. + std::string host_name() const + { + return host_name_; + } + + /// Get the service name associated with the query. + std::string service_name() const + { + return service_name_; + } + +private: + asio::detail::addrinfo_type hints_; + std::string host_name_; + std::string service_name_; +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_BASIC_RESOLVER_QUERY_HPP diff --git a/libtorrent/include/asio/ip/detail/CVS/Entries b/libtorrent/include/asio/ip/detail/CVS/Entries new file mode 100644 index 000000000..adc3dfb58 --- /dev/null +++ b/libtorrent/include/asio/ip/detail/CVS/Entries @@ -0,0 +1,2 @@ +/socket_option.hpp/1.9/Sun May 6 10:51:53 2007// +D diff --git a/libtorrent/include/asio/ip/detail/CVS/Repository b/libtorrent/include/asio/ip/detail/CVS/Repository new file mode 100644 index 000000000..b37c59644 --- /dev/null +++ b/libtorrent/include/asio/ip/detail/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio/ip/detail diff --git a/libtorrent/include/asio/ip/detail/CVS/Root b/libtorrent/include/asio/ip/detail/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/ip/detail/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ip/detail/socket_option.hpp b/libtorrent/include/asio/ip/detail/socket_option.hpp new file mode 100644 index 000000000..a86307077 --- /dev/null +++ b/libtorrent/include/asio/ip/detail/socket_option.hpp @@ -0,0 +1,536 @@ +// +// socket_option.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_DETAIL_SOCKET_OPTION_HPP +#define ASIO_IP_DETAIL_SOCKET_OPTION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/ip/address.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace ip { +namespace detail { +namespace socket_option { + +// Helper template for implementing boolean-based options. +template +class boolean +{ +public: +#if defined(__sun) + typedef unsigned char value_type; +#else + typedef int value_type; +#endif + + // Default constructor. + boolean() + : value_(0) + { + } + + // Construct with a specific option value. + explicit boolean(bool v) + : value_(v ? 1 : 0) + { + } + + // Set the value of the boolean. + boolean& operator=(bool v) + { + value_ = v ? 1 : 0; + return *this; + } + + // Get the current value of the boolean. + bool value() const + { + return !!value_; + } + + // Convert to bool. + operator bool() const + { + return !!value_; + } + + // Test for false. + bool operator!() const + { + return !value_; + } + + // Get the level of the socket option. + template + int level(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Level; + return IPv4_Level; + } + + // Get the name of the socket option. + template + int name(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Name; + return IPv4_Name; + } + + // Get the address of the boolean data. + template + value_type* data(const Protocol&) + { + return &value_; + } + + // Get the address of the boolean data. + template + const value_type* data(const Protocol&) const + { + return &value_; + } + + // Get the size of the boolean data. + template + std::size_t size(const Protocol&) const + { + return sizeof(value_); + } + + // Set the size of the boolean data. + template + void resize(const Protocol&, std::size_t s) + { + if (s != sizeof(value_)) + throw std::length_error("boolean socket option resize"); + } + +private: + value_type value_; +}; + +// Helper template for implementing unicast hops options. +template +class unicast_hops +{ +public: + // Default constructor. + unicast_hops() + : value_(0) + { + } + + // Construct with a specific option value. + explicit unicast_hops(int v) + : value_(v) + { + } + + // Set the value of the option. + unicast_hops& operator=(int v) + { + value_ = v; + return *this; + } + + // Get the current value of the option. + int value() const + { + return value_; + } + + // Get the level of the socket option. + template + int level(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Level; + return IPv4_Level; + } + + // Get the name of the socket option. + template + int name(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Name; + return IPv4_Name; + } + + // Get the address of the data. + template + int* data(const Protocol&) + { + return &value_; + } + + // Get the address of the data. + template + const int* data(const Protocol&) const + { + return &value_; + } + + // Get the size of the data. + template + std::size_t size(const Protocol&) const + { + return sizeof(value_); + } + + // Set the size of the data. + template + void resize(const Protocol&, std::size_t s) + { + if (s != sizeof(value_)) + throw std::length_error("unicast hops socket option resize"); + } + +private: + int value_; +}; + +// Helper template for implementing multicast hops options. +template +class multicast_hops +{ +public: + // Default constructor. + multicast_hops() + : ipv4_value_(0), + ipv6_value_(0) + { + } + + // Construct with a specific option value. + explicit multicast_hops(int v) + { + if (v < 0 || v > 255) + throw std::out_of_range("multicast hops value out of range"); + ipv4_value_ = static_cast(v); + ipv6_value_ = v; + } + + // Set the value of the option. + multicast_hops& operator=(int v) + { + if (v < 0 || v > 255) + throw std::out_of_range("multicast hops value out of range"); + ipv4_value_ = static_cast(v); + ipv6_value_ = v; + return *this; + } + + // Get the current value of the option. + int value() const + { + return ipv6_value_; + } + + // Get the level of the socket option. + template + int level(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Level; + return IPv4_Level; + } + + // Get the name of the socket option. + template + int name(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Name; + return IPv4_Name; + } + + // Get the address of the data. + template + void* data(const Protocol& protocol) + { + if (protocol.family() == PF_INET6) + return &ipv6_value_; + return &ipv4_value_; + } + + // Get the address of the data. + template + const void* data(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return &ipv6_value_; + return &ipv4_value_; + } + + // Get the size of the data. + template + std::size_t size(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return sizeof(ipv6_value_); + return sizeof(ipv4_value_); + } + + // Set the size of the data. + template + void resize(const Protocol& protocol, std::size_t s) + { + if (protocol.family() == PF_INET6) + { + if (s != sizeof(ipv6_value_)) + throw std::length_error("multicast hops socket option resize"); + if (ipv6_value_ < 0) + ipv4_value_ = 0; + else if (ipv6_value_ > 255) + ipv4_value_ = 255; + else + ipv4_value_ = static_cast(ipv6_value_); + } + else + { + if (s != sizeof(ipv4_value_)) + throw std::length_error("multicast hops socket option resize"); + ipv6_value_ = ipv4_value_; + } + } + +private: + unsigned char ipv4_value_; + int ipv6_value_; +}; + +// Helper template for implementing ip_mreq-based options. +template +class multicast_request +{ +public: + // Default constructor. + multicast_request() + { + ipv4_value_.imr_multiaddr.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + ipv4_value_.imr_interface.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + + asio::detail::in6_addr_type tmp_addr = IN6ADDR_ANY_INIT; + ipv6_value_.ipv6mr_multiaddr = tmp_addr; + ipv6_value_.ipv6mr_interface = 0; + } + + // Construct with multicast address only. + explicit multicast_request(const asio::ip::address& multicast_address) + { + if (multicast_address.is_v6()) + { + ipv4_value_.imr_multiaddr.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + ipv4_value_.imr_interface.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + + using namespace std; // For memcpy. + asio::ip::address_v6 ipv6_address = multicast_address.to_v6(); + asio::ip::address_v6::bytes_type bytes = ipv6_address.to_bytes(); + memcpy(ipv6_value_.ipv6mr_multiaddr.s6_addr, bytes.elems, 16); + ipv6_value_.ipv6mr_interface = 0; + } + else + { + ipv4_value_.imr_multiaddr.s_addr = + asio::detail::socket_ops::host_to_network_long( + multicast_address.to_v4().to_ulong()); + ipv4_value_.imr_interface.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + + asio::detail::in6_addr_type tmp_addr = IN6ADDR_ANY_INIT; + ipv6_value_.ipv6mr_multiaddr = tmp_addr; + ipv6_value_.ipv6mr_interface = 0; + } + } + + // Construct with multicast address and IPv4 address specifying an interface. + explicit multicast_request( + const asio::ip::address_v4& multicast_address, + const asio::ip::address_v4& network_interface + = asio::ip::address_v4::any()) + { + ipv4_value_.imr_multiaddr.s_addr = + asio::detail::socket_ops::host_to_network_long( + multicast_address.to_ulong()); + ipv4_value_.imr_interface.s_addr = + asio::detail::socket_ops::host_to_network_long( + network_interface.to_ulong()); + + asio::detail::in6_addr_type tmp_addr = IN6ADDR_ANY_INIT; + ipv6_value_.ipv6mr_multiaddr = tmp_addr; + ipv6_value_.ipv6mr_interface = 0; + } + + // Construct with multicast address and IPv6 network interface index. + explicit multicast_request( + const asio::ip::address_v6& multicast_address, + unsigned long network_interface = 0) + { + ipv4_value_.imr_multiaddr.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + ipv4_value_.imr_interface.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + + using namespace std; // For memcpy. + asio::ip::address_v6::bytes_type bytes = + multicast_address.to_bytes(); + memcpy(ipv6_value_.ipv6mr_multiaddr.s6_addr, bytes.elems, 16); + ipv6_value_.ipv6mr_interface = network_interface; + } + + // Get the level of the socket option. + template + int level(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Level; + return IPv4_Level; + } + + // Get the name of the socket option. + template + int name(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Name; + return IPv4_Name; + } + + // Get the address of the option data. + template + const void* data(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return &ipv6_value_; + return &ipv4_value_; + } + + // Get the size of the option data. + template + std::size_t size(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return sizeof(ipv6_value_); + return sizeof(ipv4_value_); + } + +private: + asio::detail::in4_mreq_type ipv4_value_; + asio::detail::in6_mreq_type ipv6_value_; +}; + +// Helper template for implementing options that specify a network interface. +template +class network_interface +{ +public: + // Default constructor. + network_interface() + { + ipv4_value_.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + ipv6_value_ = 0; + } + + // Construct with IPv4 interface. + explicit network_interface(const asio::ip::address_v4& ipv4_interface) + { + ipv4_value_.s_addr = + asio::detail::socket_ops::host_to_network_long( + ipv4_interface.to_ulong()); + ipv6_value_ = 0; + } + + // Construct with IPv6 interface. + explicit network_interface(unsigned long ipv6_interface) + { + ipv4_value_.s_addr = + asio::detail::socket_ops::host_to_network_long( + asio::ip::address_v4::any().to_ulong()); + ipv6_value_ = ipv6_interface; + } + + // Get the level of the socket option. + template + int level(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Level; + return IPv4_Level; + } + + // Get the name of the socket option. + template + int name(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return IPv6_Name; + return IPv4_Name; + } + + // Get the address of the option data. + template + const void* data(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return &ipv6_value_; + return &ipv4_value_; + } + + // Get the size of the option data. + template + std::size_t size(const Protocol& protocol) const + { + if (protocol.family() == PF_INET6) + return sizeof(ipv6_value_); + return sizeof(ipv4_value_); + } + +private: + asio::detail::in4_addr_type ipv4_value_; + unsigned long ipv6_value_; +}; + +} // namespace socket_option +} // namespace detail +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_DETAIL_SOCKET_OPTION_HPP diff --git a/libtorrent/include/asio/ip/host_name.hpp b/libtorrent/include/asio/ip/host_name.hpp new file mode 100644 index 000000000..15adc08cc --- /dev/null +++ b/libtorrent/include/asio/ip/host_name.hpp @@ -0,0 +1,62 @@ +// +// host_name.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_HOST_NAME_HPP +#define ASIO_IP_HOST_NAME_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { +namespace ip { + +/// Get the current host name. +std::string host_name(); + +/// Get the current host name. +std::string host_name(asio::error_code& ec); + +inline std::string host_name() +{ + char name[1024]; + asio::error_code ec; + if (asio::detail::socket_ops::gethostname(name, sizeof(name), ec) != 0) + { + asio::detail::throw_error(ec); + return std::string(); + } + return std::string(name); +} + +inline std::string host_name(asio::error_code& ec) +{ + char name[1024]; + if (asio::detail::socket_ops::gethostname(name, sizeof(name), ec) != 0) + return std::string(); + return std::string(name); +} + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_HOST_NAME_HPP diff --git a/libtorrent/include/asio/ip/multicast.hpp b/libtorrent/include/asio/ip/multicast.hpp new file mode 100644 index 000000000..0d90659ca --- /dev/null +++ b/libtorrent/include/asio/ip/multicast.hpp @@ -0,0 +1,181 @@ +// +// multicast.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_MULTICAST_HPP +#define ASIO_IP_MULTICAST_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/ip/detail/socket_option.hpp" + +namespace asio { +namespace ip { +namespace multicast { + +/// Socket option to join a multicast group on a specified interface. +/** + * Implements the IPPROTO_IP/IP_ADD_MEMBERSHIP socket option. + * + * @par Examples + * Setting the option to join a multicast group: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::address multicast_address = + * asio::ip::address::from_string("225.0.0.1"); + * asio::ip::multicast::join_group option(multicast_address); + * socket.set_option(option); + * @endcode + * + * @par Concepts: + * SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined join_group; +#else +typedef asio::ip::detail::socket_option::multicast_request< + IPPROTO_IP, IP_ADD_MEMBERSHIP, IPPROTO_IPV6, IPV6_JOIN_GROUP> join_group; +#endif + +/// Socket option to leave a multicast group on a specified interface. +/** + * Implements the IPPROTO_IP/IP_DROP_MEMBERSHIP socket option. + * + * @par Examples + * Setting the option to leave a multicast group: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::address multicast_address = + * asio::ip::address::from_string("225.0.0.1"); + * asio::ip::multicast::leave_group option(multicast_address); + * socket.set_option(option); + * @endcode + * + * @par Concepts: + * SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined leave_group; +#else +typedef asio::ip::detail::socket_option::multicast_request< + IPPROTO_IP, IP_DROP_MEMBERSHIP, IPPROTO_IPV6, IPV6_LEAVE_GROUP> leave_group; +#endif + +/// Socket option for local interface to use for outgoing multicast packets. +/** + * Implements the IPPROTO_IP/IP_MULTICAST_IF socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::address_v4 local_interface = + * asio::ip::address_v4::from_string("1.2.3.4"); + * asio::ip::multicast::outbound_interface option(local_interface); + * socket.set_option(option); + * @endcode + * + * @par Concepts: + * SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined outbound_interface; +#else +typedef asio::ip::detail::socket_option::network_interface< + IPPROTO_IP, IP_MULTICAST_IF, IPPROTO_IPV6, IPV6_MULTICAST_IF> + outbound_interface; +#endif + +/// Socket option for time-to-live associated with outgoing multicast packets. +/** + * Implements the IPPROTO_IP/IP_MULTICAST_TTL socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::multicast::hops option(4); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::multicast::hops option; + * socket.get_option(option); + * int ttl = option.value(); + * @endcode + * + * @par Concepts: + * GettableSocketOption, SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined hops; +#else +typedef asio::ip::detail::socket_option::multicast_hops< + IPPROTO_IP, IP_MULTICAST_TTL, IPPROTO_IPV6, IPV6_MULTICAST_HOPS> hops; +#endif + +/// Socket option determining whether outgoing multicast packets will be +/// received on the same socket if it is a member of the multicast group. +/** + * Implements the IPPROTO_IP/IP_MULTICAST_LOOP socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::multicast::enable_loopback option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::multicast::enable_loopback option; + * socket.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * GettableSocketOption, SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined enable_loopback; +#else +typedef asio::ip::detail::socket_option::boolean< + IPPROTO_IP, IP_MULTICAST_LOOP, IPPROTO_IPV6, IPV6_MULTICAST_LOOP> + enable_loopback; +#endif + +} // namespace multicast +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_MULTICAST_HPP diff --git a/libtorrent/include/asio/ip/resolver_query_base.hpp b/libtorrent/include/asio/ip/resolver_query_base.hpp new file mode 100644 index 000000000..5b76fb3c2 --- /dev/null +++ b/libtorrent/include/asio/ip/resolver_query_base.hpp @@ -0,0 +1,107 @@ +// +// resolver_query_base.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_RESOLVER_QUERY_BASE_HPP +#define ASIO_IP_RESOLVER_QUERY_BASE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace ip { + +/// The resolver_query_base class is used as a base for the +/// basic_resolver_query class templates to provide a common place to define +/// the flag constants. +class resolver_query_base +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// Determine the canonical name of the host specified in the query. + static const int canonical_name = implementation_defined; + + /// Indicate that returned endpoint is intended for use as a locally bound + /// socket endpoint. + static const int passive = implementation_defined; + + /// Host name should be treated as a numeric string defining an IPv4 or IPv6 + /// address and no name resolution should be attempted. + static const int numeric_host = implementation_defined; + + /// Service name should be treated as a numeric string defining a port number + /// and no name resolution should be attempted. + static const int numeric_service = implementation_defined; + + /// If the query protocol family is specified as IPv6, return IPv4-mapped + /// IPv6 addresses on finding no IPv6 addresses. + static const int v4_mapped = implementation_defined; + + /// If used with v4_mapped, return all matching IPv6 and IPv4 addresses. + static const int all_matching = implementation_defined; + + /// Only return IPv4 addresses if a non-loopback IPv4 address is configured + /// for the system. Only return IPv6 addresses if a non-loopback IPv6 address + /// is configured for the system. + static const int address_configured = implementation_defined; +#else + BOOST_STATIC_CONSTANT(int, canonical_name = AI_CANONNAME); + BOOST_STATIC_CONSTANT(int, passive = AI_PASSIVE); + BOOST_STATIC_CONSTANT(int, numeric_host = AI_NUMERICHOST); +# if defined(AI_NUMERICSERV) + BOOST_STATIC_CONSTANT(int, numeric_service = AI_NUMERICSERV); +# else + BOOST_STATIC_CONSTANT(int, numeric_service = 0); +# endif +# if defined(AI_V4MAPPED) + BOOST_STATIC_CONSTANT(int, v4_mapped = AI_V4MAPPED); +# else + BOOST_STATIC_CONSTANT(int, v4_mapped = 0); +# endif +# if defined(AI_ALL) + BOOST_STATIC_CONSTANT(int, all_matching = AI_ALL); +# else + BOOST_STATIC_CONSTANT(int, all_matching = 0); +# endif +# if defined(AI_ADDRCONFIG) + BOOST_STATIC_CONSTANT(int, address_configured = AI_ADDRCONFIG); +# else + BOOST_STATIC_CONSTANT(int, address_configured = 0); +# endif +#endif + +protected: + /// Protected destructor to prevent deletion through this type. + ~resolver_query_base() + { + } + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +private: + // Workaround to enable the empty base optimisation with Borland C++. + char dummy_; +#endif +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_RESOLVER_QUERY_BASE_HPP diff --git a/libtorrent/include/asio/ip/resolver_service.hpp b/libtorrent/include/asio/ip/resolver_service.hpp new file mode 100644 index 000000000..6d5193ea8 --- /dev/null +++ b/libtorrent/include/asio/ip/resolver_service.hpp @@ -0,0 +1,140 @@ +// +// resolver_service.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_RESOLVER_SERVICE_HPP +#define ASIO_IP_RESOLVER_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/resolver_service.hpp" +#include "asio/detail/service_base.hpp" + +namespace asio { +namespace ip { + +/// Default service implementation for a resolver. +template +class resolver_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base< + resolver_service > +#endif +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The protocol type. + typedef InternetProtocol protocol_type; + + /// The endpoint type. + typedef typename InternetProtocol::endpoint endpoint_type; + + /// The query type. + typedef typename InternetProtocol::resolver_query query_type; + + /// The iterator type. + typedef typename InternetProtocol::resolver_iterator iterator_type; + +private: + // The type of the platform-specific implementation. + typedef asio::detail::resolver_service + service_impl_type; + +public: + /// The type of a resolver implementation. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined implementation_type; +#else + typedef typename service_impl_type::implementation_type implementation_type; +#endif + + /// Construct a new resolver service for the specified io_service. + explicit resolver_service(asio::io_service& io_service) + : asio::detail::service_base< + resolver_service >(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Construct a new resolver implementation. + void construct(implementation_type& impl) + { + service_impl_.construct(impl); + } + + /// Destroy a resolver implementation. + void destroy(implementation_type& impl) + { + service_impl_.destroy(impl); + } + + /// Cancel pending asynchronous operations. + void cancel(implementation_type& impl) + { + service_impl_.cancel(impl); + } + + /// Resolve a query to a list of entries. + iterator_type resolve(implementation_type& impl, const query_type& query, + asio::error_code& ec) + { + return service_impl_.resolve(impl, query, ec); + } + + /// Asynchronously resolve a query to a list of entries. + template + void async_resolve(implementation_type& impl, const query_type& query, + Handler handler) + { + service_impl_.async_resolve(impl, query, handler); + } + + /// Resolve an endpoint to a list of entries. + iterator_type resolve(implementation_type& impl, + const endpoint_type& endpoint, asio::error_code& ec) + { + return service_impl_.resolve(impl, endpoint, ec); + } + + /// Asynchronously resolve an endpoint to a list of entries. + template + void async_resolve(implementation_type& impl, const endpoint_type& endpoint, + ResolveHandler handler) + { + return service_impl_.async_resolve(impl, endpoint, handler); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_RESOLVER_SERVICE_HPP diff --git a/libtorrent/include/asio/ip/tcp.hpp b/libtorrent/include/asio/ip/tcp.hpp new file mode 100644 index 000000000..e31844c4d --- /dev/null +++ b/libtorrent/include/asio/ip/tcp.hpp @@ -0,0 +1,158 @@ +// +// tcp.hpp +// ~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_TCP_HPP +#define ASIO_IP_TCP_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_socket_acceptor.hpp" +#include "asio/basic_socket_iostream.hpp" +#include "asio/basic_stream_socket.hpp" +#include "asio/ip/basic_endpoint.hpp" +#include "asio/ip/basic_resolver.hpp" +#include "asio/ip/basic_resolver_iterator.hpp" +#include "asio/ip/basic_resolver_query.hpp" +#include "asio/detail/socket_option.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace ip { + +/// Encapsulates the flags needed for TCP. +/** + * The asio::ip::tcp class contains flags necessary for TCP sockets. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Safe. + * + * @par Concepts: + * Protocol, InternetProtocol. + */ +class tcp +{ +public: + /// The type of a TCP endpoint. + typedef basic_endpoint endpoint; + + /// The type of a resolver query. + typedef basic_resolver_query resolver_query; + + /// The type of a resolver iterator. + typedef basic_resolver_iterator resolver_iterator; + + /// Construct to represent the IPv4 TCP protocol. + static tcp v4() + { + return tcp(PF_INET); + } + + /// Construct to represent the IPv6 TCP protocol. + static tcp v6() + { + return tcp(PF_INET6); + } + + /// Obtain an identifier for the type of the protocol. + int type() const + { + return SOCK_STREAM; + } + + /// Obtain an identifier for the protocol. + int protocol() const + { + return IPPROTO_TCP; + } + + /// Obtain an identifier for the protocol family. + int family() const + { + return family_; + } + + /// The TCP socket type. + typedef basic_stream_socket socket; + + /// The TCP acceptor type. + typedef basic_socket_acceptor acceptor; + + /// The TCP resolver type. + typedef basic_resolver resolver; + + /// The TCP iostream type. + typedef basic_socket_iostream iostream; + + /// Socket option for disabling the Nagle algorithm. + /** + * Implements the IPPROTO_TCP/TCP_NODELAY socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::no_delay option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::tcp::no_delay option; + * socket.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined no_delay; +#else + typedef asio::detail::socket_option::boolean< + IPPROTO_TCP, TCP_NODELAY> no_delay; +#endif + + /// Compare two protocols for equality. + friend bool operator==(const tcp& p1, const tcp& p2) + { + return p1.family_ == p2.family_; + } + + /// Compare two protocols for inequality. + friend bool operator!=(const tcp& p1, const tcp& p2) + { + return p1.family_ != p2.family_; + } + +private: + // Construct with a specific family. + explicit tcp(int family) + : family_(family) + { + } + + int family_; +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_TCP_HPP diff --git a/libtorrent/include/asio/ip/udp.hpp b/libtorrent/include/asio/ip/udp.hpp new file mode 100644 index 000000000..e434f74d9 --- /dev/null +++ b/libtorrent/include/asio/ip/udp.hpp @@ -0,0 +1,116 @@ +// +// udp.hpp +// ~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_UDP_HPP +#define ASIO_IP_UDP_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_datagram_socket.hpp" +#include "asio/ip/basic_endpoint.hpp" +#include "asio/ip/basic_resolver.hpp" +#include "asio/ip/basic_resolver_iterator.hpp" +#include "asio/ip/basic_resolver_query.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace ip { + +/// Encapsulates the flags needed for UDP. +/** + * The asio::ip::udp class contains flags necessary for UDP sockets. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Safe. + * + * @par Concepts: + * Protocol, InternetProtocol. + */ +class udp +{ +public: + /// The type of a UDP endpoint. + typedef basic_endpoint endpoint; + + /// The type of a resolver query. + typedef basic_resolver_query resolver_query; + + /// The type of a resolver iterator. + typedef basic_resolver_iterator resolver_iterator; + + /// Construct to represent the IPv4 UDP protocol. + static udp v4() + { + return udp(PF_INET); + } + + /// Construct to represent the IPv6 UDP protocol. + static udp v6() + { + return udp(PF_INET6); + } + + /// Obtain an identifier for the type of the protocol. + int type() const + { + return SOCK_DGRAM; + } + + /// Obtain an identifier for the protocol. + int protocol() const + { + return IPPROTO_UDP; + } + + /// Obtain an identifier for the protocol family. + int family() const + { + return family_; + } + + /// The UDP socket type. + typedef basic_datagram_socket socket; + + /// The UDP resolver type. + typedef basic_resolver resolver; + + /// Compare two protocols for equality. + friend bool operator==(const udp& p1, const udp& p2) + { + return p1.family_ == p2.family_; + } + + /// Compare two protocols for inequality. + friend bool operator!=(const udp& p1, const udp& p2) + { + return p1.family_ != p2.family_; + } + +private: + // Construct with a specific family. + explicit udp(int family) + : family_(family) + { + } + + int family_; +}; + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_UDP_HPP diff --git a/libtorrent/include/asio/ip/unicast.hpp b/libtorrent/include/asio/ip/unicast.hpp new file mode 100644 index 000000000..ef065f298 --- /dev/null +++ b/libtorrent/include/asio/ip/unicast.hpp @@ -0,0 +1,70 @@ +// +// unicast.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_UNICAST_HPP +#define ASIO_IP_UNICAST_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/ip/detail/socket_option.hpp" + +namespace asio { +namespace ip { +namespace unicast { + +/// Socket option for time-to-live associated with outgoing unicast packets. +/** + * Implements the IPPROTO_IP/IP_UNICAST_TTL socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::unicast::hops option(4); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::ip::unicast::hops option; + * socket.get_option(option); + * int ttl = option.value(); + * @endcode + * + * @par Concepts: + * GettableSocketOption, SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined hops; +#else +typedef asio::ip::detail::socket_option::unicast_hops< + IPPROTO_IP, IP_TTL, IPPROTO_IPV6, IPV6_UNICAST_HOPS> hops; +#endif + +} // namespace unicast +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_UNICAST_HPP diff --git a/libtorrent/include/asio/ip/v6_only.hpp b/libtorrent/include/asio/ip/v6_only.hpp new file mode 100644 index 000000000..8c3708139 --- /dev/null +++ b/libtorrent/include/asio/ip/v6_only.hpp @@ -0,0 +1,68 @@ +// +// v6_only.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IP_V6_ONLY_HPP +#define ASIO_IP_V6_ONLY_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_option.hpp" + +namespace asio { +namespace ip { + +/// Socket option for determining whether an IPv6 socket supports IPv6 +/// communication only. +/** + * Implements the IPPROTO_IPV6/IP_V6ONLY socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::v6_only option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::ip::v6_only option; + * socket.get_option(option); + * bool v6_only = option.value(); + * @endcode + * + * @par Concepts: + * GettableSocketOption, SettableSocketOption. + */ +#if defined(GENERATING_DOCUMENTATION) +typedef implementation_defined v6_only; +#elif defined(IPV6_V6ONLY) +typedef asio::detail::socket_option::boolean< + IPPROTO_IPV6, IPV6_V6ONLY> v6_only; +#else +typedef asio::detail::socket_option::boolean< + asio::detail::custom_socket_option_level, + asio::detail::always_fail_option> v6_only; +#endif + +} // namespace ip +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IP_V6_ONLY_HPP diff --git a/libtorrent/include/asio/is_read_buffered.hpp b/libtorrent/include/asio/is_read_buffered.hpp new file mode 100644 index 000000000..091ba8c5f --- /dev/null +++ b/libtorrent/include/asio/is_read_buffered.hpp @@ -0,0 +1,62 @@ +// +// is_read_buffered.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IS_READ_BUFFERED_HPP +#define ASIO_IS_READ_BUFFERED_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffered_read_stream_fwd.hpp" +#include "asio/buffered_stream_fwd.hpp" + +namespace asio { + +namespace detail { + +template +char is_read_buffered_helper(buffered_stream* s); + +template +char is_read_buffered_helper(buffered_read_stream* s); + +struct is_read_buffered_big_type { char data[10]; }; +is_read_buffered_big_type is_read_buffered_helper(...); + +} // namespace detail + +/// The is_read_buffered class is a traits class that may be used to determine +/// whether a stream type supports buffering of read data. +template +class is_read_buffered +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The value member is true only if the Stream type supports buffering of + /// read data. + static const bool value; +#else + BOOST_STATIC_CONSTANT(bool, + value = sizeof(detail::is_read_buffered_helper((Stream*)0)) == 1); +#endif +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IS_READ_BUFFERED_HPP diff --git a/libtorrent/include/asio/is_write_buffered.hpp b/libtorrent/include/asio/is_write_buffered.hpp new file mode 100644 index 000000000..946f7468c --- /dev/null +++ b/libtorrent/include/asio/is_write_buffered.hpp @@ -0,0 +1,62 @@ +// +// is_write_buffered.hpp +// ~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_IS_WRITE_BUFFERED_HPP +#define ASIO_IS_WRITE_BUFFERED_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffered_stream_fwd.hpp" +#include "asio/buffered_write_stream_fwd.hpp" + +namespace asio { + +namespace detail { + +template +char is_write_buffered_helper(buffered_stream* s); + +template +char is_write_buffered_helper(buffered_write_stream* s); + +struct is_write_buffered_big_type { char data[10]; }; +is_write_buffered_big_type is_write_buffered_helper(...); + +} // namespace detail + +/// The is_write_buffered class is a traits class that may be used to determine +/// whether a stream type supports buffering of written data. +template +class is_write_buffered +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The value member is true only if the Stream type supports buffering of + /// written data. + static const bool value; +#else + BOOST_STATIC_CONSTANT(bool, + value = sizeof(detail::is_write_buffered_helper((Stream*)0)) == 1); +#endif +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_IS_WRITE_BUFFERED_HPP diff --git a/libtorrent/include/asio/placeholders.hpp b/libtorrent/include/asio/placeholders.hpp new file mode 100644 index 000000000..0c1b6b4a6 --- /dev/null +++ b/libtorrent/include/asio/placeholders.hpp @@ -0,0 +1,107 @@ +// +// placeholders.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_PLACEHOLDERS_HPP +#define ASIO_PLACEHOLDERS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace placeholders { + +#if defined(GENERATING_DOCUMENTATION) + +/// An argument placeholder, for use with @ref boost_bind, that corresponds to +/// the error argument of a handler for any of the asynchronous functions. +unspecified error; + +/// An argument placeholder, for use with @ref boost_bind, that corresponds to +/// the bytes_transferred argument of a handler for asynchronous functions such +/// as asio::basic_stream_socket::async_write_some or +/// asio::async_write. +unspecified bytes_transferred; + +/// An argument placeholder, for use with @ref boost_bind, that corresponds to +/// the iterator argument of a handler for asynchronous functions such as +/// asio::basic_resolver::resolve. +unspecified iterator; + +#elif defined(__BORLANDC__) || defined(__GNUC__) + +inline boost::arg<1> error() +{ + return boost::arg<1>(); +} + +inline boost::arg<2> bytes_transferred() +{ + return boost::arg<2>(); +} + +inline boost::arg<2> iterator() +{ + return boost::arg<2>(); +} + +#else + +namespace detail +{ + template + struct placeholder + { + static boost::arg& get() + { + static boost::arg result; + return result; + } + }; +} + +#if BOOST_WORKAROUND(BOOST_MSVC, < 1400) + +static boost::arg<1>& error + = asio::placeholders::detail::placeholder<1>::get(); +static boost::arg<2>& bytes_transferred + = asio::placeholders::detail::placeholder<2>::get(); +static boost::arg<2>& iterator + = asio::placeholders::detail::placeholder<2>::get(); + +#else + +namespace +{ + boost::arg<1>& error + = asio::placeholders::detail::placeholder<1>::get(); + boost::arg<2>& bytes_transferred + = asio::placeholders::detail::placeholder<2>::get(); + boost::arg<2>& iterator + = asio::placeholders::detail::placeholder<2>::get(); +} // namespace + +#endif + +#endif + +} // namespace placeholders +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_PLACEHOLDERS_HPP diff --git a/libtorrent/include/asio/read.hpp b/libtorrent/include/asio/read.hpp new file mode 100644 index 000000000..6e9772dee --- /dev/null +++ b/libtorrent/include/asio/read.hpp @@ -0,0 +1,516 @@ +// +// read.hpp +// ~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_READ_HPP +#define ASIO_READ_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_streambuf.hpp" +#include "asio/error.hpp" + +namespace asio { + +/** + * @defgroup read asio::read + */ +/*@{*/ + +/// Attempt to read a certain amount of data from a stream before returning. +/** + * This function is used to read a certain number of bytes of data from a + * stream. The call will block until one of the following conditions is true: + * + * @li The supplied buffers are full. That is, the bytes transferred is equal to + * the sum of the buffer sizes. + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param buffers One or more buffers into which the data will be read. The sum + * of the buffer sizes indicates the maximum number of bytes to read from the + * stream. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code asio::read(s, asio::buffer(data, size)); @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + * + * @note This overload is equivalent to calling: + * @code asio::read( + * s, buffers, + * asio::transfer_all()); @endcode + */ +template +std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers); + +/// Attempt to read a certain amount of data from a stream before returning. +/** + * This function is used to read a certain number of bytes of data from a + * stream. The call will block until one of the following conditions is true: + * + * @li The supplied buffers are full. That is, the bytes transferred is equal to + * the sum of the buffer sizes. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param buffers One or more buffers into which the data will be read. The sum + * of the buffer sizes indicates the maximum number of bytes to read from the + * stream. + * + * @param completion_condition The function object to be called to determine + * whether the read operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest read_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the read operation is complete. False + * indicates that further calls to the stream's read_some function are required. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code asio::read(s, asio::buffer(data, size), + * asio::transfer_at_least(32)); @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ +template +std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers, + CompletionCondition completion_condition); + +/// Attempt to read a certain amount of data from a stream before returning. +/** + * This function is used to read a certain number of bytes of data from a + * stream. The call will block until one of the following conditions is true: + * + * @li The supplied buffers are full. That is, the bytes transferred is equal to + * the sum of the buffer sizes. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param buffers One or more buffers into which the data will be read. The sum + * of the buffer sizes indicates the maximum number of bytes to read from the + * stream. + * + * @param completion_condition The function object to be called to determine + * whether the read operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest read_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the read operation is complete. False + * indicates that further calls to the stream's read_some function are required. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes read. If an error occurs, returns the total + * number of bytes successfully transferred prior to the error. + */ +template +std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers, + CompletionCondition completion_condition, asio::error_code& ec); + +/// Attempt to read a certain amount of data from a stream before returning. +/** + * This function is used to read a certain number of bytes of data from a + * stream. The call will block until one of the following conditions is true: + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b The basic_streambuf object into which the data will be read. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + * + * @note This overload is equivalent to calling: + * @code asio::read( + * s, b, + * asio::transfer_all()); @endcode + */ +template +std::size_t read(SyncReadStream& s, basic_streambuf& b); + +/// Attempt to read a certain amount of data from a stream before returning. +/** + * This function is used to read a certain number of bytes of data from a + * stream. The call will block until one of the following conditions is true: + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b The basic_streambuf object into which the data will be read. + * + * @param completion_condition The function object to be called to determine + * whether the read operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest read_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the read operation is complete. False + * indicates that further calls to the stream's read_some function are required. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + */ +template +std::size_t read(SyncReadStream& s, basic_streambuf& b, + CompletionCondition completion_condition); + +/// Attempt to read a certain amount of data from a stream before returning. +/** + * This function is used to read a certain number of bytes of data from a + * stream. The call will block until one of the following conditions is true: + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b The basic_streambuf object into which the data will be read. + * + * @param completion_condition The function object to be called to determine + * whether the read operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest read_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the read operation is complete. False + * indicates that further calls to the stream's read_some function are required. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes read. If an error occurs, returns the total + * number of bytes successfully transferred prior to the error. + */ +template +std::size_t read(SyncReadStream& s, basic_streambuf& b, + CompletionCondition completion_condition, asio::error_code& ec); + +/*@}*/ +/** + * @defgroup async_read asio::async_read + */ +/*@{*/ + +/// Start an asynchronous operation to read a certain amount of data from a +/// stream. +/** + * This function is used to asynchronously read a certain number of bytes of + * data from a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions is + * true: + * + * @li The supplied buffers are full. That is, the bytes transferred is equal to + * the sum of the buffer sizes. + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param buffers One or more buffers into which the data will be read. The sum + * of the buffer sizes indicates the maximum number of bytes to read from the + * stream. Although the buffers object may be copied as necessary, ownership of + * the underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes copied into the + * // buffers. If an error occurred, + * // this will be the number of + * // bytes successfully transferred + * // prior to the error. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code + * asio::async_read(s, asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + * + * @note This overload is equivalent to calling: + * @code asio::async_read( + * s, buffers, + * asio::transfer_all(), + * handler); @endcode + */ +template +void async_read(AsyncReadStream& s, const MutableBufferSequence& buffers, + ReadHandler handler); + +/// Start an asynchronous operation to read a certain amount of data from a +/// stream. +/** + * This function is used to asynchronously read a certain number of bytes of + * data from a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions is + * true: + * + * @li The supplied buffers are full. That is, the bytes transferred is equal to + * the sum of the buffer sizes. + * + * @li The completion_condition function object returns true. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param buffers One or more buffers into which the data will be read. The sum + * of the buffer sizes indicates the maximum number of bytes to read from the + * stream. Although the buffers object may be copied as necessary, ownership of + * the underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param completion_condition The function object to be called to determine + * whether the read operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest read_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the read operation is complete. False + * indicates that further calls to the stream's async_read_some function are + * required. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes copied into the + * // buffers. If an error occurred, + * // this will be the number of + * // bytes successfully transferred + * // prior to the error. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code asio::async_read(s, + * asio::buffer(data, size), + * asio::transfer_at_least(32), + * handler); @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ +template +void async_read(AsyncReadStream& s, const MutableBufferSequence& buffers, + CompletionCondition completion_condition, ReadHandler handler); + +/// Start an asynchronous operation to read a certain amount of data from a +/// stream. +/** + * This function is used to asynchronously read a certain number of bytes of + * data from a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions is + * true: + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param b A basic_streambuf object into which the data will be read. Ownership + * of the streambuf is retained by the caller, which must guarantee that it + * remains valid until the handler is called. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes copied into the + * // buffers. If an error occurred, + * // this will be the number of + * // bytes successfully transferred + * // prior to the error. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note This overload is equivalent to calling: + * @code asio::async_read( + * s, b, + * asio::transfer_all(), + * handler); @endcode + */ +template +void async_read(AsyncReadStream& s, basic_streambuf& b, + ReadHandler handler); + +/// Start an asynchronous operation to read a certain amount of data from a +/// stream. +/** + * This function is used to asynchronously read a certain number of bytes of + * data from a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions is + * true: + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_read_some function. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param b A basic_streambuf object into which the data will be read. Ownership + * of the streambuf is retained by the caller, which must guarantee that it + * remains valid until the handler is called. + * + * @param completion_condition The function object to be called to determine + * whether the read operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest read_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the read operation is complete. False + * indicates that further calls to the stream's async_read_some function are + * required. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes copied into the + * // buffers. If an error occurred, + * // this will be the number of + * // bytes successfully transferred + * // prior to the error. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ +template +void async_read(AsyncReadStream& s, basic_streambuf& b, + CompletionCondition completion_condition, ReadHandler handler); + +/*@}*/ + +} // namespace asio + +#include "asio/impl/read.ipp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_READ_HPP diff --git a/libtorrent/include/asio/read_until.hpp b/libtorrent/include/asio/read_until.hpp new file mode 100644 index 000000000..6917ec6b8 --- /dev/null +++ b/libtorrent/include/asio/read_until.hpp @@ -0,0 +1,452 @@ +// +// read_until.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_READ_UNTIL_HPP +#define ASIO_READ_UNTIL_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_streambuf.hpp" +#include "asio/error.hpp" + +namespace asio { + +/** + * @defgroup read_until asio::read_until + */ +/*@{*/ + +/// Read data into a streambuf until a delimiter is encountered. +/** + * This function is used to read data into the specified streambuf until the + * streambuf's get area contains the specified delimiter. The call will block + * until one of the following conditions is true: + * + * @li The get area of the streambuf contains the specified delimiter. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * read_some function. If the streambuf's get area already contains the + * delimiter, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. + * + * @param delim The delimiter character. + * + * @returns The number of bytes in the streambuf's get area up to and including + * the delimiter. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To read data into a streambuf until a newline is encountered: + * @code asio::streambuf b; + * asio::read_until(s, b, '\n'); + * std::istream is(&b); + * std::string line; + * std::getline(is, line); @endcode + */ +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, char delim); + +/// Read data into a streambuf until a delimiter is encountered. +/** + * This function is used to read data into the specified streambuf until the + * streambuf's get area contains the specified delimiter. The call will block + * until one of the following conditions is true: + * + * @li The get area of the streambuf contains the specified delimiter. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * read_some function. If the streambuf's get area already contains the + * delimiter, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. + * + * @param delim The delimiter character. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes in the streambuf's get area up to and including + * the delimiter. Returns 0 if an error occurred. + */ +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, char delim, + asio::error_code& ec); + +/// Read data into a streambuf until a delimiter is encountered. +/** + * This function is used to read data into the specified streambuf until the + * streambuf's get area contains the specified delimiter. The call will block + * until one of the following conditions is true: + * + * @li The get area of the streambuf contains the specified delimiter. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * read_some function. If the streambuf's get area already contains the + * delimiter, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. + * + * @param delim The delimiter string. + * + * @returns The number of bytes in the streambuf's get area up to and including + * the delimiter. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To read data into a streambuf until a newline is encountered: + * @code asio::streambuf b; + * asio::read_until(s, b, "\r\n"); + * std::istream is(&b); + * std::string line; + * std::getline(is, line); @endcode + */ +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const std::string& delim); + +/// Read data into a streambuf until a delimiter is encountered. +/** + * This function is used to read data into the specified streambuf until the + * streambuf's get area contains the specified delimiter. The call will block + * until one of the following conditions is true: + * + * @li The get area of the streambuf contains the specified delimiter. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * read_some function. If the streambuf's get area already contains the + * delimiter, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. + * + * @param delim The delimiter string. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes in the streambuf's get area up to and including + * the delimiter. Returns 0 if an error occurred. + */ +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const std::string& delim, + asio::error_code& ec); + +/// Read data into a streambuf until a regular expression is located. +/** + * This function is used to read data into the specified streambuf until the + * streambuf's get area contains some data that matches a regular expression. + * The call will block until one of the following conditions is true: + * + * @li A substring of the streambuf's get area matches the regular expression. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * read_some function. If the streambuf's get area already contains data that + * matches the regular expression, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. + * + * @param expr The regular expression. + * + * @returns The number of bytes in the streambuf's get area up to and including + * the substring that matches the regular expression. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To read data into a streambuf until a CR-LF sequence is encountered: + * @code asio::streambuf b; + * asio::read_until(s, b, boost::regex("\r\n")); + * std::istream is(&b); + * std::string line; + * std::getline(is, line); @endcode + */ +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const boost::regex& expr); + +/// Read data into a streambuf until a regular expression is located. +/** + * This function is used to read data into the specified streambuf until the + * streambuf's get area contains some data that matches a regular expression. + * The call will block until one of the following conditions is true: + * + * @li A substring of the streambuf's get area matches the regular expression. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * read_some function. If the streambuf's get area already contains data that + * matches the regular expression, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the SyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. + * + * @param expr The regular expression. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes in the streambuf's get area up to and including + * the substring that matches the regular expression. Returns 0 if an error + * occurred. + */ +template +std::size_t read_until(SyncReadStream& s, + asio::basic_streambuf& b, const boost::regex& expr, + asio::error_code& ec); + +/*@}*/ +/** +* @defgroup async_read_until asio::async_read_until +*/ +/*@{*/ + +/// Start an asynchronous operation to read data into a streambuf until a +/// delimiter is encountered. +/** + * This function is used to asynchronously read data into the specified + * streambuf until the streambuf's get area contains the specified delimiter. + * The function call always returns immediately. The asynchronous operation + * will continue until one of the following conditions is true: + * + * @li The get area of the streambuf contains the specified delimiter. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * async_read_some function. If the streambuf's get area already contains the + * delimiter, the asynchronous operation completes immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. Ownership of + * the streambuf is retained by the caller, which must guarantee that it remains + * valid until the handler is called. + * + * @param delim The delimiter character. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // The number of bytes in the + * // streambuf's get area up to + * // and including the delimiter. + * // 0 if an error occurred. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To asynchronously read data into a streambuf until a newline is encountered: + * @code asio::streambuf b; + * ... + * void handler(const asio::error_code& e, std::size_t size) + * { + * if (!e) + * { + * std::istream is(&b); + * std::string line; + * std::getline(is, line); + * ... + * } + * } + * ... + * asio::async_read_until(s, b, '\n', handler); @endcode + */ +template +void async_read_until(AsyncReadStream& s, + asio::basic_streambuf& b, + char delim, ReadHandler handler); + +/// Start an asynchronous operation to read data into a streambuf until a +/// delimiter is encountered. +/** + * This function is used to asynchronously read data into the specified + * streambuf until the streambuf's get area contains the specified delimiter. + * The function call always returns immediately. The asynchronous operation + * will continue until one of the following conditions is true: + * + * @li The get area of the streambuf contains the specified delimiter. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * async_read_some function. If the streambuf's get area already contains the + * delimiter, the asynchronous operation completes immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. Ownership of + * the streambuf is retained by the caller, which must guarantee that it remains + * valid until the handler is called. + * + * @param delim The delimiter string. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // The number of bytes in the + * // streambuf's get area up to + * // and including the delimiter. + * // 0 if an error occurred. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To asynchronously read data into a streambuf until a newline is encountered: + * @code asio::streambuf b; + * ... + * void handler(const asio::error_code& e, std::size_t size) + * { + * if (!e) + * { + * std::istream is(&b); + * std::string line; + * std::getline(is, line); + * ... + * } + * } + * ... + * asio::async_read_until(s, b, "\r\n", handler); @endcode + */ +template +void async_read_until(AsyncReadStream& s, + asio::basic_streambuf& b, const std::string& delim, + ReadHandler handler); + +/// Start an asynchronous operation to read data into a streambuf until a +/// regular expression is located. +/** + * This function is used to asynchronously read data into the specified + * streambuf until the streambuf's get area contains some data that matches a + * regular expression. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions + * is true: + * + * @li A substring of the streambuf's get area matches the regular expression. + * + * @li An error occurred. + * + * This operation is implemented in terms of zero or more calls to the stream's + * async_read_some function. If the streambuf's get area already contains data + * that matches the regular expression, the function returns immediately. + * + * @param s The stream from which the data is to be read. The type must support + * the AsyncReadStream concept. + * + * @param b A streambuf object into which the data will be read. Ownership of + * the streambuf is retained by the caller, which must guarantee that it remains + * valid until the handler is called. + * + * @param expr The regular expression. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // The number of bytes in the + * // streambuf's get area up to + * // and including the substring + * // that matches the regular. + * // expression. 0 if an error + * // occurred. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To asynchronously read data into a streambuf until a CR-LF sequence is + * encountered: + * @code asio::streambuf b; + * ... + * void handler(const asio::error_code& e, std::size_t size) + * { + * if (!e) + * { + * std::istream is(&b); + * std::string line; + * std::getline(is, line); + * ... + * } + * } + * ... + * asio::async_read_until(s, b, boost::regex("\r\n"), handler); @endcode + */ +template +void async_read_until(AsyncReadStream& s, + asio::basic_streambuf& b, const boost::regex& expr, + ReadHandler handler); + +/*@}*/ + +} // namespace asio + +#include "asio/impl/read_until.ipp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_READ_UNTIL_HPP diff --git a/libtorrent/include/asio/socket_acceptor_service.hpp b/libtorrent/include/asio/socket_acceptor_service.hpp new file mode 100644 index 000000000..965bff39e --- /dev/null +++ b/libtorrent/include/asio/socket_acceptor_service.hpp @@ -0,0 +1,222 @@ +// +// socket_acceptor_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SOCKET_ACCEPTOR_SERVICE_HPP +#define ASIO_SOCKET_ACCEPTOR_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_socket.hpp" +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/epoll_reactor.hpp" +#include "asio/detail/kqueue_reactor.hpp" +#include "asio/detail/select_reactor.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/reactive_socket_service.hpp" +#include "asio/detail/win_iocp_socket_service.hpp" + +namespace asio { + +/// Default service implementation for a socket acceptor. +template +class socket_acceptor_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base > +#endif +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename protocol_type::endpoint endpoint_type; + +private: + // The type of the platform-specific implementation. +#if defined(ASIO_HAS_IOCP) + typedef detail::win_iocp_socket_service service_impl_type; +#elif defined(ASIO_HAS_EPOLL) + typedef detail::reactive_socket_service< + Protocol, detail::epoll_reactor > service_impl_type; +#elif defined(ASIO_HAS_KQUEUE) + typedef detail::reactive_socket_service< + Protocol, detail::kqueue_reactor > service_impl_type; +#else + typedef detail::reactive_socket_service< + Protocol, detail::select_reactor > service_impl_type; +#endif + +public: + /// The native type of the socket acceptor. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined implementation_type; +#else + typedef typename service_impl_type::implementation_type implementation_type; +#endif + + /// The native acceptor type. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined native_type; +#else + typedef typename service_impl_type::native_type native_type; +#endif + + /// Construct a new socket acceptor service for the specified io_service. + explicit socket_acceptor_service(asio::io_service& io_service) + : asio::detail::service_base< + socket_acceptor_service >(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Construct a new socket acceptor implementation. + void construct(implementation_type& impl) + { + service_impl_.construct(impl); + } + + /// Destroy a socket acceptor implementation. + void destroy(implementation_type& impl) + { + service_impl_.destroy(impl); + } + + /// Open a new socket acceptor implementation. + asio::error_code open(implementation_type& impl, + const protocol_type& protocol, asio::error_code& ec) + { + return service_impl_.open(impl, protocol, ec); + } + + /// Assign an existing native acceptor to a socket acceptor. + asio::error_code assign(implementation_type& impl, + const protocol_type& protocol, const native_type& native_acceptor, + asio::error_code& ec) + { + return service_impl_.assign(impl, protocol, native_acceptor, ec); + } + + /// Determine whether the acceptor is open. + bool is_open(const implementation_type& impl) const + { + return service_impl_.is_open(impl); + } + + /// Cancel all asynchronous operations associated with the acceptor. + asio::error_code cancel(implementation_type& impl, + asio::error_code& ec) + { + return service_impl_.cancel(impl, ec); + } + + /// Bind the socket acceptor to the specified local endpoint. + asio::error_code bind(implementation_type& impl, + const endpoint_type& endpoint, asio::error_code& ec) + { + return service_impl_.bind(impl, endpoint, ec); + } + + /// Place the socket acceptor into the state where it will listen for new + /// connections. + asio::error_code listen(implementation_type& impl, int backlog, + asio::error_code& ec) + { + return service_impl_.listen(impl, backlog, ec); + } + + /// Close a socket acceptor implementation. + asio::error_code close(implementation_type& impl, + asio::error_code& ec) + { + return service_impl_.close(impl, ec); + } + + /// Get the native acceptor implementation. + native_type native(implementation_type& impl) + { + return service_impl_.native(impl); + } + + /// Set a socket option. + template + asio::error_code set_option(implementation_type& impl, + const SettableSocketOption& option, asio::error_code& ec) + { + return service_impl_.set_option(impl, option, ec); + } + + /// Get a socket option. + template + asio::error_code get_option(const implementation_type& impl, + GettableSocketOption& option, asio::error_code& ec) const + { + return service_impl_.get_option(impl, option, ec); + } + + /// Perform an IO control command on the socket. + template + asio::error_code io_control(implementation_type& impl, + IoControlCommand& command, asio::error_code& ec) + { + return service_impl_.io_control(impl, command, ec); + } + + /// Get the local endpoint. + endpoint_type local_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.local_endpoint(impl, ec); + } + + /// Accept a new connection. + template + asio::error_code accept(implementation_type& impl, + basic_socket& peer, + endpoint_type* peer_endpoint, asio::error_code& ec) + { + return service_impl_.accept(impl, peer, peer_endpoint, ec); + } + + /// Start an asynchronous accept. + template + void async_accept(implementation_type& impl, + basic_socket& peer, + endpoint_type* peer_endpoint, AcceptHandler handler) + { + service_impl_.async_accept(impl, peer, peer_endpoint, handler); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SOCKET_ACCEPTOR_SERVICE_HPP diff --git a/libtorrent/include/asio/socket_base.hpp b/libtorrent/include/asio/socket_base.hpp new file mode 100644 index 000000000..1239989a9 --- /dev/null +++ b/libtorrent/include/asio/socket_base.hpp @@ -0,0 +1,515 @@ +// +// socket_base.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SOCKET_BASE_HPP +#define ASIO_SOCKET_BASE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/io_control.hpp" +#include "asio/detail/socket_option.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { + +/// The socket_base class is used as a base for the basic_stream_socket and +/// basic_datagram_socket class templates so that we have a common place to +/// define the shutdown_type and enum. +class socket_base +{ +public: + /// Different ways a socket may be shutdown. + enum shutdown_type + { +#if defined(GENERATING_DOCUMENTATION) + /// Shutdown the receive side of the socket. + shutdown_receive = implementation_defined, + + /// Shutdown the send side of the socket. + shutdown_send = implementation_defined, + + /// Shutdown both send and receive on the socket. + shutdown_both = implementation_defined +#else + shutdown_receive = asio::detail::shutdown_receive, + shutdown_send = asio::detail::shutdown_send, + shutdown_both = asio::detail::shutdown_both +#endif + }; + + /// Bitmask type for flags that can be passed to send and receive operations. + typedef int message_flags; + +#if defined(GENERATING_DOCUMENTATION) + /// Peek at incoming data without removing it from the input queue. + static const int message_peek = implementation_defined; + + /// Process out-of-band data. + static const int message_out_of_band = implementation_defined; + + /// Specify that the data should not be subject to routing. + static const int message_do_not_route = implementation_defined; +#else + BOOST_STATIC_CONSTANT(int, + message_peek = asio::detail::message_peek); + BOOST_STATIC_CONSTANT(int, + message_out_of_band = asio::detail::message_out_of_band); + BOOST_STATIC_CONSTANT(int, + message_do_not_route = asio::detail::message_do_not_route); +#endif + + /// Socket option to permit sending of broadcast messages. + /** + * Implements the SOL_SOCKET/SO_BROADCAST socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::socket_base::broadcast option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::socket_base::broadcast option; + * socket.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined broadcast; +#else + typedef asio::detail::socket_option::boolean< + SOL_SOCKET, SO_BROADCAST> broadcast; +#endif + + /// Socket option to enable socket-level debugging. + /** + * Implements the SOL_SOCKET/SO_DEBUG socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::debug option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::debug option; + * socket.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined debug; +#else + typedef asio::detail::socket_option::boolean< + SOL_SOCKET, SO_DEBUG> debug; +#endif + + /// Socket option to prevent routing, use local interfaces only. + /** + * Implements the SOL_SOCKET/SO_DONTROUTE socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::socket_base::do_not_route option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::udp::socket socket(io_service); + * ... + * asio::socket_base::do_not_route option; + * socket.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined do_not_route; +#else + typedef asio::detail::socket_option::boolean< + SOL_SOCKET, SO_DONTROUTE> do_not_route; +#endif + + /// Socket option to send keep-alives. + /** + * Implements the SOL_SOCKET/SO_KEEPALIVE socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::keep_alive option(true); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::keep_alive option; + * socket.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined keep_alive; +#else + typedef asio::detail::socket_option::boolean< + SOL_SOCKET, SO_KEEPALIVE> keep_alive; +#endif + + /// Socket option for the send buffer size of a socket. + /** + * Implements the SOL_SOCKET/SO_SNDBUF socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::send_buffer_size option(8192); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::send_buffer_size option; + * socket.get_option(option); + * int size = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Integer_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined send_buffer_size; +#else + typedef asio::detail::socket_option::integer< + SOL_SOCKET, SO_SNDBUF> send_buffer_size; +#endif + + /// Socket option for the send low watermark. + /** + * Implements the SOL_SOCKET/SO_SNDLOWAT socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::send_low_watermark option(1024); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::send_low_watermark option; + * socket.get_option(option); + * int size = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Integer_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined send_low_watermark; +#else + typedef asio::detail::socket_option::integer< + SOL_SOCKET, SO_SNDLOWAT> send_low_watermark; +#endif + + /// Socket option for the receive buffer size of a socket. + /** + * Implements the SOL_SOCKET/SO_RCVBUF socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::receive_buffer_size option(8192); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::receive_buffer_size option; + * socket.get_option(option); + * int size = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Integer_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined receive_buffer_size; +#else + typedef asio::detail::socket_option::integer< + SOL_SOCKET, SO_RCVBUF> receive_buffer_size; +#endif + + /// Socket option for the receive low watermark. + /** + * Implements the SOL_SOCKET/SO_RCVLOWAT socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::receive_low_watermark option(1024); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::receive_low_watermark option; + * socket.get_option(option); + * int size = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Integer_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined receive_low_watermark; +#else + typedef asio::detail::socket_option::integer< + SOL_SOCKET, SO_RCVLOWAT> receive_low_watermark; +#endif + + /// Socket option to allow the socket to be bound to an address that is + /// already in use. + /** + * Implements the SOL_SOCKET/SO_REUSEADDR socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::socket_base::reuse_address option(true); + * acceptor.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::socket_base::reuse_address option; + * acceptor.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined reuse_address; +#else + typedef asio::detail::socket_option::boolean< + SOL_SOCKET, SO_REUSEADDR> reuse_address; +#endif + + /// Socket option to specify whether the socket lingers on close if unsent + /// data is present. + /** + * Implements the SOL_SOCKET/SO_LINGER socket option. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::linger option(true, 30); + * socket.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::linger option; + * socket.get_option(option); + * bool is_set = option.enabled(); + * unsigned short timeout = option.timeout(); + * @endcode + * + * @par Concepts: + * Socket_Option, Linger_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined linger; +#else + typedef asio::detail::socket_option::linger< + SOL_SOCKET, SO_LINGER> linger; +#endif + + /// Socket option to report aborted connections on accept. + /** + * Implements a custom socket option that determines whether or not an accept + * operation is permitted to fail with asio::error::connection_aborted. + * By default the option is false. + * + * @par Examples + * Setting the option: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::socket_base::enable_connection_aborted option(true); + * acceptor.set_option(option); + * @endcode + * + * @par + * Getting the current option value: + * @code + * asio::ip::tcp::acceptor acceptor(io_service); + * ... + * asio::socket_base::enable_connection_aborted option; + * acceptor.get_option(option); + * bool is_set = option.value(); + * @endcode + * + * @par Concepts: + * Socket_Option, Boolean_Socket_Option. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined enable_connection_aborted; +#else + typedef asio::detail::socket_option::boolean< + asio::detail::custom_socket_option_level, + asio::detail::enable_connection_aborted_option> + enable_connection_aborted; +#endif + + /// IO control command to set the blocking mode of the socket. + /** + * Implements the FIONBIO IO control command. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::non_blocking_io command(true); + * socket.io_control(command); + * @endcode + * + * @par Concepts: + * IO_Control_Command, Boolean_IO_Control_Command. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined non_blocking_io; +#else + typedef asio::detail::io_control::non_blocking_io non_blocking_io; +#endif + + /// IO control command to get the amount of data that can be read without + /// blocking. + /** + * Implements the FIONREAD IO control command. + * + * @par Example + * @code + * asio::ip::tcp::socket socket(io_service); + * ... + * asio::socket_base::bytes_readable command(true); + * socket.io_control(command); + * std::size_t bytes_readable = command.get(); + * @endcode + * + * @par Concepts: + * IO_Control_Command, Size_IO_Control_Command. + */ +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined bytes_readable; +#else + typedef asio::detail::io_control::bytes_readable bytes_readable; +#endif + + /// The maximum length of the queue of pending incoming connections. +#if defined(GENERATING_DOCUMENTATION) + static const int max_connections = implementation_defined; +#else + BOOST_STATIC_CONSTANT(int, max_connections = SOMAXCONN); +#endif + +protected: + /// Protected destructor to prevent deletion through this type. + ~socket_base() + { + } + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +private: + // Workaround to enable the empty base optimisation with Borland C++. + char dummy_; +#endif +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SOCKET_BASE_HPP diff --git a/libtorrent/include/asio/ssl.hpp b/libtorrent/include/asio/ssl.hpp new file mode 100644 index 000000000..3f2d2750c --- /dev/null +++ b/libtorrent/include/asio/ssl.hpp @@ -0,0 +1,26 @@ +// +// ssl.hpp +// ~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_HPP +#define ASIO_SSL_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/ssl/basic_context.hpp" +#include "asio/ssl/context.hpp" +#include "asio/ssl/context_base.hpp" +#include "asio/ssl/context_service.hpp" +#include "asio/ssl/stream.hpp" +#include "asio/ssl/stream_base.hpp" +#include "asio/ssl/stream_service.hpp" + +#endif // ASIO_SSL_HPP diff --git a/libtorrent/include/asio/ssl/CVS/Entries b/libtorrent/include/asio/ssl/CVS/Entries new file mode 100644 index 000000000..b15450e7b --- /dev/null +++ b/libtorrent/include/asio/ssl/CVS/Entries @@ -0,0 +1,8 @@ +/basic_context.hpp/1.11/Thu Dec 21 12:29:03 2006// +/context.hpp/1.3/Mon Apr 10 12:17:44 2006// +/context_base.hpp/1.6/Sun Sep 24 07:46:22 2006// +/context_service.hpp/1.11/Fri Dec 29 02:01:24 2006// +/stream.hpp/1.13/Thu Dec 21 12:29:03 2006// +/stream_base.hpp/1.4/Wed Nov 30 01:57:07 2005// +/stream_service.hpp/1.10/Fri Dec 29 02:01:24 2006// +D/detail//// diff --git a/libtorrent/include/asio/ssl/CVS/Repository b/libtorrent/include/asio/ssl/CVS/Repository new file mode 100644 index 000000000..a0555e83a --- /dev/null +++ b/libtorrent/include/asio/ssl/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio/ssl diff --git a/libtorrent/include/asio/ssl/CVS/Root b/libtorrent/include/asio/ssl/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/ssl/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ssl/basic_context.hpp b/libtorrent/include/asio/ssl/basic_context.hpp new file mode 100755 index 000000000..b85a01c1d --- /dev/null +++ b/libtorrent/include/asio/ssl/basic_context.hpp @@ -0,0 +1,434 @@ +// +// basic_context.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_BASIC_CONTEXT_HPP +#define ASIO_SSL_BASIC_CONTEXT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/ssl/context_base.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { +namespace ssl { + +/// SSL context. +template +class basic_context + : public context_base, + private boost::noncopyable +{ +public: + /// The type of the service that will be used to provide context operations. + typedef Service service_type; + + /// The native implementation type of the locking dispatcher. + typedef typename service_type::impl_type impl_type; + + /// Constructor. + basic_context(asio::io_service& io_service, method m) + : service_(asio::use_service(io_service)), + impl_(service_.null()) + { + service_.create(impl_, m); + } + + /// Destructor. + ~basic_context() + { + service_.destroy(impl_); + } + + /// Get the underlying implementation in the native type. + /** + * This function may be used to obtain the underlying implementation of the + * context. This is intended to allow access to context functionality that is + * not otherwise provided. + */ + impl_type impl() + { + return impl_; + } + + /// Set options on the context. + /** + * This function may be used to configure the SSL options used by the context. + * + * @param o A bitmask of options. The available option values are defined in + * the context_base class. The options are bitwise-ored with any existing + * value for the options. + * + * @throws asio::system_error Thrown on failure. + */ + void set_options(options o) + { + asio::error_code ec; + service_.set_options(impl_, o, ec); + asio::detail::throw_error(ec); + } + + /// Set options on the context. + /** + * This function may be used to configure the SSL options used by the context. + * + * @param o A bitmask of options. The available option values are defined in + * the context_base class. The options are bitwise-ored with any existing + * value for the options. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code set_options(options o, + asio::error_code& ec) + { + return service_.set_options(impl_, o, ec); + } + + /// Set the peer verification mode. + /** + * This function may be used to configure the peer verification mode used by + * the context. + * + * @param v A bitmask of peer verification modes. The available verify_mode + * values are defined in the context_base class. + * + * @throws asio::system_error Thrown on failure. + */ + void set_verify_mode(verify_mode v) + { + asio::error_code ec; + service_.set_verify_mode(impl_, v, ec); + asio::detail::throw_error(ec); + } + + /// Set the peer verification mode. + /** + * This function may be used to configure the peer verification mode used by + * the context. + * + * @param v A bitmask of peer verification modes. The available verify_mode + * values are defined in the context_base class. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code set_verify_mode(verify_mode v, + asio::error_code& ec) + { + return service_.set_verify_mode(impl_, v, ec); + } + + /// Load a certification authority file for performing verification. + /** + * This function is used to load one or more trusted certification authorities + * from a file. + * + * @param filename The name of a file containing certification authority + * certificates in PEM format. + * + * @throws asio::system_error Thrown on failure. + */ + void load_verify_file(const std::string& filename) + { + asio::error_code ec; + service_.load_verify_file(impl_, filename, ec); + asio::detail::throw_error(ec); + } + + /// Load a certification authority file for performing verification. + /** + * This function is used to load the certificates for one or more trusted + * certification authorities from a file. + * + * @param filename The name of a file containing certification authority + * certificates in PEM format. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code load_verify_file(const std::string& filename, + asio::error_code& ec) + { + return service_.load_verify_file(impl_, filename, ec); + } + + /// Add a directory containing certificate authority files to be used for + /// performing verification. + /** + * This function is used to specify the name of a directory containing + * certification authority certificates. Each file in the directory must + * contain a single certificate. The files must be named using the subject + * name's hash and an extension of ".0". + * + * @param path The name of a directory containing the certificates. + * + * @throws asio::system_error Thrown on failure. + */ + void add_verify_path(const std::string& path) + { + asio::error_code ec; + service_.add_verify_path(impl_, path, ec); + asio::detail::throw_error(ec); + } + + /// Add a directory containing certificate authority files to be used for + /// performing verification. + /** + * This function is used to specify the name of a directory containing + * certification authority certificates. Each file in the directory must + * contain a single certificate. The files must be named using the subject + * name's hash and an extension of ".0". + * + * @param path The name of a directory containing the certificates. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code add_verify_path(const std::string& path, + asio::error_code& ec) + { + return service_.add_verify_path(impl_, path, ec); + } + + /// Use a certificate from a file. + /** + * This function is used to load a certificate into the context from a file. + * + * @param filename The name of the file containing the certificate. + * + * @param format The file format (ASN.1 or PEM). + * + * @throws asio::system_error Thrown on failure. + */ + void use_certificate_file(const std::string& filename, file_format format) + { + asio::error_code ec; + service_.use_certificate_file(impl_, filename, format, ec); + asio::detail::throw_error(ec); + } + + /// Use a certificate from a file. + /** + * This function is used to load a certificate into the context from a file. + * + * @param filename The name of the file containing the certificate. + * + * @param format The file format (ASN.1 or PEM). + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code use_certificate_file(const std::string& filename, + file_format format, asio::error_code& ec) + { + return service_.use_certificate_file(impl_, filename, format, ec); + } + + /// Use a certificate chain from a file. + /** + * This function is used to load a certificate chain into the context from a + * file. + * + * @param filename The name of the file containing the certificate. The file + * must use the PEM format. + * + * @throws asio::system_error Thrown on failure. + */ + void use_certificate_chain_file(const std::string& filename) + { + asio::error_code ec; + service_.use_certificate_chain_file(impl_, filename, ec); + asio::detail::throw_error(ec); + } + + /// Use a certificate chain from a file. + /** + * This function is used to load a certificate chain into the context from a + * file. + * + * @param filename The name of the file containing the certificate. The file + * must use the PEM format. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code use_certificate_chain_file( + const std::string& filename, asio::error_code& ec) + { + return service_.use_certificate_chain_file(impl_, filename, ec); + } + + /// Use a private key from a file. + /** + * This function is used to load a private key into the context from a file. + * + * @param filename The name of the file containing the private key. + * + * @param format The file format (ASN.1 or PEM). + * + * @throws asio::system_error Thrown on failure. + */ + void use_private_key_file(const std::string& filename, file_format format) + { + asio::error_code ec; + service_.use_private_key_file(impl_, filename, format, ec); + asio::detail::throw_error(ec); + } + + /// Use a private key from a file. + /** + * This function is used to load a private key into the context from a file. + * + * @param filename The name of the file containing the private key. + * + * @param format The file format (ASN.1 or PEM). + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code use_private_key_file(const std::string& filename, + file_format format, asio::error_code& ec) + { + return service_.use_private_key_file(impl_, filename, format, ec); + } + + /// Use an RSA private key from a file. + /** + * This function is used to load an RSA private key into the context from a + * file. + * + * @param filename The name of the file containing the RSA private key. + * + * @param format The file format (ASN.1 or PEM). + * + * @throws asio::system_error Thrown on failure. + */ + void use_rsa_private_key_file(const std::string& filename, file_format format) + { + asio::error_code ec; + service_.use_rsa_private_key_file(impl_, filename, format, ec); + asio::detail::throw_error(ec); + } + + /// Use an RSA private key from a file. + /** + * This function is used to load an RSA private key into the context from a + * file. + * + * @param filename The name of the file containing the RSA private key. + * + * @param format The file format (ASN.1 or PEM). + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code use_rsa_private_key_file( + const std::string& filename, file_format format, + asio::error_code& ec) + { + return service_.use_rsa_private_key_file(impl_, filename, format, ec); + } + + /// Use the specified file to obtain the temporary Diffie-Hellman parameters. + /** + * This function is used to load Diffie-Hellman parameters into the context + * from a file. + * + * @param filename The name of the file containing the Diffie-Hellman + * parameters. The file must use the PEM format. + * + * @throws asio::system_error Thrown on failure. + */ + void use_tmp_dh_file(const std::string& filename) + { + asio::error_code ec; + service_.use_tmp_dh_file(impl_, filename, ec); + asio::detail::throw_error(ec); + } + + /// Use the specified file to obtain the temporary Diffie-Hellman parameters. + /** + * This function is used to load Diffie-Hellman parameters into the context + * from a file. + * + * @param filename The name of the file containing the Diffie-Hellman + * parameters. The file must use the PEM format. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code use_tmp_dh_file(const std::string& filename, + asio::error_code& ec) + { + return service_.use_tmp_dh_file(impl_, filename, ec); + } + + /// Set the password callback. + /** + * This function is used to specify a callback function to obtain password + * information about an encrypted key in PEM format. + * + * @param callback The function object to be used for obtaining the password. + * The function signature of the handler must be: + * @code std::string password_callback( + * std::size_t max_length, // The maximum size for a password. + * password_purpose purpose // Whether password is for reading or writing. + * ); @endcode + * The return value of the callback is a string containing the password. + * + * @throws asio::system_error Thrown on failure. + */ + template + void set_password_callback(PasswordCallback callback) + { + asio::error_code ec; + service_.set_password_callback(impl_, callback, ec); + asio::detail::throw_error(ec); + } + + /// Set the password callback. + /** + * This function is used to specify a callback function to obtain password + * information about an encrypted key in PEM format. + * + * @param callback The function object to be used for obtaining the password. + * The function signature of the handler must be: + * @code std::string password_callback( + * std::size_t max_length, // The maximum size for a password. + * password_purpose purpose // Whether password is for reading or writing. + * ); @endcode + * The return value of the callback is a string containing the password. + * + * @param ec Set to indicate what error occurred, if any. + */ + template + asio::error_code set_password_callback(PasswordCallback callback, + asio::error_code& ec) + { + return service_.set_password_callback(impl_, callback, ec); + } + +private: + /// The backend service implementation. + service_type& service_; + + /// The underlying native implementation. + impl_type impl_; +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_BASIC_CONTEXT_HPP diff --git a/libtorrent/include/asio/ssl/context.hpp b/libtorrent/include/asio/ssl/context.hpp new file mode 100644 index 000000000..86e249cbc --- /dev/null +++ b/libtorrent/include/asio/ssl/context.hpp @@ -0,0 +1,35 @@ +// +// context.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_CONTEXT_HPP +#define ASIO_SSL_CONTEXT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/ssl/basic_context.hpp" +#include "asio/ssl/context_service.hpp" + +namespace asio { +namespace ssl { + +/// Typedef for the typical usage of context. +typedef basic_context context; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_CONTEXT_HPP diff --git a/libtorrent/include/asio/ssl/context_base.hpp b/libtorrent/include/asio/ssl/context_base.hpp new file mode 100755 index 000000000..0811d8ba5 --- /dev/null +++ b/libtorrent/include/asio/ssl/context_base.hpp @@ -0,0 +1,164 @@ +// +// context_base.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_CONTEXT_BASE_HPP +#define ASIO_SSL_CONTEXT_BASE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/ssl/detail/openssl_types.hpp" + +namespace asio { +namespace ssl { + +/// The context_base class is used as a base for the basic_context class +/// template so that we have a common place to define various enums. +class context_base +{ +public: + /// Different methods supported by a context. + enum method + { + /// Generic SSL version 2. + sslv2, + + /// SSL version 2 client. + sslv2_client, + + /// SSL version 2 server. + sslv2_server, + + /// Generic SSL version 3. + sslv3, + + /// SSL version 3 client. + sslv3_client, + + /// SSL version 3 server. + sslv3_server, + + /// Generic TLS version 1. + tlsv1, + + /// TLS version 1 client. + tlsv1_client, + + /// TLS version 1 server. + tlsv1_server, + + /// Generic SSL/TLS. + sslv23, + + /// SSL/TLS client. + sslv23_client, + + /// SSL/TLS server. + sslv23_server + }; + + /// Bitmask type for SSL options. + typedef int options; + +#if defined(GENERATING_DOCUMENTATION) + /// Implement various bug workarounds. + static const int default_workarounds = implementation_defined; + + /// Always create a new key when using tmp_dh parameters. + static const int single_dh_use = implementation_defined; + + /// Disable SSL v2. + static const int no_sslv2 = implementation_defined; + + /// Disable SSL v3. + static const int no_sslv3 = implementation_defined; + + /// Disable TLS v1. + static const int no_tlsv1 = implementation_defined; +#else + BOOST_STATIC_CONSTANT(int, default_workarounds = SSL_OP_ALL); + BOOST_STATIC_CONSTANT(int, single_dh_use = SSL_OP_SINGLE_DH_USE); + BOOST_STATIC_CONSTANT(int, no_sslv2 = SSL_OP_NO_SSLv2); + BOOST_STATIC_CONSTANT(int, no_sslv3 = SSL_OP_NO_SSLv3); + BOOST_STATIC_CONSTANT(int, no_tlsv1 = SSL_OP_NO_TLSv1); +#endif + + /// File format types. + enum file_format + { + /// ASN.1 file. + asn1, + + /// PEM file. + pem + }; + + /// Bitmask type for peer verification. + typedef int verify_mode; + +#if defined(GENERATING_DOCUMENTATION) + /// No verification. + static const int verify_none = implementation_defined; + + /// Verify the peer. + static const int verify_peer = implementation_defined; + + /// Fail verification if the peer has no certificate. Ignored unless + /// verify_peer is set. + static const int verify_fail_if_no_peer_cert = implementation_defined; + + /// Do not request client certificate on renegotiation. Ignored unless + /// verify_peer is set. + static const int verify_client_once = implementation_defined; +#else + BOOST_STATIC_CONSTANT(int, verify_none = SSL_VERIFY_NONE); + BOOST_STATIC_CONSTANT(int, verify_peer = SSL_VERIFY_PEER); + BOOST_STATIC_CONSTANT(int, + verify_fail_if_no_peer_cert = SSL_VERIFY_FAIL_IF_NO_PEER_CERT); + BOOST_STATIC_CONSTANT(int, verify_client_once = SSL_VERIFY_CLIENT_ONCE); +#endif + + /// Purpose of PEM password. + enum password_purpose + { + /// The password is needed for reading/decryption. + for_reading, + + /// The password is needed for writing/encryption. + for_writing + }; + +protected: + /// Protected destructor to prevent deletion through this type. + ~context_base() + { + } + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +private: + // Workaround to enable the empty base optimisation with Borland C++. + char dummy_; +#endif +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_CONTEXT_BASE_HPP diff --git a/libtorrent/include/asio/ssl/context_service.hpp b/libtorrent/include/asio/ssl/context_service.hpp new file mode 100755 index 000000000..f0698a58f --- /dev/null +++ b/libtorrent/include/asio/ssl/context_service.hpp @@ -0,0 +1,175 @@ +// +// context_service.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_CONTEXT_SERVICE_HPP +#define ASIO_SSL_CONTEXT_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/ssl/context_base.hpp" +#include "asio/ssl/detail/openssl_context_service.hpp" + +namespace asio { +namespace ssl { + +/// Default service implementation for a context. +class context_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base +#endif +{ +private: + // The type of the platform-specific implementation. + typedef detail::openssl_context_service service_impl_type; + +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The type of the context. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined impl_type; +#else + typedef service_impl_type::impl_type impl_type; +#endif + + /// Constructor. + explicit context_service(asio::io_service& io_service) + : asio::detail::service_base(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Return a null context implementation. + impl_type null() const + { + return service_impl_.null(); + } + + /// Create a new context implementation. + void create(impl_type& impl, context_base::method m) + { + service_impl_.create(impl, m); + } + + /// Destroy a context implementation. + void destroy(impl_type& impl) + { + service_impl_.destroy(impl); + } + + /// Set options on the context. + asio::error_code set_options(impl_type& impl, + context_base::options o, asio::error_code& ec) + { + return service_impl_.set_options(impl, o, ec); + } + + /// Set peer verification mode. + asio::error_code set_verify_mode(impl_type& impl, + context_base::verify_mode v, asio::error_code& ec) + { + return service_impl_.set_verify_mode(impl, v, ec); + } + + /// Load a certification authority file for performing verification. + asio::error_code load_verify_file(impl_type& impl, + const std::string& filename, asio::error_code& ec) + { + return service_impl_.load_verify_file(impl, filename, ec); + } + + /// Add a directory containing certification authority files to be used for + /// performing verification. + asio::error_code add_verify_path(impl_type& impl, + const std::string& path, asio::error_code& ec) + { + return service_impl_.add_verify_path(impl, path, ec); + } + + /// Use a certificate from a file. + asio::error_code use_certificate_file(impl_type& impl, + const std::string& filename, context_base::file_format format, + asio::error_code& ec) + { + return service_impl_.use_certificate_file(impl, filename, format, ec); + } + + /// Use a certificate chain from a file. + asio::error_code use_certificate_chain_file(impl_type& impl, + const std::string& filename, asio::error_code& ec) + { + return service_impl_.use_certificate_chain_file(impl, filename, ec); + } + + /// Use a private key from a file. + asio::error_code use_private_key_file(impl_type& impl, + const std::string& filename, context_base::file_format format, + asio::error_code& ec) + { + return service_impl_.use_private_key_file(impl, filename, format, ec); + } + + /// Use an RSA private key from a file. + asio::error_code use_rsa_private_key_file(impl_type& impl, + const std::string& filename, context_base::file_format format, + asio::error_code& ec) + { + return service_impl_.use_rsa_private_key_file(impl, filename, format, ec); + } + + /// Use the specified file to obtain the temporary Diffie-Hellman parameters. + asio::error_code use_tmp_dh_file(impl_type& impl, + const std::string& filename, asio::error_code& ec) + { + return service_impl_.use_tmp_dh_file(impl, filename, ec); + } + + /// Set the password callback. + template + asio::error_code set_password_callback(impl_type& impl, + PasswordCallback callback, asio::error_code& ec) + { + return service_impl_.set_password_callback(impl, callback, ec); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_CONTEXT_SERVICE_HPP diff --git a/libtorrent/include/asio/ssl/detail/CVS/Entries b/libtorrent/include/asio/ssl/detail/CVS/Entries new file mode 100644 index 000000000..2168f4eb5 --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/CVS/Entries @@ -0,0 +1,6 @@ +/openssl_context_service.hpp/1.12/Fri Dec 29 02:01:24 2006// +/openssl_init.hpp/1.6/Thu Nov 9 02:14:51 2006// +/openssl_operation.hpp/1.14/Fri Nov 17 10:38:57 2006// +/openssl_stream_service.hpp/1.16/Fri Dec 29 02:01:25 2006// +/openssl_types.hpp/1.3/Mon Sep 11 11:26:33 2006// +D diff --git a/libtorrent/include/asio/ssl/detail/CVS/Repository b/libtorrent/include/asio/ssl/detail/CVS/Repository new file mode 100644 index 000000000..d25434301 --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/CVS/Repository @@ -0,0 +1 @@ +asio/include/asio/ssl/detail diff --git a/libtorrent/include/asio/ssl/detail/CVS/Root b/libtorrent/include/asio/ssl/detail/CVS/Root new file mode 100644 index 000000000..a7505d52a --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ssl/detail/openssl_context_service.hpp b/libtorrent/include/asio/ssl/detail/openssl_context_service.hpp new file mode 100755 index 000000000..96d610801 --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/openssl_context_service.hpp @@ -0,0 +1,379 @@ +// +// openssl_context_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_DETAIL_OPENSSL_CONTEXT_SERVICE_HPP +#define ASIO_SSL_DETAIL_OPENSSL_CONTEXT_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/ssl/context_base.hpp" +#include "asio/ssl/detail/openssl_init.hpp" +#include "asio/ssl/detail/openssl_types.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +class openssl_context_service + : public asio::detail::service_base +{ +public: + // The native type of the context. + typedef ::SSL_CTX* impl_type; + + // The type for the password callback function object. + typedef boost::function password_callback_type; + + // Constructor. + openssl_context_service(asio::io_service& io_service) + : asio::detail::service_base(io_service) + { + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + // Return a null context implementation. + static impl_type null() + { + return 0; + } + + // Create a new context implementation. + void create(impl_type& impl, context_base::method m) + { + ::SSL_METHOD* ssl_method = 0; + switch (m) + { + case context_base::sslv2: + ssl_method = ::SSLv2_method(); + break; + case context_base::sslv2_client: + ssl_method = ::SSLv2_client_method(); + break; + case context_base::sslv2_server: + ssl_method = ::SSLv2_server_method(); + break; + case context_base::sslv3: + ssl_method = ::SSLv3_method(); + break; + case context_base::sslv3_client: + ssl_method = ::SSLv3_client_method(); + break; + case context_base::sslv3_server: + ssl_method = ::SSLv3_server_method(); + break; + case context_base::tlsv1: + ssl_method = ::TLSv1_method(); + break; + case context_base::tlsv1_client: + ssl_method = ::TLSv1_client_method(); + break; + case context_base::tlsv1_server: + ssl_method = ::TLSv1_server_method(); + break; + case context_base::sslv23: + ssl_method = ::SSLv23_method(); + break; + case context_base::sslv23_client: + ssl_method = ::SSLv23_client_method(); + break; + case context_base::sslv23_server: + ssl_method = ::SSLv23_server_method(); + break; + default: + break; + } + impl = ::SSL_CTX_new(ssl_method); + } + + // Destroy a context implementation. + void destroy(impl_type& impl) + { + if (impl != null()) + { + if (impl->default_passwd_callback_userdata) + { + password_callback_type* callback = + static_cast( + impl->default_passwd_callback_userdata); + delete callback; + impl->default_passwd_callback_userdata = 0; + } + + ::SSL_CTX_free(impl); + impl = null(); + } + } + + // Set options on the context. + asio::error_code set_options(impl_type& impl, + context_base::options o, asio::error_code& ec) + { + ::SSL_CTX_set_options(impl, o); + + ec = asio::error_code(); + return ec; + } + + // Set peer verification mode. + asio::error_code set_verify_mode(impl_type& impl, + context_base::verify_mode v, asio::error_code& ec) + { + ::SSL_CTX_set_verify(impl, v, 0); + + ec = asio::error_code(); + return ec; + } + + // Load a certification authority file for performing verification. + asio::error_code load_verify_file(impl_type& impl, + const std::string& filename, asio::error_code& ec) + { + if (::SSL_CTX_load_verify_locations(impl, filename.c_str(), 0) != 1) + { + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Add a directory containing certification authority files to be used for + // performing verification. + asio::error_code add_verify_path(impl_type& impl, + const std::string& path, asio::error_code& ec) + { + if (::SSL_CTX_load_verify_locations(impl, 0, path.c_str()) != 1) + { + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Use a certificate from a file. + asio::error_code use_certificate_file(impl_type& impl, + const std::string& filename, context_base::file_format format, + asio::error_code& ec) + { + int file_type; + switch (format) + { + case context_base::asn1: + file_type = SSL_FILETYPE_ASN1; + break; + case context_base::pem: + file_type = SSL_FILETYPE_PEM; + break; + default: + { + ec = asio::error::invalid_argument; + return ec; + } + } + + if (::SSL_CTX_use_certificate_file(impl, filename.c_str(), file_type) != 1) + { + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Use a certificate chain from a file. + asio::error_code use_certificate_chain_file(impl_type& impl, + const std::string& filename, asio::error_code& ec) + { + if (::SSL_CTX_use_certificate_chain_file(impl, filename.c_str()) != 1) + { + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Use a private key from a file. + asio::error_code use_private_key_file(impl_type& impl, + const std::string& filename, context_base::file_format format, + asio::error_code& ec) + { + int file_type; + switch (format) + { + case context_base::asn1: + file_type = SSL_FILETYPE_ASN1; + break; + case context_base::pem: + file_type = SSL_FILETYPE_PEM; + break; + default: + { + ec = asio::error::invalid_argument; + return ec; + } + } + + if (::SSL_CTX_use_PrivateKey_file(impl, filename.c_str(), file_type) != 1) + { + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Use an RSA private key from a file. + asio::error_code use_rsa_private_key_file(impl_type& impl, + const std::string& filename, context_base::file_format format, + asio::error_code& ec) + { + int file_type; + switch (format) + { + case context_base::asn1: + file_type = SSL_FILETYPE_ASN1; + break; + case context_base::pem: + file_type = SSL_FILETYPE_PEM; + break; + default: + { + ec = asio::error::invalid_argument; + return ec; + } + } + + if (::SSL_CTX_use_RSAPrivateKey_file( + impl, filename.c_str(), file_type) != 1) + { + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Use the specified file to obtain the temporary Diffie-Hellman parameters. + asio::error_code use_tmp_dh_file(impl_type& impl, + const std::string& filename, asio::error_code& ec) + { + ::BIO* bio = ::BIO_new_file(filename.c_str(), "r"); + if (!bio) + { + ec = asio::error::invalid_argument; + return ec; + } + + ::DH* dh = ::PEM_read_bio_DHparams(bio, 0, 0, 0); + if (!dh) + { + ::BIO_free(bio); + ec = asio::error::invalid_argument; + return ec; + } + + ::BIO_free(bio); + int result = ::SSL_CTX_set_tmp_dh(impl, dh); + if (result != 1) + { + ::DH_free(dh); + ec = asio::error::invalid_argument; + return ec; + } + + ec = asio::error_code(); + return ec; + } + + static int password_callback(char* buf, int size, int purpose, void* data) + { + using namespace std; // For strncat and strlen. + + if (data) + { + password_callback_type* callback = + static_cast(data); + std::string passwd = (*callback)(static_cast(size), + purpose ? context_base::for_writing : context_base::for_reading); + *buf = '\0'; + strncat(buf, passwd.c_str(), size); + return strlen(buf); + } + + return 0; + } + + // Set the password callback. + template + asio::error_code set_password_callback(impl_type& impl, + Password_Callback callback, asio::error_code& ec) + { + // Allocate callback function object if not already present. + if (impl->default_passwd_callback_userdata) + { + password_callback_type* callback_function = + static_cast( + impl->default_passwd_callback_userdata); + *callback_function = callback; + } + else + { + password_callback_type* callback_function = + new password_callback_type(callback); + impl->default_passwd_callback_userdata = callback_function; + } + + // Set the password callback. + SSL_CTX_set_default_passwd_cb(impl, + &openssl_context_service::password_callback); + + ec = asio::error_code(); + return ec; + } + +private: + // Ensure openssl is initialised. + openssl_init<> init_; +}; + +} // namespace detail +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_DETAIL_OPENSSL_CONTEXT_SERVICE_HPP diff --git a/libtorrent/include/asio/ssl/detail/openssl_init.hpp b/libtorrent/include/asio/ssl/detail/openssl_init.hpp new file mode 100755 index 000000000..06fbf86fe --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/openssl_init.hpp @@ -0,0 +1,127 @@ +// +// openssl_init.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_DETAIL_OPENSSL_INIT_HPP +#define ASIO_SSL_DETAIL_OPENSSL_INIT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/mutex.hpp" +#include "asio/ssl/detail/openssl_types.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +template +class openssl_init + : private boost::noncopyable +{ +private: + // Structure to perform the actual initialisation. + class do_init + { + public: + do_init() + { + if (Do_Init) + { + ::SSL_library_init(); + ::SSL_load_error_strings(); + ::OpenSSL_add_ssl_algorithms(); + + mutexes_.resize(::CRYPTO_num_locks()); + for (size_t i = 0; i < mutexes_.size(); ++i) + mutexes_[i].reset(new asio::detail::mutex); + ::CRYPTO_set_locking_callback(&do_init::openssl_locking_func); + } + } + + ~do_init() + { + if (Do_Init) + { + ::CRYPTO_set_locking_callback(0); + ::ERR_free_strings(); + ::ERR_remove_state(0); + ::EVP_cleanup(); + ::CRYPTO_cleanup_all_ex_data(); + ::CONF_modules_unload(1); + ::ENGINE_cleanup(); + } + } + + // Helper function to manage a do_init singleton. The static instance of the + // openssl_init object ensures that this function is always called before + // main, and therefore before any other threads can get started. The do_init + // instance must be static in this function to ensure that it gets + // initialised before any other global objects try to use it. + static boost::shared_ptr instance() + { + static boost::shared_ptr init(new do_init); + return init; + } + + private: + static void openssl_locking_func(int mode, int n, + const char *file, int line) + { + if (mode & CRYPTO_LOCK) + instance()->mutexes_[n]->lock(); + else + instance()->mutexes_[n]->unlock(); + } + + // Mutexes to be used in locking callbacks. + std::vector > mutexes_; + }; + +public: + // Constructor. + openssl_init() + : ref_(do_init::instance()) + { + while (&instance_ == 0); // Ensure openssl_init::instance_ is linked in. + } + + // Destructor. + ~openssl_init() + { + } + +private: + // Instance to force initialisation of openssl at global scope. + static openssl_init instance_; + + // Reference to singleton do_init object to ensure that openssl does not get + // cleaned up until the last user has finished with it. + boost::shared_ptr ref_; +}; + +template +openssl_init openssl_init::instance_; + +} // namespace detail +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_DETAIL_OPENSSL_INIT_HPP diff --git a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp new file mode 100755 index 000000000..b7a564464 --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp @@ -0,0 +1,482 @@ +// +// openssl_operation.hpp +// ~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP +#define ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/buffer.hpp" +#include "asio/placeholders.hpp" +#include "asio/write.hpp" +#include "asio/detail/socket_ops.hpp" +#include "asio/ssl/detail/openssl_types.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +typedef boost::function ssl_primitive_func; +typedef boost::function + user_handler_func; + +// Network send_/recv buffer implementation +// +// +class net_buffer +{ + static const int NET_BUF_SIZE = 16*1024 + 256; // SSL record size + spare + + unsigned char buf_[NET_BUF_SIZE]; + unsigned char* data_start_; + unsigned char* data_end_; + +public: + net_buffer() + { + data_start_ = data_end_ = buf_; + } + unsigned char* get_unused_start() { return data_end_; } + unsigned char* get_data_start() { return data_start_; } + size_t get_unused_len() { return (NET_BUF_SIZE - (data_end_ - buf_)); } + size_t get_data_len() { return (data_end_ - data_start_); } + void data_added(size_t count) + { + data_end_ += count; + data_end_ = data_end_ > (buf_ + NET_BUF_SIZE)? + (buf_ + NET_BUF_SIZE): + data_end_; + } + void data_removed(size_t count) + { + data_start_ += count; + if (data_start_ >= data_end_) reset(); + } + void reset() { data_start_ = buf_; data_end_ = buf_; } + bool has_data() { return (data_start_ < data_end_); } +}; // class net_buffer + +// +// Operation class +// +// +template +class openssl_operation +{ +public: + + // Constructor for asynchronous operations + openssl_operation(ssl_primitive_func primitive, + Stream& socket, + net_buffer& recv_buf, + SSL* session, + BIO* ssl_bio, + user_handler_func handler + ) + : primitive_(primitive) + , user_handler_(handler) + , recv_buf_(recv_buf) + , socket_(socket) + , ssl_bio_(ssl_bio) + , session_(session) + { + write_ = boost::bind( + &openssl_operation::do_async_write, + this, boost::arg<1>(), boost::arg<2>() + ); + handler_= boost::bind( + &openssl_operation::async_user_handler, + this, boost::arg<1>(), boost::arg<2>() + ); + } + + // Constructor for synchronous operations + openssl_operation(ssl_primitive_func primitive, + Stream& socket, + net_buffer& recv_buf, + SSL* session, + BIO* ssl_bio) + : primitive_(primitive) + , recv_buf_(recv_buf) + , socket_(socket) + , ssl_bio_(ssl_bio) + , session_(session) + { + write_ = boost::bind( + &openssl_operation::do_sync_write, + this, boost::arg<1>(), boost::arg<2>() + ); + handler_ = boost::bind( + &openssl_operation::sync_user_handler, + this, boost::arg<1>(), boost::arg<2>() + ); + } + + // Start operation + // In case of asynchronous it returns 0, in sync mode returns success code + // or throws an error... + int start() + { + int rc = primitive_( session_ ); + int sys_error_code = ERR_get_error(); + bool is_operation_done = (rc > 0); + // For connect/accept/shutdown, the operation + // is done, when return code is 1 + // for write, it is done, when is retcode > 0 + // for read, is is done when retcode > 0 + + int error_code = !is_operation_done ? + ::SSL_get_error( session_, rc ) : + 0; + bool is_read_needed = (error_code == SSL_ERROR_WANT_READ); + bool is_write_needed = (error_code == SSL_ERROR_WANT_WRITE || + ::BIO_ctrl_pending( ssl_bio_ )); + bool is_shut_down_received = + ((::SSL_get_shutdown( session_ ) & SSL_RECEIVED_SHUTDOWN) == + SSL_RECEIVED_SHUTDOWN); + bool is_shut_down_sent = + ((::SSL_get_shutdown( session_ ) & SSL_SENT_SHUTDOWN) == + SSL_SENT_SHUTDOWN); + + if (is_shut_down_sent && is_shut_down_received && is_operation_done) + // SSL connection is shut down cleanly + return handler_(asio::error_code(), 1); + + if (is_shut_down_received && !is_write_needed) + return handler_(asio::error::eof, 0); + + if (is_shut_down_received) + // Shutdown has been requested, while we were reading or writing... + // abort our action... + return handler_(asio::error::shut_down, 0); + + if (!is_operation_done && !is_read_needed && !is_write_needed + && !is_shut_down_sent) + { + // The operation has failed... It is not completed and does + // not want network communication nor does want to send shutdown out... + if (error_code == SSL_ERROR_SYSCALL) + { + return handler_(asio::error_code( + sys_error_code, asio::native_ecat), rc); + } + else + { + return handler_(asio::error_code( + error_code, asio::ssl_ecat), rc); + } + } + + if (!is_operation_done && !is_write_needed) + { + // We may have left over data that we can pass to SSL immediately + if (recv_buf_.get_data_len() > 0) + { + // Pass the buffered data to SSL + int written = ::BIO_write + ( + ssl_bio_, + recv_buf_.get_data_start(), + recv_buf_.get_data_len() + ); + + if (written > 0) + { + recv_buf_.data_removed(written); + } + else if (written < 0) + { + if (!BIO_should_retry(ssl_bio_)) + { + // Some serios error with BIO.... + return handler_(asio::error::no_recovery, 0); + } + } + + return start(); + } + } + + // Continue with operation, flush any SSL data out to network... + return write_(is_operation_done, rc); + } + +// Private implementation +private: + typedef boost::function + int_handler_func; + typedef boost::function write_func; + + ssl_primitive_func primitive_; + user_handler_func user_handler_; + write_func write_; + int_handler_func handler_; + + net_buffer send_buf_; // buffers for network IO + + // The recv buffer is owned by the stream, not the operation, since there can + // be left over bytes after passing the data up to the application, and these + // bytes need to be kept around for the next read operation issued by the + // application. + net_buffer& recv_buf_; + + Stream& socket_; + BIO* ssl_bio_; + SSL* session_; + + // + int sync_user_handler(const asio::error_code& error, int rc) + { + if (!error) + return rc; + + throw asio::system_error(error); + } + + int async_user_handler(const asio::error_code& error, int rc) + { + user_handler_(error, rc); + return 0; + } + + // Writes bytes asynchronously from SSL to NET + int do_async_write(bool is_operation_done, int rc) + { + int len = ::BIO_ctrl_pending( ssl_bio_ ); + if ( len ) + { + // There is something to write into net, do it... + len = (int)send_buf_.get_unused_len() > len? + len: + send_buf_.get_unused_len(); + + if (len == 0) + { + // In case our send buffer is full, we have just to wait until + // previous send to complete... + return 0; + } + + // Read outgoing data from bio + len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len); + + if (len > 0) + { + unsigned char *data_start = send_buf_.get_unused_start(); + send_buf_.data_added(len); + + asio::async_write + ( + socket_, + asio::buffer(data_start, len), + boost::bind + ( + &openssl_operation::async_write_handler, + this, + is_operation_done, + rc, + asio::placeholders::error, + asio::placeholders::bytes_transferred + ) + ); + + return 0; + } + else if (!BIO_should_retry(ssl_bio_)) + { + // Seems like fatal error + // reading from SSL BIO has failed... + handler_(asio::error::no_recovery, 0); + return 0; + } + } + + if (is_operation_done) + { + // Finish the operation, with success + handler_(asio::error_code(), rc); + return 0; + } + + // OPeration is not done and writing to net has been made... + // start reading... + do_async_read(); + + return 0; + } + + void async_write_handler(bool is_operation_done, int rc, + const asio::error_code& error, size_t bytes_sent) + { + if (!error) + { + // Remove data from send buffer + send_buf_.data_removed(bytes_sent); + + if (is_operation_done) + handler_(asio::error_code(), rc); + else + // Since the operation was not completed, try it again... + start(); + } + else + handler_(error, rc); + } + + void do_async_read() + { + // Wait for new data + socket_.async_read_some + ( + asio::buffer(recv_buf_.get_unused_start(), + recv_buf_.get_unused_len()), + boost::bind + ( + &openssl_operation::async_read_handler, + this, + asio::placeholders::error, + asio::placeholders::bytes_transferred + ) + ); + } + + void async_read_handler(const asio::error_code& error, + size_t bytes_recvd) + { + if (!error) + { + recv_buf_.data_added(bytes_recvd); + + // Pass the received data to SSL + int written = ::BIO_write + ( + ssl_bio_, + recv_buf_.get_data_start(), + recv_buf_.get_data_len() + ); + + if (written > 0) + { + recv_buf_.data_removed(written); + } + else if (written < 0) + { + if (!BIO_should_retry(ssl_bio_)) + { + // Some serios error with BIO.... + handler_(asio::error::no_recovery, 0); + return; + } + } + + // and try the SSL primitive again + start(); + } + else + { + // Error in network level... + // SSL can't continue either... + handler_(error, 0); + } + } + + // Syncronous functions... + int do_sync_write(bool is_operation_done, int rc) + { + int len = ::BIO_ctrl_pending( ssl_bio_ ); + if ( len ) + { + // There is something to write into net, do it... + len = (int)send_buf_.get_unused_len() > len? + len: + send_buf_.get_unused_len(); + + // Read outgoing data from bio + len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len); + + if (len > 0) + { + size_t sent_len = asio::write( + socket_, + asio::buffer(send_buf_.get_unused_start(), len) + ); + + send_buf_.data_added(len); + send_buf_.data_removed(sent_len); + } + else if (!BIO_should_retry(ssl_bio_)) + { + // Seems like fatal error + // reading from SSL BIO has failed... + throw asio::system_error(asio::error::no_recovery); + } + } + + if (is_operation_done) + // Finish the operation, with success + return rc; + + // Operation is not finished, read data from net... + return do_sync_read(); + } + + int do_sync_read() + { + size_t len = socket_.read_some + ( + asio::buffer(recv_buf_.get_unused_start(), + recv_buf_.get_unused_len()) + ); + + // Write data to ssl + recv_buf_.data_added(len); + + // Pass the received data to SSL + int written = ::BIO_write + ( + ssl_bio_, + recv_buf_.get_data_start(), + recv_buf_.get_data_len() + ); + + if (written > 0) + { + recv_buf_.data_removed(written); + } + else if (written < 0) + { + if (!BIO_should_retry(ssl_bio_)) + { + // Some serios error with BIO.... + throw asio::system_error(asio::error::no_recovery); + } + } + + // Try the operation again + return start(); + } +}; // class openssl_operation + +} // namespace detail +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP diff --git a/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp b/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp new file mode 100644 index 000000000..d90b588ef --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp @@ -0,0 +1,504 @@ +// +// stream_service.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_DETAIL_OPENSSL_STREAM_SERVICE_HPP +#define ASIO_SSL_DETAIL_OPENSSL_STREAM_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/ssl/basic_context.hpp" +#include "asio/ssl/stream_base.hpp" +#include "asio/ssl/detail/openssl_operation.hpp" +#include "asio/ssl/detail/openssl_types.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +class openssl_stream_service + : public asio::detail::service_base +{ +private: + //Base handler for asyncrhonous operations + template + class base_handler + { + public: + typedef boost::function< + void (const asio::error_code&, size_t)> func_t; + + base_handler(asio::io_service& io_service) + : op_(NULL) + , io_service_(io_service) + , work_(io_service) + {} + + void do_func(const asio::error_code& error, size_t size) + { + func_(error, size); + } + + void set_operation(openssl_operation* op) { op_ = op; } + void set_func(func_t func) { func_ = func; } + + ~base_handler() + { + delete op_; + } + + private: + func_t func_; + openssl_operation* op_; + asio::io_service& io_service_; + asio::io_service::work work_; + }; // class base_handler + + // Handler for asynchronous IO (write/read) operations + template + class io_handler + : public base_handler + { + public: + io_handler(Handler handler, asio::io_service& io_service) + : base_handler(io_service) + , handler_(handler) + { + set_func(boost::bind( + &io_handler::handler_impl, + this, boost::arg<1>(), boost::arg<2>() )); + } + + private: + Handler handler_; + void handler_impl(const asio::error_code& error, size_t size) + { + handler_(error, size); + delete this; + } + }; // class io_handler + + // Handler for asyncrhonous handshake (connect, accept) functions + template + class handshake_handler + : public base_handler + { + public: + handshake_handler(Handler handler, asio::io_service& io_service) + : base_handler(io_service) + , handler_(handler) + { + set_func(boost::bind( + &handshake_handler::handler_impl, + this, boost::arg<1>(), boost::arg<2>() )); + } + + private: + Handler handler_; + void handler_impl(const asio::error_code& error, size_t) + { + handler_(error); + delete this; + } + + }; // class handshake_handler + + // Handler for asyncrhonous shutdown + template + class shutdown_handler + : public base_handler + { + public: + shutdown_handler(Handler handler, asio::io_service& io_service) + : base_handler(io_service), + handler_(handler) + { + set_func(boost::bind( + &shutdown_handler::handler_impl, + this, boost::arg<1>(), boost::arg<2>() )); + } + + private: + Handler handler_; + void handler_impl(const asio::error_code& error, size_t) + { + handler_(error); + delete this; + } + }; // class shutdown_handler + +public: + // The implementation type. + typedef struct impl_struct + { + ::SSL* ssl; + ::BIO* ext_bio; + net_buffer recv_buf; + } * impl_type; + + // Construct a new stream socket service for the specified io_service. + explicit openssl_stream_service(asio::io_service& io_service) + : asio::detail::service_base(io_service) + { + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + // Return a null stream implementation. + impl_type null() const + { + return 0; + } + + // Create a new stream implementation. + template + void create(impl_type& impl, Stream& next_layer, + basic_context& context) + { + impl = new impl_struct; + impl->ssl = ::SSL_new(context.impl()); + ::SSL_set_mode(impl->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + ::SSL_set_mode(impl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + ::BIO* int_bio = 0; + impl->ext_bio = 0; + ::BIO_new_bio_pair(&int_bio, 8192, &impl->ext_bio, 8192); + ::SSL_set_bio(impl->ssl, int_bio, int_bio); + } + + // Destroy a stream implementation. + template + void destroy(impl_type& impl, Stream& next_layer) + { + if (impl != 0) + { + ::BIO_free(impl->ext_bio); + ::SSL_free(impl->ssl); + delete impl; + impl = 0; + } + } + + // Perform SSL handshaking. + template + asio::error_code handshake(impl_type& impl, Stream& next_layer, + stream_base::handshake_type type, asio::error_code& ec) + { + try + { + openssl_operation op( + type == stream_base::client ? + &ssl_wrap::SSL_connect: + &ssl_wrap::SSL_accept, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio); + op.start(); + } + catch (asio::system_error& e) + { + ec = e.code(); + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Start an asynchronous SSL handshake. + template + void async_handshake(impl_type& impl, Stream& next_layer, + stream_base::handshake_type type, Handler handler) + { + typedef handshake_handler connect_handler; + + connect_handler* local_handler = + new connect_handler(handler, io_service()); + + openssl_operation* op = new openssl_operation + ( + type == stream_base::client ? + &ssl_wrap::SSL_connect: + &ssl_wrap::SSL_accept, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio, + boost::bind + ( + &base_handler::do_func, + local_handler, + boost::arg<1>(), + boost::arg<2>() + ) + ); + local_handler->set_operation(op); + + io_service().post(boost::bind(&openssl_operation::start, op)); + } + + // Shut down SSL on the stream. + template + asio::error_code shutdown(impl_type& impl, Stream& next_layer, + asio::error_code& ec) + { + try + { + openssl_operation op( + &ssl_wrap::SSL_shutdown, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio); + op.start(); + } + catch (asio::system_error& e) + { + ec = e.code(); + return ec; + } + + ec = asio::error_code(); + return ec; + } + + // Asynchronously shut down SSL on the stream. + template + void async_shutdown(impl_type& impl, Stream& next_layer, Handler handler) + { + typedef shutdown_handler disconnect_handler; + + disconnect_handler* local_handler = + new disconnect_handler(handler, io_service()); + + openssl_operation* op = new openssl_operation + ( + &ssl_wrap::SSL_shutdown, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio, + boost::bind + ( + &base_handler::do_func, + local_handler, + boost::arg<1>(), + boost::arg<2>() + ) + ); + local_handler->set_operation(op); + + io_service().post(boost::bind(&openssl_operation::start, op)); + } + + // Write some data to the stream. + template + std::size_t write_some(impl_type& impl, Stream& next_layer, + const Const_Buffers& buffers, asio::error_code& ec) + { + size_t bytes_transferred = 0; + try + { + boost::function send_func = + boost::bind(&::SSL_write, boost::arg<1>(), + asio::buffer_cast(*buffers.begin()), + static_cast(asio::buffer_size(*buffers.begin()))); + openssl_operation op( + send_func, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio + ); + bytes_transferred = static_cast(op.start()); + } + catch (asio::system_error& e) + { + ec = e.code(); + return 0; + } + + ec = asio::error_code(); + return bytes_transferred; + } + + // Start an asynchronous write. + template + void async_write_some(impl_type& impl, Stream& next_layer, + const Const_Buffers& buffers, Handler handler) + { + typedef io_handler send_handler; + + send_handler* local_handler = new send_handler(handler, io_service()); + + boost::function send_func = + boost::bind(&::SSL_write, boost::arg<1>(), + asio::buffer_cast(*buffers.begin()), + static_cast(asio::buffer_size(*buffers.begin()))); + + openssl_operation* op = new openssl_operation + ( + send_func, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio, + boost::bind + ( + &base_handler::do_func, + local_handler, + boost::arg<1>(), + boost::arg<2>() + ) + ); + local_handler->set_operation(op); + + io_service().post(boost::bind(&openssl_operation::start, op)); + } + + // Read some data from the stream. + template + std::size_t read_some(impl_type& impl, Stream& next_layer, + const Mutable_Buffers& buffers, asio::error_code& ec) + { + size_t bytes_transferred = 0; + try + { + boost::function recv_func = + boost::bind(&::SSL_read, boost::arg<1>(), + asio::buffer_cast(*buffers.begin()), + asio::buffer_size(*buffers.begin())); + openssl_operation op(recv_func, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio + ); + + bytes_transferred = static_cast(op.start()); + } + catch (asio::system_error& e) + { + ec = e.code(); + return 0; + } + + ec = asio::error_code(); + return bytes_transferred; + } + + // Start an asynchronous read. + template + void async_read_some(impl_type& impl, Stream& next_layer, + const Mutable_Buffers& buffers, Handler handler) + { + typedef io_handler recv_handler; + + recv_handler* local_handler = new recv_handler(handler, io_service()); + + boost::function recv_func = + boost::bind(&::SSL_read, boost::arg<1>(), + asio::buffer_cast(*buffers.begin()), + asio::buffer_size(*buffers.begin())); + + openssl_operation* op = new openssl_operation + ( + recv_func, + next_layer, + impl->recv_buf, + impl->ssl, + impl->ext_bio, + boost::bind + ( + &base_handler::do_func, + local_handler, + boost::arg<1>(), + boost::arg<2>() + ) + ); + local_handler->set_operation(op); + + io_service().post(boost::bind(&openssl_operation::start, op)); + } + + // Peek at the incoming data on the stream. + template + std::size_t peek(impl_type& impl, Stream& next_layer, + const Mutable_Buffers& buffers, asio::error_code& ec) + { + ec = asio::error_code(); + return 0; + } + + // Determine the amount of data that may be read without blocking. + template + std::size_t in_avail(impl_type& impl, Stream& next_layer, + asio::error_code& ec) + { + ec = asio::error_code(); + return 0; + } + +private: + typedef asio::detail::mutex mutex_type; + + template + struct ssl_wrap + { + static Mutex ssl_mutex_; + + static int SSL_accept(SSL *ssl) + { + typename Mutex::scoped_lock lock(ssl_mutex_); + return ::SSL_accept(ssl); + } + + static int SSL_connect(SSL *ssl) + { + typename Mutex::scoped_lock lock(ssl_mutex_); + return ::SSL_connect(ssl); + } + + static int SSL_shutdown(SSL *ssl) + { + typename Mutex::scoped_lock lock(ssl_mutex_); + return ::SSL_shutdown(ssl); + } + }; +}; + +template +Mutex openssl_stream_service::ssl_wrap::ssl_mutex_; + +} // namespace detail +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_DETAIL_OPENSSL_STREAM_SERVICE_HPP diff --git a/libtorrent/include/asio/ssl/detail/openssl_types.hpp b/libtorrent/include/asio/ssl/detail/openssl_types.hpp new file mode 100755 index 000000000..d193b3a0c --- /dev/null +++ b/libtorrent/include/asio/ssl/detail/openssl_types.hpp @@ -0,0 +1,29 @@ +// +// openssl_types.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_DETAIL_OPENSSL_TYPES_HPP +#define ASIO_SSL_DETAIL_OPENSSL_TYPES_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_DETAIL_OPENSSL_TYPES_HPP diff --git a/libtorrent/include/asio/ssl/stream.hpp b/libtorrent/include/asio/ssl/stream.hpp new file mode 100644 index 000000000..a6af16101 --- /dev/null +++ b/libtorrent/include/asio/ssl/stream.hpp @@ -0,0 +1,490 @@ +// +// stream.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_STREAM_HPP +#define ASIO_SSL_STREAM_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/ssl/basic_context.hpp" +#include "asio/ssl/stream_base.hpp" +#include "asio/ssl/stream_service.hpp" +#include "asio/detail/throw_error.hpp" + +namespace asio { +namespace ssl { + +/// Provides stream-oriented functionality using SSL. +/** + * The stream class template provides asynchronous and blocking stream-oriented + * functionality using SSL. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Example + * To use the SSL stream template with a stream_socket, you would write: + * @code + * asio::io_service io_service; + * asio::ssl::context context(io_service, asio::ssl::context::sslv23); + * asio::ssl::stream sock(io_service, context); + * @endcode + * + * @par Concepts: + * Async_Object, Async_Read_Stream, Async_Write_Stream, Error_Source, Stream, + * Sync_Read_Stream, Sync_Write_Stream. + */ +template +class stream + : public stream_base, + private boost::noncopyable +{ +public: + /// The type of the next layer. + typedef typename boost::remove_reference::type next_layer_type; + + /// The type of the lowest layer. + typedef typename next_layer_type::lowest_layer_type lowest_layer_type; + + /// The type of the service that will be used to provide stream operations. + typedef Service service_type; + + /// The native implementation type of the stream. + typedef typename service_type::impl_type impl_type; + + /// Construct a stream. + /** + * This constructor creates a stream and initialises the underlying stream + * object. + * + * @param arg The argument to be passed to initialise the underlying stream. + * + * @param context The SSL context to be used for the stream. + */ + template + explicit stream(Arg& arg, basic_context& context) + : next_layer_(arg), + service_(asio::use_service(next_layer_.io_service())), + impl_(service_.null()) + { + service_.create(impl_, next_layer_, context); + } + + /// Destructor. + ~stream() + { + service_.destroy(impl_, next_layer_); + } + + /// Get the io_service associated with the object. + /** + * This function may be used to obtain the io_service object that the stream + * uses to dispatch handlers for asynchronous operations. + * + * @return A reference to the io_service object that stream will use to + * dispatch handlers. Ownership is not transferred to the caller. + */ + asio::io_service& io_service() + { + return next_layer_.io_service(); + } + + /// Get a reference to the next layer. + /** + * This function returns a reference to the next layer in a stack of stream + * layers. + * + * @return A reference to the next layer in the stack of stream layers. + * Ownership is not transferred to the caller. + */ + next_layer_type& next_layer() + { + return next_layer_; + } + + /// Get a reference to the lowest layer. + /** + * This function returns a reference to the lowest layer in a stack of + * stream layers. + * + * @return A reference to the lowest layer in the stack of stream layers. + * Ownership is not transferred to the caller. + */ + lowest_layer_type& lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /// Get the underlying implementation in the native type. + /** + * This function may be used to obtain the underlying implementation of the + * context. This is intended to allow access to stream functionality that is + * not otherwise provided. + */ + impl_type impl() + { + return impl_; + } + + /// Perform SSL handshaking. + /** + * This function is used to perform SSL handshaking on the stream. The + * function call will block until handshaking is complete or an error occurs. + * + * @param type The type of handshaking to be performed, i.e. as a client or as + * a server. + * + * @throws asio::system_error Thrown on failure. + */ + void handshake(handshake_type type) + { + asio::error_code ec; + service_.handshake(impl_, next_layer_, type, ec); + asio::detail::throw_error(ec); + } + + /// Perform SSL handshaking. + /** + * This function is used to perform SSL handshaking on the stream. The + * function call will block until handshaking is complete or an error occurs. + * + * @param type The type of handshaking to be performed, i.e. as a client or as + * a server. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code handshake(handshake_type type, + asio::error_code& ec) + { + return service_.handshake(impl_, next_layer_, type, ec); + } + + /// Start an asynchronous SSL handshake. + /** + * This function is used to asynchronously perform an SSL handshake on the + * stream. This function call always returns immediately. + * + * @param type The type of handshaking to be performed, i.e. as a client or as + * a server. + * + * @param handler The handler to be called when the handshake operation + * completes. Copies will be made of the handler as required. The equivalent + * function signature of the handler must be: + * @code void handler( + * const asio::error_code& error // Result of operation. + * ); @endcode + */ + template + void async_handshake(handshake_type type, HandshakeHandler handler) + { + service_.async_handshake(impl_, next_layer_, type, handler); + } + + /// Shut down SSL on the stream. + /** + * This function is used to shut down SSL on the stream. The function call + * will block until SSL has been shut down or an error occurs. + * + * @throws asio::system_error Thrown on failure. + */ + void shutdown() + { + asio::error_code ec; + service_.shutdown(impl_, next_layer_, ec); + asio::detail::throw_error(ec); + } + + /// Shut down SSL on the stream. + /** + * This function is used to shut down SSL on the stream. The function call + * will block until SSL has been shut down or an error occurs. + * + * @param ec Set to indicate what error occurred, if any. + */ + asio::error_code shutdown(asio::error_code& ec) + { + return service_.shutdown(impl_, next_layer_, ec); + } + + /// Asynchronously shut down SSL on the stream. + /** + * This function is used to asynchronously shut down SSL on the stream. This + * function call always returns immediately. + * + * @param handler The handler to be called when the handshake operation + * completes. Copies will be made of the handler as required. The equivalent + * function signature of the handler must be: + * @code void handler( + * const asio::error_code& error // Result of operation. + * ); @endcode + */ + template + void async_shutdown(ShutdownHandler handler) + { + service_.async_shutdown(impl_, next_layer_, handler); + } + + /// Write some data to the stream. + /** + * This function is used to write data on the stream. The function call will + * block until one or more bytes of data has been written successfully, or + * until an error occurs. + * + * @param buffers The data to be written. + * + * @returns The number of bytes written. + * + * @throws asio::system_error Thrown on failure. + * + * @note The write_some operation may not transmit all of the data to the + * peer. Consider using the @ref write function if you need to ensure that all + * data is written before the blocking operation completes. + */ + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = service_.write_some(impl_, next_layer_, buffers, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Write some data to the stream. + /** + * This function is used to write data on the stream. The function call will + * block until one or more bytes of data has been written successfully, or + * until an error occurs. + * + * @param buffers The data to be written to the stream. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes written. Returns 0 if an error occurred. + * + * @note The write_some operation may not transmit all of the data to the + * peer. Consider using the @ref write function if you need to ensure that all + * data is written before the blocking operation completes. + */ + template + std::size_t write_some(const ConstBufferSequence& buffers, + asio::error_code& ec) + { + return service_.write_some(impl_, next_layer_, buffers, ec); + } + + /// Start an asynchronous write. + /** + * This function is used to asynchronously write one or more bytes of data to + * the stream. The function call always returns immediately. + * + * @param buffers The data to be written to the stream. Although the buffers + * object may be copied as necessary, ownership of the underlying buffers is + * retained by the caller, which must guarantee that they remain valid until + * the handler is called. + * + * @param handler The handler to be called when the write operation completes. + * Copies will be made of the handler as required. The equivalent function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes written. + * ); @endcode + * + * @note The async_write_some operation may not transmit all of the data to + * the peer. Consider using the @ref async_write function if you need to + * ensure that all data is written before the blocking operation completes. + */ + template + void async_write_some(const ConstBufferSequence& buffers, + WriteHandler handler) + { + service_.async_write_some(impl_, next_layer_, buffers, handler); + } + + /// Read some data from the stream. + /** + * This function is used to read data from the stream. The function call will + * block until one or more bytes of data has been read successfully, or until + * an error occurs. + * + * @param buffers The buffers into which the data will be read. + * + * @returns The number of bytes read. + * + * @throws asio::system_error Thrown on failure. + * + * @note The read_some operation may not read all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that the + * requested amount of data is read before the blocking operation completes. + */ + template + std::size_t read_some(const MutableBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = service_.read_some(impl_, next_layer_, buffers, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Read some data from the stream. + /** + * This function is used to read data from the stream. The function call will + * block until one or more bytes of data has been read successfully, or until + * an error occurs. + * + * @param buffers The buffers into which the data will be read. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes read. Returns 0 if an error occurred. + * + * @note The read_some operation may not read all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that the + * requested amount of data is read before the blocking operation completes. + */ + template + std::size_t read_some(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return service_.read_some(impl_, next_layer_, buffers, ec); + } + + /// Start an asynchronous read. + /** + * This function is used to asynchronously read one or more bytes of data from + * the stream. The function call always returns immediately. + * + * @param buffers The buffers into which the data will be read. Although the + * buffers object may be copied as necessary, ownership of the underlying + * buffers is retained by the caller, which must guarantee that they remain + * valid until the handler is called. + * + * @param handler The handler to be called when the read operation completes. + * Copies will be made of the handler as required. The equivalent function + * signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes read. + * ); @endcode + * + * @note The async_read_some operation may not read all of the requested + * number of bytes. Consider using the @ref async_read function if you need to + * ensure that the requested amount of data is read before the asynchronous + * operation completes. + */ + template + void async_read_some(const MutableBufferSequence& buffers, + ReadHandler handler) + { + service_.async_read_some(impl_, next_layer_, buffers, handler); + } + + /// Peek at the incoming data on the stream. + /** + * This function is used to peek at the incoming data on the stream, without + * removing it from the input queue. The function call will block until data + * has been read successfully or an error occurs. + * + * @param buffers The buffers into which the data will be read. + * + * @returns The number of bytes read. + * + * @throws asio::system_error Thrown on failure. + */ + template + std::size_t peek(const MutableBufferSequence& buffers) + { + asio::error_code ec; + std::size_t s = service_.peek(impl_, next_layer_, buffers, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Peek at the incoming data on the stream. + /** + * This function is used to peek at the incoming data on the stream, withoutxi + * removing it from the input queue. The function call will block until data + * has been read successfully or an error occurs. + * + * @param buffers The buffers into which the data will be read. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes read. Returns 0 if an error occurred. + */ + template + std::size_t peek(const MutableBufferSequence& buffers, + asio::error_code& ec) + { + return service_.peek(impl_, next_layer_, buffers, ec); + } + + /// Determine the amount of data that may be read without blocking. + /** + * This function is used to determine the amount of data, in bytes, that may + * be read from the stream without blocking. + * + * @returns The number of bytes of data that can be read without blocking. + * + * @throws asio::system_error Thrown on failure. + */ + std::size_t in_avail() + { + asio::error_code ec; + std::size_t s = service_.in_avail(impl_, next_layer_, ec); + asio::detail::throw_error(ec); + return s; + } + + /// Determine the amount of data that may be read without blocking. + /** + * This function is used to determine the amount of data, in bytes, that may + * be read from the stream without blocking. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes of data that can be read without blocking. + */ + std::size_t in_avail(asio::error_code& ec) + { + return service_.in_avail(impl_, next_layer_, ec); + } + +private: + /// The next layer. + Stream next_layer_; + + /// The backend service implementation. + service_type& service_; + + /// The underlying native implementation. + impl_type impl_; +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_STREAM_HPP diff --git a/libtorrent/include/asio/ssl/stream_base.hpp b/libtorrent/include/asio/ssl/stream_base.hpp new file mode 100755 index 000000000..89c4b65a9 --- /dev/null +++ b/libtorrent/include/asio/ssl/stream_base.hpp @@ -0,0 +1,60 @@ +// +// stream_base.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_STREAM_BASE_HPP +#define ASIO_SSL_STREAM_BASE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace ssl { + +/// The stream_base class is used as a base for the asio::ssl::stream +/// class template so that we have a common place to define various enums. +class stream_base +{ +public: + /// Different handshake types. + enum handshake_type + { + /// Perform handshaking as a client. + client, + + /// Perform handshaking as a server. + server + }; + +protected: + /// Protected destructor to prevent deletion through this type. + ~stream_base() + { + } + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +private: + // Workaround to enable the empty base optimisation with Borland C++. + char dummy_; +#endif +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_STREAM_BASE_HPP diff --git a/libtorrent/include/asio/ssl/stream_service.hpp b/libtorrent/include/asio/ssl/stream_service.hpp new file mode 100644 index 000000000..d96e68aec --- /dev/null +++ b/libtorrent/include/asio/ssl/stream_service.hpp @@ -0,0 +1,186 @@ +// +// stream_service.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com +// Copyright (c) 2005 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SSL_STREAM_SERVICE_HPP +#define ASIO_SSL_STREAM_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/ssl/basic_context.hpp" +#include "asio/ssl/stream_base.hpp" +#include "asio/ssl/detail/openssl_stream_service.hpp" + +namespace asio { +namespace ssl { + +/// Default service implementation for an SSL stream. +class stream_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base +#endif +{ +private: + // The type of the platform-specific implementation. + typedef detail::openssl_stream_service service_impl_type; + +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The type of a stream implementation. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined impl_type; +#else + typedef service_impl_type::impl_type impl_type; +#endif + + /// Construct a new stream service for the specified io_service. + explicit stream_service(asio::io_service& io_service) + : asio::detail::service_base(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Return a null stream implementation. + impl_type null() const + { + return service_impl_.null(); + } + + /// Create a new stream implementation. + template + void create(impl_type& impl, Stream& next_layer, + basic_context& context) + { + service_impl_.create(impl, next_layer, context); + } + + /// Destroy a stream implementation. + template + void destroy(impl_type& impl, Stream& next_layer) + { + service_impl_.destroy(impl, next_layer); + } + + /// Perform SSL handshaking. + template + asio::error_code handshake(impl_type& impl, Stream& next_layer, + stream_base::handshake_type type, asio::error_code& ec) + { + return service_impl_.handshake(impl, next_layer, type, ec); + } + + /// Start an asynchronous SSL handshake. + template + void async_handshake(impl_type& impl, Stream& next_layer, + stream_base::handshake_type type, HandshakeHandler handler) + { + service_impl_.async_handshake(impl, next_layer, type, handler); + } + + /// Shut down SSL on the stream. + template + asio::error_code shutdown(impl_type& impl, Stream& next_layer, + asio::error_code& ec) + { + return service_impl_.shutdown(impl, next_layer, ec); + } + + /// Asynchronously shut down SSL on the stream. + template + void async_shutdown(impl_type& impl, Stream& next_layer, + ShutdownHandler handler) + { + service_impl_.async_shutdown(impl, next_layer, handler); + } + + /// Write some data to the stream. + template + std::size_t write_some(impl_type& impl, Stream& next_layer, + const ConstBufferSequence& buffers, asio::error_code& ec) + { + return service_impl_.write_some(impl, next_layer, buffers, ec); + } + + /// Start an asynchronous write. + template + void async_write_some(impl_type& impl, Stream& next_layer, + const ConstBufferSequence& buffers, WriteHandler handler) + { + service_impl_.async_write_some(impl, next_layer, buffers, handler); + } + + /// Read some data from the stream. + template + std::size_t read_some(impl_type& impl, Stream& next_layer, + const MutableBufferSequence& buffers, asio::error_code& ec) + { + return service_impl_.read_some(impl, next_layer, buffers, ec); + } + + /// Start an asynchronous read. + template + void async_read_some(impl_type& impl, Stream& next_layer, + const MutableBufferSequence& buffers, ReadHandler handler) + { + service_impl_.async_read_some(impl, next_layer, buffers, handler); + } + + /// Peek at the incoming data on the stream. + template + std::size_t peek(impl_type& impl, Stream& next_layer, + const MutableBufferSequence& buffers, asio::error_code& ec) + { + return service_impl_.peek(impl, next_layer, buffers, ec); + } + + /// Determine the amount of data that may be read without blocking. + template + std::size_t in_avail(impl_type& impl, Stream& next_layer, + asio::error_code& ec) + { + return service_impl_.in_avail(impl, next_layer, ec); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_STREAM_SERVICE_HPP diff --git a/libtorrent/include/asio/strand.hpp b/libtorrent/include/asio/strand.hpp new file mode 100644 index 000000000..d0869d95d --- /dev/null +++ b/libtorrent/include/asio/strand.hpp @@ -0,0 +1,172 @@ +// +// strand.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_STRAND_HPP +#define ASIO_STRAND_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/strand_service.hpp" +#include "asio/detail/wrapped_handler.hpp" + +namespace asio { + +/// Provides serialised handler execution. +/** + * The io_service::strand class provides the ability to post and dispatch + * handlers with the guarantee that none of those handlers will execute + * concurrently. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Safe. + * + * @par Concepts: + * Dispatcher. + */ +class io_service::strand +{ +public: + /// Constructor. + /** + * Constructs the strand. + * + * @param io_service The io_service object that the strand will use to + * dispatch handlers that are ready to be run. + */ + explicit strand(asio::io_service& io_service) + : service_(asio::use_service< + asio::detail::strand_service>(io_service)) + { + service_.construct(impl_); + } + + /// Destructor. + /** + * Destroys a strand. + * + * Handlers posted through the strand that have not yet been invoked will + * still be dispatched in a way that meets the guarantee of non-concurrency. + */ + ~strand() + { + service_.destroy(impl_); + } + + /// Get the io_service associated with the strand. + /** + * This function may be used to obtain the io_service object that the strand + * uses to dispatch handlers for asynchronous operations. + * + * @return A reference to the io_service object that the strand will use to + * dispatch handlers. Ownership is not transferred to the caller. + */ + asio::io_service& io_service() + { + return service_.io_service(); + } + + /// Request the strand to invoke the given handler. + /** + * This function is used to ask the strand to execute the given handler. + * + * The strand object guarantees that handlers posted or dispatched through + * the strand will not be executed concurrently. The handler may be executed + * inside this function if the guarantee can be met. If this function is + * called from within a handler that was posted or dispatched through the same + * strand, then the new handler will be executed immediately. + * + * The strand's guarantee is in addition to the guarantee provided by the + * underlying io_service. The io_service guarantees that the handler will only + * be called in a thread in which the io_service's run member function is + * currently being invoked. + * + * @param handler The handler to be called. The strand will make a copy of the + * handler object as required. The function signature of the handler must be: + * @code void handler(); @endcode + */ + template + void dispatch(Handler handler) + { + service_.dispatch(impl_, handler); + } + + /// Request the strand to invoke the given handler and return + /// immediately. + /** + * This function is used to ask the strand to execute the given handler, but + * without allowing the strand to call the handler from inside this function. + * + * The strand object guarantees that handlers posted or dispatched through + * the strand will not be executed concurrently. The strand's guarantee is in + * addition to the guarantee provided by the underlying io_service. The + * io_service guarantees that the handler will only be called in a thread in + * which the io_service's run member function is currently being invoked. + * + * @param handler The handler to be called. The strand will make a copy of the + * handler object as required. The function signature of the handler must be: + * @code void handler(); @endcode + */ + template + void post(Handler handler) + { + service_.post(impl_, handler); + } + + /// Create a new handler that automatically dispatches the wrapped handler + /// on the strand. + /** + * This function is used to create a new handler function object that, when + * invoked, will automatically pass the wrapped handler to the strand's + * dispatch function. + * + * @param handler The handler to be wrapped. The strand will make a copy of + * the handler object as required. The function signature of the handler must + * be: @code void handler(A1 a1, ... An an); @endcode + * + * @return A function object that, when invoked, passes the wrapped handler to + * the strand's dispatch function. Given a function object with the signature: + * @code R f(A1 a1, ... An an); @endcode + * If this function object is passed to the wrap function like so: + * @code strand.wrap(f); @endcode + * then the return value is a function object with the signature + * @code void g(A1 a1, ... An an); @endcode + * that, when invoked, executes code equivalent to: + * @code strand.dispatch(boost::bind(f, a1, ... an)); @endcode + */ + template +#if defined(GENERATING_DOCUMENTATION) + unspecified +#else + detail::wrapped_handler +#endif + wrap(Handler handler) + { + return detail::wrapped_handler(*this, handler); + } + +private: + asio::detail::strand_service& service_; + asio::detail::strand_service::implementation_type impl_; +}; + +/// Typedef for backwards compatibility. +typedef asio::io_service::strand strand; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_STRAND_HPP diff --git a/libtorrent/include/asio/stream_socket_service.hpp b/libtorrent/include/asio/stream_socket_service.hpp new file mode 100644 index 000000000..d7915aaf4 --- /dev/null +++ b/libtorrent/include/asio/stream_socket_service.hpp @@ -0,0 +1,283 @@ +// +// stream_socket_service.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_STREAM_SOCKET_SERVICE_HPP +#define ASIO_STREAM_SOCKET_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/detail/epoll_reactor.hpp" +#include "asio/detail/kqueue_reactor.hpp" +#include "asio/detail/select_reactor.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/win_iocp_socket_service.hpp" +#include "asio/detail/reactive_socket_service.hpp" + +namespace asio { + +/// Default service implementation for a stream socket. +template +class stream_socket_service +#if defined(GENERATING_DOCUMENTATION) + : public asio::io_service::service +#else + : public asio::detail::service_base > +#endif +{ +public: +#if defined(GENERATING_DOCUMENTATION) + /// The unique service identifier. + static asio::io_service::id id; +#endif + + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + +private: + // The type of the platform-specific implementation. +#if defined(ASIO_HAS_IOCP) + typedef detail::win_iocp_socket_service service_impl_type; +#elif defined(ASIO_HAS_EPOLL) + typedef detail::reactive_socket_service< + Protocol, detail::epoll_reactor > service_impl_type; +#elif defined(ASIO_HAS_KQUEUE) + typedef detail::reactive_socket_service< + Protocol, detail::kqueue_reactor > service_impl_type; +#else + typedef detail::reactive_socket_service< + Protocol, detail::select_reactor > service_impl_type; +#endif + +public: + /// The type of a stream socket implementation. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined implementation_type; +#else + typedef typename service_impl_type::implementation_type implementation_type; +#endif + + /// The native socket type. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined native_type; +#else + typedef typename service_impl_type::native_type native_type; +#endif + + /// Construct a new stream socket service for the specified io_service. + explicit stream_socket_service(asio::io_service& io_service) + : asio::detail::service_base< + stream_socket_service >(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Construct a new stream socket implementation. + void construct(implementation_type& impl) + { + service_impl_.construct(impl); + } + + /// Destroy a stream socket implementation. + void destroy(implementation_type& impl) + { + service_impl_.destroy(impl); + } + + /// Open a stream socket. + asio::error_code open(implementation_type& impl, + const protocol_type& protocol, asio::error_code& ec) + { + if (protocol.type() == SOCK_STREAM) + service_impl_.open(impl, protocol, ec); + else + ec = asio::error::invalid_argument; + return ec; + } + + /// Assign an existing native socket to a stream socket. + asio::error_code assign(implementation_type& impl, + const protocol_type& protocol, const native_type& native_socket, + asio::error_code& ec) + { + return service_impl_.assign(impl, protocol, native_socket, ec); + } + + /// Determine whether the socket is open. + bool is_open(const implementation_type& impl) const + { + return service_impl_.is_open(impl); + } + + /// Close a stream socket implementation. + asio::error_code close(implementation_type& impl, + asio::error_code& ec) + { + return service_impl_.close(impl, ec); + } + + /// Get the native socket implementation. + native_type native(implementation_type& impl) + { + return service_impl_.native(impl); + } + + /// Cancel all asynchronous operations associated with the socket. + asio::error_code cancel(implementation_type& impl, + asio::error_code& ec) + { + return service_impl_.cancel(impl, ec); + } + + /// Determine whether the socket is at the out-of-band data mark. + bool at_mark(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.at_mark(impl, ec); + } + + /// Determine the number of bytes available for reading. + std::size_t available(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.available(impl, ec); + } + + /// Bind the stream socket to the specified local endpoint. + asio::error_code bind(implementation_type& impl, + const endpoint_type& endpoint, asio::error_code& ec) + { + return service_impl_.bind(impl, endpoint, ec); + } + + /// Connect the stream socket to the specified endpoint. + asio::error_code connect(implementation_type& impl, + const endpoint_type& peer_endpoint, asio::error_code& ec) + { + return service_impl_.connect(impl, peer_endpoint, ec); + } + + /// Start an asynchronous connect. + template + void async_connect(implementation_type& impl, + const endpoint_type& peer_endpoint, ConnectHandler handler) + { + service_impl_.async_connect(impl, peer_endpoint, handler); + } + + /// Set a socket option. + template + asio::error_code set_option(implementation_type& impl, + const SettableSocketOption& option, asio::error_code& ec) + { + return service_impl_.set_option(impl, option, ec); + } + + /// Get a socket option. + template + asio::error_code get_option(const implementation_type& impl, + GettableSocketOption& option, asio::error_code& ec) const + { + return service_impl_.get_option(impl, option, ec); + } + + /// Perform an IO control command on the socket. + template + asio::error_code io_control(implementation_type& impl, + IoControlCommand& command, asio::error_code& ec) + { + return service_impl_.io_control(impl, command, ec); + } + + /// Get the local endpoint. + endpoint_type local_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.local_endpoint(impl, ec); + } + + /// Get the remote endpoint. + endpoint_type remote_endpoint(const implementation_type& impl, + asio::error_code& ec) const + { + return service_impl_.remote_endpoint(impl, ec); + } + + /// Disable sends or receives on the socket. + asio::error_code shutdown(implementation_type& impl, + socket_base::shutdown_type what, asio::error_code& ec) + { + return service_impl_.shutdown(impl, what, ec); + } + + /// Send the given data to the peer. + template + std::size_t send(implementation_type& impl, + const ConstBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return service_impl_.send(impl, buffers, flags, ec); + } + + /// Start an asynchronous send. + template + void async_send(implementation_type& impl, + const ConstBufferSequence& buffers, + socket_base::message_flags flags, WriteHandler handler) + { + service_impl_.async_send(impl, buffers, flags, handler); + } + + /// Receive some data from the peer. + template + std::size_t receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, asio::error_code& ec) + { + return service_impl_.receive(impl, buffers, flags, ec); + } + + /// Start an asynchronous receive. + template + void async_receive(implementation_type& impl, + const MutableBufferSequence& buffers, + socket_base::message_flags flags, ReadHandler handler) + { + service_impl_.async_receive(impl, buffers, flags, handler); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_STREAM_SOCKET_SERVICE_HPP diff --git a/libtorrent/include/asio/streambuf.hpp b/libtorrent/include/asio/streambuf.hpp new file mode 100644 index 000000000..fdcc94a00 --- /dev/null +++ b/libtorrent/include/asio/streambuf.hpp @@ -0,0 +1,31 @@ +// +// streambuf.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_STREAMBUF_HPP +#define ASIO_STREAMBUF_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_streambuf.hpp" + +namespace asio { + +/// Typedef for the typical usage of basic_streambuf. +typedef basic_streambuf<> streambuf; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_STREAMBUF_HPP diff --git a/libtorrent/include/asio/system_error.hpp b/libtorrent/include/asio/system_error.hpp new file mode 100644 index 000000000..95f9f540e --- /dev/null +++ b/libtorrent/include/asio/system_error.hpp @@ -0,0 +1,117 @@ +// +// system_error.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SYSTEM_ERROR_HPP +#define ASIO_SYSTEM_ERROR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error_code.hpp" + +namespace asio { + +/// The system_error class is used to represent system conditions that +/// prevent the library from operating correctly. +class system_error + : public std::exception +{ +public: + /// Construct with an error code. + system_error(const error_code& code) + : code_(code), + context_() + { + } + + /// Construct with an error code and context. + system_error(const error_code& code, const std::string& context) + : code_(code), + context_(context) + { + } + + /// Copy constructor. + system_error(const system_error& other) + : std::exception(other), + code_(other.code_), + context_(other.context_), + what_() + { + } + + /// Destructor. + virtual ~system_error() throw () + { + } + + /// Assignment operator. + system_error& operator=(const system_error& e) + { + context_ = e.context_; + code_ = e.code_; + what_.reset(); + return *this; + } + + /// Get a string representation of the exception. + virtual const char* what() const throw () + { + try + { + if (!what_) + { + std::string tmp(context_); + if (tmp.length()) + tmp += ": "; + tmp += code_.message(); + what_.reset(new std::string(tmp)); + } + return what_->c_str(); + } + catch (std::exception&) + { + return "system_error"; + } + } + + /// Get the error code associated with the exception. + error_code code() const + { + return code_; + } + +private: + // The code associated with the error. + error_code code_; + + // The context associated with the error. + std::string context_; + + // The string representation of the error. + mutable boost::scoped_ptr what_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SYSTEM_ERROR_HPP diff --git a/libtorrent/include/asio/thread.hpp b/libtorrent/include/asio/thread.hpp new file mode 100644 index 000000000..b8bc81bab --- /dev/null +++ b/libtorrent/include/asio/thread.hpp @@ -0,0 +1,91 @@ +// +// thread.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_THREAD_HPP +#define ASIO_THREAD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/thread.hpp" + +namespace asio { + +/// A simple abstraction for starting threads. +/** + * The asio::thread class implements the smallest possible subset of the + * functionality of boost::thread. It is intended to be used only for starting + * a thread and waiting for it to exit. If more extensive threading + * capabilities are required, you are strongly advised to use something else. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Example + * A typical use of asio::thread would be to launch a thread to run an + * io_service's event processing loop: + * + * @par + * @code asio::io_service io_service; + * // ... + * asio::thread t(boost::bind(&asio::io_service::run, &io_service)); + * // ... + * t.join(); @endcode + */ +class thread + : private noncopyable +{ +public: + /// Start a new thread that executes the supplied function. + /** + * This constructor creates a new thread that will execute the given function + * or function object. + * + * @param f The function or function object to be run in the thread. The + * function signature must be: @code void f(); @endcode + */ + template + explicit thread(Function f) + : impl_(f) + { + } + + /// Destructor. + ~thread() + { + } + + /// Wait for the thread to exit. + /** + * This function will block until the thread has exited. + * + * If this function is not called before the thread object is destroyed, the + * thread itself will continue to run until completion. You will, however, + * no longer have the ability to wait for it to exit. + */ + void join() + { + impl_.join(); + } + +private: + detail::thread impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_THREAD_HPP diff --git a/libtorrent/include/asio/time_traits.hpp b/libtorrent/include/asio/time_traits.hpp new file mode 100644 index 000000000..d3c910e53 --- /dev/null +++ b/libtorrent/include/asio/time_traits.hpp @@ -0,0 +1,78 @@ +// +// time_traits.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_TIME_TRAITS_HPP +#define ASIO_TIME_TRAITS_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/socket_types.hpp" // Must come before posix_time. + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { + +/// Time traits suitable for use with the deadline timer. +template +struct time_traits; + +/// Time traits specialised for posix_time. +template <> +struct time_traits +{ + /// The time type. + typedef boost::posix_time::ptime time_type; + + /// The duration type. + typedef boost::posix_time::time_duration duration_type; + + /// Get the current time. + static time_type now() + { + return boost::posix_time::microsec_clock::universal_time(); + } + + /// Add a duration to a time. + static time_type add(const time_type& t, const duration_type& d) + { + return t + d; + } + + /// Subtract one time from another. + static duration_type subtract(const time_type& t1, const time_type& t2) + { + return t1 - t2; + } + + /// Test whether one time is less than another. + static bool less_than(const time_type& t1, const time_type& t2) + { + return t1 < t2; + } + + /// Convert to POSIX duration type. + static boost::posix_time::time_duration to_posix_duration( + const duration_type& d) + { + return d; + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_TIME_TRAITS_HPP diff --git a/libtorrent/include/asio/version.hpp b/libtorrent/include/asio/version.hpp new file mode 100644 index 000000000..82cf4e15a --- /dev/null +++ b/libtorrent/include/asio/version.hpp @@ -0,0 +1,23 @@ +// +// version.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_VERSION_HPP +#define ASIO_VERSION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +// ASIO_VERSION % 100 is the sub-minor version +// ASIO_VERSION / 100 % 1000 is the minor version +// ASIO_VERSION / 100000 is the major version +#define ASIO_VERSION 308 // 0.3.8 + +#endif // ASIO_VERSION_HPP diff --git a/libtorrent/include/asio/write.hpp b/libtorrent/include/asio/write.hpp new file mode 100644 index 000000000..5d643626a --- /dev/null +++ b/libtorrent/include/asio/write.hpp @@ -0,0 +1,515 @@ +// +// write.hpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_WRITE_HPP +#define ASIO_WRITE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/basic_streambuf.hpp" +#include "asio/error.hpp" + +namespace asio { + +/** + * @defgroup write asio::write + */ +/*@{*/ + +/// Write all of the supplied data to a stream before returning. +/** + * This function is used to write a certain number of bytes of data to a stream. + * The call will block until one of the following conditions is true: + * + * @li All of the data in the supplied buffers has been written. That is, the + * bytes transferred is equal to the sum of the buffer sizes. + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the SyncWriteStream concept. + * + * @param buffers One or more buffers containing the data to be written. The sum + * of the buffer sizes indicates the maximum number of bytes to write to the + * stream. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code asio::write(s, asio::buffer(data, size)); @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + * + * @note This overload is equivalent to calling: + * @code asio::write( + * s, buffers, + * asio::transfer_all()); @endcode + */ +template +std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers); + +/// Write a certain amount of data to a stream before returning. +/** + * This function is used to write a certain number of bytes of data to a stream. + * The call will block until one of the following conditions is true: + * + * @li All of the data in the supplied buffers has been written. That is, the + * bytes transferred is equal to the sum of the buffer sizes. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the SyncWriteStream concept. + * + * @param buffers One or more buffers containing the data to be written. The sum + * of the buffer sizes indicates the maximum number of bytes to write to the + * stream. + * + * @param completion_condition The function object to be called to determine + * whether the write operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest write_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the write operation is complete. False + * indicates that further calls to the stream's write_some function are + * required. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code asio::write(s, asio::buffer(data, size), + * asio::transfer_at_least(32)); @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ +template +std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers, + CompletionCondition completion_condition); + +/// Write a certain amount of data to a stream before returning. +/** + * This function is used to write a certain number of bytes of data to a stream. + * The call will block until one of the following conditions is true: + * + * @li All of the data in the supplied buffers has been written. That is, the + * bytes transferred is equal to the sum of the buffer sizes. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the SyncWriteStream concept. + * + * @param buffers One or more buffers containing the data to be written. The sum + * of the buffer sizes indicates the maximum number of bytes to write to the + * stream. + * + * @param completion_condition The function object to be called to determine + * whether the write operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest write_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the write operation is complete. False + * indicates that further calls to the stream's write_some function are + * required. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes written. If an error occurs, returns the total + * number of bytes successfully transferred prior to the error. + */ +template +std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers, + CompletionCondition completion_condition, asio::error_code& ec); + +/// Write a certain amount of data to a stream before returning. +/** + * This function is used to write a certain number of bytes of data to a stream. + * The call will block until one of the following conditions is true: + * + * @li All of the data in the supplied basic_streambuf has been written. + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the SyncWriteStream concept. + * + * @param b The basic_streambuf object from which data will be written. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + * + * @note This overload is equivalent to calling: + * @code asio::write( + * s, b, + * asio::transfer_all()); @endcode + */ +template +std::size_t write(SyncWriteStream& s, basic_streambuf& b); + +/// Write a certain amount of data to a stream before returning. +/** + * This function is used to write a certain number of bytes of data to a stream. + * The call will block until one of the following conditions is true: + * + * @li All of the data in the supplied basic_streambuf has been written. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the SyncWriteStream concept. + * + * @param b The basic_streambuf object from which data will be written. + * + * @param completion_condition The function object to be called to determine + * whether the write operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest write_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the write operation is complete. False + * indicates that further calls to the stream's write_some function are + * required. + * + * @returns The number of bytes transferred. + * + * @throws asio::system_error Thrown on failure. + */ +template +std::size_t write(SyncWriteStream& s, basic_streambuf& b, + CompletionCondition completion_condition); + +/// Write a certain amount of data to a stream before returning. +/** + * This function is used to write a certain number of bytes of data to a stream. + * The call will block until one of the following conditions is true: + * + * @li All of the data in the supplied basic_streambuf has been written. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the SyncWriteStream concept. + * + * @param b The basic_streambuf object from which data will be written. + * + * @param completion_condition The function object to be called to determine + * whether the write operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest write_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the write operation is complete. False + * indicates that further calls to the stream's write_some function are + * required. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes written. If an error occurs, returns the total + * number of bytes successfully transferred prior to the error. + */ +template +std::size_t write(SyncWriteStream& s, basic_streambuf& b, + CompletionCondition completion_condition, asio::error_code& ec); + +/*@}*/ +/** + * @defgroup async_write asio::async_write + */ +/*@{*/ + +/// Start an asynchronous operation to write of all of the supplied data to a +/// stream. +/** + * This function is used to asynchronously write a certain number of bytes of + * data to a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions + * is true: + * + * @li All of the data in the supplied buffers has been written. That is, the + * bytes transferred is equal to the sum of the buffer sizes. + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the AsyncWriteStream concept. + * + * @param buffers One or more buffers containing the data to be written. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param handler The handler to be called when the write operation completes. + * Copies will be made of the handler as required. The function signature of + * the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes written from the + * // buffers. If an error occurred, + * // this will be less than the sum + * // of the buffer sizes. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code + * asio::async_write(s, asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ +template +void async_write(AsyncWriteStream& s, const ConstBufferSequence& buffers, + WriteHandler handler); + +/// Start an asynchronous operation to write a certain amount of data to a +/// stream. +/** + * This function is used to asynchronously write a certain number of bytes of + * data to a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions + * is true: + * + * @li All of the data in the supplied buffers has been written. That is, the + * bytes transferred is equal to the sum of the buffer sizes. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the AsyncWriteStream concept. + * + * @param buffers One or more buffers containing the data to be written. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the handler is called. + * + * @param completion_condition The function object to be called to determine + * whether the write operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest write_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the write operation is complete. False + * indicates that further calls to the stream's async_write_some function are + * required. + * + * @param handler The handler to be called when the write operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes written from the + * // buffers. If an error occurred, + * // this will be less than the sum + * // of the buffer sizes. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code asio::async_write(s, + * asio::buffer(data, size), + * asio::transfer_at_least(32), + * handler); @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ +template +void async_write(AsyncWriteStream& s, const ConstBufferSequence& buffers, + CompletionCondition completion_condition, WriteHandler handler); + +/// Start an asynchronous operation to write a certain amount of data to a +/// stream. +/** + * This function is used to asynchronously write a certain number of bytes of + * data to a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions + * is true: + * + * @li All of the data in the supplied basic_streambuf has been written. + * + * @li An error occurred. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the AsyncWriteStream concept. + * + * @param b A basic_streambuf object from which data will be written. Ownership + * of the streambuf is retained by the caller, which must guarantee that it + * remains valid until the handler is called. + * + * @param handler The handler to be called when the write operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes written from the + * // buffers. If an error occurred, + * // this will be less than the sum + * // of the buffer sizes. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ +template +void async_write(AsyncWriteStream& s, basic_streambuf& b, + WriteHandler handler); + +/// Start an asynchronous operation to write a certain amount of data to a +/// stream. +/** + * This function is used to asynchronously write a certain number of bytes of + * data to a stream. The function call always returns immediately. The + * asynchronous operation will continue until one of the following conditions + * is true: + * + * @li All of the data in the supplied basic_streambuf has been written. + * + * @li The completion_condition function object returns true. + * + * This operation is implemented in terms of one or more calls to the stream's + * async_write_some function. + * + * @param s The stream to which the data is to be written. The type must support + * the AsyncWriteStream concept. + * + * @param b A basic_streambuf object from which data will be written. Ownership + * of the streambuf is retained by the caller, which must guarantee that it + * remains valid until the handler is called. + * + * @param completion_condition The function object to be called to determine + * whether the write operation is complete. The signature of the function object + * must be: + * @code bool completion_condition( + * const asio::error_code& error, // Result of latest write_some + * // operation. + * + * std::size_t bytes_transferred // Number of bytes transferred + * // so far. + * ); @endcode + * A return value of true indicates that the write operation is complete. False + * indicates that further calls to the stream's async_write_some function are + * required. + * + * @param handler The handler to be called when the write operation completes. + * Copies will be made of the handler as required. The function signature of the + * handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * + * std::size_t bytes_transferred // Number of bytes written from the + * // buffers. If an error occurred, + * // this will be less than the sum + * // of the buffer sizes. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation of + * the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + */ +template +void async_write(AsyncWriteStream& s, basic_streambuf& b, + CompletionCondition completion_condition, WriteHandler handler); + +/*@}*/ + +} // namespace asio + +#include "asio/impl/write.ipp" + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_WRITE_HPP diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp new file mode 100755 index 000000000..b6b6711dc --- /dev/null +++ b/libtorrent/include/libtorrent/alert.hpp @@ -0,0 +1,164 @@ +/* + +Copyright (c) 2003, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_HPP_INCLUDED +#define TORRENT_ALERT_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/time.hpp" +#include "libtorrent/config.hpp" + +#ifndef TORRENT_MAX_ALERT_TYPES +#define TORRENT_MAX_ALERT_TYPES 15 +#endif + +namespace libtorrent { + + class TORRENT_EXPORT alert + { + public: + enum severity_t { debug, info, warning, critical, fatal, none }; + + alert(severity_t severity, const std::string& msg); + virtual ~alert(); + + // a timestamp is automatically created in the constructor + ptime timestamp() const; + + std::string const& msg() const; + + severity_t severity() const; + + virtual std::auto_ptr clone() const = 0; + + private: + std::string m_msg; + severity_t m_severity; + ptime m_timestamp; + }; + + class TORRENT_EXPORT alert_manager + { + public: + alert_manager(); + ~alert_manager(); + + void post_alert(const alert& alert_); + bool pending() const; + std::auto_ptr get(); + + void set_severity(alert::severity_t severity); + bool should_post(alert::severity_t severity) const; + + private: + std::queue m_alerts; + alert::severity_t m_severity; + mutable boost::mutex m_mutex; + }; + + struct TORRENT_EXPORT unhandled_alert : std::exception + { + unhandled_alert() {} + }; + + namespace detail { + + struct void_; + + template + void handle_alert_dispatch( + const std::auto_ptr& alert_, const Handler& handler + , const std::type_info& typeid_ + , BOOST_PP_ENUM_BINARY_PARAMS(TORRENT_MAX_ALERT_TYPES, T, *p)) + { + if (typeid_ == typeid(T0)) + handler(*static_cast(alert_.get())); + else + handle_alert_dispatch(alert_, handler, typeid_ + , BOOST_PP_ENUM_SHIFTED_PARAMS( + TORRENT_MAX_ALERT_TYPES, p), (void_*)0); + } + + template + void handle_alert_dispatch( + const std::auto_ptr& alert_ + , const Handler& handler + , const std::type_info& typeid_ + , BOOST_PP_ENUM_PARAMS(TORRENT_MAX_ALERT_TYPES, void_* BOOST_PP_INTERCEPT)) + { + throw unhandled_alert(); + } + + } // namespace detail + + template + struct TORRENT_EXPORT handle_alert + { + template + handle_alert(const std::auto_ptr& alert_ + , const Handler& handler) + { + #define ALERT_POINTER_TYPE(z, n, text) (BOOST_PP_CAT(T, n)*)0 + + detail::handle_alert_dispatch(alert_, handler, typeid(*alert_) + , BOOST_PP_ENUM(TORRENT_MAX_ALERT_TYPES, ALERT_POINTER_TYPE, _)); + + #undef ALERT_POINTER_TYPE + } + }; + +} // namespace libtorrent + +#endif // TORRENT_ALERT_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp new file mode 100755 index 000000000..7b32d2502 --- /dev/null +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -0,0 +1,318 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_TYPES_HPP_INCLUDED +#define TORRENT_ALERT_TYPES_HPP_INCLUDED + +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXPORT torrent_alert: alert + { + torrent_alert(torrent_handle const& h, alert::severity_t s + , std::string const& msg) + : alert(s, msg) + , handle(h) + {} + + torrent_handle handle; + }; + + struct TORRENT_EXPORT tracker_alert: torrent_alert + { + tracker_alert(torrent_handle const& h + , int times + , int status + , std::string const& msg) + : torrent_alert(h, alert::warning, msg) + , times_in_row(times) + , status_code(status) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new tracker_alert(*this)); } + + int times_in_row; + int status_code; + }; + + struct TORRENT_EXPORT tracker_warning_alert: torrent_alert + { + tracker_warning_alert(torrent_handle const& h + , std::string const& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new tracker_warning_alert(*this)); } + }; + + struct TORRENT_EXPORT tracker_reply_alert: torrent_alert + { + tracker_reply_alert(torrent_handle const& h + , int np + , std::string const& msg) + : torrent_alert(h, alert::info, msg) + , num_peers(np) + {} + + int num_peers; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new tracker_reply_alert(*this)); } + }; + + struct TORRENT_EXPORT tracker_announce_alert: torrent_alert + { + tracker_announce_alert(torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::info, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new tracker_announce_alert(*this)); } + }; + + struct TORRENT_EXPORT hash_failed_alert: torrent_alert + { + hash_failed_alert( + torrent_handle const& h + , int index + , std::string const& msg) + : torrent_alert(h, alert::info, msg) + , piece_index(index) + { assert(index >= 0);} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new hash_failed_alert(*this)); } + + int piece_index; + }; + + struct TORRENT_EXPORT peer_ban_alert: torrent_alert + { + peer_ban_alert(tcp::endpoint const& pip, torrent_handle h, std::string const& msg) + : torrent_alert(h, alert::info, msg) + , ip(pip) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new peer_ban_alert(*this)); } + + tcp::endpoint ip; + }; + + struct TORRENT_EXPORT peer_error_alert: alert + { + peer_error_alert(tcp::endpoint const& pip, peer_id const& pid_, std::string const& msg) + : alert(alert::debug, msg) + , ip(pip) + , pid(pid_) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new peer_error_alert(*this)); } + + tcp::endpoint ip; + peer_id pid; + }; + + struct TORRENT_EXPORT invalid_request_alert: torrent_alert + { + invalid_request_alert( + peer_request const& r + , torrent_handle const& h + , tcp::endpoint const& sender + , peer_id const& pid_ + , std::string const& msg) + : torrent_alert(h, alert::debug, msg) + , ip(sender) + , request(r) + , pid(pid_) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new invalid_request_alert(*this)); } + + tcp::endpoint ip; + peer_request request; + peer_id pid; + }; + + struct TORRENT_EXPORT torrent_finished_alert: torrent_alert + { + torrent_finished_alert( + const torrent_handle& h + , const std::string& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new torrent_finished_alert(*this)); } + }; + + struct TORRENT_EXPORT storage_moved_alert: torrent_alert + { + storage_moved_alert(torrent_handle const& h, std::string const& path) + : torrent_alert(h, alert::warning, path) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new storage_moved_alert(*this)); } + }; + + struct TORRENT_EXPORT torrent_paused_alert: torrent_alert + { + torrent_paused_alert(torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new torrent_paused_alert(*this)); } + }; + + struct TORRENT_EXPORT url_seed_alert: torrent_alert + { + url_seed_alert( + torrent_handle const& h + , const std::string& url_ + , const std::string& msg) + : torrent_alert(h, alert::warning, msg) + , url(url_) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new url_seed_alert(*this)); } + + std::string url; + }; + + struct TORRENT_EXPORT file_error_alert: torrent_alert + { + file_error_alert( + const torrent_handle& h + , const std::string& msg) + : torrent_alert(h, alert::fatal, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new file_error_alert(*this)); } + }; + + struct TORRENT_EXPORT metadata_failed_alert: torrent_alert + { + metadata_failed_alert( + const torrent_handle& h + , const std::string& msg) + : torrent_alert(h, alert::info, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new metadata_failed_alert(*this)); } + }; + + struct TORRENT_EXPORT metadata_received_alert: torrent_alert + { + metadata_received_alert( + const torrent_handle& h + , const std::string& msg) + : torrent_alert(h, alert::info, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new metadata_received_alert(*this)); } + }; + + struct TORRENT_EXPORT listen_failed_alert: alert + { + listen_failed_alert( + const std::string& msg) + : alert(alert::fatal, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new listen_failed_alert(*this)); } + }; + + struct TORRENT_EXPORT portmap_error_alert: alert + { + portmap_error_alert(const std::string& msg) + : alert(alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new portmap_error_alert(*this)); } + }; + + struct TORRENT_EXPORT portmap_alert: alert + { + portmap_alert(const std::string& msg) + : alert(alert::info, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new portmap_alert(*this)); } + }; + + struct TORRENT_EXPORT fastresume_rejected_alert: torrent_alert + { + fastresume_rejected_alert(torrent_handle const& h + , std::string const& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new fastresume_rejected_alert(*this)); } + }; + + struct TORRENT_EXPORT peer_blocked_alert: alert + { + peer_blocked_alert(address const& ip_ + , std::string const& msg) + : alert(alert::info, msg) + , ip(ip_) + {} + + address ip; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new peer_blocked_alert(*this)); } + }; + +} + + +#endif diff --git a/libtorrent/include/libtorrent/allocate_resources.hpp b/libtorrent/include/libtorrent/allocate_resources.hpp new file mode 100644 index 000000000..3d8237914 --- /dev/null +++ b/libtorrent/include/libtorrent/allocate_resources.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2003, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCATE_RESOURCES_HPP_INCLUDED +#define TORRENT_ALLOCATE_RESOURCES_HPP_INCLUDED + +#include +#include + +#include + +#include "libtorrent/resource_request.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/session.hpp" + +namespace libtorrent +{ + class peer_connection; + class torrent; + + int saturated_add(int a, int b); + + // Function to allocate a limited resource fairly among many consumers. + // It takes into account the current use, and the consumer's desired use. + // Should be invoked periodically to allow it adjust to the situation (make + // sure "used" is updated between calls!). + // If resources = std::numeric_limits::max() it means there is an infinite + // supply of resources (so everyone can get what they want). + + void allocate_resources( + int resources + , std::map >& torrents + , resource_request torrent::* res); + + void allocate_resources( + int resources + , std::map& connections + , resource_request peer_connection::* res); + + // Used for global limits. + void allocate_resources( + int resources + , std::vector& _sessions + , resource_request session::* res); +} + + +#endif diff --git a/libtorrent/include/libtorrent/aux_/allocate_resources_impl.hpp b/libtorrent/include/libtorrent/aux_/allocate_resources_impl.hpp new file mode 100644 index 000000000..31865d40a --- /dev/null +++ b/libtorrent/include/libtorrent/aux_/allocate_resources_impl.hpp @@ -0,0 +1,328 @@ +/* + +Copyright (c) 2003, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCATE_RESOURCES_IMPL_HPP_INCLUDED +#define TORRENT_ALLOCATE_RESOURCES_IMPL_HPP_INCLUDED + +#include +#include + +#include + +#include "libtorrent/resource_request.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/size_type.hpp" + +#ifdef min +#undef min +#endif + +#ifdef max +#undef max +#endif + +namespace libtorrent +{ + + int saturated_add(int a, int b); + + namespace aux + { + // give num_resources to r, + // return how how many were actually accepted. + inline int give(resource_request& r, int num_resources) + { + assert(num_resources >= 0); + assert(r.given <= r.max); + + int accepted = (std::min)(num_resources, r.max - r.given); + assert(accepted >= 0); + + r.given += accepted; + assert(r.given <= r.max); + + return accepted; + } + + inline int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + +#ifndef NDEBUG + + template + class allocate_resources_contract_check + { + int m_resources; + It m_start; + It m_end; + resource_request T::* m_res; + + public: + allocate_resources_contract_check( + int resources + , It start + , It end + , resource_request T::* res) + : m_resources(resources) + , m_start(start) + , m_end(end) + , m_res(res) + { + assert(m_resources >= 0); + for (It i = m_start, end(m_end); i != end; ++i) + { + assert(((*i).*m_res).max >= 0); + assert(((*i).*m_res).given >= 0); + } + } + + ~allocate_resources_contract_check() + { + int sum_given = 0; + int sum_max = 0; + int sum_min = 0; + for (It i = m_start, end(m_end); i != end; ++i) + { + assert(((*i).*m_res).max >= 0); + assert(((*i).*m_res).min >= 0); + assert(((*i).*m_res).max >= ((*i).*m_res).min); + assert(((*i).*m_res).given >= 0); + assert(((*i).*m_res).given <= ((*i).*m_res).max); + + sum_given = saturated_add(sum_given, ((*i).*m_res).given); + sum_max = saturated_add(sum_max, ((*i).*m_res).max); + sum_min = saturated_add(sum_min, ((*i).*m_res).min); + } + if (sum_given != (std::min)(std::max(m_resources, sum_min), sum_max)) + { + std::cerr << sum_given << " " << m_resources << " " << sum_min << " " << sum_max << std::endl; + assert(false); + } + } + }; + +#endif + + template + void allocate_resources_impl( + int resources + , It start + , It end + , resource_request T::* res) + { + assert(resources >= 0); + #ifndef NDEBUG + allocate_resources_contract_check contract_check( + resources + , start + , end + , res); + #endif + + for (It i = start; i != end; ++i) + { + resource_request& r = (*i).*res; + r.leftovers = (std::max)(r.used - r.given, 0); + } + + if (resources == resource_request::inf) + { + // No competition for resources. + // Just give everyone what they want. + for (It i = start; i != end; ++i) + { + ((*i).*res).given = ((*i).*res).max; + } + return; + } + + // Resources are scarce + + int sum_max = 0; + int sum_min = 0; + // the number of consumer that saturated their + // quota last time slice + int num_saturated = 0; + // the total resources that those saturated their + // quota used. This is used to calculate the mean + // of the saturating consumers, in order to + // balance their quotas for the next time slice. + size_type saturated_sum = 0; + for (It i = start; i != end; ++i) + { + resource_request& r = (*i).*res; + sum_max = saturated_add(sum_max, r.max); + assert(r.min < resource_request::inf); + assert(r.min >= 0); + assert(r.min <= r.max); + sum_min += r.min; + + // a consumer that uses 95% or more of its assigned + // quota is considered saturating + size_type used = r.used; + if (r.given == 0) continue; + if (used * 20 / r.given >= 19) + { + ++num_saturated; + saturated_sum += r.given; + } + } + + if (sum_max <= resources) + { + // it turns out that there's no competition for resources + // after all. + for (It i = start; i != end; ++i) + { + ((*i).*res).given = ((*i).*res).max; + } + return; + } + + if (sum_min >= resources) + { + // the amount of resources is smaller than + // the minimum resources to distribute, so + // give everyone the minimum + for (It i = start; i != end; ++i) + { + ((*i).*res).given = ((*i).*res).min; + } + return; + } + + // now, the "used" field will be used as a target value. + // the algorithm following this loop will then scale the + // used values to fit the available resources and store + // the scaled values as given. So, the ratios of the + // used values will be maintained. + for (It i = start; i != end; ++i) + { + resource_request& r = (*i).*res; + + int target; + size_type used = r.used; + if (r.given > 0 && used * 20 / r.given >= 19) + { + assert(num_saturated > 0); + target = div_round_up(saturated_sum, num_saturated); + target += div_round_up(target, 10); + } + else + { + target = r.used; + } + if (target > r.max) target = r.max; + else if (target < r.min) target = r.min; + + // move 12.5% towards the the target value + r.used = r.given + div_round_up(target - r.given, 8); + r.given = r.min; + } + + + resources = (std::max)(resources, sum_min); + int resources_to_distribute = (std::min)(resources, sum_max) - sum_min; + assert(resources_to_distribute >= 0); +#ifndef NDEBUG + int prev_resources_to_distribute = resources_to_distribute; +#endif + while (resources_to_distribute > 0) + { + // in order to scale, we need to calculate the sum of + // all the used values. + size_type total_used = 0; + size_type max_used = 0; + for (It i = start; i != end; ++i) + { + resource_request& r = (*i).*res; + if (r.given == r.max) continue; + + assert(r.given < r.max); + + max_used = (std::max)(max_used, (size_type)r.used + 1); + total_used += (size_type)r.used + 1; + } + + + size_type kNumer = resources_to_distribute; + size_type kDenom = total_used; + assert(kNumer >= 0); + assert(kDenom >= 0); + assert(kNumer <= (std::numeric_limits::max)()); + + if (kNumer * max_used <= kDenom) + { + kNumer = 1; + kDenom = max_used; + assert(kDenom >= 0); + } + + for (It i = start; i != end && resources_to_distribute > 0; ++i) + { + resource_request& r = (*i).*res; + if (r.given == r.max) continue; + + assert(r.given < r.max); + + size_type used = (size_type)r.used + 1; + if (used < 1) used = 1; + size_type to_give = used * kNumer / kDenom; + if (to_give > resources_to_distribute) + to_give = resources_to_distribute; + assert(to_give >= 0); + assert(to_give <= resources_to_distribute); +#ifndef NDEBUG + int tmp = resources_to_distribute; +#endif + resources_to_distribute -= give(r, (int)to_give); + assert(resources_to_distribute <= tmp); + assert(resources_to_distribute >= 0); + } + + assert(resources_to_distribute >= 0); + assert(resources_to_distribute < prev_resources_to_distribute); +#ifndef NDEBUG + prev_resources_to_distribute = resources_to_distribute; +#endif + } + assert(resources_to_distribute == 0); + } + + } // namespace libtorrent::aux +} + + +#endif diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp new file mode 100644 index 000000000..207016898 --- /dev/null +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -0,0 +1,562 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED +#define TORRENT_SESSION_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/connection_queue.hpp" +#include "libtorrent/disk_io_thread.hpp" + +namespace libtorrent +{ + + namespace fs = boost::filesystem; + + namespace aux + { + struct session_impl; + + // this data is shared between the main thread and the + // thread that initialize pieces + struct piece_checker_data + { + piece_checker_data() + : processing(false), progress(0.f), abort(false) {} + + boost::shared_ptr torrent_ptr; + fs::path save_path; + + sha1_hash info_hash; + + void parse_resume_data( + const entry& rd + , const torrent_info& info + , std::string& error); + + std::vector piece_map; + std::vector unfinished_pieces; + std::vector block_info; + std::vector peers; + entry resume_data; + + // this is true if this torrent is being processed (checked) + // if it is not being processed, then it can be removed from + // the queue without problems, otherwise the abort flag has + // to be set. + bool processing; + + // is filled in by storage::initialize_pieces() + // and represents the progress. It should be a + // value in the range [0, 1] + float progress; + + // abort defaults to false and is typically + // filled in by torrent_handle when the user + // aborts the torrent + bool abort; + }; + + struct checker_impl: boost::noncopyable + { + checker_impl(session_impl& s): m_ses(s), m_abort(false) {} + void operator()(); + piece_checker_data* find_torrent(const sha1_hash& info_hash); + void remove_torrent(sha1_hash const& info_hash); + +#ifndef NDEBUG + void check_invariant() const; +#endif + + // when the files has been checked + // the torrent is added to the session + session_impl& m_ses; + + mutable boost::mutex m_mutex; + boost::condition m_cond; + + // a list of all torrents that are currently in queue + // or checking their files + std::deque > m_torrents; + std::deque > m_processing; + + bool m_abort; + }; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + struct tracker_logger; +#endif + + // this is the link between the main thread and the + // thread started to run the main downloader loop + struct session_impl: boost::noncopyable + { +#ifndef NDEBUG + friend class ::libtorrent::peer_connection; +#endif + friend struct checker_impl; + friend class invariant_access; + typedef std::map + , boost::intrusive_ptr > + connection_map; + typedef std::map > torrent_map; + + session_impl( + std::pair listen_port_range + , fingerprint const& cl_fprint + , char const* listen_interface = "0.0.0.0"); + ~session_impl(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::function(torrent*)> ext); +#endif + void operator()(); + + void open_listen_port(); + + void async_accept(); + void on_incoming_connection(boost::shared_ptr const& s + , boost::weak_ptr const& as, asio::error_code const& e); + + // must be locked to access the data + // in this struct + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + + boost::weak_ptr find_torrent(const sha1_hash& info_hash); + peer_id const& get_peer_id() const { return m_peer_id; } + + void close_connection(boost::intrusive_ptr const& p); + void connection_failed(boost::shared_ptr const& s + , tcp::endpoint const& a, char const* message); + + void set_settings(session_settings const& s); + session_settings const& settings() const { return m_settings; } + +#ifndef TORRENT_DISABLE_DHT + void add_dht_node(std::pair const& node); + void add_dht_node(udp::endpoint n); + void add_dht_router(std::pair const& node); + void set_dht_settings(dht_settings const& s); + dht_settings const& get_dht_settings() const { return m_dht_settings; } + void start_dht(entry const& startup_state); + void stop_dht(); + entry dht_state() const; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + void set_pe_settings(pe_settings const& settings); + pe_settings const& get_pe_settings() const { return m_pe_settings; } +#endif + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); + + bool is_aborted() const { return m_abort; } + + void set_ip_filter(ip_filter const& f); + void set_port_filter(port_filter const& f); + + bool listen_on( + std::pair const& port_range + , const char* net_interface = 0); + bool is_listening() const; + + torrent_handle add_torrent( + torrent_info const& ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc); + + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc); + + void remove_torrent(torrent_handle const& h); + + std::vector get_torrents(); + + void set_severity_level(alert::severity_t s); + std::auto_ptr pop_alert(); + + int upload_rate_limit() const; + int download_rate_limit() const; + + void set_download_rate_limit(int bytes_per_second); + void set_upload_rate_limit(int bytes_per_second); + void set_max_half_open_connections(int limit); + void set_max_connections(int limit); + void set_max_uploads(int limit); + + int num_uploads() const; + int num_connections() const; + + session_status status() const; + void set_peer_id(peer_id const& id); + void set_key(int key); + unsigned short listen_port() const; + + void abort(); + + torrent_handle find_torrent_handle(sha1_hash const& info_hash); + + void announce_lsd(sha1_hash const& ih); + + void set_peer_proxy(proxy_settings const& s) + { m_peer_proxy = s; } + void set_web_seed_proxy(proxy_settings const& s) + { m_web_seed_proxy = s; } + void set_tracker_proxy(proxy_settings const& s) + { m_tracker_proxy = s; } + + proxy_settings const& peer_proxy() const + { return m_peer_proxy; } + proxy_settings const& web_seed_proxy() const + { return m_web_seed_proxy; } + proxy_settings const& tracker_proxy() const + { return m_tracker_proxy; } + +#ifndef TORRENT_DISABLE_DHT + void set_dht_proxy(proxy_settings const& s) + { m_dht_proxy = s; } + proxy_settings const& dht_proxy() const + { return m_dht_proxy; } +#endif + + void start_lsd(); + void start_natpmp(); + void start_upnp(); + + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + // handles delayed alerts + alert_manager m_alerts; + +// private: + + void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); + + // this is where all active sockets are stored. + // the selector can sleep while there's no activity on + // them + io_service m_io_service; + asio::strand m_strand; + + // the file pool that all storages in this session's + // torrents uses. It sets a limit on the number of + // open files by this session. + // file pool must be destructed after the torrents + // since they will still have references to it + // when they are destructed. + file_pool m_files; + + // handles disk io requests asynchronously + disk_io_thread m_disk_thread; + + // this is a list of half-open tcp connections + // (only outgoing connections) + // this has to be one of the last + // members to be destructed + connection_queue m_half_open; + + // the bandwidth manager is responsible for + // handing out bandwidth to connections that + // asks for it, it can also throttle the + // rate. + bandwidth_manager m_download_channel; + bandwidth_manager m_upload_channel; + + bandwidth_manager* m_bandwidth_manager[2]; + + tracker_manager m_tracker_manager; + torrent_map m_torrents; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + + // filters incoming connections + ip_filter m_ip_filter; + + // filters outgoing connections + port_filter m_port_filter; + + // the peer id that is generated at the start of the session + peer_id m_peer_id; + + // the key is an id that is used to identify the + // client with the tracker only. It is randomized + // at startup + int m_key; + + // the range of ports we try to listen on + std::pair m_listen_port_range; + + // the ip-address of the interface + // we are supposed to listen on. + // if the ip is set to zero, it means + // that we should let the os decide which + // interface to listen on + tcp::endpoint m_listen_interface; + + // this is typically set to the same as the local + // listen port. In case a NAT port forward was + // successfully opened, this will be set to the + // port that is open on the external (NAT) interface + // on the NAT box itself. This is the port that has + // to be published to peers, since this is the port + // the client is reachable through. + int m_external_listen_port; + + boost::shared_ptr m_listen_socket; + + // the settings for the client + session_settings m_settings; + // the proxy settings for different + // kinds of connections + proxy_settings m_peer_proxy; + proxy_settings m_web_seed_proxy; + proxy_settings m_tracker_proxy; +#ifndef TORRENT_DISABLE_DHT + proxy_settings m_dht_proxy; +#endif + + // set to true when the session object + // is being destructed and the thread + // should exit + volatile bool m_abort; + + int m_max_uploads; + int m_max_connections; + + // statistics gathered from all torrents. + stat m_stat; + + // is false by default and set to true when + // the first incoming connection is established + // this is used to know if the client is behind + // NAT or not. + bool m_incoming_connection; + + void second_tick(asio::error_code const& e); + ptime m_last_tick; + +#ifndef TORRENT_DISABLE_DHT + boost::intrusive_ptr m_dht; + dht_settings m_dht_settings; + // if this is set to true, the dht listen port + // will be set to the same as the tcp listen port + // and will be synchronlized with it as it changes + // it defaults to true + bool m_dht_same_port; + + // see m_external_listen_port. This is the same + // but for the udp port used by the DHT. + int m_external_udp_port; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + pe_settings m_pe_settings; +#endif + + boost::shared_ptr m_natpmp; + boost::shared_ptr m_upnp; + boost::shared_ptr m_lsd; + + // the timer used to fire the second_tick + deadline_timer m_timer; + + // the index of the torrent that will be offered to + // connect to a peer next time second_tick is called. + // This implements a round robin. + int m_next_connect_torrent; +#ifndef NDEBUG + void check_invariant(const char *place = 0); +#endif + +#ifdef TORRENT_STATS + // logger used to write bandwidth usage statistics + std::ofstream m_stats_logger; + int m_second_counter; +#endif +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr create_log(std::string const& name + , int instance, bool append = true); + + // this list of tracker loggers serves as tracker_callbacks when + // shutting down. This list is just here to keep them alive during + // whe shutting down process + std::list > m_tracker_loggers; + + public: + boost::shared_ptr m_logger; + private: +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list(torrent*)> > extension_list_t; + + extension_list_t m_extensions; +#endif + + // data shared between the main thread + // and the checker thread + checker_impl m_checker_impl; + + // the main working thread + boost::scoped_ptr m_thread; + + // the thread that calls initialize_pieces() + // on all torrents before they start downloading + boost::scoped_ptr m_checker_thread; + }; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + struct tracker_logger : request_callback + { + tracker_logger(session_impl& ses): m_ses(ses) {} + void tracker_warning(std::string const& str) + { + debug_log("*** tracker warning: " + str); + } + + void tracker_response(tracker_request const& + , std::vector& peers + , int interval + , int complete + , int incomplete) + { + std::stringstream s; + s << "TRACKER RESPONSE:\n" + "interval: " << interval << "\n" + "peers:\n"; + for (std::vector::const_iterator i = peers.begin(); + i != peers.end(); ++i) + { + s << " " << std::setfill(' ') << std::setw(16) << i->ip + << " " << std::setw(5) << std::dec << i->port << " "; + if (!i->pid.is_all_zeros()) s << " " << i->pid; + s << "\n"; + } + debug_log(s.str()); + } + + void tracker_request_timed_out( + tracker_request const&) + { + debug_log("*** tracker timed out"); + } + + void tracker_request_error( + tracker_request const& + , int response_code + , const std::string& str) + { + debug_log(std::string("*** tracker error: ") + + boost::lexical_cast(response_code) + ": " + + str); + } + + void debug_log(const std::string& line) + { + (*m_ses.m_logger) << line << "\n"; + } + session_impl& m_ses; + }; +#endif + + } +} + + +#endif + diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp new file mode 100644 index 000000000..30f99d0cf --- /dev/null +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -0,0 +1,449 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED +#define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/invariant_check.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using boost::weak_ptr; +using boost::shared_ptr; +using boost::intrusive_ptr; +using boost::bind; + +namespace libtorrent { + +// the maximum block of bandwidth quota to +// hand out is 33kB. The block size may +// be smaller on lower limits +enum +{ + max_bandwidth_block_size = 33000, + min_bandwidth_block_size = 400 +}; + +const time_duration bw_window_size = seconds(1); + +template +struct history_entry +{ + history_entry(intrusive_ptr p, weak_ptr t + , int a, ptime exp) + : expires_at(exp), amount(a), peer(p), tor(t) {} + ptime expires_at; + int amount; + intrusive_ptr peer; + weak_ptr tor; +}; + +template +struct bw_queue_entry +{ + bw_queue_entry(boost::intrusive_ptr const& pe + , int blk, bool no_prio) + : peer(pe), max_block_size(blk), non_prioritized(no_prio) {} + boost::intrusive_ptr peer; + int max_block_size; + bool non_prioritized; +}; + +// member of peer_connection +struct bandwidth_limit +{ + static const int inf = boost::integer_traits::const_max; + + bandwidth_limit() + : m_quota_left(0) + , m_local_limit(inf) + , m_current_rate(0) + {} + + void throttle(int limit) + { + m_local_limit = limit; + } + + int throttle() const + { + return m_local_limit; + } + + void assign(int amount) + { + assert(amount > 0); + m_current_rate += amount; + m_quota_left += amount; + } + + void use_quota(int amount) + { + assert(amount <= m_quota_left); + m_quota_left -= amount; + } + + int quota_left() const + { + return (std::max)(m_quota_left, 0); + } + + void expire(int amount) + { + assert(amount >= 0); + m_current_rate -= amount; + } + + int max_assignable() const + { + if (m_local_limit == inf) return inf; + if (m_local_limit <= m_current_rate) return 0; + return m_local_limit - m_current_rate; + } + +private: + + // this is the amount of bandwidth we have + // been assigned without using yet. i.e. + // the bandwidth that we use up every time + // we receive or send a message. Once this + // hits zero, we need to request more + // bandwidth from the torrent which + // in turn will request bandwidth from + // the bandwidth manager + int m_quota_left; + + // the local limit is the number of bytes + // per window size we are allowed to use. + int m_local_limit; + + // the current rate is the number of + // bytes we have been assigned within + // the window size. + int m_current_rate; +}; + +template +T clamp(T val, T ceiling, T floor) +{ + assert(ceiling >= floor); + if (val >= ceiling) return ceiling; + else if (val <= floor) return floor; + return val; +} + +template +struct bandwidth_manager +{ + bandwidth_manager(io_service& ios, int channel) + : m_ios(ios) + , m_history_timer(m_ios) + , m_limit(bandwidth_limit::inf) + , m_current_quota(0) + , m_channel(channel) + {} + + void throttle(int limit) + { + mutex_t::scoped_lock l(m_mutex); + assert(limit >= 0); + m_limit = limit; + } + + int throttle() const + { + mutex_t::scoped_lock l(m_mutex); + return m_limit; + } + + // non prioritized means that, if there's a line for bandwidth, + // others will cut in front of the non-prioritized peers. + // this is used by web seeds + void request_bandwidth(intrusive_ptr peer + , int blk + , bool non_prioritized) + { + INVARIANT_CHECK; + assert(blk > 0); + + assert(!peer->ignore_bandwidth_limits()); + + // make sure this peer isn't already in line + // waiting for bandwidth +#ifndef NDEBUG + for (typename queue_t::iterator i = m_queue.begin() + , end(m_queue.end()); i != end; ++i) + { + assert(i->peer < peer || peer < i->peer); + } +#endif + + assert(peer->max_assignable_bandwidth(m_channel) > 0); + boost::shared_ptr t = peer->associated_torrent().lock(); + m_queue.push_back(bw_queue_entry(peer, blk, non_prioritized)); + if (!non_prioritized) + { + typename queue_t::reverse_iterator i = m_queue.rbegin(); + typename queue_t::reverse_iterator j(i); + for (++j; j != m_queue.rend(); ++j) + { + // if the peer's torrent is not the same one + // continue looking for a peer from the same torrent + if (j->peer->associated_torrent().lock() != t) + continue; + // if we found a peer from the same torrent that + // is prioritized, there is no point looking + // any further. + if (!j->non_prioritized) break; + + using std::swap; + swap(*i, *j); + i = j; + } + } + + if (m_queue.size() == 1) hand_out_bandwidth(); + } + +#ifndef NDEBUG + void check_invariant() const + { + int current_quota = 0; + for (typename history_t::const_iterator i + = m_history.begin(), end(m_history.end()); i != end; ++i) + { + current_quota += i->amount; + } + + assert(current_quota == m_current_quota); + } +#endif + +private: + + void add_history_entry(history_entry const& e) try + { + INVARIANT_CHECK; + m_history.push_front(e); + m_current_quota += e.amount; + // in case the size > 1 there is already a timer + // active that will be invoked, no need to set one up + if (m_history.size() > 1) return; + + m_history_timer.expires_at(e.expires_at); + m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); + } + catch (std::exception&) { assert(false); } + + void on_history_expire(asio::error_code const& e) try + { + INVARIANT_CHECK; + + if (e) return; + + assert(!m_history.empty()); + + ptime now(time_now()); + while (!m_history.empty() && m_history.back().expires_at <= now) + { + history_entry e = m_history.back(); + m_history.pop_back(); + m_current_quota -= e.amount; + assert(m_current_quota >= 0); + intrusive_ptr c = e.peer; + shared_ptr t = e.tor.lock(); + if (!c->is_disconnecting()) c->expire_bandwidth(m_channel, e.amount); + if (t) t->expire_bandwidth(m_channel, e.amount); + } + + // now, wait for the next chunk to expire + if (!m_history.empty()) + { + m_history_timer.expires_at(m_history.back().expires_at); + m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); + } + + // since some bandwidth just expired, it + // means we can hand out more (in case there + // are still consumers in line) + if (!m_queue.empty()) hand_out_bandwidth(); + } + catch (std::exception&) + { + assert(false); + }; + + void hand_out_bandwidth() try + { + INVARIANT_CHECK; + + ptime now(time_now()); + + mutex_t::scoped_lock l(m_mutex); + int limit = m_limit; + l.unlock(); + + // available bandwidth to hand out + int amount = limit - m_current_quota; + + while (!m_queue.empty() && amount > 0) + { + assert(amount == limit - m_current_quota); + bw_queue_entry qe = m_queue.front(); + m_queue.pop_front(); + + shared_ptr t = qe.peer->associated_torrent().lock(); + if (!t) continue; + if (qe.peer->is_disconnecting()) + { + t->expire_bandwidth(m_channel, qe.max_block_size); + continue; + } + + // at this point, max_assignable may actually be zero. Since + // the bandwidth quota is subtracted once the data has been + // sent. If the peer was added to the queue while the data was + // still being sent, max_assignable may have been > 0 at that time. + int max_assignable = (std::min)( + qe.peer->max_assignable_bandwidth(m_channel) + , t->max_assignable_bandwidth(m_channel)); + if (max_assignable == 0) + { + t->expire_bandwidth(m_channel, qe.max_block_size); + continue; + } + + // this is the limit of the block size. It depends on the throttle + // so that it can be closer to optimal. Larger block sizes will give lower + // granularity to the rate but will be more efficient. At high rates + // the block sizes are bigger and at low rates, the granularity + // is more important and block sizes are smaller + + // the minimum rate that can be given is the block size, so, the + // block size must be smaller for lower rates. This is because + // the history window is one second, and the block will be forgotten + // after one second. + int block_size = (std::min)(qe.max_block_size + , (std::min)(qe.peer->bandwidth_throttle(m_channel) + , m_limit / 10)); + + if (block_size < min_bandwidth_block_size) + { + block_size = min_bandwidth_block_size; + } + else if (block_size > max_bandwidth_block_size) + { + if (m_limit == bandwidth_limit::inf) + { + block_size = max_bandwidth_block_size; + } + else + { + // try to make the block_size a divisor of + // m_limit to make the distributions as fair + // as possible + // TODO: move this calculcation to where the limit + // is changed + block_size = m_limit + / (m_limit / max_bandwidth_block_size); + } + } + + if (amount < block_size / 2) + { + m_queue.push_front(qe); + break; + } + + // don't hand out chunks larger than the throttle + // per second on the torrent + assert(qe.max_block_size <= t->bandwidth_throttle(m_channel)); + + // so, hand out max_assignable, but no more than + // the available bandwidth (amount) and no more + // than the max_bandwidth_block_size + int hand_out_amount = (std::min)((std::min)(block_size, max_assignable) + , amount); + assert(hand_out_amount > 0); + amount -= hand_out_amount; + t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); + qe.peer->assign_bandwidth(m_channel, hand_out_amount); + add_history_entry(history_entry( + qe.peer, t, hand_out_amount, now + bw_window_size)); + } + } + catch (std::exception& e) + { assert(false); }; + + + typedef boost::mutex mutex_t; + mutable mutex_t m_mutex; + + // the io_service used for the timer + io_service& m_ios; + + // the timer that is waiting for the entries + // in the history queue to expire (slide out + // of the history window) + deadline_timer m_history_timer; + + // the rate limit (bytes per second) + int m_limit; + + // the sum of all recently handed out bandwidth blocks + int m_current_quota; + + // these are the consumers that want bandwidth + typedef std::deque > queue_t; + queue_t m_queue; + + // these are the consumers that have received bandwidth + // that will expire + typedef std::deque > history_t; + history_t m_history; + + // this is the channel within the consumers + // that bandwidth is assigned to (upload or download) + int m_channel; +}; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp new file mode 100755 index 000000000..a142b5864 --- /dev/null +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -0,0 +1,299 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef TORRENT_BENCODE_HPP_INCLUDED +#define TORRENT_BENCODE_HPP_INCLUDED + + + +/* + * This file declares the following functions: + * + *---------------------------------- + * template + * void libtorrent::bencode(OutIt out, const libtorrent::entry& e); + * + * Encodes a message entry with bencoding into the output + * iterator given. The bencoding is described in the BitTorrent + * protocol description document OutIt must be an OutputIterator + * of type char. This may throw libtorrent::invalid_encoding if + * the entry contains invalid nodes (undefined_t for example). + * + *---------------------------------- + * template + * libtorrent::entry libtorrent::bdecode(InIt start, InIt end); + * + * Decodes the buffer given by the start and end iterators + * and returns the decoded entry. InIt must be an InputIterator + * of type char. May throw libtorrent::invalid_encoding if + * the string is not correctly bencoded. + * + */ + + + + +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/entry.hpp" +#include "libtorrent/config.hpp" + +#if defined(_MSC_VER) +namespace std +{ + using ::isdigit; + using ::atoi; +}; + +#define for if (false) {} else for +#endif + + +namespace libtorrent +{ + + struct TORRENT_EXPORT invalid_encoding: std::exception + { + virtual const char* what() const throw() { return "invalid bencoding"; } + }; + + namespace detail + { + template + void write_string(OutIt& out, const std::string& val) + { + std::string::const_iterator end = val.begin() + val.length(); + std::copy(val.begin(), end, out); + } + + TORRENT_EXPORT char const* integer_to_str(char* buf, int size, entry::integer_type val); + + template + void write_integer(OutIt& out, entry::integer_type val) + { + // the stack allocated buffer for keeping the + // decimal representation of the number can + // not hold number bigger than this: + BOOST_STATIC_ASSERT(sizeof(entry::integer_type) <= 8); + char buf[21]; + for (char const* str = integer_to_str(buf, 21, val); + *str != 0; ++str) + { + *out = *str; + ++out; + } + } + + template + void write_char(OutIt& out, char c) + { + *out = c; + ++out; + } + + template + std::string read_until(InIt& in, InIt end, char end_token) + { + if (in == end) throw invalid_encoding(); + std::string ret; + while (*in != end_token) + { + ret += *in; + ++in; + if (in == end) throw invalid_encoding(); + } + return ret; + } + + template + void read_string(InIt& in, InIt end, int len, std::string& str) + { + assert(len >= 0); + for (int i = 0; i < len; ++i) + { + if (in == end) throw invalid_encoding(); + str += *in; + ++in; + } + } + + template + void bencode_recursive(OutIt& out, const entry& e) + { + switch(e.type()) + { + case entry::int_t: + write_char(out, 'i'); + write_integer(out, e.integer()); + write_char(out, 'e'); + break; + case entry::string_t: + write_integer(out, e.string().length()); + write_char(out, ':'); + write_string(out, e.string()); + break; + case entry::list_t: + write_char(out, 'l'); + for (entry::list_type::const_iterator i = e.list().begin(); i != e.list().end(); ++i) + bencode_recursive(out, *i); + write_char(out, 'e'); + break; + case entry::dictionary_t: + write_char(out, 'd'); + for (entry::dictionary_type::const_iterator i = e.dict().begin(); + i != e.dict().end(); ++i) + { + // write key + write_integer(out, i->first.length()); + write_char(out, ':'); + write_string(out, i->first); + // write value + bencode_recursive(out, i->second); + } + write_char(out, 'e'); + break; + default: + // do nothing + break; + } + } + + template + void bdecode_recursive(InIt& in, InIt end, entry& ret) + { + if (in == end) throw invalid_encoding(); + switch (*in) + { + + // ---------------------------------------------- + // integer + case 'i': + { + ++in; // 'i' + std::string val = read_until(in, end, 'e'); + assert(*in == 'e'); + ++in; // 'e' + ret = entry(entry::int_t); + ret.integer() = boost::lexical_cast(val); + } break; + + // ---------------------------------------------- + // list + case 'l': + { + ret = entry(entry::list_t); + ++in; // 'l' + while (*in != 'e') + { + ret.list().push_back(entry()); + entry& e = ret.list().back(); + bdecode_recursive(in, end, e); + if (in == end) throw invalid_encoding(); + } + assert(*in == 'e'); + ++in; // 'e' + } break; + + // ---------------------------------------------- + // dictionary + case 'd': + { + ret = entry(entry::dictionary_t); + ++in; // 'd' + while (*in != 'e') + { + entry key; + bdecode_recursive(in, end, key); + entry& e = ret[key.string()]; + bdecode_recursive(in, end, e); + if (in == end) throw invalid_encoding(); + } + assert(*in == 'e'); + ++in; // 'e' + } break; + + // ---------------------------------------------- + // string + default: + if (isdigit((unsigned char)*in)) + { + std::string len_s = read_until(in, end, ':'); + assert(*in == ':'); + ++in; // ':' + int len = std::atoi(len_s.c_str()); + ret = entry(entry::string_t); + read_string(in, end, len, ret.string()); + } + else + { + throw invalid_encoding(); + } + } + } + } + + template + void bencode(OutIt out, const entry& e) + { + detail::bencode_recursive(out, e); + } + + template + entry bdecode(InIt start, InIt end) + { + try + { + entry e; + detail::bdecode_recursive(start, end, e); + return e; + } + catch(type_error&) + { + throw invalid_encoding(); + } + } + +} + +#endif // TORRENT_BENCODE_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp new file mode 100755 index 000000000..0f5e58e9d --- /dev/null +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,379 @@ +/* + +Copyright (c) 2003 - 2006, Arvid Norberg +Copyright (c) 2007, Arvid Norberg, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/pe_crypto.hpp" + +namespace libtorrent +{ + class torrent; + + namespace detail + { + struct session_impl; + } + + class TORRENT_EXPORT bt_peer_connection + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + bt_peer_connection( + aux::session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote + , policy::peer* peerinfo); + + // with this constructor we have been contacted and we still don't + // know which torrent the connection belongs to + bt_peer_connection( + aux::session_impl& ses + , boost::shared_ptr s + , policy::peer* peerinfo); + + ~bt_peer_connection(); + +#ifndef TORRENT_DISABLE_ENCRYPTION + bool supports_encryption() const + { return m_encrypted; } +#endif + + enum message_type + { + // standard messages + msg_choke = 0, + msg_unchoke, + msg_interested, + msg_not_interested, + msg_have, + msg_bitfield, + msg_request, + msg_piece, + msg_cancel, + msg_dht_port, + // extension protocol message + msg_extended = 20, + + num_supported_messages + }; + + // called from the main loop when this connection has any + // work to do. + + void on_sent(asio::error_code const& error + , std::size_t bytes_transferred); + void on_receive(asio::error_code const& error + , std::size_t bytes_transferred); + + virtual void get_specific_peer_info(peer_info& p) const; + virtual bool in_handshake() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool support_extensions() const { return m_supports_extensions; } + + template + T* supports_extension() const + { + for (extension_list_t::const_iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + T* ret = dynamic_cast(i->get()); + if (ret) return ret; + } + return 0; + } +#endif + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void on_keepalive(); + void on_choke(int received); + void on_unchoke(int received); + void on_interested(int received); + void on_not_interested(int received); + void on_have(int received); + void on_bitfield(int received); + void on_request(int received); + void on_piece(int received); + void on_cancel(int received); + void on_dht_port(int received); + + void on_extended(int received); + + void on_extended_handshake(); + + typedef void (bt_peer_connection::*message_handler)(int received); + + // the following functions appends messages + // to the send buffer + void write_choke(); + void write_unchoke(); + void write_interested(); + void write_not_interested(); + void write_request(peer_request const& r); + void write_cancel(peer_request const& r); + void write_bitfield(std::vector const& bitfield); + void write_have(int index); + void write_piece(peer_request const& r, char const* buffer); + void write_handshake(); +#ifndef TORRENT_DISABLE_EXTENSIONS + void write_extensions(); +#endif + void write_chat_message(const std::string& msg); + void write_metadata(std::pair req); + void write_metadata_request(std::pair req); + void write_keepalive(); + void write_dht_port(int listen_port); + void on_connected() {} + void on_metadata(); + +#ifndef NDEBUG + void check_invariant() const; + ptime m_last_choke; +#endif + + private: + + bool dispatch_message(int received); + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + boost::optional downloading_piece_progress() const; + +#ifndef TORRENT_DISABLE_ENCRYPTION + + // if (is_local()), we are 'a' otherwise 'b' + // + // 1. a -> b dhkey, pad + // 2. b -> a dhkey, pad + // 3. a -> b sync, payload + // 4. b -> a sync, payload + // 5. a -> b payload + + void write_pe1_2_dhkey(); + void write_pe3_sync(); + void write_pe4_sync(int crypto_select); + + void write_pe_vc_cryptofield(buffer::interval& write_buf, + int crypto_field, int pad_size); + + // stream key (info hash of attached torrent) + // secret is the DH shared secret + // initializes m_RC4_handler + void init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key); + + // these functions encrypt the send buffer if m_rc4_encrypted + // is true, otherwise it passes the call to the + // peer_connection functions of the same names + void send_buffer(char* begin, char* end); + buffer::interval allocate_send_buffer(int size); + void setup_send(); + + // Returns offset at which bytestream (src, src + src_size) + // matches bytestream(target, target + target_size). + // If no sync found, return -1 + int get_syncoffset(char const* src, int src_size, + char const* target, int target_size) const; +#endif + + enum state + { +#ifndef TORRENT_DISABLE_ENCRYPTION + read_pe_dhkey = 0, + read_pe_syncvc, + read_pe_synchash, + read_pe_skey_vc, + read_pe_cryptofield, + read_pe_pad, + read_pe_ia, + init_bt_handshake, + read_protocol_identifier, +#else + read_protocol_identifier = 0, +#endif + read_info_hash, + read_peer_id, + + // handshake complete + read_packet_size, + read_packet + }; + +#ifndef TORRENT_DISABLE_ENCRYPTION + enum + { + handshake_len = 68, + dh_key_len = 96 + }; +#endif + + std::string m_client_version; + + // state of on_receive + state m_state; + + // the timeout in seconds + int m_timeout; + + static const message_handler m_message_handler[num_supported_messages]; + + // this is a queue of ranges that describes + // where in the send buffer actual payload + // data is located. This is currently + // only used to be able to gather statistics + // seperately on payload and protocol data. + struct range + { + range(int s, int l) + : start(s) + , length(l) + { + assert(s >= 0); + assert(l > 0); + } + int start; + int length; + }; + static bool range_below_zero(const range& r) + { return r.start < 0; } + std::deque m_payloads; + +#ifndef TORRENT_DISABLE_EXTENSIONS + // this is set to true if the handshake from + // the peer indicated that it supports the + // extension protocol + bool m_supports_extensions; +#endif + bool m_supports_dht_port; + +#ifndef TORRENT_DISABLE_ENCRYPTION + // this is set to true after the encryption method has been + // succesfully negotiated (either plaintext or rc4), to signal + // automatic encryption/decryption. + bool m_encrypted; + + // true if rc4, false if plaintext + bool m_rc4_encrypted; + + // used to disconnect peer if sync points are not found within + // the maximum number of bytes + int m_sync_bytes_read; + + // hold information about latest allocated send buffer + // need to check for non zero (begin, end) for operations with this + buffer::interval m_enc_send_buffer; + + // initialized during write_pe1_2_dhkey, and destroyed on + // creation of m_RC4_handler. Cannot reinitialize once + // initialized. + boost::scoped_ptr m_DH_key_exchange; + + // if RC4 is negotiated, this is used for + // encryption/decryption during the entire session. Destroyed + // if plaintext is selected + boost::scoped_ptr m_RC4_handler; + + // (outgoing only) synchronize verification constant with + // remote peer, this will hold RC4_decrypt(vc). Destroyed + // after the sync step. + boost::scoped_array m_sync_vc; + + // (incoming only) synchronize hash with remote peer, holds + // the sync hash (hash("req1",secret)). Destroyed after the + // sync step. + boost::scoped_ptr m_sync_hash; +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION + +#ifndef NDEBUG + // this is set to true when the client's + // bitfield is sent to this peer + bool m_sent_bitfield; + + bool m_in_constructor; +#endif + + }; +} + +#endif // TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp new file mode 100644 index 000000000..0cb44225a --- /dev/null +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -0,0 +1,453 @@ +/* +Copyright (c) 2003 - 2005, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_BUFFER_HPP +#define LIBTORRENT_BUFFER_HPP + +//#define TORRENT_BUFFER_DEBUG + +#include "libtorrent/invariant_check.hpp" +#include + +namespace libtorrent { + +class buffer +{ +public: + struct interval + { + interval(char* begin, char* end) + : begin(begin) + , end(end) + {} + + char operator[](int index) const + { + assert(begin + index < end); + return begin[index]; + } + + int left() const { assert(end >= begin); return end - begin; } + + char* begin; + char* end; + }; + + struct const_interval + { + const_interval(char const* begin, char const* end) + : begin(begin) + , end(end) + {} + + char operator[](int index) const + { + assert(begin + index < end); + return begin[index]; + } + + bool operator==(const const_interval& p_interval) + { + return (begin == p_interval.begin + && end == p_interval.end); + } + + int left() const { assert(end >= begin); return end - begin; } + + char const* begin; + char const* end; + }; + + typedef std::pair interval_type; + + buffer(std::size_t n = 0); + ~buffer(); + + interval allocate(std::size_t n); + void insert(char const* first, char const* last); + void erase(std::size_t n); + std::size_t size() const; + std::size_t capacity() const; + void reserve(std::size_t n); + interval_type data() const; + bool empty() const; + + std::size_t space_left() const; + + char const* raw_data() const + { + return m_first; + } + +#ifndef NDEBUG + void check_invariant() const; +#endif + +private: + char* m_first; + char* m_last; + char* m_write_cursor; + char* m_read_cursor; + char* m_read_end; + bool m_empty; +#ifdef TORRENT_BUFFER_DEBUG + mutable std::vector m_debug; + mutable int m_pending_copy; +#endif +}; + +inline buffer::buffer(std::size_t n) + : m_first((char*)::operator new(n)) + , m_last(m_first + n) + , m_write_cursor(m_first) + , m_read_cursor(m_first) + , m_read_end(m_last) + , m_empty(true) +{ +#ifdef TORRENT_BUFFER_DEBUG + m_pending_copy = 0; +#endif +} + +inline buffer::~buffer() +{ + ::operator delete (m_first); +} + +inline buffer::interval buffer::allocate(std::size_t n) +{ + assert(m_read_cursor <= m_read_end || m_empty); + + INVARIANT_CHECK; + +#ifdef TORRENT_BUFFER_DEBUG + if (m_pending_copy) + { + std::copy(m_write_cursor - m_pending_copy, m_write_cursor + , m_debug.end() - m_pending_copy); + m_pending_copy = 0; + } + m_debug.resize(m_debug.size() + n); + m_pending_copy = n; +#endif + if (m_read_cursor < m_write_cursor || m_empty) + { + // ..R***W.. + if (m_last - m_write_cursor >= (std::ptrdiff_t)n) + { + interval ret(m_write_cursor, m_write_cursor + n); + m_write_cursor += n; + m_read_end = m_write_cursor; + assert(m_read_cursor <= m_read_end); + if (n) m_empty = false; + return ret; + } + + if (m_read_cursor - m_first >= (std::ptrdiff_t)n) + { + m_read_end = m_write_cursor; + interval ret(m_first, m_first + n); + m_write_cursor = m_first + n; + assert(m_read_cursor <= m_read_end); + if (n) m_empty = false; + return ret; + } + + reserve(capacity() + n - (m_last - m_write_cursor)); + assert(m_last - m_write_cursor >= (std::ptrdiff_t)n); + interval ret(m_write_cursor, m_write_cursor + n); + m_write_cursor += n; + m_read_end = m_write_cursor; + if (n) m_empty = false; + assert(m_read_cursor <= m_read_end); + return ret; + + } + //**W...R** + if (m_read_cursor - m_write_cursor >= (std::ptrdiff_t)n) + { + interval ret(m_write_cursor, m_write_cursor + n); + m_write_cursor += n; + if (n) m_empty = false; + return ret; + } + reserve(capacity() + n - (m_read_cursor - m_write_cursor)); + assert(m_read_cursor - m_write_cursor >= (std::ptrdiff_t)n); + interval ret(m_write_cursor, m_write_cursor + n); + m_write_cursor += n; + if (n) m_empty = false; + return ret; +} + +inline void buffer::insert(char const* first, char const* last) +{ + INVARIANT_CHECK; + + std::size_t n = last - first; + +#ifdef TORRENT_BUFFER_DEBUG + if (m_pending_copy) + { + std::copy(m_write_cursor - m_pending_copy, m_write_cursor + , m_debug.end() - m_pending_copy); + m_pending_copy = 0; + } + m_debug.insert(m_debug.end(), first, last); +#endif + + if (space_left() < n) + { + reserve(capacity() + n); + } + + m_empty = false; + + char const* end = (m_last - m_write_cursor) < (std::ptrdiff_t)n ? + m_last : m_write_cursor + n; + + std::size_t copied = end - m_write_cursor; + std::memcpy(m_write_cursor, first, copied); + + m_write_cursor += copied; + if (m_write_cursor > m_read_end) m_read_end = m_write_cursor; + first += copied; + n -= copied; + + if (n == 0) return; + + assert(m_write_cursor == m_last); + m_write_cursor = m_first; + + memcpy(m_write_cursor, first, n); + m_write_cursor += n; +} + +inline void buffer::erase(std::size_t n) +{ + INVARIANT_CHECK; + + if (n == 0) return; + assert(!m_empty); + +#ifndef NDEBUG + int prev_size = size(); +#endif + assert(m_read_cursor <= m_read_end); + m_read_cursor += n; + if (m_read_cursor > m_read_end) + { + m_read_cursor = m_first + (m_read_cursor - m_read_end); + assert(m_read_cursor <= m_write_cursor); + } + + m_empty = m_read_cursor == m_write_cursor; + + assert(prev_size - n == size()); + +#ifdef TORRENT_BUFFER_DEBUG + m_debug.erase(m_debug.begin(), m_debug.begin() + n); +#endif +} + +inline std::size_t buffer::size() const +{ + // ...R***W. + if (m_read_cursor < m_write_cursor) + { + return m_write_cursor - m_read_cursor; + } + // ***W..R* + else + { + if (m_empty) return 0; + return (m_write_cursor - m_first) + (m_read_end - m_read_cursor); + } +} + +inline std::size_t buffer::capacity() const +{ + return m_last - m_first; +} + +inline void buffer::reserve(std::size_t size) +{ + std::size_t n = (std::size_t)(capacity() * 1.f); + if (n < size) n = size; + + char* buf = (char*)::operator new(n); + char* old = m_first; + + if (m_read_cursor < m_write_cursor) + { + // ...R***W.<>. + std::memcpy( + buf + (m_read_cursor - m_first) + , m_read_cursor + , m_write_cursor - m_read_cursor + ); + + m_write_cursor = buf + (m_write_cursor - m_first); + m_read_cursor = buf + (m_read_cursor - m_first); + m_read_end = m_write_cursor; + m_first = buf; + m_last = buf + n; + } + else + { + // **W..<>.R** + std::size_t skip = n - (m_last - m_first); + + std::memcpy(buf, m_first, m_write_cursor - m_first); + std::memcpy( + buf + (m_read_cursor - m_first) + skip + , m_read_cursor + , m_last - m_read_cursor + ); + + m_write_cursor = buf + (m_write_cursor - m_first); + + if (!m_empty) + { + m_read_cursor = buf + (m_read_cursor - m_first) + skip; + m_read_end = buf + (m_read_end - m_first) + skip; + } + else + { + m_read_cursor = m_write_cursor; + m_read_end = m_write_cursor; + } + + m_first = buf; + m_last = buf + n; + } + + ::operator delete (old); +} + +#ifndef NDEBUG +inline void buffer::check_invariant() const +{ + assert(m_read_end >= m_read_cursor); + assert(m_last >= m_read_cursor); + assert(m_last >= m_write_cursor); + assert(m_last >= m_first); + assert(m_first <= m_read_cursor); + assert(m_first <= m_write_cursor); +#ifdef TORRENT_BUFFER_DEBUG + int a = m_debug.size(); + int b = size(); + (void)a; + (void)b; + assert(m_debug.size() == size()); +#endif +} +#endif + +inline buffer::interval_type buffer::data() const +{ + INVARIANT_CHECK; + +#ifdef TORRENT_BUFFER_DEBUG + if (m_pending_copy) + { + std::copy(m_write_cursor - m_pending_copy, m_write_cursor + , m_debug.end() - m_pending_copy); + m_pending_copy = 0; + } +#endif + + // ...R***W. + if (m_read_cursor < m_write_cursor) + { +#ifdef TORRENT_BUFFER_DEBUG + assert(m_debug.size() == size()); + assert(std::equal(m_debug.begin(), m_debug.end(), m_read_cursor)); +#endif + return interval_type( + const_interval(m_read_cursor, m_write_cursor) + , const_interval(m_last, m_last) + ); + } + // **W...R** + else + { + if (m_read_cursor == m_read_end) + { +#ifdef TORRENT_BUFFER_DEBUG + assert(m_debug.size() == size()); + assert(std::equal(m_debug.begin(), m_debug.end(), m_first)); +#endif + + return interval_type( + const_interval(m_first, m_write_cursor) + , const_interval(m_last, m_last)); + } +#ifdef TORRENT_BUFFER_DEBUG + assert(m_debug.size() == size()); + assert(std::equal(m_debug.begin(), m_debug.begin() + (m_read_end + - m_read_cursor), m_read_cursor)); + assert(std::equal(m_debug.begin() + (m_read_end - m_read_cursor), m_debug.end() + , m_first)); +#endif + + assert(m_read_cursor <= m_read_end || m_empty); + return interval_type( + const_interval(m_read_cursor, m_read_end) + , const_interval(m_first, m_write_cursor) + ); + } +} + +inline bool buffer::empty() const +{ + return m_empty; +} + +inline std::size_t buffer::space_left() const +{ + if (m_empty) return m_last - m_first; + + // ...R***W. + if (m_read_cursor < m_write_cursor) + { + return (m_last - m_write_cursor) + (m_read_cursor - m_first); + } + // ***W..R* + else + { + return m_read_cursor - m_write_cursor; + } +} + +} + +#endif // LIBTORRENT_BUFFER_HPP + diff --git a/libtorrent/include/libtorrent/config.hpp b/libtorrent/include/libtorrent/config.hpp new file mode 100755 index 000000000..c8d86955e --- /dev/null +++ b/libtorrent/include/libtorrent/config.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2005, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONFIG_HPP_INCLUDED +#define TORRENT_CONFIG_HPP_INCLUDED + +#include + +#if defined(__GNUC__) && __GNUC__ >= 4 + +# if defined(TORRENT_BUILDING_SHARED) || defined(TORRENT_LINKING_SHARED) +# define TORRENT_EXPORT __attribute__ ((visibility("default"))) +# else +# define TORRENT_EXPORT +# endif + +#elif defined(__GNUC__) + +# define TORRENT_EXPORT + +#elif defined(BOOST_MSVC) + +# if defined(TORRENT_BUILDING_SHARED) +# define TORRENT_EXPORT __declspec(dllexport) +# elif defined(TORRENT_LINKING_SHARED) +# define TORRENT_EXPORT __declspec(dllimport) +# else +# define TORRENT_EXPORT +# endif + +#else +# define TORRENT_EXPORT +#endif + + +#endif // TORRENT_CONFIG_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp new file mode 100644 index 000000000..05a8a61fe --- /dev/null +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONNECTION_QUEUE +#define TORRENT_CONNECTION_QUEUE + +#include +#include +#include +#include "libtorrent/socket.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + +class connection_queue : public boost::noncopyable +{ +public: + connection_queue(io_service& ios); + + bool free_slots() const; + + void enqueue(boost::function const& on_connect + , boost::function const& on_timeout + , time_duration timeout); + void done(int ticket); + void limit(int limit); + int limit() const; + +#ifndef NDEBUG + + void check_invariant() const; + +#endif + +private: + + void try_connect(); + void on_timeout(asio::error_code const& e); + + struct entry + { + entry(): connecting(false), ticket(0), expires(max_time()) {} + // called when the connection is initiated + boost::function on_connect; + // called if done hasn't been called within the timeout + boost::function on_timeout; + bool connecting; + int ticket; + ptime expires; + time_duration timeout; + }; + + std::list m_queue; + + // the next ticket id a connection will be given + int m_next_ticket; + int m_num_connecting; + int m_half_open_limit; + + deadline_timer m_timer; +}; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp new file mode 100755 index 000000000..436b695f6 --- /dev/null +++ b/libtorrent/include/libtorrent/debug.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEBUG_HPP_INCLUDED +#define TORRENT_DEBUG_HPP_INCLUDED + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +namespace libtorrent +{ + // DEBUG API + + namespace fs = boost::filesystem; + + struct logger + { + logger(fs::path const& filename, int instance, bool append = true) + { + fs::path dir(fs::complete("libtorrent_logs" + boost::lexical_cast(instance))); + if (!fs::exists(dir)) fs::create_directories(dir); + m_file.open((dir / filename).string().c_str(), std::ios_base::out | (append ? std::ios_base::app : std::ios_base::out)); + *this << "\n\n\n*** starting log ***\n"; + } + + template + logger& operator<<(T const& v) + { + m_file << v; + m_file.flush(); + return *this; + } + + std::ofstream m_file; + }; + +} + +#endif // TORRENT_DEBUG_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp new file mode 100644 index 000000000..aff0930e4 --- /dev/null +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -0,0 +1,119 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/storage.hpp" +#include +#include +#include +#include +#include +#include +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + struct disk_io_job + { + disk_io_job() + : action(read) + , buffer(0) + , buffer_size(0) + , piece(0) + , offset(0) + {} + + enum action_t + { + read + , write + , hash + , move_storage + , release_files + }; + + action_t action; + + char* buffer; + size_type buffer_size; + boost::intrusive_ptr storage; + // arguments used for read and write + int piece, offset; + // used for move_storage. On errors, this is set + // to the error message + std::string str; + + // this is called when operation completes + boost::function callback; + }; + + // this is a singleton consisting of the thread and a queue + // of disk io jobs + struct disk_io_thread : boost::noncopyable + { + disk_io_thread(int block_size = 16 * 1024); + ~disk_io_thread(); + + // aborts read operations + void stop(boost::intrusive_ptr s); + void add_job(disk_io_job const& j + , boost::function const& f + = boost::function()); + + // keep track of the number of bytes in the job queue + // at any given time. i.e. the sum of all buffer_size. + // this is used to slow down the download global download + // speed when the queue buffer size is too big. + size_type queue_buffer_size() const + { return m_queue_buffer_size; } + + void operator()(); + + char* allocate_buffer(); + + private: + + boost::mutex m_mutex; + boost::condition m_signal; + bool m_abort; + std::deque m_jobs; + size_type m_queue_buffer_size; + + // memory pool for read and write operations + boost::pool<> m_pool; + + // thread for performing blocking disk io operations + boost::thread m_disk_io_thread; + }; + +} + diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp new file mode 100755 index 000000000..a1eba5324 --- /dev/null +++ b/libtorrent/include/libtorrent/entry.hpp @@ -0,0 +1,278 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENTRY_HPP_INCLUDED +#define TORRENT_ENTRY_HPP_INCLUDED + +/* + * + * This file declares the entry class. It is a + * variant-type that can be an integer, list, + * dictionary (map) or a string. This type is + * used to hold bdecoded data (which is the + * encoding BitTorrent messages uses). + * + * it has 4 accessors to access the actual + * type of the object. They are: + * integer() + * string() + * list() + * dict() + * The actual type has to match the type you + * are asking for, otherwise you will get an + * assertion failure. + * When you default construct an entry, it is + * uninitialized. You can initialize it through the + * assignment operator, copy-constructor or + * the constructor that takes a data_type enum. + * + * + */ + + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/size_type.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + struct TORRENT_EXPORT type_error: std::runtime_error + { + type_error(const char* error): std::runtime_error(error) {} + }; + + namespace detail + { + template + struct max2 { enum { value = v1>v2?v1:v2 }; }; + + template + struct max3 + { + enum + { + temp = max2::value, + value = temp>v3?temp:v3 + }; + }; + + template + struct max4 + { + enum + { + temp = max3::value, + value = temp>v4?temp:v4 + }; + }; + } + + class entry; + + class TORRENT_EXPORT entry + { + public: + + // the key is always a string. If a generic entry would be allowed + // as a key, sorting would become a problem (e.g. to compare a string + // to a list). The definition doesn't mention such a limit though. + typedef std::map dictionary_type; + typedef std::string string_type; + typedef std::list list_type; + typedef size_type integer_type; + + enum data_type + { + int_t, + string_t, + list_t, + dictionary_t, + undefined_t + }; + + data_type type() const; + + entry(dictionary_type const&); + entry(string_type const&); + entry(list_type const&); + entry(integer_type const&); + + entry(); + entry(data_type t); + entry(entry const& e); + ~entry(); + + bool operator==(entry const& e) const; + + void operator=(entry const&); + void operator=(dictionary_type const&); + void operator=(string_type const&); + void operator=(list_type const&); + void operator=(integer_type const&); + + integer_type& integer(); + const integer_type& integer() const; + string_type& string(); + const string_type& string() const; + list_type& list(); + const list_type& list() const; + dictionary_type& dict(); + const dictionary_type& dict() const; + + void swap(entry& e); + + // these functions requires that the entry + // is a dictionary, otherwise they will throw + entry& operator[](char const* key); + entry& operator[](std::string const& key); + const entry& operator[](char const* key) const; + const entry& operator[](std::string const& key) const; + entry* find_key(char const* key); + entry const* find_key(char const* key) const; + + void print(std::ostream& os, int indent = 0) const; + + private: + + void construct(data_type t); + void copy(const entry& e); + void destruct(); + + data_type m_type; + +#if defined(_MSC_VER) && _MSC_VER < 1310 + // workaround for msvc-bug. + // assumes sizeof(map) == sizeof(map) + // and sizeof(list) == sizeof(list) + union + { + char data[ + detail::max4) + , sizeof(std::map) + , sizeof(string_type) + , sizeof(integer_type)>::value]; + integer_type dummy_aligner; + }; +#else + union + { + char data[detail::max4::value]; + integer_type dummy_aligner; + }; +#endif + + }; + + inline std::ostream& operator<<(std::ostream& os, const entry& e) + { + e.print(os, 0); + return os; + } + + inline entry::data_type entry::type() const { return m_type; } + + inline entry::entry(): m_type(undefined_t) {} + inline entry::entry(data_type t): m_type(t) { construct(t); } + inline entry::entry(const entry& e) { copy(e); } + inline entry::~entry() { destruct(); } + + inline void entry::operator=(const entry& e) + { + destruct(); + copy(e); + } + + inline entry::integer_type& entry::integer() + { + if (m_type == undefined_t) construct(int_t); + if (m_type != int_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::integer_type const& entry::integer() const + { + if (m_type != int_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::string_type& entry::string() + { + if (m_type == undefined_t) construct(string_t); + if (m_type != string_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::string_type const& entry::string() const + { + if (m_type != string_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::list_type& entry::list() + { + if (m_type == undefined_t) construct(list_t); + if (m_type != list_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::list_type const& entry::list() const + { + if (m_type != list_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::dictionary_type& entry::dict() + { + if (m_type == undefined_t) construct(dictionary_t); + if (m_type != dictionary_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + + inline entry::dictionary_type const& entry::dict() const + { + if (m_type != dictionary_t) throw type_error("invalid type requested from entry"); + return *reinterpret_cast(data); + } + +} + +#endif // TORRENT_ENTRY_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/escape_string.hpp b/libtorrent/include/libtorrent/escape_string.hpp new file mode 100755 index 000000000..e0e743e1e --- /dev/null +++ b/libtorrent/include/libtorrent/escape_string.hpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ESCAPE_STRING_HPP_INCLUDED +#define TORRENT_ESCAPE_STRING_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + std::string TORRENT_EXPORT unescape_string(std::string const& s); + std::string TORRENT_EXPORT escape_string(const char* str, int len); + std::string TORRENT_EXPORT escape_path(const char* str, int len); +} + +#endif // TORRENT_ESCAPE_STRING_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/extensions.hpp b/libtorrent/include/libtorrent/extensions.hpp new file mode 100644 index 000000000..5f8172649 --- /dev/null +++ b/libtorrent/include/libtorrent/extensions.hpp @@ -0,0 +1,176 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_EXTENSIONS_HPP_INCLUDED +#define TORRENT_EXTENSIONS_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" + +namespace libtorrent +{ + struct peer_plugin; + class bt_peer_connection; + struct peer_request; + class peer_connection; + class entry; + + struct TORRENT_EXPORT torrent_plugin + { + virtual ~torrent_plugin() {} + // throwing an exception closes the connection + // returning a 0 pointer is valid and will not add + // the peer_plugin to the peer_connection + virtual boost::shared_ptr new_connection(peer_connection*) + { return boost::shared_ptr(); } + + virtual void on_piece_pass(int index) {} + virtual void on_piece_failed(int index) {} + + // called aproximately once every second + virtual void tick() {} + + // if true is returned, it means the handler handled the event, + // and no other plugins will have their handlers called, and the + // default behavior will be skipped + virtual bool on_pause() { return false; } + virtual bool on_resume() { return false;} + + // this is called when the initial checking of + // files is completed. + virtual void on_files_checked() {} + }; + + struct TORRENT_EXPORT peer_plugin + { + virtual ~peer_plugin() {} + + // can add entries to the extension handshake + // this is not called for web seeds + virtual void add_handshake(entry&) {} + + // throwing an exception from any of the handlers (except add_handshake) + // closes the connection + + // this is called when the initial BT handshake is received. Returning false + // means that the other end doesn't support this extension and will remove + // it from the list of plugins. + // this is not called for web seeds + virtual bool on_handshake() { return true; } + + // called when the extension handshake from the other end is received + // if this returns false, it means that this extension isn't + // supported by this peer. It will result in this peer_plugin + // being removed from the peer_connection and destructed. + // this is not called for web seeds + virtual bool on_extension_handshake(entry const& h) { return true; } + + // returning true from any of the message handlers + // indicates that the plugin has handeled the message. + // it will break the plugin chain traversing and not let + // anyone else handle the message, including the default + // handler. + + virtual bool on_choke() + { return false; } + + virtual bool on_unchoke() + { return false; } + + virtual bool on_interested() + { return false; } + + virtual bool on_not_interested() + { return false; } + + virtual bool on_have(int index) + { return false; } + + virtual bool on_bitfield(std::vector const& bitfield) + { return false; } + + virtual bool on_request(peer_request const& req) + { return false; } + + virtual bool on_piece(peer_request const& piece, char const* data) + { return false; } + + virtual bool on_cancel(peer_request const& req) + { return false; } + + // called when an extended message is received. If returning true, + // the message is not processed by any other plugin and if false + // is returned the next plugin in the chain will receive it to + // be able to handle it + // this is not called for web seeds + virtual bool on_extended(int length + , int msg, buffer::const_interval body) + { return false; } + + // this is not called for web seeds + virtual bool on_unknown_message(int length, int msg + , buffer::const_interval body) + { return false; } + + // called when a piece that this peer participated in either + // fails or passes the hash_check + virtual void on_piece_pass(int index) {} + virtual void on_piece_failed(int index) {} + + // called aproximately once every second + virtual void tick() {} + + // called each time a request message is to be sent. If true + // is returned, the original request message won't be sent and + // no other plugin will have this function called. + virtual bool write_request(peer_request const& r) { return false; } + }; + +} + +#endif + +#endif // TORRENT_EXTENSIONS_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/extensions/logger.hpp b/libtorrent/include/libtorrent/extensions/logger.hpp new file mode 100644 index 000000000..42e08fcf6 --- /dev/null +++ b/libtorrent/include/libtorrent/extensions/logger.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LOGGER_HPP_INCLUDED +#define TORRENT_LOGGER_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace libtorrent +{ + struct torrent_plugin; + class torrent; + boost::shared_ptr create_logger_plugin(torrent*); +} + +#endif // TORRENT_LOGGER_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp new file mode 100644 index 000000000..210642161 --- /dev/null +++ b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_METADATA_TRANSFER_HPP_INCLUDED +#define TORRENT_METADATA_TRANSFER_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include "libtorrent/config.hpp" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace libtorrent +{ + struct torrent_plugin; + class torrent; + TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*); +} + +#endif // TORRENT_METADATA_TRANSFER_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/extensions/ut_pex.hpp b/libtorrent/include/libtorrent/extensions/ut_pex.hpp new file mode 100644 index 000000000..efd9ab4f6 --- /dev/null +++ b/libtorrent/include/libtorrent/extensions/ut_pex.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2006, MassaRoddel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED +#define TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include "libtorrent/config.hpp" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace libtorrent +{ + struct torrent_plugin; + class torrent; + TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*); +} + +#endif // TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/file.hpp b/libtorrent/include/libtorrent/file.hpp new file mode 100755 index 000000000..bd0d03539 --- /dev/null +++ b/libtorrent/include/libtorrent/file.hpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_HPP_INCLUDED +#define TORRENT_FILE_HPP_INCLUDED + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/size_type.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + namespace fs = boost::filesystem; + + struct TORRENT_EXPORT file_error: std::runtime_error + { + file_error(std::string const& msg): std::runtime_error(msg) {} + }; + + class TORRENT_EXPORT file: public boost::noncopyable + { + public: + + class seek_mode + { + friend class file; + private: + seek_mode(int v): m_val(v) {} + int m_val; + }; + + static const seek_mode begin; + static const seek_mode end; + + class open_mode + { + friend class file; + public: + + open_mode(): m_mask(0) {} + + open_mode operator|(open_mode m) const + { return open_mode(m.m_mask | m_mask); } + + open_mode operator&(open_mode m) const + { return open_mode(m.m_mask & m_mask); } + + open_mode operator|=(open_mode m) + { + m_mask |= m.m_mask; + return *this; + } + + bool operator==(open_mode m) const { return m_mask == m.m_mask; } + bool operator!=(open_mode m) const { return m_mask != m.m_mask; } + + private: + + open_mode(int val): m_mask(val) {} + int m_mask; + }; + + static const open_mode in; + static const open_mode out; + + file(); + file(fs::path const& p, open_mode m); + ~file(); + + void open(fs::path const& p, open_mode m); + void close(); + void set_size(size_type size); + + size_type write(const char*, size_type num_bytes); + size_type read(char*, size_type num_bytes); + + size_type seek(size_type pos, seek_mode m = begin); + size_type tell(); + + private: + + struct impl; + const std::auto_ptr m_impl; + + }; + +} + +#endif // TORRENT_FILE_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/file_pool.hpp b/libtorrent/include/libtorrent/file_pool.hpp new file mode 100644 index 000000000..a22c26538 --- /dev/null +++ b/libtorrent/include/libtorrent/file_pool.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_POOL_HPP +#define TORRENT_FILE_POOL_HPP + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/file.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + + using boost::multi_index::multi_index_container; + using boost::multi_index::ordered_non_unique; + using boost::multi_index::ordered_unique; + using boost::multi_index::indexed_by; + using boost::multi_index::member; + namespace fs = boost::filesystem; + + struct TORRENT_EXPORT file_pool : boost::noncopyable + { + file_pool(int size = 40): m_size(size) {} + + boost::shared_ptr open_file(void* st, fs::path const& p, file::open_mode m); + void release(void* st); + void resize(int size); + + private: + int m_size; + + struct lru_file_entry + { + lru_file_entry(boost::shared_ptr const& f) + : file_ptr(f) + , last_use(time_now()) {} + mutable boost::shared_ptr file_ptr; + fs::path file_path; + void* key; + ptime last_use; + file::open_mode mode; + }; + + typedef multi_index_container< + lru_file_entry, indexed_by< + ordered_unique > + , ordered_non_unique > + , ordered_non_unique > + > + > file_set; + + file_set m_files; + boost::mutex m_mutex; + }; +} + +#endif diff --git a/libtorrent/include/libtorrent/fingerprint.hpp b/libtorrent/include/libtorrent/fingerprint.hpp new file mode 100755 index 000000000..d7e5a5fc6 --- /dev/null +++ b/libtorrent/include/libtorrent/fingerprint.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FINGERPRINT_HPP_INCLUDED +#define TORRENT_FINGERPRINT_HPP_INCLUDED + +#include +#include + +#include "libtorrent/peer_id.hpp" + +namespace libtorrent +{ + + struct fingerprint + { + fingerprint(const char* id_string, int major, int minor, int revision, int tag) + : major_version(major) + , minor_version(minor) + , revision_version(revision) + , tag_version(tag) + { + assert(id_string); + assert(major >= 0); + assert(minor >= 0); + assert(revision >= 0); + assert(tag >= 0); + assert(std::strlen(id_string) == 2); + name[0] = id_string[0]; + name[1] = id_string[1]; + } + + std::string to_string() const + { + std::stringstream s; + s << "-" << name[0] << name[1] + << version_to_char(major_version) + << version_to_char(minor_version) + << version_to_char(revision_version) + << version_to_char(tag_version) << "-"; + return s.str(); + } + + char name[2]; + int major_version; + int minor_version; + int revision_version; + int tag_version; + + private: + + char version_to_char(int v) const + { + if (v >= 0 && v < 10) return '0' + v; + else if (v >= 10) return 'A' + (v - 10); + assert(false); + return '0'; + } + + }; + +} + +#endif // TORRENT_FINGERPRINT_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/hasher.hpp b/libtorrent/include/libtorrent/hasher.hpp new file mode 100755 index 000000000..932f2b100 --- /dev/null +++ b/libtorrent/include/libtorrent/hasher.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER_HPP_INCLUDED +#define TORRENT_HASHER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/config.hpp" +#include "zlib.h" + +#ifdef TORRENT_USE_OPENSSL +extern "C" +{ +#include +} +#else +// from sha1.cpp +struct TORRENT_EXPORT SHA_CTX +{ + boost::uint32_t state[5]; + boost::uint32_t count[2]; + boost::uint8_t buffer[64]; +}; + +TORRENT_EXPORT void SHA1_Init(SHA_CTX* context); +TORRENT_EXPORT void SHA1_Update(SHA_CTX* context, boost::uint8_t const* data, boost::uint32_t len); +TORRENT_EXPORT void SHA1_Final(boost::uint8_t* digest, SHA_CTX* context); + +#endif + +namespace libtorrent +{ + + class adler32_crc + { + public: + adler32_crc(): m_adler(adler32(0, 0, 0)) {} + + void update(const char* data, int len) + { + assert(data != 0); + assert(len > 0); + m_adler = adler32(m_adler, (const Bytef*)data, len); + } + unsigned long final() const { return m_adler; } + void reset() { m_adler = adler32(0, 0, 0); } + + private: + + unsigned long m_adler; + + }; + + class hasher + { + public: + + hasher() { SHA1_Init(&m_context); } + hasher(const char* data, int len) + { + SHA1_Init(&m_context); + assert(data != 0); + assert(len > 0); + SHA1_Update(&m_context, reinterpret_cast(data), len); + } + void update(const char* data, int len) + { + assert(data != 0); + assert(len > 0); + SHA1_Update(&m_context, reinterpret_cast(data), len); + } + + sha1_hash final() + { + sha1_hash digest; + SHA1_Final(digest.begin(), &m_context); + return digest; + } + + void reset() { SHA1_Init(&m_context); } + + private: + + SHA_CTX m_context; + + }; +} + +#endif // TORRENT_HASHER_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp new file mode 100644 index 000000000..409213857 --- /dev/null +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -0,0 +1,154 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_CONNECTION +#define TORRENT_HTTP_CONNECTION + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + +typedef boost::function http_handler; + +// TODO: add bind interface + +// when bottled, the last two arguments to the handler +// will always be 0 +struct http_connection : boost::enable_shared_from_this, boost::noncopyable +{ + http_connection(asio::io_service& ios, connection_queue& cc + , http_handler handler, bool bottled = true) + : m_sock(ios) + , m_read_pos(0) + , m_resolver(ios) + , m_handler(handler) + , m_timer(ios) + , m_last_receive(time_now()) + , m_bottled(bottled) + , m_called(false) + , m_rate_limit(0) + , m_download_quota(0) + , m_limiter_timer_active(false) + , m_limiter_timer(ios) + , m_redirect(true) + , m_connection_ticket(-1) + , m_cc(cc) + { + assert(!m_handler.empty()); + } + + void rate_limit(int limit); + + int rate_limit() const + { return m_rate_limit; } + + std::string sendbuffer; + + void get(std::string const& url, time_duration timeout = seconds(30) + , bool handle_redirect = true); + + void start(std::string const& hostname, std::string const& port + , time_duration timeout, bool handle_redirect = true); + void close(); + +private: + + void on_resolve(asio::error_code const& e + , tcp::resolver::iterator i); + void connect(int ticket, tcp::endpoint target_address); + void on_connect_timeout(); + void on_connect(asio::error_code const& e +/* , tcp::resolver::iterator i*/); + void on_write(asio::error_code const& e); + void on_read(asio::error_code const& e, std::size_t bytes_transferred); + static void on_timeout(boost::weak_ptr p + , asio::error_code const& e); + void on_assign_bandwidth(asio::error_code const& e); + + std::vector m_recvbuffer; + tcp::socket m_sock; + int m_read_pos; + tcp::resolver m_resolver; + http_parser m_parser; + http_handler m_handler; + deadline_timer m_timer; + time_duration m_timeout; + ptime m_last_receive; + // bottled means that the handler is called once, when + // everything is received (and buffered in memory). + // non bottled means that once the headers have been + // received, data is streamed to the handler + bool m_bottled; + // set to true the first time the handler is called + bool m_called; + std::string m_hostname; + std::string m_port; + + // the current download limit, in bytes per second + // 0 is unlimited. + int m_rate_limit; + + // the number of bytes we are allowed to receive + int m_download_quota; + + // only hand out new quota 4 times a second if the + // quota is 0. If it isn't 0 wait for it to reach + // 0 and continue to hand out quota at that time. + bool m_limiter_timer_active; + + // the timer fires every 250 millisecond as long + // as all the quota was used. + deadline_timer m_limiter_timer; + + // if set to true, the connection should handle + // HTTP redirects. + bool m_redirect; + + int m_connection_ticket; + connection_queue& m_cc; +}; + +} + +#endif diff --git a/libtorrent/include/libtorrent/http_stream.hpp b/libtorrent/include/libtorrent/http_stream.hpp new file mode 100644 index 000000000..041b7c84f --- /dev/null +++ b/libtorrent/include/libtorrent/http_stream.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(asio::io_service& io_service) + : proxy_base(io_service) + , m_no_connect(false) + {} + + void set_no_connect(bool c) { m_no_connect = c; } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + typedef boost::function handler_type; + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send HTTP CONNECT method and possibly username+password + // 4. read CONNECT response + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + + tcp::resolver::query q(m_hostname + , boost::lexical_cast(m_port)); + m_resolver.async_resolve(q, boost::bind( + &http_stream::name_lookup, this, _1, _2, h)); + } + +private: + + void name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(asio::error_code const& e, boost::shared_ptr h); + void handshake1(asio::error_code const& e, boost::shared_ptr h); + void handshake2(asio::error_code const& e, boost::shared_ptr h); + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + + // this is true if the connection is HTTP based and + // want to talk directly to the proxy + bool m_no_connect; +}; + +} + +#endif diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp new file mode 100755 index 000000000..35d529504 --- /dev/null +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,178 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/socket.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/connection_queue.hpp" + +namespace libtorrent +{ + + class http_parser + { + public: + http_parser(); + template + T header(char const* key) const; + std::string const& protocol() const { return m_protocol; } + int status_code() const { return m_status_code; } + std::string message() const { return m_server_message; } + buffer::const_interval get_body() const; + bool header_finished() const { return m_state == read_body; } + bool finished() const { return m_finished; } + boost::tuple incoming(buffer::const_interval recv_buffer); + int body_start() const { return m_body_start_pos; } + int content_length() const { return m_content_length; } + + void reset(); + private: + int m_recv_pos; + int m_status_code; + std::string m_protocol; + std::string m_server_message; + + int m_content_length; + + enum { read_status, read_header, read_body } m_state; + + std::map m_header; + buffer::const_interval m_recv_buffer; + int m_body_start_pos; + + bool m_finished; + }; + + template + T http_parser::header(char const* key) const + { + std::map::const_iterator i + = m_header.find(key); + if (i == m_header.end()) return T(); + return boost::lexical_cast(i->second); + } + + class TORRENT_EXPORT http_tracker_connection + : public tracker_connection + { + friend class tracker_manager; + public: + + http_tracker_connection( + asio::strand& str + , connection_queue& cc + , tracker_manager& man + , tracker_request const& req + , std::string const& hostname + , unsigned short port + , std::string request + , address bind_infc + , boost::weak_ptr c + , session_settings const& stn + , proxy_settings const& ps + , std::string const& password = ""); + + private: + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + void on_response(); + + void init_send_buffer( + std::string const& hostname + , std::string const& request); + + void name_lookup(asio::error_code const& error, tcp::resolver::iterator i); + void connect(int ticket, tcp::endpoint target_address); + void connected(asio::error_code const& error); + void sent(asio::error_code const& error); + void receive(asio::error_code const& error + , std::size_t bytes_transferred); + + virtual void on_timeout(); + + void parse(const entry& e); + peer_entry extract_peer_info(const entry& e); + + tracker_manager& m_man; + http_parser m_parser; + + asio::strand& m_strand; + tcp::resolver m_name_lookup; + int m_port; + boost::shared_ptr m_socket; + int m_recv_pos; + std::vector m_buffer; + std::string m_send_buffer; + + session_settings const& m_settings; + proxy_settings const& m_proxy; + std::string m_password; + + bool m_timed_out; + + int m_connection_ticket; + connection_queue& m_cc; + }; + +} + +#endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/identify_client.hpp b/libtorrent/include/libtorrent/identify_client.hpp new file mode 100755 index 000000000..e8cb3b930 --- /dev/null +++ b/libtorrent/include/libtorrent/identify_client.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED +#define TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + TORRENT_EXPORT std::string identify_client(const peer_id& p); + TORRENT_EXPORT boost::optional client_fingerprint(peer_id const& p); + +} + +#endif // TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/instantiate_connection.hpp b/libtorrent/include/libtorrent/instantiate_connection.hpp new file mode 100644 index 000000000..49cb1fe18 --- /dev/null +++ b/libtorrent/include/libtorrent/instantiate_connection.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INSTANTIATE_CONNECTION +#define TORRENT_INSTANTIATE_CONNECTION + +#include "libtorrent/socket_type.hpp" +#include +#include + +namespace libtorrent +{ + struct proxy_settings; + + boost::shared_ptr instantiate_connection( + asio::io_service& ios, proxy_settings const& ps); +} + +#endif + diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp new file mode 100644 index 000000000..a432bc350 --- /dev/null +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INTRUSIVE_PTR_BASE +#define TORRENT_INTRUSIVE_PTR_BASE + +#include +#include +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + template + struct intrusive_ptr_base + { + friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) + { + assert(s->m_refs >= 0); + assert(s != 0); + ++s->m_refs; + } + + friend void intrusive_ptr_release(intrusive_ptr_base const* s) + { + assert(s->m_refs > 0); + assert(s != 0); + if (--s->m_refs == 0) + delete static_cast(s); + } + + int refcount() const { return m_refs; } + + intrusive_ptr_base(): m_refs(0) {} + private: + // reference counter for intrusive_ptr + mutable boost::detail::atomic_count m_refs; + }; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/invariant_check.hpp b/libtorrent/include/libtorrent/invariant_check.hpp new file mode 100755 index 000000000..c6eacf338 --- /dev/null +++ b/libtorrent/include/libtorrent/invariant_check.hpp @@ -0,0 +1,78 @@ +// Copyright Daniel Wallin 2004. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TORRENT_INVARIANT_ACCESS_HPP_INCLUDED +#define TORRENT_INVARIANT_ACCESS_HPP_INCLUDED + +#include + +namespace libtorrent +{ + + class invariant_access + { + public: + template + static void check_invariant(T const& self) + { + self.check_invariant(); + } + }; + + template + void check_invariant(T const& x) + { + invariant_access::check_invariant(x); + } + + struct invariant_checker {}; + + template + struct invariant_checker_impl : invariant_checker + { + invariant_checker_impl(T const& self_) + : self(self_) + { + try + { + check_invariant(self); + } + catch (...) + { + assert(false); + } + } + + ~invariant_checker_impl() + { + try + { + check_invariant(self); + } + catch (...) + { + assert(false); + } + } + + T const& self; + }; + + template + invariant_checker_impl make_invariant_checker(T const& x) + { + return invariant_checker_impl(x); + } +} + +#ifndef NDEBUG +#define INVARIANT_CHECK \ + invariant_checker const& _invariant_check = make_invariant_checker(*this); \ + (void)_invariant_check; \ + do {} while (false) +#else +#define INVARIANT_CHECK do {} while (false) +#endif + +#endif // TORRENT_INVARIANT_ACCESS_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/io.hpp b/libtorrent/include/libtorrent/io.hpp new file mode 100755 index 000000000..f73c3e290 --- /dev/null +++ b/libtorrent/include/libtorrent/io.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include +#include + +namespace libtorrent +{ + namespace detail + { + template struct type {}; + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianess + template + inline T read_impl(InIt& start, type) + { + T ret = 0; + for (int i = 0; i < (int)sizeof(T); ++i) + { + ret <<= 8; + ret |= static_cast(*start); + ++start; + } + return ret; + } + + template + inline void write_impl(T val, OutIt& start) + { + for (int i = (int)sizeof(T)-1; i >= 0; --i) + { + *start = static_cast((val >> (i * 8)) & 0xff); + ++start; + } + } + + // -- adaptors + + template + boost::int64_t read_int64(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint64_t read_uint64(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint32_t read_uint32(InIt& start) + { return read_impl(start, type()); } + + template + boost::int32_t read_int32(InIt& start) + { return read_impl(start, type()); } + + template + boost::int16_t read_int16(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint16_t read_uint16(InIt& start) + { return read_impl(start, type()); } + + template + boost::int8_t read_int8(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint8_t read_uint8(InIt& start) + { return read_impl(start, type()); } + + + template + void write_uint64(boost::uint64_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int64(boost::int64_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint32(boost::uint32_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int32(boost::int32_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint16(boost::uint16_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int16(boost::int16_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint8(boost::uint8_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int8(boost::int8_t val, OutIt& start) + { write_impl(val, start); } + + inline void write_string(std::string const& str, char*& start) + { + std::copy(str.begin(), str.end(), start); + start += str.size(); + } + + template + void write_string(std::string const& str, OutIt& start) + { + std::copy(str.begin(), str.end(), start); + } + + } +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/ip_filter.hpp b/libtorrent/include/libtorrent/ip_filter.hpp new file mode 100644 index 000000000..8b1793c3a --- /dev/null +++ b/libtorrent/include/libtorrent/ip_filter.hpp @@ -0,0 +1,314 @@ +/* + +Copyright (c) 2005, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" +#include +#include + +namespace libtorrent +{ + +inline bool operator<=(address const& lhs + , address const& rhs) +{ + return lhs < rhs || lhs == rhs; +} + +template +struct ip_range +{ + Addr first; + Addr last; + int flags; +}; + +namespace detail +{ + + template + Addr zero() + { + typename Addr::bytes_type zero; + std::fill(zero.begin(), zero.end(), 0); + return Addr(zero); + } + + template<> + inline boost::uint16_t zero() { return 0; } + + template + Addr plus_one(Addr const& a) + { + typename Addr::bytes_type tmp(a.to_bytes()); + typedef typename Addr::bytes_type::reverse_iterator iter; + for (iter i = tmp.rbegin() + , end(tmp.rend()); i != end; ++i) + { + if (*i < (std::numeric_limits::max)()) + { + *i += 1; + break; + } + *i = 0; + } + return Addr(tmp); + } + + inline boost::uint16_t plus_one(boost::uint16_t val) { return val + 1; } + + template + Addr minus_one(Addr const& a) + { + typename Addr::bytes_type tmp(a.to_bytes()); + typedef typename Addr::bytes_type::reverse_iterator iter; + for (iter i = tmp.rbegin() + , end(tmp.rend()); i != end; ++i) + { + if (*i > 0) + { + *i -= 1; + break; + } + *i = (std::numeric_limits::max)(); + } + return Addr(tmp); + } + + inline boost::uint16_t minus_one(boost::uint16_t val) { return val - 1; } + + template + Addr max_addr() + { + typename Addr::bytes_type tmp; + std::fill(tmp.begin(), tmp.end() + , (std::numeric_limits::max)()); + return Addr(tmp); + } + + template<> + inline boost::uint16_t max_addr() + { return (std::numeric_limits::max)(); } + + // this is the generic implementation of + // a filter for a specific address type. + // it works with IPv4 and IPv6 + template + class filter_impl + { + public: + + filter_impl() + { + // make the entire ip-range non-blocked + m_access_list.insert(range(zero(), 0)); + } + + void add_rule(Addr first, Addr last, int flags) + { + using boost::next; + using boost::prior; + + assert(!m_access_list.empty()); + assert(first < last || first == last); + + typename range_t::iterator i = m_access_list.upper_bound(first); + typename range_t::iterator j = m_access_list.upper_bound(last); + + if (i != m_access_list.begin()) --i; + + assert(j != m_access_list.begin()); + assert(j != i); + + int first_access = i->access; + int last_access = prior(j)->access; + + if (i->start != first && first_access != flags) + { + i = m_access_list.insert(i, range(first, flags)); + } + else if (i != m_access_list.begin() && prior(i)->access == flags) + { + --i; + first_access = i->access; + } + assert(!m_access_list.empty()); + assert(i != m_access_list.end()); + + if (i != j) m_access_list.erase(next(i), j); + if (i->start == first) + { + // we can do this const-cast because we know that the new + // start address will keep the set correctly ordered + const_cast(i->start) = first; + const_cast(i->access) = flags; + } + else if (first_access != flags) + { + m_access_list.insert(i, range(first, flags)); + } + + if ((j != m_access_list.end() + && minus_one(j->start) != last) + || (j == m_access_list.end() + && last != max_addr())) + { + assert(j == m_access_list.end() || last < minus_one(j->start)); + if (last_access != flags) + j = m_access_list.insert(j, range(plus_one(last), last_access)); + } + + if (j != m_access_list.end() && j->access == flags) m_access_list.erase(j); + assert(!m_access_list.empty()); + } + + int access(Addr const& addr) const + { + assert(!m_access_list.empty()); + typename range_t::const_iterator i = m_access_list.upper_bound(addr); + if (i != m_access_list.begin()) --i; + assert(i != m_access_list.end()); + assert(i->start <= addr && (boost::next(i) == m_access_list.end() + || addr < boost::next(i)->start)); + return i->access; + } + + std::vector > export_filter() const + { + std::vector > ret; + ret.reserve(m_access_list.size()); + + for (typename range_t::const_iterator i = m_access_list.begin() + , end(m_access_list.end()); i != end;) + { + ip_range r; + r.first = i->start; + r.flags = i->access; + + ++i; + if (i == end) + r.last = max_addr(); + else + r.last = minus_one(i->start); + + ret.push_back(r); + } + return ret; + } + + private: + + struct range + { + range(Addr addr, int access = 0): start(addr), access(access) {} + bool operator<(range const& r) const + { return start < r.start; } + bool operator<(Addr const& a) const + { return start < a; } + Addr start; + // the end of the range is implicit + // and given by the next entry in the set + int access; + }; + + typedef std::set range_t; + range_t m_access_list; + + }; + +} + +class TORRENT_EXPORT ip_filter +{ +public: + + enum access_flags + { + blocked = 1 + }; + + // both addresses MUST be of the same type (i.e. both must + // be either IPv4 or both must be IPv6) + void add_rule(address first, address last, int flags); + int access(address const& addr) const; + + typedef boost::tuple > + , std::vector > > filter_tuple_t; + + filter_tuple_t export_filter() const; + +// void print() const; + +private: + + detail::filter_impl m_filter4; + detail::filter_impl m_filter6; +}; + +class TORRENT_EXPORT port_filter +{ +public: + + enum access_flags + { + blocked = 1 + }; + + void add_rule(boost::uint16_t first, boost::uint16_t last, int flags); + int access(boost::uint16_t port) const; + +private: + + detail::filter_impl m_filter; + +}; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/kademlia/closest_nodes.hpp b/libtorrent/include/libtorrent/kademlia/closest_nodes.hpp new file mode 100644 index 000000000..244e4bb38 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/closest_nodes.hpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef CLOSEST_NODES_050323_HPP +#define CLOSEST_NODES_050323_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +namespace libtorrent { namespace dht +{ + +class rpc_manager; + +// -------- closest nodes ----------- + +class closest_nodes : public traversal_algorithm +{ +public: + typedef boost::function< + void(std::vector const&) + > done_callback; + + static void initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + +private: + void done(); + void invoke(node_id const& id, asio::ip::udp::endpoint addr); + + closest_nodes( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + + done_callback m_done_callback; +}; + +class closest_nodes_observer : public observer +{ +public: + closest_nodes_observer( + boost::intrusive_ptr const& algorithm + , node_id self + , node_id target) + : observer(algorithm->allocator()) + , m_algorithm(algorithm) + , m_target(target) + , m_self(self) + {} + ~closest_nodes_observer(); + + void send(msg& p) + { + p.info_hash = m_target; + } + + void timeout(); + void reply(msg const&); + void abort() { m_algorithm = 0; } + +private: + boost::intrusive_ptr m_algorithm; + node_id const m_target; + node_id const m_self; +}; + +} } // namespace libtorrent::dht + +#endif // CLOSEST_NODES_050323_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp b/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp new file mode 100644 index 000000000..f61364707 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_DHT + +#ifndef TORRENT_DHT_TRACKER +#define TORRENT_DHT_TRACKER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/packet_iterator.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_status.hpp" + +namespace libtorrent { namespace dht +{ + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_DECLARE_LOG(dht_tracker); +#endif + + struct dht_tracker; + + TORRENT_EXPORT void intrusive_ptr_add_ref(dht_tracker const*); + TORRENT_EXPORT void intrusive_ptr_release(dht_tracker const*); + + struct dht_tracker + { + friend void intrusive_ptr_add_ref(dht_tracker const*); + friend void intrusive_ptr_release(dht_tracker const*); + dht_tracker(asio::io_service& ios, dht_settings const& settings + , asio::ip::address listen_interface, entry const& bootstrap); + void stop(); + + void add_node(udp::endpoint node); + void add_node(std::pair const& node); + void add_router_node(std::pair const& node); + + void rebind(asio::ip::address listen_interface, int listen_port); + + entry state() const; + + void announce(sha1_hash const& ih, int listen_port + , boost::function const& + , sha1_hash const&)> f); + + void dht_status(session_status& s); + + private: + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + void on_name_lookup(asio::error_code const& e + , udp::resolver::iterator host); + void on_router_name_lookup(asio::error_code const& e + , udp::resolver::iterator host); + void connection_timeout(asio::error_code const& e); + void refresh_timeout(asio::error_code const& e); + void tick(asio::error_code const& e); + + // translate bittorrent kademlia message into the generic kademlia message + // used by the library + void on_receive(asio::error_code const& error, size_t bytes_transferred); + void on_bootstrap(); + void send_packet(msg const& m); + + asio::strand m_strand; + asio::ip::udp::socket m_socket; + + node_impl m_dht; + + // this is the index of the receive buffer we are currently receiving to + // the other buffer is the one containing the last message + int m_buffer; + std::vector m_in_buf[2]; + udp::endpoint m_remote_endpoint[2]; + std::vector m_send_buf; + + ptime m_last_new_key; + deadline_timer m_timer; + deadline_timer m_connection_timer; + deadline_timer m_refresh_timer; + dht_settings const& m_settings; + int m_refresh_bucket; + + // used to resolve hostnames for nodes + udp::resolver m_host_resolver; + + // reference counter for intrusive_ptr + mutable boost::detail::atomic_count m_refs; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + int m_replies_sent[5]; + int m_queries_received[5]; + int m_replies_bytes_sent[5]; + int m_queries_bytes_received[5]; + int m_counter; + int m_announces; + int m_failed_announces; + + int m_total_message_input; + int m_ut_message_input; + int m_lt_message_input; + int m_mp_message_input; + int m_gr_message_input; + int m_mo_message_input; + + int m_total_in_bytes; + int m_total_out_bytes; + + int m_queries_out_bytes; +#endif + }; +}} + +#endif +#endif diff --git a/libtorrent/include/libtorrent/kademlia/find_data.hpp b/libtorrent/include/libtorrent/kademlia/find_data.hpp new file mode 100644 index 000000000..17d77c9d8 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/find_data.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FIND_DATA_050323_HPP +#define FIND_DATA_050323_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +typedef std::vector packet_t; + +class rpc_manager; + +// -------- find data ----------- + +class find_data : public traversal_algorithm +{ +public: + typedef boost::function done_callback; + + static void initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + + void got_data(msg const* m); + +private: + void done(); + void invoke(node_id const& id, udp::endpoint addr); + + find_data( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + + done_callback m_done_callback; + boost::shared_ptr m_packet; + bool m_done; +}; + +class find_data_observer : public observer +{ +public: + find_data_observer( + boost::intrusive_ptr const& algorithm + , node_id self + , node_id target) + : observer(algorithm->allocator()) + , m_algorithm(algorithm) + , m_target(target) + , m_self(self) + {} + ~find_data_observer(); + + void send(msg& m) + { + m.reply = false; + m.message_id = messages::get_peers; + m.info_hash = m_target; + } + + void timeout(); + void reply(msg const&); + void abort() { m_algorithm = 0; } + +private: + boost::intrusive_ptr m_algorithm; + node_id const m_target; + node_id const m_self; +}; + +} } // namespace libtorrent::dht + +#endif // FIND_DATA_050323_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/logging.hpp b/libtorrent/include/libtorrent/kademlia/logging.hpp new file mode 100644 index 000000000..c0cbb31a4 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/logging.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LOGGING_HPP +#define TORRENT_LOGGING_HPP + +#include +#include + +namespace libtorrent { namespace dht +{ + +class log +{ +public: + log(char const* id, std::ostream& stream) + : m_id(id) + , m_enabled(true) + , m_stream(stream) + { + } + + char const* id() const + { + return m_id; + } + + bool enabled() const + { + return m_enabled; + } + + void enable(bool e) + { + m_enabled = e; + } + + void flush() { m_stream.flush(); } + + template + log& operator<<(T const& x) + { + m_stream << x; + return *this; + } + +private: + char const* m_id; + bool m_enabled; + std::ostream& m_stream; +}; + +class log_event +{ +public: + log_event(log& log) + : log_(log) + { + if (log_.enabled()) + log_ << '[' << log.id() << "] "; + } + + ~log_event() + { + if (log_.enabled()) + { + log_ << "\n"; + log_.flush(); + } + } + + template + log_event& operator<<(T const& x) + { + log_ << x; + return *this; + } + + operator bool() const + { + return log_.enabled(); + } + +private: + log& log_; +}; + +class inverted_log_event : public log_event +{ +public: + inverted_log_event(log& log) : log_event(log) {} + + operator bool() const + { + return !log_event::operator bool(); + } +}; + +} } // namespace libtorrent::dht + +#define TORRENT_DECLARE_LOG(name) \ + libtorrent::dht::log& name ## _log() + +#define TORRENT_DEFINE_LOG(name) \ + libtorrent::dht::log& name ## _log() \ + { \ + static std::ofstream log_file("dht.log", std::ios::app); \ + static libtorrent::dht::log instance(#name, log_file); \ + return instance; \ + } + +#define TORRENT_LOG(name) \ + if (libtorrent::dht::inverted_log_event event_object__ = name ## _log()); \ + else static_cast(event_object__) + +#endif + diff --git a/libtorrent/include/libtorrent/kademlia/msg.hpp b/libtorrent/include/libtorrent/kademlia/msg.hpp new file mode 100644 index 000000000..a205ce463 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/msg.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MSG_HPP +#define MSG_HPP + +#include +#include +#include "libtorrent/entry.hpp" +#include + +namespace libtorrent { +namespace dht { + +typedef std::vector packet_t; + +using asio::ip::udp; + +namespace messages +{ + enum { ping = 0, find_node = 1, get_peers = 2, announce_peer = 3, error = 4 }; + char const* const ids[] = { "ping", "find_node", "get_peers", "announce_peer", "error" }; +} // namespace messages + +struct msg +{ + msg() : reply(false), piggy_backed_ping(false) + , message_id(-1), port(0) {} + + // true if this message is a reply + bool reply; + // true if this is a reply with a piggy backed ping + bool piggy_backed_ping; + // the kind if message + int message_id; + // if this is a reply, a copy of the transaction id + // from the request. If it's a request, a transaction + // id that should be sent back in the reply + std::string transaction_id; + // if this packet has a piggy backed ping, this + // is the transaction id of that ping + std::string ping_transaction_id; + // the node id of the process sending the message + node_id id; + // the address of the process sending or receiving + // the message. + udp::endpoint addr; + // if this is a nodes response, these are the nodes + typedef std::vector nodes_t; + nodes_t nodes; + + typedef std::vector peers_t; + peers_t peers; + + // similar to transaction_id but for write operations. + entry write_token; + + // the info has for peer_requests, announce_peer + // and responses + node_id info_hash; + + // port for announce_peer messages + int port; + + // ERROR MESSAGES + int error_code; + std::string error_msg; +}; + + +} } + +#endif diff --git a/libtorrent/include/libtorrent/kademlia/node.hpp b/libtorrent/include/libtorrent/kademlia/node.hpp new file mode 100644 index 000000000..850333043 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/node.hpp @@ -0,0 +1,259 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NODE_HPP +#define NODE_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "libtorrent/socket.hpp" + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(node); +#endif + +// this is the entry for every peer +// the timestamp is there to make it possible +// to remove stale peers +struct peer_entry +{ + tcp::endpoint addr; + ptime added; +}; + +// this is a group. It contains a set of group members +struct torrent_entry +{ + std::set peers; +}; + +inline bool operator<(peer_entry const& lhs, peer_entry const& rhs) +{ + return lhs.addr.address() == rhs.addr.address() + ? lhs.addr.port() < rhs.addr.port() + : lhs.addr.address() < rhs.addr.address(); +} + +struct null_type {}; + +class announce_observer : public observer +{ +public: + announce_observer(boost::pool<>& allocator + , sha1_hash const& info_hash + , int listen_port + , entry const& write_token) + : observer(allocator) + , m_info_hash(info_hash) + , m_listen_port(listen_port) + , m_token(write_token) + {} + + void send(msg& m) + { + m.port = m_listen_port; + m.info_hash = m_info_hash; + m.write_token = m_token; + } + + void timeout() {} + void reply(msg const&) {} + void abort() {} + +private: + sha1_hash m_info_hash; + int m_listen_port; + entry m_token; +}; + +class get_peers_observer : public observer +{ +public: + get_peers_observer(sha1_hash const& info_hash + , int listen_port + , rpc_manager& rpc + , boost::function const&, sha1_hash const&)> f) + : observer(rpc.allocator()) + , m_info_hash(info_hash) + , m_listen_port(listen_port) + , m_rpc(rpc) + , m_fun(f) + {} + + void send(msg& m) + { + m.port = m_listen_port; + m.info_hash = m_info_hash; + } + + void timeout() {} + void reply(msg const& r) + { + m_rpc.invoke(messages::announce_peer, r.addr + , observer_ptr(new (m_rpc.allocator().malloc()) announce_observer( + m_rpc.allocator(), m_info_hash, m_listen_port, r.write_token))); + m_fun(r.peers, m_info_hash); + } + void abort() {} + +private: + sha1_hash m_info_hash; + int m_listen_port; + rpc_manager& m_rpc; + boost::function const&, sha1_hash const&)> m_fun; +}; + + + +class node_impl : boost::noncopyable +{ +typedef std::map table_t; +public: + node_impl(boost::function const& f + , dht_settings const& settings, boost::optional node_id); + + virtual ~node_impl() {} + + void refresh(node_id const& id, boost::function0 f); + void bootstrap(std::vector const& nodes + , boost::function0 f); + void find_node(node_id const& id, boost::function< + void(std::vector const&)> f); + void add_router_node(udp::endpoint router); + + void incoming(msg const& m); + + void refresh(); + void refresh_bucket(int bucket); + int bucket_size(int bucket); + + typedef routing_table::iterator iterator; + + iterator begin() const { return m_table.begin(); } + iterator end() const { return m_table.end(); } + + typedef table_t::iterator data_iterator; + + node_id const& nid() const { return m_id; } + boost::tuple size() const{ return m_table.size(); } + size_type num_global_nodes() const + { return m_table.num_global_nodes(); } + + data_iterator begin_data() { return m_map.begin(); } + data_iterator end_data() { return m_map.end(); } + int data_size() const { return int(m_map.size()); } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + void print_state(std::ostream& os) const + { m_table.print_state(os); } +#endif + + void announce(sha1_hash const& info_hash, int listen_port + , boost::function const& + , sha1_hash const&)> f); + + bool verify_token(msg const& m); + entry generate_token(msg const& m); + + // the returned time is the delay until connection_timeout() + // should be called again the next time + time_duration connection_timeout(); + time_duration refresh_timeout(); + + // generates a new secret number used to generate write tokens + void new_write_key(); + + // pings the given node, and adds it to + // the routing table if it respons and if the + // bucket is not full. + void add_node(udp::endpoint node); + + void replacement_cache(bucket_t& nodes) const + { m_table.replacement_cache(nodes); } + +protected: + // is called when a find data request is received. Should + // return false if the data is not stored on this node. If + // the data is stored, it should be serialized into 'data'. + bool on_find(msg const& m, std::vector& peers) const; + + // this is called when a store request is received. The data + // is store-parameters and the data to be stored. + void on_announce(msg const& m, msg& reply); + + dht_settings const& m_settings; + + // the maximum number of peers to send in a get_peers + // reply. Ordinary trackers usually limit this to 50. + // 50 => 6 * 50 = 250 bytes + packet overhead + int m_max_peers_reply; + +private: + void incoming_request(msg const& h); + + node_id m_id; + routing_table m_table; + rpc_manager m_rpc; + table_t m_map; + + ptime m_last_tracker_tick; + + // secret random numbers used to create write tokens + int m_secret[2]; +}; + + +} } // namespace libtorrent::dht + +#endif // NODE_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/node_entry.hpp b/libtorrent/include/libtorrent/kademlia/node_entry.hpp new file mode 100644 index 000000000..edc5dff80 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/node_entry.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef KADEMLIA_NODE_ENTRY_HPP +#define KADEMLIA_NODE_ENTRY_HPP + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/socket.hpp" + +namespace libtorrent { namespace dht +{ + +struct node_entry +{ + node_entry(node_id const& id_, asio::ip::udp::endpoint addr_) + : id(id_) + , addr(addr_) + , fail_count(0) {} + node_entry(asio::ip::udp::endpoint addr_) + : id(0) + , addr(addr_) + , fail_count(0) {} + + node_id id; + udp::endpoint addr; + // the number of times this node has failed to + // respond in a row + int fail_count; +}; + +} } // namespace libtorrent::dht + +#endif + diff --git a/libtorrent/include/libtorrent/kademlia/node_id.hpp b/libtorrent/include/libtorrent/kademlia/node_id.hpp new file mode 100644 index 000000000..eb4d6c539 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/node_id.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef NODE_ID_HPP +#define NODE_ID_HPP + +#include +#include + +#include +#include "libtorrent/peer_id.hpp" + +namespace libtorrent { namespace dht +{ + +typedef libtorrent::big_number node_id; + +// returns the distance between the two nodes +// using the kademlia XOR-metric +node_id distance(node_id const& n1, node_id const& n2); + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref); + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// usefult for finding out which bucket a node belongs to +int distance_exp(node_id const& n1, node_id const& n2); + +} } // namespace libtorrent::dht + +#endif // NODE_ID_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/observer.hpp b/libtorrent/include/libtorrent/kademlia/observer.hpp new file mode 100644 index 000000000..141460dc0 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/observer.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef OBSERVER_HPP +#define OBSERVER_HPP + +#include +#include +#include + +namespace libtorrent { +namespace dht { + +struct observer; +struct msg; + +// defined in rpc_manager.cpp +TORRENT_EXPORT void intrusive_ptr_add_ref(observer const*); +TORRENT_EXPORT void intrusive_ptr_release(observer const*); + +struct observer : boost::noncopyable +{ + friend TORRENT_EXPORT void intrusive_ptr_add_ref(observer const*); + friend TORRENT_EXPORT void intrusive_ptr_release(observer const*); + + observer(boost::pool<>& p) + : sent(time_now()) + , pool_allocator(p) + , m_refs(0) + {} + + virtual ~observer() {} + + // these two callbacks lets the observer add + // information to the message before it's sent + virtual void send(msg& m) = 0; + + // this is called when a reply is received + virtual void reply(msg const& m) = 0; + + // this is called when no reply has been received within + // some timeout + virtual void timeout() = 0; + + // if this is called the destructor should + // not invoke any new messages, and should + // only clean up. It means the rpc-manager + // is being destructed + virtual void abort() = 0; + + udp::endpoint target_addr; + ptime sent; +private: + boost::pool<>& pool_allocator; + // reference counter for intrusive_ptr + mutable boost::detail::atomic_count m_refs; +}; + +typedef boost::intrusive_ptr observer_ptr; + +} } + +#endif diff --git a/libtorrent/include/libtorrent/kademlia/packet_iterator.hpp b/libtorrent/include/libtorrent/kademlia/packet_iterator.hpp new file mode 100644 index 000000000..e906a90bf --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/packet_iterator.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PACKET_ITERATOR_HPP +#define PACKET_ITERATOR_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +class packet_iterator: public boost::iterator_facade< + packet_iterator, const char, boost::forward_traversal_tag> +{ +public: + typedef std::vector::const_iterator base_iterator; + + packet_iterator() {} + + packet_iterator(std::vector::const_iterator start + , std::vector::const_iterator end + , std::string const& error_msg = "") + : m_base(start) + , m_end(end) + , m_msg(error_msg) + {} + + base_iterator base() const + { return m_base; } + + base_iterator end() const + { return m_end; } + + int left() const { return int(m_end - m_base); } + +private: + friend class boost::iterator_core_access; + + bool equal(packet_iterator const& other) const + { return m_base == other.m_base; } + + void advance(int n) + { + m_base += n; + } + + void increment() + { ++m_base; } + + char const& dereference() const + { + if (m_base == m_end) throw std::runtime_error(m_msg); + return *m_base; + } + + base_iterator m_base; + base_iterator m_end; + std::string m_msg; +}; + +} } // namespace libtorrent::dht + +#endif // PACKET_ITERATOR_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/refresh.hpp b/libtorrent/include/libtorrent/kademlia/refresh.hpp new file mode 100644 index 000000000..953c4d871 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/refresh.hpp @@ -0,0 +1,214 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef REFRESH_050324_HPP +#define REFRESH_050324_HPP + +#include + +#include +#include +#include +#include + +#include + +namespace libtorrent { namespace dht +{ + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(refresh); +#endif + +class routing_table; +class rpc_manager; + +class refresh : public traversal_algorithm +{ +public: + typedef boost::function done_callback; + + template + static void initiate( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback + ); + + void ping_reply(node_id id); + void ping_timeout(node_id id, bool prevent_request = false); + +private: + template + refresh( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback + ); + + void done(); + void invoke(node_id const& id, udp::endpoint addr); + + void invoke_pings_or_finish(bool prevent_request = false); + + int m_max_active_pings; + int m_active_pings; + + done_callback m_done_callback; + + std::vector::iterator m_leftover_nodes_iterator; +}; + +class refresh_observer : public observer +{ +public: + refresh_observer( + boost::intrusive_ptr const& algorithm + , node_id self + , node_id target) + : observer(algorithm->allocator()) + , m_target(target) + , m_self(self) + , m_algorithm(algorithm) + {} + ~refresh_observer(); + + void send(msg& m) + { + m.info_hash = m_target; + } + + void timeout(); + void reply(msg const& m); + void abort() { m_algorithm = 0; } + + +private: + node_id const m_target; + node_id const m_self; + boost::intrusive_ptr m_algorithm; +}; + +class ping_observer : public observer +{ +public: + ping_observer( + boost::intrusive_ptr const& algorithm + , node_id self) + : observer(algorithm->allocator()) + , m_self(self) + , m_algorithm(algorithm) + {} + ~ping_observer(); + + void send(msg& p) {} + void timeout(); + void reply(msg const& m); + void abort() { m_algorithm = 0; } + + +private: + node_id const m_self; + boost::intrusive_ptr m_algorithm; +}; + +template +inline refresh::refresh( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback +) + : traversal_algorithm( + target + , branch_factor + , max_results + , table + , rpc + , first + , last + ) + , m_max_active_pings(max_active_pings) + , m_active_pings(0) + , m_done_callback(callback) +{ + boost::intrusive_ptr self(this); + add_requests(); +} + +template +inline void refresh::initiate( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback +) +{ + new refresh( + target + , branch_factor + , max_active_pings + , max_results + , table + , first + , last + , rpc + , callback + ); +} + +} } // namespace libtorrent::dht + +#endif // REFRESH_050324_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/routing_table.hpp b/libtorrent/include/libtorrent/kademlia/routing_table.hpp new file mode 100644 index 000000000..45a7dd762 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/routing_table.hpp @@ -0,0 +1,248 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ROUTING_TABLE_HPP +#define ROUTING_TABLE_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +//TORRENT_DECLARE_LOG(table); + +typedef std::vector bucket_t; + +// differences in the implementation from the description in +// the paper: +// +// * The routing table tree is not allocated dynamically, there +// are always 160 buckets. +// * Nodes are not marked as being stale, they keep a counter +// that tells how many times in a row they have failed. When +// a new node is to be inserted, the node that has failed +// the most times is replaced. If none of the nodes in the +// bucket has failed, then it is put in the replacement +// cache (just like in the paper). + +class routing_table; + +namespace aux +{ + + // Iterates over a flattened routing_table structure. + class routing_table_iterator + : public boost::iterator_facade< + routing_table_iterator + , node_entry const + , boost::forward_traversal_tag + > + { + public: + routing_table_iterator() + { + } + + private: + friend class libtorrent::dht::routing_table; + friend class boost::iterator_core_access; + + typedef boost::array, 160>::const_iterator + bucket_iterator_t; + + routing_table_iterator( + bucket_iterator_t begin + , bucket_iterator_t end) + : m_bucket_iterator(begin) + , m_bucket_end(end) + , m_iterator(begin != end ? begin->first.begin() : bucket_t::const_iterator()) + { + if (m_bucket_iterator == m_bucket_end) return; + while (m_iterator == m_bucket_iterator->first.end()) + { + if (++m_bucket_iterator == m_bucket_end) + break; + m_iterator = m_bucket_iterator->first.begin(); + } + } + + bool equal(routing_table_iterator const& other) const + { + return m_bucket_iterator == other.m_bucket_iterator + && (m_bucket_iterator == m_bucket_end + || m_iterator == other.m_iterator); + } + + void increment() + { + assert(m_bucket_iterator != m_bucket_end); + ++m_iterator; + while (m_iterator == m_bucket_iterator->first.end()) + { + if (++m_bucket_iterator == m_bucket_end) + break; + m_iterator = m_bucket_iterator->first.begin(); + } + } + + node_entry const& dereference() const + { + assert(m_bucket_iterator != m_bucket_end); + return *m_iterator; + } + + bucket_iterator_t m_bucket_iterator; + bucket_iterator_t m_bucket_end; + bucket_t::const_iterator m_iterator; + }; + +} // namespace aux + +class routing_table +{ +public: + typedef aux::routing_table_iterator iterator; + typedef iterator const_iterator; + + routing_table(node_id const& id, int bucket_size + , dht_settings const& settings); + + void node_failed(node_id const& id); + + // adds an endpoint that will never be added to + // the routing table + void add_router_node(udp::endpoint router); + + // iterates over the router nodes added + typedef std::set::const_iterator router_iterator; + router_iterator router_begin() const { return m_router_nodes.begin(); } + router_iterator router_end() const { return m_router_nodes.end(); } + + // this function is called every time the node sees + // a sign of a node being alive. This node will either + // be inserted in the k-buckets or be moved to the top + // of its bucket. + bool node_seen(node_id const& id, udp::endpoint addr); + + // returns time when the given bucket needs another refresh. + // if the given bucket is empty but there are nodes + // in a bucket closer to us, or if the bucket is non-empty and + // the time from the last activity is more than 15 minutes + ptime next_refresh(int bucket); + + // fills the vector with the count nodes from our buckets that + // are nearest to the given id. + void find_node(node_id const& id, std::vector& l + , bool include_self, int count = 0); + + // returns true if the given node would be placed in a bucket + // that is not full. If the node already exists in the table + // this function returns false + bool need_node(node_id const& id); + + // this will set the given bucket's latest activity + // to the current time + void touch_bucket(int bucket); + + int bucket_size(int bucket) + { + assert(bucket >= 0 && bucket < 160); + return (int)m_buckets[bucket].first.size(); + } + int bucket_size() const { return m_bucket_size; } + + iterator begin() const; + iterator end() const; + + boost::tuple size() const; + size_type num_global_nodes() const; + + // returns true if there are no working nodes + // in the routing table + bool need_bootstrap() const; + int num_active_buckets() const + { return 160 - m_lowest_active_bucket + 1; } + + void replacement_cache(bucket_t& nodes) const; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + // used for debug and monitoring purposes. This will print out + // the state of the routing table to the given stream + void print_state(std::ostream& os) const; +#endif + +private: + + // constant called k in paper + int m_bucket_size; + + dht_settings const& m_settings; + + // 160 (k-bucket, replacement cache) pairs + typedef boost::array, 160> table_t; + table_t m_buckets; + // timestamps of the last activity in each bucket + typedef boost::array table_activity_t; + table_activity_t m_bucket_activity; + node_id m_id; // our own node id + + // this is a set of all the endpoints that have + // been identified as router nodes. They will + // be used in searches, but they will never + // be added to the routing table. + std::set m_router_nodes; + + // this is the lowest bucket index with nodes in it + int m_lowest_active_bucket; +}; + +} } // namespace libtorrent::dht + +#endif // ROUTING_TABLE_HPP + diff --git a/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp b/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp new file mode 100644 index 000000000..a7c47f29a --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef RPC_MANAGER_HPP +#define RPC_MANAGER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/time.hpp" + +namespace libtorrent { namespace dht +{ + +struct observer; + +using asio::ip::udp; +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(rpc); +#endif + +struct null_observer : public observer +{ + null_observer(boost::pool<>& allocator): observer(allocator) {} + virtual void reply(msg const&) {} + virtual void timeout() {} + virtual void send(msg&) {} + void abort() {} +}; + +class routing_table; + +class rpc_manager +{ +public: + typedef boost::function1 fun; + typedef boost::function1 send_fun; + + rpc_manager(fun const& incoming_fun, node_id const& our_id + , routing_table& table, send_fun const& sf); + ~rpc_manager(); + + // returns true if the node needs a refresh + bool incoming(msg const&); + time_duration tick(); + + void invoke(int message_id, udp::endpoint target + , observer_ptr o); + + void reply(msg& m); + void reply_with_ping(msg& m); + +#ifndef NDEBUG + void check_invariant() const; +#endif + + boost::pool<>& allocator() const + { return m_pool_allocator; } + +private: + + enum { max_transactions = 2048 }; + + unsigned int new_transaction_id(observer_ptr o); + void update_oldest_transaction_id(); + + boost::uint32_t calc_connection_id(udp::endpoint addr); + + mutable boost::pool<> m_pool_allocator; + + typedef boost::array + transactions_t; + transactions_t m_transactions; + std::vector m_aborted_transactions; + + // this is the next transaction id to be used + int m_next_transaction_id; + // this is the oldest transaction id still + // (possibly) in use. This is the transaction + // that will time out first, the one we are + // waiting for to time out + int m_oldest_transaction_id; + + fun m_incoming; + send_fun m_send; + node_id m_our_id; + routing_table& m_table; + ptime m_timer; + node_id m_random_number; + bool m_destructing; +}; + +} } // namespace libtorrent::dht + +#endif + + diff --git a/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp b/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp new file mode 100644 index 000000000..d51ed5506 --- /dev/null +++ b/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRAVERSAL_ALGORITHM_050324_HPP +#define TRAVERSAL_ALGORITHM_050324_HPP + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(traversal); +#endif + +class rpc_manager; + +// this class may not be instantiated as a stack object +class traversal_algorithm : boost::noncopyable +{ +public: + void traverse(node_id const& id, udp::endpoint addr); + void finished(node_id const& id); + void failed(node_id const& id, bool prevent_request = false); + virtual ~traversal_algorithm() {} + boost::pool<>& allocator() const; + +protected: + template + traversal_algorithm( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , InIt start + , InIt end + ); + + void add_requests(); + void add_entry(node_id const& id, udp::endpoint addr, unsigned char flags); + + virtual void done() = 0; + virtual void invoke(node_id const& id, udp::endpoint addr) = 0; + + struct result + { + result(node_id const& id, udp::endpoint addr, unsigned char f = 0) + : id(id), addr(addr), flags(f) + {} + + node_id id; + udp::endpoint addr; + enum { queried = 1, initial = 2 }; + unsigned char flags; + }; + + std::vector::iterator last_iterator(); + + friend void intrusive_ptr_add_ref(traversal_algorithm* p) + { + p->m_ref_count++; + } + + friend void intrusive_ptr_release(traversal_algorithm* p) + { + if (--p->m_ref_count == 0) + delete p; + } + + int m_ref_count; + + node_id m_target; + int m_branch_factor; + int m_max_results; + std::vector m_results; + std::set m_failed; + routing_table& m_table; + rpc_manager& m_rpc; + int m_invoke_count; +}; + +template +traversal_algorithm::traversal_algorithm( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , InIt start // <- nodes to initiate traversal with + , InIt end +) + : m_ref_count(0) + , m_target(target) + , m_branch_factor(branch_factor) + , m_max_results(max_results) + , m_table(table) + , m_rpc(rpc) + , m_invoke_count(0) +{ + using boost::bind; + + for (InIt i = start; i != end; ++i) + { + add_entry(i->id, i->addr, result::initial); + } + + // in case the routing table is empty, use the + // router nodes in the table + if (start == end) + { + for (routing_table::router_iterator i = table.router_begin() + , end(table.router_end()); i != end; ++i) + { + add_entry(node_id(0), *i, result::initial); + } + } + +} + +} } // namespace libtorrent::dht + +#endif // TRAVERSAL_ALGORITHM_050324_HPP + diff --git a/libtorrent/include/libtorrent/lsd.hpp b/libtorrent/include/libtorrent/lsd.hpp new file mode 100644 index 000000000..9ffbcdfc3 --- /dev/null +++ b/libtorrent/include/libtorrent/lsd.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" + +#include +#include +#include +#include +#include + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) +#include +#endif + +namespace libtorrent +{ + +typedef boost::function peer_callback_t; + +class lsd : boost::noncopyable +{ +public: + lsd(io_service& ios, address const& listen_interface + , peer_callback_t const& cb); + ~lsd(); + + void rebind(address const& listen_interface); + + void announce(sha1_hash const& ih, int listen_port); + void close(); + +private: + + static address_v4 lsd_multicast_address; + static udp::endpoint lsd_multicast_endpoint; + + void resend_announce(asio::error_code const& e, std::string msg); + void on_announce(asio::error_code const& e + , std::size_t bytes_transferred); + void setup_receive(); + + peer_callback_t m_callback; + + // current retry count + int m_retry_count; + + // used to receive responses in + char m_receive_buffer[1024]; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the udp socket used to send and receive + // multicast messages on + datagram_socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + bool m_disabled; +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + std::ofstream m_log; +#endif +}; + +} + + +#endif + diff --git a/libtorrent/include/libtorrent/natpmp.hpp b/libtorrent/include/libtorrent/natpmp.hpp new file mode 100644 index 000000000..1c0ffd0be --- /dev/null +++ b/libtorrent/include/libtorrent/natpmp.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/socket.hpp" + +#include + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) +#include +#endif + +namespace libtorrent +{ + +// int: external tcp port +// int: external udp port +// std::string: error message +typedef boost::function portmap_callback_t; + +class natpmp +{ +public: + natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb); + + void rebind(address const& listen_interface); + + // maps the ports, if a port is set to 0 + // it will not be mapped + void set_mappings(int tcp, int udp); + + void close(); + +private: + + void update_mapping(int i, int port); + void send_map_request(int i); + void resend_request(int i, asio::error_code const& e); + void on_reply(asio::error_code const& e + , std::size_t bytes_transferred); + void try_next_mapping(int i); + void update_expiration_timer(); + void refresh_mapping(int i); + void mapping_expired(asio::error_code const& e, int i); + + struct mapping + { + mapping() + : need_update(false) + , local_port(0) + , external_port(0) + , protocol(1) + {} + + // indicates that the mapping has changed + // and needs an update + bool need_update; + + // the time the port mapping will expire + ptime expires; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port; + + // 1 = udp, 2 = tcp + int protocol; + }; + + portmap_callback_t m_callback; + + // 0 is tcp and 1 is udp + mapping m_mappings[2]; + + // the endpoint to the nat router + udp::endpoint m_nat_endpoint; + + // this is the mapping that is currently + // being updated. It is -1 in case no + // mapping is being updated at the moment + int m_currently_mapping; + + // current retry count + int m_retry_count; + + // used to receive responses in + char m_response_buffer[16]; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the udp socket used to communicate + // with the NAT router + datagram_socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_send_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + bool m_disabled; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + std::ofstream m_log; +#endif +}; + +} + + +#endif + diff --git a/libtorrent/include/libtorrent/pch.hpp b/libtorrent/include/libtorrent/pch.hpp new file mode 100644 index 000000000..735999826 --- /dev/null +++ b/libtorrent/include/libtorrent/pch.hpp @@ -0,0 +1,96 @@ +#ifdef BOOST_BUILD_PCH_ENABLED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +#undef Protocol +#endif + +#endif + diff --git a/libtorrent/include/libtorrent/pe_crypto.hpp b/libtorrent/include/libtorrent/pe_crypto.hpp new file mode 100644 index 000000000..91616c42d --- /dev/null +++ b/libtorrent/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,125 @@ +/* + +Copyright (c) 2007, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_ENCRYPTION + +#ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED +#define TORRENT_PE_CRYPTO_HPP_INCLUDED + +#include + +#include +#include +#include + +#include "peer_id.hpp" // For sha1_hash + +namespace libtorrent +{ + class DH_key_exchange + { + public: + DH_key_exchange (); + ~DH_key_exchange (); + + // Get local public key, always 96 bytes + char const* get_local_key (void) const; + + // read remote_pubkey, generate and store shared secret in + // m_dh_secret + void compute_secret (const char* remote_pubkey); + + const char* get_secret (void) const; + + private: + int get_local_key_size () const + { + assert (m_DH); + return BN_num_bytes (m_DH->pub_key); + } + + DH* m_DH; + static const unsigned char m_dh_prime[96]; + static const unsigned char m_dh_generator[1]; + + char m_dh_local_key[96]; + char m_dh_secret[96]; + }; + + class RC4_handler // Non copyable + { + public: + // Input longkeys must be 20 bytes + RC4_handler (const sha1_hash& rc4_local_longkey, + const sha1_hash& rc4_remote_longkey) + + { + RC4_set_key (&m_local_key, 20, + reinterpret_cast(rc4_local_longkey.begin())); + RC4_set_key (&m_remote_key, 20, + reinterpret_cast(rc4_remote_longkey.begin())); + + // Discard first 1024 bytes + char buf[1024]; + encrypt (buf, 1024); + decrypt (buf, 1024); + }; + + ~RC4_handler () {}; + + void encrypt (char* pos, int len) + { + assert (len >= 0); + assert (pos); + + RC4 (&m_local_key, len, reinterpret_cast(pos), + reinterpret_cast(pos)); + } + + void decrypt (char* pos, int len) + { + assert (len >= 0); + assert (pos); + + RC4 (&m_remote_key, len, reinterpret_cast(pos), + reinterpret_cast(pos)); + } + + private: + RC4_KEY m_local_key; // Key to encrypt outgoing data + RC4_KEY m_remote_key; // Key to decrypt incoming data + }; + +} // namespace libtorrent + +#endif // TORRENT_PE_CRYPTO_HPP_INCLUDED +#endif // TORRENT_DISABLE_ENCRYPTION diff --git a/libtorrent/include/libtorrent/peer.hpp b/libtorrent/include/libtorrent/peer.hpp new file mode 100755 index 000000000..c404a611d --- /dev/null +++ b/libtorrent/include/libtorrent/peer.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_HPP_INCLUDED +#define TORRENT_PEER_HPP_INCLUDED + +#include + +#include "libtorrent/peer_id.hpp" + +namespace libtorrent +{ + + struct TORRENT_EXPORT peer_entry + { + std::string ip; + int port; + peer_id pid; + + bool operator==(const peer_entry& p) const + { + return pid == p.pid; + } + + bool operator<(const peer_entry& p) const + { + return pid < p.pid; + } + }; + +} + +#endif // TORRENT_PEER_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp new file mode 100755 index 000000000..b459c491e --- /dev/null +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -0,0 +1,707 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/buffer.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" + +// TODO: each time a block is 'taken over' +// from another peer. That peer must be given +// a chance to become not-interested. + +namespace libtorrent +{ + class torrent; + struct peer_plugin; + + namespace detail + { + struct session_impl; + } + + struct TORRENT_EXPORT protocol_error: std::runtime_error + { + protocol_error(const std::string& msg): std::runtime_error(msg) {}; + }; + + class TORRENT_EXPORT peer_connection + : public intrusive_ptr_base + , public boost::noncopyable + { + friend class invariant_access; + public: + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + peer_connection( + aux::session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote + , policy::peer* peerinfo); + + // with this constructor we have been contacted and we still don't + // know which torrent the connection belongs to + peer_connection( + aux::session_impl& ses + , boost::shared_ptr s + , policy::peer* peerinfo); + + virtual ~peer_connection(); + + void set_peer_info(policy::peer* pi) + { m_peer_info = pi; } + + policy::peer* peer_info_struct() const + { return m_peer_info; } + + enum peer_speed_t { slow, medium, fast }; + peer_speed_t peer_speed(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::shared_ptr); +#endif + + // this function is called once the torrent associated + // with this peer connection has retrieved the meta- + // data. If the torrent was spawned with metadata + // this is called from the constructor. + void init(); + + // this is called when the metadata is retrieved + // and the files has been checked + virtual void on_metadata() {} + + void set_upload_limit(int limit); + void set_download_limit(int limit); + + int upload_limit() const { return m_upload_limit; } + int download_limit() const { return m_download_limit; } + + bool prefer_whole_pieces() const + { return m_prefer_whole_pieces; } + + void prefer_whole_pieces(bool b) + { m_prefer_whole_pieces = b; } + + bool request_large_blocks() const + { return m_request_large_blocks; } + + void request_large_blocks(bool b) + { m_request_large_blocks = b; } + + void set_non_prioritized(bool b) + { m_non_prioritized = b; } + + // this adds an announcement in the announcement queue + // it will let the peer know that we have the given piece + void announce_piece(int index); + + // tells if this connection has data it want to send + // and has enough upload bandwidth quota left to send it. + bool can_write() const; + bool can_read() const; + + bool is_seed() const; + + bool has_timed_out() const; + + // will send a keep-alive message to the peer + void keep_alive(); + + peer_id const& pid() const { return m_peer_id; } + void set_pid(const peer_id& pid) { m_peer_id = pid; } + bool has_piece(int i) const; + + const std::deque& download_queue() const; + const std::deque& request_queue() const; + const std::deque& upload_queue() const; + + bool is_interesting() const { return m_interesting; } + bool is_choked() const { return m_choked; } + + bool is_peer_interested() const { return m_peer_interested; } + bool has_peer_choked() const { return m_peer_choked; } + + void update_interest(); + + virtual void get_peer_info(peer_info& p) const; + + // returns the torrent this connection is a part of + // may be zero if the connection is an incoming connection + // and it hasn't received enough information to determine + // which torrent it should be associated with + boost::weak_ptr associated_torrent() const + { return m_torrent; } + + const stat& statistics() const { return m_statistics; } + void add_stat(size_type downloaded, size_type uploaded); + + // is called once every second by the main loop + void second_tick(float tick_interval); + + boost::shared_ptr get_socket() const { return m_socket; } + tcp::endpoint const& remote() const { return m_remote; } + + std::vector const& get_bitfield() const; + + void timed_out(); + // this will cause this peer_connection to be disconnected. + void disconnect(); + bool is_disconnecting() const { return m_disconnecting; } + + // this is called when the connection attempt has succeeded + // and the peer_connection is supposed to set m_connecting + // to false, and stop monitor writability + void on_connection_complete(asio::error_code const& e); + + // returns true if this connection is still waiting to + // finish the connection attempt + bool is_connecting() const { return m_connecting; } + + // returns true if the socket of this peer hasn't been + // attempted to connect yet (i.e. it's queued for + // connection attempt). + bool is_queued() const { return m_queued; } + + // called when it's time for this peer_conncetion to actually + // initiate the tcp connection. This may be postponed until + // the library isn't using up the limitation of half-open + // tcp connections. + void connect(int ticket); + + // This is called for every peer right after the upload + // bandwidth has been distributed among them + // It will reset the used bandwidth to 0. + void reset_upload_quota(); + + // free upload. + size_type total_free_upload() const; + void add_free_upload(size_type free_upload); + + // trust management. + void received_valid_data(int index); + void received_invalid_data(int index); + + size_type share_diff() const; + + // a connection is local if it was initiated by us. + // if it was an incoming connection, it is remote + bool is_local() const { return m_active; } + + bool on_local_network() const; + bool ignore_bandwidth_limits() const + { return m_ignore_bandwidth_limits; } + + void set_failed() { m_failed = true; } + bool failed() const { return m_failed; } + + int desired_queue_size() const { return m_desired_queue_size; } + +#ifdef TORRENT_VERBOSE_LOGGING + boost::shared_ptr m_logger; +#endif + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void incoming_keepalive(); + void incoming_choke(); + void incoming_unchoke(); + void incoming_interested(); + void incoming_not_interested(); + void incoming_have(int piece_index); + void incoming_bitfield(std::vector const& bitfield); + void incoming_request(peer_request const& r); + void incoming_piece(peer_request const& p, char const* data); + void incoming_piece_fragment(); + void incoming_cancel(peer_request const& r); + void incoming_dht_port(int listen_port); + + // the following functions appends messages + // to the send buffer + void send_choke(); + void send_unchoke(); + void send_interested(); + void send_not_interested(); + + // adds a block to the request queue + void add_request(piece_block const& b); + // removes a block from the request queue or download queue + // sends a cancel message if appropriate + // refills the request queue, and possibly ignoring pieces requested + // by peers in the ignore list (to avoid recursion) + void cancel_request(piece_block const& b); + void send_block_requests(); + + int max_assignable_bandwidth(int channel) const + { return m_bandwidth_limit[channel].max_assignable(); } + + int bandwidth_throttle(int channel) const + { return m_bandwidth_limit[channel].throttle(); } + + void assign_bandwidth(int channel, int amount); + void expire_bandwidth(int channel, int amount); + +#ifndef NDEBUG + void check_invariant() const; + ptime m_last_choke; +#endif + + + // is true until we can be sure that the other end + // speaks our protocol (be it bittorrent or http). + virtual bool in_handshake() const = 0; + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + virtual boost::optional + downloading_piece_progress() const + { + #ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "downloading_piece_progress() dispatched to the base class!\n"; + #endif + return boost::optional(); + } + + void send_buffer(char const* begin, char const* end); + buffer::interval allocate_send_buffer(int size); + void setup_send(); + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void set_country(char const* c) + { + assert(strlen(c) == 2); + m_country[0] = c[0]; + m_country[1] = c[1]; + } + bool has_country() const { return m_country[0] != 0; } +#endif + + protected: + + virtual void get_specific_peer_info(peer_info& p) const = 0; + + virtual void write_choke() = 0; + virtual void write_unchoke() = 0; + virtual void write_interested() = 0; + virtual void write_not_interested() = 0; + virtual void write_request(peer_request const& r) = 0; + virtual void write_cancel(peer_request const& r) = 0; + virtual void write_have(int index) = 0; + virtual void write_keepalive() = 0; + virtual void write_piece(peer_request const& r, char const* buffer) = 0; + + virtual void on_connected() = 0; + virtual void on_tick() {} + + virtual void on_receive(asio::error_code const& error + , std::size_t bytes_transferred) = 0; + virtual void on_sent(asio::error_code const& error + , std::size_t bytes_transferred) = 0; + + int send_buffer_size() const + { + return (int)m_send_buffer[0].size() + + (int)m_send_buffer[1].size() + - m_write_pos; + } + +#ifndef TORRENT_DISABLE_ENCRYPTION + buffer::interval wr_recv_buffer() + { + return buffer::interval(&m_recv_buffer[0] + , &m_recv_buffer[0] + m_recv_pos); + } +#endif + + buffer::const_interval receive_buffer() const + { + return buffer::const_interval(&m_recv_buffer[0] + , &m_recv_buffer[0] + m_recv_pos); + } + + void cut_receive_buffer(int size, int packet_size); + + void reset_recv_buffer(int packet_size); + int packet_size() const { return m_packet_size; } + + bool packet_finished() const + { + return m_packet_size <= m_recv_pos; + } + + void setup_receive(); + + void attach_to_torrent(sha1_hash const& ih); + + bool verify_piece(peer_request const& p) const; + + // the bandwidth channels, upload and download + // keeps track of the current quotas + bandwidth_limit m_bandwidth_limit[num_channels]; + + // statistics about upload and download speeds + // and total amount of uploads and downloads for + // this peer + stat m_statistics; + + // a back reference to the session + // the peer belongs to. + aux::session_impl& m_ses; + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + // called from the main loop when this connection has any + // work to do. + void on_send_data(asio::error_code const& error + , std::size_t bytes_transferred); + void on_receive_data(asio::error_code const& error + , std::size_t bytes_transferred); + + // this is the limit on the number of outstanding requests + // we have to this peer. This is initialized to the settings + // in the session_settings structure. But it may be lowered + // if the peer is known to require a smaller limit (like BitComet). + // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. + int m_max_out_request_queue; + + void set_timeout(int s) { m_timeout = s; } + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list > extension_list_t; + extension_list_t m_extensions; +#endif + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + // in case the session settings is set + // to resolve countries, this is set to + // the two character country code this + // peer resides in. + char m_country[2]; +#endif + + private: + + void fill_send_buffer(); + void on_disk_read_complete(int ret, disk_io_job const& j, peer_request r); + void on_disk_write_complete(int ret, disk_io_job const& j + , peer_request r, boost::shared_ptr t); + + // the timeout in seconds + int m_timeout; + + // the time when we last got a part of a + // piece packet from this peer + ptime m_last_piece; + // the time we sent a request to + // this peer the last time + ptime m_last_request; + + int m_packet_size; + int m_recv_pos; + std::vector m_recv_buffer; + + // this is the buffer where data that is + // to be sent is stored until it gets + // consumed by send(). Since asio requires + // the memory buffer that is given to async. + // operations to remain valid until the operation + // finishes, there has to be two buffers. While + // waiting for a async_write operation on one + // buffer, the other is used to write data to + // be queued up. + std::vector m_send_buffer[2]; + // the current send buffer is the one to write to. + // (m_current_send_buffer + 1) % 2 is the + // buffer we're currently waiting for. + int m_current_send_buffer; + + // the number of bytes we are currently reading + // from disk, that will be added to the send + // buffer as soon as they complete + int m_reading_bytes; + + // if the sending buffer doesn't finish in one send + // operation, this is the position within that buffer + // where the next operation should continue + int m_write_pos; + + // timeouts + ptime m_last_receive; + ptime m_last_sent; + + boost::shared_ptr m_socket; + // this is the peer we're actually talking to + // it may not necessarily be the peer we're + // connected to, in case we use a proxy + tcp::endpoint m_remote; + + // this is the torrent this connection is + // associated with. If the connection is an + // incoming conncetion, this is set to zero + // until the info_hash is received. Then it's + // set to the torrent it belongs to. + boost::weak_ptr m_torrent; + // is true if it was we that connected to the peer + // and false if we got an incomming connection + // could be considered: true = local, false = remote + bool m_active; + + // remote peer's id + peer_id m_peer_id; + + // other side says that it's interested in downloading + // from us. + bool m_peer_interested; + + // the other side has told us that it won't send anymore + // data to us for a while + bool m_peer_choked; + + // the peer has pieces we are interested in + bool m_interesting; + + // we have choked the upload to the peer + bool m_choked; + + // this is set to true if the connection timed + // out or closed the connection. In that + // case we will not try to reconnect to + // this peer + bool m_failed; + + // if this is set to true, the peer will not + // request bandwidth from the limiter, but instead + // just send and receive as much as possible. + bool m_ignore_bandwidth_limits; + + // the pieces the other end have + std::vector m_have_piece; + + // the number of pieces this peer + // has. Must be the same as + // std::count(m_have_piece.begin(), + // m_have_piece.end(), true) + int m_num_pieces; + + // the queue of requests we have got + // from this peer + std::deque m_requests; + + // the blocks we have reserved in the piece + // picker and will send to this peer. + std::deque m_request_queue; + + // the queue of blocks we have requested + // from this peer + std::deque m_download_queue; + + // the number of request we should queue up + // at the remote end. + int m_desired_queue_size; + + // the amount of data this peer has been given + // as free upload. This is distributed from + // peers from which we get free download + // this will be negative on a peer from which + // we get free download, and positive on peers + // that we give the free upload, to keep the balance. + size_type m_free_upload; + + // if this is true, this peer is assumed to handle all piece + // requests in fifo order. All skipped blocks are re-requested + // immediately instead of having a looser requirement + // where blocks can be sent out of order. The default is to + // allow non-fifo order. + bool m_assume_fifo; + + // the number of invalid piece-requests + // we have got from this peer. If the request + // queue gets empty, and there have been + // invalid requests, we can assume the + // peer is waiting for those pieces. + // we can then clear its download queue + // by sending choke, unchoke. + int m_num_invalid_requests; + + // this is true if this connection has been added + // to the list of connections that will be closed. + bool m_disconnecting; + + // the time when this peer sent us a not_interested message + // the last time. + ptime m_became_uninterested; + + // the time when we sent a not_interested message to + // this peer the last time. + ptime m_became_uninteresting; + + // this is true until this socket has become + // writable for the first time (i.e. the + // connection completed). While connecting + // the timeout will not be triggered. This is + // because windows XP SP2 may delay connection + // attempts, which means that the connection + // may not even have been attempted when the + // time out is reached. + bool m_connecting; + + // This is true until connect is called on the + // peer_connection's socket. It is false on incoming + // connections. + bool m_queued; + + // these are true when there's a asynchronous write + // or read operation running. + bool m_writing; + bool m_reading; + + // if set to true, this peer will always prefer + // to request entire pieces, rather than blocks. + // if it is false, the download rate limit setting + // will be used to determine if whole pieces + // are preferred. + bool m_prefer_whole_pieces; + + // if this is true, the blocks picked by the piece + // picker will be merged before passed to the + // request function. i.e. subsequent blocks are + // merged into larger blocks. This is used by + // the http-downloader, to request whole pieces + // at a time. + bool m_request_large_blocks; + + // if this is true, other (prioritized) peers will + // skip ahead of it in the queue for bandwidth. The + // effect is that non prioritized peers will only use + // the left-over bandwidth (suitable for web seeds). + bool m_non_prioritized; + + int m_upload_limit; + int m_download_limit; + + // this peer's peer info struct. This may + // be 0, in case the connection is incoming + // and hasn't been added to a torrent yet. + policy::peer* m_peer_info; + + // this is a measurement of how fast the peer + // it allows some variance without changing + // back and forth between states + peer_speed_t m_speed; + + // the ticket id from the connection queue. + // This is used to identify the connection + // so that it can be removed from the queue + // once the connection completes + int m_connection_ticket; + + // bytes downloaded since last second + // timer timeout; used for determining + // approx download rate + int m_remote_bytes_dled; + + // approximate peer download rate + int m_remote_dl_rate; + + // a timestamp when the remote download rate + // was last updated + ptime m_remote_dl_update; + +#ifndef NDEBUG + public: + bool m_in_constructor; +#endif + }; +} + +#endif // TORRENT_PEER_CONNECTION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp new file mode 100755 index 000000000..b66c1d4bc --- /dev/null +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ID_HPP_INCLUDED +#define TORRENT_PEER_ID_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + class TORRENT_EXPORT big_number + { + // private type + struct private_pointer {}; + // the number of bytes of the number + enum { number_size = 20 }; + public: + enum { size = number_size }; + + big_number() {} + + big_number(std::string const& s) + { + assert(s.size() >= 20); + int sl = int(s.size()) < size ? int(s.size()) : size; + std::memcpy(m_number, &s[0], sl); + } + + // when initialized with 0 + big_number(private_pointer*) { clear(); } + + void clear() + { + std::fill(m_number,m_number+number_size,0); + } + + bool is_all_zeros() const + { + return std::count(m_number,m_number+number_size,0) == number_size; + } + + bool operator==(big_number const& n) const + { + return std::equal(n.m_number, n.m_number+number_size, m_number); + } + + bool operator!=(big_number const& n) const + { + return !std::equal(n.m_number, n.m_number+number_size, m_number); + } + + bool operator<(big_number const& n) const + { + for (int i = 0; i < number_size; ++i) + { + if (m_number[i] < n.m_number[i]) return true; + if (m_number[i] > n.m_number[i]) return false; + } + return false; + } + + big_number operator~() + { + big_number ret; + for (int i = 0; i< number_size; ++i) + ret.m_number[i] = ~m_number[i]; + return ret; + } + + big_number& operator &= (big_number const& n) + { + for (int i = 0; i< number_size; ++i) + m_number[i] &= n.m_number[i]; + return *this; + } + + big_number& operator |= (big_number const& n) + { + for (int i = 0; i< number_size; ++i) + m_number[i] |= n.m_number[i]; + return *this; + } + + big_number& operator ^= (big_number const& n) + { + for (int i = 0; i< number_size; ++i) + m_number[i] ^= n.m_number[i]; + return *this; + } + + unsigned char& operator[](int i) + { assert(i >= 0 && i < number_size); return m_number[i]; } + + unsigned char const& operator[](int i) const + { assert(i >= 0 && i < number_size); return m_number[i]; } + + typedef const unsigned char* const_iterator; + typedef unsigned char* iterator; + + const_iterator begin() const { return m_number; } + const_iterator end() const { return m_number+number_size; } + + iterator begin() { return m_number; } + iterator end() { return m_number+number_size; } + + private: + + unsigned char m_number[number_size]; + + }; + + typedef big_number peer_id; + typedef big_number sha1_hash; + + inline std::ostream& operator<<(std::ostream& os, big_number const& peer) + { + for (big_number::const_iterator i = peer.begin(); + i != peer.end(); ++i) + { + os << std::hex << std::setw(2) << std::setfill('0') + << static_cast(*i); + } + os << std::dec << std::setfill(' '); + return os; + } + + inline std::istream& operator>>(std::istream& is, big_number& peer) + { + using namespace std; + + for (big_number::iterator i = peer.begin(); + i != peer.end(); ++i) + { + char c[2]; + is >> c[0] >> c[1]; + c[0] = tolower(c[0]); + c[1] = tolower(c[1]); + if ( + ((c[0] < '0' || c[0] > '9') && (c[0] < 'a' || c[0] > 'f')) + || ((c[1] < '0' || c[1] > '9') && (c[1] < 'a' || c[1] > 'f')) + || is.fail()) + { + is.setstate(ios_base::failbit); + return is; + } + *i = ((isdigit(c[0])?c[0]-'0':c[0]-'a'+10) << 4) + + (isdigit(c[1])?c[1]-'0':c[1]-'a'+10); + } + return is; + } + +} + +#endif // TORRENT_PEER_ID_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp new file mode 100755 index 000000000..f8ee2feb6 --- /dev/null +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_INFO_HPP_INCLUDED +#define TORRENT_PEER_INFO_HPP_INCLUDED + +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/size_type.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXPORT peer_info + { + enum + { + interesting = 0x1, + choked = 0x2, + remote_interested = 0x4, + remote_choked = 0x8, + supports_extensions = 0x10, + local_connection = 0x20, + handshake = 0x40, + connecting = 0x80, + queued = 0x100, + on_parole = 0x200, + seed = 0x400 +#ifndef TORRENT_DISABLE_ENCRYPTION + , rc4_encrypted = 0x800, + plaintext_encrypted = 0x1000 +#endif + }; + + unsigned int flags; + + enum peer_source_flags + { + tracker = 0x1, + dht = 0x2, + pex = 0x4, + lsd = 0x8, + resume_data = 0x10 + }; + + int source; + + tcp::endpoint ip; + float up_speed; + float down_speed; + float payload_up_speed; + float payload_down_speed; + size_type total_download; + size_type total_upload; + peer_id pid; + std::vector pieces; + int upload_limit; + int download_limit; + + // time since last request + time_duration last_request; + + // time since last download or upload + time_duration last_active; + + // the size of the send buffer for this peer + int send_buffer_size; + + // the number of failed hashes for this peer + int num_hashfails; + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + // in case the session settings is set + // to resolve countries, this is set to + // the two character country code this + // peer resides in. + char country[2]; +#endif + + size_type load_balancing; + + // this is the number of requests + // we have sent to this peer + // that we haven't got a response + // for yet + int download_queue_length; + + // this is the number of requests + // the peer has sent to us + // that we haven't sent yet + int upload_queue_length; + + // the number of times this IP + // has failed to connect + int failcount; + + // the currently downloading piece + // if piece index is -1 all associated + // members are just set to 0 + int downloading_piece_index; + int downloading_block_index; + int downloading_progress; + int downloading_total; + + std::string client; + + enum + { + standard_bittorrent = 0, + web_seed = 1 + }; + int connection_type; + + // approximate peer download rate + int remote_dl_rate; + }; + +} + +#endif // TORRENT_PEER_INFO_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/peer_request.hpp b/libtorrent/include/libtorrent/peer_request.hpp new file mode 100644 index 000000000..445ff4d7e --- /dev/null +++ b/libtorrent/include/libtorrent/peer_request.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_REQUEST_HPP_INCLUDED +#define TORRENT_PEER_REQUEST_HPP_INCLUDED + +namespace libtorrent +{ + struct TORRENT_EXPORT peer_request + { + int piece; + int start; + int length; + bool operator==(peer_request const& r) const + { return piece == r.piece && start == r.start && length == r.length; } + }; +} + +#endif // TORRENT_PEER_REQUEST_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/piece_block_progress.hpp b/libtorrent/include/libtorrent/piece_block_progress.hpp new file mode 100644 index 000000000..481ffc971 --- /dev/null +++ b/libtorrent/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXPORT piece_block_progress + { + // the piece and block index + // determines exactly which + // part of the torrent that + // is currently being downloaded + int piece_index; + int block_index; + // the number of bytes we have received + // of this block + int bytes_downloaded; + // the number of bytes in the block + int full_block_bytes; + }; +} + +#endif // TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp new file mode 100755 index 000000000..c52521d0a --- /dev/null +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -0,0 +1,425 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_PIECE_PICKER_HPP_INCLUDED +#define TORRENT_PIECE_PICKER_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + class torrent; + class peer_connection; + + struct TORRENT_EXPORT piece_block + { + piece_block(int p_index, int b_index) + : piece_index(p_index) + , block_index(b_index) + {} + int piece_index; + int block_index; + + bool operator<(piece_block const& b) const + { + if (piece_index < b.piece_index) return true; + if (piece_index == b.piece_index) return block_index < b.block_index; + return false; + } + + bool operator==(piece_block const& b) const + { return piece_index == b.piece_index && block_index == b.block_index; } + + bool operator!=(piece_block const& b) const + { return piece_index != b.piece_index || block_index != b.block_index; } + + }; + + class TORRENT_EXPORT piece_picker + { + public: + + struct block_info + { + block_info(): num_downloads(0), state(state_none) {} + // the peer this block was requested or + // downloaded from + tcp::endpoint peer; + // the number of times this block has been downloaded + unsigned num_downloads:14; + enum { state_none, state_requested, state_writing, state_finished }; + unsigned state:2; + }; + + // the peers that are downloading this piece + // are considered fast peers or slow peers. + // none is set if the blocks were downloaded + // in a previous session + enum piece_state_t + { none, slow, medium, fast }; + + struct downloading_piece + { + downloading_piece(): finished(0), writing(0), requested(0) {} + piece_state_t state; + + // the index of the piece + int index; + // info about each block + // this is a pointer into the m_block_info + // vector owned by the piece_picker + block_info* info; + // the number of blocks in the finished state + boost::int16_t finished; + // the number of blocks in the writing state + boost::int16_t writing; + // the number of blocks in the requested state + boost::int16_t requested; + }; + + piece_picker(int blocks_per_piece + , int total_num_blocks); + + void get_availability(std::vector& avail) const; + + void set_sequenced_download_threshold(int sequenced_download_threshold); + + // the vector tells which pieces we already have + // and which we don't have. + void files_checked( + std::vector const& pieces + , std::vector const& unfinished + , std::vector& verify_pieces); + + // increases the peer count for the given piece + // (is used when a HAVE or BITFIELD message is received) + void inc_refcount(int index); + + // decreases the peer count for the given piece + // (used when a peer disconnects) + void dec_refcount(int index); + + // these will increase and decrease the peer count + // of all pieces. They are used when seeds join + // or leave the swarm. + void inc_refcount_all(); + void dec_refcount_all(); + + // This indicates that we just received this piece + // it means that the refcounter will indicate that + // we are not interested in this piece anymore + // (i.e. we don't have to maintain a refcount) + void we_have(int index); + + // sets the priority of a piece. + void set_piece_priority(int index, int prio); + + // returns the priority for the piece at 'index' + int piece_priority(int index) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector& pieces) const; + + // ========== start deprecation ============== + + // fills the bitmask with 1's for pieces that are filtered + void filtered_pieces(std::vector& mask) const; + + // ========== end deprecation ============== + + // pieces should be the vector that represents the pieces a + // client has. It returns a list of all pieces that this client + // has and that are interesting to download. It returns them in + // priority order. It doesn't care about the download flag. + // The user of this function must lookup if any piece is + // marked as being downloaded. If the user of this function + // decides to download a piece, it must mark it as being downloaded + // itself, by using the mark_as_downloading() member function. + // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! + // The last argument is the tcp::endpoint of the peer that we'll download + // from. + void pick_pieces(const std::vector& pieces + , std::vector& interesting_blocks + , int num_pieces, bool prefer_whole_pieces + , tcp::endpoint peer, piece_state_t speed) const; + + // returns true if any client is currently downloading this + // piece-block, or if it's queued for downloading by some client + // or if it already has been successfully downloaded + bool is_requested(piece_block block) const; + // returns true if the block has been downloaded + bool is_downloaded(piece_block block) const; + // returns true if the block has been downloaded and written to disk + bool is_finished(piece_block block) const; + + // marks this piece-block as queued for downloading + void mark_as_downloading(piece_block block, tcp::endpoint const& peer + , piece_state_t s); + void mark_as_writing(piece_block block, tcp::endpoint const& peer); + void mark_as_finished(piece_block block, tcp::endpoint const& peer); + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(int index); + + // clears the given piece's download flag + // this means that this piece-block can be picked again + void abort_download(piece_block block); + + bool is_piece_finished(int index) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(int index) const; + + // the number of downloaded blocks that hasn't passed + // the hash-check yet + int unverified_blocks() const; + + void get_downloaders(std::vector& d, int index) const; + + std::vector const& get_download_queue() const + { return m_downloads; } + + boost::optional get_downloader(piece_block block) const; + + // the number of filtered pieces we don't have + int num_filtered() const { return m_num_filtered; } + + // the number of filtered pieces we already have + int num_have_filtered() const { return m_num_have_filtered; } + +#ifndef NDEBUG + // used in debug mode + void check_invariant(const torrent* t = 0) const; +#endif + + // functor that compares indices on downloading_pieces + struct has_index + { + has_index(int i): index(i) { assert(i >= 0); } + bool operator()(const downloading_piece& p) const + { return p.index == index; } + int index; + }; + + int blocks_in_last_piece() const + { return m_blocks_in_last_piece; } + + float distributed_copies() const; + + private: + + struct piece_pos + { + piece_pos() {} + piece_pos(int peer_count_, int index_) + : peer_count(peer_count_) + , downloading(0) + , piece_priority(1) + , index(index_) + { + assert(peer_count_ >= 0); + assert(index_ >= 0); + } + + // selects which vector to look in + unsigned peer_count : 10; + // is 1 if the piece is marked as being downloaded + unsigned downloading : 1; + // is 0 if the piece is filtered (not to be downloaded) + // 1 is normal priority (default) + // 2 is higher priority than pieces at the same availability level + // 3 is same priority as partial pieces + // 4 is higher priority than partial pieces + // 5 and 6 same priority as availability 1 (ignores availability) + // 7 is maximum priority (ignores availability) + unsigned piece_priority : 3; + // index in to the piece_info vector + unsigned index : 18; + + enum + { + // index is set to this to indicate that we have the + // piece. There is no entry for the piece in the + // buckets if this is the case. + we_have_index = 0x3ffff, + // the priority value that means the piece is filtered + filter_priority = 0, + // the max number the peer count can hold + max_peer_count = 0x3ff + }; + + bool have() const { return index == we_have_index; } + void set_have() { index = we_have_index; assert(have()); } + + bool filtered() const { return piece_priority == filter_priority; } + void filtered(bool f) { piece_priority = f ? filter_priority : 0; } + + int priority(int limit) const + { + if (filtered() || have()) return 0; + // pieces we are currently downloading have high priority + int prio = downloading ? (std::min)(1, int(peer_count)) : peer_count * 2; + // if the peer_count is 0 or 1, the priority cannot be higher + if (prio <= 1) return prio; + if (prio >= limit * 2) prio = limit * 2; + // the different priority levels + switch (piece_priority) + { + case 2: return prio - 1; + case 3: return (std::max)(prio / 2, 1); + case 4: return (std::max)(prio / 2 - 1, 1); + case 5: + case 6: return (std::min)(prio / 2 - 1, 2); + case 7: return 1; + } + return prio; + } + + bool operator!=(piece_pos p) const + { return index != p.index || peer_count != p.peer_count; } + + bool operator==(piece_pos p) const + { return index == p.index && peer_count == p.peer_count; } + + }; + + BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 4); + + bool is_ordered(int priority) const + { + return priority >= m_sequenced_download_threshold * 2; + } + + void add(int index); + void move(int vec_index, int elem_index); + + int add_interesting_blocks(const std::vector& piece_list + , const std::vector& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, bool prefer_whole_pieces + , tcp::endpoint peer, piece_state_t speed) const; + + downloading_piece& add_download_piece(); + void erase_download_piece(std::vector::iterator i); + + // this vector contains all pieces we don't have. + // in the first entry (index 0) is a vector of all pieces + // that no peer have, the vector at index 1 contains + // all pieces that exactly one peer have, index 2 contains + // all pieces exactly two peers have and so on. + // this is not entirely true. The availibility of a piece + // is adjusted depending on its priority. But the principle + // is that the higher index, the lower priority a piece has. + std::vector > m_piece_info; + + // this maps indices to number of peers that has this piece and + // index into the m_piece_info vectors. + // piece_pos::we_have_index means that we have the piece, so it + // doesn't exist in the piece_info buckets + // pieces with the filtered flag set doesn't have entries in + // the m_piece_info buckets either + std::vector m_piece_map; + + // each piece that's currently being downloaded + // has an entry in this list with block allocations. + // i.e. it says wich parts of the piece that + // is being downloaded + std::vector m_downloads; + + // this holds the information of the + // blocks in partially downloaded pieces. + // the first m_blocks_per_piece entries + // in the vector belongs to the first + // entry in m_downloads, the second + // m_blocks_per_piece entries to the + // second entry in m_downloads and so on. + std::vector m_block_info; + + int m_blocks_per_piece; + int m_blocks_in_last_piece; + + // the number of filtered pieces that we don't already + // have. total_number_of_pieces - number_of_pieces_we_have + // - num_filtered is supposed to the number of pieces + // we still want to download + int m_num_filtered; + + // the number of pieces we have that also are filtered + int m_num_have_filtered; + + // the number of pieces we have + int m_num_have; + + // the required popularity of a piece in order to download + // it in sequence instead of random order. + int m_sequenced_download_threshold; +#ifndef NDEBUG + bool m_files_checked_called; +#endif + }; + + inline int piece_picker::blocks_in_piece(int index) const + { + assert(index >= 0); + assert(index < (int)m_piece_map.size()); + if (index+1 == (int)m_piece_map.size()) + return m_blocks_in_last_piece; + else + return m_blocks_per_piece; + } + +} + +#endif // TORRENT_PIECE_PICKER_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp new file mode 100755 index 000000000..fffc3bfa2 --- /dev/null +++ b/libtorrent/include/libtorrent/policy.hpp @@ -0,0 +1,263 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POLICY_HPP_INCLUDED +#define TORRENT_POLICY_HPP_INCLUDED + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/size_type.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + + class torrent; + class peer_connection; + + enum + { + // the limits of the download queue size + min_request_queue = 2, + + // the amount of free upload allowed before + // the peer is choked + free_upload_amount = 4 * 16 * 1024 + }; + + void request_a_block( + torrent& t + , peer_connection& c + , std::vector ignore = std::vector()); + + class TORRENT_EXPORT policy + { + public: + + policy(torrent* t); + + // this is called every 10 seconds to allow + // for peer choking management + void pulse(); + + // this is called once for every peer we get from + // the tracker, pex, lsd or dht. + void peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid + , int source, char flags); + + // called when an incoming connection is accepted + void new_connection(peer_connection& c); + + // the given connection was just closed + void connection_closed(const peer_connection& c); + + // the peer has got at least one interesting piece + void peer_is_interesting(peer_connection& c); + + void piece_finished(int index, bool successfully_verified); + + // the peer choked us + void choked(peer_connection& c); + + int count_choked() const; + + // the peer unchoked us + void unchoked(peer_connection& c); + + // the peer is interested in our pieces + void interested(peer_connection& c); + + // the peer is not interested in our pieces + void not_interested(peer_connection& c); + +#ifndef NDEBUG + bool has_connection(const peer_connection* p); + + void check_invariant() const; +#endif + + struct peer + { + enum connection_type { not_connectable,connectable }; + + peer(const tcp::endpoint& ip, connection_type t, int src); + + size_type total_download() const; + size_type total_upload() const; + + // the ip/port pair this peer is or was connected on + // if it was a remote (incoming) connection, type is + // set thereafter. If it was a peer we got from the + // tracker, type is set to local_connection. + tcp::endpoint ip; + connection_type type; + +#ifndef TORRENT_DISABLE_ENCRYPTION + // Hints encryption support of peer. Only effective for + // and when the outgoing encryption policy allows both + // encrypted and non encrypted connections + // (pe_settings::out_enc_policy == enabled). The initial + // state of this flag determines the initial connection + // attempt type (true = encrypted, false = standard). + // This will be toggled everytime either an encrypted or + // non-encrypted handshake fails. + bool pe_support; +#endif + // the number of failed connection attempts this peer has + int failcount; + + // the number of times this peer has been + // part of a piece that failed the hash check + int hashfails; + + // this is true if the peer is a seed + bool seed; + + // the time when this peer was optimistically unchoked + // the last time. + libtorrent::ptime last_optimistically_unchoked; + + // the time when the peer connected to us + // or disconnected if it isn't connected right now + libtorrent::ptime connected; + + // for every valid piece we receive where this + // peer was one of the participants, we increase + // this value. For every invalid piece we receive + // where this peer was a participant, we decrease + // this value. If it sinks below a threshold, its + // considered a bad peer and will be banned. + int trust_points; + + // if this is true, the peer has previously participated + // in a piece that failed the piece hash check. This will + // put the peer on parole and only request entire pieces. + // if a piece pass that was partially requested from this + // peer it will leave parole mode and continue download + // pieces as normal peers. + bool on_parole; + + // this is the accumulated amount of + // uploaded and downloaded data to this + // peer. It only accounts for what was + // shared during the last connection to + // this peer. i.e. These are only updated + // when the connection is closed. For the + // total amount of upload and download + // we'll have to add thes figures with the + // statistics from the peer_connection. + size_type prev_amount_upload; + size_type prev_amount_download; + + // is set to true if this peer has been banned + bool banned; + + // a bitmap combining the peer_source flags + // from peer_info. + int source; + + // if the peer is connected now, this + // will refer to a valid peer_connection + peer_connection* connection; + }; + + int num_peers() const + { + return m_peers.size(); + } + + int num_uploads() const + { + return m_num_unchoked; + } + + typedef std::list::iterator iterator; + typedef std::list::const_iterator const_iterator; + iterator begin_peer() { return m_peers.begin(); } + iterator end_peer() { return m_peers.end(); } + + bool connect_one_peer(); + + private: + + bool unchoke_one_peer(); + void choke_one_peer(); + iterator find_choke_candidate(); + iterator find_unchoke_candidate(); + + // the seed prefix means that the + // function is used while seeding. + bool seed_unchoke_one_peer(); + void seed_choke_one_peer(); + iterator find_seed_choke_candidate(); + iterator find_seed_unchoke_candidate(); + + bool disconnect_one_peer(); + iterator find_disconnect_candidate(); + iterator find_connect_candidate(); + + std::list m_peers; + + torrent* m_torrent; + + // the number of unchoked peers + // at any given time + int m_num_unchoked; + + // free download we have got that hasn't + // been distributed yet. + size_type m_available_free_upload; + + // if there is a connection limit, + // we disconnect one peer every minute in hope of + // establishing a connection with a better peer + ptime m_last_optimistic_disconnect; + }; + +} + +#endif // TORRENT_POLICY_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/proxy_base.hpp b/libtorrent/include/libtorrent/proxy_base.hpp new file mode 100644 index 000000000..021802dd0 --- /dev/null +++ b/libtorrent/include/libtorrent/proxy_base.hpp @@ -0,0 +1,183 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_BASE_HPP_INCLUDED +#define TORRENT_PROXY_BASE_HPP_INCLUDED + +#include "libtorrent/io.hpp" +#include "libtorrent/socket.hpp" +#include +#include +#include +#include +#include + + +namespace libtorrent { + +class proxy_base : boost::noncopyable +{ +public: + + typedef stream_socket::lowest_layer_type lowest_layer_type; + typedef stream_socket::endpoint_type endpoint_type; + typedef stream_socket::protocol_type protocol_type; + + explicit proxy_base(asio::io_service& io_service) + : m_sock(io_service) + , m_resolver(io_service) + {} + + void set_proxy(std::string hostname, int port) + { + m_hostname = hostname; + m_port = port; + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + m_sock.async_read_some(buffers, handler); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, asio::error_code& ec) + { + return m_sock.read_some(buffers, ec); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock.read_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock.io_control(ioc); + } + + template + void io_control(IO_Control_Command& ioc, asio::error_code& ec) + { + m_sock.io_control(ioc, ec); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + m_sock.async_write_some(buffers, handler); + } + + void bind(endpoint_type const& endpoint) + { + m_sock.bind(endpoint); + } + + template + void bind(endpoint_type const& endpoint, Error_Handler const& error_handler) + { + m_sock.bind(endpoint, error_handler); + } + + void open(protocol_type const& p) + { + m_sock.open(p); + } + + template + void open(protocol_type const& p, Error_Handler const& error_handler) + { + m_sock.open(p, error_handler); + } + + void close() + { + m_remote_endpoint = endpoint_type(); + m_sock.close(); + } + + template + void close(Error_Handler const& error_handler) + { + m_sock.close(error_handler); + } + + endpoint_type remote_endpoint() + { + return m_remote_endpoint; + } + + template + endpoint_type remote_endpoint(Error_Handler const& error_handler) + { + return m_remote_endpoint; + } + + endpoint_type local_endpoint() + { + return m_sock.local_endpoint(); + } + + template + endpoint_type local_endpoint(Error_Handler const& error_handler) + { + return m_sock.local_endpoint(error_handler); + } + + asio::io_service& io_service() + { + return m_sock.io_service(); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + +protected: + + stream_socket m_sock; + // the socks5 proxy + std::string m_hostname; + int m_port; + + endpoint_type m_remote_endpoint; + + tcp::resolver m_resolver; +}; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/random_sample.hpp b/libtorrent/include/libtorrent/random_sample.hpp new file mode 100644 index 000000000..8d85080df --- /dev/null +++ b/libtorrent/include/libtorrent/random_sample.hpp @@ -0,0 +1,74 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANDOM_SAMPLE_HPP +#define TORRENT_RANDOM_SAMPLE_HPP + +#include +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + template + inline void random_sample_n(InIter start, InIter end + , OutIter out, Distance n) + { + Distance t = 0; + Distance m = 0; + Distance N = std::distance(start, end); + + assert(N >= n); + + while (m < n) + { + if ((std::rand() / (RAND_MAX + 1.f)) * (N - t) >= n - m) + { + ++start; + ++t; + } + else + { + *out = *start; + ++out; + ++start; + ++t; + ++m; + } + } + } + +} + +#endif diff --git a/libtorrent/include/libtorrent/resource_request.hpp b/libtorrent/include/libtorrent/resource_request.hpp new file mode 100755 index 000000000..1e41b9cbb --- /dev/null +++ b/libtorrent/include/libtorrent/resource_request.hpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2003, Magnus Jonsson, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOURCE_REQUEST_HPP_INCLUDED +#define TORRENT_RESOURCE_REQUEST_HPP_INCLUDED + +#include + +#ifdef min +#undef min +#endif + +#ifdef max +#undef max +#endif + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXPORT resource_request + { + resource_request() + : used(0) + , min(0) + , max(0) + , given(0) + , leftovers(0) + {} + + resource_request(int used_, int min_, int max_, int given_) + : used(used_) + , min(min_) + , max(max_) + , given(given_) + , leftovers(0) + {} + + int left() const + { + assert(given <= max); + assert(given >= min); + assert(used >= 0); + return (std::max)(given - used, 0); + } + + void reset() { used = leftovers; leftovers = 0; } + + static const int inf = boost::integer_traits::const_max; + + // right now I'm actively using this amount + int used; + + // given cannot be smaller than min + // and not greater than max. + int min; + int max; + + // Reply: Okay, you're allowed to use this amount (a compromise): + int given; + + // this is the amount of resources that exceeded the + // given limit. When the used field is reset (after resources + // have been distributed), it is reset to this number. + int leftovers; + }; +} + + +#endif diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp new file mode 100755 index 000000000..52ec62cdb --- /dev/null +++ b/libtorrent/include/libtorrent/session.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/fingerprint.hpp" + +#include "libtorrent/resource_request.hpp" +#include "libtorrent/storage.hpp" + +#ifdef _MSC_VER +# include +#endif + +namespace libtorrent +{ + struct torrent_plugin; + class torrent; + class ip_filter; + class port_filter; + class connection_queue; + + namespace fs = boost::filesystem; + + namespace aux + { + // workaround for microsofts + // hardware exceptions that makes + // it hard to debug stuff +#ifdef _MSC_VER + struct eh_initializer + { + eh_initializer() + { + ::_set_se_translator(straight_to_debugger); + } + + static void straight_to_debugger(unsigned int, _EXCEPTION_POINTERS*) + { throw; } + }; +#else + struct eh_initializer {}; +#endif + struct session_impl; + + struct filesystem_init + { + filesystem_init(); + }; + + } + + class TORRENT_EXPORT session_proxy + { + friend class session; + public: + session_proxy() {} + private: + session_proxy(boost::shared_ptr impl) + : m_impl(impl) {} + boost::shared_ptr m_impl; + }; + + class TORRENT_EXPORT session: public boost::noncopyable, aux::eh_initializer + { + public: + + session(fingerprint const& print = fingerprint("LT" + , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0)); + session( + fingerprint const& print + , std::pair listen_port_range + , char const* listen_interface = "0.0.0.0"); + + ~session(); + + // returns a list of all torrents in this session + std::vector get_torrents() const; + + // returns an invalid handle in case the torrent doesn't exist + torrent_handle find_torrent(sha1_hash const& info_hash) const; + + // all torrent_handles must be destructed before the session is destructed! + torrent_handle add_torrent( + torrent_info const& ti + , fs::path const& save_path + , entry const& resume_data = entry() + , bool compact_mode = true + , int block_size = 16 * 1024 + , storage_constructor_type sc = default_storage_constructor); + + // TODO: deprecated, this is for backwards compatibility only + torrent_handle add_torrent( + entry const& e + , fs::path const& save_path + , entry const& resume_data = entry() + , bool compact_mode = true + , int block_size = 16 * 1024 + , storage_constructor_type sc = default_storage_constructor) + { + return add_torrent(torrent_info(e), save_path, resume_data + , compact_mode, block_size, sc); + } + + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , entry const& resume_data = entry() + , bool compact_mode = true + , int block_size = 16 * 1024 + , storage_constructor_type sc = default_storage_constructor); + + session_proxy abort() { return session_proxy(m_impl); } + + session_status status() const; + +#ifndef TORRENT_DISABLE_DHT + void start_dht(entry const& startup_state = entry()); + void stop_dht(); + void set_dht_settings(dht_settings const& settings); + entry dht_state() const; + void add_dht_node(std::pair const& node); + void add_dht_router(std::pair const& node); +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + void set_pe_settings(pe_settings const& settings); + pe_settings const& get_pe_settings() const; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::function(torrent*)> ext); +#endif + + void set_ip_filter(ip_filter const& f); + void set_port_filter(port_filter const& f); + void set_peer_id(peer_id const& pid); + void set_key(int key); + peer_id id() const; + + bool is_listening() const; + + // if the listen port failed in some way + // you can retry to listen on another port- + // range with this function. If the listener + // succeeded and is currently listening, + // a call to this function will shut down the + // listen port and reopen it using these new + // properties (the given interface and port range). + // As usual, if the interface is left as 0 + // this function will return false on failure. + // If it fails, it will also generate alerts describing + // the error. It will return true on success. + bool listen_on( + std::pair const& port_range + , const char* net_interface = 0); + + // returns the port we ended up listening on + unsigned short listen_port() const; + + // Get the number of uploads. + int num_uploads() const; + + // Get the number of connections. This number also contains the + // number of half open connections. + int num_connections() const; + + void remove_torrent(const torrent_handle& h); + + void set_settings(session_settings const& s); + session_settings const& settings(); + + void set_peer_proxy(proxy_settings const& s); + void set_web_seed_proxy(proxy_settings const& s); + void set_tracker_proxy(proxy_settings const& s); + + proxy_settings const& peer_proxy() const; + proxy_settings const& web_seed_proxy() const; + proxy_settings const& tracker_proxy() const; + +#ifndef TORRENT_DISABLE_DHT + void set_dht_proxy(proxy_settings const& s); + proxy_settings const& dht_proxy() const; +#endif + + int upload_rate_limit() const; + int download_rate_limit() const; + + void set_upload_rate_limit(int bytes_per_second); + void set_download_rate_limit(int bytes_per_second); + void set_max_uploads(int limit); + void set_max_connections(int limit); + void set_max_half_open_connections(int limit); + + std::auto_ptr pop_alert(); + void set_severity_level(alert::severity_t s); + + connection_queue& get_connection_queue(); + + // starts/stops UPnP, NATPMP or LSD port mappers + // they are stopped by default + void start_lsd(); + void start_natpmp(); + void start_upnp(); + + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + // Resource management used for global limits. + resource_request m_ul_bandwidth_quota; + resource_request m_dl_bandwidth_quota; + resource_request m_uploads_quota; + resource_request m_connections_quota; + + private: + + // just a way to initialize boost.filesystem + // before the session_impl is created + aux::filesystem_init m_dummy; + + // data shared between the main thread + // and the working thread + boost::shared_ptr m_impl; + }; + +} + +#endif // TORRENT_SESSION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp new file mode 100644 index 000000000..d7a4f88c1 --- /dev/null +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -0,0 +1,322 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_SESSION_SETTINGS_HPP_INCLUDED + +#include "libtorrent/version.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + struct TORRENT_EXPORT proxy_settings + { + proxy_settings() : port(0), type(none) {} + + std::string hostname; + int port; + + std::string username; + std::string password; + + enum proxy_type + { + // a plain tcp socket is used, and + // the other settings are ignored. + none, + // socks4 server, requires username. + socks4, + // the hostname and port settings are + // used to connect to the proxy. No + // username or password is sent. + socks5, + // the hostname and port are used to + // connect to the proxy. the username + // and password are used to authenticate + // with the proxy server. + socks5_pw, + // the http proxy is only available for + // tracker and web seed traffic + // assumes anonymous access to proxy + http, + // http proxy with basic authentication + // uses username and password + http_pw + }; + + proxy_type type; + + }; + + struct TORRENT_EXPORT session_settings + { + session_settings(std::string const& user_agent_ = "libtorrent/" + LIBTORRENT_VERSION) + : user_agent(user_agent_) + , tracker_completion_timeout(60) + , tracker_receive_timeout(20) + , stop_tracker_timeout(10) + , tracker_maximum_response_length(1024*1024) + , piece_timeout(120) + , request_queue_time(3.f) + , max_allowed_in_request_queue(250) + , max_out_request_queue(200) + , whole_pieces_threshold(20) + , peer_timeout(120) + , urlseed_timeout(20) + , urlseed_pipeline_size(5) + , file_pool_size(40) + , allow_multiple_connections_per_ip(false) + , max_failcount(3) + , min_reconnect_time(60) + , peer_connect_timeout(10) + , ignore_limits_on_local_network(true) + , connection_speed(20) + , send_redundant_have(false) + , lazy_bitfields(true) + , inactivity_timeout(600) + , unchoke_interval(20) + , num_want(200) +#ifndef TORRENT_DISABLE_DHT + , use_dht_as_fallback(true) +#endif + {} + + // this is the user agent that will be sent to the tracker + // when doing requests. It is used to identify the client. + // It cannot contain \r or \n + std::string user_agent; + + // the number of seconds to wait until giving up on a + // tracker request if it hasn't finished + int tracker_completion_timeout; + + // the number of seconds where no data is received + // from the tracker until it should be considered + // as timed out + int tracker_receive_timeout; + + // the time to wait when sending a stopped message + // before considering a tracker to have timed out. + // this is usually shorter, to make the client quit + // faster + int stop_tracker_timeout; + + // if the content-length is greater than this value + // the tracker connection will be aborted + int tracker_maximum_response_length; + + // the number of seconds from a request is sent until + // it times out if no piece response is returned. + int piece_timeout; + + // the length of the request queue given in the number + // of seconds it should take for the other end to send + // all the pieces. i.e. the actual number of requests + // depends on the download rate and this number. + float request_queue_time; + + // the number of outstanding block requests a peer is + // allowed to queue up in the client. If a peer sends + // more requests than this (before the first one has + // been sent) the last request will be dropped. + // the higher this is, the faster upload speeds the + // client can get to a single peer. + int max_allowed_in_request_queue; + + // the maximum number of outstanding requests to + // send to a peer. This limit takes precedence over + // request_queue_time. + int max_out_request_queue; + + // if a whole piece can be downloaded in this number + // of seconds, or less, the peer_connection will prefer + // to request whole pieces at a time from this peer. + // The benefit of this is to better utilize disk caches by + // doing localized accesses and also to make it easier + // to identify bad peers if a piece fails the hash check. + int whole_pieces_threshold; + + // the number of seconds to wait for any activity on + // the peer wire before closing the connectiong due + // to time out. + int peer_timeout; + + // same as peer_timeout, but only applies to url-seeds. + // this is usually set lower, because web servers are + // expected to be more reliable. + int urlseed_timeout; + + // controls the pipelining size of url-seeds + int urlseed_pipeline_size; + + // sets the upper limit on the total number of files this + // session will keep open. The reason why files are + // left open at all is that some anti virus software + // hooks on every file close, and scans the file for + // viruses. deferring the closing of the files will + // be the difference between a usable system and + // a completely hogged down system. Most operating + // systems also has a limit on the total number of + // file descriptors a process may have open. It is + // usually a good idea to find this limit and set the + // number of connections and the number of files + // limits so their sum is slightly below it. + int file_pool_size; + + // false to not allow multiple connections from the same + // IP address. true will allow it. + bool allow_multiple_connections_per_ip; + + // the number of times we can fail to connect to a peer + // before we stop retrying it. + int max_failcount; + + // the number of seconds to wait to reconnect to a peer. + // this time is multiplied with the failcount. + int min_reconnect_time; + + // this is the timeout for a connection attempt. If + // the connect does not succeed within this time, the + // connection is dropped. The time is specified in seconds. + int peer_connect_timeout; + + // if set to true, upload, download and unchoke limits + // are ignored for peers on the local network. + bool ignore_limits_on_local_network; + + // the number of connection attempts that + // are made per second. + int connection_speed; + + // if this is set to true, have messages will be sent + // to peers that already have the piece. This is + // typically not necessary, but it might be necessary + // for collecting statistics in some cases. Default is false. + bool send_redundant_have; + + // if this is true, outgoing bitfields will never be fuil. If the + // client is seed, a few bits will be set to 0, and later filled + // in with have messages. This is to prevent certain ISPs + // from stopping people from seeding. + bool lazy_bitfields; + + // if a peer is uninteresting and uninterested for longer + // than this number of seconds, it will be disconnected. + // default is 10 minutes + int inactivity_timeout; + + // the number of seconds between chokes/unchokes + int unchoke_interval; + + // if this is set, this IP will be reported do the + // tracker in the ip= parameter. + address announce_ip; + + // the num want sent to trackers + int num_want; + +#ifndef TORRENT_DISABLE_DHT + // while this is true, the dht will note be used unless the + // tracker is online + bool use_dht_as_fallback; +#endif + }; + +#ifndef TORRENT_DISABLE_DHT + struct dht_settings + { + dht_settings() + : max_peers_reply(50) + , search_branching(5) + , service_port(0) + , max_fail_count(20) + {} + + // the maximum number of peers to send in a + // reply to get_peers + int max_peers_reply; + + // the number of simultanous "connections" when + // searching the DHT. + int search_branching; + + // the listen port for the dht. This is a UDP port. + // zero means use the same as the tcp interface + int service_port; + + // the maximum number of times a node can fail + // in a row before it is removed from the table. + int max_fail_count; + }; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + + struct pe_settings + { + pe_settings() + : out_enc_policy(enabled) + , in_enc_policy(enabled) + , allowed_enc_level(both) + , prefer_rc4(false) + {} + + enum enc_policy + { + forced, // disallow non encrypted connections + enabled, // allow encrypted and non encrypted connections + disabled // disallow encrypted connections + }; + + enum enc_level + { + plaintext, // use only plaintext encryption + rc4, // use only rc4 encryption + both // allow both + }; + + enc_policy out_enc_policy; + enc_policy in_enc_policy; + + enc_level allowed_enc_level; + // if the allowed encryption level is both, setting this to + // true will prefer rc4 if both methods are offered, plaintext + // otherwise + bool prefer_rc4; + }; +#endif + +} + +#endif diff --git a/libtorrent/include/libtorrent/session_status.hpp b/libtorrent/include/libtorrent/session_status.hpp new file mode 100644 index 000000000..adbb1b57d --- /dev/null +++ b/libtorrent/include/libtorrent/session_status.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATUS_HPP_INCLUDED +#define TORRENT_SESSION_STATUS_HPP_INCLUDED + + +namespace libtorrent +{ + + struct TORRENT_EXPORT session_status + { + bool has_incoming_connections; + + float upload_rate; + float download_rate; + + float payload_upload_rate; + float payload_download_rate; + + size_type total_download; + size_type total_upload; + + size_type total_payload_download; + size_type total_payload_upload; + + int num_peers; + +#ifndef TORRENT_DISABLE_DHT + int dht_nodes; + int dht_node_cache; + int dht_torrents; + size_type dht_global_nodes; +#endif + }; + +} + +#endif // TORRENT_SESSION_STATUS_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/size_type.hpp b/libtorrent/include/libtorrent/size_type.hpp new file mode 100755 index 000000000..6020a5ac3 --- /dev/null +++ b/libtorrent/include/libtorrent/size_type.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SIZE_TYPE_HPP_INCLUDED +#define TORRENT_SIZE_TYPE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace libtorrent +{ + typedef boost::int64_t size_type; +} + + +#endif diff --git a/libtorrent/include/libtorrent/socket.hpp b/libtorrent/include/libtorrent/socket.hpp new file mode 100755 index 000000000..c478a92ef --- /dev/null +++ b/libtorrent/include/libtorrent/socket.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_HPP_INCLUDED +#define TORRENT_SOCKET_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +// if building as Objective C++, asio's template +// parameters Protocol has to be renamed to avoid +// colliding with keywords + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +#undef Protocol +#endif + +#include "libtorrent/io.hpp" +#include "libtorrent/time.hpp" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace libtorrent +{ + +/* + namespace asio = boost::asio; + + using boost::asio::ipv4::tcp; + using boost::asio::ipv4::address; + using boost::asio::stream_socket; + using boost::asio::datagram_socket; + using boost::asio::socket_acceptor; + using boost::asio::io_service; + using boost::asio::ipv4::host_resolver; + using boost::asio::async_write; + using boost::asio::ipv4::host; + using boost::asio::deadline_timer; +*/ +// namespace asio = ::asio; + + using asio::ip::tcp; + using asio::ip::udp; + typedef asio::ip::tcp::socket stream_socket; + typedef asio::ip::address address; + typedef asio::ip::address_v4 address_v4; + typedef asio::ip::address_v6 address_v6; + typedef asio::ip::udp::socket datagram_socket; + typedef asio::ip::tcp::acceptor socket_acceptor; + typedef asio::io_service io_service; + + using asio::async_write; + + typedef asio::basic_deadline_timer deadline_timer; + + namespace detail + { + template + void write_address(address const& a, OutIt& out) + { + if (a.is_v4()) + { + write_uint32(a.to_v4().to_ulong(), out); + } + else if (a.is_v6()) + { + asio::ip::address_v6::bytes_type bytes + = a.to_v6().to_bytes(); + std::copy(bytes.begin(), bytes.end(), out); + } + } + + template + address read_v4_address(InIt& in) + { + unsigned long ip = read_uint32(in); + return asio::ip::address_v4(ip); + } + + template + address read_v6_address(InIt& in) + { + typedef asio::ip::address_v6::bytes_type bytes_t; + bytes_t bytes; + for (bytes_t::iterator i = bytes.begin() + , end(bytes.end()); i != end; ++i) + *i = read_uint8(in); + return asio::ip::address_v6(bytes); + } + + template + void write_endpoint(Endpoint const& e, OutIt& out) + { + write_address(e.address(), out); + write_uint16(e.port(), out); + } + + template + Endpoint read_v4_endpoint(InIt& in) + { + address addr = read_v4_address(in); + int port = read_uint16(in); + return Endpoint(addr, port); + } + + template + Endpoint read_v6_endpoint(InIt& in) + { + address addr = read_v6_address(in); + int port = read_uint16(in); + return Endpoint(addr, port); + } + } +} + +#endif // TORRENT_SOCKET_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/socket_type.hpp b/libtorrent/include/libtorrent/socket_type.hpp new file mode 100644 index 000000000..9ed8f9a4b --- /dev/null +++ b/libtorrent/include/libtorrent/socket_type.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_TYPE +#define TORRENT_SOCKET_TYPE + +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/socks4_stream.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/variant_stream.hpp" + +namespace libtorrent +{ + typedef variant_stream< + stream_socket + , socks5_stream + , socks4_stream + , http_stream> socket_type; +} + +#endif + diff --git a/libtorrent/include/libtorrent/socks4_stream.hpp b/libtorrent/include/libtorrent/socks4_stream.hpp new file mode 100644 index 000000000..6110dbe4d --- /dev/null +++ b/libtorrent/include/libtorrent/socks4_stream.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKS4_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS4_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" + +namespace libtorrent { + +class socks4_stream : public proxy_base +{ +public: + + explicit socks4_stream(asio::io_service& io_service) + : proxy_base(io_service) + {} + + void set_username(std::string const& user) + { + m_user = user; + } + + typedef boost::function handler_type; + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send SOCKS4 CONNECT message + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + + tcp::resolver::query q(m_hostname + , boost::lexical_cast(m_port)); + m_resolver.async_resolve(q, boost::bind( + &socks4_stream::name_lookup, this, _1, _2, h)); + } + +private: + + void name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(asio::error_code const& e, boost::shared_ptr h); + void handshake1(asio::error_code const& e, boost::shared_ptr h); + void handshake2(asio::error_code const& e, boost::shared_ptr h); + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; +}; + +} + +#endif diff --git a/libtorrent/include/libtorrent/socks5_stream.hpp b/libtorrent/include/libtorrent/socks5_stream.hpp new file mode 100644 index 000000000..8bfc74c59 --- /dev/null +++ b/libtorrent/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" + +namespace libtorrent { + +class socks5_stream : public proxy_base +{ +public: + + explicit socks5_stream(asio::io_service& io_service) + : proxy_base(io_service) + {} + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + typedef boost::function handler_type; + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send SOCKS5 authentication method message + // 4. read SOCKS5 authentication response + // 5. send username+password + // 6. send SOCKS5 CONNECT message + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + + tcp::resolver::query q(m_hostname + , boost::lexical_cast(m_port)); + m_resolver.async_resolve(q, boost::bind( + &socks5_stream::name_lookup, this, _1, _2, h)); + } + +private: + + void name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(asio::error_code const& e, boost::shared_ptr h); + void handshake1(asio::error_code const& e, boost::shared_ptr h); + void handshake2(asio::error_code const& e, boost::shared_ptr h); + void handshake3(asio::error_code const& e, boost::shared_ptr h); + void handshake4(asio::error_code const& e, boost::shared_ptr h); + void socks_connect(boost::shared_ptr h); + void connect1(asio::error_code const& e, boost::shared_ptr h); + void connect2(asio::error_code const& e, boost::shared_ptr h); + void connect3(asio::error_code const& e, boost::shared_ptr h); + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; +}; + +} + +#endif diff --git a/libtorrent/include/libtorrent/stat.hpp b/libtorrent/include/libtorrent/stat.hpp new file mode 100755 index 000000000..8ef8da1b4 --- /dev/null +++ b/libtorrent/include/libtorrent/stat.hpp @@ -0,0 +1,193 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_HPP_INCLUDED +#define TORRENT_STAT_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/size_type.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + class TORRENT_EXPORT stat + { + friend class invariant_access; + public: + enum { history = 10 }; + + stat() + : m_downloaded_payload(0) + , m_uploaded_payload(0) + , m_downloaded_protocol(0) + , m_uploaded_protocol(0) + , m_total_download_payload(0) + , m_total_upload_payload(0) + , m_total_download_protocol(0) + , m_total_upload_protocol(0) + , m_mean_download_rate(0) + , m_mean_upload_rate(0) + , m_mean_download_payload_rate(0) + , m_mean_upload_payload_rate(0) + { + std::fill(m_download_rate_history, m_download_rate_history+history, 0.f); + std::fill(m_upload_rate_history, m_upload_rate_history+history, 0.f); + std::fill(m_download_payload_rate_history, m_download_payload_rate_history+history, 0.f); + std::fill(m_upload_payload_rate_history, m_upload_payload_rate_history+history, 0.f); + } + + void operator+=(const stat& s) + { + INVARIANT_CHECK; + + m_downloaded_payload += s.m_downloaded_payload; + m_total_download_payload += s.m_downloaded_payload; + m_downloaded_protocol += s.m_downloaded_protocol; + m_total_download_protocol += s.m_downloaded_protocol; + + m_uploaded_payload += s.m_uploaded_payload; + m_total_upload_payload += s.m_uploaded_payload; + m_uploaded_protocol += s.m_uploaded_protocol; + m_total_upload_protocol += s.m_uploaded_protocol; + } + + void received_bytes(int bytes_payload, int bytes_protocol) + { + INVARIANT_CHECK; + + assert(bytes_payload >= 0); + assert(bytes_protocol >= 0); + + m_downloaded_payload += bytes_payload; + m_total_download_payload += bytes_payload; + m_downloaded_protocol += bytes_protocol; + m_total_download_protocol += bytes_protocol; + } + + void sent_bytes(int bytes_payload, int bytes_protocol) + { + INVARIANT_CHECK; + + assert(bytes_payload >= 0); + assert(bytes_protocol >= 0); + + m_uploaded_payload += bytes_payload; + m_total_upload_payload += bytes_payload; + m_uploaded_protocol += bytes_protocol; + m_total_upload_protocol += bytes_protocol; + } + + // should be called once every second + void second_tick(float tick_interval); + + float upload_rate() const { return m_mean_upload_rate; } + float download_rate() const { return m_mean_download_rate; } + + float upload_payload_rate() const { return m_mean_upload_payload_rate; } + float download_payload_rate() const { return m_mean_download_payload_rate; } + + size_type total_payload_upload() const { return m_total_upload_payload; } + size_type total_payload_download() const { return m_total_download_payload; } + + size_type total_protocol_upload() const { return m_total_upload_protocol; } + size_type total_protocol_download() const { return m_total_download_protocol; } + + // this is used to offset the statistics when a + // peer_connection is opened and have some previous + // transfers from earlier connections. + void add_stat(size_type downloaded, size_type uploaded) + { + m_total_download_payload += downloaded; + m_total_upload_payload += uploaded; + } + + private: + +#ifndef NDEBUG + void check_invariant() const + { + assert(m_mean_upload_rate >= 0); + assert(m_mean_download_rate >= 0); + assert(m_mean_upload_payload_rate >= 0); + assert(m_mean_download_payload_rate >= 0); + assert(m_total_upload_payload >= 0); + assert(m_total_download_payload >= 0); + assert(m_total_upload_protocol >= 0); + assert(m_total_download_protocol >= 0); + } +#endif + + // history of download/upload speeds a few seconds back + float m_download_rate_history[history]; + float m_upload_rate_history[history]; + + float m_download_payload_rate_history[history]; + float m_upload_payload_rate_history[history]; + + // the accumulators we are adding the downloads/uploads + // to this second. This only counts the actual payload + // and ignores the bytes sent as protocol chatter. + int m_downloaded_payload; + int m_uploaded_payload; + + // the accumulators we are adding the downloads/uploads + // to this second. This only counts the protocol + // chatter and ignores the actual payload + int m_downloaded_protocol; + int m_uploaded_protocol; + + // total download/upload counters + // only counting payload data + size_type m_total_download_payload; + size_type m_total_upload_payload; + + // total download/upload counters + // only counting protocol chatter + size_type m_total_download_protocol; + size_type m_total_upload_protocol; + + // current mean download/upload rates + float m_mean_download_rate; + float m_mean_upload_rate; + + float m_mean_download_payload_rate; + float m_mean_upload_payload_rate; + }; + +} + +#endif // TORRENT_STAT_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp new file mode 100755 index 000000000..8a10c7148 --- /dev/null +++ b/libtorrent/include/libtorrent/storage.hpp @@ -0,0 +1,359 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_HPP_INCLUDE +#define TORRENT_STORAGE_HPP_INCLUDE + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + namespace aux + { + struct piece_checker_data; + } + + namespace fs = boost::filesystem; + + class session; + struct file_pool; + struct disk_io_job; + +#if defined(_WIN32) && defined(UNICODE) + + TORRENT_EXPORT std::wstring safe_convert(std::string const& s); + +#endif + + TORRENT_EXPORT std::vector > get_filesizes( + torrent_info const& t + , fs::path p); + + TORRENT_EXPORT bool match_filesizes( + torrent_info const& t + , fs::path p + , std::vector > const& sizes + , bool compact_mode + , std::string* error = 0); + + struct TORRENT_EXPORT file_allocation_failed: std::exception + { + file_allocation_failed(const char* error_msg): m_msg(error_msg) {} + virtual const char* what() const throw() { return m_msg.c_str(); } + virtual ~file_allocation_failed() throw() {} + std::string m_msg; + }; + + struct TORRENT_EXPORT partial_hash + { + partial_hash(): offset(0) {} + // the number of bytes in the piece that has been hashed + int offset; + // the sha-1 context + hasher h; + }; + + struct TORRENT_EXPORT storage_interface + { + // create directories and set file sizes + // if allocate_files is true. + // allocate_files is true if allocation mode + // is set to full and sparse files are supported + virtual void initialize(bool allocate_files) = 0; + + // may throw file_error if storage for slot does not exist + virtual size_type read(char* buf, int slot, int offset, int size) = 0; + + // may throw file_error if storage for slot hasn't been allocated + virtual void write(const char* buf, int slot, int offset, int size) = 0; + + virtual bool move_storage(fs::path save_path) = 0; + + // verify storage dependent fast resume entries + virtual bool verify_resume_data(entry& rd, std::string& error) = 0; + + // write storage dependent fast resume entries + virtual void write_resume_data(entry& rd) const = 0; + + // moves (or copies) the content in src_slot to dst_slot + virtual void move_slot(int src_slot, int dst_slot) = 0; + + // swaps the data in slot1 and slot2 + virtual void swap_slots(int slot1, int slot2) = 0; + + // swaps the puts the data in slot1 in slot2, the data in slot2 + // in slot3 and the data in slot3 in slot1 + virtual void swap_slots3(int slot1, int slot2, int slot3) = 0; + + // returns the sha1-hash for the data at the given slot + virtual sha1_hash hash_for_slot(int slot, partial_hash& h, int piece_size) = 0; + + // this will close all open files that are opened for + // writing. This is called when a torrent has finished + // downloading. + virtual void release_files() = 0; + virtual ~storage_interface() {} + }; + + typedef storage_interface* (&storage_constructor_type)( + torrent_info const&, fs::path const& + , file_pool&); + + TORRENT_EXPORT storage_interface* default_storage_constructor(torrent_info const& ti + , fs::path const& path, file_pool& fp); + + // returns true if the filesystem the path relies on supports + // sparse files or automatic zero filling of files. + TORRENT_EXPORT bool supports_sparse_files(fs::path const& p); + + struct disk_io_thread; + + class TORRENT_EXPORT piece_manager + : public intrusive_ptr_base + , boost::noncopyable + { + friend class invariant_access; + friend struct disk_io_thread; + public: + + piece_manager( + boost::shared_ptr const& torrent + , torrent_info const& ti + , fs::path const& path + , file_pool& fp + , disk_io_thread& io + , storage_constructor_type sc); + + ~piece_manager(); + + bool check_fastresume(aux::piece_checker_data& d + , std::vector& pieces, int& num_pieces, bool compact_mode); + std::pair check_files(std::vector& pieces + , int& num_pieces, boost::recursive_mutex& mutex); + + void write_resume_data(entry& rd) const; + bool verify_resume_data(entry& rd, std::string& error); + + bool is_allocating() const + { return m_state == state_allocating; } + + void mark_failed(int index); + + unsigned long piece_crc( + int slot_index + , int block_size + , piece_picker::block_info const* bi); + + int slot_for_piece(int piece_index) const; + + void async_read( + peer_request const& r + , boost::function const& handler); + + void async_write( + peer_request const& r + , char const* buffer + , boost::function const& f); + + void async_hash(int piece, boost::function const& f); + + fs::path save_path() const; + + void async_release_files( + boost::function const& handler + = boost::function()); + + void async_move_storage(fs::path const& p + , boost::function const& handler); + + // fills the vector that maps all allocated + // slots to the piece that is stored (or + // partially stored) there. -2 is the index + // of unassigned pieces and -1 is unallocated + void export_piece_map(std::vector& pieces) const; + + bool compact_allocation() const + { return m_compact_mode; } + +#ifndef NDEBUG + std::string name() const { return m_info.name(); } +#endif + + private: + + bool allocate_slots(int num_slots, bool abort_on_disk = false); + + int identify_data( + const std::vector& piece_data + , int current_slot + , std::vector& have_pieces + , int& num_pieces + , const std::multimap& hash_to_piece + , boost::recursive_mutex& mutex); + + size_type read_impl( + char* buf + , int piece_index + , int offset + , int size); + + void write_impl( + const char* buf + , int piece_index + , int offset + , int size); + + sha1_hash hash_for_piece_impl(int piece); + + void release_files_impl(); + + bool move_storage_impl(fs::path const& save_path); + + int allocate_slot_for_piece(int piece_index); +#ifndef NDEBUG + void check_invariant() const; +#ifdef TORRENT_STORAGE_DEBUG + void debug_log() const; +#endif +#endif + boost::scoped_ptr m_storage; + + // if this is true, pieces are always allocated at the + // lowest possible slot index. If it is false, pieces + // are always written to their final place immediately + bool m_compact_mode; + + // if this is true, pieces that haven't been downloaded + // will be filled with zeroes. Not filling with zeroes + // will not work in some cases (where a seek cannot pass + // the end of the file). + bool m_fill_mode; + + // a bitmask representing the pieces we have + std::vector m_have_piece; + + torrent_info const& m_info; + + // slots that haven't had any file storage allocated + std::vector m_unallocated_slots; + // slots that have file storage, but isn't assigned to a piece + std::vector m_free_slots; + + enum + { + has_no_slot = -3 // the piece has no storage + }; + + // maps piece indices to slots. If a piece doesn't + // have any storage, it is set to 'has_no_slot' + std::vector m_piece_to_slot; + + enum + { + unallocated = -1, // the slot is unallocated + unassigned = -2 // the slot is allocated but not assigned to a piece + }; + + // maps slots to piece indices, if a slot doesn't have a piece + // it can either be 'unassigned' or 'unallocated' + std::vector m_slot_to_piece; + + fs::path m_save_path; + + mutable boost::recursive_mutex m_mutex; + + bool m_allocating; + boost::mutex m_allocating_monitor; + boost::condition m_allocating_condition; + + // these states are used while checking/allocating the torrent + + enum { + // the default initial state + state_none, + // the file checking is complete + state_finished, + // creating the directories + state_create_files, + // checking the files + state_full_check, + // allocating files (in non-compact mode) + state_allocating + } m_state; + int m_current_slot; + + std::vector m_piece_data; + + // this maps a piece hash to piece index. It will be + // build the first time it is used (to save time if it + // isn't needed) + std::multimap m_hash_to_piece; + + std::map m_piece_hasher; + + disk_io_thread& m_io_thread; + + // the reason for this to be a void pointer + // is to avoid creating a dependency on the + // torrent. This shared_ptr is here only + // to keep the torrent object alive until + // the piece_manager destructs. This is because + // the torrent_info object is owned by the torrent. + boost::shared_ptr m_torrent; + }; + +} + +#endif // TORRENT_STORAGE_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp new file mode 100644 index 000000000..2227fc932 --- /dev/null +++ b/libtorrent/include/libtorrent/time.hpp @@ -0,0 +1,388 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TIME_HPP_INCLUDED +#define TORRENT_TIME_HPP_INCLUDED + +#include + +#ifndef _WIN32 +#include +#endif + +namespace libtorrent +{ + inline char const* time_now_string() + { + time_t t = std::time(0); + tm* timeinfo = std::localtime(&t); + static char str[200]; + std::strftime(str, 200, "%b %d %X", timeinfo); + return str; + } +} + +#if (!defined (__MACH__) && !defined (_WIN32) && (!defined(_POSIX_MONOTONIC_CLOCK) \ + || _POSIX_MONOTONIC_CLOCK < 0)) || defined (TORRENT_USE_BOOST_DATE_TIME) + +#include + +namespace libtorrent +{ + typedef boost::posix_time::ptime ptime; + typedef boost::posix_time::time_duration time_duration; + inline ptime time_now() + { return boost::posix_time::microsec_clock::universal_time(); } + inline ptime min_time() + { return boost::posix_time::ptime(boost::posix_time::min_date_time); } + inline ptime max_time() + { return boost::posix_time::ptime(boost::posix_time::max_date_time); } + inline time_duration seconds(int s) { return boost::posix_time::seconds(s); } + inline time_duration milliseconds(int s) { return boost::posix_time::milliseconds(s); } + inline time_duration microsec(int s) { return boost::posix_time::microsec(s); } + inline time_duration minutes(int s) { return boost::posix_time::minutes(s); } + inline time_duration hours(int s) { return boost::posix_time::hours(s); } + + inline int total_seconds(time_duration td) + { return td.total_seconds(); } + inline int total_milliseconds(time_duration td) + { return td.total_milliseconds(); } + inline boost::int64_t total_microseconds(time_duration td) + { return td.total_microseconds(); } + +} + +#else + +#include +#include + +namespace libtorrent +{ + // libtorrent time_duration type + struct time_duration + { + time_duration() {} + time_duration operator/(int rhs) const { return time_duration(diff / rhs); } + explicit time_duration(boost::int64_t d) : diff(d) {} + boost::int64_t diff; + }; + + inline bool is_negative(time_duration dt) { return dt.diff < 0; } + inline bool operator<(time_duration lhs, time_duration rhs) + { return lhs.diff < rhs.diff; } + inline bool operator<=(time_duration lhs, time_duration rhs) + { return lhs.diff <= rhs.diff; } + inline bool operator>(time_duration lhs, time_duration rhs) + { return lhs.diff > rhs.diff; } + inline bool operator>=(time_duration lhs, time_duration rhs) + { return lhs.diff >= rhs.diff; } + + // libtorrent time type + struct ptime + { + ptime() {} + explicit ptime(boost::int64_t t): time(t) {} + boost::int64_t time; + }; + + inline bool operator>(ptime lhs, ptime rhs) + { return lhs.time > rhs.time; } + inline bool operator>=(ptime lhs, ptime rhs) + { return lhs.time >= rhs.time; } + inline bool operator<=(ptime lhs, ptime rhs) + { return lhs.time <= rhs.time; } + inline bool operator<(ptime lhs, ptime rhs) + { return lhs.time < rhs.time; } + inline bool operator!=(ptime lhs, ptime rhs) + { return lhs.time != rhs.time;} + inline bool operator==(ptime lhs, ptime rhs) + { return lhs.time == rhs.time;} + inline time_duration operator-(ptime lhs, ptime rhs) + { return time_duration(lhs.time - rhs.time); } + inline ptime operator+(ptime lhs, time_duration rhs) + { return ptime(lhs.time + rhs.diff); } + inline ptime operator+(time_duration lhs, ptime rhs) + { return ptime(rhs.time + lhs.diff); } + inline ptime operator-(ptime lhs, time_duration rhs) + { return ptime(lhs.time - rhs.diff); } + + ptime time_now(); + inline ptime min_time() { return ptime(0); } + inline ptime max_time() { return ptime((std::numeric_limits::max)()); } + int total_seconds(time_duration td); + int total_milliseconds(time_duration td); + boost::int64_t total_microseconds(time_duration td); +} + +// asio time_traits +namespace asio +{ + template<> + struct time_traits + { + typedef libtorrent::ptime time_type; + typedef libtorrent::time_duration duration_type; + static time_type now() + { return time_type(libtorrent::time_now()); } + static time_type add(time_type t, duration_type d) + { return time_type(t.time + d.diff);} + static duration_type subtract(time_type t1, time_type t2) + { return duration_type(t1 - t2); } + static bool less_than(time_type t1, time_type t2) + { return t1 < t2; } + static boost::posix_time::time_duration to_posix_duration( + duration_type d) + { return boost::posix_time::microseconds(libtorrent::total_microseconds(d)); } + }; +} + +#if defined(__MACH__) + +#include +#include + +// high precision timer for darwin intel and ppc + +namespace libtorrent +{ + namespace aux + { + inline boost::int64_t absolutetime_to_microseconds(boost::int64_t at) + { + static mach_timebase_info_data_t timebase_info = {0,0}; + if (timebase_info.denom == 0) + mach_timebase_info(&timebase_info); + // make sure we don't overflow + assert((at >= 0 && at >= at / 1000 * timebase_info.numer / timebase_info.denom) + || (at < 0 && at < at / 1000 * timebase_info.numer / timebase_info.denom)); + return at / 1000 * timebase_info.numer / timebase_info.denom; + } + + inline boost::int64_t microseconds_to_absolutetime(boost::int64_t ms) + { + static mach_timebase_info_data_t timebase_info = {0,0}; + if (timebase_info.denom == 0) + { + mach_timebase_info(&timebase_info); + assert(timebase_info.numer > 0); + assert(timebase_info.denom > 0); + } + // make sure we don't overflow + assert((ms >= 0 && ms <= ms * timebase_info.denom / timebase_info.numer * 1000) + || (ms < 0 && ms > ms * timebase_info.denom / timebase_info.numer * 1000)); + return ms * timebase_info.denom / timebase_info.numer * 1000; + } + } + + inline int total_seconds(time_duration td) + { + return aux::absolutetime_to_microseconds(td.diff) + / 1000000; + } + inline int total_milliseconds(time_duration td) + { + return aux::absolutetime_to_microseconds(td.diff) + / 1000; + } + inline boost::int64_t total_microseconds(time_duration td) + { + return aux::absolutetime_to_microseconds(td.diff); + } + + inline ptime time_now() { return ptime(mach_absolute_time()); } + + inline time_duration microsec(boost::int64_t s) + { + return time_duration(aux::microseconds_to_absolutetime(s)); + } + inline time_duration milliseconds(boost::int64_t s) + { + return time_duration(aux::microseconds_to_absolutetime(s * 1000)); + } + inline time_duration seconds(boost::int64_t s) + { + return time_duration(aux::microseconds_to_absolutetime(s * 1000000)); + } + inline time_duration minutes(boost::int64_t s) + { + return time_duration(aux::microseconds_to_absolutetime(s * 1000000 * 60)); + } + inline time_duration hours(boost::int64_t s) + { + return time_duration(aux::microseconds_to_absolutetime(s * 1000000 * 60 * 60)); + } + +} +#elif defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace libtorrent +{ + namespace aux + { + inline boost::int64_t performance_counter_to_microseconds(boost::int64_t pc) + { + static LARGE_INTEGER performace_counter_frequency = {0,0}; + if (performace_counter_frequency.QuadPart == 0) + QueryPerformanceFrequency(&performace_counter_frequency); + +#ifndef NDEBUG + // make sure we don't overflow + boost::int64_t ret = (pc * 1000 / performace_counter_frequency.QuadPart) * 1000; + assert((pc >= 0 && pc >= ret) || (pc < 0 && pc < ret)); +#endif + return (pc * 1000 / performace_counter_frequency.QuadPart) * 1000; + } + + inline boost::int64_t microseconds_to_performance_counter(boost::int64_t ms) + { + static LARGE_INTEGER performace_counter_frequency = {0,0}; + if (performace_counter_frequency.QuadPart == 0) + QueryPerformanceFrequency(&performace_counter_frequency); +#ifndef NDEBUG + // make sure we don't overflow + boost::int64_t ret = (ms / 1000) * performace_counter_frequency.QuadPart / 1000; + assert((ms >= 0 && ms <= ret) + || (ms < 0 && ms > ret)); +#endif + return (ms / 1000) * performace_counter_frequency.QuadPart / 1000; + } + } + + inline int total_seconds(time_duration td) + { + return aux::performance_counter_to_microseconds(td.diff) + / 1000000; + } + inline int total_milliseconds(time_duration td) + { + return aux::performance_counter_to_microseconds(td.diff) + / 1000; + } + inline boost::int64_t total_microseconds(time_duration td) + { + return aux::performance_counter_to_microseconds(td.diff); + } + + inline ptime time_now() + { + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return ptime(now.QuadPart); + } + + inline time_duration microsec(boost::int64_t s) + { + return time_duration(aux::microseconds_to_performance_counter(s)); + } + inline time_duration milliseconds(boost::int64_t s) + { + return time_duration(aux::microseconds_to_performance_counter( + s * 1000)); + } + inline time_duration seconds(boost::int64_t s) + { + return time_duration(aux::microseconds_to_performance_counter( + s * 1000000)); + } + inline time_duration minutes(boost::int64_t s) + { + return time_duration(aux::microseconds_to_performance_counter( + s * 1000000 * 60)); + } + inline time_duration hours(boost::int64_t s) + { + return time_duration(aux::microseconds_to_performance_counter( + s * 1000000 * 60 * 60)); + } + +} + +#elif defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 + +#include + +namespace libtorrent +{ + inline int total_seconds(time_duration td) + { + return td.diff / 1000000; + } + inline int total_milliseconds(time_duration td) + { + return td.diff / 1000; + } + inline boost::int64_t total_microseconds(time_duration td) + { + return td.diff; + } + + inline ptime time_now() + { + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ptime(boost::int64_t(ts.tv_sec) * 1000000 + ts.tv_nsec / 1000); + } + + inline time_duration microsec(boost::int64_t s) + { + return time_duration(s); + } + inline time_duration milliseconds(boost::int64_t s) + { + return time_duration(s * 1000); + } + inline time_duration seconds(boost::int64_t s) + { + return time_duration(s * 1000000); + } + inline time_duration minutes(boost::int64_t s) + { + return time_duration(s * 1000000 * 60); + } + inline time_duration hours(boost::int64_t s) + { + return time_duration(s * 1000000 * 60 * 60); + } + +} + +#endif + +#endif +#endif + diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp new file mode 100755 index 000000000..1274c46fc --- /dev/null +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -0,0 +1,801 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HPP_INCLUDE +#define TORRENT_TORRENT_HPP_INCLUDE + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/resource_request.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/escape_string.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" + +namespace libtorrent +{ +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + struct logger; +#endif + + class piece_manager; + struct torrent_plugin; + + namespace aux + { + struct session_impl; + struct piece_checker_data; + } + + namespace fs = boost::filesystem; + + // a torrent is a class that holds information + // for a specific download. It updates itself against + // the tracker + class TORRENT_EXPORT torrent: public request_callback + , public boost::enable_shared_from_this + { + public: + + torrent( + aux::session_impl& ses + , aux::checker_impl& checker + , torrent_info const& tf + , fs::path const& save_path + , tcp::endpoint const& net_interface + , bool compact_mode + , int block_size + , session_settings const& s + , storage_constructor_type sc); + + // used with metadata-less torrents + // (the metadata is downloaded from the peers) + torrent( + aux::session_impl& ses + , aux::checker_impl& checker + , char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , tcp::endpoint const& net_interface + , bool compact_mode + , int block_size + , session_settings const& s + , storage_constructor_type sc); + + ~torrent(); + + // starts the announce timer + void start(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::shared_ptr); +#endif + + // this is called when the torrent has metadata. + // it will initialize the storage and the piece-picker + void init(); + + // this will flag the torrent as aborted. The main + // loop in session_impl will check for this state + // on all torrents once every second, and take + // the necessary actions then. + void abort(); + bool is_aborted() const { return m_abort; } + + // returns true if this torrent is being allocated + // by the checker thread. + bool is_allocating() const; + + session_settings const& settings() const; + + aux::session_impl& session() { return m_ses; } + + void set_sequenced_download_threshold(int threshold); + + bool verify_resume_data(entry& rd, std::string& error) + { assert(m_storage); return m_storage->verify_resume_data(rd, error); } + + // is called every second by session. This will + // caclulate the upload/download and number + // of connections this torrent needs. And prepare + // it for being used by allocate_resources. + void second_tick(stat& accumulator, float tick_interval); + + // debug purpose only + void print(std::ostream& os) const; + + std::string name() const; + + bool check_fastresume(aux::piece_checker_data&); + std::pair check_files(); + void files_checked(std::vector const& + unfinished_pieces); + + stat statistics() const { return m_stat; } + size_type bytes_left() const; + boost::tuples::tuple bytes_done() const; + size_type quantized_bytes_done() const; + + void pause(); + void resume(); + bool is_paused() const { return m_paused; } + + // ============ start deprecation ============= + void filter_piece(int index, bool filter); + void filter_pieces(std::vector const& bitmask); + bool is_piece_filtered(int index) const; + void filtered_pieces(std::vector& bitmask) const; + void filter_files(std::vector const& files); + // ============ end deprecation ============= + + void piece_availability(std::vector& avail) const; + + void set_piece_priority(int index, int priority); + int piece_priority(int index) const; + + void prioritize_pieces(std::vector const& pieces); + void piece_priorities(std::vector&) const; + + void prioritize_files(std::vector const& files); + + torrent_status status() const; + void file_progress(std::vector& fp) const; + + void use_interface(const char* net_interface); + tcp::endpoint const& get_interface() const { return m_net_interface; } + + void connect_to_url_seed(std::string const& url); + peer_connection* connect_to_peer(policy::peer* peerinfo); + + void set_ratio(float ratio) + { assert(ratio >= 0.0f); m_ratio = ratio; } + + float ratio() const + { return m_ratio; } + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void resolve_countries(bool r) + { m_resolve_countries = r; } + + bool resolving_countries() const { return m_resolve_countries; } +#endif + +// -------------------------------------------- + // BANDWIDTH MANAGEMENT + + bandwidth_limit m_bandwidth_limit[2]; + + void request_bandwidth(int channel + , boost::intrusive_ptr const& p + , bool non_prioritized); + + void perform_bandwidth_request(int channel + , boost::intrusive_ptr const& p + , int block_size + , bool non_prioritized); + + void expire_bandwidth(int channel, int amount); + void assign_bandwidth(int channel, int amount, int blk); + + int bandwidth_throttle(int channel) const; + + int max_assignable_bandwidth(int channel) const + { return m_bandwidth_limit[channel].max_assignable(); } + +// -------------------------------------------- + // PEER MANAGEMENT + + // add or remove a url that will be attempted for + // finding the file(s) in this torrent. + void add_url_seed(std::string const& url) + { m_web_seeds.insert(url); } + + void remove_url_seed(std::string const& url) + { m_web_seeds.erase(url); } + + // used by peer_connection to attach itself to a torrent + // since incoming connections don't know what torrent + // they're a part of until they have received an info_hash. + void attach_peer(peer_connection* p); + + // this will remove the peer and make sure all + // the pieces it had have their reference counter + // decreased in the piece_picker + void remove_peer(peer_connection* p); + + bool want_more_peers() const; + bool try_connect_peer(); + + peer_connection* connection_for(tcp::endpoint const& a) + { + peer_iterator i = m_connections.find(a); + if (i == m_connections.end()) return 0; + return i->second; + } + + // the number of peers that belong to this torrent + int num_peers() const { return (int)m_connections.size(); } + int num_seeds() const; + + typedef std::map::iterator peer_iterator; + typedef std::map::const_iterator const_peer_iterator; + + const_peer_iterator begin() const { return m_connections.begin(); } + const_peer_iterator end() const { return m_connections.end(); } + + peer_iterator begin() { return m_connections.begin(); } + peer_iterator end() { return m_connections.end(); } + + void resolve_peer_country(boost::intrusive_ptr const& p) const; + +// -------------------------------------------- + // TRACKER MANAGEMENT + + // these are callbacks called by the tracker_connection instance + // (either http_tracker_connection or udp_tracker_connection) + // when this torrent got a response from its tracker request + // or when a failure occured + virtual void tracker_response( + tracker_request const& r + , std::vector& e, int interval + , int complete, int incomplete); + virtual void tracker_request_timed_out( + tracker_request const& r); + virtual void tracker_request_error(tracker_request const& r + , int response_code, const std::string& str); + virtual void tracker_warning(std::string const& msg); + + // generates a request string for sending + // to the tracker + tracker_request generate_tracker_request(); + + // if no password and username is set + // this will return an empty string, otherwise + // it will concatenate the login and password + // ready to be sent over http (but without + // base64 encoding). + std::string tracker_login() const; + + // returns the absolute time when the next tracker + // announce will take place. + ptime next_announce() const; + + // returns true if it is time for this torrent to make another + // tracker request + bool should_request(); + + // forcefully sets next_announce to the current time + void force_tracker_request(); + void force_tracker_request(ptime); + + // sets the username and password that will be sent to + // the tracker + void set_tracker_login(std::string const& name, std::string const& pw); + + // the tcp::endpoint of the tracker that we managed to + // announce ourself at the last time we tried to announce + const tcp::endpoint& current_tracker() const; + +// -------------------------------------------- + // PIECE MANAGEMENT + + // returns true if we have downloaded the given piece + bool have_piece(int index) const + { + assert(index >= 0 && index < (signed)m_have_pieces.size()); + return m_have_pieces[index]; + } + + const std::vector& pieces() const + { return m_have_pieces; } + + int num_pieces() const { return m_num_pieces; } + + // when we get a have- or bitfield- messages, this is called for every + // piece a peer has gained. + void peer_has(int index) + { + if (m_picker.get()) + { + assert(!is_seed()); + assert(index >= 0 && index < (signed)m_have_pieces.size()); + m_picker->inc_refcount(index); + } +#ifndef NDEBUG + else + { + assert(is_seed()); + } +#endif + } + + void peer_has_all() + { + if (m_picker.get()) + { + assert(!is_seed()); + m_picker->inc_refcount_all(); + } +#ifndef NDEBUG + else + { + assert(is_seed()); + } +#endif + } + + // when peer disconnects, this is called for every piece it had + void peer_lost(int index) + { + if (m_picker.get()) + { + assert(!is_seed()); + assert(index >= 0 && index < (signed)m_have_pieces.size()); + m_picker->dec_refcount(index); + } +#ifndef NDEBUG + else + { + assert(is_seed()); + } +#endif + } + + int block_size() const { assert(m_block_size > 0); return m_block_size; } + + // this will tell all peers that we just got his piece + // and also let the piece picker know that we have this piece + // so it wont pick it for download + void announce_piece(int index); + + void disconnect_all(); + + // this is called wheh the torrent has completed + // the download. It will post an event, disconnect + // all seeds and let the tracker know we're finished. + void completed(); + + // this is the asio callback that is called when a name + // lookup for a PEER is completed. + void on_peer_name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , peer_id pid); + + // this is the asio callback that is called when a name + // lookup for a WEB SEED is completed. + void on_name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , std::string url, tcp::endpoint proxy); + + // this is the asio callback that is called when a name + // lookup for a proxy for a web seed is completed. + void on_proxy_name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , std::string url); + + // this is called when the torrent has finished. i.e. + // all the pieces we have not filtered have been downloaded. + // If no pieces are filtered, this is called first and then + // completed() is called immediately after it. + void finished(); + + void async_verify_piece(int piece_index, boost::function const&); + + // this is called from the peer_connection + // each time a piece has failed the hash + // test + void piece_finished(int index, bool passed_hash_check); + void piece_failed(int index); + void received_redundant_data(int num_bytes) + { assert(num_bytes > 0); m_total_redundant_bytes += num_bytes; } + + // this is true if we have all the pieces + bool is_seed() const + { + return valid_metadata() + && m_num_pieces == m_torrent_file.num_pieces(); + } + + // this is true if we have all the pieces that we want + bool is_finished() const + { + if (is_seed()) return true; + return valid_metadata() && m_torrent_file.num_pieces() + - m_num_pieces - m_picker->num_filtered() == 0; + } + + fs::path save_path() const; + alert_manager& alerts() const; + piece_picker& picker() + { + assert(m_picker.get()); + return *m_picker; + } + bool has_picker() const + { + return m_picker.get() != 0; + } + policy& get_policy() + { + assert(m_policy); + return *m_policy; + } + piece_manager& filesystem(); + torrent_info const& torrent_file() const + { return m_torrent_file; } + + std::vector const& trackers() const + { return m_trackers; } + + void replace_trackers(std::vector const& urls); + + torrent_handle get_handle() const; + + // LOGGING +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + virtual void debug_log(const std::string& line); +#endif + + // DEBUG +#ifndef NDEBUG + void check_invariant() const; +#endif + +// -------------------------------------------- + // RESOURCE MANAGEMENT + + void distribute_resources(float tick_interval); + + resource_request m_uploads_quota; + resource_request m_connections_quota; + + void set_peer_upload_limit(tcp::endpoint ip, int limit); + void set_peer_download_limit(tcp::endpoint ip, int limit); + + void set_upload_limit(int limit); + int upload_limit() const; + void set_download_limit(int limit); + int download_limit() const; + + void set_max_uploads(int limit); + void set_max_connections(int limit); + void move_storage(fs::path const& save_path); + + // unless this returns true, new connections must wait + // with their initialization. + bool ready_for_connections() const + { return m_connections_initialized; } + bool valid_metadata() const + { return m_torrent_file.is_valid(); } + + // parses the info section from the given + // bencoded tree and moves the torrent + // to the checker thread for initial checking + // of the storage. + void set_metadata(entry const&); + + private: + + void on_files_released(int ret, disk_io_job const& j); + void on_storage_moved(int ret, disk_io_job const& j); + + void on_piece_verified(int ret, disk_io_job const& j + , boost::function f); + + void try_next_tracker(); + int prioritize_tracker(int tracker_index); + void on_country_lookup(asio::error_code const& error, tcp::resolver::iterator i + , boost::intrusive_ptr p) const; + bool request_bandwidth_from_session(int channel) const; + + void update_peer_interest(); + + torrent_info m_torrent_file; + + // is set to true when the torrent has + // been aborted. + bool m_abort; + + // is true if this torrent has been paused + bool m_paused; + // this is true from the time when the torrent was + // paused to the time should_request() is called + bool m_just_paused; + + tracker_request::event_t m_event; + + void parse_response(const entry& e, std::vector& peer_list); + + // the size of a request block + // each piece is divided into these + // blocks when requested + int m_block_size; + + // if this pointer is 0, the torrent is in + // a state where the metadata hasn't been + // received yet. + // the piece_manager keeps the torrent object + // alive by holding a shared_ptr to it and + // the torrent keeps the piece manager alive + // with this intrusive_ptr. This cycle is + // broken when torrent::abort() is called + // Then the torrent releases the piece_manager + // and when the piece_manager is complete with all + // outstanding disk io jobs (that keeps + // the piece_manager alive) it will destruct + // and release the torrent file. The reason for + // this is that the torrent_info is used by + // the piece_manager, and stored in the + // torrent, so the torrent cannot destruct + // before the piece_manager. + boost::intrusive_ptr m_owning_storage; + + // this is a weak (non owninig) pointer to + // the piece_manager. This is used after the torrent + // has been aborted, and it can no longer own + // the object. + piece_manager* m_storage; + + // the time of next tracker request + ptime m_next_request; + + // ----------------------------- + // DATA FROM TRACKER RESPONSE + + // the number number of seconds between requests + // from the tracker + int m_duration; + + // the scrape data from the tracker response, this + // is optional and may be -1. + int m_complete; + int m_incomplete; + +#ifndef NDEBUG + public: +#endif + std::map m_connections; +#ifndef NDEBUG + private: +#endif + + // The list of web seeds in this torrent. Seeds + // with fatal errors are removed from the set + std::set m_web_seeds; + + // urls of the web seeds that we are currently + // resolving the address for + std::set m_resolving_web_seeds; + + // used to resolve the names of web seeds + mutable tcp::resolver m_host_resolver; + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + // this is true while there is a country + // resolution in progress. To avoid flodding + // the DNS request queue, only one ip is resolved + // at a time. + mutable bool m_resolving_country; + + // this is true if the user has enabled + // country resolution in this torrent + bool m_resolve_countries; +#endif + + // this announce timer is used both + // by Local service discovery and + // by the DHT. + deadline_timer m_announce_timer; + + static void on_announce_disp(boost::weak_ptr p + , asio::error_code const& e); + + // this is called once per announce interval + void on_announce(); + +#ifndef TORRENT_DISABLE_DHT + static void on_dht_announce_response_disp(boost::weak_ptr t + , std::vector const& peers); + void on_dht_announce_response(std::vector const& peers); + bool should_announce_dht() const; + + // the time when the DHT was last announced of our + // presence on this torrent + ptime m_last_dht_announce; +#endif + + // this is the upload and download statistics for the whole torrent. + // it's updated from all its peers once every second. + libtorrent::stat m_stat; + + // ----------------------------- + + boost::shared_ptr m_policy; + + // a back reference to the session + // this torrent belongs to. + aux::session_impl& m_ses; + aux::checker_impl& m_checker; + + boost::scoped_ptr m_picker; + + // the queue of peer_connections that want more bandwidth + typedef std::deque > queue_t; + queue_t m_bandwidth_queue[2]; + + std::vector m_trackers; + // this is an index into m_trackers + + int m_last_working_tracker; + int m_currently_trying_tracker; + // the number of connection attempts that has + // failed in a row, this is currently used to + // determine the timeout until next try. + int m_failed_trackers; + + // this is a counter that is increased every + // second, and when it reaches 10, the policy::pulse() + // is called and the time scaler is reset to 0. + int m_time_scaler; + + // the bitmask that says which pieces we have + std::vector m_have_pieces; + + // the number of pieces we have. The same as + // std::accumulate(m_have_pieces.begin(), + // m_have_pieces.end(), 0) + int m_num_pieces; + + // in case the piece picker hasn't been constructed + // when this settings is set, this variable will keep + // its value until the piece picker is created + int m_sequenced_download_threshold; + + // is false by default and set to + // true when the first tracker reponse + // is received + bool m_got_tracker_response; + + // the upload/download ratio that each peer + // tries to maintain. + // 0 is infinite + float m_ratio; + + // the number of bytes that has been + // downloaded that failed the hash-test + size_type m_total_failed_bytes; + size_type m_total_redundant_bytes; + + std::string m_username; + std::string m_password; + + // the network interface all outgoing connections + // are opened through + tcp::endpoint m_net_interface; + + fs::path m_save_path; + + // determines the storage state for this torrent. + const bool m_compact_mode; + + // defaults to 16 kiB, but can be set by the user + // when creating the torrent + const int m_default_block_size; + + // this is set to false as long as the connections + // of this torrent hasn't been initialized. If we + // have metadata from the start, connections are + // initialized immediately, if we didn't have metadata, + // they are initialized right after files_checked(). + // valid_resume_data() will return false as long as + // the connections aren't initialized, to avoid + // them from altering the piece-picker before it + // has been initialized with files_checked(). + bool m_connections_initialized; + + // if the torrent is started without metadata, it may + // still be given a name until the metadata is received + // once the metadata is received this field will no + // longer be used and will be reset + boost::scoped_ptr m_name; + + session_settings const& m_settings; + + storage_constructor_type m_storage_constructor; + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list > extension_list_t; + extension_list_t m_extensions; +#endif + +#ifndef NDEBUG + // this is the amount downloaded when this torrent + // is started. i.e. + // total_done - m_initial_done <= total_payload_download + size_type m_initial_done; +#endif + }; + + inline ptime torrent::next_announce() const + { + return m_next_request; + } + + inline void torrent::force_tracker_request() + { + m_next_request = time_now(); + } + + inline void torrent::force_tracker_request(ptime t) + { + m_next_request = t; + } + + inline void torrent::set_tracker_login( + std::string const& name + , std::string const& pw) + { + m_username = name; + m_password = pw; + } + +} + +#endif // TORRENT_TORRENT_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp new file mode 100755 index 000000000..b5e6bdc17 --- /dev/null +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,387 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HANDLE_HPP_INCLUDED +#define TORRENT_TORRENT_HANDLE_HPP_INCLUDED + +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + namespace fs = boost::filesystem; + + namespace aux + { + struct session_impl; + struct checker_impl; + } + + struct TORRENT_EXPORT duplicate_torrent: std::exception + { + virtual const char* what() const throw() + { return "torrent already exists in session"; } + }; + + struct TORRENT_EXPORT invalid_handle: std::exception + { + virtual const char* what() const throw() + { return "invalid torrent handle used"; } + }; + + struct TORRENT_EXPORT torrent_status + { + torrent_status() + : state(queued_for_checking) + , paused(false) + , progress(0.f) + , total_download(0) + , total_upload(0) + , total_payload_download(0) + , total_payload_upload(0) + , total_failed_bytes(0) + , total_redundant_bytes(0) + , download_rate(0) + , upload_rate(0) + , download_payload_rate(0) + , upload_payload_rate(0) + , num_peers(0) + , num_complete(-1) + , num_incomplete(-1) + , pieces(0) + , num_pieces(0) + , total_done(0) + , total_wanted_done(0) + , total_wanted(0) + , num_seeds(0) + , distributed_copies(0.f) + , block_size(0) + {} + + enum state_t + { + queued_for_checking, + checking_files, + connecting_to_tracker, + downloading_metadata, + downloading, + finished, + seeding, + allocating + }; + + state_t state; + bool paused; + float progress; + boost::posix_time::time_duration next_announce; + boost::posix_time::time_duration announce_interval; + + std::string current_tracker; + + // transferred this session! + // total, payload plus protocol + size_type total_download; + size_type total_upload; + + // payload only + size_type total_payload_download; + size_type total_payload_upload; + + // the amount of payload bytes that + // has failed their hash test + size_type total_failed_bytes; + + // the number of payload bytes that + // has been received redundantly. + size_type total_redundant_bytes; + + // current transfer rate + // payload plus protocol + float download_rate; + float upload_rate; + + // the rate of payload that is + // sent and received + float download_payload_rate; + float upload_payload_rate; + + // the number of peers this torrent + // is connected to. + int num_peers; + + // if the tracker sends scrape info in its + // announce reply, these fields will be + // set to the total number of peers that + // have the whole file and the total number + // of peers that are still downloading + int num_complete; + int num_incomplete; + + const std::vector* pieces; + + // this is the number of pieces the client has + // downloaded. it is equal to: + // std::accumulate(pieces->begin(), pieces->end()); + int num_pieces; + + // the number of bytes of the file we have + // including pieces that may have been filtered + // after we downloaded them + size_type total_done; + + // the number of bytes we have of those that we + // want. i.e. not counting bytes from pieces that + // are filtered as not wanted. + size_type total_wanted_done; + + // the total number of bytes we want to download + // this may be smaller than the total torrent size + // in case any pieces are filtered as not wanted + size_type total_wanted; + + // the number of peers this torrent is connected to + // that are seeding. + int num_seeds; + + // the number of distributed copies of the file. + // note that one copy may be spread out among many peers. + // + // the integer part tells how many copies + // there are of the rarest piece(s) + // + // the fractional part tells the fraction of pieces that + // have more copies than the rarest piece(s). + float distributed_copies; + + // the block size that is used in this torrent. i.e. + // the number of bytes each piece request asks for + // and each bit in the download queue bitfield represents + int block_size; + }; + + struct TORRENT_EXPORT block_info + { + enum block_state_t + { none, requested, writing, finished }; + + tcp::endpoint peer; + unsigned state:2; + unsigned num_downloads:14; + }; + + struct TORRENT_EXPORT partial_piece_info + { + enum { max_blocks_per_piece = 256 }; + int piece_index; + int blocks_in_piece; + block_info blocks[max_blocks_per_piece]; + enum state_t { none, slow, medium, fast }; + state_t piece_state; + }; + + struct TORRENT_EXPORT torrent_handle + { + friend class invariant_access; + friend struct aux::session_impl; + friend class torrent; + + torrent_handle(): m_ses(0), m_chk(0), m_info_hash(0) {} + + void get_peer_info(std::vector& v) const; + bool send_chat_message(tcp::endpoint ip, std::string message) const; + torrent_status status() const; + void get_download_queue(std::vector& queue) const; + + // fills the specified vector with the download progress [0, 1] + // of each file in the torrent. The files are ordered as in + // the torrent_info. + void file_progress(std::vector& progress); + + std::vector const& trackers() const; + void replace_trackers(std::vector const&) const; + + void add_url_seed(std::string const& url); + + bool has_metadata() const; + const torrent_info& get_torrent_info() const; + bool is_valid() const; + + bool is_seed() const; + bool is_paused() const; + void pause() const; + void resume() const; + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void resolve_countries(bool r); + bool resolve_countries() const; +#endif + + // all these are deprecated, use piece + // priority functions instead + + // ================ start deprecation ============ + + // marks the piece with the given index as filtered + // it will not be downloaded + void filter_piece(int index, bool filter) const; + void filter_pieces(std::vector const& pieces) const; + bool is_piece_filtered(int index) const; + std::vector filtered_pieces() const; + // marks the file with the given index as filtered + // it will not be downloaded + void filter_files(std::vector const& files) const; + + // ================ end deprecation ============ + + void piece_availability(std::vector& avail) const; + + // priority must be within the range [0, 7] + void piece_priority(int index, int priority) const; + int piece_priority(int index) const; + + void prioritize_pieces(std::vector const& pieces) const; + std::vector piece_priorities() const; + + void prioritize_files(std::vector const& files) const; + + + // set the interface to bind outgoing connections + // to. + void use_interface(const char* net_interface) const; + + entry write_resume_data() const; + + // forces this torrent to reannounce + // (make a rerequest from the tracker) + void force_reannounce() const; + + // forces a reannounce in the specified amount of time. + // This overrides the default announce interval, and no + // announce will take place until the given time has + // timed out. + void force_reannounce(boost::posix_time::time_duration) const; + + // returns the name of this torrent, in case it doesn't + // have metadata it returns the name assigned to it + // when it was added. + std::string name() const; + + // TODO: add a feature where the user can tell the torrent + // to finish all pieces currently in the pipeline, and then + // abort the torrent. + + void set_upload_limit(int limit) const; + int upload_limit() const; + void set_download_limit(int limit) const; + int download_limit() const; + + void set_sequenced_download_threshold(int threshold) const; + + void set_peer_upload_limit(tcp::endpoint ip, int limit) const; + void set_peer_download_limit(tcp::endpoint ip, int limit) const; + + // manually connect a peer + void connect_peer(tcp::endpoint const& adr, int source = 0) const; + + // valid ratios are 0 (infinite ratio) or [ 1.0 , inf ) + // the ratio is uploaded / downloaded. less than 1 is not allowed + void set_ratio(float up_down_ratio) const; + + fs::path save_path() const; + + // -1 means unlimited unchokes + void set_max_uploads(int max_uploads) const; + + // -1 means unlimited connections + void set_max_connections(int max_connections) const; + + void set_tracker_login(std::string const& name + , std::string const& password) const; + + // post condition: save_path() == save_path if true is returned + void move_storage(fs::path const& save_path) const; + + const sha1_hash& info_hash() const + { return m_info_hash; } + + bool operator==(const torrent_handle& h) const + { return m_info_hash == h.m_info_hash; } + + bool operator!=(const torrent_handle& h) const + { return m_info_hash != h.m_info_hash; } + + bool operator<(const torrent_handle& h) const + { return m_info_hash < h.m_info_hash; } + + private: + + torrent_handle(aux::session_impl* s, + aux::checker_impl* c, + const sha1_hash& h) + : m_ses(s) + , m_chk(c) + , m_info_hash(h) + { + assert(m_ses != 0); + } + +#ifndef NDEBUG + void check_invariant() const; +#endif + + aux::session_impl* m_ses; + aux::checker_impl* m_chk; + sha1_hash m_info_hash; + + }; + + +} + +#endif // TORRENT_TORRENT_HANDLE_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp new file mode 100755 index 000000000..e52024255 --- /dev/null +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -0,0 +1,273 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_INFO_HPP_INCLUDE +#define TORRENT_TORRENT_INFO_HPP_INCLUDE + + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/entry.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/size_type.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + namespace pt = boost::posix_time; + namespace gr = boost::gregorian; + + namespace fs = boost::filesystem; + + struct TORRENT_EXPORT file_entry + { + fs::path path; + size_type offset; // the offset of this file inside the torrent + size_type size; // the size of this file + // if the path was incorrectly encoded, this is + // the origianal corrupt encoded string. It is + // preserved in order to be able to reproduce + // the correct info-hash + boost::shared_ptr orig_path; + }; + + struct TORRENT_EXPORT file_slice + { + int file_index; + size_type offset; + size_type size; + }; + + struct TORRENT_EXPORT announce_entry + { + announce_entry(std::string const& u): url(u), tier(0) {} + std::string url; + int tier; + }; + + struct TORRENT_EXPORT invalid_torrent_file: std::exception + { + virtual const char* what() const throw() { return "invalid torrent file"; } + }; + + class TORRENT_EXPORT torrent_info + { + public: + + torrent_info(); + torrent_info(sha1_hash const& info_hash); + torrent_info(entry const& torrent_file); + ~torrent_info(); + + entry create_torrent() const; + entry create_info_metadata() const; + void set_comment(char const* str); + void set_creator(char const* str); + void set_piece_size(int size); + void set_hash(int index, sha1_hash const& h); + void add_tracker(std::string const& url, int tier = 0); + void add_file(fs::path file, size_type size); + void add_url_seed(std::string const& url); + + std::vector map_block(int piece, size_type offset, int size) const; + peer_request map_file(int file, size_type offset, int size) const; + + std::vector const& url_seeds() const + { + assert(!m_half_metadata); + return m_url_seeds; + } + + typedef std::vector::const_iterator file_iterator; + typedef std::vector::const_reverse_iterator reverse_file_iterator; + + // list the files in the torrent file + file_iterator begin_files() const { return m_files.begin(); } + file_iterator end_files() const { return m_files.end(); } + reverse_file_iterator rbegin_files() const { return m_files.rbegin(); } + reverse_file_iterator rend_files() const { return m_files.rend(); } + + int num_files() const + { assert(m_piece_length > 0); return (int)m_files.size(); } + const file_entry& file_at(int index) const + { assert(index >= 0 && index < (int)m_files.size()); return m_files[index]; } + + const std::vector& trackers() const { return m_urls; } + + size_type total_size() const { assert(m_piece_length > 0); return m_total_size; } + size_type piece_length() const { assert(m_piece_length > 0); return m_piece_length; } + int num_pieces() const { assert(m_piece_length > 0); return m_num_pieces; } + const sha1_hash& info_hash() const { return m_info_hash; } + const std::string& name() const { assert(m_piece_length > 0); return m_name; } + +// ------- start deprecation ------- + void print(std::ostream& os) const; +// ------- end deprecation ------- + + bool is_valid() const { return m_piece_length > 0; } + + bool priv() const { return m_private; } + void set_priv(bool v) { m_private = v; } + + void convert_file_names(); + + size_type piece_size(int index) const; + + const sha1_hash& hash_for_piece(int index) const + { + assert(index >= 0); + assert(index < (int)m_piece_hash.size()); + assert(!m_half_metadata); + return m_piece_hash[index]; + } + + boost::optional creation_date() const; + + const std::string& creator() const + { return m_created_by; } + + const std::string& comment() const + { return m_comment; } + + // dht nodes to add to the routing table/bootstrap from + typedef std::vector > nodes_t; + + nodes_t const& nodes() const + { + assert(!m_half_metadata); + return m_nodes; + } + + void add_node(std::pair const& node); + + void parse_info_section(entry const& e); + + entry extra(char const* key) const + { return m_extra_info[key]; } + + // frees parts of the metadata that isn't + // used by seeds + void seed_free(); + + void swap(torrent_info& ti); + + private: + + void read_torrent_info(const entry& libtorrent); + + // the urls to the trackers + std::vector m_urls; + + std::vector m_url_seeds; + + // the length of one piece + // if this is 0, the torrent_info is + // in an uninitialized state + size_type m_piece_length; + + // the sha-1 hashes of each piece + std::vector m_piece_hash; + + // the list of files that this torrent consists of + std::vector m_files; + + nodes_t m_nodes; + + // the sum of all filesizes + size_type m_total_size; + + // the number of pieces in the torrent + int m_num_pieces; + + // the hash that identifies this torrent + // is mutable because it's calculated + // lazily + mutable sha1_hash m_info_hash; + + std::string m_name; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + pt::ptime m_creation_date; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multifile torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + bool m_multifile; + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + bool m_private; + + // contains any non-parsed entries from the info-section + // these are kept in order to be able to accurately + // reproduce the info-section when sending the metadata + // to peers. + entry m_extra_info; + +#ifndef NDEBUG + // this is set to true when seed_free() is called + bool m_half_metadata; +#endif + }; + +} + +#endif // TORRENT_TORRENT_INFO_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp new file mode 100755 index 000000000..1435ceda6 --- /dev/null +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,253 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRACKER_MANAGER_HPP_INCLUDED +#define TORRENT_TRACKER_MANAGER_HPP_INCLUDED + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/socket.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/connection_queue.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" + +namespace libtorrent +{ + struct request_callback; + class tracker_manager; + struct timeout_handler; + struct tracker_connection; + + // encodes a string using the base64 scheme + TORRENT_EXPORT std::string base64encode(const std::string& s); + + // returns -1 if gzip header is invalid or the header size in bytes + TORRENT_EXPORT int gzip_header(const char* buf, int size); + + TORRENT_EXPORT boost::tuple + parse_url_components(std::string url); + + struct TORRENT_EXPORT tracker_request + { + tracker_request() + : kind(announce_request) + , event(none) + , key(0) + , num_want(0) + {} + + enum + { + announce_request, + scrape_request + } kind; + + enum event_t + { + none, + completed, + started, + stopped + }; + + sha1_hash info_hash; + peer_id pid; + size_type downloaded; + size_type uploaded; + size_type left; + unsigned short listen_port; + event_t event; + std::string url; + int key; + int num_want; + }; + + struct TORRENT_EXPORT request_callback + { + friend class tracker_manager; + request_callback(): m_manager(0) {} + virtual ~request_callback() {} + virtual void tracker_warning(std::string const& msg) = 0; + virtual void tracker_response( + tracker_request const& + , std::vector& peers + , int interval + , int complete + , int incomplete) = 0; + virtual void tracker_request_timed_out( + tracker_request const&) = 0; + virtual void tracker_request_error( + tracker_request const& + , int response_code + , const std::string& description) = 0; + + tcp::endpoint m_tracker_address; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + virtual void debug_log(const std::string& line) = 0; +#endif + private: + tracker_manager* m_manager; + }; + + TORRENT_EXPORT bool inflate_gzip( + std::vector& buffer + , tracker_request const& req + , request_callback* requester + , int maximum_tracker_response_length); + + struct TORRENT_EXPORT timeout_handler + : intrusive_ptr_base + , boost::noncopyable + { + timeout_handler(asio::strand& str); + + void set_timeout(int completion_timeout, int read_timeout); + void restart_read_timeout(); + void cancel(); + + virtual void on_timeout() = 0; + virtual ~timeout_handler() {} + + private: + + void timeout_callback(asio::error_code const&); + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + asio::strand& m_strand; + // used for timeouts + // this is set when the request has been sent + ptime m_start_time; + // this is set every time something is received + ptime m_read_time; + // the asio async operation + deadline_timer m_timeout; + + int m_completion_timeout; + int m_read_timeout; + + typedef boost::mutex mutex_t; + mutable mutex_t m_mutex; + }; + + struct TORRENT_EXPORT tracker_connection + : timeout_handler + { + tracker_connection(tracker_manager& man + , tracker_request req + , asio::strand& str + , address bind_interface + , boost::weak_ptr r); + + request_callback& requester(); + virtual ~tracker_connection() {} + + tracker_request const& tracker_req() const { return m_req; } + bool has_requester() const { return !m_requester.expired(); } + + void fail(int code, char const* msg); + void fail_timeout(); + void close(); + address const& bind_interface() const { return m_bind_interface; } + + protected: + boost::weak_ptr m_requester; + private: + address m_bind_interface; + tracker_manager& m_man; + const tracker_request m_req; + }; + + class TORRENT_EXPORT tracker_manager: boost::noncopyable + { + public: + + tracker_manager(session_settings const& s, proxy_settings const& ps) + : m_settings(s) + , m_proxy(ps) + , m_abort(false) {} + + void queue_request( + asio::strand& str + , connection_queue& cc + , tracker_request r + , std::string const& auth + , address bind_infc + , boost::weak_ptr c + = boost::weak_ptr()); + void abort_all_requests(); + + void remove_request(tracker_connection const*); + bool empty() const; + + private: + + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + + typedef std::list > + tracker_connections_t; + tracker_connections_t m_connections; + session_settings const& m_settings; + proxy_settings const& m_proxy; + bool m_abort; + }; +} + +#endif // TORRENT_TRACKER_MANAGER_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/udp_tracker_connection.hpp b/libtorrent/include/libtorrent/udp_tracker_connection.hpp new file mode 100755 index 000000000..c0e6853b9 --- /dev/null +++ b/libtorrent/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/socket.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + class TORRENT_EXPORT udp_tracker_connection: public tracker_connection + { + friend class tracker_manager; + public: + + udp_tracker_connection( + asio::strand& str + , tracker_manager& man + , tracker_request const& req + , std::string const& hostname + , unsigned short port + , address bind_infc + , boost::weak_ptr c + , session_settings const& stn); + + private: + + enum action_t + { + action_connect, + action_announce, + action_scrape, + action_error + }; + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + void name_lookup(asio::error_code const& error, udp::resolver::iterator i); + void timeout(asio::error_code const& error); + + void send_udp_connect(); + void connect_response(asio::error_code const& error, std::size_t bytes_transferred); + + void send_udp_announce(); + void announce_response(asio::error_code const& error, std::size_t bytes_transferred); + + void send_udp_scrape(); + void scrape_response(asio::error_code const& error, std::size_t bytes_transferred); + + virtual void on_timeout(); + + tracker_manager& m_man; + + asio::strand& m_strand; + udp::resolver m_name_lookup; + boost::shared_ptr m_socket; + udp::endpoint m_target; + udp::endpoint m_sender; + + int m_transaction_id; + boost::int64_t m_connection_id; + session_settings const& m_settings; + int m_attempts; + std::vector m_buffer; + }; + +} + +#endif // TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp new file mode 100644 index 000000000..d4b701aad --- /dev/null +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -0,0 +1,237 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UPNP_HPP +#define TORRENT_UPNP_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/connection_queue.hpp" + +#include +#include +#include +#include +#include +#include + + +#if (defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)) && !defined (TORRENT_UPNP_LOGGING) +#define TORRENT_UPNP_LOGGING +#endif + +#if defined(TORRENT_UPNP_LOGGING) +#include +#endif + +namespace libtorrent +{ + +bool is_local(address const& a); +address_v4 guess_local_address(asio::io_service&); + +// int: external tcp port +// int: external udp port +// std::string: error message +typedef boost::function portmap_callback_t; + +class upnp : boost::noncopyable +{ +public: + upnp(io_service& ios, connection_queue& cc + , address const& listen_interface, std::string const& user_agent + , portmap_callback_t const& cb); + ~upnp(); + + void rebind(address const& listen_interface); + + // maps the ports, if a port is set to 0 + // it will not be mapped + void set_mappings(int tcp, int udp); + + void close(); + +private: + + static address_v4 upnp_multicast_address; + static udp::endpoint upnp_multicast_endpoint; + + enum { num_mappings = 2 }; + enum { default_lease_time = 3600 }; + + void update_mapping(int i, int port); + void resend_request(asio::error_code const& e); + void on_reply(asio::error_code const& e + , std::size_t bytes_transferred); + void discover_device(); + + struct rootdevice; + + void on_upnp_xml(asio::error_code const& e + , libtorrent::http_parser const& p, rootdevice& d); + void on_upnp_map_response(asio::error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , int mapping); + void on_upnp_unmap_response(asio::error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , int mapping); + void on_expire(asio::error_code const& e); + + void post(rootdevice& d, std::stringstream const& s + , std::string const& soap_action); + void map_port(rootdevice& d, int i); + void unmap_port(rootdevice& d, int i); + void disable(); + + struct mapping_t + { + mapping_t() + : need_update(false) + , local_port(0) + , external_port(0) + , protocol(1) + {} + + // the time the port mapping will expire + ptime expires; + + bool need_update; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port; + + // 1 = udp, 0 = tcp + int protocol; + }; + + struct rootdevice + { + rootdevice(): service_namespace(0) + , lease_duration(default_lease_time) + , supports_specific_external(true) + , disabled(false) + { + mapping[0].protocol = 0; + mapping[1].protocol = 1; + } + + // the interface url, through which the list of + // supported interfaces are fetched + std::string url; + + // the url to the WANIP or WANPPP interface + std::string control_url; + // either the WANIP namespace or the WANPPP namespace + char const* service_namespace; + + mapping_t mapping[num_mappings]; + + std::string hostname; + int port; + std::string path; + + int lease_duration; + // true if the device supports specifying a + // specific external port, false if it doesn't + bool supports_specific_external; + + bool disabled; + + mutable boost::shared_ptr upnp_connection; + + void close() const + { + if (!upnp_connection) return; + upnp_connection->close(); + upnp_connection.reset(); + } + + bool operator<(rootdevice const& rhs) const + { return url < rhs.url; } + }; + + int m_udp_local_port; + int m_tcp_local_port; + + std::string const& m_user_agent; + + // the set of devices we've found + std::set m_devices; + + portmap_callback_t m_callback; + + // current retry count + int m_retry_count; + + // used to receive responses in + char m_receive_buffer[1024]; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the local address we're listening on + address_v4 m_local_ip; + + // the udp socket used to send and receive + // multicast messages on the network + datagram_socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + asio::strand m_strand; + + bool m_disabled; + bool m_closing; + + connection_queue& m_cc; + +#ifdef TORRENT_UPNP_LOGGING + std::ofstream m_log; +#endif +}; + +} + + +#endif + diff --git a/libtorrent/include/libtorrent/utf8.hpp b/libtorrent/include/libtorrent/utf8.hpp new file mode 100644 index 000000000..157a7fdba --- /dev/null +++ b/libtorrent/include/libtorrent/utf8.hpp @@ -0,0 +1,161 @@ +/* + Copyright (C) 2004-2005 Cory Nelson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +// namespaces added by Arvid Norberg + +#ifndef __UTF8_H__ +#define __UTF8_H__ + +#include +#include +#include +#include + +namespace libtorrent { +namespace detail { + +template +wchar_t decode_utf8_mb(InputIterator &iter, InputIterator last) +{ + if (iter == last) throw std::runtime_error("incomplete UTF-8 sequence"); + if (((*iter) & 0xc0) != 0x80) throw std::runtime_error("invalid UTF-8 sequence"); + + return (wchar_t)((*iter++) & 0x3f); +} + +template +wchar_t decode_utf8(InputIterator &iter, InputIterator last) +{ + wchar_t ret; + + if (((*iter) & 0x80) == 0) // one byte + { + ret = *iter++; + } + else if (((*iter) & 0xe0) == 0xc0) // two bytes + { + wchar_t byte1 = (*iter++) & 0x1f; + wchar_t byte2 = decode_utf8_mb(iter, last); + ret = (byte1 << 6) | byte2; + } + else if (((*iter) & 0xf0) == 0xe0) // three bytes + { + wchar_t byte1 = (*iter++) & 0x0f; + wchar_t byte2 = decode_utf8_mb(iter, last); + wchar_t byte3 = decode_utf8_mb(iter, last); + ret = (byte1 << 12) | (byte2 << 6) | byte3; + } + // TODO: support surrogate pairs + else throw std::runtime_error("UTF-8 not convertable to UTF-16"); + + return ret; +} + +template +OutputIterator utf8_wchar(InputIterator first, InputIterator last, OutputIterator dest) +{ + for(; first!=last; ++dest) + *dest = decode_utf8(first, last); + return dest; +} + +template +void encode_wchar(InputIterator iter, OutputIterator &dest) +{ + if(*iter <= 0x007F) + { + *dest=(char)*iter; + ++dest; + } + else if(*iter <= 0x07FF) + { + *dest = (char)( + 0xC0 | + ((*iter & 0x07C0) >> 6) + ); + ++dest; + + *dest = (char)( + 0x80 | + (*iter & 0x003F) + ); + ++dest; + } + else if(*iter <= 0xFFFF) + { + *dest = (char)( + 0xE0 | + ((*iter & 0xF000) >> 12) + ); + ++dest; + + *dest = (char)( + 0x80 | + ((*iter & 0x0FC0) >> 6) + ); + ++dest; + + *dest = (char)( + 0x80 | + (*iter & 0x003F) + ); + ++dest; + } +} + +template +OutputIterator wchar_utf8(InputIterator first, InputIterator last, OutputIterator dest) +{ + for(; first!=last; ++first) + encode_wchar(first, dest); + return dest; +} + +} + +inline void utf8_wchar(const std::string &utf8, std::wstring &wide) +{ + wide.clear(); + detail::utf8_wchar(utf8.begin(), utf8.end(), std::back_inserter(wide)); +} + +inline std::wstring utf8_wchar(const std::string &str) +{ + std::wstring ret; + utf8_wchar(str, ret); + return ret; +} + +inline void wchar_utf8(const std::wstring &wide, std::string &utf8) +{ + utf8.clear(); + detail::wchar_utf8(wide.begin(), wide.end(), std::back_inserter(utf8)); +} + +inline std::string wchar_utf8(const std::wstring &str) +{ + std::string ret; + wchar_utf8(str, ret); + return ret; +} + +} + +#endif diff --git a/libtorrent/include/libtorrent/variant_stream.hpp b/libtorrent/include/libtorrent/variant_stream.hpp new file mode 100644 index 000000000..8f9888519 --- /dev/null +++ b/libtorrent/include/libtorrent/variant_stream.hpp @@ -0,0 +1,716 @@ +// Copyright Daniel Wallin and Arvid Norberg 2007. +// Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef VARIANT_STREAM_070211_HPP +# define VARIANT_STREAM_070211_HPP + +# include + +# include +# include +# include +# include +# include + +# include +# include +# include + +# include +# include + +#include + +# define NETWORK_VARIANT_STREAM_LIMIT 5 + +namespace libtorrent { + +namespace aux +{ + + struct delete_visitor + : boost::static_visitor<> + { + template + void operator()(T* p) const + { + delete p; + } + + void operator()(boost::blank) const + {} + }; + +// -------------- io_control ----------- + + template + struct io_control_visitor_ec: boost::static_visitor<> + { + io_control_visitor_ec(IO_Control_Command& ioc, asio::error_code& ec) + : ioc(ioc), ec(ec) {} + + template + void operator()(T* p) const + { + p->io_control(ioc, ec); + } + + void operator()(boost::blank) const + {} + + IO_Control_Command& ioc; + asio::error_code& ec; + }; + + template + struct io_control_visitor + : boost::static_visitor<> + { + io_control_visitor(IO_Control_Command& ioc) + : ioc(ioc) {} + + template + void operator()(T* p) const + { + p->io_control(ioc); + } + + void operator()(boost::blank) const + {} + + IO_Control_Command& ioc; + }; +// -------------- async_connect ----------- + + template + struct async_connect_visitor + : boost::static_visitor<> + { + async_connect_visitor(EndpointType const& endpoint, Handler const& handler) + : endpoint(endpoint) + , handler(handler) + {} + + template + void operator()(T* p) const + { + p->async_connect(endpoint, handler); + } + + void operator()(boost::blank) const + {} + + EndpointType const& endpoint; + Handler const& handler; + }; + +// -------------- bind ----------- + + template + struct bind_visitor + : boost::static_visitor<> + { + bind_visitor(EndpointType const& ep, Error_Handler const& error_handler) + : endpoint(ep) + , error_handler(error_handler) + {} + + template + void operator()(T* p) const + { + p->bind(endpoint, error_handler); + } + + void operator()(boost::blank) const + {} + + EndpointType const& endpoint; + Error_Handler const& error_handler; + }; + + template + struct bind_visitor + : boost::static_visitor<> + { + bind_visitor(EndpointType const& ep) + : endpoint(ep) + {} + + template + void operator()(T* p) const + { + p->bind(endpoint); + } + + void operator()(boost::blank) const + {} + + EndpointType const& endpoint; + }; + +// -------------- open ----------- + + template + struct open_visitor + : boost::static_visitor<> + { + open_visitor(Protocol const& p, Error_Handler const& error_handler) + : proto(p) + , error_handler(error_handler) + {} + + template + void operator()(T* p) const + { + p->open(proto, error_handler); + } + + void operator()(boost::blank) const + {} + + Protocol const& proto; + Error_Handler const& error_handler; + }; + + template + struct open_visitor + : boost::static_visitor<> + { + open_visitor(Protocol const& p) + : proto(p) + {} + + template + void operator()(T* p) const + { + p->open(proto); + } + + void operator()(boost::blank) const + {} + + Protocol const& proto; + }; + +// -------------- close ----------- + + template + struct close_visitor + : boost::static_visitor<> + { + close_visitor(Error_Handler const& error_handler) + : error_handler(error_handler) + {} + + template + void operator()(T* p) const + { + p->close(error_handler); + } + + void operator()(boost::blank) const + {} + + Error_Handler const& error_handler; + }; + + template <> + struct close_visitor + : boost::static_visitor<> + { + template + void operator()(T* p) const + { + p->close(); + } + + void operator()(boost::blank) const + {} + }; + +// -------------- remote_endpoint ----------- + + template + struct remote_endpoint_visitor + : boost::static_visitor + { + remote_endpoint_visitor(Error_Handler const& error_handler) + : error_handler(error_handler) + {} + + template + EndpointType operator()(T* p) const + { + return p->remote_endpoint(error_handler); + } + + EndpointType operator()(boost::blank) const + { + return EndpointType(); + } + + Error_Handler const& error_handler; + }; + + template + struct remote_endpoint_visitor + : boost::static_visitor + { + template + EndpointType operator()(T* p) const + { + return p->remote_endpoint(); + } + + EndpointType operator()(boost::blank) const + { + return EndpointType(); + } + }; + +// -------------- local_endpoint ----------- + + template + struct local_endpoint_visitor + : boost::static_visitor + { + local_endpoint_visitor(Error_Handler const& error_handler) + : error_handler(error_handler) + {} + + template + EndpointType operator()(T* p) const + { + return p->local_endpoint(error_handler); + } + + EndpointType operator()(boost::blank) const + { + return EndpointType(); + } + + Error_Handler const& error_handler; + }; + + template + struct local_endpoint_visitor + : boost::static_visitor + { + template + EndpointType operator()(T* p) const + { + return p->local_endpoint(); + } + + EndpointType operator()(boost::blank) const + { + return EndpointType(); + } + }; + +// -------------- async_read_some ----------- + + template + struct async_read_some_visitor + : boost::static_visitor<> + { + async_read_some_visitor(Mutable_Buffers const& buffers, Handler const& handler) + : buffers(buffers) + , handler(handler) + {} + + template + void operator()(T* p) const + { + p->async_read_some(buffers, handler); + } + void operator()(boost::blank) const + {} + + Mutable_Buffers const& buffers; + Handler const& handler; + }; + +// -------------- read_some ----------- + + template + struct read_some_visitor + : boost::static_visitor + { + read_some_visitor(Mutable_Buffers const& buffers) + : buffers(buffers) + {} + + template + std::size_t operator()(T* p) const + { return p->read_some(buffers); } + + std::size_t operator()(boost::blank) const + { return 0; } + + Mutable_Buffers const& buffers; + }; + + template + struct read_some_visitor_ec + : boost::static_visitor + { + read_some_visitor_ec(Mutable_Buffers const& buffers, asio::error_code& ec) + : buffers(buffers) + , ec(ec) + {} + + template + std::size_t operator()(T* p) const + { return p->read_some(buffers, ec); } + + std::size_t operator()(boost::blank) const + { return 0; } + + Mutable_Buffers const& buffers; + asio::error_code& ec; + }; + +// -------------- async_write_some ----------- + + template + struct async_write_some_visitor + : boost::static_visitor<> + { + async_write_some_visitor(Const_Buffers const& buffers, Handler const& handler) + : buffers(buffers) + , handler(handler) + {} + + template + void operator()(T* p) const + { + p->async_write_some(buffers, handler); + } + + void operator()(boost::blank) const + {} + + Const_Buffers const& buffers; + Handler const& handler; + }; + +// -------------- in_avail ----------- + + template + struct in_avail_visitor + : boost::static_visitor + { + in_avail_visitor(Error_Handler const& error_handler) + : error_handler(error_handler) + {} + + template + std::size_t operator()(T* p) const + { + return p->in_avail(error_handler); + } + + std::size_t operator()(boost::blank) const + { + return 0; + } + + Error_Handler const& error_handler; + }; + + template <> + struct in_avail_visitor + : boost::static_visitor + { + template + std::size_t operator()(T* p) const + { + return p->in_avail(); + } + + void operator()(boost::blank) const + {} + }; + +// -------------- io_service ----------- + + template + struct io_service_visitor + : boost::static_visitor + { + template + IOService& operator()(T* p) const + { + return p->io_service(); + } + + IOService& operator()(boost::blank) const + { + return *(IOService*)0; + } + }; + +// -------------- lowest_layer ----------- + + template + struct lowest_layer_visitor + : boost::static_visitor + { + template + LowestLayer& operator()(T* p) const + { + return p->lowest_layer(); + } + + LowestLayer& operator()(boost::blank) const + { + return *(LowestLayer*)0; + } + }; + +} // namespace aux + +template < + BOOST_PP_ENUM_BINARY_PARAMS( + NETWORK_VARIANT_STREAM_LIMIT, class S, = boost::mpl::void_ BOOST_PP_INTERCEPT + ) +> +class variant_stream : boost::noncopyable +{ +public: + typedef BOOST_PP_CAT(boost::mpl::vector, NETWORK_VARIANT_STREAM_LIMIT)< + BOOST_PP_ENUM_PARAMS(NETWORK_VARIANT_STREAM_LIMIT, S) + > types0; + + typedef typename boost::mpl::remove::type types; + + typedef typename boost::make_variant_over< + typename boost::mpl::push_back< + typename boost::mpl::transform< + types + , boost::add_pointer + >::type + , boost::blank + >::type + >::type variant_type; + + typedef typename S0::lowest_layer_type lowest_layer_type; + typedef typename S0::endpoint_type endpoint_type; + typedef typename S0::protocol_type protocol_type; + + explicit variant_stream(asio::io_service& io_service) + : m_io_service(io_service) + , m_variant(boost::blank()) + {} + + template + void instantiate() + { + std::auto_ptr owned(new S(m_io_service)); + boost::apply_visitor(aux::delete_visitor(), m_variant); + m_variant = owned.get(); + owned.release(); + } + + template + S& get() + { + return *boost::get(m_variant); + } + + bool instantiated() const + { + return m_variant.which() != boost::mpl::size::value; + } + + ~variant_stream() + { + boost::apply_visitor(aux::delete_visitor(), m_variant); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, asio::error_code& ec) + { + assert(instantiated()); + return boost::apply_visitor( + aux::read_some_visitor_ec(buffers, ec) + , m_variant + ); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + assert(instantiated()); + return boost::apply_visitor( + aux::read_some_visitor(buffers) + , m_variant + ); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + assert(instantiated()); + boost::apply_visitor( + aux::async_read_some_visitor(buffers, handler) + , m_variant + ); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + assert(instantiated()); + boost::apply_visitor( + aux::async_write_some_visitor(buffers, handler) + , m_variant + ); + } + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + assert(instantiated()); + boost::apply_visitor( + aux::async_connect_visitor(endpoint, handler), m_variant + ); + } + + template + void io_control(IO_Control_Command& ioc) + { + assert(instantiated()); + boost::apply_visitor( + aux::io_control_visitor(ioc), m_variant + ); + } + + template + void io_control(IO_Control_Command& ioc, asio::error_code& ec) + { + assert(instantiated()); + boost::apply_visitor( + aux::io_control_visitor_ec(ioc, ec) + , m_variant + ); + } + + void bind(endpoint_type const& endpoint) + { + assert(instantiated()); + boost::apply_visitor(aux::bind_visitor(endpoint), m_variant); + } + + template + void bind(endpoint_type const& endpoint, Error_Handler const& error_handler) + { + assert(instantiated()); + boost::apply_visitor( + aux::bind_visitor(endpoint, error_handler), m_variant + ); + } + + void open(protocol_type const& p) + { + assert(instantiated()); + boost::apply_visitor(aux::open_visitor(p), m_variant); + } + + template + void open(protocol_type const& p, Error_Handler const& error_handler) + { + assert(instantiated()); + boost::apply_visitor( + aux::open_visitor(p, error_handler), m_variant + ); + } + + void close() + { + assert(instantiated()); + boost::apply_visitor(aux::close_visitor<>(), m_variant); + } + + template + void close(Error_Handler const& error_handler) + { + assert(instantiated()); + boost::apply_visitor( + aux::close_visitor(error_handler), m_variant + ); + } + + std::size_t in_avail() + { + assert(instantiated()); + return boost::apply_visitor(aux::in_avail_visitor<>(), m_variant); + } + + template + std::size_t in_avail(Error_Handler const& error_handler) + { + assert(instantiated()); + return boost::apply_visitor( + aux::in_avail_visitor(error_handler), m_variant + ); + } + + endpoint_type remote_endpoint() + { + assert(instantiated()); + return boost::apply_visitor(aux::remote_endpoint_visitor(), m_variant); + } + + template + endpoint_type remote_endpoint(Error_Handler const& error_handler) + { + assert(instantiated()); + return boost::apply_visitor( + aux::remote_endpoint_visitor(error_handler), m_variant + ); + } + + endpoint_type local_endpoint() + { + assert(instantiated()); + return boost::apply_visitor(aux::local_endpoint_visitor(), m_variant); + } + + template + endpoint_type local_endpoint(Error_Handler const& error_handler) + { + assert(instantiated()); + return boost::apply_visitor( + aux::local_endpoint_visitor(error_handler), m_variant + ); + } + + asio::io_service& io_service() + { + assert(instantiated()); + return boost::apply_visitor( + aux::io_service_visitor(), m_variant + ); + } + + lowest_layer_type& lowest_layer() + { + assert(instantiated()); + return boost::apply_visitor( + aux::lowest_layer_visitor(), m_variant + ); + } + +private: + asio::io_service& m_io_service; + variant_type m_variant; +}; + +} // namespace libtorrent + +#endif // VARIANT_STREAM_070211_HPP + diff --git a/libtorrent/include/libtorrent/version.hpp b/libtorrent/include/libtorrent/version.hpp new file mode 100755 index 000000000..de1b8bcc8 --- /dev/null +++ b/libtorrent/include/libtorrent/version.hpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VERSION_HPP_INCLUDED +#define TORRENT_VERSION_HPP_INCLUDED + +#define LIBTORRENT_VERSION_MAJOR 0 +#define LIBTORRENT_VERSION_MINOR 13 + +#define LIBTORRENT_VERSION "0.13.0.0" + +#endif diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp new file mode 100755 index 000000000..ba7450c0a --- /dev/null +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,179 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +// parse_url +#include "libtorrent/tracker_manager.hpp" +// http_parser +#include "libtorrent/http_tracker_connection.hpp" + +namespace libtorrent +{ + class torrent; + + namespace detail + { + struct session_impl; + } + + class TORRENT_EXPORT web_peer_connection + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + web_peer_connection( + aux::session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote + , std::string const& url + , policy::peer* peerinfo); + + ~web_peer_connection(); + + // called from the main loop when this connection has any + // work to do. + void on_sent(asio::error_code const& error + , std::size_t bytes_transferred); + void on_receive(asio::error_code const& error + , std::size_t bytes_transferred); + + std::string const& url() const { return m_url; } + + virtual void get_specific_peer_info(peer_info& p) const; + virtual bool in_handshake() const; + + // the following functions appends messages + // to the send buffer + void write_choke() {} + void write_unchoke() {} + void write_interested() {} + void write_not_interested() {} + void write_request(peer_request const& r); + void write_cancel(peer_request const& r) {} + void write_have(int index) {} + void write_piece(peer_request const& r, char const* buffer) { assert(false); } + void write_keepalive() {} + void on_connected(); + +#ifndef NDEBUG + void check_invariant() const; +#endif + + private: + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + boost::optional downloading_piece_progress() const; + + // this has one entry per bittorrent request + std::deque m_requests; + // this has one entry per http-request + // (might be more than the bt requests) + std::deque m_file_requests; + + std::string m_server_string; + http_parser m_parser; + std::string m_auth; + std::string m_host; + int m_port; + std::string m_path; + std::string m_url; + + // the first request will contain a little bit more data + // than subsequent ones, things that aren't critical are left + // out to save bandwidth. + bool m_first_request; + + // this is used for intermediate storage of pieces + // that is received in more than on HTTP responses + std::vector m_piece; + // the mapping of the data in the m_piece buffer + peer_request m_intermediate_piece; + + // the number of bytes into the receive buffer where + // current read cursor is. + int m_body_start; + // the number of bytes received in the current HTTP + // response. used to know where in the buffer the + // next response starts + int m_received_body; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/xml_parse.hpp b/libtorrent/include/libtorrent/xml_parse.hpp new file mode 100644 index 000000000..c3aeddad7 --- /dev/null +++ b/libtorrent/include/libtorrent/xml_parse.hpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_XML_PARSE_HPP +#define TORRENT_XML_PARSE_HPP + +namespace libtorrent +{ + const int xml_start_tag = 0; + const int xml_end_tag = 1; + const int xml_string = 2; + + template + void xml_parse(char* p, char* end, CallbackType callback) + { + for(;p != end; ++p) + { + char const* start = p; + // look for tag start + for(; *p != '<' && p != end; ++p); + + if (p != start) + { + if (p != end) + { + assert(*p == '<'); + *p = 0; + } + callback(xml_string, start); + if (p != end) *p = '<'; + } + + if (p == end) break; + + // skip '<' + ++p; + + // parse the name of the tag. Ignore attributes + for (start = p; p != end && *p != '>'; ++p) + { + // terminate the string at the first space + // to ignore tag attributes + if (*p == ' ') *p = 0; + } + + // parse error + if (p == end) break; + + assert(*p == '>'); + *p = 0; + + if (*start == '/') + { + ++start; + callback(xml_end_tag, start); + } + else + { + callback(xml_start_tag, start); + } + *p = '>'; + } + + } + +} + + +#endif + diff --git a/libtorrent/src/Makefile.am b/libtorrent/src/Makefile.am new file mode 100644 index 000000000..97e74ee25 --- /dev/null +++ b/libtorrent/src/Makefile.am @@ -0,0 +1,100 @@ +lib_LTLIBRARIES = libtorrent.la + +if USE_DHT +kademlia_sources = kademlia/closest_nodes.cpp \ +kademlia/dht_tracker.cpp \ +kademlia/find_data.cpp \ +kademlia/node.cpp \ +kademlia/node_id.cpp \ +kademlia/refresh.cpp \ +kademlia/routing_table.cpp \ +kademlia/rpc_manager.cpp \ +kademlia/traversal_algorithm.cpp +endif + +libtorrent_la_SOURCES = allocate_resources.cpp \ +bandwidth_manager.cpp entry.cpp escape_string.cpp \ +peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ +natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \ +stat.cpp storage.cpp torrent.cpp torrent_handle.cpp pe_crypto.cpp \ +torrent_info.cpp tracker_manager.cpp http_connection.cpp \ +http_tracker_connection.cpp udp_tracker_connection.cpp \ +alert.cpp identify_client.cpp ip_filter.cpp file.cpp metadata_transfer.cpp \ +logger.cpp file_pool.cpp ut_pex.cpp lsd.cpp upnp.cpp instantiate_connection.cpp \ +socks5_stream.cpp socks4_stream.cpp http_stream.cpp connection_queue.cpp \ +disk_io_thread.cpp \ +$(kademlia_sources) + +noinst_HEADERS = \ +$(top_srcdir)/include/libtorrent/alert.hpp \ +$(top_srcdir)/include/libtorrent/alert_types.hpp \ +$(top_srcdir)/include/libtorrent/allocate_resources.hpp \ +$(top_srcdir)/include/libtorrent/aux_/allocate_resources_impl.hpp \ +$(top_srcdir)/include/libtorrent/aux_/session_impl.hpp \ +$(top_srcdir)/include/libtorrent/bandwidth_manager.hpp \ +$(top_srcdir)/include/libtorrent/bencode.hpp \ +$(top_srcdir)/include/libtorrent/buffer.hpp \ +$(top_srcdir)/include/libtorrent/connection_queue.hpp \ +$(top_srcdir)/include/libtorrent/debug.hpp \ +$(top_srcdir)/include/libtorrent/disk_io_thread.hpp \ +$(top_srcdir)/include/libtorrent/entry.hpp \ +$(top_srcdir)/include/libtorrent/escape_string.hpp \ +$(top_srcdir)/include/libtorrent/extensions.hpp \ +$(top_srcdir)/include/libtorrent/extensions/metadata_transfer.hpp \ +$(top_srcdir)/include/libtorrent/extensions/logger.hpp \ +$(top_srcdir)/include/libtorrent/extensions/ut_pex.hpp \ +$(top_srcdir)/include/libtorrent/file.hpp \ +$(top_srcdir)/include/libtorrent/file_pool.hpp \ +$(top_srcdir)/include/libtorrent/fingerprint.hpp \ +$(top_srcdir)/include/libtorrent/hasher.hpp \ +$(top_srcdir)/include/libtorrent/http_connection.hpp \ +$(top_srcdir)/include/libtorrent/http_stream.hpp \ +$(top_srcdir)/include/libtorrent/session_settings.hpp \ +$(top_srcdir)/include/libtorrent/http_tracker_connection.hpp \ +$(top_srcdir)/include/libtorrent/identify_client.hpp \ +$(top_srcdir)/include/libtorrent/instantiate_connection.hpp \ +$(top_srcdir)/include/libtorrent/intrusive_ptr_base.hpp \ +$(top_srcdir)/include/libtorrent/invariant_check.hpp \ +$(top_srcdir)/include/libtorrent/io.hpp \ +$(top_srcdir)/include/libtorrent/ip_filter.hpp \ +$(top_srcdir)/include/libtorrent/lsd.hpp \ +$(top_srcdir)/include/libtorrent/peer.hpp \ +$(top_srcdir)/include/libtorrent/peer_connection.hpp \ +$(top_srcdir)/include/libtorrent/bt_peer_connection.hpp \ +$(top_srcdir)/include/libtorrent/web_peer_connection.hpp \ +$(top_srcdir)/include/libtorrent/pe_crypto.hpp \ +$(top_srcdir)/include/libtorrent/natpmp.hpp \ +$(top_srcdir)/include/libtorrent/pch.hpp \ +$(top_srcdir)/include/libtorrent/peer_id.hpp \ +$(top_srcdir)/include/libtorrent/peer_info.hpp \ +$(top_srcdir)/include/libtorrent/peer_request.hpp \ +$(top_srcdir)/include/libtorrent/piece_block_progress.hpp \ +$(top_srcdir)/include/libtorrent/piece_picker.hpp \ +$(top_srcdir)/include/libtorrent/policy.hpp \ +$(top_srcdir)/include/libtorrent/resource_request.hpp \ +$(top_srcdir)/include/libtorrent/session.hpp \ +$(top_srcdir)/include/libtorrent/size_type.hpp \ +$(top_srcdir)/include/libtorrent/socket.hpp \ +$(top_srcdir)/include/libtorrent/socket_type.hpp \ +$(top_srcdir)/include/libtorrent/socks4_stream.hpp \ +$(top_srcdir)/include/libtorrent/socks5_stream.hpp \ +$(top_srcdir)/include/libtorrent/stat.hpp \ +$(top_srcdir)/include/libtorrent/storage.hpp \ +$(top_srcdir)/include/libtorrent/time.hpp \ +$(top_srcdir)/include/libtorrent/torrent.hpp \ +$(top_srcdir)/include/libtorrent/torrent_handle.hpp \ +$(top_srcdir)/include/libtorrent/torrent_info.hpp \ +$(top_srcdir)/include/libtorrent/tracker_manager.hpp \ +$(top_srcdir)/include/libtorrent/udp_tracker_connection.hpp \ +$(top_srcdir)/include/libtorrent/utf8.hpp \ +$(top_srcdir)/include/libtorrent/xml_parse.hpp \ +$(top_srcdir)/include/libtorrent/variant_stream.hpp \ +$(top_srcdir)/include/libtorrent/version.hpp + + +libtorrent_la_LDFLAGS = $(LDFLAGS) -release @VERSION@ +libtorrent_la_LIBADD = @ZLIB@ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ + +AM_CXXFLAGS= -ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @ZLIBINCL@ @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_LDFLAGS= $(LDFLAGS) -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ + diff --git a/libtorrent/src/alert.cpp b/libtorrent/src/alert.cpp new file mode 100755 index 000000000..b990db7e1 --- /dev/null +++ b/libtorrent/src/alert.cpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2003, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/alert.hpp" + +namespace libtorrent { + + alert::alert(severity_t severity, const std::string& msg) + : m_msg(msg) + , m_severity(severity) + , m_timestamp(time_now()) + { + } + + alert::~alert() + { + } + + ptime alert::timestamp() const + { + return m_timestamp; + } + + const std::string& alert::msg() const + { + return m_msg; + } + + alert::severity_t alert::severity() const + { + return m_severity; + } + + + + alert_manager::alert_manager() + : m_severity(alert::none) + {} + + alert_manager::~alert_manager() + { + while (!m_alerts.empty()) + { + delete m_alerts.front(); + m_alerts.pop(); + } + } + + void alert_manager::post_alert(const alert& alert_) + { + boost::mutex::scoped_lock lock(m_mutex); + if (m_severity > alert_.severity()) return; + + // the internal limit is 100 alerts + if (m_alerts.size() == 100) + { + alert* result = m_alerts.front(); + m_alerts.pop(); + delete result; + } + m_alerts.push(alert_.clone().release()); + } + + std::auto_ptr alert_manager::get() + { + boost::mutex::scoped_lock lock(m_mutex); + + assert(!m_alerts.empty()); + + alert* result = m_alerts.front(); + m_alerts.pop(); + return std::auto_ptr(result); + } + + bool alert_manager::pending() const + { + boost::mutex::scoped_lock lock(m_mutex); + + return !m_alerts.empty(); + } + + void alert_manager::set_severity(alert::severity_t severity) + { + boost::mutex::scoped_lock lock(m_mutex); + + m_severity = severity; + } + + bool alert_manager::should_post(alert::severity_t severity) const + { + return severity >= m_severity; + } + +} // namespace libtorrent + diff --git a/libtorrent/src/allocate_resources.cpp b/libtorrent/src/allocate_resources.cpp new file mode 100644 index 000000000..deef06dc4 --- /dev/null +++ b/libtorrent/src/allocate_resources.cpp @@ -0,0 +1,225 @@ +/* + +Copyright (c) 2006, Magnus Jonsson, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +//The Standard Library defines the two template functions std::min() +//and std::max() in the header. In general, you should +//use these template functions for calculating the min and max values +//of a pair. Unfortunately, Visual C++ does not define these function +// templates. This is because the names min and max clash with +//the traditional min and max macros defined in . +//As a workaround, Visual C++ defines two alternative templates with +//identical functionality called _cpp_min() and _cpp_max(). You can +//use them instead of std::min() and std::max().To disable the +//generation of the min and max macros in Visual C++, #define +//NOMINMAX before #including . + +#include "libtorrent/pch.hpp" + +#ifdef _WIN32 + //support boost1.32.0(2004-11-19 18:47) + //now all libs can be compiled and linked with static module + #define NOMINMAX +#endif + +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/size_type.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/allocate_resources_impl.hpp" + +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1310 +#define for if (false) {} else for +#else +#include +#endif + +namespace libtorrent +{ + int saturated_add(int a, int b) + { + assert(a >= 0); + assert(b >= 0); + assert(a <= resource_request::inf); + assert(b <= resource_request::inf); + assert(resource_request::inf + resource_request::inf < 0); + + unsigned int sum = unsigned(a) + unsigned(b); + if (sum > unsigned(resource_request::inf)) + sum = resource_request::inf; + + assert(sum >= unsigned(a) && sum >= unsigned(b)); + return int(sum); + } + +#if defined(_MSC_VER) && _MSC_VER < 1310 + + namespace detail + { + struct iterator_wrapper + { + typedef std::map >::iterator orig_iter; + + orig_iter iter; + + iterator_wrapper(orig_iter i): iter(i) {} + void operator++() { ++iter; } + torrent& operator*() { return *(iter->second); } + bool operator==(const iterator_wrapper& i) const + { return iter == i.iter; } + bool operator!=(const iterator_wrapper& i) const + { return iter != i.iter; } + }; + + struct iterator_wrapper2 + { + typedef std::map::iterator orig_iter; + + orig_iter iter; + + iterator_wrapper2(orig_iter i): iter(i) {} + void operator++() { ++iter; } + peer_connection& operator*() { return *(iter->second); } + bool operator==(const iterator_wrapper2& i) const + { return iter == i.iter; } + bool operator!=(const iterator_wrapper2& i) const + { return iter != i.iter; } + }; + + } + + void allocate_resources( + int resources + , std::map >& c + , resource_request torrent::* res) + { + aux::allocate_resources_impl( + resources + , detail::iterator_wrapper(c.begin()) + , detail::iterator_wrapper(c.end()) + , res); + } + + void allocate_resources( + int resources + , std::map& c + , resource_request peer_connection::* res) + { + aux::allocate_resources_impl( + resources + , detail::iterator_wrapper2(c.begin()) + , detail::iterator_wrapper2(c.end()) + , res); + } + +#else + + namespace aux + { + peer_connection& pick_peer( + std::pair + , boost::intrusive_ptr > const& p) + { + return *p.second; + } + + peer_connection& pick_peer2( + std::pair const& p) + { + return *p.second; + } + + torrent& deref(std::pair > const& p) + { + return *p.second; + } + + session& deref(session* p) + { + return *p; + } + } + + void allocate_resources( + int resources + , std::map >& c + , resource_request torrent::* res) + { + typedef std::map >::iterator orig_iter; + typedef std::pair > in_param; + typedef boost::transform_iterator new_iter; + + aux::allocate_resources_impl( + resources + , new_iter(c.begin(), &aux::deref) + , new_iter(c.end(), &aux::deref) + , res); + } + + void allocate_resources( + int resources + , std::map& c + , resource_request peer_connection::* res) + { + typedef std::map::iterator orig_iter; + typedef std::pair in_param; + typedef boost::transform_iterator new_iter; + + aux::allocate_resources_impl( + resources + , new_iter(c.begin(), &aux::pick_peer2) + , new_iter(c.end(), &aux::pick_peer2) + , res); + } + + void allocate_resources( + int resources + , std::vector& _sessions + , resource_request session::* res) + { + typedef std::vector::iterator orig_iter; + typedef session* in_param; + typedef boost::transform_iterator new_iter; + + aux::allocate_resources_impl( + resources + , new_iter(_sessions.begin(), &aux::deref) + , new_iter(_sessions.end(), &aux::deref) + , res); + } + +#endif + +} // namespace libtorrent diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp new file mode 100755 index 000000000..2483b4a2a --- /dev/null +++ b/libtorrent/src/bt_peer_connection.cpp @@ -0,0 +1,2288 @@ +/* + +Copyright (c) 2003 - 2006, Arvid Norberg +Copyright (c) 2007, Arvid Norberg, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#ifndef TORRENT_DISABLE_ENCRYPTION +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/hasher.hpp" +#endif + +using boost::bind; +using boost::shared_ptr; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + const bt_peer_connection::message_handler + bt_peer_connection::m_message_handler[] = + { + &bt_peer_connection::on_choke, + &bt_peer_connection::on_unchoke, + &bt_peer_connection::on_interested, + &bt_peer_connection::on_not_interested, + &bt_peer_connection::on_have, + &bt_peer_connection::on_bitfield, + &bt_peer_connection::on_request, + &bt_peer_connection::on_piece, + &bt_peer_connection::on_cancel, + &bt_peer_connection::on_dht_port, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + &bt_peer_connection::on_extended + }; + + + bt_peer_connection::bt_peer_connection( + session_impl& ses + , boost::weak_ptr tor + , shared_ptr s + , tcp::endpoint const& remote + , policy::peer* peerinfo) + : peer_connection(ses, tor, s, remote + , peerinfo) + , m_state(read_protocol_identifier) +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_supports_extensions(false) +#endif + , m_supports_dht_port(false) +#ifndef TORRENT_DISABLE_ENCRYPTION + , m_encrypted(false) + , m_rc4_encrypted(false) + , m_sync_bytes_read(0) + , m_enc_send_buffer(0, 0) +#endif +#ifndef NDEBUG + , m_sent_bitfield(false) + , m_in_constructor(true) +#endif + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "*** bt_peer_connection\n"; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + + pe_settings::enc_policy const& out_enc_policy = m_ses.get_pe_settings().out_enc_policy; + + if (out_enc_policy == pe_settings::forced) + { + write_pe1_2_dhkey(); + + m_state = read_pe_dhkey; + reset_recv_buffer(dh_key_len); + setup_receive(); + } + else if (out_enc_policy == pe_settings::enabled) + { + assert(peer_info_struct()); + + policy::peer* pi = peer_info_struct(); + if (pi->pe_support == true) + { + // toggle encryption support flag, toggled back to + // true if encrypted portion of the handshake + // completes correctly + pi->pe_support = false; + + write_pe1_2_dhkey(); + m_state = read_pe_dhkey; + reset_recv_buffer(dh_key_len); + setup_receive(); + } + else // pi->pe_support == false + { + // toggled back to false if standard handshake + // completes correctly (without encryption) + pi->pe_support = true; + + write_handshake(); + reset_recv_buffer(20); + setup_receive(); + } + } + else if (out_enc_policy == pe_settings::disabled) +#endif + { + write_handshake(); + + // start in the state where we are trying to read the + // handshake from the other side + reset_recv_buffer(20); + setup_receive(); + } + +#ifndef NDEBUG + m_in_constructor = false; +#endif + } + + bt_peer_connection::bt_peer_connection( + session_impl& ses + , boost::shared_ptr s + , policy::peer* peerinfo) + : peer_connection(ses, s, peerinfo) + , m_state(read_protocol_identifier) +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_supports_extensions(false) +#endif + , m_supports_dht_port(false) +#ifndef TORRENT_DISABLE_ENCRYPTION + , m_encrypted(false) + , m_rc4_encrypted(false) + , m_sync_bytes_read(0) + , m_enc_send_buffer(0, 0) +#endif +#ifndef NDEBUG + , m_sent_bitfield(false) + , m_in_constructor(true) +#endif + { + + // we are not attached to any torrent yet. + // we have to wait for the handshake to see + // which torrent the connector want's to connect to + + + // upload bandwidth will only be given to connections + // that are part of a torrent. Since this is an incoming + // connection, we have to give it some initial bandwidth + // to send the handshake. +#ifndef TORRENT_DISABLE_ENCRYPTION + m_bandwidth_limit[download_channel].assign(2048); + m_bandwidth_limit[upload_channel].assign(2048); +#else + m_bandwidth_limit[download_channel].assign(80); + m_bandwidth_limit[upload_channel].assign(80); +#endif + + // start in the state where we are trying to read the + // handshake from the other side + reset_recv_buffer(20); + setup_receive(); +#ifndef NDEBUG + m_in_constructor = false; +#endif + } + + bt_peer_connection::~bt_peer_connection() + { + } + + void bt_peer_connection::on_metadata() + { + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + write_bitfield(t->pieces()); + } + + void bt_peer_connection::write_dht_port(int listen_port) + { + INVARIANT_CHECK; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> DHT_PORT [ " << listen_port << " ]\n"; +#endif + buffer::interval packet = allocate_send_buffer(7); + detail::write_uint32(3, packet.begin); + detail::write_uint8(msg_dht_port, packet.begin); + detail::write_uint16(listen_port, packet.begin); + assert(packet.begin == packet.end); + setup_send(); + } + + void bt_peer_connection::get_specific_peer_info(peer_info& p) const + { + assert(!associated_torrent().expired()); + + if (is_interesting()) p.flags |= peer_info::interesting; + if (is_choked()) p.flags |= peer_info::choked; + if (is_peer_interested()) p.flags |= peer_info::remote_interested; + if (has_peer_choked()) p.flags |= peer_info::remote_choked; + if (support_extensions()) p.flags |= peer_info::supports_extensions; + if (is_local()) p.flags |= peer_info::local_connection; + +#ifndef TORRENT_DISABLE_ENCRYPTION + if (m_encrypted) + { + m_rc4_encrypted ? + p.flags |= peer_info::rc4_encrypted : + p.flags |= peer_info::plaintext_encrypted; + } +#endif + + if (!is_connecting() && in_handshake()) + p.flags |= peer_info::handshake; + if (is_connecting() && !is_queued()) p.flags |= peer_info::connecting; + if (is_queued()) p.flags |= peer_info::queued; + + p.client = m_client_version; + p.connection_type = peer_info::standard_bittorrent; + + } + + bool bt_peer_connection::in_handshake() const + { + return m_state < read_packet_size; + } + +#ifndef TORRENT_DISABLE_ENCRYPTION + + void bt_peer_connection::write_pe1_2_dhkey() + { + INVARIANT_CHECK; + + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(!m_DH_key_exchange.get()); + +#ifdef TORRENT_VERBOSE_LOGGING + if (is_local()) + (*m_logger) << " initiating encrypted handshake\n"; +#endif + + m_DH_key_exchange.reset(new DH_key_exchange); + + int pad_size = std::rand() % 512; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " pad size: " << pad_size << "\n"; +#endif + + buffer::interval send_buf = allocate_send_buffer(dh_key_len + pad_size); + + std::copy (m_DH_key_exchange->get_local_key(), + m_DH_key_exchange->get_local_key() + dh_key_len, + send_buf.begin); + + std::generate(send_buf.begin + dh_key_len, send_buf.end, std::rand); + setup_send(); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " sent DH key\n"; +#endif + } + + void bt_peer_connection::write_pe3_sync() + { + INVARIANT_CHECK; + + assert (!m_encrypted); + assert (!m_rc4_encrypted); + assert (is_local()); + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + hasher h; + sha1_hash const& info_hash = t->torrent_file().info_hash(); + char const* const secret = m_DH_key_exchange->get_secret(); + + int pad_size = 0; // rand() % 512; // Keep 0 for now + + // synchash,skeyhash,vc,crypto_provide,len(pad),pad,len(ia) + buffer::interval send_buf = + allocate_send_buffer (20 + 20 + 8 + 4 + 2 + pad_size + 2); + + // sync hash (hash('req1',S)) + h.reset(); + h.update("req1",4); + h.update(secret, dh_key_len); + sha1_hash sync_hash = h.final(); + + std::copy (sync_hash.begin(), sync_hash.end(), send_buf.begin); + send_buf.begin += 20; + + // stream key obfuscated hash [ hash('req2',SKEY) xor hash('req3',S) ] + h.reset(); + h.update("req2",4); + h.update((const char*)info_hash.begin(), 20); + sha1_hash streamkey_hash = h.final(); + + h.reset(); + h.update("req3",4); + h.update(secret, dh_key_len); + sha1_hash obfsc_hash = h.final(); + obfsc_hash ^= streamkey_hash; + + std::copy (obfsc_hash.begin(), obfsc_hash.end(), send_buf.begin); + send_buf.begin += 20; + + // Discard DH key exchange data, setup RC4 keys + init_pe_RC4_handler(secret, info_hash); + m_DH_key_exchange.reset(); // secret should be invalid at this point + + // write the verification constant and crypto field + assert(send_buf.left() == 8 + 4 + 2 + pad_size + 2); + int encrypt_size = send_buf.left(); + + int crypto_provide = 0; + pe_settings::enc_level const& allowed_enc_level = m_ses.get_pe_settings().allowed_enc_level; + + if (allowed_enc_level == pe_settings::both) + crypto_provide = 0x03; + else if (allowed_enc_level == pe_settings::rc4) + crypto_provide = 0x02; + else if (allowed_enc_level == pe_settings::plaintext) + crypto_provide = 0x01; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " crypto provide : [ "; + if (allowed_enc_level == pe_settings::both) + (*m_logger) << "plaintext rc4 ]\n"; + else if (allowed_enc_level == pe_settings::rc4) + (*m_logger) << "rc4 ]\n"; + else if (allowed_enc_level == pe_settings::plaintext) + (*m_logger) << "plaintext ]\n"; +#endif + + write_pe_vc_cryptofield(send_buf, crypto_provide, pad_size); + m_RC4_handler->encrypt(send_buf.end - encrypt_size, encrypt_size); + + assert(send_buf.begin == send_buf.end); + setup_send(); + } + + void bt_peer_connection::write_pe4_sync(int crypto_select) + { + INVARIANT_CHECK; + + assert(!is_local()); + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(crypto_select == 0x02 || crypto_select == 0x01); + + int pad_size = 0; // rand() % 512; // Keep 0 for now + + const int buf_size = 8+4+2+pad_size; + buffer::interval send_buf = allocate_send_buffer(buf_size); + write_pe_vc_cryptofield(send_buf, crypto_select, pad_size); + + m_RC4_handler->encrypt(send_buf.end - buf_size, buf_size); + setup_send(); + + // encryption method has been negotiated + if (crypto_select == 0x02) + m_rc4_encrypted = true; + else // 0x01 + m_rc4_encrypted = false; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " crypto select : [ "; + if (crypto_select == 0x01) + (*m_logger) << "plaintext ]\n"; + else + (*m_logger) << "rc4 ]\n"; +#endif + } + + void bt_peer_connection::write_pe_vc_cryptofield(buffer::interval& write_buf, + int crypto_field, int pad_size) + { + INVARIANT_CHECK; + + assert(crypto_field <= 0x03 && crypto_field > 0); + assert(pad_size == 0); // pad not used yet + // vc,crypto_field,len(pad),pad, (len(ia)) + assert( (write_buf.left() == 8+4+2+pad_size+2 && is_local()) || + (write_buf.left() == 8+4+2+pad_size && !is_local()) ); + + // encrypt(vc, crypto_provide/select, len(Pad), len(IA)) + // len(pad) is zero for now, len(IA) only for outgoing connections + + // vc + std::fill(write_buf.begin, write_buf.begin + 8, 0); + write_buf.begin += 8; + + detail::write_uint32(crypto_field, write_buf.begin); + detail::write_uint16(pad_size, write_buf.begin); // len (pad) + + // fill pad with zeroes + // std::fill(write_buf.begin, write_buf.begin+pad_size, 0); + // write_buf.begin += pad_size; + + // append len(ia) if we are initiating + if (is_local()) + detail::write_uint16(handshake_len, write_buf.begin); // len(IA) + + assert (write_buf.begin == write_buf.end); + } + + void bt_peer_connection::init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key) + { + INVARIANT_CHECK; + + assert(secret); + + hasher h; + const char keyA[] = "keyA"; + const char keyB[] = "keyB"; + + // encryption rc4 longkeys + // outgoing connection : hash ('keyA',S,SKEY) + // incoming connection : hash ('keyB',S,SKEY) + + is_local() ? h.update(keyA, 4) : h.update(keyB, 4); + h.update(secret, dh_key_len); + h.update((char const*)stream_key.begin(), 20); + const sha1_hash local_key = h.final(); + + h.reset(); + + // decryption rc4 longkeys + // outgoing connection : hash ('keyB',S,SKEY) + // incoming connection : hash ('keyA',S,SKEY) + + is_local() ? h.update(keyB, 4) : h.update(keyA, 4); + h.update(secret, dh_key_len); + h.update((char const*)stream_key.begin(), 20); + const sha1_hash remote_key = h.final(); + + assert(!m_RC4_handler.get()); + m_RC4_handler.reset (new RC4_handler (local_key, remote_key)); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " computed RC4 keys\n"; +#endif + } + + void bt_peer_connection::send_buffer(char* begin, char* end) + { + assert (begin); + assert (end); + assert (end > begin); + assert (!m_rc4_encrypted || m_encrypted); + + if (m_rc4_encrypted) + m_RC4_handler->encrypt(begin, end - begin); + + peer_connection::send_buffer(begin, end); + } + + buffer::interval bt_peer_connection::allocate_send_buffer(int size) + { + assert(!m_rc4_encrypted || m_encrypted); + + if (m_rc4_encrypted) + { + m_enc_send_buffer = peer_connection::allocate_send_buffer(size); + return m_enc_send_buffer; + } + else + { + buffer::interval i = peer_connection::allocate_send_buffer(size); + return i; + } + } + + void bt_peer_connection::setup_send() + { + assert(!m_rc4_encrypted || m_encrypted); + + if (m_rc4_encrypted) + { + assert (m_enc_send_buffer.begin); + assert (m_enc_send_buffer.end); + assert (m_enc_send_buffer.left() > 0); + + m_RC4_handler->encrypt (m_enc_send_buffer.begin, m_enc_send_buffer.left()); + } + peer_connection::setup_send(); + } + + int bt_peer_connection::get_syncoffset(char const* src, int src_size, + char const* target, int target_size) const + { + assert (target_size >= src_size); + assert (src_size > 0); + assert (src); + assert (target); + + int traverse_limit = target_size - src_size; + + // TODO: this could be optimized using knuth morris pratt + for (int i = 0; i < traverse_limit; ++i) + { + char const* target_ptr = target + i; + if (std::equal(src, src+src_size, target_ptr)) + return i; + } + +// // Partial sync +// for (int i = 0; i < target_size; ++i) +// { +// // first is iterator in src[] at which mismatch occurs +// // second is iterator in target[] at which mismatch occurs +// std::pair ret; +// int src_sync_size; +// if (i > traverse_limit) // partial sync test +// { +// ret = std::mismatch(src, src + src_size - (i - traverse_limit), &target[i]); +// src_sync_size = ret.first - src; +// if (src_sync_size == (src_size - (i - traverse_limit))) +// return i; +// } +// else // complete sync test +// { +// ret = std::mismatch(src, src + src_size, &target[i]); +// src_sync_size = ret.first - src; +// if (src_sync_size == src_size) +// return i; +// } +// } + + // no complete sync + return -1; + } +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION + + void bt_peer_connection::write_handshake() + { + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + // add handshake to the send buffer + const char version_string[] = "BitTorrent protocol"; + const int string_len = sizeof(version_string)-1; + + buffer::interval i = allocate_send_buffer(1 + string_len + 8 + 20 + 20); + // length of version string + *i.begin = string_len; + ++i.begin; + + // version string itself + std::copy( + version_string + , version_string + string_len + , i.begin); + i.begin += string_len; + + // 8 zeroes + std::fill(i.begin, i.begin + 8, 0); + +#ifndef TORRENT_DISABLE_DHT + // indicate that we support the DHT messages + *(i.begin + 7) = 0x01; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + // we support extensions + *(i.begin + 5) = 0x10; +#endif + + i.begin += 8; + + // info hash + sha1_hash const& ih = t->torrent_file().info_hash(); + std::copy(ih.begin(), ih.end(), i.begin); + i.begin += 20; + + // peer id + std::copy( + m_ses.get_peer_id().begin() + , m_ses.get_peer_id().end() + , i.begin); + i.begin += 20; + assert(i.begin == i.end); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> HANDSHAKE\n"; +#endif + setup_send(); + } + + boost::optional bt_peer_connection::downloading_piece_progress() const + { + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + buffer::const_interval recv_buffer = receive_buffer(); + // are we currently receiving a 'piece' message? + if (m_state != read_packet + || recv_buffer.left() < 9 + || recv_buffer[0] != msg_piece) + return boost::optional(); + + const char* ptr = recv_buffer.begin + 1; + peer_request r; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = packet_size() - 9; + + // is any of the piece message header data invalid? + if (!verify_piece(r)) + return boost::optional(); + + piece_block_progress p; + + p.piece_index = r.piece; + p.block_index = r.start / t->block_size(); + p.bytes_downloaded = recv_buffer.left() - 9; + p.full_block_bytes = r.length; + + return boost::optional(p); + } + + + // message handlers + + // ----------------------------- + // --------- KEEPALIVE --------- + // ----------------------------- + + void bt_peer_connection::on_keepalive() + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== KEEPALIVE\n"; +#endif + incoming_keepalive(); + } + + // ----------------------------- + // ----------- CHOKE ----------- + // ----------------------------- + + void bt_peer_connection::on_choke(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 1) + throw protocol_error("'choke' message size != 1"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + incoming_choke(); + } + + // ----------------------------- + // ---------- UNCHOKE ---------- + // ----------------------------- + + void bt_peer_connection::on_unchoke(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 1) + throw protocol_error("'unchoke' message size != 1"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + incoming_unchoke(); + } + + // ----------------------------- + // -------- INTERESTED --------- + // ----------------------------- + + void bt_peer_connection::on_interested(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 1) + throw protocol_error("'interested' message size != 1"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + incoming_interested(); + } + + // ----------------------------- + // ------ NOT INTERESTED ------- + // ----------------------------- + + void bt_peer_connection::on_not_interested(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 1) + throw protocol_error("'not interested' message size != 1"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + incoming_not_interested(); + } + + // ----------------------------- + // ----------- HAVE ------------ + // ----------------------------- + + void bt_peer_connection::on_have(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 5) + throw protocol_error("'have' message size != 5"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + const char* ptr = recv_buffer.begin + 1; + int index = detail::read_int32(ptr); + + incoming_have(index); + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void bt_peer_connection::on_bitfield(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + // if we don't have the metedata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && packet_size() - 1 != ((int)get_bitfield().size() + 7) / 8) + throw protocol_error("bitfield with invalid size"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + std::vector bitfield; + + if (!t->valid_metadata()) + bitfield.resize((packet_size() - 1) * 8); + else + bitfield.resize(get_bitfield().size()); + + // if we don't have metadata yet + // just remember the bitmask + // don't update the piecepicker + // (since it doesn't exist yet) + for (int i = 0; i < (int)bitfield.size(); ++i) + bitfield[i] = (recv_buffer[1 + (i>>3)] & (1 << (7 - (i&7)))) != 0; + + incoming_bitfield(bitfield); + } + + // ----------------------------- + // ---------- REQUEST ---------- + // ----------------------------- + + void bt_peer_connection::on_request(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 13) + throw protocol_error("'request' message size != 13"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_request(r); + } + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void bt_peer_connection::on_piece(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + + buffer::const_interval recv_buffer = receive_buffer(); + int recv_pos = recv_buffer.end - recv_buffer.begin; + + // classify the received data as protocol chatter + // or data payload for the statistics + if (recv_pos <= 9) + // only received protocol data + m_statistics.received_bytes(0, received); + else if (recv_pos - received >= 9) + // only received payload data + m_statistics.received_bytes(received, 0); + else + { + // received a bit of both + assert(recv_pos - received < 9); + assert(recv_pos > 9); + assert(9 - (recv_pos - received) <= 9); + m_statistics.received_bytes( + recv_pos - 9 + , 9 - (recv_pos - received)); + } + + incoming_piece_fragment(); + if (!packet_finished()) return; + + const char* ptr = recv_buffer.begin + 1; + peer_request p; + p.piece = detail::read_int32(ptr); + p.start = detail::read_int32(ptr); + p.length = packet_size() - 9; + + incoming_piece(p, recv_buffer.begin + 9); + } + + // ----------------------------- + // ---------- CANCEL ----------- + // ----------------------------- + + void bt_peer_connection::on_cancel(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 13) + throw protocol_error("'cancel' message size != 13"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_cancel(r); + } + + // ----------------------------- + // --------- DHT PORT ---------- + // ----------------------------- + + void bt_peer_connection::on_dht_port(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + if (packet_size() != 3) + throw protocol_error("'dht_port' message size != 3"); + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + const char* ptr = recv_buffer.begin + 1; + int listen_port = detail::read_uint16(ptr); + + incoming_dht_port(listen_port); + } + + // ----------------------------- + // --------- EXTENDED ---------- + // ----------------------------- + + void bt_peer_connection::on_extended(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + m_statistics.received_bytes(0, received); + if (packet_size() < 2) + throw protocol_error("'extended' message smaller than 2 bytes"); + + if (associated_torrent().expired()) + throw protocol_error("'extended' message sent before proper handshake"); + + buffer::const_interval recv_buffer = receive_buffer(); + if (recv_buffer.left() < 2) return; + + assert(*recv_buffer.begin == msg_extended); + ++recv_buffer.begin; + + int extended_id = detail::read_uint8(recv_buffer.begin); + + if (extended_id == 0) + { + on_extended_handshake(); + return; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_extended(packet_size() - 2, extended_id + , recv_buffer)) + return; + } +#endif + + throw protocol_error("unknown extended message id: " + + boost::lexical_cast(extended_id)); + } + + void bt_peer_connection::on_extended_handshake() + { + if (!packet_finished()) return; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + buffer::const_interval recv_buffer = receive_buffer(); + + entry root; + try + { + root = bdecode(recv_buffer.begin + 2, recv_buffer.end); + } + catch (std::exception& exc) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "invalid extended handshake: " << exc.what() << "\n"; +#endif + return; + } + +#ifdef TORRENT_VERBOSE_LOGGING + std::stringstream ext; + root.print(ext); + (*m_logger) << "<== EXTENDED HANDSHAKE: \n" << ext.str(); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end;) + { + // a false return value means that the extension + // isn't supported by the other end. So, it is removed. + if (!(*i)->on_extension_handshake(root)) + i = m_extensions.erase(i); + else + ++i; + } +#endif + + // there is supposed to be a remote listen port + if (entry* listen_port = root.find_key("p")) + { + if (listen_port->type() == entry::int_t) + { + tcp::endpoint adr(remote().address() + , (unsigned short)listen_port->integer()); + t->get_policy().peer_from_tracker(adr, pid(), 0, 0); + } + } + // there should be a version too + // but where do we put that info? + + if (entry* client_info = root.find_key("v")) + { + if (client_info->type() == entry::string_t) + m_client_version = client_info->string(); + } + + if (entry* reqq = root.find_key("reqq")) + { + if (reqq->type() == entry::int_t) + m_max_out_request_queue = reqq->integer(); + if (m_max_out_request_queue < 1) + m_max_out_request_queue = 1; + } + } + + bool bt_peer_connection::dispatch_message(int received) + { + INVARIANT_CHECK; + + assert(received > 0); + + // this means the connection has been closed already + if (associated_torrent().expired()) return false; + + buffer::const_interval recv_buffer = receive_buffer(); + + int packet_type = recv_buffer[0]; + if (packet_type < 0 + || packet_type >= num_supported_messages + || m_message_handler[packet_type] == 0) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_unknown_message(packet_size(), packet_type + , buffer::const_interval(recv_buffer.begin+1 + , recv_buffer.end))) + return packet_finished(); + } +#endif + + throw protocol_error("unknown message id: " + + boost::lexical_cast(packet_type) + + " size: " + boost::lexical_cast(packet_size())); + } + + assert(m_message_handler[packet_type] != 0); + + // call the correct handler for this packet type + (this->*m_message_handler[packet_type])(received); + + return packet_finished(); + } + + void bt_peer_connection::write_keepalive() + { + INVARIANT_CHECK; + + char buf[] = {0,0,0,0}; + send_buffer(buf, buf + sizeof(buf)); + } + + void bt_peer_connection::write_cancel(peer_request const& r) + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + assert(associated_torrent().lock()->valid_metadata()); + + char buf[] = {0,0,0,13, msg_cancel}; + + buffer::interval i = allocate_send_buffer(17); + + std::copy(buf, buf + 5, i.begin); + i.begin += 5; + + // index + detail::write_int32(r.piece, i.begin); + // begin + detail::write_int32(r.start, i.begin); + // length + detail::write_int32(r.length, i.begin); + assert(i.begin == i.end); + + setup_send(); + } + + void bt_peer_connection::write_request(peer_request const& r) + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + assert(associated_torrent().lock()->valid_metadata()); + + char buf[] = {0,0,0,13, msg_request}; + + buffer::interval i = allocate_send_buffer(17); + + std::copy(buf, buf + 5, i.begin); + i.begin += 5; + + // index + detail::write_int32(r.piece, i.begin); + // begin + detail::write_int32(r.start, i.begin); + // length + detail::write_int32(r.length, i.begin); + assert(i.begin == i.end); + + setup_send(); + } + + void bt_peer_connection::write_bitfield(std::vector const& bitfield) + { + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + assert(m_sent_bitfield == false); + assert(t->valid_metadata()); + + int num_pieces = bitfield.size(); + int lazy_pieces[50]; + int num_lazy_pieces = 0; + int lazy_piece = 0; + + assert(t->is_seed() == (std::count(bitfield.begin(), bitfield.end(), true) == num_pieces)); + if (t->is_seed() && m_ses.settings().lazy_bitfields) + { + num_lazy_pieces = std::min(50, num_pieces / 10); + if (num_lazy_pieces < 1) num_lazy_pieces = 1; + for (int i = 0; i < num_pieces; ++i) + { + if (rand() % (num_pieces - i) >= num_lazy_pieces - lazy_piece) continue; + lazy_pieces[lazy_piece++] = i; + } + assert(lazy_piece == num_lazy_pieces); + lazy_piece = 0; + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> BITFIELD "; + + std::stringstream bitfield_string; + for (int i = 0; i < (int)get_bitfield().size(); ++i) + { + if (lazy_piece < num_lazy_pieces + && lazy_pieces[lazy_piece] == i) + { + bitfield_string << "0"; + ++lazy_piece; + continue; + } + if (bitfield[i]) bitfield_string << "1"; + else bitfield_string << "0"; + } + bitfield_string << "\n"; + (*m_logger) << bitfield_string.str(); + lazy_piece = 0; +#endif + const int packet_size = (num_pieces + 7) / 8 + 5; + + buffer::interval i = allocate_send_buffer(packet_size); + + detail::write_int32(packet_size - 4, i.begin); + detail::write_uint8(msg_bitfield, i.begin); + + std::fill(i.begin, i.end, 0); + for (int c = 0; c < num_pieces; ++c) + { + if (lazy_piece < num_lazy_pieces + && lazy_pieces[lazy_piece] == c) + { + ++lazy_piece; + continue; + } + if (bitfield[c]) + i.begin[c >> 3] |= 1 << (7 - (c & 7)); + } + assert(i.end - i.begin == (num_pieces + 7) / 8); + +#ifndef NDEBUG + m_sent_bitfield = true; +#endif + setup_send(); + + if (num_lazy_pieces > 0) + { + for (int i = 0; i < num_lazy_pieces; ++i) + { + write_have(lazy_pieces[i]); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> HAVE [ piece: " << lazy_pieces[i] << "]\n"; +#endif + } + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void bt_peer_connection::write_extensions() + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> EXTENSIONS\n"; +#endif + assert(m_supports_extensions); + + entry handshake(entry::dictionary_t); + entry extension_list(entry::dictionary_t); + + handshake["m"] = extension_list; + + // only send the port in case we bade the connection + // on incoming connections the other end already knows + // our listen port + if (is_local()) handshake["p"] = m_ses.listen_port(); + handshake["v"] = m_ses.settings().user_agent; + std::string remote_address; + std::back_insert_iterator out(remote_address); + detail::write_address(remote().address(), out); + handshake["ip"] = remote_address; + handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; + + // loop backwards, to make the first extension be the last + // to fill in the handshake (i.e. give the first extensions priority) + for (extension_list_t::reverse_iterator i = m_extensions.rbegin() + , end(m_extensions.rend()); i != end; ++i) + { + (*i)->add_handshake(handshake); + } + + std::vector msg; + bencode(std::back_inserter(msg), handshake); + + // make room for message + buffer::interval i = allocate_send_buffer(6 + msg.size()); + + // write the length of the message + detail::write_int32((int)msg.size() + 2, i.begin); + detail::write_uint8(msg_extended, i.begin); + // signal handshake message + detail::write_uint8(0, i.begin); + + std::copy(msg.begin(), msg.end(), i.begin); + i.begin += msg.size(); + assert(i.begin == i.end); + +#ifdef TORRENT_VERBOSE_LOGGING + std::stringstream ext; + handshake.print(ext); + (*m_logger) << "==> EXTENDED HANDSHAKE: \n" << ext.str(); +#endif + + setup_send(); + } +#endif + + void bt_peer_connection::write_choke() + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + if (is_choked()) return; + char msg[] = {0,0,0,1,msg_choke}; + send_buffer(msg, msg + sizeof(msg)); + } + + void bt_peer_connection::write_unchoke() + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + char msg[] = {0,0,0,1,msg_unchoke}; + send_buffer(msg, msg + sizeof(msg)); + } + + void bt_peer_connection::write_interested() + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + char msg[] = {0,0,0,1,msg_interested}; + send_buffer(msg, msg + sizeof(msg)); + } + + void bt_peer_connection::write_not_interested() + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + char msg[] = {0,0,0,1,msg_not_interested}; + send_buffer(msg, msg + sizeof(msg)); + } + + void bt_peer_connection::write_have(int index) + { + INVARIANT_CHECK; + assert(associated_torrent().lock()->valid_metadata()); + assert(index >= 0); + assert(index < associated_torrent().lock()->torrent_file().num_pieces()); + assert(m_sent_bitfield == true); + + const int packet_size = 9; + char msg[packet_size] = {0,0,0,5,msg_have}; + char* ptr = msg + 5; + detail::write_int32(index, ptr); + send_buffer(msg, msg + packet_size); + } + + void bt_peer_connection::write_piece(peer_request const& r, char const* buffer) + { + INVARIANT_CHECK; + assert(m_sent_bitfield == true); + + const int packet_size = 4 + 5 + 4 + r.length; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + buffer::interval i = allocate_send_buffer(packet_size); + + detail::write_int32(packet_size-4, i.begin); + detail::write_uint8(msg_piece, i.begin); + detail::write_int32(r.piece, i.begin); + detail::write_int32(r.start, i.begin); + std::memcpy(i.begin, buffer, r.length); + + assert(i.begin + r.length == i.end); + + m_payloads.push_back(range(send_buffer_size() - r.length, r.length)); + setup_send(); + } + + namespace + { + struct match_peer_id + { + match_peer_id(peer_id const& id, peer_connection const* pc) + : m_id(id), m_pc(pc) + { assert(pc); } + + bool operator()(policy::peer const& p) const + { + return p.connection != m_pc + && p.connection + && p.connection->pid() == m_id + && !p.connection->pid().is_all_zeros() + && p.ip.address() == m_pc->remote().address(); + } + + peer_id const& m_id; + peer_connection const* m_pc; + }; + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + // throws exception when the client should be disconnected + void bt_peer_connection::on_receive(asio::error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) return; + boost::shared_ptr t = associated_torrent().lock(); + + if (in_handshake()) + m_statistics.received_bytes(0, bytes_transferred); + +#ifndef TORRENT_DISABLE_ENCRYPTION + assert(in_handshake() || !m_rc4_encrypted || m_encrypted); + if (m_rc4_encrypted && m_encrypted) + { + buffer::interval wr_buf = wr_recv_buffer(); + m_RC4_handler->decrypt((wr_buf.end - bytes_transferred), bytes_transferred); + } +#endif + + buffer::const_interval recv_buffer = receive_buffer(); + +#ifndef TORRENT_DISABLE_ENCRYPTION + // m_state is set to read_pe_dhkey in initial state + // (read_protocol_identifier) for incoming, or in constructor + // for outgoing + if (m_state == read_pe_dhkey) + { + assert (!m_encrypted); + assert (!m_rc4_encrypted); + assert (packet_size() == dh_key_len); + assert (recv_buffer == receive_buffer()); + + if (!packet_finished()) return; + + // write our dh public key. m_DH_key_exchange is + // initialized in write_pe1_2_dhkey() + if (!is_local()) + write_pe1_2_dhkey(); + + // read dh key, generate shared secret + m_DH_key_exchange->compute_secret (recv_buffer.begin); // TODO handle errors + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " received DH key\n"; +#endif + + // PadA/B can be a max of 512 bytes, and 20 bytes more for + // the sync hash (if incoming), or 8 bytes more for the + // encrypted verification constant (if outgoing). Instead + // of requesting the maximum possible, request the maximum + // possible to ensure we do not overshoot the standard + // handshake. + + if (is_local()) + { + m_state = read_pe_syncvc; + write_pe3_sync(); + + // initial payload is the standard handshake, this is + // always rc4 if sent here. m_rc4_encrypted is flagged + // again according to peer selection. + m_rc4_encrypted = true; + m_encrypted = true; + write_handshake(); + m_rc4_encrypted = false; + m_encrypted = false; + + // vc,crypto_select,len(pad),pad, encrypt(handshake) + // 8+4+2+0+handshake_len + reset_recv_buffer(8+4+2+0+handshake_len); + } + else + { + // already written dh key + m_state = read_pe_synchash; + // synchash,skeyhash,vc,crypto_provide,len(pad),pad,encrypt(handshake) + reset_recv_buffer(20+20+8+4+2+0+handshake_len); + } + assert(!packet_finished()); + return; + } + + // cannot fall through into + if (m_state == read_pe_synchash) + { + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(!is_local()); + assert(recv_buffer == receive_buffer()); + + if (recv_buffer.left() < 20) + { + if (packet_finished()) + { + throw protocol_error ("sync hash not found"); + } + // else + return; + } + + if (!m_sync_hash.get()) + { + assert(m_sync_bytes_read == 0); + hasher h; + + // compute synchash (hash('req1',S)) + h.update("req1", 4); + h.update(m_DH_key_exchange->get_secret(), dh_key_len); + + m_sync_hash.reset(new sha1_hash(h.final())); + } + + int syncoffset = get_syncoffset((char*)m_sync_hash->begin(), 20 + , recv_buffer.begin, recv_buffer.left()); + + // No sync + if (syncoffset == -1) + { + std::size_t bytes_processed = recv_buffer.left() - 20; + m_sync_bytes_read += bytes_processed; + if (m_sync_bytes_read >= 512) + throw protocol_error("sync hash not found within 532 bytes"); + + cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+20) - m_sync_bytes_read)); + + assert(!packet_finished()); + return; + } + // found complete sync + else + { + std::size_t bytes_processed = syncoffset + 20; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " sync point (hash) found at offset " + << m_sync_bytes_read + bytes_processed - 20 << "\n"; +#endif + m_state = read_pe_skey_vc; + // skey,vc - 28 bytes + m_sync_hash.reset(); + cut_receive_buffer(bytes_processed, 28); + } + } + + if (m_state == read_pe_skey_vc) + { + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(!is_local()); + assert(packet_size() == 28); + + if (!packet_finished()) return; + + recv_buffer = receive_buffer(); + + // only calls info_hash() on the torrent_handle's, which + // never throws. + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + std::vector active_torrents = m_ses.get_torrents(); + std::vector::const_iterator i; + hasher h; + sha1_hash skey_hash, obfs_hash; + + for (i = active_torrents.begin(); i != active_torrents.end(); ++i) + { + torrent_handle const& t_h = *i; // TODO possible errors + sha1_hash const& info_hash = t_h.info_hash(); + // TODO Does info_hash need to be checked for validity? + + h.reset(); + h.update("req2", 4); + h.update((char*)info_hash.begin(), 20); + + skey_hash = h.final(); + + h.reset(); + h.update("req3", 4); + h.update(m_DH_key_exchange->get_secret(), dh_key_len); + + obfs_hash = h.final(); + obfs_hash ^= skey_hash; + + if (std::equal (recv_buffer.begin, recv_buffer.begin + 20, + (char*)obfs_hash.begin())) + { + if (!t) + { + attach_to_torrent(info_hash); + t = associated_torrent().lock(); + assert(t); + } + + init_pe_RC4_handler(m_DH_key_exchange->get_secret(), info_hash); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " stream key found, torrent located.\n"; +#endif + continue; // TODO Check flow control with multiple torrents + } + } + + if (!m_RC4_handler.get()) + throw protocol_error("invalid streamkey identifier (info hash) in encrypted handshake"); + + // verify constant + buffer::interval wr_recv_buf = wr_recv_buffer(); + m_RC4_handler->decrypt(wr_recv_buf.begin + 20, 8); + wr_recv_buf.begin += 28; + + const char sh_vc[] = {0,0,0,0, 0,0,0,0}; + if (!std::equal(sh_vc, sh_vc+8, recv_buffer.begin + 20)) + { + throw protocol_error("unable to verify constant"); + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " verification constant found\n"; +#endif + m_state = read_pe_cryptofield; + reset_recv_buffer(4 + 2); + } + + // cannot fall through into + if (m_state == read_pe_syncvc) + { + assert(is_local()); + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(recv_buffer == receive_buffer()); + + if (recv_buffer.left() < 8) + { + if (packet_finished()) + { + throw protocol_error ("sync verification constant not found"); + } + // else + return; + } + + // generate the verification constant + if (!m_sync_vc.get()) + { + assert(m_sync_bytes_read == 0); + + m_sync_vc.reset (new char[8]); + std::fill(m_sync_vc.get(), m_sync_vc.get() + 8, 0); + m_RC4_handler->decrypt(m_sync_vc.get(), 8); + } + + assert(m_sync_vc.get()); + int syncoffset = get_syncoffset(m_sync_vc.get(), 8 + , recv_buffer.begin, recv_buffer.left()); + + // No sync + if (syncoffset == -1) + { + std::size_t bytes_processed = recv_buffer.left() - 8; + m_sync_bytes_read += bytes_processed; + if (m_sync_bytes_read >= 512) + throw protocol_error("sync verification constant not found within 520 bytes"); + + cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+8) - m_sync_bytes_read)); + + assert(!packet_finished()); + return; + } + // found complete sync + else + { + std::size_t bytes_processed = syncoffset + 8; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " sync point (verification constant) found at offset " + << m_sync_bytes_read + bytes_processed - 8 << "\n"; +#endif + cut_receive_buffer (bytes_processed, 4 + 2); + + // delete verification constant + m_sync_vc.reset(); + m_state = read_pe_cryptofield; + // fall through + } + } + + if (m_state == read_pe_cryptofield) // local/remote + { + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(packet_size() == 4+2); + + if (!packet_finished()) return; + + buffer::interval wr_buf = wr_recv_buffer(); + m_RC4_handler->decrypt(wr_buf.begin, packet_size()); + + recv_buffer = receive_buffer(); + + int crypto_field = detail::read_int32(recv_buffer.begin); + +#ifdef TORRENT_VERBOSE_LOGGING + if (!is_local()) + (*m_logger) << " crypto provide : [ "; + else + (*m_logger) << " crypto select : [ "; + + if (crypto_field & 0x01) + (*m_logger) << "plaintext "; + if (crypto_field & 0x02) + (*m_logger) << "rc4 "; + (*m_logger) << "]\n"; +#endif + + if (!is_local()) + { + int crypto_select = 0; + // select a crypto method + switch (m_ses.get_pe_settings().allowed_enc_level) + { + case (pe_settings::plaintext): + { + if (!(crypto_field & 0x01)) + throw protocol_error("plaintext not provided"); + crypto_select = 0x01; + } + break; + case (pe_settings::rc4): + { + if (!(crypto_field & 0x02)) + throw protocol_error("rc4 not provided"); + crypto_select = 0x02; + } + break; + case (pe_settings::both): + { + if (m_ses.get_pe_settings().prefer_rc4) + { + if (crypto_field & 0x02) + crypto_select = 0x02; + else if (crypto_field & 0x01) + crypto_select = 0x01; + } + else + { + if (crypto_field & 0x01) + crypto_select = 0x01; + else if (crypto_field & 0x02) + crypto_select = 0x02; + } + if (!crypto_select) + throw protocol_error("rc4/plaintext not provided"); + } + } // switch + + // write the pe4 step + write_pe4_sync(crypto_select); + } + else // is_local() + { + // check if crypto select is valid + pe_settings::enc_level const& allowed_enc_level = m_ses.get_pe_settings().allowed_enc_level; + + if (crypto_field == 0x02) + { + if (allowed_enc_level == pe_settings::plaintext) + throw protocol_error("rc4 selected by peer when not provided"); + m_rc4_encrypted = true; + } + else if (crypto_field == 0x01) + { + if (allowed_enc_level == pe_settings::rc4) + throw protocol_error("plaintext selected by peer when not provided"); + m_rc4_encrypted = false; + } + else + throw protocol_error("unsupported crypto method selected by peer"); + } + + int len_pad = detail::read_int16(recv_buffer.begin); + if (len_pad < 0 || len_pad > 512) + throw protocol_error("invalid pad length"); + + m_state = read_pe_pad; + if (!is_local()) + reset_recv_buffer(len_pad + 2); // len(IA) at the end of pad + else + { + if (len_pad == 0) + { + m_encrypted = true; + m_state = init_bt_handshake; + } + else + reset_recv_buffer(len_pad); + } + } + + if (m_state == read_pe_pad) + { + assert(!m_encrypted); + if (!packet_finished()) return; + + int pad_size = is_local() ? packet_size() : packet_size() - 2; + + buffer::interval wr_buf = wr_recv_buffer(); + m_RC4_handler->decrypt(wr_buf.begin, packet_size()); + + recv_buffer = receive_buffer(); + + if (!is_local()) + { + recv_buffer.begin += pad_size; + int len_ia = detail::read_int16(recv_buffer.begin); + + if (len_ia < 0) throw protocol_error("invalid len_ia in handshake"); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " len(IA) : " << len_ia << "\n"; +#endif + if (len_ia == 0) + { + // everything after this is Encrypt2 + m_encrypted = true; + m_state = init_bt_handshake; + } + else + { + m_state = read_pe_ia; + reset_recv_buffer(len_ia); + } + } + else // is_local() + { + // everything that arrives after this is Encrypt2 + m_encrypted = true; + m_state = init_bt_handshake; + } + } + + if (m_state == read_pe_ia) + { + assert(!is_local()); + assert(!m_encrypted); + + if (!packet_finished()) return; + + // ia is always rc4, so decrypt it + buffer::interval wr_buf = wr_recv_buffer(); + m_RC4_handler->decrypt(wr_buf.begin, packet_size()); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " decrypted ia : " << packet_size() << " bytes\n"; +#endif + + if (!m_rc4_encrypted) + { + m_RC4_handler.reset(); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " destroyed rc4 keys\n"; +#endif + } + + // everything that arrives after this is Encrypt2 + m_encrypted = true; + + m_state = read_protocol_identifier; + cut_receive_buffer(0, 20); + } + + if (m_state == init_bt_handshake) + { + assert(m_encrypted); + + // decrypt remaining received bytes + if (m_rc4_encrypted) + { + buffer::interval wr_buf = wr_recv_buffer(); + wr_buf.begin += packet_size(); + m_RC4_handler->decrypt(wr_buf.begin, wr_buf.left()); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " decrypted remaining " << wr_buf.left() << " bytes\n"; +#endif + } + else // !m_rc4_encrypted + { + m_RC4_handler.reset(); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " destroyed rc4 keys\n"; +#endif + } + + // payload stream, start with 20 handshake bytes + m_state = read_protocol_identifier; + reset_recv_buffer(20); + + // encrypted portion of handshake completed, toggle + // peer_info pe_support flag back to true + if (is_local() && + m_ses.get_pe_settings().out_enc_policy == pe_settings::enabled) + { + policy::peer* pi = peer_info_struct(); + assert(pi); + + pi->pe_support = true; + } + } + +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION + + if (m_state == read_protocol_identifier) + { + assert (packet_size() == 20); + + if (!packet_finished()) return; + recv_buffer = receive_buffer(); + + int packet_size = recv_buffer[0]; + const char protocol_string[] = "BitTorrent protocol"; + + if (packet_size != 19 || + !std::equal(recv_buffer.begin + 1, recv_buffer.begin + 19, protocol_string)) + { +#ifndef TORRENT_DISABLE_ENCRYPTION + if (!is_local() && m_ses.get_pe_settings().in_enc_policy == pe_settings::disabled) + throw protocol_error("encrypted incoming connections disabled"); + + // Don't attempt to perform an encrypted handshake + // within an encrypted connection + if (!m_encrypted && !is_local()) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " attempting encrypted connection\n"; +#endif + m_state = read_pe_dhkey; + cut_receive_buffer(0, dh_key_len); + assert(!packet_finished()); + return; + } + + assert ((!is_local() && m_encrypted) || is_local()); +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION + throw protocol_error("incorrect protocol identifier"); + } + +#ifndef TORRENT_DISABLE_ENCRYPTION + assert (m_state != read_pe_dhkey); + + if (!is_local() && + (m_ses.get_pe_settings().in_enc_policy == pe_settings::forced) && + !m_encrypted) + throw protocol_error("non encrypted incoming connections disabled"); +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " BitTorrent protocol\n"; +#endif + + m_state = read_info_hash; + reset_recv_buffer(28); + } + + // fall through + if (m_state == read_info_hash) + { + assert(packet_size() == 28); + + if (!packet_finished()) return; + recv_buffer = receive_buffer(); + + +#ifdef TORRENT_VERBOSE_LOGGING + for (int i=0; i < 8; ++i) + { + for (int j=0; j < 8; ++j) + { + if (recv_buffer[i] & (0x80 >> j)) (*m_logger) << "1"; + else (*m_logger) << "0"; + } + } + (*m_logger) << "\n"; + if (recv_buffer[7] & 0x01) + (*m_logger) << "supports DHT port message\n"; + if (recv_buffer[7] & 0x04) + (*m_logger) << "supports FAST extensions\n"; + if (recv_buffer[5] & 0x10) + (*m_logger) << "supports extensions protocol\n"; +#endif + +#ifndef DISABLE_EXTENSIONS + if ((recv_buffer[5] & 0x10)) + m_supports_extensions = true; +#endif + if (recv_buffer[7] & 0x01) + m_supports_dht_port = true; + + // ok, now we have got enough of the handshake. Is this connection + // attached to a torrent? + if (!t) + { + // now, we have to see if there's a torrent with the + // info_hash we got from the peer + sha1_hash info_hash; + std::copy(recv_buffer.begin + 8, recv_buffer.begin + 28 + , (char*)info_hash.begin()); + + attach_to_torrent(info_hash); + } + else + { + // verify info hash + if (!std::equal(recv_buffer.begin + 8, recv_buffer.begin + 28 + , (const char*)t->torrent_file().info_hash().begin())) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " received invalid info_hash\n"; +#endif + throw protocol_error("invalid info-hash in handshake"); + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " info_hash received\n"; +#endif + } + + t = associated_torrent().lock(); + assert(t); + + // if this is a local connection, we have already + // send the handshake + if (!is_local()) write_handshake(); + if (t->valid_metadata()) + write_bitfield(t->pieces()); + + assert(t->get_policy().has_connection(this)); + + m_state = read_peer_id; + reset_recv_buffer(20); + } + + // fall through + if (m_state == read_peer_id) + { + if (!t) + { + assert(!packet_finished()); // TODO + return; + } + assert(packet_size() == 20); + + if (!packet_finished()) return; + recv_buffer = receive_buffer(); + +#ifdef TORRENT_VERBOSE_LOGGING + { + peer_id tmp; + std::copy(recv_buffer.begin, recv_buffer.begin + 20, (char*)tmp.begin()); + std::stringstream s; + s << "received peer_id: " << tmp << " client: " << identify_client(tmp) << "\n"; + s << "as ascii: "; + for (peer_id::iterator i = tmp.begin(); i != tmp.end(); ++i) + { + if (std::isprint(*i)) s << *i; + else s << "."; + } + s << "\n"; + (*m_logger) << s.str(); + } +#endif + peer_id pid; + std::copy(recv_buffer.begin, recv_buffer.begin + 20, (char*)pid.begin()); + set_pid(pid); + + if (t->settings().allow_multiple_connections_per_ip) + { + // now, let's see if this connection should be closed + policy& p = t->get_policy(); + policy::iterator i = std::find_if(p.begin_peer(), p.end_peer() + , match_peer_id(pid, this)); + if (i != p.end_peer()) + { + assert(i->connection->pid() == pid); + // we found another connection with the same peer-id + // which connection should be closed in order to be + // sure that the other end closes the same connection? + // the peer with greatest peer-id is the one allowed to + // initiate connections. So, if our peer-id is greater than + // the others, we should close the incoming connection, + // if not, we should close the outgoing one. + if (pid < m_ses.get_peer_id() && is_local()) + { + i->connection->disconnect(); + } + else + { + throw protocol_error("duplicate peer-id, connection closed"); + } + } + } + + if (pid == m_ses.get_peer_id()) + { + throw protocol_error("closing connection to ourself"); + } + +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.get_dht_settings().service_port); +#endif + + m_client_version = identify_client(pid); + boost::optional f = client_fingerprint(pid); + if (f && std::equal(f->name, f->name + 2, "BC")) + { + // if this is a bitcomet client, lower the request queue size limit + if (m_max_out_request_queue > 50) m_max_out_request_queue = 50; + } + + // disconnect if the peer has the same peer-id as ourself + // since it most likely is ourself then + if (pid == m_ses.get_peer_id()) + throw std::runtime_error("closing connection to ourself"); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end;) + { + if (!(*i)->on_handshake()) + { + i = m_extensions.erase(i); + } + else + { + ++i; + } + } + + if (m_supports_extensions) write_extensions(); +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== HANDSHAKE\n"; +#endif + // consider this a successful connection, reset the failcount + if (peer_info_struct()) peer_info_struct()->failcount = 0; + +#ifndef TORRENT_DISABLE_ENCRYPTION + // Toggle pe_support back to false if this is a + // standard successful connection + if (is_local() && !m_encrypted && + m_ses.get_pe_settings().out_enc_policy == pe_settings::enabled) + { + policy::peer* pi = peer_info_struct(); + assert(pi); + + pi->pe_support = false; + } +#endif + + m_state = read_packet_size; + reset_recv_buffer(4); + + assert(!packet_finished()); + return; + } + + // cannot fall through into + if (m_state == read_packet_size) + { + // Make sure this is not fallen though into + assert (recv_buffer == receive_buffer()); + + if (!t) return; + m_statistics.received_bytes(0, bytes_transferred); + if (!packet_finished()) return; + + const char* ptr = recv_buffer.begin; + int packet_size = detail::read_int32(ptr); + + // don't accept packets larger than 1 MB + if (packet_size > 1024*1024 || packet_size < 0) + { + // packet too large + throw std::runtime_error("packet > 1 MB (" + + boost::lexical_cast( + (unsigned int)packet_size) + " bytes)"); + } + + if (packet_size == 0) + { + incoming_keepalive(); + // keepalive message + m_state = read_packet_size; + reset_recv_buffer(4); + } + else + { + m_state = read_packet; + reset_recv_buffer(packet_size); + } + assert(!packet_finished()); + return; + } + + if (m_state == read_packet) + { + assert(recv_buffer == receive_buffer()); + if (!t) return; + if (dispatch_message(bytes_transferred)) + { + m_state = read_packet_size; + reset_recv_buffer(4); + } + assert(!packet_finished()); + return; + } + + assert(!packet_finished()); + } + + // -------------------------- + // SEND DATA + // -------------------------- + + // throws exception when the client should be disconnected + void bt_peer_connection::on_sent(asio::error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) return; + + // manage the payload markers + int amount_payload = 0; + if (!m_payloads.empty()) + { + for (std::deque::iterator i = m_payloads.begin(); + i != m_payloads.end(); ++i) + { + i->start -= bytes_transferred; + if (i->start < 0) + { + if (i->start + i->length <= 0) + { + amount_payload += i->length; + } + else + { + amount_payload += -i->start; + i->length -= -i->start; + i->start = 0; + } + } + } + } + + // TODO: move the erasing into the loop above + // remove all payload ranges that has been sent + m_payloads.erase( + std::remove_if(m_payloads.begin(), m_payloads.end(), range_below_zero) + , m_payloads.end()); + + assert(amount_payload <= (int)bytes_transferred); + m_statistics.sent_bytes(amount_payload, bytes_transferred - amount_payload); + } + +#ifndef NDEBUG + void bt_peer_connection::check_invariant() const + { +#ifndef TORRENT_DISABLE_ENCRYPTION + assert( (bool(m_state != read_pe_dhkey) || m_DH_key_exchange.get()) + || !is_local()); + + assert(!m_rc4_encrypted || m_RC4_handler.get()); +#endif + if (!m_in_constructor) + peer_connection::check_invariant(); + + if (!m_payloads.empty()) + { + for (std::deque::const_iterator i = m_payloads.begin(); + i != m_payloads.end() - 1; ++i) + { + assert(i->start + i->length <= (i+1)->start); + } + } + } +#endif + +} + diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp new file mode 100644 index 000000000..f83baa196 --- /dev/null +++ b/libtorrent/src/connection_queue.cpp @@ -0,0 +1,169 @@ + +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/connection_queue.hpp" + +namespace libtorrent +{ + + connection_queue::connection_queue(io_service& ios): m_next_ticket(0) + , m_num_connecting(0) + , m_half_open_limit(0) + , m_timer(ios) + {} + + bool connection_queue::free_slots() const + { return m_num_connecting < m_half_open_limit || m_half_open_limit <= 0; } + + void connection_queue::enqueue(boost::function const& on_connect + , boost::function const& on_timeout + , time_duration timeout) + { + INVARIANT_CHECK; + + m_queue.push_back(entry()); + entry& e = m_queue.back(); + e.on_connect = on_connect; + e.on_timeout = on_timeout; + e.ticket = m_next_ticket; + e.timeout = timeout; + ++m_next_ticket; + try_connect(); + } + + void connection_queue::done(int ticket) + { + INVARIANT_CHECK; + + std::list::iterator i = std::find_if(m_queue.begin() + , m_queue.end(), boost::bind(&entry::ticket, _1) == ticket); + if (i == m_queue.end()) + { + // this might not be here in case on_timeout calls remove + return; + } + if (i->connecting) --m_num_connecting; + m_queue.erase(i); + try_connect(); + } + + void connection_queue::limit(int limit) + { m_half_open_limit = limit; } + + int connection_queue::limit() const + { return m_half_open_limit; } + +#ifndef NDEBUG + + void connection_queue::check_invariant() const + { + int num_connecting = 0; + for (std::list::const_iterator i = m_queue.begin(); + i != m_queue.end(); ++i) + { + if (i->connecting) ++num_connecting; + } + assert(num_connecting == m_num_connecting); + } + +#endif + + void connection_queue::try_connect() + { + INVARIANT_CHECK; + + if (!free_slots() || m_queue.empty()) + return; + + std::list::iterator i = std::find_if(m_queue.begin() + , m_queue.end(), boost::bind(&entry::connecting, _1) == false); + while (i != m_queue.end()) + { + assert(i->connecting == false); + ptime expire = time_now() + i->timeout; + if (m_num_connecting == 0) + { + m_timer.expires_at(expire); + m_timer.async_wait(boost::bind(&connection_queue::on_timeout, this, _1)); + } + i->connecting = true; + ++m_num_connecting; + i->expires = expire; + + INVARIANT_CHECK; + + entry& ent = *i; + ++i; + try { ent.on_connect(ent.ticket); } catch (std::exception&) {} + + if (!free_slots()) break; + i = std::find_if(i, m_queue.end(), boost::bind(&entry::connecting, _1) == false); + } + } + + void connection_queue::on_timeout(asio::error_code const& e) + { + INVARIANT_CHECK; + + assert(!e || e == asio::error::operation_aborted); + if (e) return; + + ptime next_expire = max_time(); + ptime now = time_now(); + for (std::list::iterator i = m_queue.begin(); + i != m_queue.end();) + { + if (i->connecting && i->expires < now) + { + boost::function on_timeout = i->on_timeout; + m_queue.erase(i++); + --m_num_connecting; + try { on_timeout(); } catch (std::exception&) {} + continue; + } + if (i->expires < next_expire) + next_expire = i->expires; + ++i; + } + if (next_expire < max_time()) + { + m_timer.expires_at(next_expire); + m_timer.async_wait(boost::bind(&connection_queue::on_timeout, this, _1)); + } + try_connect(); + } + +} + diff --git a/libtorrent/src/deluge_core.cpp b/libtorrent/src/deluge_core.cpp new file mode 100644 index 000000000..930c7d496 --- /dev/null +++ b/libtorrent/src/deluge_core.cpp @@ -0,0 +1,1439 @@ +/* + * Copyright 2006 Alon Zakai ('Kripken') + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the OpenSSL + * library. + * You must obey the GNU General Public License in all respects for all of + * the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete + * this exception statement from your version. If you delete this exception + * statement from all source files in the program, then also delete it here. + * + * Thank You: Some code portions were derived from BSD-licensed work by + * Arvid Norberg, and GPL-licensed work by Christophe Dumez + */ + +//------------------ +// TODO: +// +// The DHT capability requires UDP. We need to check that this port is in fact +// open, just like the normal TCP port for bittorrent. +// +//----------------- +#include + +#include +#include +#include + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/extensions/metadata_transfer.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +using namespace boost::filesystem; +using namespace libtorrent; + +//---------------- +// CONSTANTS +//---------------- + +#ifdef AMD64 +#define python_long int +#else +#define python_long long +#endif + +#define EVENT_NULL 0 +#define EVENT_FINISHED 1 +#define EVENT_PEER_ERROR 2 +#define EVENT_INVALID_REQUEST 3 +#define EVENT_FILE_ERROR 4 +#define EVENT_HASH_FAILED_ERROR 5 +#define EVENT_PEER_BAN_ERROR 6 +#define EVENT_FASTRESUME_REJECTED_ERROR 8 +#define EVENT_TRACKER 9 +#define EVENT_OTHER 10 + +#define STATE_QUEUED 0 +#define STATE_CHECKING 1 +#define STATE_CONNECTING 2 +#define STATE_DOWNLOADING_META 3 +#define STATE_DOWNLOADING 4 +#define STATE_FINISHED 5 +#define STATE_SEEDING 6 +#define STATE_ALLOCATING 7 + +#define DHT_ROUTER_PORT 6881 + +//----------------- +// TYPES +//----------------- + +typedef long unique_ID_t; +typedef std::vector filter_out_t; +typedef std::string torrent_name_t; + +struct torrent_t +{ + torrent_handle handle; + unique_ID_t unique_ID; +}; + +typedef std::vector torrents_t; +typedef torrents_t::iterator torrents_t_iterator; + +//--------------------------- +// MODULE-GLOBAL VARIABLES +//--------------------------- + +long M_unique_counter = 0; +session_settings *M_settings = NULL; +pe_settings *M_pe_settings = NULL; +proxy_settings *M_proxy_settings = NULL; +session *M_ses = NULL; +PyObject *M_constants = NULL; +ip_filter *M_the_filter = NULL; +torrents_t *M_torrents = NULL; + +//------------------------ +// Exception types & macro +//------------------------ + +static PyObject *DelugeError = NULL; +static PyObject *InvalidEncodingError = NULL; +static PyObject *FilesystemError = NULL; +static PyObject *DuplicateTorrentError = NULL; +static PyObject *InvalidTorrentError = NULL; + +#define RAISE_PTR(e,s) { printf("Raising error: %s\r\n", s); PyErr_SetString(e, s); return NULL; } +#define RAISE_INT(e,s) { printf("Raising error: %s\r\n", s); PyErr_SetString(e, s); return -1; } + +//--------------------- +// Internal functions +//--------------------- + +bool empty_name_check(const std::string & name) +{ + return 1; +} + + +long handle_exists(torrent_handle &handle) +{ + for (unsigned long i = 0; i < M_torrents->size(); i++) + if ((*M_torrents)[i].handle == handle) + return 1; + + return 0; +} + + +long get_torrent_index(torrent_handle &handle) +{ + for (unsigned long i = 0; i < M_torrents->size(); i++) + if ((*M_torrents)[i].handle == handle) + { + // printf("Found: %li\r\n", i); + return i; + } + + RAISE_INT(DelugeError, "Handle not found."); +} + + +long get_index_from_unique_ID(long unique_ID) +{ + assert(M_handles->size() == M_unique_IDs->size()); + + for (unsigned long i = 0; i < M_torrents->size(); i++) + if ((*M_torrents)[i].unique_ID == unique_ID) + return i; + + RAISE_INT(DelugeError, "No such unique_ID."); +} + + +long internal_add_torrent(std::string const& torrent_name, +float preferred_ratio, +bool compact_mode, +boost::filesystem::path const& save_path) +{ + + std::ifstream in(torrent_name.c_str(), std::ios_base::binary); + in.unsetf(std::ios_base::skipws); + entry e; + e = bdecode(std::istream_iterator(in), std::istream_iterator()); + + torrent_info t(e); + + entry resume_data; + try + { + std::stringstream s; + s << torrent_name << ".fastresume"; + boost::filesystem::ifstream resumeFile(s.str(), std::ios_base::binary); + resumeFile.unsetf(std::ios_base::skipws); + resume_data = bdecode(std::istream_iterator(resumeFile), + std::istream_iterator()); + } + catch (invalid_encoding&) + { + } + catch (boost::filesystem::filesystem_error&) {} + + // Create new torrent object + + torrent_t new_torrent; + + torrent_handle h = M_ses->add_torrent(t, save_path, resume_data, compact_mode, 16 * 1024); + // h.set_max_connections(60); // at some point we should use this + h.set_max_uploads(-1); + h.set_ratio(preferred_ratio); + new_torrent.handle = h; + + new_torrent.unique_ID = M_unique_counter; + M_unique_counter++; + + M_torrents->push_back(new_torrent); + + return (new_torrent.unique_ID); +} + + +void internal_remove_torrent(long index) +{ + assert(index < M_torrents->size()); + + torrent_handle& h = M_torrents->at(index).handle; + + M_ses->remove_torrent(h); + + torrents_t_iterator it = M_torrents->begin() + index; + M_torrents->erase(it); +} + + +long get_peer_index(tcp::endpoint addr, std::vector const& peers) +{ + long index = -1; + + for (unsigned long i = 0; i < peers.size(); i++) + if (peers[i].ip == addr) + index = i; + + return index; +} + + +// The following function contains code by Christophe Dumez and Arvid Norberg +void internal_add_files(torrent_info& t, +boost::filesystem::path const& p, +boost::filesystem::path const& l) +{ + // change default checker, perhaps? + boost::filesystem::path f(p / l); + if (is_directory(f)) + { + for (boost::filesystem::directory_iterator i(f), end; i != end; ++i) + internal_add_files(t, p, l / i->leaf()); + } else + t.add_file(l, file_size(f)); +} + + +long count_DHT_peers(entry &state) +{ + long num_peers = 0; + entry *nodes = state.find_key("nodes"); + if (nodes) + { + entry::list_type &peers = nodes->list(); + entry::list_type::const_iterator i; + i = peers.begin(); + + while (i != peers.end()) + { + num_peers++; + i++; + } + } + + return num_peers; +} + + +//===================== +// External functions +//===================== + +static PyObject *torrent_pre_init(PyObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "OOOOO", &DelugeError, + &InvalidEncodingError, + &FilesystemError, + &DuplicateTorrentError, + &InvalidTorrentError)) + return NULL; + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_init(PyObject *self, PyObject *args) +{ + printf("deluge_core; using libtorrent %s. Compiled with NDEBUG value: %d\r\n", + LIBTORRENT_VERSION, + NDEBUG); + + // Tell Boost that we are on *NIX, so bloody '.'s are ok inside a directory name! + boost::filesystem::path::default_name_check(empty_name_check); + + char *client_ID, *user_agent; + python_long v1,v2,v3,v4; + + if (!PyArg_ParseTuple(args, "siiiis", &client_ID, &v1, &v2, &v3, &v4, &user_agent)) + return NULL; + + M_settings = new session_settings; + M_ses = new session(fingerprint(client_ID, v1, v2, v3, v4)); + + M_torrents = new torrents_t; + M_torrents->reserve(10); // pretty cheap, just 10 + + // Init values + + M_settings->user_agent = std::string(user_agent); + + M_ses->set_max_half_open_connections(-1); + M_ses->set_download_rate_limit(-1); + M_ses->set_upload_rate_limit(-1); + + M_ses->set_settings(*M_settings); + M_ses->set_severity_level(alert::debug); + + M_ses->add_extension(&libtorrent::create_metadata_plugin); + + M_constants = Py_BuildValue("{s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i}", + "EVENT_NULL", EVENT_NULL, + "EVENT_FINISHED", EVENT_FINISHED, + "EVENT_PEER_ERROR", EVENT_PEER_ERROR, + "EVENT_INVALID_REQUEST", EVENT_INVALID_REQUEST, + "EVENT_FILE_ERROR", EVENT_FILE_ERROR, + "EVENT_HASH_FAILED_ERROR", EVENT_HASH_FAILED_ERROR, + "EVENT_PEER_BAN_ERROR", EVENT_PEER_BAN_ERROR, + "EVENT_FASTRESUME_REJECTED_ERROR", EVENT_FASTRESUME_REJECTED_ERROR, + "EVENT_TRACKER", EVENT_TRACKER, + "EVENT_OTHER", EVENT_OTHER, + "STATE_QUEUED", STATE_QUEUED, + "STATE_CHECKING", STATE_CHECKING, + "STATE_CONNECTING", STATE_CONNECTING, + "STATE_DOWNLOADING_META", STATE_DOWNLOADING_META, + "STATE_DOWNLOADING", STATE_DOWNLOADING, + "STATE_FINISHED", STATE_FINISHED, + "STATE_SEEDING", STATE_SEEDING, + "STATE_ALLOCATING", STATE_ALLOCATING); + + Py_INCREF(Py_None); return Py_None; +}; + +static PyObject *torrent_quit(PyObject *self, PyObject *args) +{ + M_settings->stop_tracker_timeout = 5; + M_ses->set_settings(*M_settings); + printf("core: removing torrents...\r\n"); + delete M_torrents; + printf("core: removing settings...\r\n"); + delete M_settings; + printf("core: shutting down session...\r\n"); + delete M_ses; // 100% CPU... + Py_DECREF(M_constants); + + printf("core shut down.\r\n"); + + Py_INCREF(Py_None); return Py_None; +}; + +static PyObject *torrent_save_fastresume(PyObject *self, PyObject *args) +{ + python_long unique_ID; + const char *torrent_name; + if (!PyArg_ParseTuple(args, "is", &unique_ID, &torrent_name)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + torrent_handle& h = M_torrents->at(index).handle; + // For valid torrents, save fastresume data + if (h.is_valid() && h.has_metadata()) + { + h.pause(); + + entry data = h.write_resume_data(); + + std::stringstream s; + s << torrent_name << ".fastresume"; + + boost::filesystem::ofstream out(s.str(), std::ios_base::binary); + + out.unsetf(std::ios_base::skipws); + + bencode(std::ostream_iterator(out), data); + + h.resume(); + + Py_INCREF(Py_None); return Py_None; + } else + RAISE_PTR(DelugeError, "Invalid handle or no metadata for fastresume."); +} + + +static PyObject *torrent_set_max_half_open(PyObject *self, PyObject *args) +{ + python_long arg; + if (!PyArg_ParseTuple(args, "i", &arg)) + return NULL; + + M_ses->set_max_half_open_connections(arg); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_set_download_rate_limit(PyObject *self, PyObject *args) +{ + python_long arg; + if (!PyArg_ParseTuple(args, "i", &arg)) + return NULL; + + // printf("Capping download to %d bytes per second\r\n", (int)arg); + M_ses->set_download_rate_limit(arg); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_set_upload_rate_limit(PyObject *self, PyObject *args) +{ + python_long arg; + if (!PyArg_ParseTuple(args, "i", &arg)) + return NULL; + + // printf("Capping upload to %d bytes per second\r\n", (int)arg); + M_ses->set_upload_rate_limit(arg); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_set_listen_on(PyObject *self, PyObject *args) +{ + PyObject *port_vec; + if (!PyArg_ParseTuple(args, "O", &port_vec)) + return NULL; + + M_ses->listen_on(std::make_pair( PyInt_AsLong(PyList_GetItem(port_vec, 0)), + PyInt_AsLong(PyList_GetItem(port_vec, 1))), ""); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_is_listening(PyObject *self, PyObject *args) +{ + long ret = (M_ses->is_listening() != 0); + + return Py_BuildValue("i", ret); +} + + +static PyObject *torrent_listening_port(PyObject *self, PyObject *args) +{ + return Py_BuildValue("i", (python_long)M_ses->listen_port()); +} + + +static PyObject *torrent_set_max_uploads(PyObject *self, PyObject *args) +{ + python_long max_up; + if (!PyArg_ParseTuple(args, "i", &max_up)) + return NULL; + + M_ses->set_max_uploads(max_up); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_set_max_connections(PyObject *self, PyObject *args) +{ + python_long max_conn; + if (!PyArg_ParseTuple(args, "i", &max_conn)) + return NULL; + + // printf("Setting max connections: %d\r\n", max_conn); + M_ses->set_max_connections(max_conn); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_add_torrent(PyObject *self, PyObject *args) +{ + const char *name, *save_dir; + python_long compact; + if (!PyArg_ParseTuple(args, "ssi", &name, &save_dir, &compact)) + return NULL; + + boost::filesystem::path save_dir_2 (save_dir, empty_name_check); + try + { + long ret = internal_add_torrent(name, 0, compact, save_dir_2); + if (PyErr_Occurred()) + return NULL; + else + return Py_BuildValue("i", ret); + } + catch (invalid_encoding&) + { RAISE_PTR(InvalidEncodingError, ""); } + catch (invalid_torrent_file&) + { RAISE_PTR(InvalidTorrentError, ""); } + catch (boost::filesystem::filesystem_error&) + { RAISE_PTR(FilesystemError, ""); } + catch (duplicate_torrent&) + { RAISE_PTR(DuplicateTorrentError, "libtorrent reports this is a duplicate torrent"); } +} + + +static PyObject *torrent_remove_torrent(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + internal_remove_torrent(index); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_get_num_torrents(PyObject *self, PyObject *args) +{ + return Py_BuildValue("i", M_torrents->size()); +} + + +static PyObject *torrent_reannounce(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + M_torrents->at(index).handle.force_reannounce(); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_pause(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + M_torrents->at(index).handle.pause(); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_resume(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + M_torrents->at(index).handle.resume(); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_get_torrent_state(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + torrent_t &t = M_torrents->at(index); + torrent_status s = t.handle.status(); + const torrent_info &i = t.handle.get_torrent_info(); + + std::vector peers; + t.handle.get_peer_info(peers); + + long connected_seeds = 0; + long connected_peers = 0; + long total_seeds = 0; + long total_peers = 0; + + for (unsigned long i = 0; i < peers.size(); i++) { + + connected_peers = s.num_peers - s.num_seeds; + + connected_seeds = s.num_seeds; + + total_seeds = s.num_complete != -1? s.num_complete : connected_seeds; + + total_peers = s.num_incomplete != -1? s.num_incomplete : connected_peers; + } + + return Py_BuildValue("{s:s,s:i,s:i,s:l,s:l,s:f,s:f,s:f,s:L,s:L,s:b,s:s,s:s,s:f,s:L,s:L,s:l,s:i,s:i,s:L,s:L,s:i,s:l,s:l,s:b,s:b,s:L,s:L,s:L}", + "name", t.handle.get_torrent_info().name().c_str(), + "num_files", t.handle.get_torrent_info().num_files(), + "state", s.state, + "num_peers", connected_peers, + "num_seeds", connected_seeds, + "distributed_copies", s.distributed_copies, + "download_rate", s.download_rate, + "upload_rate", s.upload_rate, + "total_download", s.total_download, + "total_upload", s.total_upload, + "tracker_ok", !s.current_tracker.empty(), + "next_announce", boost::posix_time::to_simple_string(s.next_announce).c_str(), + "tracker", s.current_tracker.c_str(), + "progress", s.progress, + "total_payload_download", s.total_payload_download, + "total_payload_upload", s.total_payload_upload, + "pieces", long(s.pieces), // this is really a std::vector* + "pieces_done", s.num_pieces, + "block_size", s.block_size, + "total_size", i.total_size(), + "piece_length", i.piece_length(), + "num_pieces", i.num_pieces(), + "total_peers", total_peers, + "total_seeds", total_seeds, + "is_paused", t.handle.is_paused(), + "is_seed", t.handle.is_seed(), + "total_done", s.total_done, + "total_wanted", s.total_wanted, + "total_wanted_done", s.total_wanted_done); +}; + +static PyObject *torrent_pop_event(PyObject *self, PyObject *args) +{ + std::auto_ptr a; + + a = M_ses->pop_alert(); + + alert *popped_alert = a.get(); + + if (!popped_alert) + { + Py_INCREF(Py_None); return Py_None; + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i}", "event_type", EVENT_FINISHED, + "unique_ID", + M_torrents->at(index).unique_ID); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + peer_id peer_ID = (dynamic_cast(popped_alert))->pid; + std::string peer_IP = + (dynamic_cast(popped_alert))->ip.address().to_string(); + + return Py_BuildValue("{s:i,s:s,s:s,s:s}", "event_type", EVENT_PEER_ERROR, + "client_ID", identify_client(peer_ID).c_str(), + "ip", peer_IP.c_str(), + "message", a->msg().c_str()); + } else if (dynamic_cast(popped_alert)) + { + peer_id peer_ID = (dynamic_cast(popped_alert))->pid; + + return Py_BuildValue("{s:i,s:s,s:s}", + "event_type", EVENT_INVALID_REQUEST, + "client_ID", identify_client(peer_ID).c_str(), + "message", a->msg().c_str()); + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s}", + "event_type", EVENT_FILE_ERROR, + "unique_ID", M_torrents->at(index).unique_ID, + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:i,s:s}", + "event_type", EVENT_HASH_FAILED_ERROR, + "unique_ID", M_torrents->at(index).unique_ID, + "piece_index", + long((dynamic_cast(popped_alert))->piece_index), + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + std::string peer_IP = (dynamic_cast(popped_alert))->ip.address().to_string(); + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s,s:s}", + "event_type", EVENT_PEER_BAN_ERROR, + "unique_ID", M_torrents->at(index).unique_ID, + "ip", peer_IP.c_str(), + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s}", + "event_type", EVENT_FASTRESUME_REJECTED_ERROR, + "unique_ID", M_torrents->at(index).unique_ID, + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s,s:s}", + "event_type", EVENT_TRACKER, + "unique_ID", + M_torrents->at(index).unique_ID, + "tracker_status", "Announce sent", + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s,s:s}", + "event_type", EVENT_TRACKER, + "unique_ID", + M_torrents->at(index).unique_ID, + "tracker_status", "Bad response (status code=?)", + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s,s:s}", + "event_type", EVENT_TRACKER, + "unique_ID", + M_torrents->at(index).unique_ID, + "tracker_status", "Announce succeeded", + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } else if (dynamic_cast(popped_alert)) + { + torrent_handle handle = (dynamic_cast(popped_alert))->handle; + long index = get_torrent_index(handle); + if (PyErr_Occurred()) + return NULL; + + if (handle_exists(handle)) + return Py_BuildValue("{s:i,s:i,s:s,s:s}", + "event_type", EVENT_TRACKER, + "unique_ID", + M_torrents->at(index).unique_ID, + "tracker_status", "Warning in response", + "message", a->msg().c_str()); + else + { Py_INCREF(Py_None); return Py_None; } + } + + return Py_BuildValue("{s:i,s:s}", "event_type", EVENT_OTHER, + "message", a->msg().c_str() ); +} + + +static PyObject *torrent_get_session_info(PyObject *self, PyObject *args) +{ + session_status s = M_ses->status(); + + return Py_BuildValue("{s:l,s:f,s:f,s:f,s:f,s:l}", + "has_incoming_connections", long(s.has_incoming_connections), + "upload_rate", float(s.upload_rate), + "download_rate", float(s.download_rate), + "payload_upload_rate", float(s.payload_upload_rate), + "payload_download_rate", float(s.payload_download_rate), + "num_peers", long(s.num_peers)); +} + + +static PyObject *torrent_get_peer_info(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + std::vector peers; + M_torrents->at(index).handle.get_peer_info(peers); + + PyObject *peer_info; + PyObject *ret = PyTuple_New(peers.size()); + PyObject *curr_piece, *py_pieces; + + for (unsigned long i = 0; i < peers.size(); i++) + { + std::vector &pieces = peers[i].pieces; + unsigned long pieces_had = 0; + + py_pieces = PyTuple_New(pieces.size()); + + for (unsigned long piece = 0; piece < pieces.size(); piece++) + { + if (pieces[piece]) + pieces_had++; + + curr_piece = Py_BuildValue("i", long(pieces[piece])); + PyTuple_SetItem(py_pieces, piece, curr_piece); + } + + peer_info = Py_BuildValue( + "{s:f,s:L,s:f,s:L,s:i,s:i,s:b,s:b,s:b,s:b,s:b,s:b,s:b,s:b,s:b,s:s,s:b,s:s,s:f,s:O,s:b,s:b}", + "download_speed", peers[i].down_speed, + "total_download", peers[i].total_download, + "upload_speed", peers[i].up_speed, + "total_upload", peers[i].total_upload, + "download_queue_length", peers[i].download_queue_length, + "upload_queue_length", peers[i].upload_queue_length, + "is_interesting", ((peers[i].flags & peer_info::interesting) != 0), + "is_choked", ((peers[i].flags & peer_info::choked) != 0), + "is_remote_interested", ((peers[i].flags & peer_info::remote_interested) != 0), + "is_remote_choked", ((peers[i].flags & peer_info::remote_choked) != 0), + "supports_extensions", ((peers[i].flags & peer_info::supports_extensions) != 0), + "is_local_connection", ((peers[i].flags & peer_info::local_connection) != 0), + "is_awaiting_handshake", ((peers[i].flags & peer_info::handshake) != 0), + "is_connecting", ((peers[i].flags & peer_info::connecting) != 0), + "is_queued", ((peers[i].flags & peer_info::queued) != 0), + "client", peers[i].client.c_str(), + "is_seed", ((peers[i].flags & peer_info::seed) != 0), + "ip", peers[i].ip.address().to_string().c_str(), + "peer_has", float(float(pieces_had)*100.0/pieces.size()), + "pieces", py_pieces, + "rc4_encrypted", ((peers[i].flags & peer_info::rc4_encrypted) != 0), + "plaintext_encrypted", ((peers[i].flags & peer_info::plaintext_encrypted) != 0) + ); + + Py_DECREF(py_pieces); // Assuming the previous line does NOT steal the ref, then this is + // needed! + + PyTuple_SetItem(ret, i, peer_info); + }; + + return ret; +}; + +static PyObject *torrent_get_file_info(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + std::vector temp_files; + + PyObject *file_info; + + std::vector progresses; + + torrent_t &t = M_torrents->at(index); + t.handle.file_progress(progresses); + + torrent_info::file_iterator start = + t.handle.get_torrent_info().begin_files(); + torrent_info::file_iterator end = + t.handle.get_torrent_info().end_files(); + + long fileIndex = 0; + + for(torrent_info::file_iterator i = start; i != end; ++i) + { + file_entry const &currFile = (*i); + + file_info = Py_BuildValue( + "{s:s,s:d,s:d,s:f}", + "path", currFile.path.string().c_str(), + "offset", double(currFile.offset), + "size", double(currFile.size), + "progress", progresses[i - start]*100.0 + ); + + fileIndex++; + + temp_files.push_back(file_info); + }; + + PyObject *ret = PyTuple_New(temp_files.size()); + + for (unsigned long i = 0; i < temp_files.size(); i++) + PyTuple_SetItem(ret, i, temp_files[i]); + + return ret; +}; + +static PyObject *torrent_set_filter_out(PyObject *self, PyObject *args) +{ + python_long unique_ID; + PyObject *filter_out_object; + if (!PyArg_ParseTuple(args, "iO", &unique_ID, &filter_out_object)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + torrent_t &t = M_torrents->at(index); + long num_files = t.handle.get_torrent_info().num_files(); + assert(PyList_Size(filter_out_object) == num_files); + + filter_out_t filter_out(num_files); + + for (long i = 0; i < num_files; i++) + { + filter_out.at(i) = + PyInt_AsLong(PyList_GetItem(filter_out_object, i)); + }; + + t.handle.filter_files(filter_out); + + Py_INCREF(Py_None); return Py_None; +} + + +/*static PyObject *torrent_get_unique_IDs(PyObject *self, PyObject *args) +{ + PyObject *ret = PyTuple_New(M_torrents.size()); + PyObject *temp; + + for (unsigned long i = 0; i < M_torrents.size(); i++) + { + temp = Py_BuildValue("i", M_torrents->at(i).unique_ID) + + PyTuple_SetItem(ret, i, temp); + }; + + return ret; +};*/ + +static PyObject *torrent_constants(PyObject *self, PyObject *args) +{ + Py_INCREF(M_constants); return M_constants; +} + + +static PyObject *torrent_start_DHT(PyObject *self, PyObject *args) +{ + const char *DHT_path; + if (!PyArg_ParseTuple(args, "s", &DHT_path)) + return NULL; + + // printf("Loading DHT state from %s\r\n", DHT_path); + + boost::filesystem::path tempPath(DHT_path, empty_name_check); + boost::filesystem::ifstream DHT_state_file(tempPath, std::ios_base::binary); + DHT_state_file.unsetf(std::ios_base::skipws); + + entry DHT_state; + try + { + DHT_state = bdecode(std::istream_iterator(DHT_state_file), + std::istream_iterator()); + M_ses->start_dht(DHT_state); + // printf("DHT state recovered.\r\n"); + + // // Print out the state data from the FILE (not the session!) + // printf("Number of DHT peers in recovered state: %ld\r\n", count_DHT_peers(DHT_state)); + + } + catch (std::exception&) + { + printf("No DHT file to resume\r\n"); + M_ses->start_dht(); + } + + M_ses->add_dht_router(std::make_pair(std::string("router.bittorrent.com"), + DHT_ROUTER_PORT)); + M_ses->add_dht_router(std::make_pair(std::string("router.utorrent.com"), + DHT_ROUTER_PORT)); + M_ses->add_dht_router(std::make_pair(std::string("router.bitcomet.com"), + DHT_ROUTER_PORT)); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_stop_DHT(PyObject *self, PyObject *args) +{ + const char *DHT_path; + if (!PyArg_ParseTuple(args, "s", &DHT_path)) + return NULL; + + // printf("Saving DHT state to %s\r\n", DHT_path); + + boost::filesystem::path tempPath = boost::filesystem::path(DHT_path, empty_name_check); + + try + { + entry DHT_state = M_ses->dht_state(); + + // printf("Number of DHT peers in state, saving: %ld\r\n", count_DHT_peers(DHT_state)); + + boost::filesystem::ofstream out(tempPath, std::ios_base::binary); + out.unsetf(std::ios_base::skipws); + bencode(std::ostream_iterator(out), DHT_state); + } + catch (std::exception& e) + { + printf("An error occured in saving DHT\r\n"); + std::cerr << e.what() << "\n"; + } + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_get_DHT_info(PyObject *self, PyObject *args) +{ + entry DHT_state = M_ses->dht_state(); + + return Py_BuildValue("l", python_long(count_DHT_peers(DHT_state))); + + /* + // DHT_state.print(cout); + entry *nodes = DHT_state.find_key("nodes"); + if (!nodes) + return Py_BuildValue("l", -1); // No nodes - we are just starting up... + + entry::list_type &peers = nodes->list(); + entry::list_type::const_iterator i; + + python_long num_peers = 0; + + i = peers.begin(); + while (i != peers.end()) + { + num_peers++; + i++; + } + + return Py_BuildValue("l", num_peers); + */ +} + + +// Create Torrents: call with something like: +// create_torrent("mytorrent.torrent", "directory or file to make a torrent out of", +// "tracker1\ntracker2\ntracker3", "no comment", 256, "Deluge"); +// That makes a torrent with pieces of 256K, with "Deluge" as the creator string. +// +// The following function contains code by Christophe Dumez and Arvid Norberg +static PyObject *torrent_create_torrent(PyObject *self, PyObject *args) +{ + using namespace libtorrent; + using namespace boost::filesystem; + + //path::default_name_check(no_check); + + char *destination, *comment, *creator_str, *input, *trackers; + python_long piece_size; + if (!PyArg_ParseTuple(args, "ssssis", + &destination, &input, &trackers, &comment, &piece_size, &creator_str)) + return NULL; + + piece_size = piece_size * 1024; + + try + { + torrent_info t; + boost::filesystem::path full_path = complete(boost::filesystem::path(input)); + boost::filesystem::ofstream out(complete(boost::filesystem::path(destination)), std::ios_base::binary); + + internal_add_files(t, full_path.branch_path(), full_path.leaf()); + t.set_piece_size(piece_size); + + file_pool fp; + boost::scoped_ptr st(default_storage_constructor(t, full_path.branch_path(), fp)); + + std::string stdTrackers(trackers); + unsigned long index = 0, next = stdTrackers.find("\n"); + while (1 == 1) + { + t.add_tracker(stdTrackers.substr(index, next-index)); + index = next + 1; + if (next >= stdTrackers.length()) + break; + next = stdTrackers.find("\n", index); + if (next == std::string::npos) + break; + } + + int num = t.num_pieces(); + std::vector buf(piece_size); + for (int i = 0; i < num; ++i) + { + st->read(&buf[0], i, 0, t.piece_size(i)); + hasher h(&buf[0], t.piece_size(i)); + t.set_hash(i, h.final()); + } + + t.set_creator(creator_str); + t.set_comment(comment); + + entry e = t.create_torrent(); + bencode(std::ostream_iterator(out), e); + return Py_BuildValue("l", 1); + } catch (std::exception& e) + { + // std::cerr << e.what() << "\n"; + // return Py_BuildValue("l", 0); + RAISE_PTR(DelugeError, e.what()); + return Py_BuildValue("l", 0); + } +} + + +static PyObject *torrent_reset_IP_filter(PyObject *self, PyObject *args) +{ + // Remove existing filter, if there is one + if (M_the_filter != NULL) + delete M_the_filter; + + M_the_filter = new ip_filter(); + + M_ses->set_ip_filter(*M_the_filter); + + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_add_range_to_IP_filter(PyObject *self, PyObject *args) +{ + if (M_the_filter == NULL) { + RAISE_PTR(DelugeError, "No filter defined, use reset_IP_filter"); + } + + char *start, *end; + if (!PyArg_ParseTuple(args, "ss", &start, &end)) + return NULL; + + address_v4 inet_start = address_v4::from_string(start); + address_v4 inet_end = address_v4::from_string(end); + M_the_filter->add_rule(inet_start, inet_end, ip_filter::blocked); + + Py_INCREF(Py_None); return Py_None; +} + +static PyObject *torrent_use_upnp(PyObject *self, PyObject *args) +{ + python_long action; + PyArg_ParseTuple(args, "i", &action); + + if (action){ + M_ses->start_upnp(); + } + else{ + M_ses->stop_upnp(); + } + + Py_INCREF(Py_None); return Py_None; + +} + +static PyObject *torrent_use_natpmp(PyObject *self, PyObject *args) +{ + python_long action; + + PyArg_ParseTuple(args, "i", &action); + + if (action){ + M_ses->start_natpmp(); + } + else{ + M_ses->stop_natpmp(); + } + + Py_INCREF(Py_None); return Py_None; +} + +static PyObject *torrent_use_utpex(PyObject *self, PyObject *args) +{ + python_long action; + + PyArg_ParseTuple(args, "i", &action); + + if (action){ + M_ses->add_extension(&libtorrent::create_ut_pex_plugin); + } + + Py_INCREF(Py_None); return Py_None; +} + +static PyObject *torrent_pe_settings(PyObject *self, PyObject *args) +{ + M_pe_settings = new pe_settings(); + libtorrent::pe_settings::enc_policy out, in, prefer; + libtorrent::pe_settings::enc_level level; + + PyArg_ParseTuple(args, "iiii", &out, &in, &level, &prefer); + + M_pe_settings->out_enc_policy = out; + M_pe_settings->in_enc_policy = in; + M_pe_settings->allowed_enc_level = level; + M_pe_settings->prefer_rc4 = prefer; + + M_ses->set_pe_settings(*M_pe_settings); + + return Py_None; +} + +static PyObject *torrent_set_ratio(PyObject *self, PyObject *args) +{ + python_long unique_ID; + float num; + if (!PyArg_ParseTuple(args, "if", &unique_ID, &num)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + M_torrents->at(index).handle.set_ratio(num); + + Py_INCREF(Py_None); return Py_None; +} + +static PyObject *torrent_proxy_settings(PyObject *self, PyObject *args) +{ + M_proxy_settings = new proxy_settings(); + + char *server, *login, *pasw; + int portnum; + libtorrent::proxy_settings::proxy_type proxytype; + bool peerproxy, trackerproxy, dhtproxy; + + PyArg_ParseTuple(args, "sssiibbb", &server, &login, &pasw, &portnum, &proxytype, &peerproxy, &trackerproxy, &dhtproxy); + + M_proxy_settings->type = proxytype; + M_proxy_settings->username = login; + M_proxy_settings->password = pasw; + M_proxy_settings->hostname = server; + M_proxy_settings->port = portnum; + + if (peerproxy) { + M_ses->set_peer_proxy(*M_proxy_settings); + } + + if (trackerproxy) { + M_ses->set_tracker_proxy(*M_proxy_settings); + } + + if (dhtproxy) { + M_ses->set_dht_proxy(*M_proxy_settings); + } + + return Py_None; +} + +static PyObject *torrent_get_trackers(PyObject *self, PyObject *args) +{ + python_long unique_ID; + if (!PyArg_ParseTuple(args, "i", &unique_ID)) + return NULL; + + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + torrent_handle& h = M_torrents->at(index).handle; + std::string trackerslist; + if (h.is_valid() && h.has_metadata()) + { + for (std::vector::const_iterator i = h.trackers().begin(); + i != h.trackers().end(); ++i) + { + trackerslist = trackerslist + i->url +"\n"; + } + } + return Py_BuildValue("s",trackerslist.c_str()); +} + +static PyObject *torrent_replace_trackers(PyObject *self, PyObject *args) +{ + python_long unique_ID; + const char* tracker; + if (!PyArg_ParseTuple(args, "iz", &unique_ID, &tracker)) + return NULL; + long index = get_index_from_unique_ID(unique_ID); + if (PyErr_Occurred()) + return NULL; + + torrent_handle& h = M_torrents->at(index).handle; + + std::vector trackerlist; + + std::istringstream trackers(tracker); + std::string line; + + while (std::getline(trackers, line)) { + libtorrent::announce_entry a_entry(line); + trackerlist.push_back(a_entry); + } + h.replace_trackers(trackerlist); + h.force_reannounce(); + return Py_None; +} +//==================== +// Python Module data +//==================== + +static PyMethodDef deluge_core_methods[] = +{ + {"pe_settings", torrent_pe_settings, METH_VARARGS, "."}, + {"pre_init", torrent_pre_init, METH_VARARGS, "."}, + {"init", torrent_init, METH_VARARGS, "."}, + {"quit", torrent_quit, METH_VARARGS, "."}, + {"save_fastresume", torrent_save_fastresume, METH_VARARGS, "."}, + {"set_max_half_open", torrent_set_max_half_open, METH_VARARGS, "."}, + {"set_download_rate_limit", torrent_set_download_rate_limit, METH_VARARGS, "."}, + {"set_upload_rate_limit", torrent_set_upload_rate_limit, METH_VARARGS, "."}, + {"set_listen_on", torrent_set_listen_on, METH_VARARGS, "."}, + {"is_listening", torrent_is_listening, METH_VARARGS, "."}, + {"listening_port", torrent_listening_port, METH_VARARGS, "."}, + {"set_max_uploads", torrent_set_max_uploads, METH_VARARGS, "."}, + {"set_max_connections", torrent_set_max_connections, METH_VARARGS, "."}, + {"add_torrent", torrent_add_torrent, METH_VARARGS, "."}, + {"remove_torrent", torrent_remove_torrent, METH_VARARGS, "."}, + {"get_num_torrents", torrent_get_num_torrents, METH_VARARGS, "."}, + {"reannounce", torrent_reannounce, METH_VARARGS, "."}, + {"pause", torrent_pause, METH_VARARGS, "."}, + {"resume", torrent_resume, METH_VARARGS, "."}, + {"get_torrent_state", torrent_get_torrent_state, METH_VARARGS, "."}, + {"pop_event", torrent_pop_event, METH_VARARGS, "."}, + {"get_session_info", torrent_get_session_info, METH_VARARGS, "."}, + {"get_peer_info", torrent_get_peer_info, METH_VARARGS, "."}, + {"get_file_info", torrent_get_file_info, METH_VARARGS, "."}, + {"set_filter_out", torrent_set_filter_out, METH_VARARGS, "."}, + {"constants", torrent_constants, METH_VARARGS, "."}, + {"start_DHT", torrent_start_DHT, METH_VARARGS, "."}, + {"stop_DHT", torrent_stop_DHT, METH_VARARGS, "."}, + {"get_DHT_info", torrent_get_DHT_info, METH_VARARGS, "."}, + {"create_torrent", torrent_create_torrent, METH_VARARGS, "."}, + {"reset_IP_filter", torrent_reset_IP_filter, METH_VARARGS, "."}, + {"add_range_to_IP_filter", torrent_add_range_to_IP_filter, METH_VARARGS, "."}, + {"use_upnp", torrent_use_upnp, METH_VARARGS, "."}, + {"use_natpmp", torrent_use_natpmp, METH_VARARGS, "."}, + {"use_utpex", torrent_use_utpex, METH_VARARGS, "."}, + {"set_ratio", torrent_set_ratio, METH_VARARGS, "."}, + {"proxy_settings", torrent_proxy_settings, METH_VARARGS, "."}, + {"get_trackers", torrent_get_trackers, METH_VARARGS, "."}, + {"replace_trackers", torrent_replace_trackers, METH_VARARGS, "."}, + {NULL} +}; + +PyMODINIT_FUNC +initdeluge_core(void) +{ + Py_InitModule("deluge_core", deluge_core_methods); +}; diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp new file mode 100644 index 000000000..5398d5ae8 --- /dev/null +++ b/libtorrent/src/disk_io_thread.cpp @@ -0,0 +1,245 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/storage.hpp" +#include +#include "libtorrent/disk_io_thread.hpp" + +namespace libtorrent +{ + + disk_io_thread::disk_io_thread(int block_size) + : m_abort(false) + , m_queue_buffer_size(0) + , m_pool(block_size) + , m_disk_io_thread(boost::ref(*this)) + {} + + disk_io_thread::~disk_io_thread() + { + boost::mutex::scoped_lock l(m_mutex); + m_abort = true; + m_signal.notify_all(); + l.unlock(); + + m_disk_io_thread.join(); + } + + // aborts read operations + void disk_io_thread::stop(boost::intrusive_ptr s) + { + boost::mutex::scoped_lock l(m_mutex); + // read jobs are aborted, write and move jobs are syncronized + for (std::deque::iterator i = m_jobs.begin(); + i != m_jobs.end();) + { + if (i->storage != s) + { + ++i; + continue; + } + if (i->action == disk_io_job::read) + { + i->callback(-1, *i); + m_jobs.erase(i++); + continue; + } + ++i; + } + m_signal.notify_all(); + } + + bool range_overlap(int start1, int length1, int start2, int length2) + { + return (start1 <= start2 && start1 + length1 > start2) + || (start2 <= start1 && start2 + length2 > start1); + } + + namespace + { + bool operator<(disk_io_job const& lhs, disk_io_job const& rhs) + { + if (lhs.storage.get() < rhs.storage.get()) return true; + if (lhs.storage.get() > rhs.storage.get()) return false; + if (lhs.piece < rhs.piece) return true; + if (lhs.piece > rhs.piece) return false; + if (lhs.offset < rhs.offset) return true; +// if (lhs.offset > rhs.offset) return false; + return false; + } + } + + void disk_io_thread::add_job(disk_io_job const& j + , boost::function const& f) + { + assert(!j.callback); + boost::mutex::scoped_lock l(m_mutex); + + std::deque::reverse_iterator i = m_jobs.rbegin(); + if (j.action == disk_io_job::read) + { + // when we're reading, we may not skip + // ahead of any write operation that overlaps + // the region we're reading + for (; i != m_jobs.rend(); ++i) + { + if (i->action == disk_io_job::read && *i < j) + break; + if (i->action == disk_io_job::write + && i->storage == j.storage + && i->piece == j.piece + && range_overlap(i->offset, i->buffer_size + , j.offset, j.buffer_size)) + { + // we have to stop, and we haven't + // found a suitable place for this job + // so just queue it up at the end + i = m_jobs.rbegin(); + break; + } + } + } + else if (j.action == disk_io_job::write) + { + for (; i != m_jobs.rend(); ++i) + { + if (i->action == disk_io_job::write && *i < j) + { + if (i != m_jobs.rbegin() + && i.base()->storage.get() != j.storage.get()) + i = m_jobs.rbegin(); + break; + } + } + } + + if (i == m_jobs.rend()) i = m_jobs.rbegin(); + + std::deque::iterator k = m_jobs.insert(i.base(), j); + k->callback.swap(const_cast&>(f)); + if (j.action == disk_io_job::write) + m_queue_buffer_size += j.buffer_size; + assert(j.storage.get()); + m_signal.notify_all(); + } + + char* disk_io_thread::allocate_buffer() + { + boost::mutex::scoped_lock l(m_mutex); + return (char*)m_pool.ordered_malloc(); + } + + void disk_io_thread::operator()() + { + for (;;) + { + boost::mutex::scoped_lock l(m_mutex); + while (m_jobs.empty() && !m_abort) + m_signal.wait(l); + if (m_abort && m_jobs.empty()) return; + + boost::function handler; + handler.swap(m_jobs.front().callback); + disk_io_job j = m_jobs.front(); + m_jobs.pop_front(); + m_queue_buffer_size -= j.buffer_size; + l.unlock(); + + int ret = 0; + + try + { +// std::cerr << "DISK THREAD: executing job: " << j.action << std::endl; + switch (j.action) + { + case disk_io_job::read: + l.lock(); + j.buffer = (char*)m_pool.ordered_malloc(); + l.unlock(); + if (j.buffer == 0) + { + ret = -1; + j.str = "out of memory"; + } + else + { + ret = j.storage->read_impl(j.buffer, j.piece, j.offset + , j.buffer_size); + + // simulates slow drives + // usleep(300); + } + break; + case disk_io_job::write: + assert(j.buffer); + j.storage->write_impl(j.buffer, j.piece, j.offset + , j.buffer_size); + + // simulates a slow drive + // usleep(300); + break; + case disk_io_job::hash: + { + sha1_hash h = j.storage->hash_for_piece_impl(j.piece); + j.str.resize(20); + std::memcpy(&j.str[0], &h[0], 20); + } + break; + case disk_io_job::move_storage: + ret = j.storage->move_storage_impl(j.str) ? 1 : 0; + break; + case disk_io_job::release_files: + j.storage->release_files_impl(); + break; + } + } + catch (std::exception& e) + { +// std::cerr << "DISK THREAD: exception: " << e.what() << std::endl; + j.str = e.what(); + ret = -1; + } + +// if (!handler) std::cerr << "DISK THREAD: no callback specified" << std::endl; +// else std::cerr << "DISK THREAD: invoking callback" << std::endl; + try { if (handler) handler(ret, j); } + catch (std::exception&) {} + + if (j.buffer) + { + l.lock(); + m_pool.ordered_free(j.buffer); + } + } + } +} + diff --git a/libtorrent/src/entry.cpp b/libtorrent/src/entry.cpp new file mode 100755 index 000000000..16dffc275 --- /dev/null +++ b/libtorrent/src/entry.cpp @@ -0,0 +1,350 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include "libtorrent/entry.hpp" +#include "libtorrent/config.hpp" + +#if defined(_MSC_VER) +namespace std +{ + using ::isprint; +} +#define for if (false) {} else for +#endif + +namespace +{ + template + void call_destructor(T* o) + { + assert(o); + o->~T(); + } + + struct compare_string + { + compare_string(char const* s): m_str(s) {} + + bool operator()( + std::pair const& e) const + { + return m_str && e.first == m_str; + } + char const* m_str; + }; +} + +namespace libtorrent +{ + namespace detail + { + TORRENT_EXPORT char const* integer_to_str(char* buf, int size, entry::integer_type val) + { + int sign = 0; + if (val < 0) + { + sign = 1; + val = -val; + } + buf[--size] = '\0'; + if (val == 0) buf[--size] = '0'; + for (; size > sign && val != 0;) + { + buf[--size] = '0' + char(val % 10); + val /= 10; + } + if (sign) buf[--size] = '-'; + return buf + size; + } + } + + entry& entry::operator[](char const* key) + { + dictionary_type::iterator i = dict().find(key); + if (i != dict().end()) return i->second; + dictionary_type::iterator ret = dict().insert( + dict().begin() + , std::make_pair(std::string(key), entry())); + return ret->second; + } + + + entry& entry::operator[](std::string const& key) + { + return (*this)[key.c_str()]; + } + + entry* entry::find_key(char const* key) + { + dictionary_type::iterator i = std::find_if( + dict().begin() + , dict().end() + , compare_string(key)); + if (i == dict().end()) return 0; + return &i->second; + + } + + entry const* entry::find_key(char const* key) const + { + dictionary_type::const_iterator i = dict().find(key); + if (i == dict().end()) return 0; + return &i->second; + } + + const entry& entry::operator[](char const* key) const + { + dictionary_type::const_iterator i = dict().find(key); + if (i == dict().end()) throw type_error( + (std::string("key not found: ") + key).c_str()); + return i->second; + } + + const entry& entry::operator[](std::string const& key) const + { + return (*this)[key.c_str()]; + } + + entry::entry(dictionary_type const& v) + { + new(data) dictionary_type(v); + m_type = dictionary_t; + } + + entry::entry(string_type const& v) + { + new(data) string_type(v); + m_type = string_t; + } + + entry::entry(list_type const& v) + { + new(data) list_type(v); + m_type = list_t; + } + + entry::entry(integer_type const& v) + { + new(data) integer_type(v); + m_type = int_t; + } + + void entry::operator=(dictionary_type const& v) + { + destruct(); + new(data) dictionary_type(v); + m_type = dictionary_t; + } + + void entry::operator=(string_type const& v) + { + destruct(); + new(data) string_type(v); + m_type = string_t; + } + + void entry::operator=(list_type const& v) + { + destruct(); + new(data) list_type(v); + m_type = list_t; + } + + void entry::operator=(integer_type const& v) + { + destruct(); + new(data) integer_type(v); + m_type = int_t; + } + + bool entry::operator==(entry const& e) const + { + if (m_type != e.m_type) return false; + + switch(m_type) + { + case int_t: + return integer() == e.integer(); + case string_t: + return string() == e.string(); + case list_t: + return list() == e.list(); + case dictionary_t: + return dict() == e.dict(); + default: + assert(m_type == undefined_t); + return true; + } + } + + void entry::construct(data_type t) + { + m_type = t; + switch(m_type) + { + case int_t: + new(data) integer_type; + break; + case string_t: + new(data) string_type; + break; + case list_t: + new(data) list_type; + break; + case dictionary_t: + new (data) dictionary_type; + break; + default: + assert(m_type == undefined_t); + m_type = undefined_t; + } + } + + void entry::copy(entry const& e) + { + m_type = e.m_type; + switch(m_type) + { + case int_t: + new(data) integer_type(e.integer()); + break; + case string_t: + new(data) string_type(e.string()); + break; + case list_t: + new(data) list_type(e.list()); + break; + case dictionary_t: + new (data) dictionary_type(e.dict()); + break; + default: + m_type = undefined_t; + } + } + + void entry::destruct() + { + switch(m_type) + { + case int_t: + call_destructor(reinterpret_cast(data)); + break; + case string_t: + call_destructor(reinterpret_cast(data)); + break; + case list_t: + call_destructor(reinterpret_cast(data)); + break; + case dictionary_t: + call_destructor(reinterpret_cast(data)); + break; + default: + assert(m_type == undefined_t); + break; + } + } + + void entry::swap(entry& e) + { + // not implemented + assert(false); + } + + void entry::print(std::ostream& os, int indent) const + { + assert(indent >= 0); + for (int i = 0; i < indent; ++i) os << " "; + switch (m_type) + { + case int_t: + os << integer() << "\n"; + break; + case string_t: + { + bool binary_string = false; + for (std::string::const_iterator i = string().begin(); i != string().end(); ++i) + { + if (!std::isprint(static_cast(*i))) + { + binary_string = true; + break; + } + } + if (binary_string) + { + os.unsetf(std::ios_base::dec); + os.setf(std::ios_base::hex); + for (std::string::const_iterator i = string().begin(); i != string().end(); ++i) + os << std::setfill('0') << std::setw(2) + << static_cast((unsigned char)*i); + os.unsetf(std::ios_base::hex); + os.setf(std::ios_base::dec); + os << "\n"; + } + else + { + os << string() << "\n"; + } + } break; + case list_t: + { + os << "list\n"; + for (list_type::const_iterator i = list().begin(); i != list().end(); ++i) + { + i->print(os, indent+1); + } + } break; + case dictionary_t: + { + os << "dictionary\n"; + for (dictionary_type::const_iterator i = dict().begin(); i != dict().end(); ++i) + { + for (int j = 0; j < indent+1; ++j) os << " "; + os << "[" << i->first << "]"; + if (i->second.type() != entry::string_t + && i->second.type() != entry::int_t) + os << "\n"; + else os << " "; + i->second.print(os, indent+2); + } + } break; + default: + os << "\n"; + } + } +} + diff --git a/libtorrent/src/escape_string.cpp b/libtorrent/src/escape_string.cpp new file mode 100755 index 000000000..02a4fa085 --- /dev/null +++ b/libtorrent/src/escape_string.cpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent +{ + std::string unescape_string(std::string const& s) + { + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) + { + if(*i == '+') + { + ret += ' '; + } + else if (*i != '%') + { + ret += *i; + } + else + { + ++i; + if (i == s.end()) + throw std::runtime_error("invalid escaped string"); + + int high; + if(*i >= '0' && *i <= '9') high = *i - '0'; + else if(*i >= 'A' && *i <= 'F') high = *i + 10 - 'A'; + else if(*i >= 'a' && *i <= 'f') high = *i + 10 - 'a'; + else throw std::runtime_error("invalid escaped string"); + + ++i; + if (i == s.end()) + throw std::runtime_error("invalid escaped string"); + + int low; + if(*i >= '0' && *i <= '9') low = *i - '0'; + else if(*i >= 'A' && *i <= 'F') low = *i + 10 - 'A'; + else if(*i >= 'a' && *i <= 'f') low = *i + 10 - 'a'; + else throw std::runtime_error("invalid escaped string"); + + ret += char(high * 16 + low); + } + } + return ret; + } + + + std::string escape_string(const char* str, int len) + { + assert(str != 0); + assert(len >= 0); + // http://www.ietf.org/rfc/rfc2396.txt + // section 2.3 + // some trackers seems to require that ' is escaped +// static const char unreserved_chars[] = "-_.!~*'()"; + static const char unreserved_chars[] = "-_.!~*()" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789"; + + std::stringstream ret; + ret << std::hex << std::setfill('0'); + for (int i = 0; i < len; ++i) + { + if (std::count( + unreserved_chars + , unreserved_chars+sizeof(unreserved_chars)-1 + , *str)) + { + ret << *str; + } + else + { + ret << '%' + << std::setw(2) + << (int)static_cast(*str); + } + ++str; + } + return ret.str(); + } + + std::string escape_path(const char* str, int len) + { + assert(str != 0); + assert(len >= 0); + static const char unreserved_chars[] = "/-_.!~*()" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789"; + + std::stringstream ret; + ret << std::hex << std::setfill('0'); + for (int i = 0; i < len; ++i) + { + if (std::count( + unreserved_chars + , unreserved_chars+sizeof(unreserved_chars)-1 + , *str)) + { + ret << *str; + } + else + { + ret << '%' + << std::setw(2) + << (int)static_cast(*str); + } + ++str; + } + return ret.str(); + } +} diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp new file mode 100755 index 000000000..515406a46 --- /dev/null +++ b/libtorrent/src/file.cpp @@ -0,0 +1,347 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _WIN32 +// windows part +#include "libtorrent/utf8.hpp" + +#include +#include +#include +#include + +#ifndef _MODE_T_ +typedef int mode_t; +#endif + +#ifdef UNICODE +#include "libtorrent/storage.hpp" +#endif + +#else +// unix part +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include + +#include +// make sure the _FILE_OFFSET_BITS define worked +// on this platform +BOOST_STATIC_ASSERT(sizeof(lseek(0, 0, 0)) >= 8); + +#endif + +#include +#include "libtorrent/file.hpp" +#include + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef O_RANDOM +#define O_RANDOM 0 +#endif + +#ifdef UNICODE +#include "libtorrent/storage.hpp" +#endif + + +namespace +{ + enum { mode_in = 1, mode_out = 2 }; + + mode_t map_open_mode(int m) + { + if (m == (mode_in | mode_out)) return O_RDWR | O_CREAT | O_BINARY | O_RANDOM; + if (m == mode_out) return O_WRONLY | O_CREAT | O_BINARY | O_RANDOM; + if (m == mode_in) return O_RDONLY | O_BINARY | O_RANDOM; + assert(false); + return 0; + } + +#ifdef WIN32 + std::string utf8_native(std::string const& s) + { + try + { + std::wstring ws; + libtorrent::utf8_wchar(s, ws); + std::size_t size = wcstombs(0, ws.c_str(), 0); + if (size == std::size_t(-1)) return s; + std::string ret; + ret.resize(size); + size = wcstombs(&ret[0], ws.c_str(), size + 1); + if (size == wchar_t(-1)) return s; + ret.resize(size); + return ret; + } + catch(std::exception) + { + return s; + } + } +#else + std::string utf8_native(std::string const& s) + { + return s; + } +#endif + +} + +namespace libtorrent +{ + + namespace fs = boost::filesystem; + + const file::open_mode file::in(mode_in); + const file::open_mode file::out(mode_out); + + const file::seek_mode file::begin(1); + const file::seek_mode file::end(2); + + struct file::impl + { + impl() + : m_fd(-1) + , m_open_mode(0) + {} + + impl(fs::path const& path, int mode) + : m_fd(-1) + , m_open_mode(0) + { + open(path, mode); + } + + ~impl() + { + close(); + } + + void open(fs::path const& path, int mode) + { + assert(path.is_complete()); + close(); +#if defined(_WIN32) && defined(UNICODE) + std::wstring wpath(safe_convert(path.native_file_string())); + m_fd = ::_wopen( + wpath.c_str() + , map_open_mode(mode) + , S_IREAD | S_IWRITE); +#else +#ifdef _WIN32 + m_fd = ::_open( +#else + m_fd = ::open( +#endif + utf8_native(path.native_file_string()).c_str() + , map_open_mode(mode) +#ifdef _WIN32 + , S_IREAD | S_IWRITE); +#else + , S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif +#endif + if (m_fd == -1) + { + std::stringstream msg; + msg << "open failed: '" << path.native_file_string() << "'. " + << strerror(errno); + throw file_error(msg.str()); + } + m_open_mode = mode; + } + + void close() + { + if (m_fd == -1) return; + +#ifdef _WIN32 + ::_close(m_fd); +#else + ::close(m_fd); +#endif + m_fd = -1; + m_open_mode = 0; + } + + size_type read(char* buf, size_type num_bytes) + { + assert(m_open_mode & mode_in); + assert(m_fd != -1); + +#ifdef _WIN32 + size_type ret = ::_read(m_fd, buf, num_bytes); +#else + size_type ret = ::read(m_fd, buf, num_bytes); +#endif + if (ret == -1) + { + std::stringstream msg; + msg << "read failed: " << strerror(errno); + throw file_error(msg.str()); + } + return ret; + } + + size_type write(const char* buf, size_type num_bytes) + { + assert(m_open_mode & mode_out); + assert(m_fd != -1); + + // TODO: Test this a bit more, what happens with random failures in + // the files? +// if ((rand() % 100) > 80) +// throw file_error("debug"); + +#ifdef _WIN32 + size_type ret = ::_write(m_fd, buf, num_bytes); +#else + size_type ret = ::write(m_fd, buf, num_bytes); +#endif + if (ret == -1) + { + std::stringstream msg; + msg << "write failed: " << strerror(errno); + throw file_error(msg.str()); + } + return ret; + } + + void set_size(size_type s) + { + size_type pos = tell(); + seek(s - 1); + char dummy = 0; + read(&dummy, 1); + seek(s - 1); + write(&dummy, 1); + seek(pos); + } + + size_type seek(size_type offset, int m = 1) + { + assert(m_open_mode); + assert(m_fd != -1); + + int seekdir = (m == 1)?SEEK_SET:SEEK_END; +#ifdef _WIN32 + size_type ret = _lseeki64(m_fd, offset, seekdir); +#else + size_type ret = lseek(m_fd, offset, seekdir); +#endif + + // For some strange reason this fails + // on win32. Use windows specific file + // wrapper instead. + if (ret == -1) + { + std::stringstream msg; + msg << "seek failed: '" << strerror(errno) + << "' fd: " << m_fd + << " offset: " << offset + << " seekdir: " << seekdir; + throw file_error(msg.str()); + } + return ret; + } + + size_type tell() + { + assert(m_open_mode); + assert(m_fd != -1); + +#ifdef _WIN32 + return _telli64(m_fd); +#else + return lseek(m_fd, 0, SEEK_CUR); +#endif + } + + int m_fd; + int m_open_mode; + }; + + // pimpl forwardings + + file::file() : m_impl(new impl()) {} + + file::file(fs::path const& p, file::open_mode m) + : m_impl(new impl(p, m.m_mask)) + {} + + file::~file() {} + + void file::open(fs::path const& p, file::open_mode m) + { + m_impl->open(p, m.m_mask); + } + + void file::close() + { + m_impl->close(); + } + + size_type file::write(const char* buf, size_type num_bytes) + { + return m_impl->write(buf, num_bytes); + } + + size_type file::read(char* buf, size_type num_bytes) + { + return m_impl->read(buf, num_bytes); + } + + void file::set_size(size_type s) + { + m_impl->set_size(s); + } + + size_type file::seek(size_type pos, file::seek_mode m) + { + return m_impl->seek(pos, m.m_val); + } + + size_type file::tell() + { + return m_impl->tell(); + } + +} diff --git a/libtorrent/src/file_pool.cpp b/libtorrent/src/file_pool.cpp new file mode 100644 index 000000000..ab4ea8d6c --- /dev/null +++ b/libtorrent/src/file_pool.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/file_pool.hpp" + +#include + +namespace libtorrent +{ + using boost::multi_index::nth_index; + using boost::multi_index::get; + + boost::shared_ptr file_pool::open_file(void* st, fs::path const& p, file::open_mode m) + { + assert(st != 0); + assert(p.is_complete()); + assert(m == file::in || m == (file::in | file::out)); + boost::mutex::scoped_lock l(m_mutex); + typedef nth_index::type path_view; + path_view& pt = get<0>(m_files); + path_view::iterator i = pt.find(p); + if (i != pt.end()) + { + lru_file_entry e = *i; + e.last_use = time_now(); + + if (e.key != st) + { + // this means that another instance of the storage + // is using the exact same file. + throw file_error("torrent uses the same file as another torrent " + "(" + p.string() + ")"); + } + + e.key = st; + if ((e.mode & m) != m) + { + // close the file before we open it with + // the new read/write privilages + i->file_ptr.reset(); + assert(e.file_ptr.unique()); + e.file_ptr.reset(); + e.file_ptr.reset(new file(p, m)); + e.mode = m; + } + pt.replace(i, e); + return e.file_ptr; + } + // the file is not in our cache + if ((int)m_files.size() >= m_size) + { + // the file cache is at its maximum size, close + // the least recently used (lru) file from it + typedef nth_index::type lru_view; + lru_view& lt = get<1>(m_files); + lru_view::iterator i = lt.begin(); + // the first entry in this view is the least recently used + assert(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); + lt.erase(i); + } + lru_file_entry e(boost::shared_ptr(new file(p, m))); + e.mode = m; + e.key = st; + e.file_path = p; + pt.insert(e); + return e.file_ptr; + } + + void file_pool::release(void* st) + { + boost::mutex::scoped_lock l(m_mutex); + assert(st != 0); + using boost::tie; + + typedef nth_index::type key_view; + key_view& kt = get<2>(m_files); + + key_view::iterator start, end; + tie(start, end) = kt.equal_range(st); + kt.erase(start, end); + } + + void file_pool::resize(int size) + { + assert(size > 0); + if (size == m_size) return; + boost::mutex::scoped_lock l(m_mutex); + m_size = size; + if (int(m_files.size()) <= m_size) return; + + // close the least recently used files + typedef nth_index::type lru_view; + lru_view& lt = get<1>(m_files); + lru_view::iterator i = lt.begin(); + while (int(m_files.size()) > m_size) + { + // the first entry in this view is the least recently used + assert(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); + lt.erase(i++); + } + } + +} diff --git a/libtorrent/src/file_win.cpp b/libtorrent/src/file_win.cpp new file mode 100644 index 000000000..9d2c2f4bf --- /dev/null +++ b/libtorrent/src/file_win.cpp @@ -0,0 +1,366 @@ +/* + +Copyright (c) 2003, Magnus Jonsson & Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file.hpp" +#include "libtorrent/utf8.hpp" + +#ifdef UNICODE +#include "libtorrent/storage.hpp" +#endif + +#include +#include +#include + +namespace +{ + // must be used to not leak memory in case something would throw + class auto_localfree + { + public: + auto_localfree(HLOCAL memory) + : m_memory(memory) + { + } + ~auto_localfree() + { + if (m_memory) + LocalFree(m_memory); + } + private: + HLOCAL m_memory; + }; + + std::string utf8_native(std::string const& s) + { + try + { + std::wstring ws; + libtorrent::utf8_wchar(s, ws); + std::size_t size = wcstombs(0, ws.c_str(), 0); + if (size == std::size_t(-1)) return s; + std::string ret; + ret.resize(size); + size = wcstombs(&ret[0], ws.c_str(), size + 1); + if (size == wchar_t(-1)) return s; + ret.resize(size); + return ret; + } + catch(std::exception) + { + return s; + } + } + + void throw_exception(const char* thrower) + { + DWORD err = GetLastError(); + +#ifdef UNICODE + wchar_t *wbuffer = 0; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_ALLOCATE_BUFFER + , 0, err, 0, (LPWSTR)&wbuffer, 0, 0); + auto_localfree auto_free(wbuffer); + std::string tmp_utf8; + libtorrent::wchar_utf8(wbuffer, tmp_utf8); + char const* buffer = tmp_utf8.c_str(); +#else + char* buffer = 0; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_ALLOCATE_BUFFER + , 0, err, 0, (LPSTR)&buffer, 0, 0); + auto_localfree auto_free(buffer); +#endif + + std::stringstream s; + s << (thrower ? thrower : "NULL") << ": " << (buffer ? buffer : "NULL"); + + throw libtorrent::file_error(s.str()); + } +} + +namespace libtorrent +{ + + struct file::impl : boost::noncopyable + { + enum open_flags + { + read_flag = 1, + write_flag = 2 + }; + + enum seek_mode + { + seek_begin = FILE_BEGIN, + seek_from_here = FILE_CURRENT, + seek_end = FILE_END + }; + + impl() + { + m_file_handle = INVALID_HANDLE_VALUE; + } + + void open(const char *file_name, open_flags flags) + { + assert(file_name); + assert(flags & (read_flag | write_flag)); + + DWORD access_mask = 0; + if (flags & read_flag) + access_mask |= GENERIC_READ; + if (flags & write_flag) + access_mask |= GENERIC_WRITE; + + assert(access_mask & (GENERIC_READ | GENERIC_WRITE)); + + #ifdef UNICODE + std::wstring wfile_name(safe_convert(file_name)); + HANDLE new_handle = CreateFile( + wfile_name.c_str() + , access_mask + , FILE_SHARE_READ + , 0 + , (flags & write_flag)?OPEN_ALWAYS:OPEN_EXISTING + , FILE_ATTRIBUTE_NORMAL + , 0); + #else + HANDLE new_handle = CreateFile( + utf8_native(file_name).c_str() + , access_mask + , FILE_SHARE_READ + , 0 + , (flags & write_flag)?OPEN_ALWAYS:OPEN_EXISTING + , FILE_ATTRIBUTE_NORMAL + , 0); + #endif + + if (new_handle == INVALID_HANDLE_VALUE) + throw_exception(file_name); + // try to make the file sparse if supported + if (access_mask & GENERIC_WRITE) + { + DWORD temp; + ::DeviceIoControl(new_handle, FSCTL_SET_SPARSE, 0, 0 + , 0, 0, &temp, 0); + } + // will only close old file if the open succeeded + close(); + m_file_handle = new_handle; + } + + void close() + { + if (m_file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_file_handle); + m_file_handle = INVALID_HANDLE_VALUE; + } + } + + ~impl() + { + close(); + } + + size_type write(const char* buffer, size_type num_bytes) + { + assert(buffer); + assert((DWORD)num_bytes == num_bytes); + DWORD bytes_written = 0; + if (num_bytes != 0) + { + if (FALSE == WriteFile( + m_file_handle + , buffer + , (DWORD)num_bytes + , &bytes_written + , 0)) + { + throw_exception("file::write"); + } + } + return bytes_written; + } + + size_type read(char* buffer, size_type num_bytes) + { + assert(buffer); + assert(num_bytes >= 0); + assert((DWORD)num_bytes == num_bytes); + + DWORD bytes_read = 0; + if (num_bytes != 0) + { + if (FALSE == ReadFile( + m_file_handle + , buffer + , (DWORD)num_bytes + , &bytes_read + , 0)) + { + throw_exception("file::read"); + } + } + return bytes_read; + } + + void set_size(size_type s) + { + size_type pos = tell(); + seek(s, seek_begin); + if (FALSE == ::SetEndOfFile(m_file_handle)) + throw_exception("file::set_size"); + + seek(pos, seek_begin); + } + + size_type seek(size_type pos, seek_mode from_where) + { + assert(pos >= 0 || from_where != seek_begin); + assert(pos <= 0 || from_where != seek_end); + LARGE_INTEGER offs; + offs.QuadPart = pos; + if (FALSE == SetFilePointerEx( + m_file_handle + , offs + , &offs + , from_where)) + { + throw_exception("file::seek"); + } + return offs.QuadPart; + } + + size_type tell() + { + LARGE_INTEGER offs; + offs.QuadPart = 0; + + // is there any other way to get offset? + if (FALSE == SetFilePointerEx( + m_file_handle + , offs + , &offs + , FILE_CURRENT)) + { + throw_exception("file::tell"); + } + + size_type pos = offs.QuadPart; + assert(pos >= 0); + return pos; + } +/* + size_type size() + { + LARGE_INTEGER s; + if (FALSE == GetFileSizeEx(m_file_handle, &s)) + { + throw_exception("file::size"); + } + + size_type size = s.QuadPart; + assert(size >= 0); + return size; + } +*/ + private: + + HANDLE m_file_handle; + + }; +} + +namespace libtorrent +{ + + const file::seek_mode file::begin(file::impl::seek_begin); + const file::seek_mode file::end(file::impl::seek_end); + + const file::open_mode file::in(file::impl::read_flag); + const file::open_mode file::out(file::impl::write_flag); + + file::file() + : m_impl(new libtorrent::file::impl()) + { + } + file::file(boost::filesystem::path const& p, open_mode m) + : m_impl(new libtorrent::file::impl()) + { + open(p,m); + } + + file::~file() + { + } + + void file::open(boost::filesystem::path const& p, open_mode m) + { + assert(p.is_complete()); + m_impl->open(p.native_file_string().c_str(), impl::open_flags(m.m_mask)); + } + + void file::close() + { + m_impl->close(); + } + + size_type file::write(const char* buffer, size_type num_bytes) + { + return m_impl->write(buffer, num_bytes); + } + + size_type file::read(char* buffer, size_type num_bytes) + { + return m_impl->read(buffer, num_bytes); + } + + void file::set_size(size_type s) + { + m_impl->set_size(s); + } + + size_type file::seek(size_type pos, seek_mode m) + { + return m_impl->seek(pos,impl::seek_mode(m.m_val)); + } + + size_type file::tell() + { + return m_impl->tell(); + } +} diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp new file mode 100644 index 000000000..53798cae1 --- /dev/null +++ b/libtorrent/src/http_connection.cpp @@ -0,0 +1,384 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/http_connection.hpp" + +#include +#include +#include +#include + +using boost::bind; + +namespace libtorrent +{ + +void http_connection::get(std::string const& url, time_duration timeout + , bool handle_redirect) +{ + m_redirect = handle_redirect; + std::string protocol; + std::string auth; + std::string hostname; + std::string path; + int port; + boost::tie(protocol, auth, hostname, port, path) = parse_url_components(url); + std::stringstream headers; + headers << "GET " << path << " HTTP/1.0\r\n" + "Host:" << hostname << + "\r\nConnection: close\r\n"; + if (!auth.empty()) + headers << "Authorization: Basic " << base64encode(auth) << "\r\n"; + headers << "\r\n"; + sendbuffer = headers.str(); + start(hostname, boost::lexical_cast(port), timeout); +} + +void http_connection::start(std::string const& hostname, std::string const& port + , time_duration timeout, bool handle_redirect) +{ + m_redirect = handle_redirect; + m_timeout = timeout; + m_timer.expires_from_now(m_timeout); + m_timer.async_wait(bind(&http_connection::on_timeout + , boost::weak_ptr(shared_from_this()), _1)); + m_called = false; + if (m_sock.is_open() && m_hostname == hostname && m_port == port) + { + m_parser.reset(); + asio::async_write(m_sock, asio::buffer(sendbuffer) + , bind(&http_connection::on_write, shared_from_this(), _1)); + } + else + { + m_sock.close(); + tcp::resolver::query query(hostname, port); + m_resolver.async_resolve(query, bind(&http_connection::on_resolve + , shared_from_this(), _1, _2)); + m_hostname = hostname; + m_port = port; + } +} + +void http_connection::on_connect_timeout() +{ + if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + + if (m_bottled && m_called) return; + m_called = true; + m_handler(asio::error::timed_out, m_parser, 0, 0); + close(); +} + +void http_connection::on_timeout(boost::weak_ptr p + , asio::error_code const& e) +{ + boost::shared_ptr c = p.lock(); + if (!c) return; + if (c->m_connection_ticket > -1) c->m_cc.done(c->m_connection_ticket); + c->m_connection_ticket = -1; + + if (e == asio::error::operation_aborted) return; + + if (c->m_bottled && c->m_called) return; + + if (c->m_last_receive + c->m_timeout < time_now()) + { + c->m_called = true; + c->m_handler(asio::error::timed_out, c->m_parser, 0, 0); + return; + } + + c->m_timer.expires_at(c->m_last_receive + c->m_timeout); + c->m_timer.async_wait(bind(&http_connection::on_timeout, p, _1)); +} + +void http_connection::close() +{ + m_timer.cancel(); + m_limiter_timer.cancel(); + m_sock.close(); + m_hostname.clear(); + m_port.clear(); + + if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); + m_connection_ticket = -1; +} + +void http_connection::on_resolve(asio::error_code const& e + , tcp::resolver::iterator i) +{ + if (e) + { + close(); + if (m_bottled && m_called) return; + m_called = true; + m_handler(e, m_parser, 0, 0); + return; + } + assert(i != tcp::resolver::iterator()); + m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i) + , bind(&http_connection::on_connect_timeout, shared_from_this()) + , m_timeout); +} + +void http_connection::connect(int ticket, tcp::endpoint target_address) +{ + m_connection_ticket = ticket; + m_sock.async_connect(target_address, boost::bind(&http_connection::on_connect + , shared_from_this(), _1/*, ++i*/)); +} + +void http_connection::on_connect(asio::error_code const& e + /*, tcp::resolver::iterator i*/) +{ + if (!e) + { + m_last_receive = time_now(); + asio::async_write(m_sock, asio::buffer(sendbuffer) + , bind(&http_connection::on_write, shared_from_this(), _1)); + } +/* else if (i != tcp::resolver::iterator()) + { + // The connection failed. Try the next endpoint in the list. + m_sock.close(); + m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i) + , bind(&http_connection::on_connect_timeout, shared_from_this()) + , m_timeout); + } +*/ else + { + close(); + if (m_bottled && m_called) return; + m_called = true; + m_handler(e, m_parser, 0, 0); + } +} + +void http_connection::on_write(asio::error_code const& e) +{ + if (e) + { + close(); + if (m_bottled && m_called) return; + m_called = true; + m_handler(e, m_parser, 0, 0); + return; + } + + std::string().swap(sendbuffer); + m_recvbuffer.resize(4096); + + int amount_to_read = m_recvbuffer.size() - m_read_pos; + if (m_rate_limit > 0 && amount_to_read > m_download_quota) + { + amount_to_read = m_download_quota; + if (m_download_quota == 0) + { + if (!m_limiter_timer_active) + on_assign_bandwidth(asio::error_code()); + return; + } + } + m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos + , amount_to_read) + , bind(&http_connection::on_read + , shared_from_this(), _1, _2)); +} + +void http_connection::on_read(asio::error_code const& e + , std::size_t bytes_transferred) +{ + if (m_rate_limit) + { + m_download_quota -= bytes_transferred; + assert(m_download_quota >= 0); + } + + if (e == asio::error::eof) + { + close(); + if (m_bottled && m_called) return; + m_called = true; + char const* data = 0; + std::size_t size = 0; + if (m_bottled) + { + data = m_parser.get_body().begin; + size = m_parser.get_body().left(); + } + m_handler(e, m_parser, data, size); + return; + } + + if (e) + { + close(); + if (m_bottled && m_called) return; + m_called = true; + m_handler(e, m_parser, 0, 0); + return; + } + + m_read_pos += bytes_transferred; + assert(m_read_pos <= int(m_recvbuffer.size())); + + // having a nonempty path means we should handle redirects + if (m_redirect && m_parser.header_finished()) + { + int code = m_parser.status_code(); + if (code >= 300 && code < 400) + { + // attempt a redirect + std::string url = m_parser.header("location"); + if (url.empty()) + { + // missing location header + if (m_bottled && m_called) return; + m_called = true; + m_handler(e, m_parser, 0, 0); + return; + } + + m_limiter_timer_active = false; + close(); + + get(url, m_timeout); + return; + } + + m_redirect = false; + } + + if (m_bottled || !m_parser.header_finished()) + { + libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0] + , &m_recvbuffer[0] + m_read_pos); + m_parser.incoming(rcv_buf); + if (!m_bottled && m_parser.header_finished()) + { + if (m_read_pos > m_parser.body_start()) + m_handler(e, m_parser, &m_recvbuffer[0] + m_parser.body_start() + , m_read_pos - m_parser.body_start()); + m_read_pos = 0; + m_last_receive = time_now(); + } + else if (m_bottled && m_parser.finished()) + { + m_timer.cancel(); + if (m_bottled && m_called) return; + m_called = true; + m_handler(e, m_parser, m_parser.get_body().begin, m_parser.get_body().left()); + } + } + else + { + assert(!m_bottled); + m_handler(e, m_parser, &m_recvbuffer[0], m_read_pos); + m_read_pos = 0; + m_last_receive = time_now(); + } + + if (int(m_recvbuffer.size()) == m_read_pos) + m_recvbuffer.resize((std::min)(m_read_pos + 2048, 1024*500)); + if (m_read_pos == 1024 * 500) + { + close(); + if (m_bottled && m_called) return; + m_called = true; + m_handler(asio::error::eof, m_parser, 0, 0); + return; + } + int amount_to_read = m_recvbuffer.size() - m_read_pos; + if (m_rate_limit > 0 && amount_to_read > m_download_quota) + { + amount_to_read = m_download_quota; + if (m_download_quota == 0) + { + if (!m_limiter_timer_active) + on_assign_bandwidth(asio::error_code()); + return; + } + } + m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos + , amount_to_read) + , bind(&http_connection::on_read + , shared_from_this(), _1, _2)); +} + +void http_connection::on_assign_bandwidth(asio::error_code const& e) +{ + if ((e == asio::error::operation_aborted + && m_limiter_timer_active) + || !m_sock.is_open()) + { + if (!m_bottled || !m_called) + m_handler(e, m_parser, 0, 0); + return; + } + m_limiter_timer_active = false; + if (e) return; + + if (m_download_quota > 0) return; + + m_download_quota = m_rate_limit / 4; + + int amount_to_read = m_recvbuffer.size() - m_read_pos; + if (amount_to_read > m_download_quota) + amount_to_read = m_download_quota; + + m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos + , amount_to_read) + , bind(&http_connection::on_read + , shared_from_this(), _1, _2)); + + m_limiter_timer_active = true; + m_limiter_timer.expires_from_now(milliseconds(250)); + m_limiter_timer.async_wait(bind(&http_connection::on_assign_bandwidth + , shared_from_this(), _1)); +} + +void http_connection::rate_limit(int limit) +{ + if (!m_limiter_timer_active) + { + m_limiter_timer_active = true; + m_limiter_timer.expires_from_now(milliseconds(250)); + m_limiter_timer.async_wait(bind(&http_connection::on_assign_bandwidth + , shared_from_this(), _1)); + } + m_rate_limit = limit; +} + +} + diff --git a/libtorrent/src/http_stream.cpp b/libtorrent/src/http_stream.cpp new file mode 100644 index 000000000..0973af798 --- /dev/null +++ b/libtorrent/src/http_stream.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/http_stream.hpp" +#include "libtorrent/tracker_manager.hpp" // for base64encode + +namespace libtorrent +{ + + void http_stream::name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { + if (e || i == tcp::resolver::iterator()) + { + (*h)(e); + close(); + return; + } + + m_sock.async_connect(i->endpoint(), boost::bind( + &http_stream::connected, this, _1, h)); + } + + void http_stream::connected(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + (*h)(e); + return; + } + + // send CONNECT + std::back_insert_iterator > p(m_buffer); + write_string("CONNECT " + boost::lexical_cast(m_remote_endpoint) + + " HTTP/1.0\r\n", p); + if (!m_user.empty()) + { + write_string("Proxy-Authorization: Basic " + base64encode( + m_user + ":" + m_password) + "\r\n", p); + } + write_string("\r\n", p); + asio::async_write(m_sock, asio::buffer(m_buffer) + , boost::bind(&http_stream::handshake1, this, _1, h)); + } + + void http_stream::handshake1(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + // read one byte from the socket + m_buffer.resize(1); + asio::async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&http_stream::handshake2, this, _1, h)); + } + + void http_stream::handshake2(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + int read_pos = m_buffer.size(); + // look for \n\n and \r\n\r\n + // both of which means end of http response header + bool found_end = false; + if (m_buffer[read_pos - 1] == '\n' && read_pos > 2) + { + if (m_buffer[read_pos - 2] == '\n') + { + found_end = true; + } + else if (read_pos > 4 + && m_buffer[read_pos - 2] == '\r' + && m_buffer[read_pos - 3] == '\n' + && m_buffer[read_pos - 4] == '\r') + { + found_end = true; + } + } + + if (found_end) + { + m_buffer.push_back(0); + char* status = strchr(&m_buffer[0], ' '); + if (status == 0) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + status++; + int code = atoi(status); + if (code != 200) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + (*h)(e); + std::vector().swap(m_buffer); + return; + } + + // read another byte from the socket + m_buffer.resize(read_pos + 1); + asio::async_read(m_sock, asio::buffer(&m_buffer[0] + read_pos, 1) + , boost::bind(&http_stream::handshake2, this, _1, h)); + } + +} + diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp new file mode 100755 index 000000000..936f8292a --- /dev/null +++ b/libtorrent/src/http_tracker_connection.cpp @@ -0,0 +1,931 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "zlib.h" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/instantiate_connection.hpp" + +using namespace libtorrent; +using boost::bind; + +namespace +{ + enum + { + minimum_tracker_response_length = 3, + http_buffer_size = 2048 + }; + + + enum + { + FTEXT = 0x01, + FHCRC = 0x02, + FEXTRA = 0x04, + FNAME = 0x08, + FCOMMENT = 0x10, + FRESERVED = 0xe0, + + GZIP_MAGIC0 = 0x1f, + GZIP_MAGIC1 = 0x8b + }; + +} + +namespace +{ + bool url_has_argument(std::string const& url, std::string argument) + { + size_t i = url.find('?'); + if (i == std::string::npos) return false; + + argument += '='; + + if (url.compare(i + 1, argument.size(), argument) == 0) return true; + argument.insert(0, "&"); + return url.find(argument, i) + != std::string::npos; + } + + char to_lower(char c) { return std::tolower(c); } +} + +namespace libtorrent +{ + http_parser::http_parser() + : m_recv_pos(0) + , m_status_code(-1) + , m_content_length(-1) + , m_state(read_status) + , m_recv_buffer(0, 0) + , m_body_start_pos(0) + , m_finished(false) + {} + + boost::tuple http_parser::incoming(buffer::const_interval recv_buffer) + { + assert(recv_buffer.left() >= m_recv_buffer.left()); + boost::tuple ret(0, 0); + + // early exit if there's nothing new in the receive buffer + if (recv_buffer.left() == m_recv_buffer.left()) return ret; + m_recv_buffer = recv_buffer; + + char const* pos = recv_buffer.begin + m_recv_pos; + if (m_state == read_status) + { + assert(!m_finished); + char const* newline = std::find(pos, recv_buffer.end, '\n'); + // if we don't have a full line yet, wait. + if (newline == recv_buffer.end) return ret; + + if (newline == pos) + throw std::runtime_error("unexpected newline in HTTP response"); + + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + + std::istringstream line(std::string(pos, line_end)); + ++newline; + int incoming = (int)std::distance(pos, newline); + m_recv_pos += incoming; + boost::get<1>(ret) += incoming; + pos = newline; + + line >> m_protocol; + if (m_protocol.substr(0, 5) != "HTTP/") + { + throw std::runtime_error("unknown protocol in HTTP response: " + + m_protocol + " line: " + std::string(pos, newline)); + } + line >> m_status_code; + std::getline(line, m_server_message); + m_state = read_header; + } + + if (m_state == read_header) + { + assert(!m_finished); + char const* newline = std::find(pos, recv_buffer.end, '\n'); + std::string line; + + while (newline != recv_buffer.end && m_state == read_header) + { + // if the LF character is preceeded by a CR + // charachter, don't copy it into the line string. + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + line.assign(pos, line_end); + ++newline; + m_recv_pos += newline - pos; + boost::get<1>(ret) += newline - pos; + pos = newline; + + std::string::size_type separator = line.find(':'); + if (separator == std::string::npos) + { + // this means we got a blank line, + // the header is finished and the body + // starts. + m_state = read_body; + m_body_start_pos = m_recv_pos; + break; + } + + std::string name = line.substr(0, separator); + std::transform(name.begin(), name.end(), name.begin(), &to_lower); + ++separator; + // skip whitespace + while (separator < line.size() + && (line[separator] == ' ' || line[separator] == '\t')) + ++separator; + std::string value = line.substr(separator, std::string::npos); + m_header.insert(std::make_pair(name, value)); + + if (name == "content-length") + { + try + { + m_content_length = boost::lexical_cast(value); + } + catch(boost::bad_lexical_cast&) {} + } + else if (name == "content-range") + { + std::stringstream range_str(value); + char dummy; + std::string bytes; + size_type range_start, range_end; + range_str >> bytes >> range_start >> dummy >> range_end; + if (!range_str || range_end < range_start) + { + throw std::runtime_error("invalid content-range in HTTP response: " + range_str.str()); + } + // the http range is inclusive + m_content_length = range_end - range_start + 1; + } + + assert(m_recv_pos <= (int)recv_buffer.left()); + newline = std::find(pos, recv_buffer.end, '\n'); + } + } + + if (m_state == read_body) + { + int incoming = recv_buffer.end - pos; + if (m_recv_pos - m_body_start_pos + incoming > m_content_length + && m_content_length >= 0) + incoming = m_content_length - m_recv_pos + m_body_start_pos; + + assert(incoming >= 0); + m_recv_pos += incoming; + boost::get<0>(ret) += incoming; + + if (m_content_length >= 0 + && m_recv_pos - m_body_start_pos >= m_content_length) + { + m_finished = true; + } + } + return ret; + } + + buffer::const_interval http_parser::get_body() const + { + assert(m_state == read_body); + if (m_content_length >= 0) + return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos + , m_recv_buffer.begin + std::min(m_recv_pos + , m_body_start_pos + m_content_length)); + else + return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos + , m_recv_buffer.begin + m_recv_pos); + } + + void http_parser::reset() + { + m_recv_pos = 0; + m_body_start_pos = 0; + m_status_code = -1; + m_content_length = -1; + m_finished = false; + m_state = read_status; + m_recv_buffer.begin = 0; + m_recv_buffer.end = 0; + m_header.clear(); + } + + http_tracker_connection::http_tracker_connection( + asio::strand& str + , connection_queue& cc + , tracker_manager& man + , tracker_request const& req + , std::string const& hostname + , unsigned short port + , std::string request + , address bind_infc + , boost::weak_ptr c + , session_settings const& stn + , proxy_settings const& ps + , std::string const& auth) + : tracker_connection(man, req, str, bind_infc, c) + , m_man(man) + , m_strand(str) + , m_name_lookup(m_strand.io_service()) + , m_port(port) + , m_recv_pos(0) + , m_buffer(http_buffer_size) + , m_settings(stn) + , m_proxy(ps) + , m_password(auth) + , m_timed_out(false) + , m_connection_ticket(-1) + , m_cc(cc) + { + m_send_buffer.assign("GET "); + + // should we use the proxy? + if (m_proxy.type == proxy_settings::http + || m_proxy.type == proxy_settings::http_pw) + { + m_send_buffer += "http://"; + m_send_buffer += hostname; + if (port != 80) + { + m_send_buffer += ":"; + m_send_buffer += boost::lexical_cast(port); + } + } + + if (tracker_req().kind == tracker_request::scrape_request) + { + // find and replace "announce" with "scrape" + // in request + + std::size_t pos = request.find("announce"); + if (pos == std::string::npos) + throw std::runtime_error("scrape is not available on url: '" + + tracker_req().url +"'"); + request.replace(pos, 8, "scrape"); + } + + m_send_buffer += request; + + // if request-string already contains + // some parameters, append an ampersand instead + // of a question mark + size_t arguments_start = request.find('?'); + if (arguments_start != std::string::npos) + m_send_buffer += "&"; + else + m_send_buffer += "?"; + + if (!url_has_argument(request, "info_hash")) + { + m_send_buffer += "info_hash="; + m_send_buffer += escape_string( + reinterpret_cast(req.info_hash.begin()), 20); + m_send_buffer += '&'; + } + + if (tracker_req().kind == tracker_request::announce_request) + { + if (!url_has_argument(request, "peer_id")) + { + m_send_buffer += "peer_id="; + m_send_buffer += escape_string( + reinterpret_cast(req.pid.begin()), 20); + m_send_buffer += '&'; + } + + if (!url_has_argument(request, "port")) + { + m_send_buffer += "port="; + m_send_buffer += boost::lexical_cast(req.listen_port); + m_send_buffer += '&'; + } + + if (!url_has_argument(request, "uploaded")) + { + m_send_buffer += "uploaded="; + m_send_buffer += boost::lexical_cast(req.uploaded); + m_send_buffer += '&'; + } + + if (!url_has_argument(request, "downloaded")) + { + m_send_buffer += "downloaded="; + m_send_buffer += boost::lexical_cast(req.downloaded); + m_send_buffer += '&'; + } + + if (!url_has_argument(request, "left")) + { + m_send_buffer += "left="; + m_send_buffer += boost::lexical_cast(req.left); + m_send_buffer += '&'; + } + + if (req.event != tracker_request::none) + { + if (!url_has_argument(request, "event")) + { + const char* event_string[] = {"completed", "started", "stopped"}; + m_send_buffer += "event="; + m_send_buffer += event_string[req.event - 1]; + m_send_buffer += '&'; + } + } + if (!url_has_argument(request, "key")) + { + m_send_buffer += "key="; + std::stringstream key_string; + key_string << std::hex << req.key; + m_send_buffer += key_string.str(); + m_send_buffer += '&'; + } + + if (!url_has_argument(request, "compact")) + { + m_send_buffer += "compact=1&"; + } + if (!url_has_argument(request, "numwant")) + { + m_send_buffer += "numwant="; + m_send_buffer += boost::lexical_cast( + std::min(req.num_want, 999)); + m_send_buffer += '&'; + } + if (m_settings.announce_ip != address() && !url_has_argument(request, "ip")) + { + m_send_buffer += "ip="; + m_send_buffer += m_settings.announce_ip.to_string(); + m_send_buffer += '&'; + } + +#ifndef TORRENT_DISABLE_ENCRYPTION + m_send_buffer += "supportcrypto=1&"; +#endif + + // extension that tells the tracker that + // we don't need any peer_id's in the response + if (!url_has_argument(request, "no_peer_id")) + { + m_send_buffer += "no_peer_id=1"; + } + else + { + // remove the trailing '&' + m_send_buffer.resize(m_send_buffer.size() - 1); + } + } + + m_send_buffer += " HTTP/1.0\r\nAccept-Encoding: gzip\r\n" + "User-Agent: "; + m_send_buffer += m_settings.user_agent; + m_send_buffer += "\r\n" + "Host: "; + m_send_buffer += hostname; + if (port != 80) + { + m_send_buffer += ':'; + m_send_buffer += boost::lexical_cast(port); + } + if (m_proxy.type == proxy_settings::http_pw) + { + m_send_buffer += "\r\nProxy-Authorization: Basic "; + m_send_buffer += base64encode(m_proxy.username + ":" + m_proxy.password); + } + if (!auth.empty()) + { + m_send_buffer += "\r\nAuthorization: Basic "; + m_send_buffer += base64encode(auth); + } + m_send_buffer += "\r\n\r\n"; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); + std::stringstream info_hash_str; + info_hash_str << req.info_hash; + requester().debug_log("info_hash: " + + boost::lexical_cast(req.info_hash)); + requester().debug_log("name lookup: " + hostname); + } +#endif + + tcp::resolver::query q(hostname + , boost::lexical_cast(m_port)); + m_name_lookup.async_resolve(q, m_strand.wrap( + boost::bind(&http_tracker_connection::name_lookup, self(), _1, _2))); + set_timeout(m_settings.tracker_completion_timeout + , m_settings.tracker_receive_timeout); + } + + void http_tracker_connection::on_timeout() + { + m_timed_out = true; + m_socket.reset(); + m_name_lookup.cancel(); + if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + fail_timeout(); + } + + void http_tracker_connection::name_lookup(asio::error_code const& error + , tcp::resolver::iterator i) try + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker name lookup handler called"); +#endif + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + + if (error || i == tcp::resolver::iterator()) + { + fail(-1, error.message().c_str()); + return; + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker name lookup successful"); +#endif + restart_read_timeout(); + + // look for an address that has the same kind as the one + // we're listening on. To make sure the tracker get our + // correct listening address. + tcp::resolver::iterator target = i; + tcp::resolver::iterator end; + tcp::endpoint target_address = *i; + for (; target != end && target->endpoint().address().is_v4() + != bind_interface().is_v4(); ++target); + if (target == end) + { + assert(target_address.address().is_v4() != bind_interface().is_v4()); + if (has_requester()) + { + std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; + std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; + requester().tracker_warning("the tracker only resolves to an " + + tracker_address_type + " address, and you're listening on an " + + bind_address_type + " socket. This may prevent you from receiving incoming connections."); + } + } + else + { + target_address = *target; + } + + if (has_requester()) requester().m_tracker_address = target_address; + m_socket = instantiate_connection(m_name_lookup.io_service(), m_proxy); + + if (m_proxy.type == proxy_settings::http + || m_proxy.type == proxy_settings::http_pw) + { + // the tracker connection will talk immediately to + // the proxy, without requiring CONNECT support + m_socket->get().set_no_connect(true); + } + + m_socket->open(target_address.protocol()); + m_socket->bind(tcp::endpoint(bind_interface(), 0)); + m_cc.enqueue(bind(&http_tracker_connection::connect, self(), _1, target_address) + , bind(&http_tracker_connection::on_timeout, self()) + , seconds(m_settings.tracker_receive_timeout)); + } + catch (std::exception& e) + { + fail(-1, e.what()); + }; + + void http_tracker_connection::connect(int ticket, tcp::endpoint target_address) + { + m_connection_ticket = ticket; + m_socket->async_connect(target_address, bind(&http_tracker_connection::connected, self(), _1)); + } + + void http_tracker_connection::connected(asio::error_code const& error) try + { + if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + if (error) + { + fail(-1, error.message().c_str()); + return; + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker connection successful"); +#endif + + restart_read_timeout(); + async_write(*m_socket, asio::buffer(m_send_buffer.c_str() + , m_send_buffer.size()), bind(&http_tracker_connection::sent + , self(), _1)); + } + catch (std::exception& e) + { + fail(-1, e.what()); + } + + void http_tracker_connection::sent(asio::error_code const& error) try + { + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + if (error) + { + fail(-1, error.message().c_str()); + return; + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker send data completed"); +#endif + restart_read_timeout(); + assert(m_buffer.size() - m_recv_pos > 0); + m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] + , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive + , self(), _1, _2)); + } + catch (std::exception& e) + { + fail(-1, e.what()); + }; // msvc 7.1 seems to require this semi-colon + + + void http_tracker_connection::receive(asio::error_code const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + + if (error) + { + if (error == asio::error::eof) + { + on_response(); + close(); + return; + } + + fail(-1, error.message().c_str()); + return; + } + + restart_read_timeout(); + assert(bytes_transferred > 0); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker connection reading " + + boost::lexical_cast(bytes_transferred)); +#endif + + m_recv_pos += bytes_transferred; + m_parser.incoming(buffer::const_interval(&m_buffer[0] + , &m_buffer[0] + m_recv_pos)); + + // if the receive buffer is full, expand it with http_buffer_size + if ((int)m_buffer.size() == m_recv_pos) + { + if ((int)m_buffer.size() >= m_settings.tracker_maximum_response_length) + { + fail(200, "too large tracker response"); + return; + } + assert(http_buffer_size > 0); + if ((int)m_buffer.size() + http_buffer_size + > m_settings.tracker_maximum_response_length) + m_buffer.resize(m_settings.tracker_maximum_response_length); + else + m_buffer.resize(m_buffer.size() + http_buffer_size); + } + + if (m_parser.header_finished()) + { + int cl = m_parser.header("content-length"); + if (cl > m_settings.tracker_maximum_response_length) + { + fail(-1, "content-length is greater than maximum response length"); + return; + } + + if (cl > 0 && cl < minimum_tracker_response_length && m_parser.status_code() == 200) + { + fail(-1, "content-length is smaller than minimum response length"); + return; + } + } + + if (m_parser.finished()) + { + on_response(); + close(); + return; + } + + assert(m_buffer.size() - m_recv_pos > 0); + m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] + , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive + , self(), _1, _2)); + } + catch (std::exception& e) + { + fail(-1, e.what()); + }; + + void http_tracker_connection::on_response() + { + if (!m_parser.header_finished()) + { + fail(-1, "premature end of file"); + return; + } + + std::string location = m_parser.header("location"); + + if (m_parser.status_code() >= 300 && m_parser.status_code() < 400) + { + if (location.empty()) + { + std::string error_str = "got redirection response ("; + error_str += boost::lexical_cast(m_parser.status_code()); + error_str += ") without 'Location' header"; + fail(-1, error_str.c_str()); + return; + } + + // if the protocol isn't specified, assume http + if (location.compare(0, 7, "http://") != 0 + && location.compare(0, 6, "udp://") != 0) + { + location.insert(0, "http://"); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("Redirecting to \"" + location + "\""); +#endif + if (has_requester()) requester().tracker_warning("Redirecting to \"" + location + "\""); + tracker_request req = tracker_req(); + + req.url = location; + + m_man.queue_request(m_strand, m_cc, req + , m_password, bind_interface(), m_requester); + close(); + return; + } + + if (m_parser.status_code() != 200) + { + fail(m_parser.status_code(), m_parser.message().c_str()); + close(); + return; + } + + buffer::const_interval buf(&m_buffer[0] + m_parser.body_start(), &m_buffer[0] + m_recv_pos); + + std::string content_encoding = m_parser.header("content-encoding"); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("content-encoding: \"" + content_encoding + "\""); +#endif + + if (content_encoding == "gzip" || content_encoding == "x-gzip") + { + boost::shared_ptr r = m_requester.lock(); + + if (!r) + { + close(); + return; + } + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_parser.body_start()); + if (inflate_gzip(m_buffer, tracker_request(), r.get(), + m_settings.tracker_maximum_response_length)) + { + close(); + return; + } + buf.begin = &m_buffer[0]; + buf.end = &m_buffer[0] + m_buffer.size(); + } + else if (!content_encoding.empty()) + { + std::string error_str = "unknown content encoding in response: \""; + error_str += content_encoding; + error_str += "\""; + fail(-1, error_str.c_str()); + return; + } + + // handle tracker response + try + { + entry e = bdecode(buf.begin, buf.end); + parse(e); + } + catch (std::exception& e) + { + std::string error_str(e.what()); + error_str += ": \""; + for (char const* i = buf.begin, *end(buf.end); i != end; ++i) + { + if (std::isprint(*i)) error_str += *i; + else error_str += "0x" + boost::lexical_cast((unsigned int)*i) + " "; + } + error_str += "\""; + fail(m_parser.status_code(), error_str.c_str()); + } + #ifndef NDEBUG + catch (...) + { + assert(false); + } + #endif + } + + peer_entry http_tracker_connection::extract_peer_info(const entry& info) + { + peer_entry ret; + + // extract peer id (if any) + entry const* i = info.find_key("peer id"); + if (i != 0) + { + if (i->string().length() != 20) + throw std::runtime_error("invalid response from tracker"); + std::copy(i->string().begin(), i->string().end(), ret.pid.begin()); + } + else + { + // if there's no peer_id, just initialize it to a bunch of zeroes + std::fill_n(ret.pid.begin(), 20, 0); + } + + // extract ip + i = info.find_key("ip"); + if (i == 0) throw std::runtime_error("invalid response from tracker"); + ret.ip = i->string(); + + // extract port + i = info.find_key("port"); + if (i == 0) throw std::runtime_error("invalid response from tracker"); + ret.port = (unsigned short)i->integer(); + + return ret; + } + + void http_tracker_connection::parse(entry const& e) + { + if (!has_requester()) return; + + try + { + // parse the response + try + { + entry const& failure = e["failure reason"]; + + fail(m_parser.status_code(), failure.string().c_str()); + return; + } + catch (type_error const&) {} + + try + { + entry const& warning = e["warning message"]; + if (has_requester()) + requester().tracker_warning(warning.string()); + } + catch(type_error const&) {} + + std::vector peer_list; + + if (tracker_req().kind == tracker_request::scrape_request) + { + std::string ih; + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end() + , std::back_inserter(ih)); + entry scrape_data = e["files"][ih]; + int complete = scrape_data["complete"].integer(); + int incomplete = scrape_data["incomplete"].integer(); + requester().tracker_response(tracker_request(), peer_list, 0, complete + , incomplete); + return; + } + + int interval = (int)e["interval"].integer(); + + if (e["peers"].type() == entry::string_t) + { + std::string const& peers = e["peers"].string(); + for (std::string::const_iterator i = peers.begin(); + i != peers.end();) + { + if (std::distance(i, peers.end()) < 6) break; + + peer_entry p; + p.pid.clear(); + std::stringstream ip_str; + ip_str << (int)detail::read_uint8(i) << "."; + ip_str << (int)detail::read_uint8(i) << "."; + ip_str << (int)detail::read_uint8(i) << "."; + ip_str << (int)detail::read_uint8(i); + p.ip = ip_str.str(); + p.port = detail::read_uint16(i); + peer_list.push_back(p); + } + } + else + { + entry::list_type const& l = e["peers"].list(); + for(entry::list_type::const_iterator i = l.begin(); i != l.end(); ++i) + { + peer_entry p = extract_peer_info(*i); + peer_list.push_back(p); + } + } + + // look for optional scrape info + int complete = -1; + int incomplete = -1; + + try { complete = e["complete"].integer(); } + catch(type_error&) {} + + try { incomplete = e["incomplete"].integer(); } + catch(type_error&) {} + + requester().tracker_response(tracker_request(), peer_list, interval, complete + , incomplete); + } + catch(type_error& e) + { + requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + } + catch(std::runtime_error& e) + { + requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + } + } + +} + diff --git a/libtorrent/src/identify_client.cpp b/libtorrent/src/identify_client.cpp new file mode 100755 index 000000000..26ddb51dc --- /dev/null +++ b/libtorrent/src/identify_client.cpp @@ -0,0 +1,351 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/identify_client.hpp" +#include "libtorrent/fingerprint.hpp" + +namespace +{ + + using namespace libtorrent; + + int decode_digit(char c) + { + if (std::isdigit(c)) return c - '0'; + return unsigned(c) - 'A' + 10; + } + + // takes a peer id and returns a valid boost::optional + // object if the peer id matched the azureus style encoding + // the returned fingerprint contains information about the + // client's id + boost::optional parse_az_style(const peer_id& id) + { + fingerprint ret("..", 0, 0, 0, 0); + + if (id[0] != '-' || !std::isprint(id[1]) || (id[2] < '0') + || (id[3] < '0') || (id[4] < '0') + || (id[5] < '0') || (id[6] < '0') + || id[7] != '-') + return boost::optional(); + + ret.name[0] = id[1]; + ret.name[1] = id[2]; + ret.major_version = decode_digit(id[3]); + ret.minor_version = decode_digit(id[4]); + ret.revision_version = decode_digit(id[5]); + ret.tag_version = decode_digit(id[6]); + + return boost::optional(ret); + } + + // checks if a peer id can possibly contain a shadow-style + // identification + boost::optional parse_shadow_style(const peer_id& id) + { + fingerprint ret("..", 0, 0, 0, 0); + + if (!std::isalnum(id[0])) + return boost::optional(); + + if (std::equal(id.begin()+4, id.begin()+6, "--")) + { + if ((id[1] < '0') || (id[2] < '0') + || (id[3] < '0')) + return boost::optional(); + ret.major_version = decode_digit(id[1]); + ret.minor_version = decode_digit(id[2]); + ret.revision_version = decode_digit(id[3]); + } + else + { + if (id[8] != 0 || id[1] > 127 || id[2] > 127 || id[3] > 127) + return boost::optional(); + ret.major_version = id[1]; + ret.minor_version = id[2]; + ret.revision_version = id[3]; + } + + ret.name[0] = id[0]; + ret.name[1] = 0; + + ret.tag_version = 0; + return boost::optional(ret); + } + + // checks if a peer id can possibly contain a mainline-style + // identification + boost::optional parse_mainline_style(const peer_id& id) + { + char ids[21]; + std::copy(id.begin(), id.end(), ids); + ids[20] = 0; + fingerprint ret("..", 0, 0, 0, 0); + ret.name[1] = 0; + ret.tag_version = 0; + if (sscanf(ids, "%c%d-%d-%d--", &ret.name[0], &ret.major_version, &ret.minor_version + , &ret.revision_version) != 4 + || !std::isprint(ret.name[0])) + return boost::optional(); + + return boost::optional(ret); + } + + struct map_entry + { + char const* id; + char const* name; + }; + + // only support BitTorrentSpecification + // must be ordered alphabetically + map_entry name_map[] = + { + {"A", "ABC"} + , {"AG", "Ares"} + , {"AR", "Arctic Torrent"} + , {"AV", "Avicora"} + , {"AX", "BitPump"} + , {"AZ", "Azureus"} + , {"A~", "Ares"} + , {"BB", "BitBuddy"} + , {"BC", "BitComet"} + , {"BF", "Bitflu"} + , {"BG", "BTG"} + , {"BR", "BitRocket"} + , {"BS", "BTSlave"} + , {"BX", "BittorrentX"} + , {"CD", "Enhanced CTorrent"} + , {"CT", "CTorrent"} + , {"DE", "Deluge Torrent"} + , {"EB", "EBit"} + , {"ES", "electric sheep"} + , {"HL", "Halite"} + , {"HN", "Hydranode"} + , {"KT", "KTorrent"} + , {"LK", "Linkage"} + , {"LP", "lphant"} + , {"LT", "libtorrent"} + , {"M", "Mainline"} + , {"ML", "MLDonkey"} + , {"MO", "Mono Torrent"} + , {"MP", "MooPolice"} + , {"MT", "Moonlight Torrent"} + , {"O", "Osprey Permaseed"} + , {"PD", "Pando"} + , {"Q", "BTQueue"} + , {"QT", "Qt 4"} + , {"R", "Tribler"} + , {"S", "Shadow"} + , {"SB", "Swiftbit"} + , {"SN", "ShareNet"} + , {"SS", "SwarmScope"} + , {"SZ", "Shareaza"} + , {"S~", "Shareaza (beta)"} + , {"T", "BitTornado"} + , {"TN", "Torrent.NET"} + , {"TR", "Transmission"} + , {"TS", "TorrentStorm"} + , {"TT", "TuoTu"} + , {"U", "UPnP"} + , {"UL", "uLeecher"} + , {"UT", "uTorrent"} + , {"XT", "XanTorrent"} + , {"XX", "Xtorrent"} + , {"ZT", "ZipTorrent"} + , {"lt", "rTorrent"} + , {"pX", "pHoeniX"} + , {"qB", "qBittorrent"} + }; + + bool compare_id(map_entry const& lhs, map_entry const& rhs) + { + return lhs.id[0] < rhs.id[0] + || ((lhs.id[0] == rhs.id[0]) && (lhs.id[1] < rhs.id[1])); + } + + std::string lookup(fingerprint const& f) + { + std::stringstream identity; + + const int size = sizeof(name_map)/sizeof(name_map[0]); + map_entry tmp = {f.name, ""}; + map_entry* i = + std::lower_bound(name_map, name_map + size + , tmp, &compare_id); + +#ifndef NDEBUG + for (int i = 1; i < size; ++i) + { + assert(compare_id(name_map[i-1] + , name_map[i])); + } +#endif + + if (i < name_map + size && std::equal(f.name, f.name + 2, i->id)) + identity << i->name; + else + { + identity << f.name[0]; + if (f.name[1] != 0) identity << f.name[1]; + } + + identity << " " << (int)f.major_version + << "." << (int)f.minor_version + << "." << (int)f.revision_version; + + if (f.name[1] != 0) + identity << "." << (int)f.tag_version; + + return identity.str(); + } + + bool find_string(unsigned char const* id, char const* search) + { + return std::equal(search, search + std::strlen(search), id); + } +} + +namespace libtorrent +{ + + boost::optional client_fingerprint(peer_id const& p) + { + // look for azureus style id + boost::optional f; + f = parse_az_style(p); + if (f) return f; + + // look for shadow style id + f = parse_shadow_style(p); + if (f) return f; + + // look for mainline style id + f = parse_mainline_style(p); + if (f) return f; + return f; + } + + std::string identify_client(peer_id const& p) + { + peer_id::const_iterator PID = p.begin(); + boost::optional f; + + if (p.is_all_zeros()) return "Unknown"; + + // ---------------------- + // non standard encodings + // ---------------------- + + if (find_string(PID, "Deadman Walking-")) return "Deadman"; + if (find_string(PID + 5, "Azureus")) return "Azureus 2.0.3.2"; + if (find_string(PID, "DansClient")) return "XanTorrent"; + if (find_string(PID + 4, "btfans")) return "SimpleBT"; + if (find_string(PID, "PRC.P---")) return "Bittorrent Plus! II"; + if (find_string(PID, "P87.P---")) return "Bittorrent Plus!"; + if (find_string(PID, "S587Plus")) return "Bittorrent Plus!"; + if (find_string(PID, "martini")) return "Martini Man"; + if (find_string(PID, "Plus---")) return "Bittorrent Plus"; + if (find_string(PID, "turbobt")) return "TurboBT"; + if (find_string(PID, "a00---0")) return "Swarmy"; + if (find_string(PID, "a02---0")) return "Swarmy"; + if (find_string(PID, "T00---0")) return "Teeweety"; + if (find_string(PID, "BTDWV-")) return "Deadman Walking"; + if (find_string(PID + 2, "BS")) return "BitSpirit"; + if (find_string(PID, "btuga")) return "BTugaXP"; + if (find_string(PID, "oernu")) return "BTugaXP"; + if (find_string(PID, "Mbrst")) return "Burst!"; + if (find_string(PID, "Plus")) return "Plus!"; + if (find_string(PID, "-Qt-")) return "Qt"; + if (find_string(PID, "exbc")) return "BitComet"; + if (find_string(PID, "-G3")) return "G3 Torrent"; + if (find_string(PID, "XBT")) return "XBT"; + if (find_string(PID, "OP")) return "Opera"; + + if (find_string(PID, "-BOW") && PID[7] == '-') + return "Bits on Wheels " + std::string(PID + 4, PID + 7); + + + if (find_string(PID, "eX")) + { + std::string user(PID + 2, PID + 14); + return std::string("eXeem ('") + user.c_str() + "')"; + } + + if (std::equal(PID, PID + 13, "\0\0\0\0\0\0\0\0\0\0\0\0\x97")) + return "Experimental 3.2.1b2"; + + if (std::equal(PID, PID + 13, "\0\0\0\0\0\0\0\0\0\0\0\0\0")) + return "Experimental 3.1"; + + + // look for azureus style id + f = parse_az_style(p); + if (f) return lookup(*f); + + // look for shadow style id + f = parse_shadow_style(p); + if (f) return lookup(*f); + + // look for mainline style id + f = parse_mainline_style(p); + if (f) return lookup(*f); + + + if (std::equal(PID, PID + 12, "\0\0\0\0\0\0\0\0\0\0\0\0")) + return "Generic"; + + std::string unknown("Unknown ["); + for (peer_id::const_iterator i = p.begin(); i != p.end(); ++i) + { + unknown += std::isprint(*i)?*i:'.'; + } + unknown += "]"; + return unknown; + } + +} diff --git a/libtorrent/src/instantiate_connection.cpp b/libtorrent/src/instantiate_connection.cpp new file mode 100644 index 000000000..43b70f40d --- /dev/null +++ b/libtorrent/src/instantiate_connection.cpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/socket_type.hpp" +#include +#include +#include + +namespace libtorrent +{ + + boost::shared_ptr instantiate_connection( + asio::io_service& ios, proxy_settings const& ps) + { + boost::shared_ptr s(new socket_type(ios)); + + if (ps.type == proxy_settings::none) + { + s->instantiate(); + } + else if (ps.type == proxy_settings::http + || ps.type == proxy_settings::http_pw) + { + s->instantiate(); + s->get().set_proxy(ps.hostname, ps.port); + if (ps.type == proxy_settings::socks5_pw) + s->get().set_username(ps.username, ps.password); + } + else if (ps.type == proxy_settings::socks5 + || ps.type == proxy_settings::socks5_pw) + { + s->instantiate(); + s->get().set_proxy(ps.hostname, ps.port); + if (ps.type == proxy_settings::socks5_pw) + s->get().set_username(ps.username, ps.password); + } + else if (ps.type == proxy_settings::socks4) + { + s->instantiate(); + s->get().set_proxy(ps.hostname, ps.port); + s->get().set_username(ps.username); + } + else + { + throw std::runtime_error("unsupported proxy type"); + } + return s; + } + +} + diff --git a/libtorrent/src/ip_filter.cpp b/libtorrent/src/ip_filter.cpp new file mode 100644 index 000000000..cf368c4d1 --- /dev/null +++ b/libtorrent/src/ip_filter.cpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2005, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/ip_filter.hpp" +#include +//#include + + +namespace libtorrent +{ + void ip_filter::add_rule(address first, address last, int flags) + { + if (first.is_v4()) + { + assert(last.is_v4()); + m_filter4.add_rule(first.to_v4(), last.to_v4(), flags); + } + else if (first.is_v6()) + { + assert(last.is_v6()); + m_filter6.add_rule(first.to_v6(), last.to_v6(), flags); + } + else + assert(false); + } + + int ip_filter::access(address const& addr) const + { + if (addr.is_v4()) + return m_filter4.access(addr.to_v4()); + assert(addr.is_v6()); + return m_filter6.access(addr.to_v6()); + } + + ip_filter::filter_tuple_t ip_filter::export_filter() const + { + return boost::make_tuple(m_filter4.export_filter() + , m_filter6.export_filter()); + } + + void port_filter::add_rule(boost::uint16_t first, boost::uint16_t last, int flags) + { + m_filter.add_rule(first, last, flags); + } + + int port_filter::access(boost::uint16_t port) const + { + return m_filter.access(port); + } +/* + void ip_filter::print() const + { + for (range_t::iterator i = m_access_list.begin(); i != m_access_list.end(); ++i) + { + std::cout << i->start.as_string() << " " << i->access << "\n"; + } + } +*/ +} + diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp new file mode 100644 index 000000000..0c7d9d276 --- /dev/null +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +closest_nodes_observer::~closest_nodes_observer() +{ + if (m_algorithm) m_algorithm->failed(m_self, true); +} + +void closest_nodes_observer::reply(msg const& in) +{ + if (!m_algorithm) + { + assert(false); + return; + } + + if (!in.nodes.empty()) + { + for (msg::nodes_t::const_iterator i = in.nodes.begin() + , end(in.nodes.end()); i != end; ++i) + { + m_algorithm->traverse(i->id, i->addr); + } + } + m_algorithm->finished(m_self); + m_algorithm = 0; +} + +void closest_nodes_observer::timeout() +{ + if (!m_algorithm) return; + m_algorithm->failed(m_self); + m_algorithm = 0; +} + + +closest_nodes::closest_nodes( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) + : traversal_algorithm( + target + , branch_factor + , max_results + , table + , rpc + , table.begin() + , table.end() + ) + , m_done_callback(callback) +{ + boost::intrusive_ptr self(this); + add_requests(); +} + +void closest_nodes::invoke(node_id const& id, udp::endpoint addr) +{ + observer_ptr o(new (m_rpc.allocator().malloc()) closest_nodes_observer(this, id, m_target)); + m_rpc.invoke(messages::find_node, addr, o); +} + +void closest_nodes::done() +{ + std::vector results; + int result_size = m_table.bucket_size(); + if (result_size > (int)m_results.size()) result_size = (int)m_results.size(); + for (std::vector::iterator i = m_results.begin() + , end(m_results.begin() + result_size); i != end; ++i) + { + results.push_back(node_entry(i->id, i->addr)); + } + m_done_callback(results); +} + +void closest_nodes::initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) +{ + new closest_nodes(target, branch_factor, max_results, table, rpc, callback); +} + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp new file mode 100644 index 000000000..eda6cd864 --- /dev/null +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -0,0 +1,974 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" + +using boost::ref; +using boost::lexical_cast; +using libtorrent::dht::node_impl; +using libtorrent::dht::node_id; +using libtorrent::dht::packet_t; +using libtorrent::dht::msg; +using libtorrent::dht::packet_iterator; +namespace messages = libtorrent::dht::messages; +using namespace libtorrent::detail; + +enum +{ + key_refresh = 5 // generate a new write token key every 5 minutes +}; + +using asio::ip::udp; +typedef asio::ip::address_v4 address; + +namespace +{ + const int tick_period = 1; // minutes + + struct count_peers + { + int& count; + count_peers(int& c): count(c) {} + void operator()(std::pair const& t) + { + count += std::distance(t.second.peers.begin() + , t.second.peers.end()); + } + }; + + boost::optional read_id(libtorrent::entry const& d) + { + using namespace libtorrent; + using libtorrent::dht::node_id; + + if (d.type() != entry::dictionary_t) return boost::optional(); + entry const* nid = d.find_key("node-id"); + if (!nid + || nid->type() != entry::string_t + || nid->string().length() != 40) + return boost::optional(); + return boost::optional( + boost::lexical_cast(nid->string())); + } + + template + void read_endpoint_list(libtorrent::entry const* n, std::vector& epl) + { + using namespace libtorrent; + entry::list_type const& contacts = n->list(); + for (entry::list_type::const_iterator i = contacts.begin() + , end(contacts.end()); i != end; ++i) + { + std::string const& p = i->string(); + if (p.size() < 6) continue; + std::string::const_iterator in = p.begin(); + if (p.size() == 6) + epl.push_back(read_v4_endpoint(in)); + else if (p.size() == 18) + epl.push_back(read_v6_endpoint(in)); + } + } + +} + +namespace libtorrent { namespace dht +{ + + void intrusive_ptr_add_ref(dht_tracker const* c) + { + assert(c != 0); + assert(c->m_refs >= 0); + ++c->m_refs; + } + + void intrusive_ptr_release(dht_tracker const* c) + { + assert(c != 0); + assert(c->m_refs > 0); + if (--c->m_refs == 0) + delete c; + } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_DEFINE_LOG(dht_tracker) +#endif + + // class that puts the networking and the kademlia node in a single + // unit and connecting them together. + dht_tracker::dht_tracker(asio::io_service& ios, dht_settings const& settings + , asio::ip::address listen_interface, entry const& bootstrap) + : m_strand(ios) + , m_socket(ios, udp::endpoint(listen_interface, settings.service_port)) + , m_dht(bind(&dht_tracker::send_packet, this, _1), settings + , read_id(bootstrap)) + , m_buffer(0) + , m_last_new_key(time_now() - minutes(key_refresh)) + , m_timer(ios) + , m_connection_timer(ios) + , m_refresh_timer(ios) + , m_settings(settings) + , m_refresh_bucket(160) + , m_host_resolver(ios) + , m_refs(0) + { + using boost::bind; + + m_in_buf[0].resize(1000); + m_in_buf[1].resize(1000); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + m_counter = 0; + std::fill_n(m_replies_bytes_sent, 5, 0); + std::fill_n(m_queries_bytes_received, 5, 0); + std::fill_n(m_replies_sent, 5, 0); + std::fill_n(m_queries_received, 5, 0); + m_announces = 0; + m_failed_announces = 0; + m_total_message_input = 0; + m_ut_message_input = 0; + m_lt_message_input = 0; + m_mp_message_input = 0; + m_gr_message_input = 0; + m_mo_message_input = 0; + m_total_in_bytes = 0; + m_total_out_bytes = 0; + m_queries_out_bytes = 0; + + // turns on and off individual components' logging + +// rpc_log().enable(false); +// node_log().enable(false); +// traversal_log().enable(false); +// dht_tracker_log.enable(false); + +#endif + std::vector initial_nodes; + + if (bootstrap.type() == entry::dictionary_t) + { + try + { + if (entry const* nodes = bootstrap.find_key("nodes")) + read_endpoint_list(nodes, initial_nodes); + } catch (std::exception&) {} + } + + m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] + , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] + , m_strand.wrap(bind(&dht_tracker::on_receive, self(), _1, _2))); + m_timer.expires_from_now(seconds(1)); + m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, self(), _1))); + + m_connection_timer.expires_from_now(seconds(10)); + m_connection_timer.async_wait(m_strand.wrap( + bind(&dht_tracker::connection_timeout, self(), _1))); + + m_refresh_timer.expires_from_now(seconds(5)); + m_refresh_timer.async_wait(m_strand.wrap(bind(&dht_tracker::refresh_timeout, self(), _1))); + + m_dht.bootstrap(initial_nodes, bind(&dht_tracker::on_bootstrap, self())); + } + + void dht_tracker::stop() + { + m_timer.cancel(); + m_connection_timer.cancel(); + m_refresh_timer.cancel(); + m_socket.close(); + } + + void dht_tracker::dht_status(session_status& s) + { + boost::tie(s.dht_nodes, s.dht_node_cache) = m_dht.size(); + s.dht_torrents = m_dht.data_size(); + s.dht_global_nodes = m_dht.num_global_nodes(); + } + + void dht_tracker::connection_timeout(asio::error_code const& e) + try + { + if (e) return; + time_duration d = m_dht.connection_timeout(); + m_connection_timer.expires_from_now(d); + m_connection_timer.async_wait(m_strand.wrap(bind(&dht_tracker::connection_timeout, self(), _1))); + } + catch (std::exception& exc) + { +#ifndef NDEBUG + std::cerr << "exception-type: " << typeid(exc).name() << std::endl; + std::cerr << "what: " << exc.what() << std::endl; + assert(false); +#endif + }; + + void dht_tracker::refresh_timeout(asio::error_code const& e) + try + { + if (e) return; + time_duration d = m_dht.refresh_timeout(); + m_refresh_timer.expires_from_now(d); + m_refresh_timer.async_wait(m_strand.wrap( + bind(&dht_tracker::refresh_timeout, self(), _1))); + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::rebind(asio::ip::address listen_interface, int listen_port) + { + m_socket.close(); + udp::endpoint ep(listen_interface, listen_port); + m_socket.open(ep.protocol()); + m_socket.bind(ep); + } + + void dht_tracker::tick(asio::error_code const& e) + try + { + if (e) return; + m_timer.expires_from_now(minutes(tick_period)); + m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, this, _1))); + + ptime now = time_now(); + if (now - m_last_new_key > minutes(key_refresh)) + { + m_last_new_key = now; + m_dht.new_write_key(); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << time_now_string() << " new write key"; +#endif + } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + static bool first = true; + if (first) + { + boost::filesystem::create_directory("libtorrent_logs"); + } + + std::ofstream st("libtorrent_logs/routing_table_state.txt", std::ios_base::trunc); + m_dht.print_state(st); + + // count torrents + int torrents = std::distance(m_dht.begin_data(), m_dht.end_data()); + + // count peers + int peers = 0; + std::for_each(m_dht.begin_data(), m_dht.end_data(), count_peers(peers)); + + std::ofstream pc("libtorrent_logs/dht_stats.log", std::ios_base::app); + if (first) + { + first = false; + pc << "\n\n ***** starting log at " << time_now_string() << " *****\n\n" + << "minute:active nodes:passive nodes" + ":ping replies sent:ping queries recvd:ping" + ":ping replies sent:ping queries recvd:ping" + ":find_node replies bytes sent:find_node queries bytes recv" + ":find_node replies bytes sent:find_node queries bytes recv" + ":get_peers replies sent:get_peers queries recvd:get_peers" + ":get_peers replies bytes sent:get_peers queries bytes recv" + ":announce_peer replies sent:announce_peer queries recvd:announce_peer" + ":announce_peer replies bytes sent:announce_peer queries bytes recv" + ":error replies sent:error queries recvd:error" + ":error replies bytes sent:error queries bytes recv" + ":num torrents:num peers:announces per min" + ":failed announces per min:total msgs per min" + ":ut msgs per min:lt msgs per min:mp msgs per min" + ":gr msgs per min:bytes in per sec:bytes out per sec" + ":queries out bytes per sec\n\n"; + } + + int active; + int passive; + boost::tie(active, passive) = m_dht.size(); + pc << (m_counter * tick_period) + << "\t" << active + << "\t" << passive; + for (int i = 0; i < 5; ++i) + pc << "\t" << (m_replies_sent[i] / float(tick_period)) + << "\t" << (m_queries_received[i] / float(tick_period)) + << "\t" << (m_replies_bytes_sent[i] / float(tick_period*60)) + << "\t" << (m_queries_bytes_received[i] / float(tick_period*60)); + + pc << "\t" << torrents + << "\t" << peers + << "\t" << m_announces / float(tick_period) + << "\t" << m_failed_announces / float(tick_period) + << "\t" << (m_total_message_input / float(tick_period)) + << "\t" << (m_ut_message_input / float(tick_period)) + << "\t" << (m_lt_message_input / float(tick_period)) + << "\t" << (m_mp_message_input / float(tick_period)) + << "\t" << (m_gr_message_input / float(tick_period)) + << "\t" << (m_mo_message_input / float(tick_period)) + << "\t" << (m_total_in_bytes / float(tick_period*60)) + << "\t" << (m_total_out_bytes / float(tick_period*60)) + << "\t" << (m_queries_out_bytes / float(tick_period*60)) + << std::endl; + ++m_counter; + std::fill_n(m_replies_bytes_sent, 5, 0); + std::fill_n(m_queries_bytes_received, 5, 0); + std::fill_n(m_replies_sent, 5, 0); + std::fill_n(m_queries_received, 5, 0); + m_announces = 0; + m_failed_announces = 0; + m_total_message_input = 0; + m_ut_message_input = 0; + m_lt_message_input = 0; + m_total_in_bytes = 0; + m_total_out_bytes = 0; + m_queries_out_bytes = 0; +#endif + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::announce(sha1_hash const& ih, int listen_port + , boost::function const& + , sha1_hash const&)> f) + { + m_dht.announce(ih, listen_port, f); + } + + // translate bittorrent kademlia message into the generice kademlia message + // used by the library + void dht_tracker::on_receive(asio::error_code const& error, size_t bytes_transferred) + try + { + if (error == asio::error::operation_aborted) return; + + int current_buffer = m_buffer; + m_buffer = (m_buffer + 1) & 1; + m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] + , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] + , m_strand.wrap(bind(&dht_tracker::on_receive, self(), _1, _2))); + + if (error) return; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + ++m_total_message_input; + m_total_in_bytes += bytes_transferred; +#endif + + try + { + using libtorrent::entry; + using libtorrent::bdecode; + + assert(bytes_transferred > 0); + + entry e = bdecode(m_in_buf[current_buffer].begin() + , m_in_buf[current_buffer].end()); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << time_now_string() << " RECEIVED [" + << m_remote_endpoint[current_buffer] << "]:"; +#endif + + libtorrent::dht::msg m; + m.message_id = 0; + m.addr = m_remote_endpoint[current_buffer]; + m.transaction_id = e["t"].string(); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + try + { + entry const* ver = e.find_key("v"); + if (!ver) throw std::exception(); + + std::string const& client = ver->string(); + if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "UT")) + { + ++m_ut_message_input; + TORRENT_LOG(dht_tracker) << " client: uTorrent"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "LT")) + { + ++m_lt_message_input; + TORRENT_LOG(dht_tracker) << " client: libtorrent"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "MP")) + { + ++m_mp_message_input; + TORRENT_LOG(dht_tracker) << " client: MooPolice"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "GR")) + { + ++m_gr_message_input; + TORRENT_LOG(dht_tracker) << " client: GetRight"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "MO")) + { + ++m_mo_message_input; + TORRENT_LOG(dht_tracker) << " client: Mono Torrent"; + } + else + { + TORRENT_LOG(dht_tracker) << " client: " << client; + } + } + catch (std::exception&) + { + TORRENT_LOG(dht_tracker) << " client: generic"; + }; +#endif + + std::string const& msg_type = e["y"].string(); + + if (msg_type == "r") + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " reply: transaction: " + << m.transaction_id; +#endif + + m.reply = true; + entry const& r = e["r"]; + std::string const& id = r["id"].string(); + if (id.size() != 20) throw std::runtime_error("invalid size of id"); + std::copy(id.begin(), id.end(), m.id.begin()); + + if (entry const* n = r.find_key("values")) + { + m.peers.clear(); + if (n->list().size() == 1) + { + // assume it's mainline format + std::string const& peers = n->list().front().string(); + std::string::const_iterator i = peers.begin(); + std::string::const_iterator end = peers.end(); + + while (std::distance(i, end) >= 6) + m.peers.push_back(read_v4_endpoint(i)); + } + else + { + // assume it's uTorrent/libtorrent format + read_endpoint_list(n, m.peers); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " peers: " << m.peers.size(); +#endif + } + + m.nodes.clear(); + if (entry const* n = r.find_key("nodes")) + { + std::string const& nodes = n->string(); + std::string::const_iterator i = nodes.begin(); + std::string::const_iterator end = nodes.end(); + + while (std::distance(i, end) >= 26) + { + node_id id; + std::copy(i, i + 20, id.begin()); + i += 20; + m.nodes.push_back(libtorrent::dht::node_entry( + id, read_v4_endpoint(i))); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " nodes: " << m.nodes.size(); +#endif + } + + if (entry const* n = r.find_key("nodes2")) + { + entry::list_type const& contacts = n->list(); + for (entry::list_type::const_iterator i = contacts.begin() + , end(contacts.end()); i != end; ++i) + { + std::string const& p = i->string(); + if (p.size() < 6 + 20) continue; + std::string::const_iterator in = p.begin(); + + node_id id; + std::copy(in, in + 20, id.begin()); + in += 20; + if (p.size() == 6 + 20) + m.nodes.push_back(libtorrent::dht::node_entry( + id, read_v4_endpoint(in))); + else if (p.size() == 18 + 20) + m.nodes.push_back(libtorrent::dht::node_entry( + id, read_v6_endpoint(in))); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " nodes2 + nodes: " << m.nodes.size(); +#endif + } + + entry const* token = r.find_key("token"); + if (token) m.write_token = *token; + } + else if (msg_type == "q") + { + m.reply = false; + entry const& a = e["a"]; + std::string const& id = a["id"].string(); + if (id.size() != 20) throw std::runtime_error("invalid size of id"); + std::copy(id.begin(), id.end(), m.id.begin()); + + std::string request_kind(e["q"].string()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " query: " << request_kind; +#endif + + if (request_kind == "ping") + { + m.message_id = libtorrent::dht::messages::ping; + } + else if (request_kind == "find_node") + { + std::string const& target = a["target"].string(); + if (target.size() != 20) throw std::runtime_error("invalid size of target id"); + std::copy(target.begin(), target.end(), m.info_hash.begin()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " target: " + << boost::lexical_cast(m.info_hash); +#endif + + m.message_id = libtorrent::dht::messages::find_node; + } + else if (request_kind == "get_peers") + { + std::string const& info_hash = a["info_hash"].string(); + if (info_hash.size() != 20) throw std::runtime_error("invalid size of info-hash"); + std::copy(info_hash.begin(), info_hash.end(), m.info_hash.begin()); + m.message_id = libtorrent::dht::messages::get_peers; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " info_hash: " + << boost::lexical_cast(m.info_hash); +#endif + } + else if (request_kind == "announce_peer") + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + ++m_announces; +#endif + std::string const& info_hash = a["info_hash"].string(); + if (info_hash.size() != 20) + throw std::runtime_error("invalid size of info-hash"); + std::copy(info_hash.begin(), info_hash.end(), m.info_hash.begin()); + m.port = a["port"].integer(); + m.write_token = a["token"]; + m.message_id = libtorrent::dht::messages::announce_peer; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " info_hash: " + << boost::lexical_cast(m.info_hash); + TORRENT_LOG(dht_tracker) << " port: " << m.port; + + if (!m_dht.verify_token(m)) + ++m_failed_announces; +#endif + } + else + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " *** UNSUPPORTED REQUEST *** : " + << request_kind; +#endif + throw std::runtime_error("unsupported request: " + request_kind); + } + } + else if (msg_type == "e") + { + entry::list_type const& list = e["e"].list(); + m.message_id = messages::error; + m.error_msg = list.back().string(); + m.error_code = list.front().integer(); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " incoming error: " << m.error_code << " " + << m.error_msg; +#endif + throw std::runtime_error("DHT error message"); + } + else + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " *** UNSUPPORTED MESSAGE TYPE *** : " + << msg_type; +#endif + throw std::runtime_error("unsupported message type: " + msg_type); + } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + if (!m.reply) + { + ++m_queries_received[m.message_id]; + m_queries_bytes_received[m.message_id] += int(bytes_transferred); + } + TORRENT_LOG(dht_tracker) << e; +#endif + assert(m.message_id != messages::error); + m_dht.incoming(m); + } + catch (std::exception& e) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + int current_buffer = (m_buffer + 1) & 1; + std::string msg(m_in_buf[current_buffer].begin() + , m_in_buf[current_buffer].begin() + bytes_transferred); + TORRENT_LOG(dht_tracker) << "invalid incoming packet: " + << e.what() << "\n" << msg << "\n"; +#endif + } + } + catch (std::exception& e) + { + assert(false); + }; + + entry dht_tracker::state() const + { + entry ret(entry::dictionary_t); + { + entry nodes(entry::list_t); + for (node_impl::iterator i(m_dht.begin()) + , end(m_dht.end()); i != end; ++i) + { + std::string node; + std::back_insert_iterator out(node); + write_endpoint(i->addr, out); + nodes.list().push_back(entry(node)); + } + bucket_t cache; + m_dht.replacement_cache(cache); + for (bucket_t::iterator i(cache.begin()) + , end(cache.end()); i != end; ++i) + { + std::string node; + std::back_insert_iterator out(node); + write_endpoint(i->addr, out); + nodes.list().push_back(entry(node)); + } + if (!nodes.list().empty()) + ret["nodes"] = nodes; + } + + ret["node-id"] = boost::lexical_cast(m_dht.nid()); + return ret; + } + + void dht_tracker::add_node(udp::endpoint node) + { + m_dht.add_node(node); + } + + void dht_tracker::add_node(std::pair const& node) + { + udp::resolver::query q(node.first, lexical_cast(node.second)); + m_host_resolver.async_resolve(q, m_strand.wrap( + bind(&dht_tracker::on_name_lookup, self(), _1, _2))); + } + + void dht_tracker::on_name_lookup(asio::error_code const& e + , udp::resolver::iterator host) try + { + if (e || host == udp::resolver::iterator()) return; + add_node(host->endpoint()); + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::add_router_node(std::pair const& node) + { + udp::resolver::query q(node.first, lexical_cast(node.second)); + m_host_resolver.async_resolve(q, m_strand.wrap( + bind(&dht_tracker::on_router_name_lookup, self(), _1, _2))); + } + + void dht_tracker::on_router_name_lookup(asio::error_code const& e + , udp::resolver::iterator host) try + { + if (e || host == udp::resolver::iterator()) return; + m_dht.add_router_node(host->endpoint()); + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::on_bootstrap() + {} + + namespace + { + void write_nodes_entry(entry& r, libtorrent::dht::msg const& m) + { + bool ipv6_nodes = false; + r["nodes"] = entry(entry::string_t); + entry& n = r["nodes"]; + std::back_insert_iterator out(n.string()); + for (msg::nodes_t::const_iterator i = m.nodes.begin() + , end(m.nodes.end()); i != end; ++i) + { + if (!i->addr.address().is_v4()) + { + ipv6_nodes = true; + continue; + } + std::copy(i->id.begin(), i->id.end(), out); + write_endpoint(i->addr, out); + } + + if (ipv6_nodes) + { + r["nodes2"] = entry(entry::list_t); + entry& p = r["nodes2"]; + std::string endpoint; + for (msg::nodes_t::const_iterator i = m.nodes.begin() + , end(m.nodes.end()); i != end; ++i) + { + if (!i->addr.address().is_v6()) continue; + endpoint.resize(18 + 20); + std::string::iterator out = endpoint.begin(); + std::copy(i->id.begin(), i->id.end(), out); + out += 20; + write_endpoint(i->addr, out); + endpoint.resize(out - endpoint.begin()); + p.list().push_back(entry(endpoint)); + } + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " nodes: " << m.nodes.size(); +#endif + } + } + + void dht_tracker::send_packet(msg const& m) + try + { + using libtorrent::bencode; + using libtorrent::entry; + entry e(entry::dictionary_t); + assert(!m.transaction_id.empty() || m.message_id == messages::error); + e["t"] = m.transaction_id; + static char const version_str[] = {'L', 'T' + , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR}; + e["v"] = std::string(version_str, version_str + 4); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << time_now_string() + << " SENDING [" << m.addr << "]:"; + TORRENT_LOG(dht_tracker) << " transaction: " << m.transaction_id; +#endif + + if (m.message_id == messages::error) + { + assert(m.reply); + e["y"] = "e"; + entry error_list(entry::list_t); + assert(m.error_code > 200 && m.error_code <= 204); + error_list.list().push_back(entry(m.error_code)); + error_list.list().push_back(entry(m.error_msg)); + e["e"] = error_list; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << time_now_string() + << " outgoing error: " << m.error_code << " " << m.error_msg; +#endif + } + else if (m.reply) + { + e["y"] = "r"; + e["r"] = entry(entry::dictionary_t); + entry& r = e["r"]; + r["id"] = std::string(m.id.begin(), m.id.end()); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << time_now_string() + << " reply: " << messages::ids[m.message_id]; +#endif + + if (m.write_token.type() != entry::undefined_t) + r["token"] = m.write_token; + + switch (m.message_id) + { + case messages::ping: + break; + case messages::find_node: + { + write_nodes_entry(r, m); + break; + } + case messages::get_peers: + { + if (m.peers.empty()) + { + write_nodes_entry(r, m); + } + else + { + r["values"] = entry(entry::list_t); + entry& p = r["values"]; + std::string endpoint; + for (msg::peers_t::const_iterator i = m.peers.begin() + , end(m.peers.end()); i != end; ++i) + { + endpoint.resize(18); + std::string::iterator out = endpoint.begin(); + write_endpoint(*i, out); + endpoint.resize(out - endpoint.begin()); + p.list().push_back(entry(endpoint)); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " peers: " << m.peers.size(); +#endif + } + break; + } + + case messages::announce_peer: + break; + break; + } + } + else + { + e["y"] = "q"; + e["a"] = entry(entry::dictionary_t); + entry& a = e["a"]; + a["id"] = std::string(m.id.begin(), m.id.end()); + + if (m.write_token.type() != entry::undefined_t) + a["token"] = m.write_token; + assert(m.message_id <= messages::error); + e["q"] = messages::ids[m.message_id]; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " query: " + << messages::ids[m.message_id]; +#endif + + switch (m.message_id) + { + case messages::find_node: + { + a["target"] = std::string(m.info_hash.begin(), m.info_hash.end()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " target: " + << boost::lexical_cast(m.info_hash); +#endif + break; + } + case messages::get_peers: + { + a["info_hash"] = std::string(m.info_hash.begin(), m.info_hash.end()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " info_hash: " + << boost::lexical_cast(m.info_hash); +#endif + break; + } + case messages::announce_peer: + a["port"] = m_settings.service_port; + a["info_hash"] = std::string(m.info_hash.begin(), m.info_hash.end()); + a["token"] = m.write_token; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " port: " + << m_settings.service_port + << " info_hash: " << boost::lexical_cast(m.info_hash); +#endif + break; + default: break; + } + + } + + m_send_buf.clear(); + bencode(std::back_inserter(m_send_buf), e); + asio::error_code ec; + m_socket.send_to(asio::buffer(&m_send_buf[0] + , (int)m_send_buf.size()), m.addr, 0, ec); + if (ec) return; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + m_total_out_bytes += m_send_buf.size(); + + if (m.reply) + { + ++m_replies_sent[m.message_id]; + m_replies_bytes_sent[m.message_id] += int(m_send_buf.size()); + } + else + { + m_queries_out_bytes += m_send_buf.size(); + } + TORRENT_LOG(dht_tracker) << e; +#endif + + if (!m.piggy_backed_ping) return; + + msg pm; + pm.reply = false; + pm.piggy_backed_ping = false; + pm.message_id = messages::ping; + pm.transaction_id = m.ping_transaction_id; + pm.id = m.id; + pm.addr = m.addr; + + send_packet(pm); + } + catch (std::exception&) + { + // m_send may fail with "no route to host" + // but it shouldn't throw since an error code + // is passed in instead + assert(false); + } + +}} + diff --git a/libtorrent/src/kademlia/find_data.cpp b/libtorrent/src/kademlia/find_data.cpp new file mode 100644 index 000000000..4ada42fb3 --- /dev/null +++ b/libtorrent/src/kademlia/find_data.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +find_data_observer::~find_data_observer() +{ + if (m_algorithm) m_algorithm->failed(m_self); +} + +void find_data_observer::reply(msg const& m) +{ + if (!m_algorithm) + { + assert(false); + return; + } + + if (!m.peers.empty()) + { + m_algorithm->got_data(&m); + } + else + { + for (msg::nodes_t::const_iterator i = m.nodes.begin() + , end(m.nodes.end()); i != end; ++i) + { + m_algorithm->traverse(i->id, i->addr); + } + } + m_algorithm->finished(m_self); + m_algorithm = 0; +} + +void find_data_observer::timeout() +{ + if (!m_algorithm) return; + m_algorithm->failed(m_self); + m_algorithm = 0; +} + + +find_data::find_data( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) + : traversal_algorithm( + target + , branch_factor + , max_results + , table + , rpc + , table.begin() + , table.end() + ) + , m_done_callback(callback) + , m_done(false) +{ + boost::intrusive_ptr self(this); + add_requests(); +} + +void find_data::invoke(node_id const& id, asio::ip::udp::endpoint addr) +{ + if (m_done) + { + m_invoke_count = -1; + return; + } + + observer_ptr o(new (m_rpc.allocator().malloc()) find_data_observer(this, id, m_target)); + m_rpc.invoke(messages::get_peers, addr, o); +} + +void find_data::got_data(msg const* m) +{ + m_done = true; + m_done_callback(m); +} + +void find_data::done() +{ + if (m_invoke_count != 0) return; + if (!m_done) m_done_callback(0); +} + +void find_data::initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) +{ + std::cerr << "find_data::initiate, key: " << target << "\n"; + new find_data(target, branch_factor, max_results, table, rpc, callback); +} + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/kademlia/node.cpp b/libtorrent/src/kademlia/node.cpp new file mode 100644 index 000000000..74641ec43 --- /dev/null +++ b/libtorrent/src/kademlia/node.cpp @@ -0,0 +1,497 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/io.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/random_sample.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/packet_iterator.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/node.hpp" + +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/closest_nodes.hpp" +#include "libtorrent/kademlia/find_data.hpp" + +using boost::bind; + +namespace libtorrent { namespace dht +{ + +#ifdef _MSC_VER +namespace +{ + char rand() { return (char)std::rand(); } +} +#endif + +// TODO: configurable? +enum { announce_interval = 30 }; + +using asio::ip::udp; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(node) +#endif + +node_id generate_id() +{ + char random[20]; + std::srand(std::time(0)); +#ifdef _MSC_VER + std::generate(random, random + 20, &rand); +#else + std::generate(random, random + 20, &std::rand); +#endif + + hasher h; + h.update(random, 20); + return h.final(); +} + +// remove peers that have timed out +void purge_peers(std::set& peers) +{ + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end;) + { + // the peer has timed out + if (i->added + minutes(int(announce_interval * 1.5f)) < time_now()) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "peer timed out at: " << i->addr.address(); +#endif + peers.erase(i++); + } + else + ++i; + } +} + +void nop() {} + +node_impl::node_impl(boost::function const& f + , dht_settings const& settings, boost::optional node_id) + : m_settings(settings) + , m_id(node_id ? *node_id : generate_id()) + , m_table(m_id, 8, settings) + , m_rpc(bind(&node_impl::incoming_request, this, _1) + , m_id, m_table, f) + , m_last_tracker_tick(time_now()) +{ + m_secret[0] = std::rand(); + m_secret[1] = std::rand(); +} + +bool node_impl::verify_token(msg const& m) +{ + if (m.write_token.type() != entry::string_t) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "token of incorrect type " << m.write_token.type(); +#endif + return false; + } + std::string const& token = m.write_token.string(); + if (token.length() != 4) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "token of incorrect length: " << token.length(); +#endif + return false; + } + + hasher h1; + std::string address = m.addr.address().to_string(); + h1.update(&address[0], address.length()); + h1.update((char*)&m_secret[0], sizeof(m_secret[0])); + h1.update((char*)&m.info_hash[0], sha1_hash::size); + + sha1_hash h = h1.final(); + if (std::equal(token.begin(), token.end(), (signed char*)&h[0])) + return true; + + hasher h2; + h2.update(&address[0], address.length()); + h2.update((char*)&m_secret[1], sizeof(m_secret[1])); + h2.update((char*)&m.info_hash[0], sha1_hash::size); + h = h2.final(); + if (std::equal(token.begin(), token.end(), (signed char*)&h[0])) + return true; + return false; +} + +entry node_impl::generate_token(msg const& m) +{ + std::string token; + token.resize(4); + hasher h; + std::string address = m.addr.address().to_string(); + h.update(&address[0], address.length()); + h.update((char*)&m_secret[0], sizeof(m_secret[0])); + h.update((char*)&m.info_hash[0], sha1_hash::size); + + sha1_hash hash = h.final(); + std::copy(hash.begin(), hash.begin() + 4, (signed char*)&token[0]); + return entry(token); +} + +void node_impl::refresh(node_id const& id + , boost::function0 f) +{ + // use the 'bucket size' closest nodes + // to start the refresh with + std::vector start; + start.reserve(m_table.bucket_size()); + m_table.find_node(id, start, false); + refresh::initiate(id, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, f); +} + +void node_impl::bootstrap(std::vector const& nodes + , boost::function0 f) +{ + std::vector start; + start.reserve(nodes.size()); + std::copy(nodes.begin(), nodes.end(), std::back_inserter(start)); + refresh::initiate(m_id, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, f); +} + +void node_impl::refresh() +{ + std::vector start; + start.reserve(m_table.size().get<0>()); + std::copy(m_table.begin(), m_table.end(), std::back_inserter(start)); + + refresh::initiate(m_id, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, bind(&nop)); +} + +int node_impl::bucket_size(int bucket) +{ + return m_table.bucket_size(bucket); +} + +void node_impl::new_write_key() +{ + m_secret[1] = m_secret[0]; + m_secret[0] = std::rand(); +} + +void node_impl::refresh_bucket(int bucket) try +{ + assert(bucket >= 0 && bucket < 160); + + // generate a random node_id within the given bucket + node_id target = generate_id(); + int num_bits = 160 - bucket; + node_id mask(0); + for (int i = 0; i < num_bits; ++i) + { + int byte = i / 8; + mask[byte] |= 0x80 >> (i % 8); + } + + node_id root = m_id; + root &= mask; + target &= ~mask; + target |= root; + + // make sure this is in another subtree than m_id + // clear the (num_bits - 1) bit and then set it to the + // inverse of m_id's corresponding bit. + target[(num_bits - 1) / 8] &= ~(0x80 >> ((num_bits - 1) % 8)); + target[(num_bits - 1) / 8] |= + (~(m_id[(num_bits - 1) / 8])) & (0x80 >> ((num_bits - 1) % 8)); + + assert(distance_exp(m_id, target) == bucket); + + std::vector start; + start.reserve(m_table.bucket_size()); + m_table.find_node(target, start, false, m_table.bucket_size()); + + refresh::initiate(target, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, bind(&nop)); + m_table.touch_bucket(bucket); +} +catch (std::exception&) {} + +void node_impl::incoming(msg const& m) +{ + if (m_rpc.incoming(m)) + { + refresh(); + } +} + +namespace +{ + void announce_fun(std::vector const& v, rpc_manager& rpc + , int listen_port, sha1_hash const& ih + , boost::function const&, sha1_hash const&)> f) + { + bool nodes = false; + // only store on the first k nodes + for (std::vector::const_iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + rpc.invoke(messages::get_peers, i->addr, observer_ptr( + new (rpc.allocator().malloc()) get_peers_observer(ih, listen_port, rpc, f))); + nodes = true; + } + } +} + +void node_impl::add_router_node(udp::endpoint router) +{ + m_table.add_router_node(router); +} + +void node_impl::add_node(udp::endpoint node) +{ + // ping the node, and if we get a reply, it + // will be added to the routing table + observer_ptr o(new (m_rpc.allocator().malloc()) null_observer(m_rpc.allocator())); + m_rpc.invoke(messages::ping, node, o); +} + +void node_impl::announce(sha1_hash const& info_hash, int listen_port + , boost::function const&, sha1_hash const&)> f) +{ + // search for nodes with ids close to id, and then invoke the + // get_peers and then announce_peer rpc on them. + closest_nodes::initiate(info_hash, m_settings.search_branching + , m_table.bucket_size(), m_table, m_rpc + , boost::bind(&announce_fun, _1, boost::ref(m_rpc), listen_port + , info_hash, f)); +} + +time_duration node_impl::refresh_timeout() +{ + int refresh = -1; + ptime now = time_now(); + ptime next = now + minutes(15); + try + { + for (int i = 0; i < 160; ++i) + { + ptime r = m_table.next_refresh(i); + if (r <= next) + { + refresh = i; + next = r; + } + } + if (next < now) + { + assert(refresh > -1); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "refreshing bucket: " << refresh; +#endif + refresh_bucket(refresh); + } + } + catch (std::exception&) {} + + time_duration next_refresh = next - now; + time_duration min_next_refresh + = minutes(15) / (m_table.num_active_buckets()); + if (min_next_refresh > seconds(40)) + min_next_refresh = seconds(40); + + if (next_refresh < min_next_refresh) + next_refresh = min_next_refresh; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "next refresh: " << total_seconds(next_refresh) << " seconds"; +#endif + + return next_refresh; +} + +time_duration node_impl::connection_timeout() +{ + time_duration d = m_rpc.tick(); + try + { + ptime now(time_now()); + if (now - m_last_tracker_tick < minutes(10)) return d; + m_last_tracker_tick = now; + + // look through all peers and see if any have timed out + for (data_iterator i = begin_data(), end(end_data()); i != end;) + { + torrent_entry& t = i->second; + node_id const& key = i->first; + ++i; + purge_peers(t.peers); + + // if there are no more peers, remove the entry altogether + if (t.peers.empty()) + { + table_t::iterator i = m_map.find(key); + if (i != m_map.end()) m_map.erase(i); + } + } + } + catch (std::exception&) {} + + return d; +} + +void node_impl::on_announce(msg const& m, msg& reply) +{ + if (!verify_token(m)) + { + reply.message_id = messages::error; + reply.error_code = 203; + reply.error_msg = "Incorrect token in announce_peer"; + return; + } + + // the token was correct. That means this + // node is not spoofing its address. So, let + // the table get a chance to add it. + m_table.node_seen(m.id, m.addr); + + torrent_entry& v = m_map[m.info_hash]; + peer_entry e; + e.addr = tcp::endpoint(m.addr.address(), m.addr.port()); + e.added = time_now(); + std::set::iterator i = v.peers.find(e); + if (i != v.peers.end()) v.peers.erase(i++); + v.peers.insert(i, e); +} + +namespace +{ + tcp::endpoint get_endpoint(peer_entry const& p) + { + return p.addr; + } +} + +bool node_impl::on_find(msg const& m, std::vector& peers) const +{ + table_t::const_iterator i = m_map.find(m.info_hash); + if (i == m_map.end()) return false; + + torrent_entry const& v = i->second; + + int num = (std::min)((int)v.peers.size(), m_settings.max_peers_reply); + peers.clear(); + peers.reserve(num); + random_sample_n(boost::make_transform_iterator(v.peers.begin(), &get_endpoint) + , boost::make_transform_iterator(v.peers.end(), &get_endpoint) + , std::back_inserter(peers), num); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + TORRENT_LOG(node) << " " << *i; + } +#endif + return true; +} + +void node_impl::incoming_request(msg const& m) +{ + msg reply; + reply.message_id = m.message_id; + reply.addr = m.addr; + reply.reply = true; + reply.transaction_id = m.transaction_id; + + switch (m.message_id) + { + case messages::ping: + break; + case messages::get_peers: + { + reply.info_hash = m.info_hash; + reply.write_token = generate_token(m); + + if (!on_find(m, reply.peers)) + { + // we don't have any peers for this info_hash, + // return nodes instead + m_table.find_node(m.info_hash, reply.nodes, false); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + for (std::vector::iterator i = reply.nodes.begin() + , end(reply.nodes.end()); i != end; ++i) + { + TORRENT_LOG(node) << " " << i->id << " " << i->addr; + } +#endif + } + } + break; + case messages::find_node: + { + reply.info_hash = m.info_hash; + + m_table.find_node(m.info_hash, reply.nodes, false); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + for (std::vector::iterator i = reply.nodes.begin() + , end(reply.nodes.end()); i != end; ++i) + { + TORRENT_LOG(node) << " " << i->id << " " << i->addr; + } +#endif + } + break; + case messages::announce_peer: + on_announce(m, reply); + break; + default: + assert(false); + }; + + if (m_table.need_node(m.id)) + m_rpc.reply_with_ping(reply); + else + m_rpc.reply(reply); +} + + +} } // namespace libtorrent::dht diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp new file mode 100644 index 000000000..4ed413714 --- /dev/null +++ b/libtorrent/src/kademlia/node_id.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include + +#include "libtorrent/kademlia/node_id.hpp" + +using boost::bind; + +namespace libtorrent { namespace dht +{ + +// returns the distance between the two nodes +// using the kademlia XOR-metric +node_id distance(node_id const& n1, node_id const& n2) +{ + node_id ret; + node_id::iterator k = ret.begin(); + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , end(n1.end()); i != end; ++i, ++j, ++k) + { + *k = *i ^ *j; + } + return ret; +} + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref) +{ + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , k = ref.begin(), end(n1.end()); i != end; ++i, ++j, ++k) + { + boost::uint8_t lhs = (*i ^ *k); + boost::uint8_t rhs = (*j ^ *k); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; +} + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// useful for finding out which bucket a node belongs to +int distance_exp(node_id const& n1, node_id const& n2) +{ + int byte = node_id::size - 1; + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , end(n1.end()); i != end; ++i, ++j, --byte) + { + assert(byte >= 0); + boost::uint8_t t = *i ^ *j; + if (t == 0) continue; + // we have found the first non-zero byte + // return the bit-number of the first bit + // that differs + int bit = byte * 8; + for (int b = 7; b > 0; --b) + if (t >= (1 << b)) return bit + b; + return bit; + } + + return 0; +} + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/kademlia/refresh.cpp b/libtorrent/src/kademlia/refresh.cpp new file mode 100644 index 000000000..ce94ca93b --- /dev/null +++ b/libtorrent/src/kademlia/refresh.cpp @@ -0,0 +1,174 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +using boost::bind; + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(refresh) +#endif + +refresh_observer::~refresh_observer() +{ + if (m_algorithm) m_algorithm->failed(m_self, true); +} + +void refresh_observer::reply(msg const& in) +{ + if (!m_algorithm) return; + + if (!in.nodes.empty()) + { + for (msg::nodes_t::const_iterator i = in.nodes.begin() + , end(in.nodes.end()); i != end; ++i) + { + m_algorithm->traverse(i->id, i->addr); + } + } + m_algorithm->finished(m_self); + m_algorithm = 0; +} + +void refresh_observer::timeout() +{ + if (!m_algorithm) return; + m_algorithm->failed(m_self); + m_algorithm = 0; +} + +ping_observer::~ping_observer() +{ + if (m_algorithm) m_algorithm->ping_timeout(m_self, true); +} + +void ping_observer::reply(msg const& m) +{ + if (!m_algorithm) return; + + m_algorithm->ping_reply(m_self); + m_algorithm = 0; +} + +void ping_observer::timeout() +{ + if (!m_algorithm) return; + m_algorithm->ping_timeout(m_self); + m_algorithm = 0; +} + +void refresh::invoke(node_id const& nid, udp::endpoint addr) +{ + observer_ptr o(new (m_rpc.allocator().malloc()) refresh_observer( + this, nid, m_target)); + + m_rpc.invoke(messages::find_node, addr, o); +} + +void refresh::done() +{ + m_leftover_nodes_iterator = (int)m_results.size() > m_max_results ? + m_results.begin() + m_max_results : m_results.end(); + + invoke_pings_or_finish(); +} + +void refresh::ping_reply(node_id nid) +{ + m_active_pings--; + invoke_pings_or_finish(); +} + +void refresh::ping_timeout(node_id nid, bool prevent_request) +{ + m_active_pings--; + invoke_pings_or_finish(prevent_request); +} + +void refresh::invoke_pings_or_finish(bool prevent_request) +{ + if (prevent_request) + { + --m_max_active_pings; + if (m_max_active_pings <= 0) + m_max_active_pings = 1; + } + else + { + while (m_active_pings < m_max_active_pings) + { + if (m_leftover_nodes_iterator == m_results.end()) break; + + result const& node = *m_leftover_nodes_iterator; + + // Skip initial nodes + if (node.flags & result::initial) + { + ++m_leftover_nodes_iterator; + continue; + } + + try + { + observer_ptr o(new (m_rpc.allocator().malloc()) ping_observer( + this, node.id)); + m_rpc.invoke(messages::ping, node.addr, o); + ++m_active_pings; + ++m_leftover_nodes_iterator; + } + catch (std::exception& e) {} + } + } + + if (m_active_pings == 0) + { + m_done_callback(); + } +} + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/kademlia/routing_table.cpp b/libtorrent/src/kademlia/routing_table.cpp new file mode 100644 index 000000000..45091481c --- /dev/null +++ b/libtorrent/src/kademlia/routing_table.cpp @@ -0,0 +1,448 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/session_settings.hpp" + +using boost::bind; +using boost::uint8_t; + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; +typedef asio::ip::address_v4 address; + +routing_table::routing_table(node_id const& id, int bucket_size + , dht_settings const& settings) + : m_bucket_size(bucket_size) + , m_settings(settings) + , m_id(id) + , m_lowest_active_bucket(160) +{ + // distribute the refresh times for the buckets in an + // attempt do even out the network load + for (int i = 0; i < 160; ++i) + m_bucket_activity[i] = time_now() - milliseconds(i*5625); + m_bucket_activity[0] = time_now() - minutes(15); +} + +boost::tuple routing_table::size() const +{ + int nodes = 0; + int replacements = 0; + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + nodes += i->first.size(); + replacements += i->second.size(); + } + return boost::make_tuple(nodes, replacements); +} + +size_type routing_table::num_global_nodes() const +{ + int first_full = m_lowest_active_bucket; + int num_nodes = 1; // we are one of the nodes + for (; first_full < 160 + && int(m_buckets[first_full].first.size()) < m_bucket_size; + ++first_full) + { + num_nodes += m_buckets[first_full].first.size(); + } + + return (2 << (160 - first_full)) * num_nodes; +} + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + +void routing_table::print_state(std::ostream& os) const +{ + os << "kademlia routing table state\n" + << "bucket_size: " << m_bucket_size << "\n" + << "global node count: " << num_global_nodes() << "\n" + << "node_id: " << m_id << "\n\n"; + + os << "number of nodes per bucket:\n-- live "; + for (int i = 8; i < 160; ++i) + os << "-"; + os << "\n"; + + for (int k = 0; k < 8; ++k) + { + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + os << (int(i->first.size()) > (7 - k) ? "|" : " "); + } + os << "\n"; + } + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + os << "+"; + } + os << "\n"; + for (int k = 0; k < 8; ++k) + { + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + os << (int(i->second.size()) > k ? "|" : " "); + } + os << "\n"; + } + os << "-- cached "; + for (int i = 10; i < 160; ++i) + os << "-"; + os << "\n\n"; + + os << "nodes:\n"; + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + int bucket_index = int(i - m_buckets.begin()); + os << "=== BUCKET = " << bucket_index + << " = " << (bucket_index >= m_lowest_active_bucket?"active":"inactive") + << " = " << total_seconds(time_now() - m_bucket_activity[bucket_index]) + << " s ago ===== \n"; + for (bucket_t::const_iterator j = i->first.begin() + , end(i->first.end()); j != end; ++j) + { + os << "ip: " << j->addr << " fails: " << j->fail_count + << " id: " << j->id << "\n"; + } + } +} + +#endif + +void routing_table::touch_bucket(int bucket) +{ + m_bucket_activity[bucket] = time_now(); +} + +ptime routing_table::next_refresh(int bucket) +{ + assert(bucket < 160); + assert(bucket >= 0); + // lower than or equal to since a refresh of bucket 0 will + // effectively refresh the lowest active bucket as well + if (bucket < m_lowest_active_bucket && bucket > 0) + return time_now() + minutes(15); + return m_bucket_activity[bucket] + minutes(15); +} + +void routing_table::replacement_cache(bucket_t& nodes) const +{ + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + std::copy(i->second.begin(), i->second.end() + , std::back_inserter(nodes)); + } +} + +bool routing_table::need_node(node_id const& id) +{ + int bucket_index = distance_exp(m_id, id); + assert(bucket_index < (int)m_buckets.size()); + assert(bucket_index >= 0); + bucket_t& b = m_buckets[bucket_index].first; + bucket_t& rb = m_buckets[bucket_index].second; + + // if the replacement cache is full, we don't + // need another node. The table is fine the + // way it is. + if ((int)rb.size() >= m_bucket_size) return false; + + // if the node already exists, we don't need it + if (std::find_if(b.begin(), b.end(), bind(&node_entry::id, _1) == id) + != b.end()) return false; + + if (std::find_if(rb.begin(), rb.end(), bind(&node_entry::id, _1) == id) + != rb.end()) return false; + + return true; +} + +void routing_table::node_failed(node_id const& id) +{ + int bucket_index = distance_exp(m_id, id); + assert(bucket_index < (int)m_buckets.size()); + assert(bucket_index >= 0); + bucket_t& b = m_buckets[bucket_index].first; + bucket_t& rb = m_buckets[bucket_index].second; + + bucket_t::iterator i = std::find_if(b.begin(), b.end() + , bind(&node_entry::id, _1) == id); + + if (i == b.end()) return; + + // if messages to ourself fails, ignore it + if (bucket_index == 0) return; + + if (rb.empty()) + { + ++i->fail_count; + if (i->fail_count >= m_settings.max_fail_count) + { + b.erase(i); + assert(m_lowest_active_bucket <= bucket_index); + while (m_buckets[m_lowest_active_bucket].first.empty() + && m_lowest_active_bucket < 160) + { + ++m_lowest_active_bucket; + } + } + return; + } + + b.erase(i); + b.push_back(rb.back()); + rb.erase(rb.end() - 1); +} + +void routing_table::add_router_node(udp::endpoint router) +{ + m_router_nodes.insert(router); +} + +// this function is called every time the node sees +// a sign of a node being alive. This node will either +// be inserted in the k-buckets or be moved to the top +// of its bucket. +// the return value indicates if the table needs a refresh. +// if true, the node should refresh the table (i.e. do a find_node +// on its own id) +bool routing_table::node_seen(node_id const& id, udp::endpoint addr) +{ + if (m_router_nodes.find(addr) != m_router_nodes.end()) return false; + int bucket_index = distance_exp(m_id, id); + assert(bucket_index < (int)m_buckets.size()); + assert(bucket_index >= 0); + bucket_t& b = m_buckets[bucket_index].first; + + bucket_t::iterator i = std::find_if(b.begin(), b.end() + , bind(&node_entry::id, _1) == id); + + bool ret = need_bootstrap(); + + //m_bucket_activity[bucket_index] = time_now(); + + if (i != b.end()) + { + // TODO: what do we do if we see a node with + // the same id as a node at a different address? +// assert(i->addr == addr); + + // we already have the node in our bucket + // just move it to the back since it was + // the last node we had any contact with + // in this bucket + b.erase(i); + b.push_back(node_entry(id, addr)); +// TORRENT_LOG(table) << "replacing node: " << id << " " << addr; + return ret; + } + + // if the node was not present in our list + // we will only insert it if there is room + // for it, or if some of our nodes have gone + // offline + if ((int)b.size() < m_bucket_size) + { + if (b.empty()) b.reserve(m_bucket_size); + b.push_back(node_entry(id, addr)); + // if bucket index is 0, the node is ourselves + // don't updated m_lowest_active_bucket + if (bucket_index < m_lowest_active_bucket + && bucket_index > 0) + m_lowest_active_bucket = bucket_index; +// TORRENT_LOG(table) << "inserting node: " << id << " " << addr; + return ret; + } + + // if there is no room, we look for nodes marked as stale + // in the k-bucket. If we find one, we can replace it. + // A node is considered stale if it has failed at least one + // time. Here we choose the node that has failed most times. + // If we don't find one, place this node in the replacement- + // cache and replace any nodes that will fail in the future + // with nodes from that cache. + + i = std::max_element(b.begin(), b.end() + , bind(&node_entry::fail_count, _1) + < bind(&node_entry::fail_count, _2)); + + if (i != b.end() && i->fail_count > 0) + { + // i points to a node that has been marked + // as stale. Replace it with this new one + b.erase(i); + b.push_back(node_entry(id, addr)); +// TORRENT_LOG(table) << "replacing stale node: " << id << " " << addr; + return ret; + } + + // if we don't have any identified stale nodes in + // the bucket, and the bucket is full, we have to + // cache this node and wait until some node fails + // and then replace it. + + bucket_t& rb = m_buckets[bucket_index].second; + + i = std::find_if(rb.begin(), rb.end() + , bind(&node_entry::id, _1) == id); + + // if the node is already in the replacement bucket + // just return. + if (i != rb.end()) return ret; + + if ((int)rb.size() > m_bucket_size) rb.erase(rb.begin()); + if (rb.empty()) rb.reserve(m_bucket_size); + rb.push_back(node_entry(id, addr)); +// TORRENT_LOG(table) << "inserting node in replacement cache: " << id << " " << addr; + return ret; +} + +bool routing_table::need_bootstrap() const +{ + for (const_iterator i = begin(); i != end(); ++i) + { + if (i->fail_count == 0) return false; + } + return true; +} + +// fills the vector with the k nodes from our buckets that +// are nearest to the given id. +void routing_table::find_node(node_id const& target + , std::vector& l, bool include_self, int count) +{ + l.clear(); + if (count == 0) count = m_bucket_size; + l.reserve(count); + + int bucket_index = distance_exp(m_id, target); + bucket_t& b = m_buckets[bucket_index].first; + + // copy all nodes that hasn't failed into the target + // vector. + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) + , bind(&node_entry::fail_count, _1)); + assert((int)l.size() <= count); + + if ((int)l.size() == count) + { + assert(std::count_if(l.begin(), l.end() + , boost::bind(&node_entry::fail_count, _1) != 0) == 0); + return; + } + + // if we didn't have enough nodes in that bucket + // we have to reply with nodes from buckets closer + // to us. i.e. all the buckets in the range + // [0, bucket_index) if we are to include ourself + // or [1, bucket_index) if not. + bucket_t tmpb; + for (int i = include_self?0:1; i < count; ++i) + { + bucket_t& b = m_buckets[i].first; + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(tmpb) + , bind(&node_entry::fail_count, _1)); + } + + std::random_shuffle(tmpb.begin(), tmpb.end()); + size_t to_copy = (std::min)(m_bucket_size - l.size() + , tmpb.size()); + std::copy(tmpb.begin(), tmpb.begin() + to_copy + , std::back_inserter(l)); + + assert((int)l.size() <= m_bucket_size); + + // return if we have enough nodes or if the bucket index + // is the biggest index available (there are no more buckets) + // to look in. + if ((int)l.size() == count + || bucket_index == (int)m_buckets.size() - 1) + { + assert(std::count_if(l.begin(), l.end() + , boost::bind(&node_entry::fail_count, _1) != 0) == 0); + return; + } + + for (size_t i = bucket_index + 1; i < m_buckets.size(); ++i) + { + bucket_t& b = m_buckets[i].first; + + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) + , bind(&node_entry::fail_count, _1)); + if ((int)l.size() >= count) + { + l.erase(l.begin() + count, l.end()); + assert(std::count_if(l.begin(), l.end() + , boost::bind(&node_entry::fail_count, _1) != 0) == 0); + return; + } + } + assert((int)l.size() == count + || std::distance(l.begin(), l.end()) < m_bucket_size); + assert((int)l.size() <= count); + + assert(std::count_if(l.begin(), l.end() + , boost::bind(&node_entry::fail_count, _1) != 0) == 0); +} + +routing_table::iterator routing_table::begin() const +{ + return iterator(m_buckets.begin(), m_buckets.end()); +} + +routing_table::iterator routing_table::end() const +{ + return iterator(m_buckets.end(), m_buckets.end()); +} + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp new file mode 100644 index 000000000..93eac8565 --- /dev/null +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -0,0 +1,430 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" +#include "libtorrent/socket.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using boost::shared_ptr; +using boost::bind; + +namespace libtorrent { namespace dht +{ + +namespace io = libtorrent::detail; +namespace mpl = boost::mpl; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(rpc) +#endif + +void intrusive_ptr_add_ref(observer const* o) +{ + assert(o->m_refs >= 0); + assert(o != 0); + ++o->m_refs; +} + +void intrusive_ptr_release(observer const* o) +{ + assert(o->m_refs > 0); + assert(o != 0); + if (--o->m_refs == 0) + { + boost::pool<>& p = o->pool_allocator; + o->~observer(); + p.ordered_free(const_cast(o)); + } +} + +node_id generate_id(); + +typedef mpl::vector< + closest_nodes_observer + , find_data_observer + , announce_observer + , get_peers_observer + , refresh_observer + , ping_observer + , null_observer + > observer_types; + +typedef mpl::max_element< + mpl::transform_view > + >::type max_observer_type_iter; + +rpc_manager::rpc_manager(fun const& f, node_id const& our_id + , routing_table& table, send_fun const& sf) + : m_pool_allocator(sizeof(mpl::deref::type)) + , m_next_transaction_id(rand() % max_transactions) + , m_oldest_transaction_id(m_next_transaction_id) + , m_incoming(f) + , m_send(sf) + , m_our_id(our_id) + , m_table(table) + , m_timer(time_now()) + , m_random_number(generate_id()) + , m_destructing(false) +{ + std::srand(time(0)); +} + +rpc_manager::~rpc_manager() +{ + m_destructing = true; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Destructing"; +#endif + std::for_each(m_aborted_transactions.begin(), m_aborted_transactions.end() + , bind(&observer::abort, _1)); + + for (transactions_t::iterator i = m_transactions.begin() + , end(m_transactions.end()); i != end; ++i) + { + if (*i) (*i)->abort(); + } +} + +#ifndef NDEBUG +void rpc_manager::check_invariant() const +{ + assert(m_oldest_transaction_id >= 0); + assert(m_oldest_transaction_id < max_transactions); + assert(m_next_transaction_id >= 0); + assert(m_next_transaction_id < max_transactions); + assert(!m_transactions[m_next_transaction_id]); + + for (int i = (m_next_transaction_id + 1) % max_transactions; + i != m_oldest_transaction_id; i = (i + 1) % max_transactions) + { + assert(!m_transactions[i]); + } +} +#endif + +bool rpc_manager::incoming(msg const& m) +{ + INVARIANT_CHECK; + + if (m_destructing) return false; + + if (m.reply) + { + // if we don't have the transaction id in our + // request list, ignore the packet + + if (m.transaction_id.size() < 2) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with invalid transaction id size: " + << m.transaction_id.size() << " from " << m.addr; +#endif + msg reply; + reply.reply = true; + reply.message_id = messages::error; + reply.error_code = 203; // Protocol error + reply.error_msg = "reply with invalid transaction id, size " + + boost::lexical_cast(m.transaction_id.size()); + reply.addr = m.addr; + reply.transaction_id = ""; + m_send(reply); + return false; + } + + std::string::const_iterator i = m.transaction_id.begin(); + int tid = io::read_uint16(i); + + if (tid >= (int)m_transactions.size() + || tid < 0) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with invalid transaction id: " + << tid << " from " << m.addr; +#endif + msg reply; + reply.reply = true; + reply.message_id = messages::error; + reply.error_code = 203; // Protocol error + reply.error_msg = "reply with invalid transaction id"; + reply.addr = m.addr; + reply.transaction_id = ""; + m_send(reply); + return false; + } + + observer_ptr o = m_transactions[tid]; + + if (!o) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with unknown transaction id: " + << tid << " from " << m.addr << " (possibly timed out)"; +#endif + return false; + } + + if (m.addr != o->target_addr) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with incorrect address and valid transaction id: " + << tid << " from " << m.addr; +#endif + return false; + } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + std::ofstream reply_stats("libtorrent_logs/round_trip_ms.log", std::ios::app); + reply_stats << m.addr << "\t" << total_milliseconds(time_now() - o->sent) + << std::endl; +#endif + o->reply(m); + m_transactions[tid] = 0; + + if (m.piggy_backed_ping) + { + // there is a ping request piggy + // backed in this reply + msg ph; + ph.message_id = messages::ping; + ph.transaction_id = m.ping_transaction_id; + ph.addr = m.addr; + ph.reply = true; + + reply(ph); + } + return m_table.node_seen(m.id, m.addr); + } + else + { + assert(m.message_id != messages::error); + // this is an incoming request + m_incoming(m); + } + return false; +} + +time_duration rpc_manager::tick() +{ + INVARIANT_CHECK; + + const int timeout_ms = 10 * 1000; + + // look for observers that has timed out + + if (m_next_transaction_id == m_oldest_transaction_id) return milliseconds(timeout_ms); + + std::vector timeouts; + + for (;m_next_transaction_id != m_oldest_transaction_id; + m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions) + { + assert(m_oldest_transaction_id >= 0); + assert(m_oldest_transaction_id < max_transactions); + + observer_ptr o = m_transactions[m_oldest_transaction_id]; + if (!o) continue; + + time_duration diff = o->sent + milliseconds(timeout_ms) - time_now(); + if (diff > seconds(0)) + { + if (diff < seconds(1)) return seconds(1); + return diff; + } + + try + { + m_transactions[m_oldest_transaction_id] = 0; + timeouts.push_back(o); + } catch (std::exception) {} + } + + std::for_each(timeouts.begin(), timeouts.end(), bind(&observer::timeout, _1)); + timeouts.clear(); + + // clear the aborted transactions, will likely + // generate new requests. We need to swap, since the + // destrutors may add more observers to the m_aborted_transactions + std::vector().swap(m_aborted_transactions); + return milliseconds(timeout_ms); +} + +unsigned int rpc_manager::new_transaction_id(observer_ptr o) +{ + INVARIANT_CHECK; + + unsigned int tid = m_next_transaction_id; + m_next_transaction_id = (m_next_transaction_id + 1) % max_transactions; + if (m_transactions[m_next_transaction_id]) + { + // moving the observer into the set of aborted transactions + // it will prevent it from spawning new requests right now, + // since that would break the invariant + m_aborted_transactions.push_back(m_transactions[m_next_transaction_id]); + m_transactions[m_next_transaction_id] = 0; + assert(m_oldest_transaction_id == m_next_transaction_id); + } + assert(!m_transactions[tid]); + m_transactions[tid] = o; + if (m_oldest_transaction_id == m_next_transaction_id) + { + m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "WARNING: transaction limit reached! Too many concurrent" + " messages! limit: " << (int)max_transactions; +#endif + update_oldest_transaction_id(); + } + + return tid; +} + +void rpc_manager::update_oldest_transaction_id() +{ + INVARIANT_CHECK; + + assert(m_oldest_transaction_id != m_next_transaction_id); + while (!m_transactions[m_oldest_transaction_id]) + { + m_oldest_transaction_id = (m_oldest_transaction_id + 1) + % max_transactions; + if (m_oldest_transaction_id == m_next_transaction_id) + break; + } +} + +void rpc_manager::invoke(int message_id, udp::endpoint target_addr + , observer_ptr o) +{ + INVARIANT_CHECK; + + if (m_destructing) + { + o->abort(); + return; + } + + msg m; + m.message_id = message_id; + m.reply = false; + m.id = m_our_id; + m.addr = target_addr; + assert(!m_transactions[m_next_transaction_id]); +#ifndef NDEBUG + int potential_new_id = m_next_transaction_id; +#endif + try + { + m.transaction_id.clear(); + std::back_insert_iterator out(m.transaction_id); + io::write_uint16(m_next_transaction_id, out); + + o->send(m); + + o->sent = time_now(); + o->target_addr = target_addr; + + #ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Invoking " << messages::ids[message_id] + << " -> " << target_addr; + #endif + m_send(m); + new_transaction_id(o); + } + catch (std::exception& e) + { + // m_send may fail with "no route to host" + assert(potential_new_id == m_next_transaction_id); + o->abort(); + } +} + +void rpc_manager::reply(msg& m) +{ + INVARIANT_CHECK; + + if (m_destructing) return; + + assert(m.reply); + m.piggy_backed_ping = false; + m.id = m_our_id; + + m_send(m); +} + +void rpc_manager::reply_with_ping(msg& m) +{ + INVARIANT_CHECK; + + if (m_destructing) return; + assert(m.reply); + + m.piggy_backed_ping = true; + m.id = m_our_id; + + m.ping_transaction_id.clear(); + std::back_insert_iterator out(m.ping_transaction_id); + io::write_uint16(m_next_transaction_id, out); + + observer_ptr o(new (allocator().malloc()) null_observer(allocator())); + assert(!m_transactions[m_next_transaction_id]); + o->sent = time_now(); + o->target_addr = m.addr; + + m_send(m); + new_transaction_id(o); +} + + + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/kademlia/traversal_algorithm.cpp b/libtorrent/src/kademlia/traversal_algorithm.cpp new file mode 100644 index 000000000..ceb977f19 --- /dev/null +++ b/libtorrent/src/kademlia/traversal_algorithm.cpp @@ -0,0 +1,184 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include + +#include + +using boost::bind; +using asio::ip::udp; + +namespace libtorrent { namespace dht +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(traversal) +#endif + +void traversal_algorithm::add_entry(node_id const& id, udp::endpoint addr, unsigned char flags) +{ + if (m_failed.find(addr) != m_failed.end()) return; + + result const entry(id, addr, flags); + + std::vector::iterator i = std::lower_bound( + m_results.begin() + , m_results.end() + , entry + , bind( + compare_ref + , bind(&result::id, _1) + , bind(&result::id, _2) + , m_target + ) + ); + + if (i == m_results.end() || i->id != id) + { + assert(std::find_if(m_results.begin(), m_results.end() + , bind(&result::id, _1) == id) == m_results.end()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "adding result: " << id << " " << addr; +#endif + m_results.insert(i, entry); + } +} + +boost::pool<>& traversal_algorithm::allocator() const +{ + return m_rpc.allocator(); +} + +void traversal_algorithm::traverse(node_id const& id, udp::endpoint addr) +{ + add_entry(id, addr, 0); +} + +void traversal_algorithm::finished(node_id const& id) +{ + --m_invoke_count; + add_requests(); + if (m_invoke_count == 0) done(); +} + +// prevent request means that the total number of requests has +// overflown. This query failed because it was the oldest one. +// So, if this is true, don't make another request +void traversal_algorithm::failed(node_id const& id, bool prevent_request) +{ + m_invoke_count--; + + std::vector::iterator i = std::find_if( + m_results.begin() + , m_results.end() + , bind( + std::equal_to() + , bind(&result::id, _1) + , id + ) + ); + + assert(i != m_results.end()); + + if (i != m_results.end()) + { + assert(i->flags & result::queried); + m_failed.insert(i->addr); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "failed: " << i->id << " " << i->addr; +#endif + m_results.erase(i); + } + if (prevent_request) + { + --m_branch_factor; + if (m_branch_factor <= 0) m_branch_factor = 1; + } + else + { + m_table.node_failed(id); + } + add_requests(); + if (m_invoke_count == 0) done(); +} + +namespace +{ + bool bitwise_nand(unsigned char lhs, unsigned char rhs) + { + return (lhs & rhs) == 0; + } +} + +void traversal_algorithm::add_requests() +{ + while (m_invoke_count < m_branch_factor) + { + // Find the first node that hasn't already been queried. + // TODO: Better heuristic + std::vector::iterator i = std::find_if( + m_results.begin() + , last_iterator() + , bind( + &bitwise_nand + , bind(&result::flags, _1) + , (unsigned char)result::queried + ) + ); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "nodes left (" << this << "): " << (last_iterator() - i); +#endif + + if (i == last_iterator()) break; + + try + { + invoke(i->id, i->addr); + ++m_invoke_count; + i->flags |= result::queried; + } + catch (std::exception& e) {} + } +} + +std::vector::iterator traversal_algorithm::last_iterator() +{ + return (int)m_results.size() >= m_max_results ? + m_results.begin() + m_max_results + : m_results.end(); +} + +} } // namespace libtorrent::dht + diff --git a/libtorrent/src/logger.cpp b/libtorrent/src/logger.cpp new file mode 100644 index 000000000..b33816a59 --- /dev/null +++ b/libtorrent/src/logger.cpp @@ -0,0 +1,233 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include + +#include "libtorrent/extensions/logger.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/peer_connection.hpp" + +namespace libtorrent { + +namespace fs = boost::filesystem; + +namespace +{ + + struct logger_peer_plugin : peer_plugin + { + logger_peer_plugin(std::string const& filename) + { + fs::path dir(fs::complete("libtorrent_ext_logs")); + if (!fs::exists(dir)) fs::create_directories(dir); + m_file.open((dir / filename).string().c_str(), std::ios_base::out | std::ios_base::out); + m_file << "\n\n\n"; + log_timestamp(); + m_file << "*** starting log ***\n"; + } + + void log_timestamp() + { + m_file << time_now_string() << ": "; + } + + // can add entries to the extension handshake + virtual void add_handshake(entry&) {} + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(entry const& h) + { + log_timestamp(); + m_file << "<== EXTENSION_HANDSHAKE\n"; + h.print(m_file); + return true; + } + + // returning true from any of the message handlers + // indicates that the plugin has handeled the message. + // it will break the plugin chain traversing and not let + // anyone else handle the message, including the default + // handler. + + virtual bool on_choke() + { + log_timestamp(); + m_file << "<== CHOKE\n"; + m_file.flush(); + return false; + } + + virtual bool on_unchoke() + { + log_timestamp(); + m_file << "<== UNCHOKE\n"; + m_file.flush(); + return false; + } + + virtual bool on_interested() + { + log_timestamp(); + m_file << "<== INTERESTED\n"; + m_file.flush(); + return false; + } + + virtual bool on_not_interested() + { + log_timestamp(); + m_file << "<== NOT_INTERESTED\n"; + m_file.flush(); + return false; + } + + virtual bool on_have(int index) + { + log_timestamp(); + m_file << "<== HAVE [" << index << "]\n"; + m_file.flush(); + return false; + } + + virtual bool on_bitfield(std::vector const& bitfield) + { + log_timestamp(); + m_file << "<== BITFIELD\n"; + m_file.flush(); + return false; + } + + virtual bool on_request(peer_request const& r) + { + log_timestamp(); + m_file << "<== REQUEST [ piece: " << r.piece << " | s: " << r.start + << " | l: " << r.length << " ]\n"; + m_file.flush(); + return false; + } + + virtual bool on_piece(peer_request const& r, char const*) + { + log_timestamp(); + m_file << "<== PIECE [ piece: " << r.piece << " | s: " << r.start + << " | l: " << r.length << " ]\n"; + m_file.flush(); + return false; + } + + virtual bool on_cancel(peer_request const& r) + { + log_timestamp(); + m_file << "<== CANCEL [ piece: " << r.piece << " | s: " << r.start + << " | l: " << r.length << " ]\n"; + m_file.flush(); + return false; + } + + // called when an extended message is received. If returning true, + // the message is not processed by any other plugin and if false + // is returned the next plugin in the chain will receive it to + // be able to handle it + virtual bool on_extended(int length + , int msg, buffer::const_interval body) + { return false; } + + virtual bool on_unknown_message(int length, int msg + , buffer::const_interval body) + { + if (body.left() < length) return false; + log_timestamp(); + m_file << "<== UNKNOWN [ msg: " << msg + << " | l: " << length << " ]\n"; + m_file.flush(); + return false; + } + + virtual void on_piece_pass(int index) + { + log_timestamp(); + m_file << "*** HASH PASSED *** [ piece: " << index << " ]\n"; + m_file.flush(); + } + + virtual void on_piece_failed(int index) + { + log_timestamp(); + m_file << "*** HASH FAILED *** [ piece: " << index << " ]\n"; + m_file.flush(); + } + + private: + std::ofstream m_file; + }; + + struct logger_plugin : torrent_plugin + { + virtual boost::shared_ptr new_connection( + peer_connection* pc) + { + return boost::shared_ptr(new logger_peer_plugin( + pc->remote().address().to_string() + "_" + + boost::lexical_cast(pc->remote().port()) + ".log")); + } + }; + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_logger_plugin(torrent*) + { + return boost::shared_ptr(new logger_plugin()); + } + +} + + diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp new file mode 100644 index 000000000..b73e407bc --- /dev/null +++ b/libtorrent/src/lsd.cpp @@ -0,0 +1,250 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/lsd.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/xml_parse.hpp" +#include +#include +#include +#include +#include +#include +#include + +using boost::bind; +using namespace libtorrent; + +namespace libtorrent +{ + // defined in upnp.cpp + address_v4 guess_local_address(asio::io_service&); +} + +address_v4 lsd::lsd_multicast_address; +udp::endpoint lsd::lsd_multicast_endpoint; + +lsd::lsd(io_service& ios, address const& listen_interface + , peer_callback_t const& cb) + : m_callback(cb) + , m_retry_count(0) + , m_socket(ios) + , m_broadcast_timer(ios) + , m_disabled(false) +{ + // Bittorrent Local discovery multicast address and port + lsd_multicast_address = address_v4::from_string("239.192.152.143"); + lsd_multicast_endpoint = udp::endpoint(lsd_multicast_address, 6771); + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log.open("lsd.log", std::ios::in | std::ios::out | std::ios::trunc); +#endif + assert(lsd_multicast_address.is_multicast()); + rebind(listen_interface); +} + +lsd::~lsd() {} + +void lsd::rebind(address const& listen_interface) +{ + address_v4 local_ip = address_v4::any(); + if (listen_interface.is_v4() && listen_interface != address_v4::any()) + { + local_ip = listen_interface.to_v4(); + } + + try + { + // the local interface hasn't changed + if (m_socket.is_open() + && m_socket.local_endpoint().address() == local_ip) + return; + + m_socket.close(); + + using namespace asio::ip::multicast; + + m_socket.open(udp::v4()); + m_socket.set_option(datagram_socket::reuse_address(true)); + m_socket.bind(udp::endpoint(local_ip, 6771)); + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "local ip: " << local_ip << std::endl; +#endif + + m_socket.set_option(join_group(lsd_multicast_address)); + m_socket.set_option(outbound_interface(local_ip)); + m_socket.set_option(enable_loopback(true)); + m_socket.set_option(hops(255)); + } + catch (std::exception& e) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "socket multicast error " << e.what() + << ". disabling local service discovery" << std::endl; +#endif + m_disabled = true; + return; + } + m_disabled = false; + + setup_receive(); +} + +void lsd::announce(sha1_hash const& ih, int listen_port) +{ + if (m_disabled) return; + + std::stringstream btsearch; + btsearch << "BT-SEARCH * HTTP/1.1\r\n" + "Host: 239.192.152.143:6771\r\n" + "Port: " << listen_port << "\r\n" + "Infohash: " << ih << "\r\n" + "\r\n\r\n"; + std::string const& msg = btsearch.str(); + + m_retry_count = 0; + asio::error_code ec; + m_socket.send_to(asio::buffer(msg.c_str(), msg.size() - 1) + , lsd_multicast_endpoint, 0, ec); + if (ec) + { + m_disabled = true; + return; + } + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " ==> announce: ih: " << ih << " port: " << listen_port << std::endl; +#endif + + m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_broadcast_timer.async_wait(bind(&lsd::resend_announce, this, _1, msg)); +} + +void lsd::resend_announce(asio::error_code const& e, std::string msg) try +{ + if (e) return; + + m_socket.send_to(asio::buffer(msg, msg.size() - 1) + , lsd_multicast_endpoint); + + ++m_retry_count; + if (m_retry_count >= 5) + return; + + m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_broadcast_timer.async_wait(bind(&lsd::resend_announce, this, _1, msg)); +} +catch (std::exception&) +{} + +void lsd::on_announce(asio::error_code const& e + , std::size_t bytes_transferred) +{ + using namespace libtorrent::detail; + if (e) return; + + char* p = m_receive_buffer; + char* end = m_receive_buffer + bytes_transferred; + char* line = std::find(p, end, '\n'); + for (char* i = p; i < line; ++i) *i = std::tolower(*i); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " <== announce: " << std::string(p, line) << std::endl; +#endif + if (line == end || (line - p >= 9 && std::memcmp("bt-search", p, 9))) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " *** assumed 'bt-search', ignoring" << std::endl; +#endif + setup_receive(); + return; + } + p = line + 1; + int port = 0; + sha1_hash ih(0); + while (p != end) + { + line = std::find(p, end, '\n'); + if (line == end) break; + *line = 0; + for (char* i = p; i < line; ++i) *i = std::tolower(*i); + if (line - p >= 5 && memcmp(p, "port:", 5) == 0) + { + p += 5; + while (*p == ' ') ++p; + port = atoi(p); + } + else if (line - p >= 9 && memcmp(p, "infohash:", 9) == 0) + { + p += 9; + while (*p == ' ') ++p; + if (line - p > 40) p[40] = 0; + try { ih = boost::lexical_cast(p); } + catch (std::exception&) {} + } + p = line + 1; + } + + if (!ih.is_all_zeros() && port != 0) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " *** incoming local announce " << m_remote.address() + << ":" << port << " ih: " << ih << std::endl; +#endif + // we got an announce, pass it on through the callback + try { m_callback(tcp::endpoint(m_remote.address(), port), ih); } + catch (std::exception&) {} + } + setup_receive(); +} + +void lsd::setup_receive() try +{ + assert(m_socket.is_open()); + m_socket.async_receive_from(asio::buffer(m_receive_buffer + , sizeof(m_receive_buffer)), m_remote, bind(&lsd::on_announce, this, _1, _2)); +} +catch (std::exception&) +{} + +void lsd::close() +{ + m_socket.close(); +} + diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp new file mode 100644 index 000000000..97635cdb9 --- /dev/null +++ b/libtorrent/src/metadata_transfer.cpp @@ -0,0 +1,566 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/metadata_transfer.hpp" +#include "libtorrent/alert_types.hpp" + +namespace libtorrent { namespace +{ + int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + + std::pair req_to_offset(std::pair req, int total_size) + { + assert(req.first >= 0); + assert(req.second > 0); + assert(req.second <= 256); + assert(req.first + req.second <= 256); + + int start = div_round_up(req.first * total_size, 256); + int size = div_round_up((req.first + req.second) * total_size, 256) - start; + return std::make_pair(start, size); + } + + std::pair offset_to_req(std::pair offset, int total_size) + { + int start = offset.first * 256 / total_size; + int size = (offset.first + offset.second) * 256 / total_size - start; + + std::pair ret(start, size); + + assert(start >= 0); + assert(size > 0); + assert(start <= 256); + assert(start + size <= 256); + + // assert the identity of this function +#ifndef NDEBUG + std::pair identity = req_to_offset(ret, total_size); + assert(offset == identity); +#endif + return ret; + } + + + struct metadata_plugin : torrent_plugin + { + metadata_plugin(torrent& t) + : m_torrent(t) + , m_metadata_progress(0) + , m_metadata_size(0) + { + m_requested_metadata.resize(256, 0); + } + + virtual void on_files_checked() + { + // if the torrent is a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + virtual boost::shared_ptr new_connection( + peer_connection* pc); + + std::vector const& metadata() const + { + if (m_metadata.empty()) + { + bencode(std::back_inserter(m_metadata) + , m_torrent.torrent_file().create_info_metadata()); + + assert(hasher(&m_metadata[0], m_metadata.size()).final() + == m_torrent.torrent_file().info_hash()); + } + assert(!m_metadata.empty()); + return m_metadata; + } + + bool received_metadata(char const* buf, int size, int offset, int total_size) + { + if (m_torrent.valid_metadata()) return false; + + if ((int)m_metadata.size() < total_size) + m_metadata.resize(total_size); + + std::copy( + buf + , buf + size + , &m_metadata[offset]); + + if (m_have_metadata.empty()) + m_have_metadata.resize(256, false); + + std::pair req = offset_to_req(std::make_pair(offset, size) + , total_size); + + assert(req.first + req.second <= (int)m_have_metadata.size()); + + std::fill( + m_have_metadata.begin() + req.first + , m_have_metadata.begin() + req.first + req.second + , true); + + bool have_all = std::count( + m_have_metadata.begin() + , m_have_metadata.end() + , true) == 256; + + if (!have_all) return false; + + hasher h; + h.update(&m_metadata[0], (int)m_metadata.size()); + sha1_hash info_hash = h.final(); + + if (info_hash != m_torrent.torrent_file().info_hash()) + { + std::fill( + m_have_metadata.begin() + , m_have_metadata.begin() + req.first + req.second + , false); + m_metadata_progress = 0; + m_metadata_size = 0; + + if (m_torrent.alerts().should_post(alert::info)) + { + m_torrent.alerts().post_alert(metadata_failed_alert( + m_torrent.get_handle(), "invalid metadata received from swarm")); + } + + return false; + } + + entry metadata = bdecode(m_metadata.begin(), m_metadata.end()); + m_torrent.set_metadata(metadata); + + // clear the storage for the bitfield + std::vector().swap(m_have_metadata); + std::vector().swap(m_requested_metadata); + + return true; + } + + // returns a range of the metadata that + // we should request. + std::pair metadata_request(); + + void cancel_metadata_request(std::pair req) + { + for (int i = req.first; i < req.first + req.second; ++i) + { + assert(m_requested_metadata[i] > 0); + if (m_requested_metadata[i] > 0) + --m_requested_metadata[i]; + } + } + + // this is called from the peer_connection for + // each piece of metadata it receives + void metadata_progress(int total_size, int received) + { + m_metadata_progress += received; + m_metadata_size = total_size; + } + + void piece_pass(int) + { + // if we became a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + private: + torrent& m_torrent; + + // this buffer is filled with the info-section of + // the metadata file while downloading it from + // peers, and while sending it. + // it is mutable because it's generated lazily + mutable std::vector m_metadata; + + int m_metadata_progress; + int m_metadata_size; + + // this is a bitfield of size 256, each bit represents + // a piece of the metadata. It is set to one if we + // have that piece. This vector may be empty + // (size 0) if we haven't received any metadata + // or if we already have all metadata + std::vector m_have_metadata; + // this vector keeps track of how many times each meatdata + // block has been requested + std::vector m_requested_metadata; + }; + + + struct metadata_peer_plugin : peer_plugin + { + metadata_peer_plugin(torrent& t, peer_connection& pc + , metadata_plugin& tp) + : m_waiting_metadata_request(false) + , m_message_index(0) + , m_metadata_progress(0) + , m_no_metadata(min_time()) + , m_metadata_request(min_time()) + , m_torrent(t) + , m_pc(pc) + , m_tp(tp) + {} + + // can add entries to the extension handshake + virtual void add_handshake(entry& h) + { + entry& messages = h["m"]; + messages["LT_metadata"] = 14; + } + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(entry const& h) + { + entry const& messages = h["m"]; + if (entry const* index = messages.find_key("LT_metadata")) + { + m_message_index = index->integer(); + return true; + } + else + { + m_message_index = 0; + return false; + } + } + + void write_metadata_request(std::pair req) + { + assert(req.first >= 0); + assert(req.second > 0); + assert(req.first + req.second <= 256); + assert(!m_pc.associated_torrent().expired()); + assert(!m_pc.associated_torrent().lock()->valid_metadata()); + + int start = req.first; + int size = req.second; + + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + buffer::interval i = m_pc.allocate_send_buffer(9); + + detail::write_uint32(1 + 1 + 3, i.begin); + detail::write_uint8(bt_peer_connection::msg_extended, i.begin); + detail::write_uint8(m_message_index, i.begin); + // means 'request data' + detail::write_uint8(0, i.begin); + detail::write_uint8(start, i.begin); + detail::write_uint8(size - 1, i.begin); + assert(i.begin == i.end); + m_pc.setup_send(); + } + + void write_metadata(std::pair req) + { + assert(req.first >= 0); + assert(req.second > 0); + assert(req.second <= 256); + assert(req.first + req.second <= 256); + assert(!m_pc.associated_torrent().expired()); + + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + // only send metadata if the torrent is non-private + if (m_torrent.valid_metadata() && !m_torrent.torrent_file().priv()) + { + std::pair offset + = req_to_offset(req, (int)m_tp.metadata().size()); + + buffer::interval i = m_pc.allocate_send_buffer(15 + offset.second); + + // yes, we have metadata, send it + detail::write_uint32(11 + offset.second, i.begin); + detail::write_uint8(bt_peer_connection::msg_extended, i.begin); + detail::write_uint8(m_message_index, i.begin); + // means 'data packet' + detail::write_uint8(1, i.begin); + detail::write_uint32((int)m_tp.metadata().size(), i.begin); + detail::write_uint32(offset.first, i.begin); + std::vector const& metadata = m_tp.metadata(); + std::copy(metadata.begin() + offset.first + , metadata.begin() + offset.first + offset.second, i.begin); + i.begin += offset.second; + assert(i.begin == i.end); + } + else + { + buffer::interval i = m_pc.allocate_send_buffer(4 + 3); + // we don't have the metadata, reply with + // don't have-message + detail::write_uint32(1 + 2, i.begin); + detail::write_uint8(bt_peer_connection::msg_extended, i.begin); + detail::write_uint8(m_message_index, i.begin); + // means 'have no data' + detail::write_uint8(2, i.begin); + assert(i.begin == i.end); + } + m_pc.setup_send(); + } + + virtual bool on_extended(int length + , int msg, buffer::const_interval body) + { + if (msg != 14) return false; + if (m_message_index == 0) return false; + + if (length > 500 * 1024) + throw protocol_error("LT_metadata message larger than 500 kB"); + + if (body.left() < 1) return true; + int type = detail::read_uint8(body.begin); + + switch (type) + { + case 0: // request + { + if (body.left() < 2) return true; + int start = detail::read_uint8(body.begin); + int size = detail::read_uint8(body.begin) + 1; + + if (length != 3) + { + // invalid metadata request + throw protocol_error("invalid metadata request"); + } + + write_metadata(std::make_pair(start, size)); + } + break; + case 1: // data + { + if (body.left() < 8) return true; + + int total_size = detail::read_int32(body.begin); + int offset = detail::read_int32(body.begin); + int data_size = length - 9; + + if (total_size > 500 * 1024) + throw protocol_error("metadata size larger than 500 kB"); + if (total_size <= 0) + throw protocol_error("invalid metadata size"); + if (offset > total_size || offset < 0) + throw protocol_error("invalid metadata offset"); + if (offset + data_size > total_size) + throw protocol_error("invalid metadata message"); + + m_tp.metadata_progress(total_size + , body.left() - m_metadata_progress); + m_metadata_progress = body.left(); + + if (body.left() < data_size) return true; + + m_waiting_metadata_request = false; + m_tp.received_metadata(body.begin, data_size + , offset, total_size); + m_metadata_progress = 0; + } + break; + case 2: // have no data + m_no_metadata = time_now(); + if (m_waiting_metadata_request) + m_tp.cancel_metadata_request(m_last_metadata_request); + m_waiting_metadata_request = false; + break; + default: + throw protocol_error("unknown metadata extension message: " + + boost::lexical_cast(type)); + } + return true; + } + + virtual void tick() + { + // if we don't have any metadata, and this peer + // supports the request metadata extension + // and we aren't currently waiting for a request + // reply. Then, send a request for some metadata. + if (!m_torrent.valid_metadata() + && m_message_index != 0 + && !m_waiting_metadata_request + && has_metadata()) + { + m_last_metadata_request = m_tp.metadata_request(); + write_metadata_request(m_last_metadata_request); + m_waiting_metadata_request = true; + m_metadata_request = time_now(); + } + } + + bool has_metadata() const + { + return time_now() - m_no_metadata > minutes(5); + } + + private: + + // this is set to true when we send a metadata + // request to this peer, and reset to false when + // we receive a reply to our request. + bool m_waiting_metadata_request; + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + // the number of bytes of metadata we have received + // so far from this per, only counting the current + // request. Any previously finished requests + // that have been forwarded to the torrent object + // do not count. + int m_metadata_progress; + + // this is set to the current time each time we get a + // "I don't have metadata" message. + ptime m_no_metadata; + + // this is set to the time when we last sent + // a request for metadata to this peer + ptime m_metadata_request; + + // if we're waiting for a metadata request + // this was the request we sent + std::pair m_last_metadata_request; + + torrent& m_torrent; + peer_connection& m_pc; + metadata_plugin& m_tp; + }; + + boost::shared_ptr metadata_plugin::new_connection( + peer_connection* pc) + { + bt_peer_connection* c = dynamic_cast(pc); + if (!c) return boost::shared_ptr(); + return boost::shared_ptr(new metadata_peer_plugin(m_torrent, *pc, *this)); + } + + std::pair metadata_plugin::metadata_request() + { + // count the number of peers that supports the + // extension and that has metadata + int peers = 0; +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::map conn_map; + for (conn_map::iterator i = m_torrent.begin() + , end(m_torrent.end()); i != end; ++i) + { + bt_peer_connection* c = dynamic_cast(i->second); + if (c == 0) continue; + metadata_peer_plugin* p + = c->supports_extension(); + if (p == 0) continue; + if (!p->has_metadata()) continue; + ++peers; + } +#endif + + // the number of blocks to request + int num_blocks = 256 / (peers + 1); + if (num_blocks < 1) num_blocks = 1; + assert(num_blocks <= 128); + + int min_element = std::numeric_limits::max(); + int best_index = 0; + for (int i = 0; i < 256 - num_blocks + 1; ++i) + { + int min = *std::min_element(m_requested_metadata.begin() + i + , m_requested_metadata.begin() + i + num_blocks); + min += std::accumulate(m_requested_metadata.begin() + i + , m_requested_metadata.begin() + i + num_blocks, (int)0); + + if (min_element > min) + { + best_index = i; + min_element = min; + } + } + + std::pair ret(best_index, num_blocks); + for (int i = ret.first; i < ret.first + ret.second; ++i) + m_requested_metadata[i]++; + + assert(ret.first >= 0); + assert(ret.second > 0); + assert(ret.second <= 256); + assert(ret.first + ret.second <= 256); + + return ret; + } + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_metadata_plugin(torrent* t) + { + return boost::shared_ptr(new metadata_plugin(*t)); + } + +} + + diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp new file mode 100644 index 000000000..0a5932a56 --- /dev/null +++ b/libtorrent/src/natpmp.cpp @@ -0,0 +1,393 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include + +using boost::bind; +using namespace libtorrent; + +enum { num_mappings = 2 }; + +namespace libtorrent +{ + // defined in upnp.cpp + bool is_local(address const& a); + address_v4 guess_local_address(asio::io_service&); +} + +natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb) + : m_callback(cb) + , m_currently_mapping(-1) + , m_retry_count(0) + , m_socket(ios) + , m_send_timer(ios) + , m_refresh_timer(ios) + , m_disabled(false) +{ + m_mappings[0].protocol = 2; // tcp + m_mappings[1].protocol = 1; // udp + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc); +#endif + rebind(listen_interface); +} + +void natpmp::rebind(address const& listen_interface) try +{ + address_v4 local = address_v4::any(); + if (listen_interface.is_v4() && listen_interface != address_v4::any()) + { + local = listen_interface.to_v4(); + } + else + { + local = guess_local_address(m_socket.io_service()); + + if (local == address_v4::any()) + { + throw std::runtime_error("local host is probably not on a NATed " + "network. disabling NAT-PMP"); + } + } + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " local ip: " << local.to_string() << std::endl; +#endif + + if (!is_local(local)) + { + // the local address seems to be an external + // internet address. Assume it is not behind a NAT + throw std::runtime_error("local IP is not on a local network"); + } + + m_disabled = false; + + // assume the router is located on the local + // network as x.x.x.1 + udp::endpoint nat_endpoint( + address_v4((local.to_ulong() & 0xffffff00) | 1), 5351); + + if (nat_endpoint == m_nat_endpoint) return; + + // TODO: find a better way to figure out the router IP + m_nat_endpoint = nat_endpoint; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "assuming router is at: " << m_nat_endpoint.address().to_string() << std::endl; +#endif + + m_socket.open(udp::v4()); + m_socket.bind(udp::endpoint(address_v4::any(), 0)); + + for (int i = 0; i < num_mappings; ++i) + { + if (m_mappings[i].local_port == 0) + continue; + refresh_mapping(i); + } +} +catch (std::exception& e) +{ + m_disabled = true; + std::stringstream msg; + msg << "NAT-PMP disabled: " << e.what(); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << msg.str() << std::endl; +#endif + m_callback(0, 0, msg.str()); +}; + +void natpmp::set_mappings(int tcp, int udp) +{ + if (m_disabled) return; + update_mapping(0, tcp); + update_mapping(1, udp); +} + +void natpmp::update_mapping(int i, int port) +{ + natpmp::mapping& m = m_mappings[i]; + if (port <= 0) return; + if (m.local_port != port) + m.need_update = true; + + m.local_port = port; + // prefer the same external port as the local port + if (m.external_port == 0) m.external_port = port; + + if (m_currently_mapping == -1) + { + // the socket is not currently in use + // send out a mapping request + m_retry_count = 0; + send_map_request(i); + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + } +} + +void natpmp::send_map_request(int i) try +{ + using namespace libtorrent::detail; + + assert(m_currently_mapping == -1 + || m_currently_mapping == i); + m_currently_mapping = i; + mapping& m = m_mappings[i]; + char buf[12]; + char* out = buf; + write_uint8(0, out); // NAT-PMP version + write_uint8(m.protocol, out); // map "protocol" + write_uint16(0, out); // reserved + write_uint16(m.local_port, out); // private port + write_uint16(m.external_port, out); // requested public port + int ttl = m.external_port == 0 ? 0 : 3600; + write_uint32(ttl, out); // port mapping lifetime + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " ==> port map request: " << (m.protocol == 1 ? "udp" : "tcp") + << " local: " << m.local_port << " external: " << m.external_port + << " ttl: " << ttl << std::endl; +#endif + + m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint); + // linear back-off instead of exponential + ++m_retry_count; + m_send_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_send_timer.async_wait(bind(&natpmp::resend_request, this, i, _1)); +} +catch (std::exception& e) +{ + std::string err = e.what(); +}; + +void natpmp::resend_request(int i, asio::error_code const& e) +{ + if (e) return; + if (m_currently_mapping != i) return; + if (m_retry_count >= 9) + { + m_mappings[i].need_update = false; + // try again in two hours + m_mappings[i].expires = time_now() + hours(2); + return; + } + send_map_request(i); +} + +void natpmp::on_reply(asio::error_code const& e + , std::size_t bytes_transferred) +{ + using namespace libtorrent::detail; + if (e) return; + + try + { + + if (m_remote != m_nat_endpoint) + { + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + return; + } + + m_send_timer.cancel(); + + assert(m_currently_mapping >= 0); + int i = m_currently_mapping; + mapping& m = m_mappings[i]; + + char* in = m_response_buffer; + int version = read_uint8(in); + int cmd = read_uint8(in); + int result = read_uint16(in); + int time = read_uint32(in); + int private_port = read_uint16(in); + int public_port = read_uint16(in); + int lifetime = read_uint32(in); + + (void)time; // to remove warning + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " <== port map response: " << (cmd - 128 == 1 ? "udp" : "tcp") + << " local: " << private_port << " external: " << public_port + << " ttl: " << lifetime << std::endl; +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (version != 0) + { + m_log << "*** unexpected version: " << version << std::endl; + } +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (private_port != m.local_port) + { + m_log << "*** unexpected local port: " << private_port << std::endl; + } +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (cmd != 128 + m.protocol) + { + m_log << "*** unexpected protocol: " << (cmd - 128) << std::endl; + } +#endif + + if (public_port == 0 || lifetime == 0) + { + // this means the mapping was + // successfully closed + m.local_port = 0; + } + else + { + m.expires = time_now() + seconds(int(lifetime * 0.7f)); + m.external_port = public_port; + } + + if (result != 0) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "*** ERROR: " << result << std::endl; +#endif + std::stringstream errmsg; + errmsg << "NAT router reports error (" << result << ") "; + switch (result) + { + case 1: errmsg << "Unsupported protocol version"; break; + case 2: errmsg << "Not authorized to create port map (enable NAT-PMP on your router)"; break; + case 3: errmsg << "Network failure"; break; + case 4: errmsg << "Out of resources"; break; + case 5: errmsg << "Unsupported opcode"; break; + } + throw std::runtime_error(errmsg.str()); + } + + // don't report when we remove mappings + if (m.local_port != 0) + { + int tcp_port = 0; + int udp_port = 0; + if (m.protocol == 1) udp_port = m.external_port; + else tcp_port = public_port; + m_callback(tcp_port, udp_port, ""); + } + } + catch (std::exception& e) + { + // try again in two hours + m_mappings[m_currently_mapping].expires = time_now() + hours(2); + m_callback(0, 0, e.what()); + } + int i = m_currently_mapping; + m_currently_mapping = -1; + m_mappings[i].need_update = false; + m_send_timer.cancel(); + update_expiration_timer(); + try_next_mapping(i); +} + +void natpmp::update_expiration_timer() +{ + ptime now = time_now(); + ptime min_expire = now + seconds(3600); + int min_index = -1; + for (int i = 0; i < num_mappings; ++i) + if (m_mappings[i].expires < min_expire + && m_mappings[i].local_port != 0) + { + min_expire = m_mappings[i].expires; + min_index = i; + } + + if (min_index >= 0) + { + m_refresh_timer.expires_from_now(min_expire - now); + m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, this, _1, min_index)); + } +} + +void natpmp::mapping_expired(asio::error_code const& e, int i) +{ + if (e) return; +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "*** mapping " << i << " expired, updating" << std::endl; +#endif + refresh_mapping(i); +} + +void natpmp::refresh_mapping(int i) +{ + m_mappings[i].need_update = true; + if (m_currently_mapping == -1) + { + // the socket is not currently in use + // send out a mapping request + m_retry_count = 0; + send_map_request(i); + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + } +} + +void natpmp::try_next_mapping(int i) +{ + ++i; + if (i >= num_mappings) i = 0; + if (m_mappings[i].need_update) + refresh_mapping(i); +} + +void natpmp::close() +{ + if (m_disabled) return; + for (int i = 0; i < num_mappings; ++i) + { + if (m_mappings[i].local_port == 0) + continue; + m_mappings[i].external_port = 0; + refresh_mapping(i); + } +} + diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp new file mode 100644 index 000000000..a763e5458 --- /dev/null +++ b/libtorrent/src/pe_crypto.cpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2007, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_ENCRYPTION + +#include +#include + +#include +#include + +#include "libtorrent/pe_crypto.hpp" + +namespace libtorrent { + + + // Set the prime P and the generator, generate local public key + DH_key_exchange::DH_key_exchange () + { + m_DH = DH_new (); + + m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); + m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); + + assert (sizeof(m_dh_prime) == DH_size(m_DH)); + + DH_generate_key (m_DH); // TODO Check != 0 + + assert (m_DH->pub_key); + + // DH can generate key sizes that are smaller than the size of + // P with exponentially decreasing probability, in which case + // the msb's of m_dh_local_key need to be zeroed + // appropriately. + int key_size = get_local_key_size(); + int len_dh = sizeof(m_dh_prime); // must equal DH_size(m_DH) + if (key_size != len_dh) + { + assert(key_size > 0 && key_size < len_dh); + + int pad_zero_size = len_dh - key_size; + std::fill(m_dh_local_key, m_dh_local_key + pad_zero_size, 0); + BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key + pad_zero_size); + } + else + BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key); // TODO Check return value + } + + DH_key_exchange::~DH_key_exchange () + { + assert (m_DH); + DH_free (m_DH); + } + + char const* DH_key_exchange::get_local_key () const + { + return m_dh_local_key; + } + + + // compute shared secret given remote public key + void DH_key_exchange::compute_secret (char const* remote_pubkey) + { + assert (remote_pubkey); + BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); + + int ret = + DH_compute_key ( (unsigned char*)m_dh_secret, bn_remote_pubkey, m_DH); // TODO Check for errors + + BN_free (bn_remote_pubkey); + } + + char const* DH_key_exchange::get_secret () const + { + return m_dh_secret; + } + + const unsigned char DH_key_exchange::m_dh_prime[96] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 + }; + + const unsigned char DH_key_exchange::m_dh_generator[1] = { 2 }; + +} // namespace libtorrent + +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp new file mode 100755 index 000000000..8a2ac0ae3 --- /dev/null +++ b/libtorrent/src/peer_connection.cpp @@ -0,0 +1,2701 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/socket_type.hpp" + +using boost::bind; +using boost::shared_ptr; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + + // outbound connection + peer_connection::peer_connection( + session_impl& ses + , boost::weak_ptr tor + , shared_ptr s + , tcp::endpoint const& remote + , policy::peer* peerinfo) + : +#ifndef NDEBUG + m_last_choke(time_now() - hours(1)) + , +#endif + m_ses(ses) + , m_max_out_request_queue(m_ses.settings().max_out_request_queue) + , m_timeout(m_ses.settings().peer_timeout) + , m_last_piece(time_now()) + , m_last_request(time_now()) + , m_packet_size(0) + , m_recv_pos(0) + , m_current_send_buffer(0) + , m_reading_bytes(0) + , m_write_pos(0) + , m_last_receive(time_now()) + , m_last_sent(time_now()) + , m_socket(s) + , m_remote(remote) + , m_torrent(tor) + , m_active(true) + , m_peer_interested(false) + , m_peer_choked(true) + , m_interesting(false) + , m_choked(true) + , m_failed(false) + , m_ignore_bandwidth_limits(false) + , m_num_pieces(0) + , m_desired_queue_size(2) + , m_free_upload(0) + , m_assume_fifo(false) + , m_num_invalid_requests(0) + , m_disconnecting(false) + , m_became_uninterested(time_now()) + , m_became_uninteresting(time_now()) + , m_connecting(true) + , m_queued(true) + , m_writing(false) + , m_reading(false) + , m_prefer_whole_pieces(false) + , m_request_large_blocks(false) + , m_non_prioritized(false) + , m_upload_limit(resource_request::inf) + , m_download_limit(resource_request::inf) + , m_peer_info(peerinfo) + , m_speed(slow) + , m_connection_ticket(-1) + , m_remote_bytes_dled(0) + , m_remote_dl_rate(0) + , m_remote_dl_update(time_now()) +#ifndef NDEBUG + , m_in_constructor(true) +#endif + { +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + std::fill(m_country, m_country + 2, 0); +#endif +#ifdef TORRENT_VERBOSE_LOGGING + m_logger = m_ses.create_log(m_remote.address().to_string() + "_" + + boost::lexical_cast(m_remote.port()), m_ses.listen_port()); + (*m_logger) << "*** OUTGOING CONNECTION\n"; +#endif + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + std::fill(m_peer_id.begin(), m_peer_id.end(), 0); + + if (t->ready_for_connections()) + init(); + } + + // incoming connection + peer_connection::peer_connection( + session_impl& ses + , boost::shared_ptr s + , policy::peer* peerinfo) + : +#ifndef NDEBUG + m_last_choke(time_now() - hours(1)) + , +#endif + m_ses(ses) + , m_max_out_request_queue(m_ses.settings().max_out_request_queue) + , m_timeout(m_ses.settings().peer_timeout) + , m_last_piece(time_now()) + , m_last_request(time_now()) + , m_packet_size(0) + , m_recv_pos(0) + , m_current_send_buffer(0) + , m_reading_bytes(0) + , m_write_pos(0) + , m_last_receive(time_now()) + , m_last_sent(time_now()) + , m_socket(s) + , m_active(false) + , m_peer_interested(false) + , m_peer_choked(true) + , m_interesting(false) + , m_choked(true) + , m_failed(false) + , m_ignore_bandwidth_limits(false) + , m_num_pieces(0) + , m_desired_queue_size(2) + , m_free_upload(0) + , m_assume_fifo(false) + , m_num_invalid_requests(0) + , m_disconnecting(false) + , m_became_uninterested(time_now()) + , m_became_uninteresting(time_now()) + , m_connecting(false) + , m_queued(false) + , m_writing(false) + , m_reading(false) + , m_prefer_whole_pieces(false) + , m_request_large_blocks(false) + , m_non_prioritized(false) + , m_upload_limit(resource_request::inf) + , m_download_limit(resource_request::inf) + , m_peer_info(peerinfo) + , m_speed(slow) + , m_remote_bytes_dled(0) + , m_remote_dl_rate(0) + , m_remote_dl_update(time_now()) +#ifndef NDEBUG + , m_in_constructor(true) +#endif + { + tcp::socket::non_blocking_io ioc(true); + m_socket->io_control(ioc); +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + std::fill(m_country, m_country + 2, 0); +#endif + m_remote = m_socket->remote_endpoint(); + +#ifdef TORRENT_VERBOSE_LOGGING + assert(m_socket->remote_endpoint() == remote()); + m_logger = m_ses.create_log(remote().address().to_string() + "_" + + boost::lexical_cast(remote().port()), m_ses.listen_port()); + (*m_logger) << "*** INCOMING CONNECTION\n"; +#endif + + std::fill(m_peer_id.begin(), m_peer_id.end(), 0); + } + + void peer_connection::update_interest() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + bool interested = false; + const std::vector& we_have = t->pieces(); + for (int j = 0; j != (int)we_have.size(); ++j) + { + if (!we_have[j] + && t->piece_priority(j) > 0 + && m_have_piece[j]) + { + interested = true; + break; + } + } + try + { + if (!interested) + send_not_interested(); + else + t->get_policy().peer_is_interesting(*this); + } + // may throw an asio error if socket has disconnected + catch (std::exception& e) {} + + assert(is_interesting() == interested); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void peer_connection::add_extension(boost::shared_ptr ext) + { + m_extensions.push_back(ext); + } +#endif + + void peer_connection::init() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + assert(t->valid_metadata()); + assert(t->ready_for_connections()); + + m_have_piece.resize(t->torrent_file().num_pieces(), false); + + // now that we have a piece_picker, + // update it with this peers pieces + + int num_pieces = std::count(m_have_piece.begin(), m_have_piece.end(), true); + if (num_pieces == int(m_have_piece.size())) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** THIS IS A SEED ***\n"; +#endif + // if this is a web seed. we don't have a peer_info struct + if (m_peer_info) m_peer_info->seed = true; + // if we're a seed too, disconnect + if (t->is_seed()) + { + throw std::runtime_error("seed to seed connection redundant, disconnecting"); + } + m_num_pieces = num_pieces; + t->peer_has_all(); + if (!t->is_finished()) + t->get_policy().peer_is_interesting(*this); + return; + } + + m_num_pieces = num_pieces; + // if we're a seed, we don't keep track of piece availability + if (!t->is_seed()) + { + bool interesting = false; + for (int i = 0; i < int(m_have_piece.size()); ++i) + { + if (m_have_piece[i]) + { + t->peer_has(i); + // if the peer has a piece and we don't, the peer is interesting + if (!t->have_piece(i) + && t->picker().piece_priority(i) != 0) + interesting = true; + } + } + if (interesting) + t->get_policy().peer_is_interesting(*this); + } + } + + peer_connection::~peer_connection() + { +// INVARIANT_CHECK; + assert(m_disconnecting); + +#ifdef TORRENT_VERBOSE_LOGGING + if (m_logger) + { + (*m_logger) << time_now_string() + << " *** CONNECTION CLOSED\n"; + } +#endif +#ifndef NDEBUG + if (m_peer_info) + assert(m_peer_info->connection == 0); + + boost::shared_ptr t = m_torrent.lock(); + if (t) assert(t->connection_for(remote()) != this); +#endif + } + + void peer_connection::announce_piece(int index) + { + // dont announce during handshake + if (in_handshake()) return; + + // optimization, don't send have messages + // to peers that already have the piece + if (!m_ses.settings().send_redundant_have + && has_piece(index)) return; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> HAVE [ piece: " << index << "]\n"; +#endif + write_have(index); +#ifndef NDEBUG + boost::shared_ptr t = m_torrent.lock(); + assert(t); + assert(t->have_piece(index)); +#endif + } + + bool peer_connection::has_piece(int i) const + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + assert(t->valid_metadata()); + assert(i >= 0); + assert(i < t->torrent_file().num_pieces()); + return m_have_piece[i]; + } + + std::deque const& peer_connection::request_queue() const + { + return m_request_queue; + } + + std::deque const& peer_connection::download_queue() const + { + return m_download_queue; + } + + std::deque const& peer_connection::upload_queue() const + { + return m_requests; + } + + void peer_connection::add_stat(size_type downloaded, size_type uploaded) + { + INVARIANT_CHECK; + + m_statistics.add_stat(downloaded, uploaded); + } + + std::vector const& peer_connection::get_bitfield() const + { + return m_have_piece; + } + + void peer_connection::received_valid_data(int index) + { + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { (*i)->on_piece_pass(index); } catch (std::exception&) {} + } +#endif + + if (peer_info_struct()) + { + peer_info_struct()->on_parole = false; + int& trust_points = peer_info_struct()->trust_points; + trust_points++; + // TODO: make this limit user settable + if (trust_points > 20) trust_points = 20; + } + } + + void peer_connection::received_invalid_data(int index) + { + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { (*i)->on_piece_failed(index); } catch (std::exception&) {} + } +#endif + + if (peer_info_struct()) + { + peer_info_struct()->on_parole = true; + ++peer_info_struct()->hashfails; + int& trust_points = peer_info_struct()->trust_points; + + // we decrease more than we increase, to keep the + // allowed failed/passed ratio low. + // TODO: make this limit user settable + trust_points -= 2; + if (trust_points < -7) trust_points = -7; + } + } + + size_type peer_connection::total_free_upload() const + { + return m_free_upload; + } + + void peer_connection::add_free_upload(size_type free_upload) + { + INVARIANT_CHECK; + + m_free_upload += free_upload; + } + + // verifies a piece to see if it is valid (is within a valid range) + // and if it can correspond to a request generated by libtorrent. + bool peer_connection::verify_piece(const peer_request& p) const + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + assert(t->valid_metadata()); + + return p.piece >= 0 + && p.piece < t->torrent_file().num_pieces() + && p.length > 0 + && p.start >= 0 + && (p.length == t->block_size() + || (p.length < t->block_size() + && p.piece == t->torrent_file().num_pieces()-1 + && p.start + p.length == t->torrent_file().piece_size(p.piece)) + || (m_request_large_blocks + && p.length <= t->torrent_file().piece_size(p.piece))) + && p.start + p.length <= t->torrent_file().piece_size(p.piece) + && (p.start % t->block_size() == 0); + } + + struct disconnect_torrent + { + disconnect_torrent(boost::weak_ptr& t): m_t(&t) {} + ~disconnect_torrent() { if (m_t) m_t->reset(); } + void cancel() { m_t = 0; } + private: + boost::weak_ptr* m_t; + }; + + void peer_connection::attach_to_torrent(sha1_hash const& ih) + { + INVARIANT_CHECK; + + assert(!m_disconnecting); + m_torrent = m_ses.find_torrent(ih); + + boost::shared_ptr t = m_torrent.lock(); + + if (t && t->is_aborted()) + { + m_torrent.reset(); + t.reset(); + } + + if (!t) + { + // we couldn't find the torrent! +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " couldn't find a torrent with the given info_hash: " << ih << "\n"; +#endif + throw std::runtime_error("got info-hash that is not in our session"); + } + + disconnect_torrent disconnect(m_torrent); + if (t->is_paused()) + { + // paused torrents will not accept + // incoming connections +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " rejected connection to paused torrent\n"; +#endif + throw std::runtime_error("connection rejected by paused torrent"); + } + + // check to make sure we don't have another connection with the same + // info_hash and peer_id. If we do. close this connection. + t->attach_peer(this); + + // if the torrent isn't ready to accept + // connections yet, we'll have to wait with + // our initialization + if (t->ready_for_connections()) init(); + + // assume the other end has no pieces + // if we don't have valid metadata yet, + // leave the vector unallocated + assert(m_num_pieces == 0); + std::fill(m_have_piece.begin(), m_have_piece.end(), false); + disconnect.cancel(); + } + + // message handlers + + // ----------------------------- + // --------- KEEPALIVE --------- + // ----------------------------- + + void peer_connection::incoming_keepalive() + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== KEEPALIVE\n"; +#endif + } + + // ----------------------------- + // ----------- CHOKE ----------- + // ----------------------------- + + void peer_connection::incoming_choke() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_choke()) return; + } +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== CHOKE\n"; +#endif + m_peer_choked = true; + t->get_policy().choked(*this); + + if (!t->is_seed()) + { + piece_picker& p = t->picker(); + // remove all pieces from this peers download queue and + // remove the 'downloading' flag from piece_picker. + for (std::deque::iterator i = m_download_queue.begin(); + i != m_download_queue.end(); ++i) + { + p.abort_download(*i); + } + for (std::deque::const_iterator i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) + { + // since this piece was skipped, clear it and allow it to + // be requested from other peers + p.abort_download(*i); + } + } + + m_download_queue.clear(); + m_request_queue.clear(); + } + + // ----------------------------- + // ---------- UNCHOKE ---------- + // ----------------------------- + + void peer_connection::incoming_unchoke() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_unchoke()) return; + } +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== UNCHOKE\n"; +#endif + m_peer_choked = false; + t->get_policy().unchoked(*this); + } + + // ----------------------------- + // -------- INTERESTED --------- + // ----------------------------- + + void peer_connection::incoming_interested() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_interested()) return; + } +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== INTERESTED\n"; +#endif + m_peer_interested = true; + t->get_policy().interested(*this); + } + + // ----------------------------- + // ------ NOT INTERESTED ------- + // ----------------------------- + + void peer_connection::incoming_not_interested() + { + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_not_interested()) return; + } +#endif + + m_became_uninterested = time_now(); + + // clear the request queue if the client isn't interested + m_requests.clear(); +// setup_send(); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== NOT_INTERESTED\n"; +#endif + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + m_peer_interested = false; + t->get_policy().not_interested(*this); + } + + // ----------------------------- + // ----------- HAVE ------------ + // ----------------------------- + + void peer_connection::incoming_have(int index) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_have(index)) return; + } +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== HAVE [ piece: " << index << "]\n"; +#endif + + // if we got an invalid message, abort + if (index >= (int)m_have_piece.size() || index < 0) + throw protocol_error("got 'have'-message with higher index " + "than the number of pieces"); + + if (m_have_piece[index]) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " got redundant HAVE message for index: " << index << "\n"; +#endif + } + else + { + m_have_piece[index] = true; + + // only update the piece_picker if + // we have the metadata and if + // we're not a seed (in which case + // we won't have a piece picker) + if (t->valid_metadata()) + { + ++m_num_pieces; + t->peer_has(index); + + if (!t->have_piece(index) + && !t->is_seed() + && !is_interesting() + && t->picker().piece_priority(index) != 0) + t->get_policy().peer_is_interesting(*this); + + // this will disregard all have messages we get within + // the first two seconds. Since some clients implements + // lazy bitfields, these will not be reliable to use + // for an estimated peer download rate. + if (!peer_info_struct() || time_now() - peer_info_struct()->connected > seconds(2)) + { + // update bytes downloaded since last timer + m_remote_bytes_dled += t->torrent_file().piece_size(index); + } + } + + if (is_seed()) + { + assert(m_peer_info); + m_peer_info->seed = true; + if (t->is_seed()) + { + throw protocol_error("seed to seed connection redundant, disconnecting"); + } + } + } + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void peer_connection::incoming_bitfield(std::vector const& bitfield) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_bitfield(bitfield)) return; + } +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== BITFIELD "; + + for (int i = 0; i < int(bitfield.size()); ++i) + { + if (bitfield[i]) (*m_logger) << "1"; + else (*m_logger) << "0"; + } + (*m_logger) << "\n"; +#endif + + // if we don't have the metedata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && (bitfield.size() / 8) != (m_have_piece.size() / 8)) + throw protocol_error("got bitfield with invalid size: " + + boost::lexical_cast(bitfield.size() / 8) + + "bytes. expected: " + + boost::lexical_cast(m_have_piece.size() / 8) + + "bytes"); + + // if we don't have metadata yet + // just remember the bitmask + // don't update the piecepicker + // (since it doesn't exist yet) + if (!t->ready_for_connections()) + { + m_have_piece = bitfield; + m_num_pieces = std::count(bitfield.begin(), bitfield.end(), true); + + if (m_peer_info) m_peer_info->seed = true; + return; + } + + int num_pieces = std::count(bitfield.begin(), bitfield.end(), true); + if (num_pieces == int(m_have_piece.size())) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** THIS IS A SEED ***\n"; +#endif + // if this is a web seed. we don't have a peer_info struct + if (m_peer_info) m_peer_info->seed = true; + // if we're a seed too, disconnect + if (t->is_seed()) + { + throw protocol_error("seed to seed connection redundant, disconnecting"); + } + + std::fill(m_have_piece.begin(), m_have_piece.end(), true); + m_num_pieces = num_pieces; + t->peer_has_all(); + if (!t->is_finished()) + t->get_policy().peer_is_interesting(*this); + return; + } + + // let the torrent know which pieces the + // peer has + // if we're a seed, we don't keep track of piece availability + if (!t->is_seed()) + { + bool interesting = false; + for (int i = 0; i < (int)m_have_piece.size(); ++i) + { + bool have = bitfield[i]; + if (have && !m_have_piece[i]) + { + m_have_piece[i] = true; + ++m_num_pieces; + t->peer_has(i); + if (!t->have_piece(i) && t->picker().piece_priority(i) != 0) + interesting = true; + } + else if (!have && m_have_piece[i]) + { + // this should probably not be allowed + m_have_piece[i] = false; + --m_num_pieces; + t->peer_lost(i); + } + } + + if (interesting) t->get_policy().peer_is_interesting(*this); + } + else + { + for (int i = 0; i < (int)m_have_piece.size(); ++i) + { + bool have = bitfield[i]; + if (have && !m_have_piece[i]) + { + m_have_piece[i] = true; + ++m_num_pieces; + } + else if (!have && m_have_piece[i]) + { + // this should probably not be allowed + m_have_piece[i] = false; + --m_num_pieces; + } + } + } + } + + // ----------------------------- + // ---------- REQUEST ---------- + // ----------------------------- + + void peer_connection::incoming_request(peer_request const& r) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_request(r)) return; + } +#endif + + if (!t->valid_metadata()) + { + // if we don't have valid metadata yet, + // we shouldn't get a request +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== UNEXPECTED_REQUEST [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " | " + "i: " << m_peer_interested << " | " + "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " ]\n"; +#endif + return; + } + + if (int(m_requests.size()) > m_ses.settings().max_allowed_in_request_queue) + { + // don't allow clients to abuse our + // memory consumption. + // ignore requests if the client + // is making too many of them. +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== TOO MANY REQUESTS [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " | " + "i: " << m_peer_interested << " | " + "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " ]\n"; +#endif + return; + } + + // make sure this request + // is legal and that the peer + // is not choked + if (r.piece >= 0 + && r.piece < t->torrent_file().num_pieces() + && t->have_piece(r.piece) + && r.start >= 0 + && r.start < t->torrent_file().piece_size(r.piece) + && r.length > 0 + && r.length + r.start <= t->torrent_file().piece_size(r.piece) + && m_peer_interested) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== REQUEST [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; +#endif + // if we have choked the client + // ignore the request + if (m_choked) + return; + + m_requests.push_back(r); + fill_send_buffer(); + } + else + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== INVALID_REQUEST [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " | " + "i: " << m_peer_interested << " | " + "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " | " + "h: " << t->have_piece(r.piece) << " ]\n"; +#endif + + ++m_num_invalid_requests; + + if (t->alerts().should_post(alert::debug)) + { + t->alerts().post_alert(invalid_request_alert( + r + , t->get_handle() + , m_remote + , m_peer_id + , "peer sent an illegal piece request, ignoring")); + } + } + } + + void peer_connection::incoming_piece_fragment() + { + m_last_piece = time_now(); + } + +#ifndef NDEBUG + struct check_postcondition + { + check_postcondition(boost::shared_ptr const& t_ + , bool init_check = true): t(t_) { if (init_check) check(); } + + ~check_postcondition() { check(); } + + void check() + { + if (!t->is_seed()) + { + const int blocks_per_piece = static_cast( + t->torrent_file().piece_length() / t->block_size()); + + std::vector const& dl_queue + = t->picker().get_download_queue(); + + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + assert(i->finished <= blocks_per_piece); + } + } + } + + shared_ptr t; + }; +#endif + + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void peer_connection::incoming_piece(peer_request const& p, char const* data) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_piece(p, data)) return; + } +#endif + +#ifndef NDEBUG + check_postcondition post_checker_(t); + t->check_invariant(); +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== PIECE [ piece: " << p.piece << " | " + "s: " << p.start << " | " + "l: " << p.length << " | " + "ds: " << statistics().download_rate() << " | " + "qs: " << m_desired_queue_size << " ]\n"; +#endif + + if (!verify_piece(p)) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== INVALID_PIECE [ piece: " << p.piece << " | " + "start: " << p.start << " | " + "length: " << p.length << " ]\n"; +#endif + throw protocol_error("got invalid piece packet"); + } + + // if we're already seeding, don't bother, + // just ignore it + if (t->is_seed()) + { + t->received_redundant_data(p.length); + return; + } + + piece_picker& picker = t->picker(); + piece_manager& fs = t->filesystem(); + + std::vector finished_blocks; + piece_block block_finished(p.piece, p.start / t->block_size()); + assert(p.start % t->block_size() == 0); + assert(p.length == t->block_size() + || p.length == t->torrent_file().total_size() % t->block_size()); + + std::deque::iterator b + = std::find( + m_download_queue.begin() + , m_download_queue.end() + , block_finished); + + // if there's another peer that needs to do another + // piece request, this will point to it + peer_connection* request_peer = 0; + + if (b != m_download_queue.end()) + { + if (m_assume_fifo) + { + for (std::deque::iterator i = m_download_queue.begin(); + i != b; ++i) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " *** SKIPPED_PIECE [ piece: " << i->piece_index << " | " + "b: " << i->block_index << " ] ***\n"; +#endif + // since this piece was skipped, clear it and allow it to + // be requested from other peers + // TODO: send cancel? + picker.abort_download(*i); + } + + // remove the request that just finished + // from the download queue plus the + // skipped blocks. + m_download_queue.erase(m_download_queue.begin() + , boost::next(b)); + } + else + { + m_download_queue.erase(b); + } + } + else + { +/* // cancel the block from the + // peer that has taken over it. + boost::optional peer + = t->picker().get_downloader(block_finished); + if (peer && t->picker().is_requested(block_finished)) + { + peer_connection* pc = t->connection_for(*peer); + if (pc && pc != this) + { + pc->cancel_request(block_finished); + request_peer = pc; + } + } + else +*/ { + if (t->alerts().should_post(alert::debug)) + { + t->alerts().post_alert( + peer_error_alert( + m_remote + , m_peer_id + , "got a block that was not requested")); + } +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** The block we just got was not in the " + "request queue ***\n"; +#endif + t->received_redundant_data(p.length); + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); + } + return; + } + } + + assert(picker.is_requested(block_finished)); + + // if the block we got is already finished, then ignore it + if (picker.is_downloaded(block_finished)) + { + t->received_redundant_data(p.length); + + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); + } + + if (request_peer && !request_peer->has_peer_choked() && !t->is_seed()) + { + request_a_block(*t, *request_peer); + request_peer->send_block_requests(); + } + return; + } + + fs.async_write(p, data, bind(&peer_connection::on_disk_write_complete + , self(), _1, _2, p, t)); + picker.mark_as_writing(block_finished, m_remote); + + if (request_peer && !request_peer->has_peer_choked() && !t->is_seed()) + { + request_a_block(*t, *request_peer); + request_peer->send_block_requests(); + } + + } + + void peer_connection::on_disk_write_complete(int ret, disk_io_job const& j + , peer_request p, boost::shared_ptr t) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (ret == -1 || !t) + { + if (!t) + { + m_ses.connection_failed(m_socket, remote(), j.str.c_str()); + return; + } + + if (t->alerts().should_post(alert::fatal)) + { + std::string err = "torrent paused: disk write error, " + j.str; + t->alerts().post_alert(file_error_alert(t->get_handle(), err)); + } + t->pause(); + return; + } + + if (t->is_seed()) return; + + piece_picker& picker = t->picker(); + + assert(p.piece == j.piece); + assert(p.start == j.offset); + piece_block block_finished(p.piece, p.start / t->block_size()); + picker.mark_as_finished(block_finished, m_remote); + + if (!has_peer_choked() && !t->is_seed() && !m_torrent.expired()) + { + // this is a free function defined in policy.cpp + request_a_block(*t, *this); + try + { + send_block_requests(); + } + catch (std::exception const&) {} + } + +#ifndef NDEBUG + try + { +#endif + + // did we just finish the piece? + if (picker.is_piece_finished(p.piece)) + { +#ifndef NDEBUG + check_postcondition post_checker2_(t, false); +#endif + t->async_verify_piece(p.piece, bind(&torrent::piece_finished, t + , p.piece, _1)); + } + +#ifndef NDEBUG + } + catch (std::exception const& e) + { + std::cerr << e.what() << std::endl; + assert(false); + } +#endif + } + + // ----------------------------- + // ---------- CANCEL ----------- + // ----------------------------- + + void peer_connection::incoming_cancel(peer_request const& r) + { + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_cancel(r)) return; + } +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== CANCEL [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; +#endif + + std::deque::iterator i + = std::find(m_requests.begin(), m_requests.end(), r); + + if (i != m_requests.end()) + { + m_requests.erase(i); + } + else + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** GOT CANCEL NOT IN THE QUEUE\n"; +#endif + } + } + + // ----------------------------- + // --------- DHT PORT ---------- + // ----------------------------- + + void peer_connection::incoming_dht_port(int listen_port) + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== DHT_PORT [ p: " << listen_port << " ]\n"; +#endif +#ifndef TORRENT_DISABLE_DHT + m_ses.add_dht_node(udp::endpoint( + m_remote.address(), listen_port)); +#endif + } + + void peer_connection::add_request(piece_block const& block) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + assert(t->valid_metadata()); + assert(block.piece_index >= 0); + assert(block.piece_index < t->torrent_file().num_pieces()); + assert(block.block_index >= 0); + assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); + assert(!t->picker().is_requested(block)); + + piece_picker::piece_state_t state; + peer_speed_t speed = peer_speed(); + if (speed == fast) state = piece_picker::fast; + else if (speed == medium) state = piece_picker::medium; + else state = piece_picker::slow; + + t->picker().mark_as_downloading(block, m_remote, state); + + m_request_queue.push_back(block); + } + + void peer_connection::cancel_request(piece_block const& block) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + assert(t->valid_metadata()); + + assert(block.piece_index >= 0); + assert(block.piece_index < t->torrent_file().num_pieces()); + assert(block.block_index >= 0); + assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); + assert(t->picker().is_requested(block)); + + t->picker().abort_download(block); + + std::deque::iterator it + = std::find(m_download_queue.begin(), m_download_queue.end(), block); + if (it == m_download_queue.end()) + { + it = std::find(m_request_queue.begin(), m_request_queue.end(), block); + assert(it != m_request_queue.end()); + if (it == m_request_queue.end()) return; + m_request_queue.erase(it); + // since we found it in the request queue, it means it hasn't been + // sent yet, so we don't have to send a cancel. + return; + } + else + { + m_download_queue.erase(it); + } + + int block_offset = block.block_index * t->block_size(); + int block_size + = std::min((int)t->torrent_file().piece_size(block.piece_index)-block_offset, + t->block_size()); + assert(block_size > 0); + assert(block_size <= t->block_size()); + + peer_request r; + r.piece = block.piece_index; + r.start = block_offset; + r.length = block_size; + + write_cancel(r); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> CANCEL [ piece: " << block.piece_index << " | s: " + << block_offset << " | l: " << block_size << " | " << block.block_index << " ]\n"; +#endif + } + + void peer_connection::send_choke() + { + INVARIANT_CHECK; + + if (m_choked) return; + write_choke(); + m_choked = true; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> CHOKE\n"; +#endif +#ifndef NDEBUG + m_last_choke = time_now(); +#endif + m_num_invalid_requests = 0; + m_requests.clear(); + } + + void peer_connection::send_unchoke() + { + INVARIANT_CHECK; + +#ifndef NDEBUG + // TODO: once the policy lowers the interval for optimistic + // unchoke, increase this value that interval + // this condition cannot be guaranteed since if peers disconnect + // a new one will be unchoked ignoring when it was last choked + //assert(time_now() - m_last_choke > seconds(9)); +#endif + + if (!m_choked) return; + write_unchoke(); + m_choked = false; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> UNCHOKE\n"; +#endif + } + + void peer_connection::send_interested() + { + INVARIANT_CHECK; + + if (m_interesting) return; + write_interested(); + m_interesting = true; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> INTERESTED\n"; +#endif + } + + void peer_connection::send_not_interested() + { + INVARIANT_CHECK; + + if (!m_interesting) return; + write_not_interested(); + m_interesting = false; + + m_became_uninteresting = time_now(); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " ==> NOT_INTERESTED\n"; +#endif + } + + void peer_connection::send_block_requests() + { + INVARIANT_CHECK; + + if (has_peer_choked()) return; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + assert(!has_peer_choked()); + + if ((int)m_download_queue.size() >= m_desired_queue_size) return; + + while (!m_request_queue.empty() + && (int)m_download_queue.size() < m_desired_queue_size) + { + piece_block block = m_request_queue.front(); + + int block_offset = block.block_index * t->block_size(); + int block_size = std::min((int)t->torrent_file().piece_size( + block.piece_index) - block_offset, t->block_size()); + assert(block_size > 0); + assert(block_size <= t->block_size()); + + peer_request r; + r.piece = block.piece_index; + r.start = block_offset; + r.length = block_size; + + m_request_queue.pop_front(); + m_download_queue.push_back(block); +/* +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " *** REQUEST-QUEUE** [ " + "piece: " << block.piece_index << " | " + "block: " << block.block_index << " ]\n"; +#endif +*/ + // if we are requesting large blocks, merge the smaller + // blocks that are in the same piece into larger requests + if (m_request_large_blocks) + { + while (!m_request_queue.empty() + && m_request_queue.front().piece_index == r.piece + && m_request_queue.front().block_index == block.block_index + 1) + { + block = m_request_queue.front(); + m_request_queue.pop_front(); + m_download_queue.push_back(block); +/* +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " *** REQUEST-QUEUE** [ " + "piece: " << block.piece_index << " | " + "block: " << block.block_index << " ]\n"; +#endif +*/ + block_offset = block.block_index * t->block_size(); + block_size = std::min((int)t->torrent_file().piece_size( + block.piece_index) - block_offset, t->block_size()); + assert(block_size > 0); + assert(block_size <= t->block_size()); + + r.length += block_size; + } + } + + assert(verify_piece(r)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool handled = false; + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if (handled = (*i)->write_request(r)) break; + } + if (!handled) + { + write_request(r); + m_last_request = time_now(); + } +#else + write_request(r); + m_last_request = time_now(); +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> REQUEST [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " | " + "ds: " << statistics().download_rate() << " B/s | " + "qs: " << m_desired_queue_size << " ]\n"; +#endif + } + m_last_piece = time_now(); + } + + + void close_socket_ignore_error(boost::shared_ptr s) + { + try { s->close(); } catch (std::exception& e) {} + } + + void peer_connection::timed_out() + { + if (m_peer_info) ++m_peer_info->failcount; +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "CONNECTION TIMED OUT: " << m_remote.address().to_string() + << "\n"; +#endif + m_ses.connection_failed(m_socket, m_remote, "timed out"); + } + + void peer_connection::disconnect() + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + boost::intrusive_ptr me(this); + + INVARIANT_CHECK; + + if (m_disconnecting) return; + m_disconnecting = true; + if (m_connecting) + m_ses.m_half_open.done(m_connection_ticket); + + m_ses.m_io_service.post(boost::bind(&close_socket_ignore_error, m_socket)); + + boost::shared_ptr t = m_torrent.lock(); + + if (t) + { + if (t->has_picker()) + { + piece_picker& picker = t->picker(); + + while (!m_download_queue.empty()) + { + picker.abort_download(m_download_queue.back()); + m_download_queue.pop_back(); + } + while (!m_request_queue.empty()) + { + picker.abort_download(m_request_queue.back()); + m_request_queue.pop_back(); + } + } + + t->remove_peer(this); + + m_torrent.reset(); + } + + m_ses.close_connection(me); + } + + void peer_connection::set_upload_limit(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = resource_request::inf; + if (limit < 10) limit = 10; + m_upload_limit = limit; + m_bandwidth_limit[upload_channel].throttle(m_upload_limit); + } + + void peer_connection::set_download_limit(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = resource_request::inf; + if (limit < 10) limit = 10; + m_download_limit = limit; + m_bandwidth_limit[download_channel].throttle(m_download_limit); + } + + size_type peer_connection::share_diff() const + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + float ratio = t->ratio(); + + // if we have an infinite ratio, just say we have downloaded + // much more than we have uploaded. And we'll keep uploading. + if (ratio == 0.f) + return std::numeric_limits::max(); + + return m_free_upload + + static_cast(m_statistics.total_payload_download() * ratio) + - m_statistics.total_payload_upload(); + } + + // defined in upnp.cpp + bool is_local(address const& a); + + bool peer_connection::on_local_network() const + { + if (libtorrent::is_local(m_remote.address())) return true; + return false; + } + + void peer_connection::get_peer_info(peer_info& p) const + { + assert(!associated_torrent().expired()); + + p.down_speed = statistics().download_rate(); + p.up_speed = statistics().upload_rate(); + p.payload_down_speed = statistics().download_payload_rate(); + p.payload_up_speed = statistics().upload_payload_rate(); + p.pid = pid(); + p.ip = remote(); + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + p.country[0] = m_country[0]; + p.country[1] = m_country[1]; +#endif + + p.total_download = statistics().total_payload_download(); + p.total_upload = statistics().total_payload_upload(); + + if (m_bandwidth_limit[upload_channel].throttle() == bandwidth_limit::inf) + p.upload_limit = -1; + else + p.upload_limit = m_bandwidth_limit[upload_channel].throttle(); + + if (m_bandwidth_limit[download_channel].throttle() == bandwidth_limit::inf) + p.download_limit = -1; + else + p.download_limit = m_bandwidth_limit[download_channel].throttle(); + + p.load_balancing = total_free_upload(); + + p.download_queue_length = (int)download_queue().size(); + p.upload_queue_length = (int)upload_queue().size(); + + if (boost::optional ret = downloading_piece_progress()) + { + p.downloading_piece_index = ret->piece_index; + p.downloading_block_index = ret->block_index; + p.downloading_progress = ret->bytes_downloaded; + p.downloading_total = ret->full_block_bytes; + } + else + { + p.downloading_piece_index = -1; + p.downloading_block_index = -1; + p.downloading_progress = 0; + p.downloading_total = 0; + } + + p.pieces = get_bitfield(); + ptime now = time_now(); + p.last_request = now - m_last_request; + p.last_active = now - std::max(m_last_sent, m_last_receive); + + // this will set the flags so that we can update them later + p.flags = 0; + get_specific_peer_info(p); + + p.flags |= is_seed() ? peer_info::seed : 0; + if (peer_info_struct()) + { + p.source = peer_info_struct()->source; + p.failcount = peer_info_struct()->failcount; + p.num_hashfails = peer_info_struct()->hashfails; + p.flags |= peer_info_struct()->on_parole ? peer_info::on_parole : 0; + p.remote_dl_rate = m_remote_dl_rate; + } + else + { + p.source = 0; + p.failcount = 0; + p.num_hashfails = 0; + p.remote_dl_rate = 0; + } + + p.send_buffer_size = send_buffer_size(); + } + + void peer_connection::cut_receive_buffer(int size, int packet_size) + { + INVARIANT_CHECK; + + assert(packet_size > 0); + assert(int(m_recv_buffer.size()) >= size); + assert(int(m_recv_buffer.size()) >= m_recv_pos); + assert(m_recv_pos >= size); + + if (size > 0) + std::memmove(&m_recv_buffer[0], &m_recv_buffer[0] + size, m_recv_pos - size); + + m_recv_pos -= size; + +#ifndef NDEBUG + std::fill(m_recv_buffer.begin() + m_recv_pos, m_recv_buffer.end(), 0); +#endif + + m_packet_size = packet_size; + if (m_packet_size >= m_recv_pos) m_recv_buffer.resize(m_packet_size); + } + + void peer_connection::second_tick(float tick_interval) + { + INVARIANT_CHECK; + + ptime now(time_now()); + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + on_tick(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + (*i)->tick(); + } +#endif + + m_ignore_bandwidth_limits = m_ses.settings().ignore_limits_on_local_network + && on_local_network(); + + m_statistics.second_tick(tick_interval); + + if (!t->valid_metadata()) return; + + // calculate the desired download queue size + const float queue_time = m_ses.settings().request_queue_time; + // (if the latency is more than this, the download will stall) + // so, the queue size is queue_time * down_rate / 16 kiB + // (16 kB is the size of each request) + // the minimum number of requests is 2 and the maximum is 48 + // the block size doesn't have to be 16. So we first query the + // torrent for it + const int block_size = m_request_large_blocks + ? t->torrent_file().piece_length() : t->block_size(); + assert(block_size > 0); + + m_desired_queue_size = static_cast(queue_time + * statistics().download_rate() / block_size); + if (m_desired_queue_size > m_max_out_request_queue) + m_desired_queue_size = m_max_out_request_queue; + if (m_desired_queue_size < min_request_queue) + m_desired_queue_size = min_request_queue; + + if (!m_download_queue.empty() + && now - m_last_piece > seconds(m_ses.settings().piece_timeout)) + { + // this peer isn't sending the pieces we've + // requested (this has been observed by BitComet) + // in this case we'll clear our download queue and + // re-request the blocks. +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " *** PIECE_REQUESTS TIMED OUT [ " << (int)m_download_queue.size() + << " " << total_seconds(now - m_last_piece) << "] ***\n"; +#endif + + if (t->is_seed()) + { + m_download_queue.clear(); + m_request_queue.clear(); + } + else + { + piece_picker& picker = t->picker(); + while (!m_download_queue.empty()) + { + picker.abort_download(m_download_queue.back()); + m_download_queue.pop_back(); + } + while (!m_request_queue.empty()) + { + picker.abort_download(m_request_queue.back()); + m_request_queue.pop_back(); + } + + // TODO: If we have a limited number of upload + // slots, choke this peer + + m_assume_fifo = true; + + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); + } + } + } + + m_statistics.second_tick(tick_interval); + + // If the client sends more data + // we send it data faster, otherwise, slower. + // It will also depend on how much data the + // client has sent us. This is the mean to + // maintain the share ratio given by m_ratio + // with all peers. + + if (t->is_seed() || is_choked() || t->ratio() == 0.0f) + { + // if we have downloaded more than one piece more + // than we have uploaded OR if we are a seed + // have an unlimited upload rate + m_bandwidth_limit[upload_channel].throttle(m_upload_limit); + } + else + { + size_type bias = 0x10000 + 2 * t->block_size() + m_free_upload; + + double break_even_time = 15; // seconds. + size_type have_uploaded = m_statistics.total_payload_upload(); + size_type have_downloaded = m_statistics.total_payload_download(); + double download_speed = m_statistics.download_rate(); + + size_type soon_downloaded = + have_downloaded + (size_type)(download_speed * break_even_time*1.5); + + if (t->ratio() != 1.f) + soon_downloaded = (size_type)(soon_downloaded*(double)t->ratio()); + + double upload_speed_limit = std::min((soon_downloaded - have_uploaded + + bias) / break_even_time, double(m_upload_limit)); + + upload_speed_limit = std::min(upload_speed_limit, + (double)std::numeric_limits::max()); + + m_bandwidth_limit[upload_channel].throttle( + std::min(std::max((int)upload_speed_limit, 20) + , m_upload_limit)); + } + + // update once every minute + if (now - m_remote_dl_update >= seconds(60)) + { + float factor = 0.6666666666667f; + + if (m_remote_dl_rate == 0) factor = 0.0f; + + m_remote_dl_rate = int((m_remote_dl_rate * factor) + + ((m_remote_bytes_dled * (1.0f-factor)) / 60.f)); + + m_remote_bytes_dled = 0; + m_remote_dl_update = now; + } + + fill_send_buffer(); +/* + size_type diff = share_diff(); + + enum { block_limit = 2 }; // how many blocks difference is considered unfair + + // if the peer has been choked, send the current piece + // as fast as possible + if (diff > block_limit*m_torrent->block_size() || m_torrent->is_seed() || is_choked()) + { + // if we have downloaded more than one piece more + // than we have uploaded OR if we are a seed + // have an unlimited upload rate + m_ul_bandwidth_quota.wanted = std::numeric_limits::max(); + } + else + { + float ratio = m_torrent->ratio(); + // if we have downloaded too much, response with an + // upload rate of 10 kB/s more than we dowlload + // if we have uploaded too much, send with a rate of + // 10 kB/s less than we receive + int bias = 0; + if (diff > -block_limit*m_torrent->block_size()) + { + bias = static_cast(m_statistics.download_rate() * ratio) / 2; + if (bias < 10*1024) bias = 10*1024; + } + else + { + bias = -static_cast(m_statistics.download_rate() * ratio) / 2; + } + m_ul_bandwidth_quota.wanted = static_cast(m_statistics.download_rate()) + bias; + + // the maximum send_quota given our download rate from this peer + if (m_ul_bandwidth_quota.wanted < 256) m_ul_bandwidth_quota.wanted = 256; + } +*/ + } + + void peer_connection::fill_send_buffer() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + + // only add new piece-chunks if the send buffer is small enough + // otherwise there will be no end to how large it will be! + + int buffer_size_watermark = int(m_statistics.upload_rate()) / 2; + if (buffer_size_watermark < 1024) buffer_size_watermark = 1024; + else if (buffer_size_watermark > 80 * 1024) buffer_size_watermark = 80 * 1024; + + while (!m_requests.empty() + && (send_buffer_size() + m_reading_bytes < buffer_size_watermark) + && !m_choked) + { + assert(t->valid_metadata()); + peer_request& r = m_requests.front(); + + assert(r.piece >= 0); + assert(r.piece < (int)m_have_piece.size()); + assert(t->have_piece(r.piece)); + assert(r.start + r.length <= t->torrent_file().piece_size(r.piece)); + assert(r.length > 0 && r.start >= 0); + + t->filesystem().async_read(r, bind(&peer_connection::on_disk_read_complete + , self(), _1, _2, r)); + m_reading_bytes += r.length; + + m_requests.erase(m_requests.begin()); +/* + if (m_requests.empty() + && m_num_invalid_requests > 0 + && is_peer_interested() + && !is_seed()) + { + // this will make the peer clear + // its download queue and re-request + // pieces. Hopefully it will not + // send invalid requests then + send_choke(); + send_unchoke(); + } +*/ + } + } + + void peer_connection::on_disk_read_complete(int ret, disk_io_job const& j, peer_request r) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + m_reading_bytes -= r.length; + + if (ret != r.length || m_torrent.expired()) + { + boost::shared_ptr t = m_torrent.lock(); + if (!t) + { + m_ses.connection_failed(m_socket, remote(), j.str.c_str()); + return; + } + + if (t->alerts().should_post(alert::fatal)) + { + std::string err = "torrent paused: disk read error"; + if (!j.str.empty()) + { + err += ", "; + err += j.str; + } + t->alerts().post_alert(file_error_alert(t->get_handle(), err)); + } + t->pause(); + return; + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> PIECE [ piece: " << r.piece << " | s: " << r.start + << " | l: " << r.length << " ]\n"; +#endif + + write_piece(r, j.buffer); + setup_send(); + } + + void peer_connection::assign_bandwidth(int channel, int amount) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "bandwidth [ " << channel << " ] + " << amount << "\n"; +#endif + + m_bandwidth_limit[channel].assign(amount); + if (channel == upload_channel) + { + m_writing = false; + setup_send(); + } + else if (channel == download_channel) + { + m_reading = false; + setup_receive(); + } + } + + void peer_connection::expire_bandwidth(int channel, int amount) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + m_bandwidth_limit[channel].expire(amount); + if (channel == upload_channel) + { + setup_send(); + } + else if (channel == download_channel) + { + setup_receive(); + } + } + + void peer_connection::setup_send() + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (m_writing) return; + + shared_ptr t = m_torrent.lock(); + + if (m_bandwidth_limit[upload_channel].quota_left() == 0 + && (!m_send_buffer[m_current_send_buffer].empty() + || !m_send_buffer[(m_current_send_buffer + 1) & 1].empty()) + && !m_connecting + && t + && !m_ignore_bandwidth_limits) + { + // in this case, we have data to send, but no + // bandwidth. So, we simply request bandwidth + // from the torrent + assert(t); + if (m_bandwidth_limit[upload_channel].max_assignable() > 0) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "req bandwidth [ " << upload_channel << " ]\n"; +#endif + + // peers that we are not interested in are non-prioritized + t->request_bandwidth(upload_channel, self() + , !(is_interesting() && !has_peer_choked())); + m_writing = true; + } + return; + } + + if (!can_write()) return; + + assert(!m_writing); + + int sending_buffer = (m_current_send_buffer + 1) & 1; + if (m_send_buffer[sending_buffer].empty()) + { + // this means we have to swap buffer, because there's no + // previous buffer we're still waiting for. + std::swap(m_current_send_buffer, sending_buffer); + m_write_pos = 0; + } + + // send the actual buffer + if (!m_send_buffer[sending_buffer].empty()) + { + int amount_to_send = (int)m_send_buffer[sending_buffer].size() - m_write_pos; + int quota_left = m_bandwidth_limit[upload_channel].quota_left(); + if (!m_ignore_bandwidth_limits && amount_to_send > quota_left) + amount_to_send = quota_left; + + assert(amount_to_send > 0); + + assert(m_write_pos < (int)m_send_buffer[sending_buffer].size()); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "async_write " << amount_to_send << " bytes\n"; +#endif + m_socket->async_write_some(asio::buffer( + &m_send_buffer[sending_buffer][m_write_pos], amount_to_send) + , bind(&peer_connection::on_send_data, self(), _1, _2)); + + m_writing = true; + } + } + + void peer_connection::setup_receive() + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (m_reading) return; + + shared_ptr t = m_torrent.lock(); + + if (m_bandwidth_limit[download_channel].quota_left() == 0 + && !m_connecting + && t + && !m_ignore_bandwidth_limits) + { + if (m_bandwidth_limit[download_channel].max_assignable() > 0) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "req bandwidth [ " << download_channel << " ]\n"; +#endif + t->request_bandwidth(download_channel, self(), m_non_prioritized); + m_reading = true; + } + return; + } + + if (!can_read()) return; + + assert(m_packet_size > 0); + int max_receive = m_packet_size - m_recv_pos; + int quota_left = m_bandwidth_limit[download_channel].quota_left(); + if (!m_ignore_bandwidth_limits && max_receive > quota_left) + max_receive = quota_left; + + assert(max_receive > 0); + + assert(m_recv_pos >= 0); + assert(m_packet_size > 0); + + assert(can_read()); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "async_read " << max_receive << " bytes\n"; +#endif + m_socket->async_read_some(asio::buffer(&m_recv_buffer[m_recv_pos] + , max_receive), bind(&peer_connection::on_receive_data, self(), _1, _2)); + m_reading = true; + } + + void peer_connection::reset_recv_buffer(int packet_size) + { + assert(packet_size > 0); + if (m_recv_pos > m_packet_size) + { + cut_receive_buffer(m_packet_size, packet_size); + return; + } + m_recv_pos = 0; + m_packet_size = packet_size; + if (int(m_recv_buffer.size()) < m_packet_size) + m_recv_buffer.resize(m_packet_size); + } + + void peer_connection::send_buffer(char const* begin, char const* end) + { + std::vector& buf = m_send_buffer[m_current_send_buffer]; + buf.insert(buf.end(), begin, end); + setup_send(); + } + +// TODO: change this interface to automatically call setup_send() when the +// return value is destructed + buffer::interval peer_connection::allocate_send_buffer(int size) + { + std::vector& buf = m_send_buffer[m_current_send_buffer]; + buf.resize(buf.size() + size); + buffer::interval ret(&buf[0] + buf.size() - size, &buf[0] + buf.size()); + return ret; + } + + template + struct set_to_zero + { + set_to_zero(T& v, bool cond): m_val(v), m_cond(cond) {} + void fire() { if (!m_cond) return; m_cond = false; m_val = 0; } + ~set_to_zero() { if (m_cond) m_val = 0; } + private: + T& m_val; + bool m_cond; + }; + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + // throws exception when the client should be disconnected + void peer_connection::on_receive_data(const asio::error_code& error + , std::size_t bytes_transferred) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + assert(m_reading); + m_reading = false; + + if (error) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "**ERROR**: " << error.message() << "[in peer_connection::on_receive_data]\n"; +#endif + on_receive(error, bytes_transferred); + throw std::runtime_error(error.message()); + } + + do + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "read " << bytes_transferred << " bytes\n"; +#endif + // correct the dl quota usage, if not all of the buffer was actually read + if (!m_ignore_bandwidth_limits) + m_bandwidth_limit[download_channel].use_quota(bytes_transferred); + + if (m_disconnecting) return; + + assert(m_packet_size > 0); + assert(bytes_transferred > 0); + + m_last_receive = time_now(); + m_recv_pos += bytes_transferred; + assert(m_recv_pos <= int(m_recv_buffer.size())); + + { + INVARIANT_CHECK; + on_receive(error, bytes_transferred); + } + + assert(m_packet_size > 0); + + if (m_peer_choked + && m_recv_pos == 0 + && (m_recv_buffer.capacity() - m_packet_size) > 128) + { + std::vector(m_packet_size).swap(m_recv_buffer); + } + + int max_receive = m_packet_size - m_recv_pos; + int quota_left = m_bandwidth_limit[download_channel].quota_left(); + if (!m_ignore_bandwidth_limits && max_receive > quota_left) + max_receive = quota_left; + + if (max_receive == 0) break; + + asio::error_code ec; + bytes_transferred = m_socket->read_some(asio::buffer(&m_recv_buffer[m_recv_pos] + , max_receive), ec); + if (ec && ec != asio::error::would_block) + throw asio::system_error(ec); + } + while (bytes_transferred > 0); + + setup_receive(); + } + catch (file_error& e) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + boost::shared_ptr t = m_torrent.lock(); + if (!t) + { + m_ses.connection_failed(m_socket, remote(), e.what()); + return; + } + + if (t->alerts().should_post(alert::fatal)) + { + t->alerts().post_alert( + file_error_alert(t->get_handle() + , std::string("torrent paused: ") + e.what())); + } + t->pause(); + } + catch (std::exception& e) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), e.what()); + } + catch (...) + { + // all exceptions should derive from std::exception + assert(false); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), "connection failed for unknown reason"); + } + + bool peer_connection::can_write() const + { + INVARIANT_CHECK; + + // if we have requests or pending data to be sent or announcements to be made + // we want to send data + return (!m_send_buffer[m_current_send_buffer].empty() + || !m_send_buffer[(m_current_send_buffer + 1) & 1].empty()) + && (m_bandwidth_limit[upload_channel].quota_left() > 0 + || m_ignore_bandwidth_limits) + && !m_connecting; + } + + bool peer_connection::can_read() const + { + INVARIANT_CHECK; + + return (m_bandwidth_limit[download_channel].quota_left() > 0 + || m_ignore_bandwidth_limits) + && !m_connecting; + } + + void peer_connection::connect(int ticket) + { + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "CONNECTING: " << m_remote.address().to_string() + << ":" << m_remote.port() << "\n"; +#endif + + m_connection_ticket = ticket; + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + m_queued = false; + assert(m_connecting); + m_socket->open(t->get_interface().protocol()); + + // set the socket to non-blocking, so that we can + // read the entire buffer on each read event we get + tcp::socket::non_blocking_io ioc(true); + m_socket->io_control(ioc); + m_socket->bind(t->get_interface()); + m_socket->async_connect(m_remote + , bind(&peer_connection::on_connection_complete, self(), _1)); + + if (t->alerts().should_post(alert::debug)) + { + t->alerts().post_alert(peer_error_alert( + m_remote, m_peer_id, "connecting to peer")); + } + } + + void peer_connection::on_connection_complete(asio::error_code const& e) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (m_disconnecting) return; + + m_connecting = false; + m_ses.m_half_open.done(m_connection_ticket); + + if (e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "CONNECTION FAILED: " << m_remote.address().to_string() + << ": " << e.message() << "\n"; +#endif + m_ses.connection_failed(m_socket, m_remote, e.message().c_str()); + return; + } + + if (m_disconnecting) return; + m_last_receive = time_now(); + + // this means the connection just succeeded + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "COMPLETED: " << m_remote.address().to_string() << "\n"; +#endif + + on_connected(); + setup_send(); + setup_receive(); + } + catch (std::exception& ex) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), ex.what()); + } + catch (...) + { + // all exceptions should derive from std::exception + assert(false); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), "connection failed for unkown reason"); + } + + // -------------------------- + // SEND DATA + // -------------------------- + + // throws exception when the client should be disconnected + void peer_connection::on_send_data(asio::error_code const& error + , std::size_t bytes_transferred) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + assert(m_writing); + m_writing = false; + + if (!m_ignore_bandwidth_limits) + m_bandwidth_limit[upload_channel].use_quota(bytes_transferred); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "wrote " << bytes_transferred << " bytes\n"; +#endif + + m_write_pos += bytes_transferred; + + + if (error) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "**ERROR**: " << error.message() << " [in peer_connection::on_send_data]\n"; +#endif + throw std::runtime_error(error.message()); + } + if (m_disconnecting) return; + + assert(!m_connecting); + assert(bytes_transferred > 0); + + int sending_buffer = (m_current_send_buffer + 1) & 1; + + assert(int(m_send_buffer[sending_buffer].size()) >= m_write_pos); + if (int(m_send_buffer[sending_buffer].size()) == m_write_pos) + { + m_send_buffer[sending_buffer].clear(); + m_write_pos = 0; + } + + m_last_sent = time_now(); + + on_sent(error, bytes_transferred); + fill_send_buffer(); + + if (m_choked) + { + for (int i = 0; i < 2; ++i) + { + if (int(m_send_buffer[i].size()) < 64 + && int(m_send_buffer[i].capacity()) > 128) + { + std::vector tmp(m_send_buffer[i]); + tmp.swap(m_send_buffer[i]); + assert(m_send_buffer[i].capacity() == m_send_buffer[i].size()); + } + } + } + + setup_send(); + } + catch (std::exception& e) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), e.what()); + } + catch (...) + { + // all exceptions should derive from std::exception + assert(false); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), "connection failed for unknown reason"); + } + + +#ifndef NDEBUG + void peer_connection::check_invariant() const + { + if (m_peer_info) + assert(m_peer_info->connection == this + || m_peer_info->connection == 0); + + boost::shared_ptr t = m_torrent.lock(); + if (!t) + { + typedef session_impl::torrent_map torrent_map; + torrent_map& m = m_ses.m_torrents; + for (torrent_map::iterator i = m.begin(), end(m.end()); i != end; ++i) + { + torrent& t = *i->second; + assert(t.connection_for(m_remote) != this); + } + return; + } + + if (!m_in_constructor && t->connection_for(remote()) != this + && !m_ses.settings().allow_multiple_connections_per_ip) + { + assert(false); + } + +// expensive when using checked iterators +/* + if (t->valid_metadata()) + { + int piece_count = std::count(m_have_piece.begin() + , m_have_piece.end(), true); + if (m_num_pieces != piece_count) + { + assert(false); + } + } +*/ + assert(m_write_pos <= int(m_send_buffer[ + (m_current_send_buffer + 1) & 1].size())); + +// extremely expensive invariant check +/* + if (!t->is_seed()) + { + piece_picker& p = t->picker(); + const std::vector& dlq = p.get_download_queue(); + const int blocks_per_piece = static_cast( + t->torrent_file().piece_length() / t->block_size()); + + for (std::vector::const_iterator i = + dlq.begin(); i != dlq.end(); ++i) + { + for (int j = 0; j < blocks_per_piece; ++j) + { + if (std::find(m_request_queue.begin(), m_request_queue.end() + , piece_block(i->index, j)) != m_request_queue.end() + || + std::find(m_download_queue.begin(), m_download_queue.end() + , piece_block(i->index, j)) != m_download_queue.end()) + { + assert(i->info[j].peer == m_remote); + } + else + { + assert(i->info[j].peer != m_remote || i->info[j].finished); + } + } + } + } +*/ + } +#endif + + bool peer_connection::has_timed_out() const + { + // TODO: the timeout should be called by an event + INVARIANT_CHECK; + +#ifndef NDEBUG + // allow step debugging without timing out + return false; +#endif + + ptime now(time_now()); + + // if the socket is still connecting, don't + // consider it timed out. Because Windows XP SP2 + // may delay connection attempts. + if (m_connecting) return false; + + // if the peer hasn't said a thing for a certain + // time, it is considered to have timed out + time_duration d; + d = time_now() - m_last_receive; + if (d > seconds(m_timeout)) return true; + + // TODO: as long as we have less than 95% of the + // global (or local) connection limit, connections should + // never time out for another reason + + // if the peer hasn't become interested and we haven't + // become interested in the peer for 10 minutes, it + // has also timed out. + time_duration d1; + time_duration d2; + d1 = now - m_became_uninterested; + d2 = now - m_became_uninteresting; + time_duration time_limit = seconds( + m_ses.settings().inactivity_timeout); + + if (!m_interesting + && !m_peer_interested + && d1 > time_limit + && d2 > time_limit) + { + return true; + } + + return false; + } + + peer_connection::peer_speed_t peer_connection::peer_speed() + { + shared_ptr t = m_torrent.lock(); + assert(t); + + int download_rate = int(statistics().download_payload_rate()); + int torrent_download_rate = int(t->statistics().download_payload_rate()); + + if (download_rate > 512 && download_rate > torrent_download_rate / 16) + m_speed = fast; + else if (download_rate > 4096 && download_rate > torrent_download_rate / 64) + m_speed = medium; + else if (download_rate < torrent_download_rate / 15 && m_speed == fast) + m_speed = medium; + else if (download_rate < torrent_download_rate / 63 && m_speed == medium) + m_speed = slow; + + return m_speed; + } + + void peer_connection::keep_alive() + { + INVARIANT_CHECK; + + time_duration d; + d = time_now() - m_last_sent; + if (total_seconds(d) < m_timeout / 2) return; + + if (m_connecting) return; + if (in_handshake()) return; + + // if the last send has not completed yet, do not send a keep + // alive + if (m_writing) return; + +#ifdef TORRENT_VERBOSE_LOGGING + using namespace boost::posix_time; + (*m_logger) << time_now_string() << " ==> KEEPALIVE\n"; +#endif + + write_keepalive(); + } + + bool peer_connection::is_seed() const + { + INVARIANT_CHECK; + // if m_num_pieces == 0, we probably don't have the + // metadata yet. + return m_num_pieces == (int)m_have_piece.size() && m_num_pieces > 0; + } +} + diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp new file mode 100755 index 000000000..bd568210f --- /dev/null +++ b/libtorrent/src/piece_picker.cpp @@ -0,0 +1,1543 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include + +// non-standard header, is_sorted() +//#include + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#ifndef NDEBUG +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#endif + +#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK +//#define TORRENT_PIECE_PICKER_INVARIANT_CHECK + +namespace libtorrent +{ + + piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks) + : m_piece_info(2) + , m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece) + , m_num_filtered(0) + , m_num_have_filtered(0) + , m_num_have(0) + , m_sequenced_download_threshold(100) + { + assert(blocks_per_piece > 0); + assert(total_num_blocks >= 0); +#ifndef NDEBUG + m_files_checked_called = false; +#endif + + // the piece index is stored in 20 bits, which limits the allowed + // number of pieces somewhat + if (m_piece_map.size() >= piece_pos::we_have_index) + throw std::runtime_error("too many pieces in torrent"); + + m_blocks_per_piece = blocks_per_piece; + m_blocks_in_last_piece = total_num_blocks % blocks_per_piece; + if (m_blocks_in_last_piece == 0) m_blocks_in_last_piece = blocks_per_piece; + + assert(m_blocks_in_last_piece <= m_blocks_per_piece); + + // allocate the piece_map to cover all pieces + // and make them invalid (as if though we already had every piece) + std::fill(m_piece_map.begin(), m_piece_map.end() + , piece_pos(0, piece_pos::we_have_index)); + m_num_have = m_piece_map.size(); + } + + // pieces is a bitmask with the pieces we have + void piece_picker::files_checked( + std::vector const& pieces + , std::vector const& unfinished + , std::vector& verify_pieces) + { +#ifndef NDEBUG + m_files_checked_called = true; +#endif + for (std::vector::const_iterator i = pieces.begin(); + i != pieces.end(); ++i) + { + if (*i) continue; + int index = static_cast(i - pieces.begin()); + m_piece_map[index].index = 0; + --m_num_have; + if (m_piece_map[index].filtered()) + { + ++m_num_filtered; + --m_num_have_filtered; + } + } + + // if we have fast resume info + // use it + if (!unfinished.empty()) + { + for (std::vector::const_iterator i + = unfinished.begin(); i != unfinished.end(); ++i) + { + tcp::endpoint peer; + for (int j = 0; j < m_blocks_per_piece; ++j) + { + if (i->info[j].state == block_info::state_finished) + mark_as_finished(piece_block(i->index, j), peer); + } + if (is_piece_finished(i->index)) + { + verify_pieces.push_back(i->index); + } + } + } + } + + void piece_picker::set_sequenced_download_threshold( + int sequenced_download_threshold) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + if (sequenced_download_threshold == m_sequenced_download_threshold) + return; + + assert(sequenced_download_threshold > 0); + + int old_limit = m_sequenced_download_threshold; + m_sequenced_download_threshold = sequenced_download_threshold; + + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + if (i->priority(old_limit) != i->priority(m_sequenced_download_threshold)) + { + piece_pos& p = *i; + int prev_priority = p.priority(old_limit); + if (prev_priority == 0) continue; + move(prev_priority, p.index); + } + } + + typedef std::vector info_t; + + if (old_limit < sequenced_download_threshold) + { + // the threshold was incremented, in case + // the previous max availability was reached + // we need to shuffle that bucket, if not, we + // don't have to do anything + if (int(m_piece_info.size()) > old_limit) + { + info_t& in = m_piece_info[old_limit]; + std::random_shuffle(in.begin(), in.end()); + int c = 0; + for (info_t::iterator i = in.begin() + , end(in.end()); i != end; ++i) + { + m_piece_map[*i].index = c++; + assert(m_piece_map[*i].priority(old_limit) == old_limit); + } + } + } + else if (int(m_piece_info.size()) > sequenced_download_threshold) + { + info_t& in = m_piece_info[sequenced_download_threshold]; + std::sort(in.begin(), in.end()); + int c = 0; + for (info_t::iterator i = in.begin() + , end(in.end()); i != end; ++i) + { + m_piece_map[*i].index = c++; + assert(m_piece_map[*i].priority( + sequenced_download_threshold) == sequenced_download_threshold); + } + } + } + + piece_picker::downloading_piece& piece_picker::add_download_piece() + { + int num_downloads = m_downloads.size(); + int block_index = num_downloads * m_blocks_per_piece; + if (int(m_block_info.size()) < block_index + m_blocks_per_piece) + { + m_block_info.resize(block_index + m_blocks_per_piece); + if (!m_downloads.empty() && &m_block_info[0] != m_downloads.front().info) + { + // this means the memory was reallocated, update the pointers + for (int i = 0; i < int(m_downloads.size()); ++i) + m_downloads[i].info = &m_block_info[i * m_blocks_per_piece]; + } + } + m_downloads.push_back(downloading_piece()); + downloading_piece& ret = m_downloads.back(); + ret.info = &m_block_info[block_index]; + for (int i = 0; i < m_blocks_per_piece; ++i) + { + ret.info[i].num_downloads = 0; + ret.info[i].state = block_info::state_none; + ret.info[i].peer = tcp::endpoint(); + } + return ret; + } + + void piece_picker::erase_download_piece(std::vector::iterator i) + { + if (i != m_downloads.end() - 1) + { + int remove_index = i - m_downloads.begin(); + int last_index = m_downloads.size() - 1; + assert(remove_index < last_index); + + assert(int(m_block_info.size()) >= last_index * m_blocks_per_piece + m_blocks_per_piece); + std::copy(m_block_info.begin() + (last_index * m_blocks_per_piece) + , m_block_info.begin() + (last_index * m_blocks_per_piece + m_blocks_per_piece) + , m_block_info.begin() + (remove_index * m_blocks_per_piece)); + m_downloads[remove_index] = m_downloads[last_index]; + m_downloads[remove_index].info = &m_block_info[remove_index * m_blocks_per_piece]; + } + + m_downloads.pop_back(); + } +#ifndef NDEBUG + + void piece_picker::check_invariant(const torrent* t) const + { + assert(sizeof(piece_pos) == 4); + + assert(m_piece_info.empty() || m_piece_info[0].empty()); + + if (t != 0) + assert((int)m_piece_map.size() == t->torrent_file().num_pieces()); + + for (int i = m_sequenced_download_threshold * 2 + 1; i < int(m_piece_info.size()); ++i) + assert(m_piece_info[i].empty()); + + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + bool blocks_requested = false; + int num_blocks = blocks_in_piece(i->index); + int num_requested = 0; + int num_finished = 0; + int num_writing = 0; + for (int k = 0; k < num_blocks; ++k) + { + if (i->info[k].state == block_info::state_finished) + { + ++num_finished; + continue; + } + if (i->info[k].state == block_info::state_requested) + { + ++num_requested; + blocks_requested = true; + } + if (i->info[k].state == block_info::state_writing) + { + ++num_writing; + } + } + assert(blocks_requested == (i->state != none)); + assert(num_requested == i->requested); + assert(num_writing == i->writing); + assert(num_finished == i->finished); + } + + + int num_filtered = 0; + int num_have_filtered = 0; + int num_have = 0; + for (std::vector::const_iterator i = m_piece_map.begin(); + i != m_piece_map.end(); ++i) + { + int index = static_cast(i - m_piece_map.begin()); + if (i->filtered()) + { + if (i->index != piece_pos::we_have_index) + ++num_filtered; + else + ++num_have_filtered; + } + if (i->index == piece_pos::we_have_index) + ++num_have; + +#if 0 + if (t != 0) + { + int actual_peer_count = 0; + for (torrent::const_peer_iterator peer = t->begin(); + peer != t->end(); ++peer) + { + if (peer->second->has_piece(index)) actual_peer_count++; + } + + assert((int)i->peer_count == actual_peer_count); +/* + int num_downloaders = 0; + for (std::vector::const_iterator peer = t->begin(); + peer != t->end(); + ++peer) + { + const std::vector& queue = (*peer)->download_queue(); + if (std::find_if(queue.begin(), queue.end(), has_index(index)) == queue.end()) continue; + + ++num_downloaders; + } + + if (i->downloading) + { + assert(num_downloaders == 1); + } + else + { + assert(num_downloaders == 0); + } +*/ + } +#endif + + if (i->index == piece_pos::we_have_index) + { + assert(t == 0 || t->have_piece(index)); + assert(i->downloading == 0); +/* + // make sure there's no entry + // with this index. (there shouldn't + // be since the piece_map is piece_pos::we_have_index) + for (int i = 0; i < int(m_piece_info.size()); ++i) + { + for (int j = 0; j < int(m_piece_info[i].size()); ++j) + { + assert(m_piece_info[i][j] != index); + } + } +*/ + } + else + { + if (t != 0) + assert(!t->have_piece(index)); + + int prio = i->priority(m_sequenced_download_threshold); + if (prio > 0) + { + const std::vector& vec = m_piece_info[prio]; + assert (i->index < vec.size()); + assert(vec[i->index] == index); + } +/* + for (int k = 0; k < int(m_piece_info.size()); ++k) + { + for (int j = 0; j < int(m_piece_info[k].size()); ++j) + { + assert(int(m_piece_info[k][j]) != index + || (prio > 0 && prio == k && int(i->index) == j)); + } + } +*/ + } + + int count = std::count_if(m_downloads.begin(), m_downloads.end() + , has_index(index)); + if (i->downloading == 1) + { + assert(count == 1); + } + else + { + assert(count == 0); + } + } + assert(num_have == m_num_have); + assert(num_filtered == m_num_filtered); + assert(num_have_filtered == m_num_have_filtered); + } +#endif + + float piece_picker::distributed_copies() const + { + const float num_pieces = static_cast(m_piece_map.size()); + + int min_availability = piece_pos::max_peer_count; + // find the lowest availability count + // count the number of pieces that have that availability + // and also the number of pieces that have more than that. + int integer_part = 0; + int fraction_part = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int peer_count = int(i->peer_count); + // take ourself into account + if (i->have()) ++peer_count; + if (min_availability > peer_count) + { + min_availability = i->peer_count; + fraction_part += integer_part; + integer_part = 1; + } + else if (peer_count == min_availability) + { + ++integer_part; + } + else + { + assert(peer_count > min_availability); + ++fraction_part; + } + } + assert(integer_part + fraction_part == num_pieces); + return float(min_availability) + (fraction_part / num_pieces); + } + + void piece_picker::add(int index) + { + assert(index >= 0); + assert(index < int(m_piece_map.size())); + piece_pos& p = m_piece_map[index]; + assert(!p.filtered()); + assert(!p.have()); + + int priority = p.priority(m_sequenced_download_threshold); + assert(priority > 0); + if (int(m_piece_info.size()) <= priority) + m_piece_info.resize(priority + 1); + + assert(int(m_piece_info.size()) > priority); + + if (is_ordered(priority)) + { + // the piece should be inserted ordered, not randomly + std::vector& v = m_piece_info[priority]; +// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + std::vector::iterator i = std::lower_bound(v.begin(), v.end() + , index/*, std::greater()*/); + p.index = i - v.begin(); + v.insert(i, index); + i = v.begin() + p.index + 1; + for (;i != v.end(); ++i) + { + ++m_piece_map[*i].index; + assert(v[m_piece_map[*i].index] == *i); + } +// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + } + else if (m_piece_info[priority].size() < 2) + { + p.index = m_piece_info[priority].size(); + m_piece_info[priority].push_back(index); + } + else + { + // find a random position in the destination vector where we will place + // this entry. + int dst_index = rand() % m_piece_info[priority].size(); + + // copy the entry at that position to the back + m_piece_map[m_piece_info[priority][dst_index]].index + = m_piece_info[priority].size(); + m_piece_info[priority].push_back(m_piece_info[priority][dst_index]); + + // and then replace the one at dst_index with the one we're moving. + // this procedure is to make sure there's no ordering when pieces + // are moved in sequenced order. + p.index = dst_index; + m_piece_info[priority][p.index] = index; + } + } + + // will update the piece with the given properties (priority, elem_index) + // to place it at the correct position in the vectors. + void piece_picker::move(int priority, int elem_index) + { + assert(priority > 0); + assert(elem_index >= 0); + assert(m_files_checked_called); + + assert(int(m_piece_info.size()) > priority); + assert(int(m_piece_info[priority].size()) > elem_index); + + int index = m_piece_info[priority][elem_index]; + // update the piece_map + piece_pos& p = m_piece_map[index]; + assert(int(p.index) == elem_index || p.have()); + + int new_priority = p.priority(m_sequenced_download_threshold); + + if (new_priority == priority) return; + + if (int(m_piece_info.size()) <= new_priority + && new_priority > 0) + { + m_piece_info.resize(new_priority + 1); + assert(int(m_piece_info.size()) > new_priority); + } + + if (new_priority == 0) + { + // this means the piece should not have an entry + } + else if (is_ordered(new_priority)) + { + // the piece should be inserted ordered, not randomly + std::vector& v = m_piece_info[new_priority]; +// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + std::vector::iterator i = std::lower_bound(v.begin(), v.end() + , index/*, std::greater()*/); + p.index = i - v.begin(); + v.insert(i, index); + i = v.begin() + p.index + 1; + for (;i != v.end(); ++i) + { + ++m_piece_map[*i].index; + assert(v[m_piece_map[*i].index] == *i); + } +// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + } + else if (m_piece_info[new_priority].size() < 2) + { + p.index = m_piece_info[new_priority].size(); + m_piece_info[new_priority].push_back(index); + } + else + { + // find a random position in the destination vector where we will place + // this entry. + int dst_index = rand() % m_piece_info[new_priority].size(); + + // copy the entry at that position to the back + m_piece_map[m_piece_info[new_priority][dst_index]].index + = m_piece_info[new_priority].size(); + m_piece_info[new_priority].push_back(m_piece_info[new_priority][dst_index]); + + // and then replace the one at dst_index with the one we're moving. + // this procedure is to make sure there's no ordering when pieces + // are moved in sequenced order. + p.index = dst_index; + m_piece_info[new_priority][p.index] = index; + } + assert(new_priority == 0 || p.index < m_piece_info[p.priority(m_sequenced_download_threshold)].size()); + assert(new_priority == 0 || m_piece_info[p.priority(m_sequenced_download_threshold)][p.index] == index); + + if (is_ordered(priority)) + { + // remove the element from the source vector and preserve the order + std::vector& v = m_piece_info[priority]; + v.erase(v.begin() + elem_index); + for (std::vector::iterator i = v.begin() + elem_index; + i != v.end(); ++i) + { + --m_piece_map[*i].index; + assert(v[m_piece_map[*i].index] == *i); + } + } + else + { + // this will remove elem from the source vector without + // preserving order, but the order is random anyway + int replace_index = m_piece_info[priority][elem_index] = m_piece_info[priority].back(); + if (index != replace_index) + { + // update the entry we moved from the back + m_piece_map[replace_index].index = elem_index; + + assert(int(m_piece_info[priority].size()) > elem_index); + // this may not necessarily be the case. If we've just updated the threshold and are updating + // the piece map +// assert((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); + assert(int(m_piece_map[replace_index].index) == elem_index); + assert(m_piece_info[priority][elem_index] == replace_index); + } + else + { + assert(int(m_piece_info[priority].size()) == elem_index+1); + } + + m_piece_info[priority].pop_back(); + } + } + + void piece_picker::restore_piece(int index) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(index >= 0); + assert(index < (int)m_piece_map.size()); + assert(m_files_checked_called); + + assert(m_piece_map[index].downloading == 1); + + std::vector::iterator i + = std::find_if(m_downloads.begin(), + m_downloads.end(), + has_index(index)); + assert(i != m_downloads.end()); + erase_download_piece(i); + + piece_pos& p = m_piece_map[index]; + int prev_priority = p.priority(m_sequenced_download_threshold); + p.downloading = 0; + int new_priority = p.priority(m_sequenced_download_threshold); + + if (new_priority == prev_priority) return; + + if (prev_priority == 0) + { + add(index); + } + else + { + move(prev_priority, p.index); + } + } + + void piece_picker::inc_refcount_all() + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(m_files_checked_called); + + // in general priority = availability * 2 + // see piece_block::priority() + + // this will insert two empty vectors at the start of the + // piece_info vector. It is done like this as an optimization, + // to swap vectors instead of copying them + while (m_piece_info.size() < 3 + || (!m_piece_info.rbegin()->empty()) + || (!(m_piece_info.rbegin()+1)->empty())) + { + m_piece_info.push_back(std::vector()); + } + assert(m_piece_info.rbegin()->empty()); + assert((m_piece_info.rbegin()+1)->empty()); + typedef std::vector > piece_info_t; + for (piece_info_t::reverse_iterator i = m_piece_info.rbegin(), j(i+1) + , k(j+1), end(m_piece_info.rend()); k != end; ++i, ++j, ++k) + { + k->swap(*i); + } + assert(m_piece_info.begin()->empty()); + assert((m_piece_info.begin()+1)->empty()); + + // if we have some priorities that are clamped to the + // sequenced download, move that vector back down + int last_index = m_piece_info.size() - 1; + int cap_index = m_sequenced_download_threshold * 2; + if (last_index == cap_index) + { + // this is the case when the top bucket + // was moved up into the sequenced download bucket. + m_piece_info.push_back(std::vector()); + m_piece_info[cap_index].swap(m_piece_info[cap_index+1]); + ++last_index; + } + else if (last_index > cap_index) + { + if (last_index - cap_index == 1) + { + m_piece_info.push_back(std::vector()); + ++last_index; + } + m_piece_info[cap_index+1].swap(m_piece_info[cap_index+2]); + m_piece_info[cap_index].swap(m_piece_info[cap_index+1]); + } + + // now, increase the peer count of all the pieces. + // because of different priorities, some pieces may have + // ended up in the wrong priority bucket. Adjust that. + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int prev_prio = i->priority(m_sequenced_download_threshold); + ++i->peer_count; + // if the assumption that the priority would + // increase by 2 when increasing the availability + // by one isn't true for this particular piece, correct it. + // that assumption is true for all pieces with priority 0 or 1 + int new_prio = i->priority(m_sequenced_download_threshold); + assert(new_prio <= cap_index); + if (prev_prio == 0 && new_prio > 0) + { + add(i - m_piece_map.begin()); + continue; + } + if (new_prio == 0) + { + assert(prev_prio == 0); + continue; + } + if (prev_prio == cap_index) + { + assert(new_prio == cap_index); + continue; + } + if (new_prio == prev_prio + 2 && new_prio != cap_index) + { + assert(new_prio != cap_index); + continue; + } + if (prev_prio + 2 >= cap_index) + { + // these two vectors will have moved one extra step + // passed the sequenced download threshold + ++prev_prio; + } + assert(prev_prio + 2 != cap_index); + assert(prev_prio + 2 != new_prio); + move(prev_prio + 2, i->index); + } + } + + void piece_picker::dec_refcount_all() + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(m_files_checked_called); + assert(m_piece_info.size() >= 2); + assert(m_piece_info.front().empty()); + // swap all vectors two steps down + if (m_piece_info.size() > 2) + { + typedef std::vector > piece_info_t; + for (piece_info_t::iterator i = m_piece_info.begin(), j(i+1) + , k(j+1), end(m_piece_info.end()); k != end; ++i, ++j, ++k) + { + k->swap(*i); + } + } + else + { + m_piece_info.resize(3); + } + int last_index = m_piece_info.size() - 1; + if ((m_piece_info.size() & 1) == 0) + { + // if there's an even number of vectors, swap + // the last two to get the same layout in both cases + m_piece_info[last_index].swap(m_piece_info[last_index-1]); + } + assert(m_piece_info.back().empty()); + int pushed_out_index = m_piece_info.size() - 2; + + int cap_index = m_sequenced_download_threshold * 2; + assert(m_piece_info[last_index].empty()); + if (last_index >= cap_index) + { + assert(pushed_out_index == cap_index - 1 + || m_piece_info[cap_index - 1].empty()); + m_piece_info[cap_index].swap(m_piece_info[cap_index - 2]); + if (cap_index == pushed_out_index) + pushed_out_index = cap_index - 2; + } + + // first is the vector that were + // bumped down to 0. The should always be moved + // since they have to be removed or reinserted + std::vector().swap(m_piece_info.front()); + + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int prev_prio = i->priority(m_sequenced_download_threshold); + assert(i->peer_count > 0); + --i->peer_count; + // if the assumption that the priority would + // decrease by 2 when decreasing the availability + // by one isn't true for this particular piece, correct it. + // that assumption is true for all pieces with priority 0 or 1 + if (prev_prio == 0) + { + assert(i->priority(m_sequenced_download_threshold) == 0); + continue; + } + + int new_prio = i->priority(m_sequenced_download_threshold); + if (prev_prio == cap_index) + { + if (new_prio == cap_index) continue; + prev_prio += 2; + } + else if (new_prio == prev_prio - 2) + { + continue; + } + else if (prev_prio == 2) + { + // if this piece was pushed down to priority 0, it was + // removed + assert(new_prio > 0); + add(i - m_piece_map.begin()); + continue; + } + else if (prev_prio == 1) + { + // if this piece was one of the vectors that was pushed to the + // top, adjust the prev_prio to point to that vector, so that + // the pieces are moved from there + prev_prio = pushed_out_index + 2; + } + move(prev_prio - 2, i->index); + } + } + + void piece_picker::inc_refcount(int i) + { +// TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(i >= 0); + assert(i < (int)m_piece_map.size()); + assert(m_files_checked_called); + + piece_pos& p = m_piece_map[i]; + int index = p.index; + int prev_priority = p.priority(m_sequenced_download_threshold); + + assert(p.peer_count < piece_pos::max_peer_count); + p.peer_count++; + assert(p.peer_count != 0); + + // if we have the piece or if it's filtered + // we don't have to move any entries in the piece_info vector + if (p.priority(m_sequenced_download_threshold) == prev_priority) return; + + if (prev_priority == 0) + { + add(i); + } + else + { + move(prev_priority, index); + } + +#ifndef NDEBUG +// integrity_check(); +#endif + return; + } + + void piece_picker::dec_refcount(int i) + { +// TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(m_files_checked_called); + assert(i >= 0); + assert(i < (int)m_piece_map.size()); + + piece_pos& p = m_piece_map[i]; + int prev_priority = p.priority(m_sequenced_download_threshold); + int index = p.index; + assert(p.peer_count > 0); + + if (p.peer_count > 0) + p.peer_count--; + + if (p.priority(m_sequenced_download_threshold) == prev_priority) return; + + move(prev_priority, index); + } + + // this is used to indicate that we succesfully have + // downloaded a piece, and that no further attempts + // to pick that piece should be made. The piece will + // be removed from the available piece list. + void piece_picker::we_have(int index) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(index >= 0); + assert(index < (int)m_piece_map.size()); + + piece_pos& p = m_piece_map[index]; + int info_index = p.index; + int priority = p.priority(m_sequenced_download_threshold); + + assert(p.downloading == 1); + assert(!p.have()); + + std::vector::iterator i + = std::find_if(m_downloads.begin() + , m_downloads.end() + , has_index(index)); + assert(i != m_downloads.end()); + erase_download_piece(i); + p.downloading = 0; + + assert(std::find_if(m_downloads.begin(), m_downloads.end() + , has_index(index)) == m_downloads.end()); + + if (p.have()) return; + if (p.filtered()) + { + --m_num_filtered; + ++m_num_have_filtered; + } + ++m_num_have; + p.set_have(); + if (priority == 0) return; + assert(p.priority(m_sequenced_download_threshold) == 0); + move(priority, info_index); + } + + + void piece_picker::set_piece_priority(int index, int new_piece_priority) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(new_piece_priority >= 0); + assert(new_piece_priority <= 7); + assert(index >= 0); + assert(index < (int)m_piece_map.size()); + + piece_pos& p = m_piece_map[index]; + + // if the priority isn't changed, don't do anything + if (new_piece_priority == int(p.piece_priority)) return; + + int prev_priority = p.priority(m_sequenced_download_threshold); + + if (new_piece_priority == piece_pos::filter_priority + && p.piece_priority != piece_pos::filter_priority) + { + // the piece just got filtered + if (p.have()) ++m_num_have_filtered; + else ++m_num_filtered; + } + else if (new_piece_priority != piece_pos::filter_priority + && p.piece_priority == piece_pos::filter_priority) + { + // the piece just got unfiltered + if (p.have()) --m_num_have_filtered; + else --m_num_filtered; + } + assert(m_num_filtered >= 0); + assert(m_num_have_filtered >= 0); + + p.piece_priority = new_piece_priority; + int new_priority = p.priority(m_sequenced_download_threshold); + + if (new_priority == prev_priority) return; + + if (prev_priority == 0) + { + add(index); + } + else + { + move(prev_priority, p.index); + } + } + + int piece_picker::piece_priority(int index) const + { + assert(index >= 0); + assert(index < (int)m_piece_map.size()); + + return m_piece_map[index].piece_priority; + } + + void piece_picker::piece_priorities(std::vector& pieces) const + { + pieces.resize(m_piece_map.size()); + std::vector::iterator j = pieces.begin(); + for (std::vector::const_iterator i = m_piece_map.begin(), + end(m_piece_map.end()); i != end; ++i, ++j) + { + *j = i->piece_priority; + } + } + + // ============ start deprecation ============== + + void piece_picker::filtered_pieces(std::vector& mask) const + { + mask.resize(m_piece_map.size()); + std::vector::iterator j = mask.begin(); + for (std::vector::const_iterator i = m_piece_map.begin(), + end(m_piece_map.end()); i != end; ++i, ++j) + { + *j = i->filtered(); + } + } + + // ============ end deprecation ============== + + // pieces describes which pieces the peer we're requesting from + // has. + // interesting_blocks is an out parameter, and will be filled + // with (up to) num_blocks of interesting blocks that the peer has. + // prefer_whole_pieces can be set if this peer should download + // whole pieces rather than trying to download blocks from the + // same piece as other peers. + // the endpoint is the address of the peer we're picking pieces + // from. This is used when downloading whole pieces, to only + // pick from the same piece the same peer is downloading from. + // state is supposed to be set to fast if the peer is downloading + // relatively fast, by some notion. Slow peers will prefer not + // to pick blocks from the same pieces as fast peers, and vice + // versa. Downloading pieces are marked as being fast, medium + // or slow once they're started. + void piece_picker::pick_pieces(const std::vector& pieces + , std::vector& interesting_blocks + , int num_blocks, bool prefer_whole_pieces + , tcp::endpoint peer, piece_state_t speed) const + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(num_blocks > 0); + assert(pieces.size() == m_piece_map.size()); + assert(m_files_checked_called); + + assert(m_piece_info.begin() != m_piece_info.end()); + + // this will be filled with blocks that we should not request + // unless we can't find num_blocks among the other ones. + // blocks that belong to pieces with a mismatching speed + // category for instance, or if we prefer whole pieces, + // blocks belonging to a piece that others have + // downloaded to + std::vector backup_blocks; + + // this loop will loop from pieces with priority 1 and up + // until we either reach the end of the piece list or + // has filled the interesting_blocks with num_blocks + // blocks. + + // When prefer_whole_pieces is set (usually set when downloading from + // fast peers) the partial pieces will not be prioritized, but actually + // ignored as long as possible. + + // +1 is to ignore pieces that no peer has. The bucket with index 0 contains + // pieces that 0 other peers have. bucket will point to a bucket with + // pieces with the same priority. It will be iterated in priority + // order (high priority/rare pices first). The content of each + // bucket is randomized + for (std::vector >::const_iterator bucket + = m_piece_info.begin() + 1; bucket != m_piece_info.end(); + ++bucket) + { + if (bucket->empty()) continue; + num_blocks = add_interesting_blocks(*bucket, pieces + , interesting_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer, speed); + assert(num_blocks >= 0); + if (num_blocks == 0) return; + } + + assert(num_blocks > 0); + + if (!backup_blocks.empty()) + interesting_blocks.insert(interesting_blocks.end() + , backup_blocks.begin(), backup_blocks.begin() + + (std::min)(num_blocks, (int)backup_blocks.size())); + } + + namespace + { + bool exclusively_requested_from(piece_picker::downloading_piece const& p + , int num_blocks_in_piece, tcp::endpoint peer) + { + for (int j = 0; j < num_blocks_in_piece; ++j) + { + piece_picker::block_info const& info = p.info[j]; + if (info.state != piece_picker::block_info::state_none + && info.peer != peer + && info.peer != tcp::endpoint()) + { + return false; + } + } + return true; + } + } + + int piece_picker::add_interesting_blocks(std::vector const& piece_list + , std::vector const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, bool prefer_whole_pieces + , tcp::endpoint peer, piece_state_t speed) const + { + // if we have less than 1% of the pieces, ignore speed priorities and just try + // to finish any downloading piece + bool ignore_speed_categories = (m_num_have * 100 / m_piece_map.size()) < 1; + for (std::vector::const_iterator i = piece_list.begin(); + i != piece_list.end(); ++i) + { + assert(*i >= 0); + assert(*i < (int)m_piece_map.size()); + + // if the peer doesn't have the piece + // skip it + if (!pieces[*i]) continue; + + int num_blocks_in_piece = blocks_in_piece(*i); + + if (m_piece_map[*i].downloading == 1) + { + std::vector::const_iterator p + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); + assert(p != m_downloads.end()); + + // is true if all the other pieces that are currently + // requested from this piece are from the same + // peer as 'peer'. + bool only_same_peer = exclusively_requested_from(*p + , num_blocks_in_piece, peer); + + // this means that this partial piece has + // been downloaded/requested partially from + // another peer that isn't us. And since + // we prefer whole pieces, add this piece's + // blocks to the backup list. If the prioritized + // blocks aren't enough, blocks from this list + // will be picked. + if (prefer_whole_pieces && !only_same_peer) + { + if (int(backup_blocks.size()) >= num_blocks) continue; + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = p->info[j]; + if (info.state == block_info::state_finished + || info.state == block_info::state_writing) + continue; + if (info.state == block_info::state_requested + && info.peer == peer) continue; + backup_blocks.push_back(piece_block(*i, j)); + } + continue; + } + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + // ignore completed blocks + block_info const& info = p->info[j]; + if (info.state == block_info::state_finished + || info.state == block_info::state_writing) + continue; + // ignore blocks requested from this peer already + if (info.state == block_info::state_requested + && info.peer == peer) + continue; + // if the piece is fast and the peer is slow, or vice versa, + // add the block as a backup. + // override this behavior if all the other blocks + // have been requested from the same peer or + // if the state of the piece is none (the + // piece will in that case change state). + if (p->state != none && p->state != speed + && !only_same_peer + && !ignore_speed_categories) + { + if (int(backup_blocks.size()) >= num_blocks) continue; + backup_blocks.push_back(piece_block(*i, j)); + continue; + } + // this block is interesting (we don't have it + // yet). But it may already have been requested + // from another peer. We have to add it anyway + // to allow the requester to determine if the + // block should be requested from more than one + // peer. If it is being downloaded, we continue + // to look for blocks until we have num_blocks + // blocks that have not been requested from any + // other peer. + interesting_blocks.push_back(piece_block(*i, j)); + if (p->info[j].state == block_info::state_none) + { + // we have found a block that's free to download + num_blocks--; + // if we prefer whole pieces, continue picking from this + // piece even though we have num_blocks + if (prefer_whole_pieces) continue; + assert(num_blocks >= 0); + if (num_blocks == 0) return num_blocks; + } + } + assert(num_blocks >= 0 || prefer_whole_pieces); + if (num_blocks < 0) num_blocks = 0; + } + else + { + if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.push_back(piece_block(*i, j)); + } + num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); + } + assert(num_blocks >= 0); + if (num_blocks == 0) return num_blocks; + } + return num_blocks; + } + + bool piece_picker::is_piece_finished(int index) const + { + assert(index < (int)m_piece_map.size()); + assert(index >= 0); + + if (m_piece_map[index].downloading == 0) + { + assert(std::find_if(m_downloads.begin(), m_downloads.end() + , has_index(index)) == m_downloads.end()); + return false; + } + std::vector::const_iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); + assert(i != m_downloads.end()); + assert((int)i->finished <= m_blocks_per_piece); + int max_blocks = blocks_in_piece(index); + if ((int)i->finished < max_blocks) return false; + +#ifndef NDEBUG + for (int k = 0; k < max_blocks; ++k) + { + assert(i->info[k].state == block_info::state_finished); + } +#endif + + assert((int)i->finished == max_blocks); + return true; + } + + bool piece_picker::is_requested(piece_block block) const + { + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + + if (m_piece_map[block.piece_index].downloading == 0) return false; + std::vector::const_iterator i + = std::find_if( + m_downloads.begin() + , m_downloads.end() + , has_index(block.piece_index)); + + assert(i != m_downloads.end()); + return i->info[block.block_index].state == block_info::state_requested; + } + + bool piece_picker::is_downloaded(piece_block block) const + { + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + + if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true; + if (m_piece_map[block.piece_index].downloading == 0) return false; + std::vector::const_iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + return i->info[block.block_index].state == block_info::state_finished + || i->info[block.block_index].state == block_info::state_writing; + } + + bool piece_picker::is_finished(piece_block block) const + { + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + + if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true; + if (m_piece_map[block.piece_index].downloading == 0) return false; + std::vector::const_iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + return i->info[block.block_index].state == block_info::state_finished; + } + + + void piece_picker::mark_as_downloading(piece_block block + , const tcp::endpoint& peer, piece_state_t state) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + assert(block.block_index < blocks_in_piece(block.piece_index)); + + piece_pos& p = m_piece_map[block.piece_index]; + if (p.downloading == 0) + { + int prio = p.priority(m_sequenced_download_threshold); + p.downloading = 1; + move(prio, p.index); + + downloading_piece& dp = add_download_piece(); + dp.state = state; + dp.index = block.piece_index; + block_info& info = dp.info[block.block_index]; + info.state = block_info::state_requested; + info.peer = peer; + ++dp.requested; + } + else + { + std::vector::iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + block_info& info = i->info[block.block_index]; + assert(info.state == block_info::state_none); + info.peer = peer; + info.state = block_info::state_requested; + ++i->requested; + if (i->state == none) i->state = state; + } + } + + void piece_picker::get_availability(std::vector& avail) const + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + avail.resize(m_piece_map.size()); + std::vector::iterator j = avail.begin(); + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i, ++j) + *j = i->peer_count; + } + + void piece_picker::mark_as_writing(piece_block block, tcp::endpoint const& peer) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + assert(block.block_index < blocks_in_piece(block.piece_index)); + + piece_pos& p = m_piece_map[block.piece_index]; + assert(p.downloading); + +/* if (p.downloading == 0) + { + int prio = p.priority(m_sequenced_download_threshold); + p.downloading = 1; + if (prio > 0) move(prio, p.index); + else assert(p.priority(m_sequenced_download_threshold) == 0); + + downloading_piece& dp = add_download_piece(); + dp.state = none; + dp.index = block.piece_index; + block_info& info = dp.info[block.block_index]; + info.peer = peer; + if (info.state == block_info::state_requested) --dp.requested; + assert(dp.requested >= 0); + assert (info.state != block_info::state_finished); + assert (info.state != block_info::state_writing); + if (info.state != block_info::state_requested) ++dp.writing; + info.state = block_info::state_writing; + } + else +*/ { + std::vector::iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + block_info& info = i->info[block.block_index]; + info.peer == peer; + assert(info.state == block_info::state_requested); + if (info.state == block_info::state_requested) --i->requested; + assert(i->requested >= 0); + assert (info.state != block_info::state_writing); + ++i->writing; + info.state = block_info::state_writing; + + if (i->requested == 0) + { + // there are no blocks requested in this piece. + // remove the fast/slow state from it + i->state = none; + } + } + } + + void piece_picker::mark_as_finished(piece_block block, tcp::endpoint const& peer) + { + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + assert(block.block_index < blocks_in_piece(block.piece_index)); + + piece_pos& p = m_piece_map[block.piece_index]; + + if (p.downloading == 0) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(peer == tcp::endpoint()); + int prio = p.priority(m_sequenced_download_threshold); + p.downloading = 1; + if (prio > 0) move(prio, p.index); + else assert(p.priority(m_sequenced_download_threshold) == 0); + + downloading_piece& dp = add_download_piece(); + dp.state = none; + dp.index = block.piece_index; + block_info& info = dp.info[block.block_index]; + info.peer = peer; + assert(info.state == block_info::state_none); +// if (info.state == block_info::state_writing) --dp.writing; +// assert(dp.writing >= 0); + if (info.state != block_info::state_finished) ++dp.finished; + info.state = block_info::state_finished; + } + else + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + std::vector::iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + block_info& info = i->info[block.block_index]; + info.peer = peer; + assert(info.state == block_info::state_writing + || peer == tcp::endpoint()); + if (info.state == block_info::state_writing) --i->writing; + assert(i->writing >= 0); + ++i->finished; + info.state = block_info::state_finished; + + if (i->requested == 0) + { + // there are no blocks requested in this piece. + // remove the fast/slow state from it + i->state = none; + } + } + } + + void piece_picker::get_downloaders(std::vector& d, int index) const + { + assert(index >= 0 && index <= (int)m_piece_map.size()); + std::vector::const_iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); + assert(i != m_downloads.end()); + + d.clear(); + for (int j = 0; j < blocks_in_piece(index); ++j) + { + d.push_back(i->info[j].peer); + } + } + + boost::optional piece_picker::get_downloader(piece_block block) const + { + std::vector::const_iterator i = std::find_if( + m_downloads.begin() + , m_downloads.end() + , has_index(block.piece_index)); + + if (i == m_downloads.end()) + return boost::optional(); + + assert(block.block_index >= 0); + + if (i->info[block.block_index].state == block_info::state_none) + return boost::optional(); + + return boost::optional(i->info[block.block_index].peer); + } + + void piece_picker::abort_download(piece_block block) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + assert(block.block_index < blocks_in_piece(block.piece_index)); + + if (m_piece_map[block.piece_index].downloading == 0) + { + assert(std::find_if(m_downloads.begin(), m_downloads.end() + , has_index(block.piece_index)) == m_downloads.end()); + return; + } + + std::vector::iterator i = std::find_if(m_downloads.begin() + , m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + + if (i->info[block.block_index].state == block_info::state_finished + || i->info[block.block_index].state == block_info::state_writing) + { + return; + } + + assert(block.block_index < blocks_in_piece(block.piece_index)); + assert(i->info[block.block_index].state == block_info::state_requested); + + // clear this block as being downloaded + i->info[block.block_index].state = block_info::state_none; + --i->requested; + + // clear the downloader of this block + i->info[block.block_index].peer = tcp::endpoint(); + + // if there are no other blocks in this piece + // that's being downloaded, remove it from the list + if (i->requested + i->finished + i->writing == 0) + { + erase_download_piece(i); + piece_pos& p = m_piece_map[block.piece_index]; + int prio = p.priority(m_sequenced_download_threshold); + p.downloading = 0; + if (prio > 0) move(prio, p.index); + + assert(std::find_if(m_downloads.begin(), m_downloads.end() + , has_index(block.piece_index)) == m_downloads.end()); + } + else if (i->requested == 0) + { + // there are no blocks requested in this piece. + // remove the fast/slow state from it + i->state = none; + } + } + + int piece_picker::unverified_blocks() const + { + int counter = 0; + for (std::vector::const_iterator i = m_downloads.begin(); + i != m_downloads.end(); ++i) + { + counter += (int)i->finished; + } + return counter; + } + +} + diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp new file mode 100755 index 000000000..eca5ba613 --- /dev/null +++ b/libtorrent/src/policy.cpp @@ -0,0 +1,1488 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +namespace libtorrent +{ + class peer_connection; +} + +using boost::bind; + +namespace +{ + using namespace libtorrent; + + size_type collect_free_download( + torrent::peer_iterator start + , torrent::peer_iterator end) + { + size_type accumulator = 0; + for (torrent::peer_iterator i = start; i != end; ++i) + { + // if the peer is interested in us, it means it may + // want to trade it's surplus uploads for downloads itself + // (and we should not consider it free). If the share diff is + // negative, there's no free download to get from this peer. + size_type diff = i->second->share_diff(); + assert(diff < std::numeric_limits::max()); + if (i->second->is_peer_interested() || diff <= 0) + continue; + + assert(diff > 0); + i->second->add_free_upload(-diff); + accumulator += diff; + assert(accumulator > 0); + } + assert(accumulator >= 0); + return accumulator; + } + + + // returns the amount of free upload left after + // it has been distributed to the peers + size_type distribute_free_upload( + torrent::peer_iterator start + , torrent::peer_iterator end + , size_type free_upload) + { + if (free_upload <= 0) return free_upload; + int num_peers = 0; + size_type total_diff = 0; + for (torrent::peer_iterator i = start; i != end; ++i) + { + size_type d = i->second->share_diff(); + assert(d < std::numeric_limits::max()); + total_diff += d; + if (!i->second->is_peer_interested() || i->second->share_diff() >= 0) continue; + ++num_peers; + } + + if (num_peers == 0) return free_upload; + size_type upload_share; + if (total_diff >= 0) + { + upload_share = std::min(free_upload, total_diff) / num_peers; + } + else + { + upload_share = (free_upload + total_diff) / num_peers; + } + if (upload_share < 0) return free_upload; + + for (torrent::peer_iterator i = start; i != end; ++i) + { + peer_connection* p = i->second; + if (!p->is_peer_interested() || p->share_diff() >= 0) continue; + p->add_free_upload(upload_share); + free_upload -= upload_share; + } + return free_upload; + } + + struct match_peer_ip + { + match_peer_ip(tcp::endpoint const& ip) + : m_ip(ip) + {} + + bool operator()(policy::peer const& p) const + { return p.ip.address() == m_ip.address(); } + + tcp::endpoint const& m_ip; + }; + + struct match_peer_id + { + match_peer_id(peer_id const& id_) + : m_id(id_) + {} + + bool operator()(policy::peer const& p) const + { return p.connection && p.connection->pid() == m_id; } + + peer_id const& m_id; + }; + + struct match_peer_connection + { + match_peer_connection(peer_connection const& c) + : m_conn(c) + {} + + bool operator()(policy::peer const& p) const + { + return p.connection == &m_conn + || (p.ip == m_conn.remote() + && p.type == policy::peer::connectable); + } + + peer_connection const& m_conn; + }; + + +} + +namespace libtorrent +{ + // the case where ignore_peer is motivated is if two peers + // have only one piece that we don't have, and it's the + // same piece for both peers. Then they might get into an + // infinite loop, fighting to request the same blocks. + void request_a_block( + torrent& t + , peer_connection& c + , std::vector ignore) + { + assert(!t.is_seed()); + assert(!c.has_peer_choked()); + int num_requests = c.desired_queue_size() + - (int)c.download_queue().size() + - (int)c.request_queue().size(); + + assert(c.desired_queue_size() > 0); + // if our request queue is already full, we + // don't have to make any new requests yet + if (num_requests <= 0) return; + + piece_picker& p = t.picker(); + std::vector interesting_pieces; + interesting_pieces.reserve(100); + + bool prefer_whole_pieces = c.prefer_whole_pieces() + || (c.peer_info_struct() && c.peer_info_struct()->on_parole); + + if (!prefer_whole_pieces) + { + prefer_whole_pieces = c.statistics().download_payload_rate() + * t.settings().whole_pieces_threshold + > t.torrent_file().piece_length(); + } + + // if we prefer whole pieces, the piece picker will pick at least + // the number of blocks we want, but it will try to make the picked + // blocks be from whole pieces, possibly by returning more blocks + // than we requested. + assert(c.remote() == c.get_socket()->remote_endpoint()); + + piece_picker::piece_state_t state; + peer_connection::peer_speed_t speed = c.peer_speed(); + if (speed == peer_connection::fast) state = piece_picker::fast; + else if (speed == peer_connection::medium) state = piece_picker::medium; + else state = piece_picker::slow; + + // picks the interesting pieces from this peer + // the integer is the number of pieces that + // should be guaranteed to be available for download + // (if num_requests is too big, too many pieces are + // picked and cpu-time is wasted) + // the last argument is if we should prefer whole pieces + // for this peer. If we're downloading one piece in 20 seconds + // then use this mode. + p.pick_pieces(c.get_bitfield(), interesting_pieces + , num_requests, prefer_whole_pieces, c.remote(), state); + + // this vector is filled with the interesting pieces + // that some other peer is currently downloading + // we should then compare this peer's download speed + // with the other's, to see if we should abort another + // peer_connection in favour of this one + std::vector busy_pieces; + busy_pieces.reserve(10); + + for (std::vector::iterator i = interesting_pieces.begin(); + i != interesting_pieces.end(); ++i) + { + if (p.is_requested(*i)) + { + busy_pieces.push_back(*i); + continue; + } + + // ok, we found a piece that's not being downloaded + // by somebody else. request it from this peer + // and return + c.add_request(*i); + num_requests--; + } + + c.send_block_requests(); + + // in this case, we could not find any blocks + // that was free. If we couldn't find any busy + // blocks as well, we cannot download anything + // more from this peer. + + if (busy_pieces.empty()) return; + + // first look for blocks that are just queued + // and not actually sent to us yet + // (then we can cancel those and request them + // from this peer instead) + + while (num_requests > 0) + { + peer_connection* peer = 0; + + const int initial_queue_size = (int)c.download_queue().size() + + (int)c.request_queue().size(); + + // This peer's weight will be the minimum, to prevent + // cancelling requests from a faster peer. + float min_weight = initial_queue_size == 0 + ? std::numeric_limits::max() + : c.statistics().download_payload_rate() / initial_queue_size; + + // find the peer with the lowest download + // speed that also has a piece that this + // peer could send us + for (torrent::peer_iterator i = t.begin(); + i != t.end(); ++i) + { + // don't try to take over blocks from ourself + if (i->second == &c) + continue; + + // ignore all peers in the ignore list + if (std::find(ignore.begin(), ignore.end(), i->second) != ignore.end()) + continue; + + const std::deque& download_queue = i->second->download_queue(); + const std::deque& request_queue = i->second->request_queue(); + const int queue_size = (int)i->second->download_queue().size() + + (int)i->second->request_queue().size(); + + bool in_request_queue = std::find_first_of( + busy_pieces.begin() + , busy_pieces.end() + , request_queue.begin() + , request_queue.end()) != busy_pieces.end(); + + bool in_download_queue = std::find_first_of( + busy_pieces.begin() + , busy_pieces.end() + , download_queue.begin() + , download_queue.end()) != busy_pieces.end(); + + // if the block is in the request queue rather than the download queue + // (i.e. the request message hasn't been sent yet) lower the weight in + // order to prioritize it. Taking over a block in the request queue is + // free in terms of redundant download. A block that already has been + // requested is likely to be in transit already, and would in that case + // mean redundant data to receive. + const float weight = (queue_size == 0) + ? std::numeric_limits::max() + : i->second->statistics().download_payload_rate() / queue_size + * in_request_queue ? .1f : 1.f; + + // if the peer's (i) weight is less than the lowest we've found so + // far (weight == priority) and it has blocks in its request- + // or download queue that we could request from this peer (c), + // replace the currently lowest ranking peer. + if (weight < min_weight && (in_request_queue || in_download_queue)) + { + peer = i->second; + min_weight = weight; + } + } + + if (peer == 0) + { + // we probably couldn't request the block because + // we are ignoring some peers + break; + } + + // find a suitable block to take over from this peer + + std::deque::const_reverse_iterator common_block = + std::find_first_of( + peer->request_queue().rbegin() + , peer->request_queue().rend() + , busy_pieces.begin() + , busy_pieces.end()); + + if (common_block == peer->request_queue().rend()) + { + common_block = std::find_first_of( + peer->download_queue().rbegin() + , peer->download_queue().rend() + , busy_pieces.begin() + , busy_pieces.end()); + assert(common_block != peer->download_queue().rend()); + } + + piece_block block = *common_block; + + // the one we interrupted may need to request a new piece. + // make sure it doesn't take over a block from the peer + // that just took over its block (that would cause an + // infinite recursion) + peer->cancel_request(block); + c.add_request(block); + ignore.push_back(&c); + if (!peer->has_peer_choked() && !t.is_seed()) + { + request_a_block(t, *peer, ignore); + peer->send_block_requests(); + } + + num_requests--; + + const int queue_size = (int)c.download_queue().size() + + (int)c.request_queue().size(); + const float weight = queue_size == 0 + ? std::numeric_limits::max() + : c.statistics().download_payload_rate() / queue_size; + + // this peer doesn't have a faster connection than the + // slowest peer. Don't take over any blocks + if (weight <= min_weight) break; + } + c.send_block_requests(); + } + + policy::policy(torrent* t) + : m_torrent(t) + , m_num_unchoked(0) + , m_available_free_upload(0) + , m_last_optimistic_disconnect(min_time()) + { assert(t); } + // finds the peer that has the worst download rate + // and returns it. May return 0 if all peers are + // choked. + policy::iterator policy::find_choke_candidate() + { + INVARIANT_CHECK; + + iterator worst_peer = m_peers.end(); + size_type min_weight = std::numeric_limits::min(); + +#ifndef NDEBUG + int unchoked_counter = m_num_unchoked; +#endif + + // TODO: make this selection better + + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + peer_connection* c = i->connection; + + if (c == 0) continue; + if (c->is_choked()) continue; +#ifndef NDEBUG + unchoked_counter--; +#endif + if (c->is_disconnecting()) continue; + // if the peer isn't interested, just choke it + if (!c->is_peer_interested()) + return i; + + size_type diff = i->total_download() + - i->total_upload(); + + size_type weight = static_cast(c->statistics().download_rate() * 10.f) + + diff + + ((c->is_interesting() && c->has_peer_choked())?-10:10)*1024; + + if (weight >= min_weight && worst_peer != m_peers.end()) continue; + + min_weight = weight; + worst_peer = i; + continue; + } + assert(unchoked_counter == 0); + return worst_peer; + } + + policy::iterator policy::find_unchoke_candidate() + { + INVARIANT_CHECK; + + // if all of our peers are unchoked, there's + // no left to unchoke + if (m_num_unchoked == m_torrent->num_peers()) + return m_peers.end(); + + iterator unchoke_peer = m_peers.end(); + ptime min_time = libtorrent::min_time(); + float max_down_speed = 0.f; + + // TODO: make this selection better + + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + peer_connection* c = i->connection; + if (c == 0) continue; + if (c->is_disconnecting()) continue; + if (!c->is_choked()) continue; + if (!c->is_peer_interested()) continue; + if (c->share_diff() < -free_upload_amount + && m_torrent->ratio() != 0) continue; + if (c->statistics().download_rate() < max_down_speed) continue; + + min_time = i->last_optimistically_unchoked; + max_down_speed = c->statistics().download_rate(); + unchoke_peer = i; + } + return unchoke_peer; + } + + policy::iterator policy::find_disconnect_candidate() + { + INVARIANT_CHECK; + + iterator disconnect_peer = m_peers.end(); + double slowest_transfer_rate = std::numeric_limits::max(); + + ptime now = time_now(); + + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + peer_connection* c = i->connection; + if (c == 0) continue; + if (c->is_disconnecting()) continue; + + // never disconnect an interesting peer if we have a candidate that + // isn't interesting + if (disconnect_peer != m_peers.end() + && c->is_interesting() + && !disconnect_peer->connection->is_interesting()) + continue; + + double transferred_amount + = (double)c->statistics().total_payload_download(); + + time_duration connected_time = now - i->connected; + + double connected_time_in_seconds = total_seconds(connected_time); + + double transfer_rate + = transferred_amount / (connected_time_in_seconds + 1); + + // prefer to disconnect uninteresting peers, and secondly slow peers + if (transfer_rate <= slowest_transfer_rate + || (disconnect_peer != m_peers.end() + && disconnect_peer->connection->is_interesting() + && !c->is_interesting())) + { + slowest_transfer_rate = transfer_rate; + disconnect_peer = i; + } + } + return disconnect_peer; + } + + policy::iterator policy::find_connect_candidate() + { + INVARIANT_CHECK; + + ptime now = time_now(); + ptime min_connect_time(now); + iterator candidate = m_peers.end(); + + int max_failcount = m_torrent->settings().max_failcount; + int min_reconnect_time = m_torrent->settings().min_reconnect_time; + + aux::session_impl& ses = m_torrent->session(); + + for (iterator i = m_peers.begin(); i != m_peers.end(); ++i) + { + if (i->connection) continue; + if (i->banned) continue; + if (i->type == peer::not_connectable) continue; + if (i->seed && m_torrent->is_seed()) continue; + if (i->failcount >= max_failcount) continue; + if (now - i->connected < seconds(i->failcount * min_reconnect_time)) + continue; + if (ses.m_port_filter.access(i->ip.port()) & port_filter::blocked) + continue; + + assert(i->connected <= now); + + if (i->connected <= min_connect_time) + { + min_connect_time = i->connected; + candidate = i; + } + } + + assert(min_connect_time <= now); + + return candidate; + } + + policy::iterator policy::find_seed_choke_candidate() + { + INVARIANT_CHECK; + + assert(m_num_unchoked > 0); + // first choice candidate. + // it is a candidate we owe nothing to and which has been unchoked + // the longest. + iterator candidate = m_peers.end(); + + // not valid when candidate == 0 + ptime last_unchoke = min_time(); + + // second choice candidate. + // if there is no first choice candidate, this candidate will be chosen. + // it is the candidate that we owe the least to. + iterator second_candidate = m_peers.end(); + size_type lowest_share_diff = 0; // not valid when secondCandidate==0 + + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + peer_connection* c = i->connection; + // ignore peers that are choked or + // whose connection is closed + if (c == 0) continue; + + if (c->is_choked()) continue; + if (c->is_disconnecting()) continue; + + size_type share_diff = c->share_diff(); + + // select as second candidate the one that we owe the least + // to + if (second_candidate == m_peers.end() + || share_diff <= lowest_share_diff) + { + lowest_share_diff = share_diff; + second_candidate = i; + } + + // select as first candidate the one that we don't owe anything to + // and has been waiting for an unchoke the longest + if (share_diff > 0) continue; + if (candidate == m_peers.end() + || last_unchoke > i->last_optimistically_unchoked) + { + last_unchoke = i->last_optimistically_unchoked; + candidate = i; + } + } + if (candidate != m_peers.end()) return candidate; + assert(second_candidate != m_peers.end()); + return second_candidate; + } + + policy::iterator policy::find_seed_unchoke_candidate() + { + INVARIANT_CHECK; + + iterator candidate = m_peers.end(); + ptime last_unchoke = time_now(); + + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + peer_connection* c = i->connection; + if (c == 0) continue; + if (!c->is_choked()) continue; + if (!c->is_peer_interested()) continue; + if (c->is_disconnecting()) continue; + if (last_unchoke < i->last_optimistically_unchoked) continue; + last_unchoke = i->last_optimistically_unchoked; + candidate = i; + } + return candidate; + } + + bool policy::seed_unchoke_one_peer() + { + INVARIANT_CHECK; + + iterator p = find_seed_unchoke_candidate(); + if (p != m_peers.end()) + { + assert(p->connection->is_choked()); + p->connection->send_unchoke(); + p->last_optimistically_unchoked = time_now(); + ++m_num_unchoked; + } + return p != m_peers.end(); + } + + void policy::seed_choke_one_peer() + { + INVARIANT_CHECK; + + iterator p = find_seed_choke_candidate(); + if (p != m_peers.end()) + { + assert(!p->connection->is_choked()); + p->connection->send_choke(); + --m_num_unchoked; + } + } + + void policy::pulse() + { + INVARIANT_CHECK; + + if (m_torrent->is_paused()) return; + + ptime now = time_now(); + // remove old disconnected peers from the list + for (iterator i = m_peers.begin(); i != m_peers.end();) + { + // this timeout has to be customizable! + if (i->connection == 0 + && i->connected != min_time() + && now - i->connected > minutes(120)) + { + m_peers.erase(i++); + } + else + { + ++i; + } + } + + // ------------------------------------- + // maintain the number of connections + // ------------------------------------- + + // count the number of connected peers except for peers + // that are currently in the process of disconnecting + int num_connected_peers = 0; + + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + if (i->connection && !i->connection->is_disconnecting()) + ++num_connected_peers; + } + + if (m_torrent->m_connections_quota.given != std::numeric_limits::max()) + { + + int max_connections = m_torrent->m_connections_quota.given; + + if (num_connected_peers >= max_connections) + { + // every minute, disconnect the worst peer in hope of finding a better peer + + ptime local_time = time_now(); + if (m_last_optimistic_disconnect + seconds(120) <= local_time + && find_connect_candidate() != m_peers.end()) + { + m_last_optimistic_disconnect = local_time; + --max_connections; // this will have the effect of disconnecting the worst peer + } + } + else + { + // don't do a disconnect earlier than 1 minute after some peer was connected + m_last_optimistic_disconnect = time_now(); + } + + while (num_connected_peers > max_connections) + { + bool ret = disconnect_one_peer(); + (void)ret; + assert(ret); + --num_connected_peers; + } + } + + // ------------------------ + // upload shift + // ------------------------ + + // this part will shift downloads + // from peers that are seeds and peers + // that don't want to download from us + // to peers that cannot upload anything + // to us. The shifting will make sure + // that the torrent's share ratio + // will be maintained + + // if the share ratio is 0 (infinite) + // m_available_free_upload isn't used + // because it isn't necessary + if (m_torrent->ratio() != 0.f) + { + // accumulate all the free download we get + // and add it to the available free upload + m_available_free_upload + += collect_free_download( + m_torrent->begin() + , m_torrent->end()); + + // distribute the free upload among the peers + m_available_free_upload = distribute_free_upload( + m_torrent->begin() + , m_torrent->end() + , m_available_free_upload); + } + + // ------------------------ + // seed choking policy + // ------------------------ + if (m_torrent->is_seed()) + { + if (m_num_unchoked > m_torrent->m_uploads_quota.given) + { + do + { + iterator p = find_seed_choke_candidate(); + --m_num_unchoked; + assert(p != m_peers.end()); + if (p == m_peers.end()) break; + + assert(!p->connection->is_choked()); + p->connection->send_choke(); + } while (m_num_unchoked > m_torrent->m_uploads_quota.given); + } + else if (m_num_unchoked > 0) + { + // optimistic unchoke. trade the 'worst' + // unchoked peer with one of the choked + // TODO: This rotation should happen + // far less frequent than this! + assert(m_num_unchoked <= m_torrent->num_peers()); + iterator p = find_seed_unchoke_candidate(); + if (p != m_peers.end()) + { + assert(p->connection->is_choked()); + seed_choke_one_peer(); + p->connection->send_unchoke(); + ++m_num_unchoked; + } + + } + + // make sure we have enough + // unchoked peers + while (m_num_unchoked < m_torrent->m_uploads_quota.given) + { + if (!seed_unchoke_one_peer()) break; + } +#ifndef NDEBUG + check_invariant(); +#endif + } + + // ---------------------------- + // downloading choking policy + // ---------------------------- + else + { + if (m_torrent->ratio() != 0) + { + // choke peers that have leeched too much without giving anything back + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + peer_connection* c = i->connection; + if (c == 0) continue; + + size_type diff = i->connection->share_diff(); + if (diff < -free_upload_amount + && !c->is_choked()) + { + // if we have uploaded more than a piece for free, choke peer and + // wait until we catch up with our download. + c->send_choke(); + --m_num_unchoked; + } + } + } + + if (m_torrent->m_uploads_quota.given < m_torrent->num_peers()) + { + assert(m_torrent->m_uploads_quota.given >= 0); + + // make sure we don't have too many + // unchoked peers + if (m_num_unchoked > m_torrent->m_uploads_quota.given) + { + do + { + iterator p = find_choke_candidate(); + if (p == m_peers.end()) break; + assert(p != m_peers.end()); + assert(!p->connection->is_choked()); + p->connection->send_choke(); + --m_num_unchoked; + } while (m_num_unchoked > m_torrent->m_uploads_quota.given); + } + // this should prevent the choke/unchoke + // problem, since it will not unchoke unless + // there actually are any choked peers + else if (count_choked() > 0) + { + // optimistic unchoke. trade the 'worst' + // unchoked peer with one of the choked + assert(m_num_unchoked <= m_torrent->num_peers()); + iterator p = find_unchoke_candidate(); + if (p != m_peers.end()) + { + assert(p->connection->is_choked()); + choke_one_peer(); + p->connection->send_unchoke(); + ++m_num_unchoked; + } + } + } + + // make sure we have enough + // unchoked peers + while (m_num_unchoked < m_torrent->m_uploads_quota.given + && unchoke_one_peer()); + } + } + + int policy::count_choked() const + { + int ret = 0; + for (const_iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + if (!i->connection + || i->connection->is_connecting() + || i->connection->is_disconnecting() + || !i->connection->is_peer_interested()) + continue; + if (i->connection->is_choked()) ++ret; + } + return ret; + } + + void policy::new_connection(peer_connection& c) + { + assert(!c.is_local()); + + INVARIANT_CHECK; + + // if the connection comes from the tracker, + // it's probably just a NAT-check. Ignore the + // num connections constraint then. + + // TODO: only allow _one_ connection to use this + // override at a time + assert(c.remote() == c.get_socket()->remote_endpoint()); + + if (m_torrent->num_peers() >= m_torrent->m_connections_quota.given + && c.remote().address() != m_torrent->current_tracker().address()) + { + throw protocol_error("too many connections, refusing incoming connection"); // cause a disconnect + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (c.remote().address() == m_torrent->current_tracker().address()) + { + m_torrent->debug_log("overriding connection limit for tracker NAT-check"); + } +#endif + + iterator i; + + if (m_torrent->settings().allow_multiple_connections_per_ip) + { + i = std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_connection(c)); + } + else + { + i = std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_ip(c.remote())); + } + + if (i != m_peers.end()) + { + if (i->banned) + throw protocol_error("ip address banned, closing"); + + if (i->connection != 0) + { + assert(i->connection != &c); + // the new connection is a local (outgoing) connection + // or the current one is already connected + if (!i->connection->is_connecting() || c.is_local()) + { + throw protocol_error("duplicate connection, closing"); + } + else + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + m_torrent->debug_log("duplicate connection. existing connection" + " is connecting and this connection is incoming. closing existing " + "connection in favour of this one"); +#endif + i->connection->disconnect(); + } + } + } + else + { + // we don't have ny info about this peer. + // add a new entry + assert(c.remote() == c.get_socket()->remote_endpoint()); + + peer p(c.remote(), peer::not_connectable, 0); + m_peers.push_back(p); + i = boost::prior(m_peers.end()); + } + + c.set_peer_info(&*i); + assert(i->connection == 0); + c.add_stat(i->prev_amount_download, i->prev_amount_upload); + i->prev_amount_download = 0; + i->prev_amount_upload = 0; + i->connection = &c; + assert(i->connection); + i->connected = time_now(); + m_last_optimistic_disconnect = time_now(); + } + + void policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid + , int src, char flags) + { + INVARIANT_CHECK; + + // just ignore the obviously invalid entries + if(remote.address() == address() || remote.port() == 0) + return; + + aux::session_impl& ses = m_torrent->session(); + + port_filter const& pf = ses.m_port_filter; + if (pf.access(remote.port()) & port_filter::blocked) + { + if (ses.m_alerts.should_post(alert::info)) + { + ses.m_alerts.post_alert(peer_blocked_alert(remote.address() + , "outgoing port blocked, peer not added to peer list")); + } + return; + } + + try + { + iterator i; + + if (m_torrent->settings().allow_multiple_connections_per_ip) + { + i = std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_id(pid)); + } + else + { + i = std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_ip(remote)); + } + + if (i == m_peers.end()) + { + // if the IP is blocked, don't add it + if (ses.m_ip_filter.access(remote.address()) & ip_filter::blocked) + { + if (ses.m_alerts.should_post(alert::info)) + { + ses.m_alerts.post_alert(peer_blocked_alert(remote.address() + , "blocked peer not added to peer list")); + } + return; + } + + // we don't have any info about this peer. + // add a new entry + peer p(remote, peer::connectable, src); + m_peers.push_back(p); + // the iterator is invalid + // because of the push_back() + i = boost::prior(m_peers.end()); +#ifndef TORRENT_DISABLE_ENCRYPTION + if (flags & 0x01) p.pe_support = true; +#endif + if (flags & 0x02) i->seed = true; + + // try to send a DHT ping to this peer + // as well, to figure out if it supports + // DHT (uTorrent and BitComet doesn't + // advertise support) +#ifndef TORRENT_DISABLE_DHT + udp::endpoint node(remote.address(), remote.port()); + m_torrent->session().add_dht_node(node); +#endif + } + else + { + i->type = peer::connectable; + + // in case we got the ip from a remote connection, port is + // not known, so save it. Client may also have changed port + // for some reason. + i->ip = remote; + i->source |= src; + + // if this peer has failed before, decrease the + // counter to allow it another try, since somebody + // else is appearantly able to connect to it + // if it comes from the DHT it might be stale though + if (i->failcount > 0 && src != peer_info::dht) + --i->failcount; + + if (flags & 0x02) i->seed = true; + + if (i->connection) + { + // this means we're already connected + // to this peer. don't connect to + // it again. + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + m_torrent->debug_log("already connected to peer: " + remote.address().to_string() + ":" + + boost::lexical_cast(remote.port()) + " " + + boost::lexical_cast(i->connection->pid())); +#endif + + assert(i->connection->associated_torrent().lock().get() == m_torrent); + return; + } + } + } + catch(std::exception& e) + { + if (m_torrent->alerts().should_post(alert::debug)) + { + m_torrent->alerts().post_alert( + peer_error_alert(remote, pid, e.what())); + } + } + } + + // this is called when we are choked by a peer + // i.e. a peer lets us know that we will not receive + // anything for a while + void policy::choked(peer_connection&) + { + } + + void policy::piece_finished(int index, bool successfully_verified) + { + INVARIANT_CHECK; + + assert(index >= 0 && index < m_torrent->torrent_file().num_pieces()); + + if (successfully_verified) + { + // have all peers update their interested-flag + for (iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + if (i->connection == 0) continue; + // if we're not interested, we will not become interested + if (!i->connection->is_interesting()) continue; + if (!i->connection->has_piece(index)) continue; + + i->connection->update_interest(); + } + } + } + + // this is called when we are unchoked by a peer + // i.e. a peer lets us know that we will receive + // data from now on + void policy::unchoked(peer_connection& c) + { + INVARIANT_CHECK; + if (c.is_interesting()) + { + request_a_block(*m_torrent, c); + } + } + + // called when a peer is interested in us + void policy::interested(peer_connection& c) + { + INVARIANT_CHECK; + + assert(std::find_if(m_peers.begin(), m_peers.end() + , boost::bind(std::equal_to(), bind(&peer::connection, _1) + , &c)) != m_peers.end()); + + // if the peer is choked and we have upload slots left, + // then unchoke it. Another condition that has to be met + // is that the torrent doesn't keep track of the individual + // up/down ratio for each peer (ratio == 0) or (if it does + // keep track) this particular connection isn't a leecher. + // If the peer was choked because it was leeching, don't + // unchoke it again. + // The exception to this last condition is if we're a seed. + // In that case we don't care if people are leeching, they + // can't pay for their downloads anyway. + if (c.is_choked() + && m_num_unchoked < m_torrent->m_uploads_quota.given + && (m_torrent->ratio() == 0 + || c.share_diff() >= -free_upload_amount + || m_torrent->is_seed())) + { + c.send_unchoke(); + ++m_num_unchoked; + } + } + + // called when a peer is no longer interested in us + void policy::not_interested(peer_connection& c) + { + INVARIANT_CHECK; + + if (m_torrent->ratio() != 0.f) + { + assert(c.share_diff() < std::numeric_limits::max()); + size_type diff = c.share_diff(); + if (diff > 0 && c.is_seed()) + { + // the peer is a seed and has sent + // us more than we have sent it back. + // consider the download as free download + m_available_free_upload += diff; + c.add_free_upload(-diff); + } + } +/* + if (!c.is_choked()) + { + c.send_choke(); + --m_num_unchoked; + + if (m_torrent->is_seed()) seed_unchoke_one_peer(); + else unchoke_one_peer(); + } +*/ + } + + bool policy::unchoke_one_peer() + { + INVARIANT_CHECK; + + iterator p = find_unchoke_candidate(); + if (p == m_peers.end()) return false; + assert(p->connection); + assert(!p->connection->is_disconnecting()); + + assert(p->connection->is_choked()); + p->connection->send_unchoke(); + p->last_optimistically_unchoked = time_now(); + ++m_num_unchoked; + return true; + } + + void policy::choke_one_peer() + { + INVARIANT_CHECK; + + iterator p = find_choke_candidate(); + if (p == m_peers.end()) return; + assert(p->connection); + assert(!p->connection->is_disconnecting()); + assert(!p->connection->is_choked()); + p->connection->send_choke(); + --m_num_unchoked; + } + + bool policy::connect_one_peer() + { + INVARIANT_CHECK; + + assert(m_torrent->want_more_peers()); + + iterator p = find_connect_candidate(); + if (p == m_peers.end()) return false; + + assert(!p->banned); + assert(!p->connection); + assert(p->type == peer::connectable); + + try + { + p->connected = m_last_optimistic_disconnect = time_now(); + p->connection = m_torrent->connect_to_peer(&*p); + if (p->connection == 0) return false; + p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); + p->prev_amount_download = 0; + p->prev_amount_upload = 0; + return true; + } + catch (std::exception& e) + { +#if defined(TORRENT_VERBOSE_LOGGING) + (*m_torrent->session().m_logger) << "*** CONNECTION FAILED '" + << e.what() << "'\n"; +#endif + ++p->failcount; + return false; + } + } + + bool policy::disconnect_one_peer() + { + iterator p = find_disconnect_candidate(); + if (p == m_peers.end()) + return false; +#if defined(TORRENT_VERBOSE_LOGGING) + (*p->connection->m_logger) << "*** CLOSING CONNECTION 'too many connections'\n"; +#endif + + p->connection->disconnect(); + return true; + } + + // this is called whenever a peer connection is closed + void policy::connection_closed(const peer_connection& c) try + { + INVARIANT_CHECK; + +// assert(c.is_disconnecting()); + bool unchoked = false; + + iterator i = std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_connection(c)); + + // if we couldn't find the connection in our list, just ignore it. + if (i == m_peers.end()) return; + assert(i->connection == &c); + i->connection = 0; + + i->connected = time_now(); + if (!c.is_choked() && !m_torrent->is_aborted()) + { + unchoked = true; + } + + if (c.failed()) + { + ++i->failcount; + i->connected = time_now(); + } + + // if the share ratio is 0 (infinite), the + // m_available_free_upload isn't used, + // because it isn't necessary. + if (m_torrent->ratio() != 0.f) + { + assert(c.associated_torrent().lock().get() == m_torrent); + assert(c.share_diff() < std::numeric_limits::max()); + m_available_free_upload += c.share_diff(); + } + i->prev_amount_download += c.statistics().total_payload_download(); + i->prev_amount_upload += c.statistics().total_payload_upload(); + + if (unchoked) + { + // if the peer that is diconnecting is unchoked + // then unchoke another peer in order to maintain + // the total number of unchoked peers + --m_num_unchoked; + if (m_torrent->is_seed()) seed_unchoke_one_peer(); + else unchoke_one_peer(); + } + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::string err = e.what(); +#endif + assert(false); + } + + void policy::peer_is_interesting(peer_connection& c) + { + INVARIANT_CHECK; + + c.send_interested(); + if (c.has_peer_choked()) return; + request_a_block(*m_torrent, c); + } + +#ifndef NDEBUG + bool policy::has_connection(const peer_connection* c) + { + INVARIANT_CHECK; + + assert(c); + assert(c->remote() == c->get_socket()->remote_endpoint()); + + return std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_connection(*c)) != m_peers.end(); + } + + void policy::check_invariant() const + { + if (m_torrent->is_aborted()) return; + int actual_unchoked = 0; + int connected_peers = 0; + + int total_connections = 0; + int nonempty_connections = 0; + + std::set
unique_test; + for (const_iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + if (!m_torrent->settings().allow_multiple_connections_per_ip) + assert(unique_test.find(i->ip.address()) == unique_test.end()); + unique_test.insert(i->ip.address()); + ++total_connections; + if (!i->connection) continue; + assert(i->connection->peer_info_struct() == 0 + || i->connection->peer_info_struct() == &*i); + ++nonempty_connections; + if (!i->connection->is_disconnecting()) + ++connected_peers; + if (!i->connection->is_choked()) ++actual_unchoked; + } +// assert(actual_unchoked <= m_torrent->m_uploads_quota.given); + assert(actual_unchoked == m_num_unchoked); + + int num_torrent_peers = 0; + for (torrent::const_peer_iterator i = m_torrent->begin(); + i != m_torrent->end(); ++i) + { + if (i->second->is_disconnecting()) continue; + // ignore web_peer_connections since they are not managed + // by the policy class + if (dynamic_cast(i->second)) continue; + ++num_torrent_peers; + } + + // this invariant is a bit complicated. + // the usual case should be that connected_peers + // == num_torrent_peers. But when there's an incoming + // connection, it will first be added to the policy + // and then be added to the torrent. + // When there's an outgoing connection, it will first + // be added to the torrent and then to the policy. + // that's why the two second cases are in there. +/* + assert(connected_peers == num_torrent_peers + || (connected_peers == num_torrent_peers + 1 + && connected_peers > 0) + || (connected_peers + 1 == num_torrent_peers + && num_torrent_peers > 0)); +*/ + } +#endif + + policy::peer::peer(const tcp::endpoint& ip_, peer::connection_type t, int src) + : ip(ip_) + , type(t) +#ifndef TORRENT_DISABLE_ENCRYPTION + , pe_support(true) +#endif + , failcount(0) + , hashfails(0) + , seed(false) + , last_optimistically_unchoked(min_time()) + , connected(min_time()) + , trust_points(0) + , on_parole(false) + , prev_amount_upload(0) + , prev_amount_download(0) + , banned(false) + , source(src) + , connection(0) + { + assert(connected < time_now()); + } + + size_type policy::peer::total_download() const + { + if (connection != 0) + { + assert(prev_amount_download == 0); + return connection->statistics().total_payload_download(); + } + else + { + return prev_amount_download; + } + } + + size_type policy::peer::total_upload() const + { + if (connection != 0) + { + assert(prev_amount_upload == 0); + return connection->statistics().total_payload_upload(); + } + else + { + return prev_amount_upload; + } + } +} + diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp new file mode 100755 index 000000000..485c90d62 --- /dev/null +++ b/libtorrent/src/session.cpp @@ -0,0 +1,420 @@ +/* + +Copyright (c) 2006, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" + +using boost::shared_ptr; +using boost::weak_ptr; +using boost::bind; +using boost::mutex; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + + namespace aux + { + filesystem_init::filesystem_init() + { +#if BOOST_VERSION < 103400 + using namespace boost::filesystem; + if (path::default_name_check_writable()) + path::default_name_check(no_check); +#endif + } + } + + session::session( + fingerprint const& id + , std::pair listen_port_range + , char const* listen_interface) + : m_impl(new session_impl(listen_port_range, id, listen_interface)) + { + // turn off the filename checking in boost.filesystem + assert(listen_port_range.first > 0); + assert(listen_port_range.first < listen_port_range.second); +#ifndef NDEBUG + // this test was added after it came to my attention + // that devstudios managed c++ failed to generate + // correct code for boost.function + boost::function0 test = boost::ref(*m_impl); + assert(!test.empty()); +#endif + } + + session::session(fingerprint const& id) + : m_impl(new session_impl(std::make_pair(0, 0), id)) + { +#ifndef NDEBUG + boost::function0 test = boost::ref(*m_impl); + assert(!test.empty()); +#endif + } + + session::~session() + { + assert(m_impl); + // if there is at least one destruction-proxy + // abort the session and let the destructor + // of the proxy to syncronize + if (!m_impl.unique()) + m_impl->abort(); + } + + void session::add_extension(boost::function(torrent*)> ext) + { + m_impl->add_extension(ext); + } + + void session::set_ip_filter(ip_filter const& f) + { + m_impl->set_ip_filter(f); + } + + void session::set_port_filter(port_filter const& f) + { + m_impl->set_port_filter(f); + } + + void session::set_peer_id(peer_id const& id) + { + m_impl->set_peer_id(id); + } + + peer_id session::id() const + { + return m_impl->get_peer_id(); + } + + void session::set_key(int key) + { + m_impl->set_key(key); + } + + std::vector session::get_torrents() const + { + return m_impl->get_torrents(); + } + + torrent_handle session::find_torrent(sha1_hash const& info_hash) const + { + return m_impl->find_torrent_handle(info_hash); + } + + + // if the torrent already exists, this will throw duplicate_torrent + torrent_handle session::add_torrent( + torrent_info const& ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc) + { + return m_impl->add_torrent(ti, save_path, resume_data + , compact_mode, block_size, sc); + } + + torrent_handle session::add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , entry const& e + , bool compact_mode + , int block_size + , storage_constructor_type sc) + { + return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e + , compact_mode, block_size, sc); + } + + void session::remove_torrent(const torrent_handle& h) + { + m_impl->remove_torrent(h); + } + + bool session::listen_on( + std::pair const& port_range + , const char* net_interface) + { + return m_impl->listen_on(port_range, net_interface); + } + + unsigned short session::listen_port() const + { + return m_impl->listen_port(); + } + + session_status session::status() const + { + return m_impl->status(); + } + +#ifndef TORRENT_DISABLE_DHT + + void session::start_dht(entry const& startup_state) + { + m_impl->start_dht(startup_state); + } + + void session::stop_dht() + { + m_impl->stop_dht(); + } + + void session::set_dht_settings(dht_settings const& settings) + { + m_impl->set_dht_settings(settings); + } + + entry session::dht_state() const + { + return m_impl->dht_state(); + } + + void session::add_dht_node(std::pair const& node) + { + m_impl->add_dht_node(node); + } + + void session::add_dht_router(std::pair const& node) + { + m_impl->add_dht_router(node); + } + +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + void session::set_pe_settings(pe_settings const& settings) + { + m_impl->set_pe_settings(settings); + } + + pe_settings const& session::get_pe_settings() const + { + return m_impl->get_pe_settings(); + } +#endif + + bool session::is_listening() const + { + return m_impl->is_listening(); + } + + void session::set_settings(session_settings const& s) + { + m_impl->set_settings(s); + } + + session_settings const& session::settings() + { + return m_impl->settings(); + } + + void session::set_peer_proxy(proxy_settings const& s) + { + m_impl->set_peer_proxy(s); + } + + void session::set_web_seed_proxy(proxy_settings const& s) + { + m_impl->set_web_seed_proxy(s); + } + + void session::set_tracker_proxy(proxy_settings const& s) + { + m_impl->set_tracker_proxy(s); + } + + proxy_settings const& session::peer_proxy() const + { + return m_impl->peer_proxy(); + } + + proxy_settings const& session::web_seed_proxy() const + { + return m_impl->web_seed_proxy(); + } + + proxy_settings const& session::tracker_proxy() const + { + return m_impl->tracker_proxy(); + } + + +#ifndef TORRENT_DISABLE_DHT + void session::set_dht_proxy(proxy_settings const& s) + { + m_impl->set_dht_proxy(s); + } + + proxy_settings const& session::dht_proxy() const + { + return m_impl->dht_proxy(); + } +#endif + + void session::set_max_uploads(int limit) + { + m_impl->set_max_uploads(limit); + } + + void session::set_max_connections(int limit) + { + m_impl->set_max_connections(limit); + } + + void session::set_max_half_open_connections(int limit) + { + m_impl->set_max_half_open_connections(limit); + } + + int session::upload_rate_limit() const + { + return m_impl->upload_rate_limit(); + } + + int session::download_rate_limit() const + { + return m_impl->download_rate_limit(); + } + + void session::set_upload_rate_limit(int bytes_per_second) + { + m_impl->set_upload_rate_limit(bytes_per_second); + } + + void session::set_download_rate_limit(int bytes_per_second) + { + m_impl->set_download_rate_limit(bytes_per_second); + } + + int session::num_uploads() const + { + return m_impl->num_uploads(); + } + + int session::num_connections() const + { + return m_impl->num_connections(); + } + + std::auto_ptr session::pop_alert() + { + return m_impl->pop_alert(); + } + + void session::set_severity_level(alert::severity_t s) + { + m_impl->set_severity_level(s); + } + + void session::start_lsd() + { + m_impl->start_lsd(); + } + + void session::start_natpmp() + { + m_impl->start_natpmp(); + } + + void session::start_upnp() + { + m_impl->start_upnp(); + } + + void session::stop_lsd() + { + m_impl->stop_lsd(); + } + + void session::stop_natpmp() + { + m_impl->stop_natpmp(); + } + + void session::stop_upnp() + { + m_impl->stop_upnp(); + } + + connection_queue& session::get_connection_queue() + { + return m_impl->m_half_open; + } +} + diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp new file mode 100755 index 000000000..f07f48d62 --- /dev/null +++ b/libtorrent/src/session_impl.cpp @@ -0,0 +1,2180 @@ +/* + +Copyright (c) 2006, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" + +using boost::shared_ptr; +using boost::weak_ptr; +using boost::bind; +using boost::mutex; +using libtorrent::aux::session_impl; + +namespace libtorrent { + +namespace fs = boost::filesystem; + +namespace detail +{ + + std::string generate_auth_string(std::string const& user + , std::string const& passwd) + { + if (user.empty()) return std::string(); + return user + ":" + passwd; + } + + + } namespace aux { + // This is the checker thread + // it is looping in an infinite loop + // until the session is aborted. It will + // normally just block in a wait() call, + // waiting for a signal from session that + // there's a new torrent to check. + + void checker_impl::operator()() + { + eh_initializer(); + // if we're currently performing a full file check, + // this is the torrent being processed + boost::shared_ptr processing; + boost::shared_ptr t; + for (;;) + { + // temporary torrent used while checking fastresume data + try + { + t.reset(); + { + boost::mutex::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + // if the job queue is empty and + // we shouldn't abort + // wait for a signal + while (m_torrents.empty() && !m_abort && !processing) + m_cond.wait(l); + + if (m_abort) + { + // no lock is needed here, because the main thread + // has already been shut down by now + processing.reset(); + t.reset(); + std::for_each(m_torrents.begin(), m_torrents.end() + , boost::bind(&torrent::abort + , boost::bind(&shared_ptr::get + , boost::bind(&piece_checker_data::torrent_ptr, _1)))); + m_torrents.clear(); + std::for_each(m_processing.begin(), m_processing.end() + , boost::bind(&torrent::abort + , boost::bind(&shared_ptr::get + , boost::bind(&piece_checker_data::torrent_ptr, _1)))); + m_processing.clear(); + return; + } + + if (!m_torrents.empty()) + { + t = m_torrents.front(); + if (t->abort) + { + // make sure the locking order is + // consistent to avoid dead locks + // we need to lock the session because closing + // torrents assume to have access to it + l.unlock(); + session_impl::mutex_t::scoped_lock l2(m_ses.m_mutex); + l.lock(); + + t->torrent_ptr->abort(); + m_torrents.pop_front(); + continue; + } + } + } + + if (t) + { + std::string error_msg; + t->parse_resume_data(t->resume_data, t->torrent_ptr->torrent_file() + , error_msg); + + if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.m_alerts.post_alert(fastresume_rejected_alert( + t->torrent_ptr->get_handle() + , error_msg)); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "fastresume data for " + << t->torrent_ptr->torrent_file().name() << " rejected: " + << error_msg << "\n"; +#endif + } + + // lock the session to add the new torrent + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); + // clear the resume data now that it has been used + // (the fast resume data is now parsed and stored in t) + t->resume_data = entry(); + bool up_to_date = t->torrent_ptr->check_fastresume(*t); + + if (up_to_date) + { + INVARIANT_CHECK; + + assert(m_torrents.front() == t); + + t->torrent_ptr->files_checked(t->unfinished_pieces); + m_torrents.pop_front(); + + // we cannot add the torrent if the session is aborted. + if (!m_ses.is_aborted()) + { + m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr)); + if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(torrent_finished_alert( + t->torrent_ptr->get_handle() + , "torrent is complete")); + } + + peer_id id; + std::fill(id.begin(), id.end(), 0); + for (std::vector::const_iterator i = t->peers.begin(); + i != t->peers.end(); ++i) + { + t->torrent_ptr->get_policy().peer_from_tracker(*i, id + , peer_info::resume_data, 0); + } + } + else + { + t->torrent_ptr->abort(); + } + t.reset(); + continue; + } + + l.unlock(); + + // move the torrent from + // m_torrents to m_processing + assert(m_torrents.front() == t); + + m_torrents.pop_front(); + m_processing.push_back(t); + if (!processing) + { + processing = t; + processing->processing = true; + t.reset(); + } + } + } + catch (const std::exception& e) + { + // This will happen if the storage fails to initialize + // for example if one of the files has an invalid filename. + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); + + if (m_ses.m_alerts.should_post(alert::fatal)) + { + m_ses.m_alerts.post_alert( + file_error_alert( + t->torrent_ptr->get_handle() + , e.what())); + } + t->torrent_ptr->abort(); + + assert(!m_torrents.empty()); + m_torrents.pop_front(); + } + catch(...) + { +#ifndef NDEBUG + std::cerr << "error while checking resume data\n"; +#endif + mutex::scoped_lock l(m_mutex); + assert(!m_torrents.empty()); + m_torrents.pop_front(); + assert(false); + } + + if (!processing) continue; + + try + { + assert(processing); + + float finished = false; + float progress = 0.f; + boost::tie(finished, progress) = processing->torrent_ptr->check_files(); + + { + mutex::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + processing->progress = progress; + if (processing->abort) + { + assert(!m_processing.empty()); + assert(m_processing.front() == processing); + + processing->torrent_ptr->abort(); + + processing.reset(); + m_processing.pop_front(); + if (!m_processing.empty()) + { + processing = m_processing.front(); + processing->processing = true; + } + continue; + } + } + if (finished) + { + // lock the session to add the new torrent + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); + + INVARIANT_CHECK; + + assert(!m_processing.empty()); + assert(m_processing.front() == processing); + + // TODO: factor out the adding of torrents to the session + // and to the checker thread to avoid duplicating the + // check for abortion. + if (!m_ses.is_aborted()) + { + processing->torrent_ptr->files_checked(processing->unfinished_pieces); + m_ses.m_torrents.insert(std::make_pair( + processing->info_hash, processing->torrent_ptr)); + if (processing->torrent_ptr->is_seed() + && m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(torrent_finished_alert( + processing->torrent_ptr->get_handle() + , "torrent is complete")); + } + + peer_id id; + std::fill(id.begin(), id.end(), 0); + for (std::vector::const_iterator i = processing->peers.begin(); + i != processing->peers.end(); ++i) + { + processing->torrent_ptr->get_policy().peer_from_tracker(*i, id + , peer_info::resume_data, 0); + } + } + else + { + processing->torrent_ptr->abort(); + } + processing.reset(); + m_processing.pop_front(); + if (!m_processing.empty()) + { + processing = m_processing.front(); + processing->processing = true; + } + } + } + catch(std::exception const& e) + { + // This will happen if the storage fails to initialize + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); + + if (m_ses.m_alerts.should_post(alert::fatal)) + { + m_ses.m_alerts.post_alert( + file_error_alert( + processing->torrent_ptr->get_handle() + , e.what())); + } + assert(!m_processing.empty()); + + processing->torrent_ptr->abort(); + + processing.reset(); + m_processing.pop_front(); + if (!m_processing.empty()) + { + processing = m_processing.front(); + processing->processing = true; + } + } + catch(...) + { +#ifndef NDEBUG + std::cerr << "error while checking files\n"; +#endif + mutex::scoped_lock l(m_mutex); + assert(!m_processing.empty()); + + processing.reset(); + m_processing.pop_front(); + if (!m_processing.empty()) + { + processing = m_processing.front(); + processing->processing = true; + } + + assert(false); + } + } + } + + aux::piece_checker_data* checker_impl::find_torrent(sha1_hash const& info_hash) + { + INVARIANT_CHECK; + for (std::deque >::iterator i + = m_torrents.begin(); i != m_torrents.end(); ++i) + { + if ((*i)->info_hash == info_hash) return i->get(); + } + for (std::deque >::iterator i + = m_processing.begin(); i != m_processing.end(); ++i) + { + if ((*i)->info_hash == info_hash) return i->get(); + } + + return 0; + } + + void checker_impl::remove_torrent(sha1_hash const& info_hash) + { + INVARIANT_CHECK; + for (std::deque >::iterator i + = m_torrents.begin(); i != m_torrents.end(); ++i) + { + if ((*i)->info_hash == info_hash) + { + assert((*i)->processing == false); + m_torrents.erase(i); + return; + } + } + for (std::deque >::iterator i + = m_processing.begin(); i != m_processing.end(); ++i) + { + if ((*i)->info_hash == info_hash) + { + assert((*i)->processing == false); + m_processing.erase(i); + return; + } + } + + assert(false); + } + +#ifndef NDEBUG + void checker_impl::check_invariant() const + { + for (std::deque >::const_iterator i + = m_torrents.begin(); i != m_torrents.end(); ++i) + { + assert(*i); + assert((*i)->torrent_ptr); + } + for (std::deque >::const_iterator i + = m_processing.begin(); i != m_processing.end(); ++i) + { + assert(*i); + assert((*i)->torrent_ptr); + } + } +#endif + + struct seed_random_generator + { + seed_random_generator() + { + std::srand(total_microseconds(time_now() - min_time())); + } + }; + + session_impl::session_impl( + std::pair listen_port_range + , fingerprint const& cl_fprint + , char const* listen_interface) + : m_strand(m_io_service) + , m_files(40) + , m_half_open(m_io_service) + , m_download_channel(m_io_service, peer_connection::download_channel) + , m_upload_channel(m_io_service, peer_connection::upload_channel) + , m_tracker_manager(m_settings, m_tracker_proxy) + , m_listen_port_range(listen_port_range) + , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) + , m_external_listen_port(0) + , m_abort(false) + , m_max_uploads(-1) + , m_max_connections(-1) + , m_incoming_connection(false) + , m_last_tick(time_now()) +#ifndef TORRENT_DISABLE_DHT + , m_dht_same_port(true) + , m_external_udp_port(0) +#endif + , m_timer(m_io_service) + , m_next_connect_torrent(0) + , m_checker_impl(*this) + { + m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; + m_bandwidth_manager[peer_connection::upload_channel] = &m_upload_channel; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + m_logger = create_log("main_session", listen_port(), false); + (*m_logger) << time_now_string() << "\n"; +#endif + +#ifdef TORRENT_STATS + m_stats_logger.open("session_stats.log"); + m_stats_logger << + "1. second\n" + "2. upload rate\n" + "3. download rate\n" + "4. downloading torrents\n" + "5. seeding torrents\n" + "6. peers\n" + "7. connecting peers\n" + "\n"; + m_second_counter = 0; +#endif + + // ---- generate a peer id ---- + static seed_random_generator seeder; + + m_key = rand() + (rand() << 15) + (rand() << 30); + std::string print = cl_fprint.to_string(); + assert(print.length() <= 20); + + // the client's fingerprint + std::copy( + print.begin() + , print.begin() + print.length() + , m_peer_id.begin()); + + // http-accepted characters: + static char const printable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz-_.!~*'()"; + + // the random number + for (unsigned char* i = m_peer_id.begin() + print.length(); + i != m_peer_id.end(); ++i) + { + *i = printable[rand() % (sizeof(printable)-1)]; + } + + m_timer.expires_from_now(seconds(1)); + m_timer.async_wait(m_strand.wrap( + bind(&session_impl::second_tick, this, _1))); + + m_thread.reset(new boost::thread(boost::ref(*this))); + m_checker_thread.reset(new boost::thread(boost::ref(m_checker_impl))); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void session_impl::add_extension( + boost::function(torrent*)> ext) + { + m_extensions.push_back(ext); + } +#endif + +#ifndef TORRENT_DISABLE_DHT + void session_impl::add_dht_node(udp::endpoint n) + { + if (m_dht) m_dht->add_node(n); + } +#endif + + void session_impl::abort() + { + mutex_t::scoped_lock l(m_mutex); + assert(!m_abort); + // abort the main thread + m_abort = true; + m_io_service.stop(); + l.unlock(); + + mutex::scoped_lock l2(m_checker_impl.m_mutex); + // abort the checker thread + m_checker_impl.m_abort = true; + } + + void session_impl::set_port_filter(port_filter const& f) + { + mutex_t::scoped_lock l(m_mutex); + m_port_filter = f; + } + + void session_impl::set_ip_filter(ip_filter const& f) + { + mutex_t::scoped_lock l(m_mutex); + m_ip_filter = f; + + // Close connections whose endpoint is filtered + // by the new ip-filter + for (session_impl::connection_map::iterator i + = m_connections.begin(); i != m_connections.end();) + { + tcp::endpoint sender; + try { sender = i->first->remote_endpoint(); } + catch (std::exception&) { sender = i->second->remote(); } + if (m_ip_filter.access(sender.address()) & ip_filter::blocked) + { +#if defined(TORRENT_VERBOSE_LOGGING) + (*i->second->m_logger) << "*** CONNECTION FILTERED\n"; +#endif + if (m_alerts.should_post(alert::info)) + { + m_alerts.post_alert(peer_blocked_alert(sender.address() + , "peer connection closed by IP filter")); + } + + session_impl::connection_map::iterator j = i; + ++i; + j->second->disconnect(); + } + else ++i; + } + } + + void session_impl::set_settings(session_settings const& s) + { + mutex_t::scoped_lock l(m_mutex); + assert(s.connection_speed > 0); + assert(s.file_pool_size > 0); + + // less than 5 seconds unchoke interval is insane + assert(s.unchoke_interval >= 5); + m_settings = s; + m_files.resize(m_settings.file_pool_size); + // replace all occurances of '\n' with ' '. + std::string::iterator i = m_settings.user_agent.begin(); + while ((i = std::find(i, m_settings.user_agent.end(), '\n')) + != m_settings.user_agent.end()) + *i = ' '; + } + + void session_impl::open_listen_port() + { + try + { + // create listener socket + m_listen_socket = boost::shared_ptr(new socket_acceptor(m_io_service)); + + for(;;) + { + try + { + m_listen_socket->open(m_listen_interface.protocol()); + m_listen_socket->bind(m_listen_interface); + m_listen_socket->listen(); + m_external_listen_port = m_listen_interface.port(); + break; + } + catch (asio::system_error& e) + { + // TODO: make sure this is correct + if (e.code() == asio::error::host_not_found) + { + if (m_alerts.should_post(alert::fatal)) + { + std::string msg = "cannot listen on the given interface '" + + m_listen_interface.address().to_string() + "'"; + m_alerts.post_alert(listen_failed_alert(msg)); + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::string msg = "cannot listen on the given interface '" + + m_listen_interface.address().to_string() + "'"; + (*m_logger) << msg << "\n"; +#endif + assert(m_listen_socket.unique()); + m_listen_socket.reset(); + break; + } + m_listen_socket->close(); + m_listen_interface.port(m_listen_interface.port() + 1); + if (m_listen_interface.port() > m_listen_port_range.second) + { + std::stringstream msg; + msg << "none of the ports in the range [" + << m_listen_port_range.first + << ", " << m_listen_port_range.second + << "] could be opened for listening"; + m_alerts.post_alert(listen_failed_alert(msg.str())); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << msg.str() << "\n"; +#endif + m_listen_socket.reset(); + break; + } + } + } + } + catch (asio::system_error& e) + { + if (m_alerts.should_post(alert::fatal)) + { + m_alerts.post_alert(listen_failed_alert( + std::string("failed to open listen port: ") + e.what())); + } + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (m_listen_socket) + { + (*m_logger) << "listening on port: " << m_listen_interface.port() + << " external port: " << m_external_listen_port << "\n"; + } +#endif + if (m_listen_socket) async_accept(); + } + + void session_impl::async_accept() + { + shared_ptr c(new socket_type(m_io_service)); + c->instantiate(); + m_listen_socket->async_accept(c->get() + , bind(&session_impl::on_incoming_connection, this, c + , weak_ptr(m_listen_socket), _1)); + } + + void session_impl::on_incoming_connection(shared_ptr const& s + , weak_ptr const& listen_socket, asio::error_code const& e) try + { + if (listen_socket.expired()) + return; + + if (e == asio::error::operation_aborted) + return; + + mutex_t::scoped_lock l(m_mutex); + assert(listen_socket.lock() == m_listen_socket); + + if (m_abort) return; + + async_accept(); + if (e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::string msg = "error accepting connection on '" + + m_listen_interface.address().to_string() + "'"; + (*m_logger) << msg << "\n"; +#endif + assert(m_listen_socket.unique()); + return; + } + + // we got a connection request! + m_incoming_connection = true; + tcp::endpoint endp = s->remote_endpoint(); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << endp << " <== INCOMING CONNECTION\n"; +#endif + if (m_ip_filter.access(endp.address()) & ip_filter::blocked) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << "filtered blocked ip\n"; +#endif + if (m_alerts.should_post(alert::info)) + { + m_alerts.post_alert(peer_blocked_alert(endp.address() + , "incoming connection blocked by IP filter")); + } + return; + } + + boost::intrusive_ptr c( + new bt_peer_connection(*this, s, 0)); +#ifndef NDEBUG + c->m_in_constructor = false; +#endif + + m_connections.insert(std::make_pair(s, c)); + } + catch (std::exception& exc) + { +#ifndef NDEBUG + std::string err = exc.what(); +#endif + }; + + void session_impl::connection_failed(boost::shared_ptr const& s + , tcp::endpoint const& a, char const* message) +#ifndef NDEBUG + try +#endif + { + mutex_t::scoped_lock l(m_mutex); + + connection_map::iterator p = m_connections.find(s); + + // the connection may have been disconnected in the receive or send phase + if (p == m_connections.end()) return; + if (m_alerts.should_post(alert::debug)) + { + m_alerts.post_alert( + peer_error_alert( + a + , p->second->pid() + , message)); + } + +#if defined(TORRENT_VERBOSE_LOGGING) + (*p->second->m_logger) << "*** CONNECTION FAILED " << message << "\n"; +#endif + p->second->set_failed(); + p->second->disconnect(); + } +#ifndef NDEBUG + catch (...) + { + assert(false); + }; +#endif + + void session_impl::close_connection(boost::intrusive_ptr const& p) + { + mutex_t::scoped_lock l(m_mutex); + + assert(p->is_disconnecting()); + connection_map::iterator i = m_connections.find(p->get_socket()); + if (i != m_connections.end()) + m_connections.erase(i); + } + + void session_impl::set_peer_id(peer_id const& id) + { + mutex_t::scoped_lock l(m_mutex); + m_peer_id = id; + } + + void session_impl::set_key(int key) + { + mutex_t::scoped_lock l(m_mutex); + m_key = key; + } + + void session_impl::second_tick(asio::error_code const& e) try + { + session_impl::mutex_t::scoped_lock l(m_mutex); + + if (e) + { +#if defined(TORRENT_LOGGING) + (*m_logger) << "*** SECOND TIMER FAILED " << e.message() << "\n"; +#endif + m_abort = true; + m_io_service.stop(); + return; + } + + if (m_abort) return; + float tick_interval = total_microseconds(time_now() - m_last_tick) / 1000000.f; + m_last_tick = time_now(); + + m_timer.expires_from_now(seconds(1)); + m_timer.async_wait(m_strand.wrap( + bind(&session_impl::second_tick, this, _1))); + +#ifdef TORRENT_STATS + ++m_second_counter; + int downloading_torrents = 0; + int seeding_torrents = 0; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + if (i->second->is_seed()) + ++seeding_torrents; + else + ++downloading_torrents; + } + int num_connections = 0; + int num_half_open = 0; + for (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + if (i->second->is_connecting()) + ++num_half_open; + else + ++num_connections; + } + + m_stats_logger + << m_second_counter << "\t" + << m_stat.upload_rate() << "\t" + << m_stat.download_rate() << "\t" + << downloading_torrents << "\t" + << seeding_torrents << "\t" + << num_connections << "\t" + << num_half_open << "\t" + << std::endl; +#endif + + + // let torrents connect to peers if they want to + // if there are any torrents and any free slots + + // this loop will "hand out" max(connection_speed + // , half_open.free_slots()) to the torrents, in a + // round robin fashion, so that every torrent is + // equallt likely to connect to a peer + + if (!m_torrents.empty() && m_half_open.free_slots()) + { + // this is the maximum number of connections we will + // attempt this tick + int max_connections = m_settings.connection_speed; + + torrent_map::iterator i = m_torrents.begin(); + if (m_next_connect_torrent < int(m_torrents.size())) + std::advance(i, m_next_connect_torrent); + else + m_next_connect_torrent = 0; + int steps_since_last_connect = 0; + int num_torrents = int(m_torrents.size()); + for (;;) + { + torrent& t = *i->second; + if (t.want_more_peers()) + if (t.try_connect_peer()) + { + --max_connections; + steps_since_last_connect = 0; + } + ++m_next_connect_torrent; + ++steps_since_last_connect; + ++i; + if (i == m_torrents.end()) + { + assert(m_next_connect_torrent == num_torrents); + i = m_torrents.begin(); + m_next_connect_torrent = 0; + } + // if we have gone one whole loop without + // handing out a single connection, break + if (steps_since_last_connect > num_torrents) break; + // if there are no more free connection slots, abort + if (m_half_open.free_slots() == 0) break; + // if we should not make any more connections + // attempts this tick, abort + if (max_connections == 0) break; + } + } + + // do the second_tick() on each connection + // this will update their statistics (download and upload speeds) + // also purge sockets that have timed out + // and keep sockets open by keeping them alive. + for (connection_map::iterator i = m_connections.begin(); + i != m_connections.end();) + { + // we need to do like this because j->second->disconnect() will + // erase the connection from the map we're iterating + connection_map::iterator j = i; + ++i; + // if this socket has timed out + // close it. + peer_connection& c = *j->second; + if (c.has_timed_out()) + { + if (m_alerts.should_post(alert::debug)) + { + m_alerts.post_alert( + peer_error_alert( + c.remote() + , c.pid() + , "connection timed out")); + } +#if defined(TORRENT_VERBOSE_LOGGING) + (*c.m_logger) << "*** CONNECTION TIMED OUT\n"; +#endif + + c.set_failed(); + c.disconnect(); + continue; + } + + try + { + c.keep_alive(); + } + catch (std::exception& exc) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*c.m_logger) << "**ERROR**: " << exc.what() << "\n"; +#endif + c.set_failed(); + c.disconnect(); + } + } + + // check each torrent for tracker updates + // TODO: do this in a timer-event in each torrent instead + for (torrent_map::iterator i = m_torrents.begin(); + i != m_torrents.end();) + { + torrent& t = *i->second; + assert(!t.is_aborted()); + if (t.should_request()) + { + tracker_request req = t.generate_tracker_request(); + req.listen_port = m_external_listen_port; + req.key = m_key; + m_tracker_manager.queue_request(m_strand, m_half_open, req + , t.tracker_login(), m_listen_interface.address(), i->second); + + if (m_alerts.should_post(alert::info)) + { + m_alerts.post_alert( + tracker_announce_alert( + t.get_handle(), "tracker announce")); + } + } + + // second_tick() will set the used upload quota + t.second_tick(m_stat, tick_interval); + ++i; + } + + m_stat.second_tick(tick_interval); + // distribute the maximum upload rate among the torrents + + assert(m_max_uploads >= -1); + assert(m_max_connections >= -1); + + allocate_resources(m_max_uploads == -1 + ? std::numeric_limits::max() + : m_max_uploads + , m_torrents + , &torrent::m_uploads_quota); + + allocate_resources(m_max_connections == -1 + ? std::numeric_limits::max() + : m_max_connections + , m_torrents + , &torrent::m_connections_quota); + + for (std::map >::iterator i + = m_torrents.begin(); i != m_torrents.end(); ++i) + { +#ifndef NDEBUG + i->second->check_invariant(); +#endif + i->second->distribute_resources(tick_interval); + } + } + catch (std::exception& exc) + { +#ifndef NDEBUG + std::cerr << exc.what() << std::endl; + assert(false); +#endif + }; // msvc 7.1 seems to require this +/* + void session_impl::connection_completed( + boost::intrusive_ptr const& p) try + { + mutex_t::scoped_lock l(m_mutex); + + connection_map::iterator i = m_half_open.find(p->get_socket()); + m_connections.insert(std::make_pair(p->get_socket(), p)); + assert(i != m_half_open.end()); + if (i != m_half_open.end()) m_half_open.erase(i); + + if (m_abort) return; + + process_connection_queue(); + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::cerr << e.what() << std::endl; + assert(false); +#endif + }; +*/ + void session_impl::operator()() + { + eh_initializer(); + + if (m_listen_port_range.first != 0 && m_listen_port_range.second != 0) + { + session_impl::mutex_t::scoped_lock l(m_mutex); + open_listen_port(); + if (m_natpmp.get()) + m_natpmp->set_mappings(m_listen_interface.port(), 0); + if (m_upnp.get()) + m_upnp->set_mappings(m_listen_interface.port(), 0); + } + + ptime timer = time_now(); + + do + { + try + { + m_io_service.run(); + assert(m_abort == true); + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::cerr << e.what() << "\n"; + std::string err = e.what(); +#endif + assert(false); + } + } + while (!m_abort); + + deadline_timer tracker_timer(m_io_service); + // this will remove the port mappings + if (m_natpmp.get()) + m_natpmp->close(); + if (m_upnp.get()) + m_upnp->close(); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " locking mutex\n"; +#endif + session_impl::mutex_t::scoped_lock l(m_mutex); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " aborting all tracker requests\n"; +#endif + m_tracker_manager.abort_all_requests(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " sending stopped to all torrent's trackers\n"; +#endif + for (std::map >::iterator i = + m_torrents.begin(); i != m_torrents.end(); ++i) + { + i->second->abort(); + // generate a tracker request in case the torrent is not paused + // (in which case it's not currently announced with the tracker) + // or if the torrent itself thinks we should request. Do not build + // a request in case the torrent doesn't have any trackers + if ((!i->second->is_paused() || i->second->should_request()) + && !i->second->trackers().empty()) + { + tracker_request req = i->second->generate_tracker_request(); + assert(m_external_listen_port > 0); + req.listen_port = m_external_listen_port; + req.key = m_key; + std::string login = i->second->tracker_login(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr tl(new tracker_logger(*this)); + m_tracker_loggers.push_back(tl); + m_tracker_manager.queue_request(m_strand, m_half_open, req, login + , m_listen_interface.address(), tl); +#else + m_tracker_manager.queue_request(m_strand, m_half_open, req, login + , m_listen_interface.address()); +#endif + } + } + + ptime start(time_now()); + l.unlock(); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " waiting for trackers to respond (" + << m_settings.stop_tracker_timeout << " seconds timeout)\n"; +#endif + + while (time_now() - start < seconds( + m_settings.stop_tracker_timeout) + && !m_tracker_manager.empty()) + { + tracker_timer.expires_from_now(milliseconds(100)); + tracker_timer.async_wait(m_strand.wrap( + bind(&io_service::stop, &m_io_service))); + + m_io_service.reset(); + m_io_service.run(); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " tracker shutdown complete, locking mutex\n"; +#endif + + l.lock(); + assert(m_abort); + m_abort = true; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " cleaning up connections\n"; +#endif + while (!m_connections.empty()) + m_connections.begin()->second->disconnect(); + +#ifndef NDEBUG + for (torrent_map::iterator i = m_torrents.begin(); + i != m_torrents.end(); ++i) + { + assert(i->second->num_peers() == 0); + } +#endif + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " cleaning up torrents\n"; +#endif + m_torrents.clear(); + + assert(m_torrents.empty()); + assert(m_connections.empty()); + } + + + // the return value from this function is valid only as long as the + // session is locked! + boost::weak_ptr session_impl::find_torrent(sha1_hash const& info_hash) + { + std::map >::iterator i + = m_torrents.find(info_hash); +#ifndef NDEBUG + for (std::map >::iterator j + = m_torrents.begin(); j != m_torrents.end(); ++j) + { + torrent* p = boost::get_pointer(j->second); + assert(p); + } +#endif + if (i != m_torrents.end()) return i->second; + return boost::weak_ptr(); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr session_impl::create_log(std::string const& name + , int instance, bool append) + { + // current options are file_logger, cout_logger and null_logger + return boost::shared_ptr(new logger(name + ".log", instance, append)); + } +#endif + + std::vector session_impl::get_torrents() + { + mutex_t::scoped_lock l(m_mutex); + mutex::scoped_lock l2(m_checker_impl.m_mutex); + std::vector ret; + for (std::deque >::iterator i + = m_checker_impl.m_torrents.begin() + , end(m_checker_impl.m_torrents.end()); i != end; ++i) + { + if ((*i)->abort) continue; + ret.push_back(torrent_handle(this, &m_checker_impl + , (*i)->info_hash)); + } + + for (std::deque >::iterator i + = m_checker_impl.m_processing.begin() + , end(m_checker_impl.m_processing.end()); i != end; ++i) + { + if ((*i)->abort) continue; + ret.push_back(torrent_handle(this, &m_checker_impl + , (*i)->info_hash)); + } + + for (session_impl::torrent_map::iterator i + = m_torrents.begin(), end(m_torrents.end()); + i != end; ++i) + { + if (i->second->is_aborted()) continue; + ret.push_back(torrent_handle(this, &m_checker_impl + , i->first)); + } + return ret; + } + + torrent_handle session_impl::find_torrent_handle(sha1_hash const& info_hash) + { + return torrent_handle(this, &m_checker_impl, info_hash); + } + + torrent_handle session_impl::add_torrent( + torrent_info const& ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc) + { + // if you get this assert, you haven't managed to + // open a listen port. call listen_on() first. + assert(m_external_listen_port > 0); + + // make sure the block_size is an even power of 2 +#ifndef NDEBUG + for (int i = 0; i < 32; ++i) + { + if (block_size & (1 << i)) + { + assert((block_size & ~(1 << i)) == 0); + break; + } + } +#endif + + assert(!save_path.empty()); + + if (ti.begin_files() == ti.end_files()) + throw std::runtime_error("no files in torrent"); + + // lock the session and the checker thread (the order is important!) + mutex_t::scoped_lock l(m_mutex); + mutex::scoped_lock l2(m_checker_impl.m_mutex); + + if (is_aborted()) + throw std::runtime_error("session is closing"); + + // is the torrent already active? + if (!find_torrent(ti.info_hash()).expired()) + throw duplicate_torrent(); + + // is the torrent currently being checked? + if (m_checker_impl.find_torrent(ti.info_hash())) + throw duplicate_torrent(); + + // create the torrent and the data associated with + // the checker thread and store it before starting + // the thread + boost::shared_ptr torrent_ptr( + new torrent(*this, m_checker_impl, ti, save_path + , m_listen_interface, compact_mode, block_size + , settings(), sc)); + torrent_ptr->start(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr tp((*i)(torrent_ptr.get())); + if (tp) torrent_ptr->add_extension(tp); + } +#endif + + boost::shared_ptr d( + new aux::piece_checker_data); + d->torrent_ptr = torrent_ptr; + d->save_path = save_path; + d->info_hash = ti.info_hash(); + d->resume_data = resume_data; + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + { + torrent_info::nodes_t const& nodes = ti.nodes(); + std::for_each(nodes.begin(), nodes.end(), bind( + (void(dht::dht_tracker::*)(std::pair const&)) + &dht::dht_tracker::add_node + , boost::ref(m_dht), _1)); + } +#endif + + // add the torrent to the queue to be checked + m_checker_impl.m_torrents.push_back(d); + // and notify the thread that it got another + // job in its queue + m_checker_impl.m_cond.notify_one(); + + return torrent_handle(this, &m_checker_impl, ti.info_hash()); + } + + torrent_handle session_impl::add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , entry const& + , bool compact_mode + , int block_size + , storage_constructor_type sc) + { + // make sure the block_size is an even power of 2 +#ifndef NDEBUG + for (int i = 0; i < 32; ++i) + { + if (block_size & (1 << i)) + { + assert((block_size & ~(1 << i)) == 0); + break; + } + } +#endif + + // TODO: support resume data in this case + assert(!save_path.empty()); + { + // lock the checker_thread + mutex::scoped_lock l(m_checker_impl.m_mutex); + + // is the torrent currently being checked? + if (m_checker_impl.find_torrent(info_hash)) + throw duplicate_torrent(); + } + + // lock the session + session_impl::mutex_t::scoped_lock l(m_mutex); + + // is the torrent already active? + if (!find_torrent(info_hash).expired()) + throw duplicate_torrent(); + + // you cannot add new torrents to a session that is closing down + assert(!is_aborted()); + + // create the torrent and the data associated with + // the checker thread and store it before starting + // the thread + boost::shared_ptr torrent_ptr( + new torrent(*this, m_checker_impl, tracker_url, info_hash, name + , save_path, m_listen_interface, compact_mode, block_size + , settings(), sc)); + torrent_ptr->start(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr tp((*i)(torrent_ptr.get())); + if (tp) torrent_ptr->add_extension(tp); + } +#endif + + m_torrents.insert( + std::make_pair(info_hash, torrent_ptr)).first; + + return torrent_handle(this, &m_checker_impl, info_hash); + } + + void session_impl::remove_torrent(const torrent_handle& h) + { + if (h.m_ses != this) return; + assert(h.m_chk == &m_checker_impl || h.m_chk == 0); + assert(h.m_ses != 0); + + mutex_t::scoped_lock l(m_mutex); + session_impl::torrent_map::iterator i = + m_torrents.find(h.m_info_hash); + if (i != m_torrents.end()) + { + torrent& t = *i->second; + t.abort(); + + if ((!t.is_paused() || t.should_request()) + && !t.torrent_file().trackers().empty()) + { + tracker_request req = t.generate_tracker_request(); + assert(req.event == tracker_request::stopped); + assert(m_external_listen_port > 0); + req.listen_port = m_external_listen_port; + req.key = m_key; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr tl(new tracker_logger(*this)); + m_tracker_loggers.push_back(tl); + m_tracker_manager.queue_request(m_strand, m_half_open, req + , t.tracker_login(), m_listen_interface.address(), tl); +#else + m_tracker_manager.queue_request(m_strand, m_half_open, req + , t.tracker_login(), m_listen_interface.address()); +#endif + + if (m_alerts.should_post(alert::info)) + { + m_alerts.post_alert( + tracker_announce_alert( + t.get_handle(), "tracker announce, event=stopped")); + } + } +#ifndef NDEBUG + sha1_hash i_hash = t.torrent_file().info_hash(); +#endif + m_torrents.erase(i); + assert(m_torrents.find(i_hash) == m_torrents.end()); + return; + } + l.unlock(); + + if (h.m_chk) + { + mutex::scoped_lock l(m_checker_impl.m_mutex); + + aux::piece_checker_data* d = m_checker_impl.find_torrent(h.m_info_hash); + if (d != 0) + { + if (d->processing) d->abort = true; + else m_checker_impl.remove_torrent(h.m_info_hash); + return; + } + } + } + + bool session_impl::listen_on( + std::pair const& port_range + , const char* net_interface) + { + session_impl::mutex_t::scoped_lock l(m_mutex); + + tcp::endpoint new_interface; + if (net_interface && std::strlen(net_interface) > 0) + new_interface = tcp::endpoint(address::from_string(net_interface), port_range.first); + else + new_interface = tcp::endpoint(address(), port_range.first); + + m_listen_port_range = port_range; + + // if the interface is the same and the socket is open + // don't do anything + if (new_interface == m_listen_interface + && m_listen_socket) return true; + + if (m_listen_socket) + m_listen_socket.reset(); + + m_incoming_connection = false; + m_listen_interface = new_interface; + + open_listen_port(); + + bool new_listen_address = m_listen_interface.address() != new_interface.address(); + + if (new_listen_address) + { + if (m_natpmp.get()) + m_natpmp->rebind(new_interface.address()); + if (m_upnp.get()) + m_upnp->rebind(new_interface.address()); + if (m_lsd.get()) + m_lsd->rebind(new_interface.address()); + } + + if (m_natpmp.get()) + m_natpmp->set_mappings(m_listen_interface.port(), 0); + if (m_upnp.get()) + m_upnp->set_mappings(m_listen_interface.port(), 0); + +#ifndef TORRENT_DISABLE_DHT + if ((new_listen_address || m_dht_same_port) && m_dht) + { + if (m_dht_same_port) + m_dht_settings.service_port = new_interface.port(); + // the listen interface changed, rebind the dht listen socket as well + m_dht->rebind(new_interface.address() + , m_dht_settings.service_port); + if (m_natpmp.get()) + m_natpmp->set_mappings(0, m_dht_settings.service_port); + if (m_upnp.get()) + m_upnp->set_mappings(0, m_dht_settings.service_port); + } +#endif + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + m_logger = create_log("main_session", listen_port(), false); + (*m_logger) << time_now_string() << "\n"; +#endif + + return m_listen_socket; + } + + unsigned short session_impl::listen_port() const + { + mutex_t::scoped_lock l(m_mutex); + return m_external_listen_port; + } + + void session_impl::announce_lsd(sha1_hash const& ih) + { + mutex_t::scoped_lock l(m_mutex); + // use internal listen port for local peers + if (m_lsd.get()) + m_lsd->announce(ih, m_listen_interface.port()); + } + + void session_impl::on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih) + { + mutex_t::scoped_lock l(m_mutex); + + boost::shared_ptr t = find_torrent(ih).lock(); + if (!t) return; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() + << ": added peer from local discovery: " << peer << "\n"; +#endif + t->get_policy().peer_from_tracker(peer, peer_id(0), peer_info::lsd, 0); + } + + void session_impl::on_port_mapping(int tcp_port, int udp_port + , std::string const& errmsg) + { +#ifndef TORRENT_DISABLE_DHT + if (udp_port != 0) + { + m_external_udp_port = udp_port; + m_dht_settings.service_port = udp_port; + if (m_alerts.should_post(alert::info)) + { + std::stringstream msg; + msg << "successfully mapped UDP port " << udp_port; + m_alerts.post_alert(portmap_alert(msg.str())); + } + } +#endif + + if (tcp_port != 0) + { + m_external_listen_port = tcp_port; + if (m_alerts.should_post(alert::info)) + { + std::stringstream msg; + msg << "successfully mapped TCP port " << tcp_port; + m_alerts.post_alert(portmap_alert(msg.str())); + } + } + + if (!errmsg.empty()) + { + if (m_alerts.should_post(alert::warning)) + { + std::stringstream msg; + msg << "Error while mapping ports on NAT router: " << errmsg; + m_alerts.post_alert(portmap_error_alert(msg.str())); + } + } + } + + session_status session_impl::status() const + { + mutex_t::scoped_lock l(m_mutex); + session_status s; + s.has_incoming_connections = m_incoming_connection; + s.num_peers = (int)m_connections.size(); + + s.download_rate = m_stat.download_rate(); + s.upload_rate = m_stat.upload_rate(); + + s.payload_download_rate = m_stat.download_payload_rate(); + s.payload_upload_rate = m_stat.upload_payload_rate(); + + s.total_download = m_stat.total_protocol_download() + + m_stat.total_payload_download(); + + s.total_upload = m_stat.total_protocol_upload() + + m_stat.total_payload_upload(); + + s.total_payload_download = m_stat.total_payload_download(); + s.total_payload_upload = m_stat.total_payload_upload(); + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + { + m_dht->dht_status(s); + } + else + { + s.dht_nodes = 0; + s.dht_node_cache = 0; + s.dht_torrents = 0; + s.dht_global_nodes = 0; + } +#endif + + return s; + } + +#ifndef TORRENT_DISABLE_DHT + + void session_impl::start_dht(entry const& startup_state) + { + mutex_t::scoped_lock l(m_mutex); + if (m_dht) + { + m_dht->stop(); + m_dht = 0; + } + if (m_dht_settings.service_port == 0 + || m_dht_same_port) + { + m_dht_same_port = true; + // if you hit this assert you are trying to start the + // DHT with the same port as the tcp listen port + // (which is default) _before_ you have opened the + // tcp listen port (so there is no configured port to use) + // basically, make sure you call listen_on() before + // start_dht(). See documentation for listen_on() for + // more information. + assert(m_listen_interface.port() > 0); + m_dht_settings.service_port = m_listen_interface.port(); + } + m_external_udp_port = m_dht_settings.service_port; + if (m_natpmp.get()) + m_natpmp->set_mappings(0, m_dht_settings.service_port); + if (m_upnp.get()) + m_upnp->set_mappings(0, m_dht_settings.service_port); + m_dht = new dht::dht_tracker(m_io_service + , m_dht_settings, m_listen_interface.address() + , startup_state); + } + + void session_impl::stop_dht() + { + mutex_t::scoped_lock l(m_mutex); + if (!m_dht) return; + m_dht->stop(); + m_dht = 0; + } + + void session_impl::set_dht_settings(dht_settings const& settings) + { + mutex_t::scoped_lock l(m_mutex); + // only change the dht listen port in case the settings + // contains a vaiid port, and if it is different from + // the current setting + if (settings.service_port != 0) + m_dht_same_port = false; + else + m_dht_same_port = true; + if (!m_dht_same_port + && settings.service_port != m_dht_settings.service_port + && m_dht) + { + m_dht->rebind(m_listen_interface.address() + , settings.service_port); + if (m_natpmp.get()) + m_natpmp->set_mappings(0, m_dht_settings.service_port); + if (m_upnp.get()) + m_upnp->set_mappings(0, m_dht_settings.service_port); + m_external_udp_port = settings.service_port; + } + m_dht_settings = settings; + if (m_dht_same_port) + m_dht_settings.service_port = m_listen_interface.port(); + } + + entry session_impl::dht_state() const + { + assert(m_dht); + mutex_t::scoped_lock l(m_mutex); + return m_dht->state(); + } + + void session_impl::add_dht_node(std::pair const& node) + { + assert(m_dht); + mutex_t::scoped_lock l(m_mutex); + m_dht->add_node(node); + } + + void session_impl::add_dht_router(std::pair const& node) + { + assert(m_dht); + mutex_t::scoped_lock l(m_mutex); + m_dht->add_router_node(node); + } + +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + void session_impl::set_pe_settings(pe_settings const& settings) + { + mutex_t::scoped_lock l(m_mutex); + m_pe_settings = settings; + } +#endif + + bool session_impl::is_listening() const + { + mutex_t::scoped_lock l(m_mutex); + return m_listen_socket; + } + + session_impl::~session_impl() + { +#ifndef TORRENT_DISABLE_DHT + stop_dht(); +#endif + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << "\n\n *** shutting down session *** \n\n"; +#endif + // lock the main thread and abort it + mutex_t::scoped_lock l(m_mutex); + m_abort = true; + m_io_service.stop(); + l.unlock(); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " waiting for main thread\n"; +#endif + m_thread->join(); + + assert(m_torrents.empty()); + + // it's important that the main thread is closed completely before + // the checker thread is terminated. Because all the connections + // have to be closed and removed from the torrents before they + // can be destructed. (because the weak pointers in the + // peer_connections will be invalidated when the torrents are + // destructed and then the invariant will be broken). + + { + mutex::scoped_lock l(m_checker_impl.m_mutex); + // abort the checker thread + m_checker_impl.m_abort = true; + + // abort the currently checking torrent + if (!m_checker_impl.m_torrents.empty()) + { + m_checker_impl.m_torrents.front()->abort = true; + } + m_checker_impl.m_cond.notify_one(); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " waiting for checker thread\n"; +#endif + m_checker_thread->join(); + + assert(m_torrents.empty()); + assert(m_connections.empty()); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " shutdown complete!\n"; +#endif + } + + void session_impl::set_max_uploads(int limit) + { + assert(limit > 0 || limit == -1); + mutex_t::scoped_lock l(m_mutex); + m_max_uploads = limit; + } + + void session_impl::set_max_connections(int limit) + { + assert(limit > 0 || limit == -1); + mutex_t::scoped_lock l(m_mutex); + m_max_connections = limit; + } + + void session_impl::set_max_half_open_connections(int limit) + { + assert(limit > 0 || limit == -1); + mutex_t::scoped_lock l(m_mutex); + + m_half_open.limit(limit); + } + + void session_impl::set_download_rate_limit(int bytes_per_second) + { + assert(bytes_per_second > 0 || bytes_per_second == -1); + mutex_t::scoped_lock l(m_mutex); + if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; + m_bandwidth_manager[peer_connection::download_channel]->throttle(bytes_per_second); + } + + void session_impl::set_upload_rate_limit(int bytes_per_second) + { + assert(bytes_per_second > 0 || bytes_per_second == -1); + mutex_t::scoped_lock l(m_mutex); + if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; + m_bandwidth_manager[peer_connection::upload_channel]->throttle(bytes_per_second); + } + + int session_impl::num_uploads() const + { + int uploads = 0; + mutex_t::scoped_lock l(m_mutex); + for (torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; i++) + { + uploads += i->second->get_policy().num_uploads(); + } + return uploads; + } + + int session_impl::num_connections() const + { + mutex_t::scoped_lock l(m_mutex); + return m_connections.size(); + } + + std::auto_ptr session_impl::pop_alert() + { + mutex_t::scoped_lock l(m_mutex); + if (m_alerts.pending()) + return m_alerts.get(); + return std::auto_ptr(0); + } + + void session_impl::set_severity_level(alert::severity_t s) + { + mutex_t::scoped_lock l(m_mutex); + m_alerts.set_severity(s); + } + + int session_impl::upload_rate_limit() const + { + mutex_t::scoped_lock l(m_mutex); + return m_bandwidth_manager[peer_connection::upload_channel]->throttle(); + } + + int session_impl::download_rate_limit() const + { + mutex_t::scoped_lock l(m_mutex); + return m_bandwidth_manager[peer_connection::download_channel]->throttle(); + } + + void session_impl::start_lsd() + { + mutex_t::scoped_lock l(m_mutex); + m_lsd.reset(new lsd(m_io_service + , m_listen_interface.address() + , bind(&session_impl::on_lsd_peer, this, _1, _2))); + } + + void session_impl::start_natpmp() + { + mutex_t::scoped_lock l(m_mutex); + m_natpmp.reset(new natpmp(m_io_service + , m_listen_interface.address() + , bind(&session_impl::on_port_mapping + , this, _1, _2, _3))); + + m_natpmp->set_mappings(m_listen_interface.port(), +#ifndef TORRENT_DISABLE_DHT + m_dht ? m_dht_settings.service_port : +#endif + 0); + } + + void session_impl::start_upnp() + { + mutex_t::scoped_lock l(m_mutex); + m_upnp.reset(new upnp(m_io_service, m_half_open + , m_listen_interface.address() + , m_settings.user_agent + , bind(&session_impl::on_port_mapping + , this, _1, _2, _3))); + + m_upnp->set_mappings(m_listen_interface.port(), +#ifndef TORRENT_DISABLE_DHT + m_dht ? m_dht_settings.service_port : +#endif + 0); + } + + void session_impl::stop_lsd() + { + mutex_t::scoped_lock l(m_mutex); + m_lsd.reset(); + } + + void session_impl::stop_natpmp() + { + mutex_t::scoped_lock l(m_mutex); + if (m_natpmp.get()) + m_natpmp->close(); + m_natpmp.reset(); + } + + void session_impl::stop_upnp() + { + mutex_t::scoped_lock l(m_mutex); + if (m_upnp.get()) + m_upnp->close(); + m_upnp.reset(); + } + + +#ifndef NDEBUG + void session_impl::check_invariant(const char *place) + { + assert(place); + for (connection_map::iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + assert(i->second); + boost::shared_ptr t = i->second->associated_torrent().lock(); + + if (t) + { + assert(t->get_policy().has_connection(boost::get_pointer(i->second))); + } + } + } +#endif + + void piece_checker_data::parse_resume_data( + const entry& resume_data + , const torrent_info& info + , std::string& error) + { + // if we don't have any resume data, return + if (resume_data.type() == entry::undefined_t) return; + + entry rd = resume_data; + + try + { + if (rd["file-format"].string() != "libtorrent resume file") + { + error = "missing file format tag"; + return; + } + + if (rd["file-version"].integer() > 1) + { + error = "incompatible file version " + + boost::lexical_cast(rd["file-version"].integer()); + return; + } + + // verify info_hash + sha1_hash hash = rd["info-hash"].string(); + if (hash != info.info_hash()) + { + error = "mismatching info-hash: " + boost::lexical_cast(hash); + return; + } + + // the peers + + if (rd.find_key("peers")) + { + entry::list_type& peer_list = rd["peers"].list(); + + std::vector tmp_peers; + tmp_peers.reserve(peer_list.size()); + for (entry::list_type::iterator i = peer_list.begin(); + i != peer_list.end(); ++i) + { + tcp::endpoint a( + address::from_string((*i)["ip"].string()) + , (unsigned short)(*i)["port"].integer()); + tmp_peers.push_back(a); + } + + peers.swap(tmp_peers); + } + + // read piece map + const entry::list_type& slots = rd["slots"].list(); + if ((int)slots.size() > info.num_pieces()) + { + error = "file has more slots than torrent (slots: " + + boost::lexical_cast(slots.size()) + " size: " + + boost::lexical_cast(info.num_pieces()) + " )"; + return; + } + + std::vector tmp_pieces; + tmp_pieces.reserve(slots.size()); + for (entry::list_type::const_iterator i = slots.begin(); + i != slots.end(); ++i) + { + int index = (int)i->integer(); + if (index >= info.num_pieces() || index < -2) + { + error = "too high index number in slot map (index: " + + boost::lexical_cast(index) + " size: " + + boost::lexical_cast(info.num_pieces()) + ")"; + return; + } + tmp_pieces.push_back(index); + } + + // only bother to check the partial pieces if we have the same block size + // as in the fast resume data. If the blocksize has changed, then throw + // away all partial pieces. + std::vector tmp_unfinished; + int num_blocks_per_piece = (int)rd["blocks per piece"].integer(); + if (num_blocks_per_piece == info.piece_length() / torrent_ptr->block_size()) + { + // the unfinished pieces + + entry::list_type& unfinished = rd["unfinished"].list(); + int unfinished_size = int(unfinished.size()); + block_info.resize(num_blocks_per_piece * unfinished_size); + tmp_unfinished.reserve(unfinished_size); + int index = 0; + for (entry::list_type::iterator i = unfinished.begin(); + i != unfinished.end(); ++i, ++index) + { + piece_picker::downloading_piece p; + p.info = &block_info[index * num_blocks_per_piece]; + p.index = (int)(*i)["piece"].integer(); + if (p.index < 0 || p.index >= info.num_pieces()) + { + error = "invalid piece index in unfinished piece list (index: " + + boost::lexical_cast(p.index) + " size: " + + boost::lexical_cast(info.num_pieces()) + ")"; + return; + } + + const std::string& bitmask = (*i)["bitmask"].string(); + + const int num_bitmask_bytes = std::max(num_blocks_per_piece / 8, 1); + if ((int)bitmask.size() != num_bitmask_bytes) + { + error = "invalid size of bitmask (" + boost::lexical_cast(bitmask.size()) + ")"; + return; + } + for (int j = 0; j < num_bitmask_bytes; ++j) + { + unsigned char bits = bitmask[j]; + int num_bits = std::min(num_blocks_per_piece - j*8, 8); + for (int k = 0; k < num_bits; ++k) + { + const int bit = j * 8 + k; + if (bits & (1 << k)) + { + p.info[bit].state = piece_picker::block_info::state_finished; + ++p.finished; + } + } + } + + if (p.finished == 0) continue; + + std::vector::iterator slot_iter + = std::find(tmp_pieces.begin(), tmp_pieces.end(), p.index); + if (slot_iter == tmp_pieces.end()) + { + // this piece is marked as unfinished + // but doesn't have any storage + error = "piece " + boost::lexical_cast(p.index) + " is " + "marked as unfinished, but doesn't have any storage"; + return; + } + + assert(*slot_iter == p.index); + int slot_index = static_cast(slot_iter - tmp_pieces.begin()); + unsigned long adler + = torrent_ptr->filesystem().piece_crc( + slot_index + , torrent_ptr->block_size() + , p.info); + + const entry& ad = (*i)["adler32"]; + + // crc's didn't match, don't use the resume data + if (ad.integer() != entry::integer_type(adler)) + { + error = "checksum mismatch on piece " + + boost::lexical_cast(p.index); + return; + } + + tmp_unfinished.push_back(p); + } + } + + if (!torrent_ptr->verify_resume_data(rd, error)) + return; + + piece_map.swap(tmp_pieces); + unfinished_pieces.swap(tmp_unfinished); + } + catch (invalid_encoding&) + { + return; + } + catch (type_error&) + { + return; + } + catch (file_error&) + { + return; + } + } +}} + diff --git a/libtorrent/src/sha1.cpp b/libtorrent/src/sha1.cpp new file mode 100755 index 000000000..a7ea67113 --- /dev/null +++ b/libtorrent/src/sha1.cpp @@ -0,0 +1,317 @@ +/* +SHA-1 C++ conversion + +original version: + +SHA-1 in C +By Steve Reid +100% Public Domain + +changelog at the end of the file. +*/ + +#include "libtorrent/pch.hpp" + +#include +#include + +// if you don't want boost +// replace with +// #include + +#include +using boost::uint32_t; +using boost::uint8_t; + +#include "libtorrent/config.hpp" + +struct TORRENT_EXPORT SHA_CTX +{ + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; +}; + +TORRENT_EXPORT void SHA1_Init(SHA_CTX* context); +TORRENT_EXPORT void SHA1_Update(SHA_CTX* context, uint8_t const* data, uint32_t len); +TORRENT_EXPORT void SHA1_Final(uint8_t* digest, SHA_CTX* context); + +namespace +{ + union CHAR64LONG16 + { + uint8_t c[64]; + uint32_t l[16]; + }; + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +// blk0() and blk() perform the initial expand. +// I got the idea of expanding during the round function from SSLeay + struct little_endian_blk0 + { + static uint32_t apply(CHAR64LONG16* block, int i) + { + return block->l[i] = (rol(block->l[i],24)&0xFF00FF00) + | (rol(block->l[i],8)&0x00FF00FF); + } + }; + + struct big_endian_blk0 + { + static uint32_t apply(CHAR64LONG16* block, int i) + { + return block->l[i]; + } + }; + + +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +// (R0+R1), R2, R3, R4 are the different operations used in SHA1 +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+BlkFun::apply(block, i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + // Hash a single 512-bit block. This is the core of the algorithm. + template + void SHA1Transform(uint32_t state[5], uint8_t const buffer[64]) + { + using namespace std; + uint32_t a, b, c, d, e; + + CHAR64LONG16* block; + uint8_t workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + // Wipe variables + a = b = c = d = e = 0; + } + + void SHAPrintContext(SHA_CTX *context, char *msg) + { + using namespace std; + printf("%s (%d,%d) %x %x %x %x %x\n" + , msg, context->count[0], context->count[1] + , context->state[0], context->state[1] + , context->state[2], context->state[3] + , context->state[4]); + } + + template + void internal_update(SHA_CTX* context, uint8_t const* data, uint32_t len) + { + using namespace std; + uint32_t i, j; // JHB + +#ifdef VERBOSE + SHAPrintContext(context, "before"); +#endif + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else + { + i = 0; + } + memcpy(&context->buffer[j], &data[i], len - i); +#ifdef VERBOSE + SHAPrintContext(context, "after "); +#endif + } + + bool is_big_endian() + { + uint32_t test = 1; + return *reinterpret_cast(&test) == 0; + } +} + +// SHA1Init - Initialize new context + +void SHA1_Init(SHA_CTX* context) +{ + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +// Run your data through this. + +void SHA1_Update(SHA_CTX* context, uint8_t const* data, uint32_t len) +{ +#if defined __BIG_ENDIAN__ + internal_update(context, data, len); +#elif defined LITTLE_ENDIAN + internal_update(context, data, len); +#else + // select different functions depending on endianess + // and figure out the endianess runtime + if (is_big_endian()) + internal_update(context, data, len); + else + internal_update(context, data, len); +#endif +} + + +// Add padding and return the message digest. + +void SHA1_Final(uint8_t* digest, SHA_CTX* context) +{ + uint8_t finalcount[8]; + + for (uint32_t i = 0; i < 8; ++i) + { + // Endian independent + finalcount[i] = static_cast( + (context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); + } + + SHA1_Update(context, (uint8_t const*)"\200", 1); + while ((context->count[0] & 504) != 448) + SHA1_Update(context, (uint8_t const*)"\0", 1); + SHA1_Update(context, finalcount, 8); // Should cause a SHA1Transform() + + for (uint32_t i = 0; i < 20; ++i) + { + digest[i] = static_cast( + (context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} + +/************************************************************ + +----------------- +Modified 7/98 +By James H. Brown +Still 100% Public Domain + +Corrected a problem which generated improper hash values on 16 bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int +len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned +long len) + +The 'len' parameter was declared an int which works fine on 32 bit machines. +However, on 16 bit machines an int is too small for the shifts being done +against +it. This caused the hash function to generate incorrect values if len was +greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or larger would +be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million +"a"s). + +I also changed the declaration of variables i & j in SHA1Update to +unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit implementations since +an +int and a long are the same size in those environments. + +-- +I also corrected a few compiler warnings generated by Borland C. +1. Added #include for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments containing 'JHB' +----------------- +Modified 8/98 +By Steve Reid +Still 100% public domain + +1- Removed #include and used return() instead of exit() +2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) +3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + +----------------- +Modified 4/01 +By Saul Kravitz +Still 100% PD +Modified to run on Compaq Alpha hardware. + +----------------- +Converted to C++ 6/04 +By Arvid Norberg +1- made the input buffer const, and made the + previous SHA1HANDSOFF implicit +2- uses C99 types with size guarantees + from boost +3- if none of __BIG_ENDIAN__ or LITTLE_ENDIAN + are defined, endianess is determined + at runtime. templates are used to duplicate + the transform function for each endianess +4- using anonymous namespace to avoid external + linkage on internal functions +5- using standard C++ includes +6- made API compatible with openssl + +still 100% PD +*/ + +/* +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ diff --git a/libtorrent/src/socks4_stream.cpp b/libtorrent/src/socks4_stream.cpp new file mode 100644 index 000000000..3a31b2375 --- /dev/null +++ b/libtorrent/src/socks4_stream.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/socks4_stream.hpp" + +namespace libtorrent +{ + + void socks4_stream::name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + // SOCKS4 doesn't support IPv6 addresses + while (i != tcp::resolver::iterator() && i->endpoint().address().is_v6()) + ++i; + + if (i == tcp::resolver::iterator()) + { + asio::error_code ec = asio::error::operation_not_supported; + (*h)(e); + close(); + return; + } + + m_sock.async_connect(i->endpoint(), boost::bind( + &socks4_stream::connected, this, _1, h)); + } + + void socks4_stream::connected(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + + m_buffer.resize(m_user.size() + 9); + char* p = &m_buffer[0]; + write_uint8(4, p); // SOCKS VERSION 4 + write_uint8(1, p); // SOCKS CONNECT + write_uint16(m_remote_endpoint.port(), p); + write_uint32(m_remote_endpoint.address().to_v4().to_ulong(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // NULL terminator + + asio::async_write(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks4_stream::handshake1, this, _1, h)); + } + + void socks4_stream::handshake1(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + m_buffer.resize(8); + asio::async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks4_stream::handshake2, this, _1, h)); + } + + void socks4_stream::handshake2(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int reply_version = read_uint8(p); + int status_code = read_uint8(p); + + if (reply_version != 0) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + // access granted + if (status_code == 90) + { + std::vector().swap(m_buffer); + (*h)(e); + return; + } + + asio::error_code ec = asio::error::fault; + switch (status_code) + { + case 91: ec = asio::error::connection_refused; break; + case 92: ec = asio::error::no_permission; break; + case 93: ec = asio::error::no_permission; break; + } + (*h)(ec); + close(); + } + +} + diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp new file mode 100644 index 000000000..b1679c4ac --- /dev/null +++ b/libtorrent/src/socks5_stream.cpp @@ -0,0 +1,315 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/socks5_stream.hpp" + +namespace libtorrent +{ + + void socks5_stream::name_lookup(asio::error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { + if (e || i == tcp::resolver::iterator()) + { + (*h)(e); + close(); + return; + } + + m_sock.async_connect(i->endpoint(), boost::bind( + &socks5_stream::connected, this, _1, h)); + } + + void socks5_stream::connected(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + // send SOCKS5 authentication methods + m_buffer.resize(m_user.empty()?3:4); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + if (m_user.empty()) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } + asio::async_write(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake1, this, _1, h)); + } + + void socks5_stream::handshake1(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + m_buffer.resize(2); + asio::async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake2, this, _1, h)); + } + + void socks5_stream::handshake2(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < 5) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + if (method == 0) + { + socks_connect(h); + } + else if (method == 2) + { + if (m_user.empty()) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + // start sub-negotiation + m_buffer.resize(m_user.size() + m_password.size() + 3); + char* p = &m_buffer[0]; + write_uint8(1, p); + write_uint8(m_user.size(), p); + write_string(m_user, p); + write_uint8(m_password.size(), p); + write_string(m_password, p); + asio::async_write(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake3, this, _1, h)); + } + else + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + } + + void socks5_stream::handshake3(asio::error_code const& e + , boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + m_buffer.resize(2); + asio::async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake4, this, _1, h)); + } + + void socks5_stream::handshake4(asio::error_code const& e + , boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + if (status != 0) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + + std::vector().swap(m_buffer); + (*h)(e); + } + + void socks5_stream::socks_connect(boost::shared_ptr h) + { + using namespace libtorrent::detail; + + // send SOCKS5 connect command + m_buffer.resize(6 + (m_remote_endpoint.address().is_v4()?4:16)); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(1, p); // CONNECT command + write_uint8(0, p); // reserved + write_uint8(m_remote_endpoint.address().is_v4()?1:4, p); // address type + write_address(m_remote_endpoint.address(), p); + write_uint16(m_remote_endpoint.port(), p); + assert(p - &m_buffer[0] == int(m_buffer.size())); + + asio::async_write(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::connect1, this, _1, h)); + } + + void socks5_stream::connect1(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + m_buffer.resize(6 + 4); // assume an IPv4 address + asio::async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::connect2, this, _1, h)); + } + + void socks5_stream::connect2(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + using namespace libtorrent::detail; + + // send SOCKS5 connect command + char* p = &m_buffer[0]; + int version = read_uint8(p); + if (version < 5) + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + int response = read_uint8(p); + if (response != 0) + { + asio::error_code e = asio::error::fault; + switch (response) + { + case 1: e = asio::error::fault; break; + case 2: e = asio::error::no_permission; break; + case 3: e = asio::error::network_unreachable; break; + case 4: e = asio::error::host_unreachable; break; + case 5: e = asio::error::connection_refused; break; + case 6: e = asio::error::timed_out; break; + case 7: e = asio::error::operation_not_supported; break; + case 8: e = asio::error::address_family_not_supported; break; + } + (*h)(e); + close(); + return; + } + p += 1; // reserved + int atyp = read_uint8(p); + // we ignore the proxy IP it was bound to + if (atyp == 1) + { + std::vector().swap(m_buffer); + (*h)(e); + return; + } + int skip_bytes = 0; + if (atyp == 4) + { + skip_bytes = 12; + } + else if (atyp == 3) + { + skip_bytes = read_uint8(p) - 3; + } + else + { + (*h)(asio::error::operation_not_supported); + close(); + return; + } + m_buffer.resize(skip_bytes); + + asio::async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&socks5_stream::connect3, this, _1, h)); + } + + void socks5_stream::connect3(asio::error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + close(); + return; + } + + std::vector().swap(m_buffer); + (*h)(e); + } +} + diff --git a/libtorrent/src/stat.cpp b/libtorrent/src/stat.cpp new file mode 100755 index 000000000..d695edc42 --- /dev/null +++ b/libtorrent/src/stat.cpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +// TODO: Use two algorithms to estimate transfer rate. +// one (simple) for transfer rates that are >= 1 packet +// per second and one (low pass-filter) for rates < 1 +// packet per second. + +#include "libtorrent/pch.hpp" + +#include +#include + +#include "libtorrent/stat.hpp" +#include "libtorrent/invariant_check.hpp" + +#if defined _MSC_VER && _MSC_VER <= 1200 +#define for if (false) {} else for +#endif + +using namespace libtorrent; + +void libtorrent::stat::second_tick(float tick_interval) +{ + INVARIANT_CHECK; + + for (int i = history - 2; i >= 0; --i) + { + m_download_rate_history[i + 1] = m_download_rate_history[i]; + m_upload_rate_history[i + 1] = m_upload_rate_history[i]; + m_download_payload_rate_history[i + 1] = m_download_payload_rate_history[i]; + m_upload_payload_rate_history[i + 1] = m_upload_payload_rate_history[i]; + } + + m_download_rate_history[0] = (m_downloaded_payload + m_downloaded_protocol) + / tick_interval; + m_upload_rate_history[0] = (m_uploaded_payload + m_uploaded_protocol) + / tick_interval; + m_download_payload_rate_history[0] = m_downloaded_payload / tick_interval; + m_upload_payload_rate_history[0] = m_uploaded_payload / tick_interval; + + m_downloaded_payload = 0; + m_uploaded_payload = 0; + m_downloaded_protocol = 0; + m_uploaded_protocol = 0; + + m_mean_download_rate = 0; + m_mean_upload_rate = 0; + m_mean_download_payload_rate = 0; + m_mean_upload_payload_rate = 0; + + for (int i = 0; i < history; ++i) + { + m_mean_download_rate += m_download_rate_history[i]; + m_mean_upload_rate += m_upload_rate_history[i]; + m_mean_download_payload_rate += m_download_payload_rate_history[i]; + m_mean_upload_payload_rate += m_upload_payload_rate_history[i]; + } + + m_mean_download_rate /= history; + m_mean_upload_rate /= history; + m_mean_download_payload_rate /= history; + m_mean_upload_payload_rate /= history; +} diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp new file mode 100755 index 000000000..863cc5c07 --- /dev/null +++ b/libtorrent/src/storage.cpp @@ -0,0 +1,2238 @@ +/* + +Copyright (c) 2003, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/storage.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#ifndef NDEBUG +#include +#include +#include +#include +#endif + +#if defined(__APPLE__) +// for getattrlist() +#include +#include +// for statfs() +#include +#include +#endif + +#if defined(__linux__) +#include +#endif + +#if defined(_WIN32) && defined(UNICODE) + +#include +#include +#include "libtorrent/utf8.hpp" + +namespace libtorrent +{ + std::wstring safe_convert(std::string const& s) + { + try + { + return libtorrent::utf8_wchar(s); + } + catch (std::exception) + { + std::wstring ret; + const char* end = &s[0] + s.size(); + for (const char* i = &s[0]; i < end;) + { + wchar_t c = '.'; + int result = std::mbtowc(&c, i, end - i); + if (result > 0) i += result; + else ++i; + ret += c; + } + return ret; + } + } +} +#endif + +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 +namespace +{ + using libtorrent::safe_convert; + using namespace boost::filesystem; + + // based on code from Boost.Fileystem + bool create_directories_win(const fs::path& ph) + { + if (ph.empty() || exists(ph)) + { + if ( !ph.empty() && !is_directory(ph) ) + boost::throw_exception( filesystem_error( + "boost::filesystem::create_directories", + ph, "path exists and is not a directory", + not_directory_error ) ); + return false; + } + + // First create branch, by calling ourself recursively + create_directories_win(ph.branch_path()); + // Now that parent's path exists, create the directory + std::wstring wph(safe_convert(ph.native_directory_string())); + CreateDirectory(wph.c_str(), 0); + return true; + } + + bool exists_win( const fs::path & ph ) + { + std::wstring wpath(safe_convert(ph.string())); + if(::GetFileAttributes( wpath.c_str() ) == 0xFFFFFFFF) + { + UINT err = ::GetLastError(); + if((err == ERROR_FILE_NOT_FOUND) + || (err == ERROR_INVALID_PARAMETER) + || (err == ERROR_NOT_READY) + || (err == ERROR_PATH_NOT_FOUND) + || (err == ERROR_INVALID_NAME) + || (err == ERROR_BAD_NETPATH )) + return false; // GetFileAttributes failed because the path does not exist + // for any other error we assume the file does exist and fall through, + // this may not be the best policy though... (JM 20040330) + return true; + } + return true; + } + + boost::intmax_t file_size_win( const fs::path & ph ) + { + std::wstring wpath(safe_convert(ph.string())); + // by now, intmax_t is 64-bits on all Windows compilers + WIN32_FILE_ATTRIBUTE_DATA fad; + if ( !::GetFileAttributesExW( wpath.c_str(), + ::GetFileExInfoStandard, &fad ) ) + boost::throw_exception( filesystem_error( + "boost::filesystem::file_size", + ph, detail::system_error_code() ) ); + if ( (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) !=0 ) + boost::throw_exception( filesystem_error( + "boost::filesystem::file_size", + ph, "invalid: is a directory", + is_directory_error ) ); + return (static_cast(fad.nFileSizeHigh) + << (sizeof(fad.nFileSizeLow)*8)) + + fad.nFileSizeLow; + } + + std::time_t last_write_time_win( const fs::path & ph ) + { + struct _stat path_stat; + std::wstring wph(safe_convert(ph.native_file_string())); + if ( ::_wstat( wph.c_str(), &path_stat ) != 0 ) + boost::throw_exception( filesystem_error( + "boost::filesystem::last_write_time", + ph, detail::system_error_code() ) ); + return path_stat.st_mtime; + } + + void rename_win( const fs::path & old_path, + const fs::path & new_path ) + { + std::wstring wold_path(safe_convert(old_path.string())); + std::wstring wnew_path(safe_convert(new_path.string())); + if ( !::MoveFile( wold_path.c_str(), wnew_path.c_str() ) ) + boost::throw_exception( filesystem_error( + "boost::filesystem::rename", + old_path, new_path, detail::system_error_code() ) ); + } + +} // anonymous namespace + +#endif + +#if BOOST_VERSION < 103200 +bool operator<(fs::path const& lhs, fs::path const& rhs) +{ + return lhs.string() < rhs.string(); +} +#endif + +namespace fs = boost::filesystem; +using boost::bind; +using namespace ::boost::multi_index; +using boost::multi_index::multi_index_container; + +#if !defined(NDEBUG) && defined(TORRENT_STORAGE_DEBUG) +namespace +{ + using namespace libtorrent; + + void print_to_log(const std::string& s) + { + static std::ofstream log("log.txt"); + log << s; + log.flush(); + } +} +#endif + +namespace libtorrent +{ + + std::vector > get_filesizes( + torrent_info const& t, fs::path p) + { + p = complete(p); + std::vector > sizes; + for (torrent_info::file_iterator i = t.begin_files(); + i != t.end_files(); ++i) + { + size_type size = 0; + std::time_t time = 0; + try + { +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + fs::path f = p / i->path; + size = file_size_win(f); + time = last_write_time_win(f); +#elif defined(_WIN32) && defined(UNICODE) + fs::wpath f = safe_convert((p / i->path).string()); + size = file_size(f); + time = last_write_time(f); +#else + fs::path f = p / i->path; + size = file_size(f); + time = last_write_time(f); +#endif + } + catch (std::exception&) {} + sizes.push_back(std::make_pair(size, time)); + } + return sizes; + } + + // matches the sizes and timestamps of the files passed in + // in non-compact mode, actual file sizes and timestamps + // are allowed to be bigger and more recent than the fast + // resume data. This is because full allocation will not move + // pieces, so any older version of the resume data will + // still be a correct subset of the actual data on disk. + bool match_filesizes( + torrent_info const& t + , fs::path p + , std::vector > const& sizes + , bool compact_mode + , std::string* error) + { + if ((int)sizes.size() != t.num_files()) + { + if (error) *error = "mismatching number of files"; + return false; + } + p = complete(p); + + std::vector >::const_iterator s + = sizes.begin(); + for (torrent_info::file_iterator i = t.begin_files(); + i != t.end_files(); ++i, ++s) + { + size_type size = 0; + std::time_t time = 0; + try + { +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + fs::path f = p / i->path; + size = file_size_win(f); + time = last_write_time_win(f); +#elif defined(_WIN32) && defined(UNICODE) + fs::wpath f = safe_convert((p / i->path).string()); + size = file_size(f); + time = last_write_time(f); +#else + fs::path f = p / i->path; + size = file_size(f); + time = last_write_time(f); +#endif + } + catch (std::exception&) {} + if ((compact_mode && size != s->first) + || (!compact_mode && size < s->first)) + { + if (error) *error = "filesize mismatch for file '" + + i->path.native_file_string() + + "', size: " + boost::lexical_cast(size) + + ", expected to be " + boost::lexical_cast(s->first) + + " bytes"; + return false; + } + if ((compact_mode && time != s->second) + || (!compact_mode && time < s->second)) + { + if (error) *error = "timestamp mismatch for file '" + + i->path.native_file_string() + + "', modification date: " + boost::lexical_cast(time) + + ", expected to have modification date " + + boost::lexical_cast(s->second); + return false; + } + } + return true; + } + + struct thread_safe_storage + { + thread_safe_storage(std::size_t n) + : slots(n, false) + {} + + boost::mutex mutex; + boost::condition condition; + std::vector slots; + }; + + struct slot_lock + { + slot_lock(thread_safe_storage& s, int slot_) + : storage_(s) + , slot(slot_) + { + assert(slot_>=0 && slot_ < (int)s.slots.size()); + boost::mutex::scoped_lock lock(storage_.mutex); + + while (storage_.slots[slot]) + storage_.condition.wait(lock); + storage_.slots[slot] = true; + } + + ~slot_lock() + { + storage_.slots[slot] = false; + storage_.condition.notify_all(); + } + + thread_safe_storage& storage_; + int slot; + }; + + class storage : public storage_interface, thread_safe_storage, boost::noncopyable + { + public: + storage(torrent_info const& info, fs::path const& path, file_pool& fp) + : thread_safe_storage(info.num_pieces()) + , m_info(info) + , m_files(fp) + { + assert(info.begin_files() != info.end_files()); + m_save_path = fs::complete(path); + assert(m_save_path.is_complete()); + } + + void release_files(); + void initialize(bool allocate_files); + bool move_storage(fs::path save_path); + size_type read(char* buf, int slot, int offset, int size); + void write(const char* buf, int slot, int offset, int size); + void move_slot(int src_slot, int dst_slot); + void swap_slots(int slot1, int slot2); + void swap_slots3(int slot1, int slot2, int slot3); + bool verify_resume_data(entry& rd, std::string& error); + void write_resume_data(entry& rd) const; + sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size); + + size_type read_impl(char* buf, int slot, int offset, int size, bool fill_zero); + + ~storage() + { + m_files.release(this); + } + + torrent_info const& m_info; + fs::path m_save_path; + // the file pool is typically stored in + // the session, to make all storage + // instances use the same pool + file_pool& m_files; + + // temporary storage for moving pieces + std::vector m_scratch_buffer; + }; + + sha1_hash storage::hash_for_slot(int slot, partial_hash& ph, int piece_size) + { +#ifndef NDEBUG + hasher partial; + hasher whole; + int slot_size1 = piece_size; + m_scratch_buffer.resize(slot_size1); + read_impl(&m_scratch_buffer[0], slot, 0, slot_size1, true); + if (ph.offset > 0) + partial.update(&m_scratch_buffer[0], ph.offset); + whole.update(&m_scratch_buffer[0], slot_size1); + hasher partial_copy = ph.h; + assert(ph.offset == 0 || partial_copy.final() == partial.final()); +#endif + int slot_size = piece_size - ph.offset; + if (slot_size == 0) return ph.h.final(); + m_scratch_buffer.resize(slot_size); + read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); + ph.h.update(&m_scratch_buffer[0], slot_size); + sha1_hash ret = ph.h.final(); + assert(whole.final() == ret); + return ret; + } + + void storage::initialize(bool allocate_files) + { + // first, create all missing directories + fs::path last_path; + for (torrent_info::file_iterator file_iter = m_info.begin_files(), + end_iter = m_info.end_files(); file_iter != end_iter; ++file_iter) + { + fs::path dir = (m_save_path / file_iter->path).branch_path(); + + if (dir != last_path) + { + +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + last_path = dir; + if (!exists_win(last_path)) + create_directories_win(last_path); +#elif defined(_WIN32) && defined(UNICODE) + last_path = dir; + fs::wpath wp = safe_convert(last_path.string()); + if (!exists(wp)) + create_directories(wp); +#else + last_path = dir; + if (!exists(last_path)) + create_directories(last_path); +#endif + } + + // if the file is empty, just create it. But also make sure + // the directory exits. + if (file_iter->size == 0) + { + file(m_save_path / file_iter->path, file::out); + continue; + } + + if (allocate_files) + { + m_files.open_file(this, m_save_path / file_iter->path, file::in | file::out) + ->set_size(file_iter->size); + } + } + } + + void storage::release_files() + { + m_files.release(this); + std::vector().swap(m_scratch_buffer); + } + + void storage::write_resume_data(entry& rd) const + { + std::vector > file_sizes + = get_filesizes(m_info, m_save_path); + + rd["file sizes"] = entry::list_type(); + entry::list_type& fl = rd["file sizes"].list(); + for (std::vector >::iterator i + = file_sizes.begin(), end(file_sizes.end()); i != end; ++i) + { + entry::list_type p; + p.push_back(entry(i->first)); + p.push_back(entry(i->second)); + fl.push_back(entry(p)); + } + } + + bool storage::verify_resume_data(entry& rd, std::string& error) + { + std::vector > file_sizes; + entry::list_type& l = rd["file sizes"].list(); + + for (entry::list_type::iterator i = l.begin(); + i != l.end(); ++i) + { + file_sizes.push_back(std::pair( + i->list().front().integer() + , i->list().back().integer())); + } + + if (file_sizes.empty()) + { + error = "the number of files in resume data is 0"; + return false; + } + + entry::list_type& slots = rd["slots"].list(); + bool seed = int(slots.size()) == m_info.num_pieces() + && std::find_if(slots.begin(), slots.end() + , boost::bind(std::less() + , boost::bind((size_type const& (entry::*)() const) + &entry::integer, _1), 0)) == slots.end(); + + bool full_allocation_mode = false; + try + { + full_allocation_mode = rd["allocation"].string() == "full"; + } + catch (std::exception&) {} + + if (seed) + { + if (m_info.num_files() != (int)file_sizes.size()) + { + error = "the number of files does not match the torrent (num: " + + boost::lexical_cast(file_sizes.size()) + " actual: " + + boost::lexical_cast(m_info.num_files()) + ")"; + return false; + } + + std::vector >::iterator + fs = file_sizes.begin(); + // the resume data says we have the entire torrent + // make sure the file sizes are the right ones + for (torrent_info::file_iterator i = m_info.begin_files() + , end(m_info.end_files()); i != end; ++i, ++fs) + { + if (i->size != fs->first) + { + error = "file size for '" + i->path.native_file_string() + + "' was expected to be " + + boost::lexical_cast(i->size) + " bytes"; + return false; + } + } + return true; + } + + return match_filesizes(m_info, m_save_path, file_sizes + , !full_allocation_mode, &error); + } + + // returns true on success + bool storage::move_storage(fs::path save_path) + { +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 + fs::wpath old_path; + fs::wpath new_path; +#else + fs::path old_path; + fs::path new_path; +#endif + + save_path = complete(save_path); + +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + std::wstring wsave_path(safe_convert(save_path.native_file_string())); + if (!exists_win(save_path)) + CreateDirectory(wsave_path.c_str(), 0); + else if ((GetFileAttributes(wsave_path.c_str()) & FILE_ATTRIBUTE_DIRECTORY) == 0) + return false; +#elif defined(_WIN32) && defined(UNICODE) + fs::wpath wp = safe_convert(save_path.string()); + if (!exists(wp)) + create_directory(wp); + else if (!is_directory(wp)) + return false; +#else + if (!exists(save_path)) + create_directory(save_path); + else if (!is_directory(save_path)) + return false; +#endif + + m_files.release(this); + +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 + old_path = safe_convert((m_save_path / m_info.name()).string()); + new_path = safe_convert((save_path / m_info.name()).string()); +#else + old_path = m_save_path / m_info.name(); + new_path = save_path / m_info.name(); +#endif + + try + { +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + rename_win(old_path, new_path); + rename(old_path, new_path); +#else + rename(old_path, new_path); +#endif + m_save_path = save_path; + return true; + } + catch (std::exception&) {} + return false; + } + +#ifndef NDEBUG +/* + void storage::shuffle() + { + int num_pieces = m_info.num_pieces(); + + std::vector pieces(num_pieces); + for (std::vector::iterator i = pieces.begin(); + i != pieces.end(); ++i) + { + *i = static_cast(i - pieces.begin()); + } + std::srand((unsigned int)std::time(0)); + std::vector targets(pieces); + std::random_shuffle(pieces.begin(), pieces.end()); + std::random_shuffle(targets.begin(), targets.end()); + + for (int i = 0; i < (std::max)(num_pieces / 50, 1); ++i) + { + const int slot_index = targets[i]; + const int piece_index = pieces[i]; + const int slot_size =static_cast(m_info.piece_size(slot_index)); + std::vector buf(slot_size); + read(&buf[0], piece_index, 0, slot_size); + write(&buf[0], slot_index, 0, slot_size); + } + } +*/ +#endif + + void storage::move_slot(int src_slot, int dst_slot) + { + int piece_size = m_info.piece_size(dst_slot); + m_scratch_buffer.resize(piece_size); + read_impl(&m_scratch_buffer[0], src_slot, 0, piece_size, true); + write(&m_scratch_buffer[0], dst_slot, 0, piece_size); + } + + void storage::swap_slots(int slot1, int slot2) + { + // the size of the target slot is the size of the piece + int piece_size = m_info.piece_length(); + int piece1_size = m_info.piece_size(slot2); + int piece2_size = m_info.piece_size(slot1); + m_scratch_buffer.resize(piece_size * 2); + read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); + read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); + write(&m_scratch_buffer[0], slot2, 0, piece1_size); + write(&m_scratch_buffer[piece_size], slot1, 0, piece2_size); + } + + void storage::swap_slots3(int slot1, int slot2, int slot3) + { + // the size of the target slot is the size of the piece + int piece_size = m_info.piece_length(); + int piece1_size = m_info.piece_size(slot2); + int piece2_size = m_info.piece_size(slot3); + int piece3_size = m_info.piece_size(slot1); + m_scratch_buffer.resize(piece_size * 2); + read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); + read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); + write(&m_scratch_buffer[0], slot2, 0, piece1_size); + read_impl(&m_scratch_buffer[0], slot3, 0, piece3_size, true); + write(&m_scratch_buffer[piece_size], slot3, 0, piece2_size); + write(&m_scratch_buffer[0], slot1, 0, piece3_size); + } + + size_type storage::read( + char* buf + , int slot + , int offset + , int size) + { + return read_impl(buf, slot, offset, size, false); + } + + size_type storage::read_impl( + char* buf + , int slot + , int offset + , int size + , bool fill_zero) + { + assert(buf != 0); + assert(slot >= 0 && slot < m_info.num_pieces()); + assert(offset >= 0); + assert(offset < m_info.piece_size(slot)); + assert(size > 0); + + slot_lock lock(*this, slot); + +#ifndef NDEBUG + std::vector slices + = m_info.map_block(slot, offset, size); + assert(!slices.empty()); +#endif + + size_type start = slot * (size_type)m_info.piece_length() + offset; + assert(start + size <= m_info.total_size()); + + // find the file iterator and file offset + size_type file_offset = start; + std::vector::const_iterator file_iter; + + for (file_iter = m_info.begin_files();;) + { + if (file_offset < file_iter->size) + break; + + file_offset -= file_iter->size; + ++file_iter; + } + + int buf_pos = 0; + boost::shared_ptr in(m_files.open_file( + this, m_save_path / file_iter->path, file::in)); + + assert(file_offset < file_iter->size); + + assert(slices[0].offset == file_offset); + + size_type new_pos = in->seek(file_offset); + if (new_pos != file_offset) + { + // the file was not big enough + if (!fill_zero) + throw file_error("slot has no storage"); + std::memset(buf + buf_pos, 0, size - buf_pos); + return size; + } + +#ifndef NDEBUG + size_type in_tell = in->tell(); + assert(in_tell == file_offset); +#endif + + int left_to_read = size; + int slot_size = static_cast(m_info.piece_size(slot)); + + if (offset + left_to_read > slot_size) + left_to_read = slot_size - offset; + + assert(left_to_read >= 0); + + size_type result = left_to_read; + +#ifndef NDEBUG + int counter = 0; +#endif + + while (left_to_read > 0) + { + int read_bytes = left_to_read; + if (file_offset + read_bytes > file_iter->size) + read_bytes = static_cast(file_iter->size - file_offset); + + if (read_bytes > 0) + { +#ifndef NDEBUG + assert(int(slices.size()) > counter); + size_type slice_size = slices[counter].size; + assert(slice_size == read_bytes); + assert(m_info.file_at(slices[counter].file_index).path + == file_iter->path); +#endif + + size_type actual_read = in->read(buf + buf_pos, read_bytes); + + if (read_bytes != actual_read) + { + // the file was not big enough + if (actual_read > 0) buf_pos += actual_read; + if (!fill_zero) + throw file_error("slot has no storage"); + std::memset(buf + buf_pos, 0, size - buf_pos); + return size; + } + + left_to_read -= read_bytes; + buf_pos += read_bytes; + assert(buf_pos >= 0); + file_offset += read_bytes; + } + + if (left_to_read > 0) + { + ++file_iter; +#ifndef NDEBUG + // empty files are not returned by map_block, so if + // this file was empty, don't increment the slice counter + if (read_bytes > 0) ++counter; +#endif + fs::path path = m_save_path / file_iter->path; + + file_offset = 0; + in = m_files.open_file( + this, path, file::in); + in->seek(0); + } + } + return result; + } + + // throws file_error if it fails to write + void storage::write( + const char* buf + , int slot + , int offset + , int size) + { + assert(buf != 0); + assert(slot >= 0); + assert(slot < m_info.num_pieces()); + assert(offset >= 0); + assert(size > 0); + + slot_lock lock(*this, slot); + +#ifndef NDEBUG + std::vector slices + = m_info.map_block(slot, offset, size); + assert(!slices.empty()); +#endif + + size_type start = slot * (size_type)m_info.piece_length() + offset; + + // find the file iterator and file offset + size_type file_offset = start; + std::vector::const_iterator file_iter; + + for (file_iter = m_info.begin_files();;) + { + if (file_offset < file_iter->size) + break; + + file_offset -= file_iter->size; + ++file_iter; + assert(file_iter != m_info.end_files()); + } + + fs::path p(m_save_path / file_iter->path); + boost::shared_ptr out = m_files.open_file( + this, p, file::out | file::in); + + assert(file_offset < file_iter->size); + assert(slices[0].offset == file_offset); + + size_type pos = out->seek(file_offset); + + if (pos != file_offset) + { + std::stringstream s; + s << "no storage for slot " << slot; + throw file_error(s.str()); + } + + int left_to_write = size; + int slot_size = static_cast(m_info.piece_size(slot)); + + if (offset + left_to_write > slot_size) + left_to_write = slot_size - offset; + + assert(left_to_write >= 0); + + int buf_pos = 0; +#ifndef NDEBUG + int counter = 0; +#endif + while (left_to_write > 0) + { + int write_bytes = left_to_write; + if (file_offset + write_bytes > file_iter->size) + { + assert(file_iter->size >= file_offset); + write_bytes = static_cast(file_iter->size - file_offset); + } + + if (write_bytes > 0) + { + assert(int(slices.size()) > counter); + assert(slices[counter].size == write_bytes); + assert(m_info.file_at(slices[counter].file_index).path + == file_iter->path); + + assert(buf_pos >= 0); + assert(write_bytes >= 0); + size_type written = out->write(buf + buf_pos, write_bytes); + + if (written != write_bytes) + { + std::stringstream s; + s << "no storage for slot " << slot; + throw file_error(s.str()); + } + + left_to_write -= write_bytes; + buf_pos += write_bytes; + assert(buf_pos >= 0); + file_offset += write_bytes; + assert(file_offset <= file_iter->size); + } + + if (left_to_write > 0) + { +#ifndef NDEBUG + if (write_bytes > 0) ++counter; +#endif + ++file_iter; + + assert(file_iter != m_info.end_files()); + fs::path p = m_save_path / file_iter->path; + file_offset = 0; + out = m_files.open_file( + this, p, file::out | file::in); + + out->seek(0); + } + } + } + + storage_interface* default_storage_constructor(torrent_info const& ti + , fs::path const& path, file_pool& fp) + { + return new storage(ti, path, fp); + } + + bool supports_sparse_files(fs::path const& p) + { + assert(p.is_complete()); +#if defined(_WIN32) + // assume windows API is available + DWORD max_component_len = 0; + DWORD volume_flags = 0; + std::string root_device = p.root_name() + "\\"; +#if defined(UNICODE) + std::wstring wph(safe_convert(root_device)); + bool ret = ::GetVolumeInformation(wph.c_str(), 0 + , 0, 0, &max_component_len, &volume_flags, 0, 0); +#else + bool ret = ::GetVolumeInformation(root_device.c_str(), 0 + , 0, 0, &max_component_len, &volume_flags, 0, 0); +#endif + + if (!ret) return false; + if (volume_flags & FILE_SUPPORTS_SPARSE_FILES) + return true; +#endif + +#if defined(__APPLE__) || defined(__linux__) + // find the last existing directory of the save path + fs::path query_path = p; + while (!query_path.empty() && !exists(query_path)) + query_path = query_path.branch_path(); +#endif + +#if defined(__APPLE__) + + struct statfs fsinfo; + int ret = statfs(query_path.native_directory_string().c_str(), &fsinfo); + if (ret != 0) return false; + + attrlist request; + request.bitmapcount = ATTR_BIT_MAP_COUNT; + request.reserved = 0; + request.commonattr = 0; + request.volattr = ATTR_VOL_CAPABILITIES; + request.dirattr = 0; + request.fileattr = 0; + request.forkattr = 0; + + struct vol_capabilities_attr_buf + { + unsigned long length; + vol_capabilities_attr_t info; + } vol_cap; + + ret = getattrlist(fsinfo.f_mntonname, &request, &vol_cap + , sizeof(vol_cap), 0); + if (ret != 0) return false; + + if (vol_cap.info.capabilities[VOL_CAPABILITIES_FORMAT] + & (VOL_CAP_FMT_SPARSE_FILES | VOL_CAP_FMT_ZERO_RUNS)) + { + return true; + } + + return true; +#endif + +#if defined(__linux__) + struct statfs buf; + int err = statfs(query_path.native_directory_string().c_str(), &buf); + if (err == 0) + { +#ifndef NDEBUG + std::cerr << "buf.f_type " << std::hex << buf.f_type << std::endl; +#endif + switch (buf.f_type) + { + case 0x5346544e: // NTFS + case 0xEF51: // EXT2 OLD + case 0xEF53: // EXT2 and EXT3 + case 0x00011954: // UFS + case 0x52654973: // ReiserFS + case 0x58465342: // XFS + return true; + } + } +#ifndef NDEBUG + else + { + std::cerr << "statfs returned " << err << std::endl; + std::cerr << "errno: " << errno << std::endl; + std::cerr << "path: " << query_path.native_directory_string() << std::endl; + } +#endif +#endif + + // TODO: POSIX implementation + return false; + } + + // -- piece_manager ----------------------------------------------------- + + piece_manager::piece_manager( + boost::shared_ptr const& torrent + , torrent_info const& ti + , fs::path const& save_path + , file_pool& fp + , disk_io_thread& io + , storage_constructor_type sc) + : m_storage(sc(ti, save_path, fp)) + , m_compact_mode(false) + , m_fill_mode(true) + , m_info(ti) + , m_save_path(complete(save_path)) + , m_allocating(false) + , m_io_thread(io) + , m_torrent(torrent) + { + m_fill_mode = !supports_sparse_files(save_path); + } + + piece_manager::~piece_manager() + { + } + + void piece_manager::write_resume_data(entry& rd) const + { + m_storage->write_resume_data(rd); + } + + bool piece_manager::verify_resume_data(entry& rd, std::string& error) + { + return m_storage->verify_resume_data(rd, error); + } + + void piece_manager::async_release_files( + boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::release_files; + m_io_thread.add_job(j, handler); + } + + void piece_manager::async_move_storage(fs::path const& p + , boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::move_storage; + j.str = p.string(); + m_io_thread.add_job(j, handler); + } + + void piece_manager::async_read( + peer_request const& r + , boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::read; + j.piece = r.piece; + j.offset = r.start; + j.buffer_size = r.length; + m_io_thread.add_job(j, handler); + } + + void piece_manager::async_write( + peer_request const& r + , char const* buffer + , boost::function const& handler) + { + assert(r.length <= 16 * 1024); + + disk_io_job j; + j.storage = this; + j.action = disk_io_job::write; + j.piece = r.piece; + j.offset = r.start; + j.buffer_size = r.length; + j.buffer = m_io_thread.allocate_buffer(); + if (j.buffer == 0) throw file_error("out of memory"); + std::memcpy(j.buffer, buffer, j.buffer_size); + m_io_thread.add_job(j, handler); + } + + void piece_manager::async_hash(int piece + , boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::hash; + j.piece = piece; + + m_io_thread.add_job(j, handler); + } + + fs::path piece_manager::save_path() const + { + boost::recursive_mutex::scoped_lock l(m_mutex); + return m_save_path; + } + + sha1_hash piece_manager::hash_for_piece_impl(int piece) + { + partial_hash ph; + + std::map::iterator i = m_piece_hasher.find(piece); + if (i != m_piece_hasher.end()) + { + ph = i->second; + m_piece_hasher.erase(i); + } + + int slot = m_piece_to_slot[piece]; + assert(slot != has_no_slot); + return m_storage->hash_for_slot(slot, ph, m_info.piece_size(piece)); + } + + void piece_manager::release_files_impl() + { + m_storage->release_files(); + } + + bool piece_manager::move_storage_impl(fs::path const& save_path) + { + if (m_storage->move_storage(save_path)) + { + m_save_path = fs::complete(save_path); + return true; + } + return false; + } + void piece_manager::export_piece_map( + std::vector& p) const + { + boost::recursive_mutex::scoped_lock lock(m_mutex); + + INVARIANT_CHECK; + + p.clear(); + std::vector::const_reverse_iterator last; + for (last = m_slot_to_piece.rbegin(); + last != m_slot_to_piece.rend(); ++last) + { + if (*last != unallocated) break; + } + + for (std::vector::const_iterator i = + m_slot_to_piece.begin(); + i != last.base(); ++i) + { + p.push_back(*i); + } + } + + void piece_manager::mark_failed(int piece_index) + { + boost::recursive_mutex::scoped_lock lock(m_mutex); + + INVARIANT_CHECK; + + assert(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + assert(m_piece_to_slot[piece_index] >= 0); + + int slot_index = m_piece_to_slot[piece_index]; + + assert(slot_index >= 0); + + m_slot_to_piece[slot_index] = unassigned; + m_piece_to_slot[piece_index] = has_no_slot; + m_free_slots.push_back(slot_index); + } + + int piece_manager::slot_for_piece(int piece_index) const + { + assert(piece_index >= 0 && piece_index < m_info.num_pieces()); + return m_piece_to_slot[piece_index]; + } + + unsigned long piece_manager::piece_crc( + int slot_index + , int block_size + , piece_picker::block_info const* bi) + try + { + assert(slot_index >= 0); + assert(slot_index < m_info.num_pieces()); + assert(block_size > 0); + + adler32_crc crc; + std::vector buf(block_size); + int num_blocks = static_cast(m_info.piece_size(slot_index)) / block_size; + int last_block_size = static_cast(m_info.piece_size(slot_index)) % block_size; + if (last_block_size == 0) last_block_size = block_size; + + for (int i = 0; i < num_blocks-1; ++i) + { + if (bi[i].state != piece_picker::block_info::state_finished) continue; + m_storage->read( + &buf[0] + , slot_index + , i * block_size + , block_size); + crc.update(&buf[0], block_size); + } + if (bi[num_blocks - 1].state == piece_picker::block_info::state_finished) + { + m_storage->read( + &buf[0] + , slot_index + , block_size * (num_blocks - 1) + , last_block_size); + crc.update(&buf[0], last_block_size); + } + return crc.final(); + } + catch (std::exception&) + { + return 0; + } + + size_type piece_manager::read_impl( + char* buf + , int piece_index + , int offset + , int size) + { + assert(buf); + assert(offset >= 0); + assert(size > 0); + assert(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + assert(m_piece_to_slot[piece_index] >= 0 + && m_piece_to_slot[piece_index] < (int)m_slot_to_piece.size()); + int slot = m_piece_to_slot[piece_index]; + assert(slot >= 0 && slot < (int)m_slot_to_piece.size()); + return m_storage->read(buf, slot, offset, size); + } + + void piece_manager::write_impl( + const char* buf + , int piece_index + , int offset + , int size) + { + assert(buf); + assert(offset >= 0); + assert(size > 0); + assert(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + + if (offset == 0) + { + partial_hash& ph = m_piece_hasher[piece_index]; + assert(ph.offset == 0); + ph.offset = size; + ph.h.update(buf, size); + } + else + { + std::map::iterator i = m_piece_hasher.find(piece_index); + if (i != m_piece_hasher.end()) + { + assert(i->second.offset > 0); + if (offset == i->second.offset) + { + i->second.offset += size; + i->second.h.update(buf, size); + } + } + } + + int slot = allocate_slot_for_piece(piece_index); + assert(slot >= 0 && slot < (int)m_slot_to_piece.size()); + m_storage->write(buf, slot, offset, size); + } + + int piece_manager::identify_data( + const std::vector& piece_data + , int current_slot + , std::vector& have_pieces + , int& num_pieces + , const std::multimap& hash_to_piece + , boost::recursive_mutex& mutex) + { +// INVARIANT_CHECK; + + assert((int)have_pieces.size() == m_info.num_pieces()); + + const int piece_size = static_cast(m_info.piece_length()); + const int last_piece_size = static_cast(m_info.piece_size( + m_info.num_pieces() - 1)); + + assert((int)piece_data.size() >= last_piece_size); + + // calculate a small digest, with the same + // size as the last piece. And a large digest + // which has the same size as a normal piece + hasher small_digest; + small_digest.update(&piece_data[0], last_piece_size); + hasher large_digest(small_digest); + assert(piece_size - last_piece_size >= 0); + if (piece_size - last_piece_size > 0) + { + large_digest.update( + &piece_data[last_piece_size] + , piece_size - last_piece_size); + } + sha1_hash large_hash = large_digest.final(); + sha1_hash small_hash = small_digest.final(); + + typedef std::multimap::const_iterator map_iter; + map_iter begin1; + map_iter end1; + map_iter begin2; + map_iter end2; + + // makes the lookups for the small digest and the large digest + boost::tie(begin1, end1) = hash_to_piece.equal_range(small_hash); + boost::tie(begin2, end2) = hash_to_piece.equal_range(large_hash); + + // copy all potential piece indices into this vector + std::vector matching_pieces; + for (map_iter i = begin1; i != end1; ++i) + matching_pieces.push_back(i->second); + for (map_iter i = begin2; i != end2; ++i) + matching_pieces.push_back(i->second); + + // no piece matched the data in the slot + if (matching_pieces.empty()) + return unassigned; + + // ------------------------------------------ + // CHECK IF THE PIECE IS IN ITS CORRECT PLACE + // ------------------------------------------ + + if (std::find( + matching_pieces.begin() + , matching_pieces.end() + , current_slot) != matching_pieces.end()) + { + // the current slot is among the matching pieces, so + // we will assume that the piece is in the right place + const int piece_index = current_slot; + + // lock because we're writing to have_pieces + boost::recursive_mutex::scoped_lock l(mutex); + + if (have_pieces[piece_index]) + { + // we have already found a piece with + // this index. + int other_slot = m_piece_to_slot[piece_index]; + assert(other_slot >= 0); + + // take one of the other matching pieces + // that hasn't already been assigned + int other_piece = -1; + for (std::vector::iterator i = matching_pieces.begin(); + i != matching_pieces.end(); ++i) + { + if (have_pieces[*i] || *i == piece_index) continue; + other_piece = *i; + break; + } + if (other_piece >= 0) + { + // replace the old slot with 'other_piece' + assert(have_pieces[other_piece] == false); + have_pieces[other_piece] = true; + m_slot_to_piece[other_slot] = other_piece; + m_piece_to_slot[other_piece] = other_slot; + ++num_pieces; + } + else + { + // this index is the only piece with this + // hash. The previous slot we found with + // this hash must be the same piece. Mark + // that piece as unassigned, since this slot + // is the correct place for the piece. + m_slot_to_piece[other_slot] = unassigned; + m_free_slots.push_back(other_slot); + } + assert(m_piece_to_slot[piece_index] != current_slot); + assert(m_piece_to_slot[piece_index] >= 0); + m_piece_to_slot[piece_index] = has_no_slot; +#ifndef NDEBUG + // to make the assert happy, a few lines down + have_pieces[piece_index] = false; +#endif + } + else + { + ++num_pieces; + } + + assert(have_pieces[piece_index] == false); + assert(m_piece_to_slot[piece_index] == has_no_slot); + have_pieces[piece_index] = true; + + return piece_index; + } + + // find a matching piece that hasn't + // already been assigned + int free_piece = unassigned; + for (std::vector::iterator i = matching_pieces.begin(); + i != matching_pieces.end(); ++i) + { + if (have_pieces[*i]) continue; + free_piece = *i; + break; + } + + if (free_piece >= 0) + { + // lock because we're writing to have_pieces + boost::recursive_mutex::scoped_lock l(mutex); + + assert(have_pieces[free_piece] == false); + assert(m_piece_to_slot[free_piece] == has_no_slot); + have_pieces[free_piece] = true; + ++num_pieces; + + return free_piece; + } + else + { + assert(free_piece == unassigned); + return unassigned; + } + } + + // check if the fastresume data is up to date + // if it is, use it and return true. If it + // isn't return false and the full check + // will be run + bool piece_manager::check_fastresume( + aux::piece_checker_data& data + , std::vector& pieces + , int& num_pieces, bool compact_mode) + { + boost::recursive_mutex::scoped_lock lock(m_mutex); + + INVARIANT_CHECK; + + assert(m_info.piece_length() > 0); + + m_compact_mode = compact_mode; + + // This will corrupt the storage + // use while debugging to find + // states that cannot be scanned + // by check_pieces. +// m_storage->shuffle(); + + m_piece_to_slot.resize(m_info.num_pieces(), has_no_slot); + m_slot_to_piece.resize(m_info.num_pieces(), unallocated); + m_free_slots.clear(); + m_unallocated_slots.clear(); + + pieces.clear(); + pieces.resize(m_info.num_pieces(), false); + num_pieces = 0; + + // if we have fast-resume info + // use it instead of doing the actual checking + if (!data.piece_map.empty() + && data.piece_map.size() <= m_slot_to_piece.size()) + { + for (int i = 0; i < (int)data.piece_map.size(); ++i) + { + m_slot_to_piece[i] = data.piece_map[i]; + if (data.piece_map[i] >= 0) + { + m_piece_to_slot[data.piece_map[i]] = i; + int found_piece = data.piece_map[i]; + + // if the piece is not in the unfinished list + // we have all of it + if (std::find_if( + data.unfinished_pieces.begin() + , data.unfinished_pieces.end() + , piece_picker::has_index(found_piece)) + == data.unfinished_pieces.end()) + { + ++num_pieces; + pieces[found_piece] = true; + } + } + else if (data.piece_map[i] == unassigned) + { + m_free_slots.push_back(i); + } + else + { + assert(data.piece_map[i] == unallocated); + m_unallocated_slots.push_back(i); + } + } + + m_unallocated_slots.reserve(int(pieces.size() - data.piece_map.size())); + for (int i = (int)data.piece_map.size(); i < (int)pieces.size(); ++i) + { + m_unallocated_slots.push_back(i); + } + + if (m_unallocated_slots.empty()) + { + m_state = state_create_files; + return false; + } + + if (m_compact_mode) + { + m_state = state_create_files; + return false; + } + } + + m_state = state_full_check; + return false; + } + +/* + state chart: + + check_fastresume() + + | | + | v + | +------------+ + | | full_check | + | +------------+ + | | + | v + | +------------+ + | | allocating | + | +------------+ + | | + | v + | +--------------+ + |->| create_files | + +--------------+ + | + v + +----------+ + | finished | + +----------+ +*/ + + + // performs the full check and full allocation + // (if necessary). returns true if finished and + // false if it should be called again + // the second return value is the progress the + // file check is at. 0 is nothing done, and 1 + // is finished + std::pair piece_manager::check_files( + std::vector& pieces, int& num_pieces, boost::recursive_mutex& mutex) + { + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + + if (m_state == state_allocating) + { + if (m_compact_mode || m_unallocated_slots.empty()) + { + m_state = state_create_files; + return std::make_pair(false, 1.f); + } + + if (int(m_unallocated_slots.size()) == m_info.num_pieces() + && !m_fill_mode) + { + // if there is not a single file on disk, just + // create the files + m_state = state_create_files; + return std::make_pair(false, 1.f); + } + + // if we're not in compact mode, make sure the + // pieces are spread out and placed at their + // final position. + assert(!m_unallocated_slots.empty()); + + if (!m_fill_mode) + { + // if we're not filling the allocation + // just make sure we move the current pieces + // into place, and just skip all other + // allocation + // allocate_slots returns true if it had to + // move any data + allocate_slots(m_unallocated_slots.size(), true); + } + else + { + allocate_slots(1); + } + + return std::make_pair(false, 1.f - (float)m_unallocated_slots.size() + / (float)m_slot_to_piece.size()); + } + + if (m_state == state_create_files) + { + m_storage->initialize(!m_fill_mode && !m_compact_mode); + + if (!m_unallocated_slots.empty() && !m_compact_mode) + { + assert(!m_fill_mode); + std::vector().swap(m_unallocated_slots); + std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); + m_free_slots.resize(m_info.num_pieces()); + for (int i = 0; i < m_info.num_pieces(); ++i) + m_free_slots[i] = i; + } + + m_state = state_finished; + return std::make_pair(true, 1.f); + } + + assert(m_state == state_full_check); + + // ------------------------ + // DO THE FULL CHECK + // ------------------------ + + try + { + // initialization for the full check + if (m_hash_to_piece.empty()) + { + m_current_slot = 0; + for (int i = 0; i < m_info.num_pieces(); ++i) + { + m_hash_to_piece.insert(std::make_pair(m_info.hash_for_piece(i), i)); + } + std::fill(pieces.begin(), pieces.end(), false); + } + + m_piece_data.resize(int(m_info.piece_length())); + int piece_size = int(m_info.piece_size(m_current_slot)); + int num_read = m_storage->read(&m_piece_data[0] + , m_current_slot, 0, piece_size); + + // if the file is incomplete, skip the rest of it + if (num_read != piece_size) + throw file_error(""); + + int piece_index = identify_data(m_piece_data, m_current_slot + , pieces, num_pieces, m_hash_to_piece, mutex); + + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + assert(piece_index == unassigned || piece_index >= 0); + + const bool this_should_move = piece_index >= 0 && m_slot_to_piece[piece_index] != unallocated; + const bool other_should_move = m_piece_to_slot[m_current_slot] != has_no_slot; + + // check if this piece should be swapped with any other slot + // this section will ensure that the storage is correctly sorted + // libtorrent will never leave the storage in a state that + // requires this sorting, but other clients may. + + // example of worst case: + // | m_current_slot = 5 + // V + // +---+- - - +---+- - - +---+- - + // | x | | 5 | | 3 | <- piece data in slots + // +---+- - - +---+- - - +---+- - + // 3 y 5 <- slot index + + // in this example, the data in the m_current_slot (5) + // is piece 3. It has to be moved into slot 3. The data + // in slot y (piece 5) should be moved into the m_current_slot. + // and the data in slot 3 (piece x) should be moved to slot y. + + // there are three possible cases. + // 1. There's another piece that should be placed into this slot + // 2. This piece should be placed into another slot. + // 3. There's another piece that should be placed into this slot + // and this piece should be placed into another slot + + // swap piece_index with this slot + + // case 1 + if (this_should_move && !other_should_move) + { + assert(piece_index != m_current_slot); + + const int other_slot = piece_index; + assert(other_slot >= 0); + int other_piece = m_slot_to_piece[other_slot]; + + m_slot_to_piece[other_slot] = piece_index; + m_slot_to_piece[m_current_slot] = other_piece; + m_piece_to_slot[piece_index] = piece_index; + if (other_piece >= 0) m_piece_to_slot[other_piece] = m_current_slot; + + if (other_piece == unassigned) + { + std::vector::iterator i = + std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); + assert(i != m_free_slots.end()); + m_free_slots.erase(i); + m_free_slots.push_back(m_current_slot); + } + + if (other_piece >= 0) + m_storage->swap_slots(other_slot, m_current_slot); + else + m_storage->move_slot(m_current_slot, other_slot); + + assert(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + // case 2 + else if (!this_should_move && other_should_move) + { + assert(piece_index != m_current_slot); + + const int other_piece = m_current_slot; + const int other_slot = m_piece_to_slot[other_piece]; + assert(other_slot >= 0); + + m_slot_to_piece[m_current_slot] = other_piece; + m_slot_to_piece[other_slot] = piece_index; + m_piece_to_slot[other_piece] = m_current_slot; + + if (piece_index == unassigned) + m_free_slots.push_back(other_slot); + + if (piece_index >= 0) + { + m_piece_to_slot[piece_index] = other_slot; + m_storage->swap_slots(other_slot, m_current_slot); + } + else + { + m_storage->move_slot(other_slot, m_current_slot); + } + assert(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + else if (this_should_move && other_should_move) + { + assert(piece_index != m_current_slot); + assert(piece_index >= 0); + + const int piece1 = m_slot_to_piece[piece_index]; + const int piece2 = m_current_slot; + const int slot1 = piece_index; + const int slot2 = m_piece_to_slot[piece2]; + + assert(slot1 >= 0); + assert(slot2 >= 0); + assert(piece2 >= 0); + + if (slot1 == slot2) + { + // this means there are only two pieces involved in the swap + assert(piece1 >= 0); + + // movement diagram: + // +-------------------------------+ + // | | + // +--> slot1 --> m_current_slot --+ + + m_slot_to_piece[slot1] = piece_index; + m_slot_to_piece[m_current_slot] = piece1; + + m_piece_to_slot[piece_index] = slot1; + m_piece_to_slot[piece1] = m_current_slot; + + assert(piece1 == m_current_slot); + assert(piece_index == slot1); + + m_storage->swap_slots(m_current_slot, slot1); + + assert(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + else + { + assert(slot1 != slot2); + assert(piece1 != piece2); + + // movement diagram: + // +-----------------------------------------+ + // | | + // +--> slot1 --> slot2 --> m_current_slot --+ + + m_slot_to_piece[slot1] = piece_index; + m_slot_to_piece[slot2] = piece1; + m_slot_to_piece[m_current_slot] = piece2; + + m_piece_to_slot[piece_index] = slot1; + m_piece_to_slot[m_current_slot] = piece2; + + if (piece1 == unassigned) + { + std::vector::iterator i = + std::find(m_free_slots.begin(), m_free_slots.end(), slot1); + assert(i != m_free_slots.end()); + m_free_slots.erase(i); + m_free_slots.push_back(slot2); + } + + if (piece1 >= 0) + { + m_piece_to_slot[piece1] = slot2; + m_storage->swap_slots3(m_current_slot, slot1, slot2); + } + else + { + m_storage->move_slot(m_current_slot, slot1); + m_storage->move_slot(slot2, m_current_slot); + } + + assert(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + } + else + { + assert(m_piece_to_slot[m_current_slot] == has_no_slot || piece_index != m_current_slot); + assert(m_slot_to_piece[m_current_slot] == unallocated); + assert(piece_index == unassigned || m_piece_to_slot[piece_index] == has_no_slot); + + // the slot was identified as piece 'piece_index' + if (piece_index != unassigned) + m_piece_to_slot[piece_index] = m_current_slot; + else + m_free_slots.push_back(m_current_slot); + + m_slot_to_piece[m_current_slot] = piece_index; + + assert(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + } + catch (file_error&) + { + // find the file that failed, and skip all the blocks in that file + size_type file_offset = 0; + size_type current_offset = m_current_slot * m_info.piece_length(); + for (torrent_info::file_iterator i = m_info.begin_files(); + i != m_info.end_files(); ++i) + { + file_offset += i->size; + if (file_offset > current_offset) break; + } + + assert(file_offset > current_offset); + int skip_blocks = static_cast( + (file_offset - current_offset + m_info.piece_length() - 1) + / m_info.piece_length()); + + for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) + { + assert(m_slot_to_piece[i] == unallocated); + m_unallocated_slots.push_back(i); + } + + // current slot will increase by one at the end of the for-loop too + m_current_slot += skip_blocks - 1; + } + ++m_current_slot; + + if (m_current_slot >= m_info.num_pieces()) + { + assert(m_current_slot == m_info.num_pieces()); + + // clear the memory we've been using + std::vector().swap(m_piece_data); + std::multimap().swap(m_hash_to_piece); + m_state = state_allocating; + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + return std::make_pair(false, 1.f); + } + + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + + return std::make_pair(false, (float)m_current_slot / m_info.num_pieces()); + } + + int piece_manager::allocate_slot_for_piece(int piece_index) + { + boost::recursive_mutex::scoped_lock lock(m_mutex); + +// INVARIANT_CHECK; + + assert(piece_index >= 0); + assert(piece_index < (int)m_piece_to_slot.size()); + assert(m_piece_to_slot.size() == m_slot_to_piece.size()); + + int slot_index = m_piece_to_slot[piece_index]; + + if (slot_index != has_no_slot) + { + assert(slot_index >= 0); + assert(slot_index < (int)m_slot_to_piece.size()); + return slot_index; + } + + if (m_free_slots.empty()) + { + allocate_slots(1); + assert(!m_free_slots.empty()); + } + + std::vector::iterator iter( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , piece_index)); + + if (iter == m_free_slots.end()) + { + assert(m_slot_to_piece[piece_index] != unassigned); + assert(!m_free_slots.empty()); + iter = m_free_slots.end() - 1; + + // special case to make sure we don't use the last slot + // when we shouldn't, since it's smaller than ordinary slots + if (*iter == m_info.num_pieces() - 1 && piece_index != *iter) + { + if (m_free_slots.size() == 1) + allocate_slots(1); + assert(m_free_slots.size() > 1); + // assumes that all allocated slots + // are put at the end of the free_slots vector + iter = m_free_slots.end() - 1; + } + } + + slot_index = *iter; + m_free_slots.erase(iter); + + assert(m_slot_to_piece[slot_index] == unassigned); + + m_slot_to_piece[slot_index] = piece_index; + m_piece_to_slot[piece_index] = slot_index; + + // there is another piece already assigned to + // the slot we are interested in, swap positions + if (slot_index != piece_index + && m_slot_to_piece[piece_index] >= 0) + { + +#if !defined(NDEBUG) && defined(TORRENT_STORAGE_DEBUG) + std::stringstream s; + + s << "there is another piece at our slot, swapping.."; + + s << "\n piece_index: "; + s << piece_index; + s << "\n slot_index: "; + s << slot_index; + s << "\n piece at our slot: "; + s << m_slot_to_piece[piece_index]; + s << "\n"; + + print_to_log(s.str()); + debug_log(); +#endif + + int piece_at_our_slot = m_slot_to_piece[piece_index]; + assert(m_piece_to_slot[piece_at_our_slot] == piece_index); + + std::swap( + m_slot_to_piece[piece_index] + , m_slot_to_piece[slot_index]); + + std::swap( + m_piece_to_slot[piece_index] + , m_piece_to_slot[piece_at_our_slot]); + + m_storage->move_slot(piece_index, slot_index); + + assert(m_slot_to_piece[piece_index] == piece_index); + assert(m_piece_to_slot[piece_index] == piece_index); + + slot_index = piece_index; + +#if !defined(NDEBUG) && defined(TORRENT_STORAGE_DEBUG) + debug_log(); +#endif + } + + assert(slot_index >= 0); + assert(slot_index < (int)m_slot_to_piece.size()); + return slot_index; + } + + bool piece_manager::allocate_slots(int num_slots, bool abort_on_disk) + { + assert(num_slots > 0); + + boost::recursive_mutex::scoped_lock lock(m_mutex); + +// INVARIANT_CHECK; + + assert(!m_unallocated_slots.empty()); + + const int stack_buffer_size = 16*1024; + char zeroes[stack_buffer_size]; + memset(zeroes, 0, stack_buffer_size); + + bool written = false; + + for (int i = 0; i < num_slots && !m_unallocated_slots.empty(); ++i) + { +// INVARIANT_CHECK; + + int pos = m_unallocated_slots.front(); + assert(m_slot_to_piece[pos] == unallocated); + assert(m_piece_to_slot[pos] != pos); + + int new_free_slot = pos; + if (m_piece_to_slot[pos] != has_no_slot) + { + new_free_slot = m_piece_to_slot[pos]; + m_storage->move_slot(new_free_slot, pos); + m_slot_to_piece[pos] = pos; + m_piece_to_slot[pos] = pos; + written = true; + } + else if (m_fill_mode) + { + int piece_size = int(m_info.piece_size(pos)); + int offset = 0; + for (; piece_size > 0; piece_size -= stack_buffer_size + , offset += stack_buffer_size) + { + m_storage->write(zeroes, pos, offset + , std::min(piece_size, stack_buffer_size)); + } + written = true; + } + m_unallocated_slots.erase(m_unallocated_slots.begin()); + m_slot_to_piece[new_free_slot] = unassigned; + m_free_slots.push_back(new_free_slot); + if (abort_on_disk && written) return true; + } + + assert(m_free_slots.size() > 0); + return written; + } + +#ifndef NDEBUG + void piece_manager::check_invariant() const + { + boost::recursive_mutex::scoped_lock lock(m_mutex); + if (m_piece_to_slot.empty()) return; + + assert((int)m_piece_to_slot.size() == m_info.num_pieces()); + assert((int)m_slot_to_piece.size() == m_info.num_pieces()); + + for (std::vector::const_iterator i = m_free_slots.begin(); + i != m_free_slots.end(); ++i) + { + assert(*i < (int)m_slot_to_piece.size()); + assert(*i >= 0); + assert(m_slot_to_piece[*i] == unassigned); + assert(std::find(i+1, m_free_slots.end(), *i) + == m_free_slots.end()); + } + + for (std::vector::const_iterator i = m_unallocated_slots.begin(); + i != m_unallocated_slots.end(); ++i) + { + assert(*i < (int)m_slot_to_piece.size()); + assert(*i >= 0); + assert(m_slot_to_piece[*i] == unallocated); + assert(std::find(i+1, m_unallocated_slots.end(), *i) + == m_unallocated_slots.end()); + } + + for (int i = 0; i < m_info.num_pieces(); ++i) + { + // Check domain of piece_to_slot's elements + if (m_piece_to_slot[i] != has_no_slot) + { + assert(m_piece_to_slot[i] >= 0); + assert(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); + } + + // Check domain of slot_to_piece's elements + if (m_slot_to_piece[i] != unallocated + && m_slot_to_piece[i] != unassigned) + { + assert(m_slot_to_piece[i] >= 0); + assert(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + } + + // do more detailed checks on piece_to_slot + if (m_piece_to_slot[i] >= 0) + { + assert(m_slot_to_piece[m_piece_to_slot[i]] == i); + if (m_piece_to_slot[i] != i) + { + assert(m_slot_to_piece[i] == unallocated); + } + } + else + { + assert(m_piece_to_slot[i] == has_no_slot); + } + + // do more detailed checks on slot_to_piece + + if (m_slot_to_piece[i] >= 0) + { + assert(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + assert(m_piece_to_slot[m_slot_to_piece[i]] == i); +#ifdef TORRENT_STORAGE_DEBUG + assert( + std::find( + m_unallocated_slots.begin() + , m_unallocated_slots.end() + , i) == m_unallocated_slots.end() + ); + assert( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , i) == m_free_slots.end() + ); +#endif + } + else if (m_slot_to_piece[i] == unallocated) + { +#ifdef TORRENT_STORAGE_DEBUG + assert(m_unallocated_slots.empty() + || (std::find( + m_unallocated_slots.begin() + , m_unallocated_slots.end() + , i) != m_unallocated_slots.end()) + ); +#endif + } + else if (m_slot_to_piece[i] == unassigned) + { +#ifdef TORRENT_STORAGE_DEBUG + assert( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , i) != m_free_slots.end() + ); +#endif + } + else + { + assert(false && "m_slot_to_piece[i] is invalid"); + } + } + } + +#ifdef TORRENT_STORAGE_DEBUG + void piece_manager::debug_log() const + { + std::stringstream s; + + s << "index\tslot\tpiece\n"; + + for (int i = 0; i < m_info.num_pieces(); ++i) + { + s << i << "\t" << m_slot_to_piece[i] << "\t"; + s << m_piece_to_slot[i] << "\n"; + } + + s << "---------------------------------\n"; + + print_to_log(s.str()); + } +#endif +#endif +} // namespace libtorrent + diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp new file mode 100755 index 000000000..d173f8943 --- /dev/null +++ b/libtorrent/src/torrent.cpp @@ -0,0 +1,2859 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/instantiate_connection.hpp" + +using namespace libtorrent; +using boost::tuples::tuple; +using boost::tuples::get; +using boost::tuples::make_tuple; +using boost::bind; +using boost::mutex; +using libtorrent::aux::session_impl; + +namespace +{ + + enum + { + // wait 60 seconds before retrying a failed tracker + tracker_retry_delay_min = 60 + // when tracker_failed_max trackers + // has failed, wait 10 minutes instead + , tracker_retry_delay_max = 10 * 60 + , tracker_failed_max = 5 + }; + + int calculate_block_size(const torrent_info& i, int default_block_size) + { + if (default_block_size < 1024) default_block_size = 1024; + + // if pieces are too small, adjust the block size + if (i.piece_length() < default_block_size) + { + return static_cast(i.piece_length()); + } + + // otherwise, go with the default + return default_block_size; + } + + struct find_peer_by_ip + { + find_peer_by_ip(tcp::endpoint const& a, const torrent* t) + : ip(a) + , tor(t) + { assert(t != 0); } + + bool operator()(const session_impl::connection_map::value_type& c) const + { + tcp::endpoint sender = c.first->remote_endpoint(); + if (sender.address() != ip.address()) return false; + if (tor != c.second->associated_torrent().lock().get()) return false; + return true; + } + + tcp::endpoint const& ip; + torrent const* tor; + }; + + struct peer_by_id + { + peer_by_id(const peer_id& i): pid(i) {} + + bool operator()(const std::pair& p) const + { + if (p.second->pid() != pid) return false; + // have a special case for all zeros. We can have any number + // of peers with that pid, since it's used to indicate no pid. + if (std::count(pid.begin(), pid.end(), 0) == 20) return false; + return true; + } + + peer_id const& pid; + }; +} + +namespace libtorrent +{ + + torrent::torrent( + session_impl& ses + , aux::checker_impl& checker + , torrent_info const& tf + , fs::path const& save_path + , tcp::endpoint const& net_interface + , bool compact_mode + , int block_size + , session_settings const& s + , storage_constructor_type sc) + : m_torrent_file(tf) + , m_abort(false) + , m_paused(false) + , m_just_paused(false) + , m_event(tracker_request::started) + , m_block_size(0) + , m_storage(0) + , m_next_request(time_now()) + , m_duration(1800) + , m_complete(-1) + , m_incomplete(-1) + , m_host_resolver(ses.m_io_service) +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + , m_resolving_country(false) + , m_resolve_countries(false) +#endif + , m_announce_timer(ses.m_io_service) +#ifndef TORRENT_DISABLE_DHT + , m_last_dht_announce(time_now() - minutes(15)) +#endif + , m_policy() + , m_ses(ses) + , m_checker(checker) + , m_picker(0) + , m_trackers(m_torrent_file.trackers()) + , m_last_working_tracker(-1) + , m_currently_trying_tracker(0) + , m_failed_trackers(0) + , m_time_scaler(0) + , m_num_pieces(0) + , m_sequenced_download_threshold(0) + , m_got_tracker_response(false) + , m_ratio(0.f) + , m_total_failed_bytes(0) + , m_total_redundant_bytes(0) + , m_net_interface(net_interface.address(), 0) + , m_save_path(complete(save_path)) + , m_compact_mode(compact_mode) + , m_default_block_size(block_size) + , m_connections_initialized(true) + , m_settings(s) + , m_storage_constructor(sc) + { +#ifndef NDEBUG + m_initial_done = 0; +#endif + + m_uploads_quota.min = 2; + m_connections_quota.min = 2; + // this will be corrected the next time the main session + // distributes resources, i.e. on average in 0.5 seconds + m_connections_quota.given = 100; + m_uploads_quota.max = std::numeric_limits::max(); + m_connections_quota.max = std::numeric_limits::max(); + m_policy.reset(new policy(this)); + } + + + torrent::torrent( + session_impl& ses + , aux::checker_impl& checker + , char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , tcp::endpoint const& net_interface + , bool compact_mode + , int block_size + , session_settings const& s + , storage_constructor_type sc) + : m_torrent_file(info_hash) + , m_abort(false) + , m_paused(false) + , m_just_paused(false) + , m_event(tracker_request::started) + , m_block_size(0) + , m_storage(0) + , m_next_request(time_now()) + , m_duration(1800) + , m_complete(-1) + , m_incomplete(-1) + , m_host_resolver(ses.m_io_service) +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + , m_resolving_country(false) + , m_resolve_countries(false) +#endif + , m_announce_timer(ses.m_io_service) +#ifndef TORRENT_DISABLE_DHT + , m_last_dht_announce(time_now() - minutes(15)) +#endif + , m_policy() + , m_ses(ses) + , m_checker(checker) + , m_picker(0) + , m_last_working_tracker(-1) + , m_currently_trying_tracker(0) + , m_failed_trackers(0) + , m_time_scaler(0) + , m_num_pieces(0) + , m_sequenced_download_threshold(0) + , m_got_tracker_response(false) + , m_ratio(0.f) + , m_total_failed_bytes(0) + , m_total_redundant_bytes(0) + , m_net_interface(net_interface.address(), 0) + , m_save_path(complete(save_path)) + , m_compact_mode(compact_mode) + , m_default_block_size(block_size) + , m_connections_initialized(false) + , m_settings(s) + , m_storage_constructor(sc) + { +#ifndef NDEBUG + m_initial_done = 0; +#endif + + INVARIANT_CHECK; + + if (name) m_name.reset(new std::string(name)); + + m_uploads_quota.min = 2; + m_connections_quota.min = 2; + // this will be corrected the next time the main session + // distributes resources, i.e. on average in 0.5 seconds + m_connections_quota.given = 100; + m_uploads_quota.max = std::numeric_limits::max(); + m_connections_quota.max = std::numeric_limits::max(); + if (tracker_url) + { + m_trackers.push_back(announce_entry(tracker_url)); + m_torrent_file.add_tracker(tracker_url); + } + + m_policy.reset(new policy(this)); + } + + void torrent::start() + { + boost::weak_ptr self(shared_from_this()); + if (m_torrent_file.is_valid()) init(); + m_announce_timer.expires_from_now(seconds(1)); + m_announce_timer.async_wait(m_ses.m_strand.wrap( + bind(&torrent::on_announce_disp, self, _1))); + } + +#ifndef TORRENT_DISABLE_DHT + bool torrent::should_announce_dht() const + { + // don't announce private torrents + if (m_torrent_file.is_valid() && m_torrent_file.priv()) return false; + + if (m_trackers.empty()) return true; + + return m_failed_trackers > 0 || !m_ses.settings().use_dht_as_fallback; + } +#endif + + torrent::~torrent() + { + // The invariant can't be maintained here, since the torrent + // is being destructed, all weak references to it have been + // reset, which means that all its peers already have an + // invalidated torrent pointer (so it cannot be verified to be correct) + + // i.e. the invariant can only be maintained if all connections have + // been closed by the time the torrent is destructed. And they are + // supposed to be closed. So we can still do the invariant check. + + assert(m_connections.empty()); + + INVARIANT_CHECK; + + assert(m_abort); + if (!m_connections.empty()) + disconnect_all(); + } + + std::string torrent::name() const + { + if (valid_metadata()) return m_torrent_file.name(); + if (m_name) return *m_name; + return ""; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void torrent::add_extension(boost::shared_ptr ext) + { + m_extensions.push_back(ext); + } +#endif + + // this may not be called from a constructor because of the call to + // shared_from_this() + void torrent::init() + { + assert(m_torrent_file.is_valid()); + assert(m_torrent_file.num_files() > 0); + assert(m_torrent_file.total_size() >= 0); + + m_have_pieces.resize(m_torrent_file.num_pieces(), false); + // the shared_from_this() will create an intentional + // cycle of ownership, se the hpp file for description. + m_owning_storage = new piece_manager(shared_from_this(), m_torrent_file + , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor); + m_storage = m_owning_storage.get(); + m_block_size = calculate_block_size(m_torrent_file, m_default_block_size); + m_picker.reset(new piece_picker( + static_cast(m_torrent_file.piece_length() / m_block_size) + , static_cast((m_torrent_file.total_size()+m_block_size-1)/m_block_size))); + + std::vector const& url_seeds = m_torrent_file.url_seeds(); + std::copy(url_seeds.begin(), url_seeds.end(), std::inserter(m_web_seeds + , m_web_seeds.begin())); + } + + void torrent::use_interface(const char* net_interface) + { + INVARIANT_CHECK; + + m_net_interface = tcp::endpoint(address::from_string(net_interface), 0); + } + + void torrent::on_announce_disp(boost::weak_ptr p + , asio::error_code const& e) + { + if (e) return; + boost::shared_ptr t = p.lock(); + if (!t) return; + t->on_announce(); + } + + void torrent::on_announce() +#ifndef NDEBUG + try +#endif + { + boost::weak_ptr self(shared_from_this()); + + // announce on local network every 5 minutes + m_announce_timer.expires_from_now(minutes(5)); + m_announce_timer.async_wait(m_ses.m_strand.wrap( + bind(&torrent::on_announce_disp, self, _1))); + + // announce with the local discovery service + m_ses.announce_lsd(m_torrent_file.info_hash()); + +#ifndef TORRENT_DISABLE_DHT + if (!m_ses.m_dht) return; + ptime now = time_now(); + if (should_announce_dht() && now - m_last_dht_announce > minutes(14)) + { + m_last_dht_announce = now; + // TODO: There should be a way to abort an announce operation on the dht. + // when the torrent is destructed + assert(m_ses.m_external_listen_port > 0); + m_ses.m_dht->announce(m_torrent_file.info_hash() + , m_ses.m_external_listen_port + , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); + } +#endif + } +#ifndef NDEBUG + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + assert(false); + }; +#endif + +#ifndef TORRENT_DISABLE_DHT + + void torrent::on_dht_announce_response_disp(boost::weak_ptr t + , std::vector const& peers) + { + boost::shared_ptr tor = t.lock(); + if (!tor) return; + tor->on_dht_announce_response(peers); + } + + void torrent::on_dht_announce_response(std::vector const& peers) + { + if (peers.empty()) return; + + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(tracker_reply_alert( + get_handle(), peers.size(), "Got peers from DHT")); + } + std::for_each(peers.begin(), peers.end(), bind( + &policy::peer_from_tracker, boost::ref(m_policy), _1, peer_id(0) + , peer_info::dht, 0)); + } + +#endif + + // returns true if it is time for this torrent to make another + // tracker request + bool torrent::should_request() + { + INVARIANT_CHECK; + + if (m_torrent_file.trackers().empty()) return false; + + if (m_just_paused) + { + m_just_paused = false; + return true; + } + return !m_paused && m_next_request < time_now(); + } + + void torrent::tracker_warning(std::string const& msg) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (m_ses.m_alerts.should_post(alert::warning)) + { + m_ses.m_alerts.post_alert(tracker_warning_alert(get_handle(), msg)); + } + } + + void torrent::tracker_response( + tracker_request const& + , std::vector& peer_list + , int interval + , int complete + , int incomplete) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + m_failed_trackers = 0; + // announce intervals less than 5 minutes + // are insane. + if (interval < 60 * 5) interval = 60 * 5; + + m_last_working_tracker + = prioritize_tracker(m_currently_trying_tracker); + m_currently_trying_tracker = 0; + + m_duration = interval; + m_next_request = time_now() + seconds(m_duration); + + if (complete >= 0) m_complete = complete; + if (incomplete >= 0) m_incomplete = incomplete; + + // connect to random peers from the list + std::random_shuffle(peer_list.begin(), peer_list.end()); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::stringstream s; + s << "TRACKER RESPONSE:\n" + "interval: " << m_duration << "\n" + "peers:\n"; + for (std::vector::const_iterator i = peer_list.begin(); + i != peer_list.end(); ++i) + { + s << " " << std::setfill(' ') << std::setw(16) << i->ip + << " " << std::setw(5) << std::dec << i->port << " "; + if (!i->pid.is_all_zeros()) s << " " << i->pid << " " << identify_client(i->pid); + s << "\n"; + } + debug_log(s.str()); +#endif + // for each of the peers we got from the tracker + for (std::vector::iterator i = peer_list.begin(); + i != peer_list.end(); ++i) + { + // don't make connections to ourself + if (i->pid == m_ses.get_peer_id()) + continue; + + try + { + tcp::endpoint a(address::from_string(i->ip), i->port); + + if (m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + debug_log("blocked ip from tracker: " + i->ip); +#endif + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(peer_blocked_alert(a.address() + , "peer from tracker blocked by IP filter")); + } + + continue; + } + + m_policy->peer_from_tracker(a, i->pid, peer_info::tracker, 0); + } + catch (std::exception&) + { + // assume this is because we got a hostname instead of + // an ip address from the tracker + + tcp::resolver::query q(i->ip, boost::lexical_cast(i->port)); + m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( + bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i->pid))); + } + } + + if (m_ses.m_alerts.should_post(alert::info)) + { + std::stringstream s; + s << "Got response from tracker: " + << m_trackers[m_last_working_tracker].url; + m_ses.m_alerts.post_alert(tracker_reply_alert( + get_handle(), peer_list.size(), s.str())); + } + m_got_tracker_response = true; + } + + void torrent::on_peer_name_lookup(asio::error_code const& e, tcp::resolver::iterator host + , peer_id pid) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (e || host == tcp::resolver::iterator() || + m_ses.is_aborted()) return; + + if (m_ses.m_ip_filter.access(host->endpoint().address()) & ip_filter::blocked) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + debug_log("blocked ip from tracker: " + host->endpoint().address().to_string()); +#endif + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(peer_blocked_alert(host->endpoint().address() + , "peer from tracker blocked by IP filter")); + } + + return; + } + + m_policy->peer_from_tracker(*host, pid, peer_info::tracker, 0); + } + catch (std::exception&) + {}; + + size_type torrent::bytes_left() const + { + // if we don't have the metadata yet, we + // cannot tell how big the torrent is. + if (!valid_metadata()) return -1; + return m_torrent_file.total_size() + - quantized_bytes_done(); + } + + size_type torrent::quantized_bytes_done() const + { +// INVARIANT_CHECK; + + if (!valid_metadata()) return 0; + + if (m_torrent_file.num_pieces() == 0) + return 0; + + if (is_seed()) return m_torrent_file.total_size(); + + const int last_piece = m_torrent_file.num_pieces() - 1; + + size_type total_done + = m_num_pieces * m_torrent_file.piece_length(); + + // if we have the last piece, we have to correct + // the amount we have, since the first calculation + // assumed all pieces were of equal size + if (m_have_pieces[last_piece]) + { + int corr = m_torrent_file.piece_size(last_piece) + - m_torrent_file.piece_length(); + total_done += corr; + } + return total_done; + } + + // the first value is the total number of bytes downloaded + // the second value is the number of bytes of those that haven't + // been filtered as not wanted we have downloaded + tuple torrent::bytes_done() const + { + INVARIANT_CHECK; + + if (!valid_metadata() || m_torrent_file.num_pieces() == 0) + return tuple(0,0); + + const int last_piece = m_torrent_file.num_pieces() - 1; + + if (is_seed()) + return make_tuple(m_torrent_file.total_size() + , m_torrent_file.total_size()); + + size_type wanted_done = (m_num_pieces - m_picker->num_have_filtered()) + * m_torrent_file.piece_length(); + + size_type total_done + = m_num_pieces * m_torrent_file.piece_length(); + assert(m_num_pieces < m_torrent_file.num_pieces()); + + // if we have the last piece, we have to correct + // the amount we have, since the first calculation + // assumed all pieces were of equal size + if (m_have_pieces[last_piece]) + { + int corr = m_torrent_file.piece_size(last_piece) + - m_torrent_file.piece_length(); + total_done += corr; + if (m_picker->piece_priority(last_piece) != 0) + wanted_done += corr; + } + + assert(total_done <= m_torrent_file.total_size()); + assert(wanted_done <= m_torrent_file.total_size()); + + const std::vector& dl_queue + = m_picker->get_download_queue(); + + const int blocks_per_piece = static_cast( + m_torrent_file.piece_length() / m_block_size); + + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + int corr = 0; + int index = i->index; + assert(!m_have_pieces[index]); + assert(i->finished <= m_picker->blocks_in_piece(index)); + +#ifndef NDEBUG + for (std::vector::const_iterator j = boost::next(i); + j != dl_queue.end(); ++j) + { + assert(j->index != index); + } +#endif + + for (int j = 0; j < blocks_per_piece; ++j) + { + assert(m_picker->is_finished(piece_block(index, j)) == (i->info[j].state == piece_picker::block_info::state_finished)); + corr += (i->info[j].state == piece_picker::block_info::state_finished) * m_block_size; + assert(index != last_piece || j < m_picker->blocks_in_last_piece() + || i->info[j].state != piece_picker::block_info::state_finished); + } + + // correction if this was the last piece + // and if we have the last block + if (i->index == last_piece + && i->info[m_picker->blocks_in_last_piece()-1].state + == piece_picker::block_info::state_finished) + { + corr -= m_block_size; + corr += m_torrent_file.piece_size(last_piece) % m_block_size; + } + total_done += corr; + if (m_picker->piece_priority(index) != 0) + wanted_done += corr; + } + + assert(total_done <= m_torrent_file.total_size()); + assert(wanted_done <= m_torrent_file.total_size()); + + std::map downloading_piece; + for (const_peer_iterator i = begin(); i != end(); ++i) + { + peer_connection* pc = i->second; + boost::optional p + = pc->downloading_piece_progress(); + if (p) + { + if (m_have_pieces[p->piece_index]) + continue; + + piece_block block(p->piece_index, p->block_index); + if (m_picker->is_finished(block)) + continue; + + std::map::iterator dp + = downloading_piece.find(block); + if (dp != downloading_piece.end()) + { + if (dp->second < p->bytes_downloaded) + dp->second = p->bytes_downloaded; + } + else + { + downloading_piece[block] = p->bytes_downloaded; + } +#ifndef NDEBUG + assert(p->bytes_downloaded <= p->full_block_bytes); + int last_piece = m_torrent_file.num_pieces() - 1; + if (p->piece_index == last_piece + && p->block_index == m_torrent_file.piece_size(last_piece) / block_size()) + assert(p->full_block_bytes == m_torrent_file.piece_size(last_piece) % block_size()); + else + assert(p->full_block_bytes == block_size()); +#endif + } + } + for (std::map::iterator i = downloading_piece.begin(); + i != downloading_piece.end(); ++i) + { + total_done += i->second; + if (m_picker->piece_priority(i->first.piece_index) != 0) + wanted_done += i->second; + } + +#ifndef NDEBUG + + if (total_done >= m_torrent_file.total_size()) + { + std::copy(m_have_pieces.begin(), m_have_pieces.end() + , std::ostream_iterator(std::cerr, " ")); + std::cerr << std::endl; + std::cerr << "num_pieces: " << m_num_pieces << std::endl; + + std::cerr << "unfinished:" << std::endl; + + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + std::cerr << " " << i->index << " "; + for (int j = 0; j < blocks_per_piece; ++j) + { + std::cerr << (i->info[j].state == piece_picker::block_info::state_finished ? "1" : "0"); + } + std::cerr << std::endl; + } + + std::cerr << "downloading pieces:" << std::endl; + + for (std::map::iterator i = downloading_piece.begin(); + i != downloading_piece.end(); ++i) + { + std::cerr << " " << i->first.piece_index << ":" << i->first.block_index + << " " << i->second << std::endl; + } + + } + + assert(total_done <= m_torrent_file.total_size()); + assert(wanted_done <= m_torrent_file.total_size()); + +#endif + + assert(total_done >= wanted_done); + return make_tuple(total_done, wanted_done); + } + + void torrent::piece_finished(int index, bool passed_hash_check) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + bool was_seed = is_seed(); + bool was_finished = m_picker->num_filtered() + num_pieces() + == torrent_file().num_pieces(); + + if (passed_hash_check) + { + // the following call may cause picker to become invalid + // in case we just became a seed + announce_piece(index); + assert(valid_metadata()); + // if we just became a seed, picker is now invalid, since it + // is deallocated by the torrent once it starts seeding + if (!was_finished + && (is_seed() + || m_picker->num_filtered() + num_pieces() + == torrent_file().num_pieces())) + { + // torrent finished + // i.e. all the pieces we're interested in have + // been downloaded. Release the files (they will open + // in read only mode if needed) + try { finished(); } + catch (std::exception& e) + { +#ifndef NDEBUG + std::cerr << e.what() << std::endl; + assert(false); +#endif + } + } + } + else + { + piece_failed(index); + } + +#ifndef NDEBUG + try + { +#endif + + m_policy->piece_finished(index, passed_hash_check); + +#ifndef NDEBUG + } + catch (std::exception const& e) + { + std::cerr << e.what() << std::endl; + assert(false); + } +#endif + +#ifndef NDEBUG + try + { +#endif + + if (!was_seed && is_seed()) + { + assert(passed_hash_check); + completed(); + } + +#ifndef NDEBUG + } + catch (std::exception const& e) + { + std::cerr << e.what() << std::endl; + assert(false); + } +#endif + + } + + void torrent::piece_failed(int index) + { + // if the last piece fails the peer connection will still + // think that it has received all of it until this function + // resets the download queue. So, we cannot do the + // invariant check here since it assumes: + // (total_done == m_torrent_file.total_size()) => is_seed() +// INVARIANT_CHECK; + + assert(m_storage); + assert(m_storage->refcount() > 0); + assert(m_picker.get()); + assert(index >= 0); + assert(index < m_torrent_file.num_pieces()); + + if (m_ses.m_alerts.should_post(alert::info)) + { + std::stringstream s; + s << "hash for piece " << index << " failed"; + m_ses.m_alerts.post_alert(hash_failed_alert(get_handle(), index, s.str())); + } + // increase the total amount of failed bytes + m_total_failed_bytes += m_torrent_file.piece_size(index); + + std::vector downloaders; + m_picker->get_downloaders(downloaders, index); + + // decrease the trust point of all peers that sent + // parts of this piece. + // first, build a set of all peers that participated + std::set peers; + std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { (*i)->on_piece_failed(index); } catch (std::exception&) {} + } +#endif + + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_iterator p = m_connections.find(*i); + if (p == m_connections.end()) continue; + peer_connection& peer = *p->second; + peer.received_invalid_data(index); + + // either, we have received too many failed hashes + // or this was the only peer that sent us this piece. + // TODO: make this a changable setting + if ((peer.peer_info_struct() + && peer.peer_info_struct()->trust_points <= -7) + || peers.size() == 1) + { + // we don't trust this peer anymore + // ban it. + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(peer_ban_alert( + p->first + , get_handle() + , "banning peer because of too many corrupt pieces")); + } + + // mark the peer as banned + policy::peer* peerinfo = p->second->peer_info_struct(); + if (peerinfo) + { + peerinfo->banned = true; + } + else + { + // it might be a web seed + if (web_peer_connection const* wpc + = dynamic_cast(p->second)) + { + remove_url_seed(wpc->url()); + } + } + +#if defined(TORRENT_VERBOSE_LOGGING) + (*p->second->m_logger) << "*** BANNING PEER 'too many corrupt pieces'\n"; +#endif + p->second->disconnect(); + } + } + + // we have to let the piece_picker know that + // this piece failed the check as it can restore it + // and mark it as being interesting for download + // TODO: do this more intelligently! and keep track + // of how much crap (data that failed hash-check) and + // how much redundant data we have downloaded + // if some clients has sent more than one piece + // start with redownloading the pieces that the client + // that has sent the least number of pieces + m_picker->restore_piece(index); + assert(m_storage); + m_storage->mark_failed(index); + + assert(m_have_pieces[index] == false); + } + + void torrent::abort() + { + INVARIANT_CHECK; + + m_abort = true; + // if the torrent is paused, it doesn't need + // to announce with even=stopped again. + if (!m_paused) + m_event = tracker_request::stopped; + // disconnect all peers and close all + // files belonging to the torrents + disconnect_all(); + if (m_owning_storage.get()) m_storage->async_release_files(); + m_owning_storage = 0; + } + + void torrent::on_files_released(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(torrent_paused_alert(get_handle(), "torrent paused")); + } + } + + void torrent::announce_piece(int index) + { +// INVARIANT_CHECK; + + assert(index >= 0); + assert(index < m_torrent_file.num_pieces()); + + std::vector downloaders; + m_picker->get_downloaders(downloaders, index); + + // increase the trust point of all peers that sent + // parts of this piece. + std::set peers; + std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); + + if (!m_have_pieces[index]) + m_num_pieces++; + m_have_pieces[index] = true; + + assert(std::accumulate(m_have_pieces.begin(), m_have_pieces.end(), 0) + == m_num_pieces); + + m_picker->we_have(index); + for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) + try { i->second->announce_piece(index); } catch (std::exception&) {} + + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_iterator p = m_connections.find(*i); + if (p == m_connections.end()) continue; + p->second->received_valid_data(index); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { (*i)->on_piece_pass(index); } catch (std::exception&) {} + } +#endif + if (is_seed()) + { + m_picker.reset(); + m_torrent_file.seed_free(); + } + } + + std::string torrent::tracker_login() const + { + if (m_username.empty() && m_password.empty()) return ""; + return m_username + ":" + m_password; + } + + void torrent::piece_availability(std::vector& avail) const + { + INVARIANT_CHECK; + + assert(valid_metadata()); + if (is_seed()) + { + avail.clear(); + return; + } + + m_picker->get_availability(avail); + } + + void torrent::set_piece_priority(int index, int priority) + { + INVARIANT_CHECK; + + assert(valid_metadata()); + if (is_seed()) return; + + // this call is only valid on torrents with metadata + assert(m_picker.get()); + assert(index >= 0); + assert(index < m_torrent_file.num_pieces()); + + m_picker->set_piece_priority(index, priority); + update_peer_interest(); + } + + int torrent::piece_priority(int index) const + { + INVARIANT_CHECK; + + assert(valid_metadata()); + if (is_seed()) return 1; + + // this call is only valid on torrents with metadata + assert(m_picker.get()); + assert(index >= 0); + assert(index < m_torrent_file.num_pieces()); + + return m_picker->piece_priority(index); + } + + void torrent::prioritize_pieces(std::vector const& pieces) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + assert(valid_metadata()); + if (is_seed()) return; + + assert(m_picker.get()); + + int index = 0; + for (std::vector::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i, ++index) + { + assert(*i >= 0); + assert(*i <= 7); + m_picker->set_piece_priority(index, *i); + } + update_peer_interest(); + } + + void torrent::piece_priorities(std::vector& pieces) const + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + assert(valid_metadata()); + if (is_seed()) + { + pieces.clear(); + pieces.resize(m_torrent_file.num_pieces(), 1); + return; + } + + assert(m_picker.get()); + m_picker->piece_priorities(pieces); + } + + namespace + { + void set_if_greater(int& piece_prio, int file_prio) + { + if (file_prio > piece_prio) piece_prio = file_prio; + } + } + + void torrent::prioritize_files(std::vector const& files) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + if (!valid_metadata() || is_seed()) return; + + // the bitmask need to have exactly one bit for every file + // in the torrent + assert(int(files.size()) == m_torrent_file.num_files()); + + size_type position = 0; + + if (m_torrent_file.num_pieces() == 0) return; + + int piece_length = m_torrent_file.piece_length(); + // initialize the piece priorities to 0, then only allow + // setting higher priorities + std::vector pieces(m_torrent_file.num_pieces(), 0); + for (int i = 0; i < int(files.size()); ++i) + { + size_type start = position; + size_type size = m_torrent_file.file_at(i).size; + if (size == 0) continue; + position += size; + // mark all pieces of the file with this file's priority + // but only if the priority is higher than the pieces + // already set (to avoid problems with overlapping pieces) + int start_piece = int(start / piece_length); + int last_piece = int((position - 1) / piece_length); + assert(last_piece <= int(pieces.size())); + // if one piece spans several files, we might + // come here several times with the same start_piece, end_piece + std::for_each(pieces.begin() + start_piece + , pieces.begin() + last_piece + 1 + , bind(&set_if_greater, _1, files[i])); + } + prioritize_pieces(pieces); + update_peer_interest(); + } + + // updates the interested flag in peers + void torrent::update_peer_interest() + { + for (peer_iterator i = begin(); i != end(); ++i) + i->second->update_interest(); + } + + void torrent::filter_piece(int index, bool filter) + { + INVARIANT_CHECK; + + assert(valid_metadata()); + if (is_seed()) return; + + // this call is only valid on torrents with metadata + assert(m_picker.get()); + assert(index >= 0); + assert(index < m_torrent_file.num_pieces()); + + m_picker->set_piece_priority(index, filter ? 1 : 0); + update_peer_interest(); + } + + void torrent::filter_pieces(std::vector const& bitmask) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + assert(valid_metadata()); + if (is_seed()) return; + + assert(m_picker.get()); + + int index = 0; + for (std::vector::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if ((m_picker->piece_priority(index) == 0) == *i) continue; + if (*i) + m_picker->set_piece_priority(index, 0); + else + m_picker->set_piece_priority(index, 1); + } + update_peer_interest(); + } + + bool torrent::is_piece_filtered(int index) const + { + // this call is only valid on torrents with metadata + assert(valid_metadata()); + if (is_seed()) return false; + + assert(m_picker.get()); + assert(index >= 0); + assert(index < m_torrent_file.num_pieces()); + + return m_picker->piece_priority(index) == 0; + } + + void torrent::filtered_pieces(std::vector& bitmask) const + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + assert(valid_metadata()); + if (is_seed()) + { + bitmask.clear(); + bitmask.resize(m_torrent_file.num_pieces(), false); + return; + } + + assert(m_picker.get()); + m_picker->filtered_pieces(bitmask); + } + + void torrent::filter_files(std::vector const& bitmask) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + if (!valid_metadata() || is_seed()) return; + + // the bitmask need to have exactly one bit for every file + // in the torrent + assert((int)bitmask.size() == m_torrent_file.num_files()); + + size_type position = 0; + + if (m_torrent_file.num_pieces()) + { + int piece_length = m_torrent_file.piece_length(); + // mark all pieces as filtered, then clear the bits for files + // that should be downloaded + std::vector piece_filter(m_torrent_file.num_pieces(), true); + for (int i = 0; i < (int)bitmask.size(); ++i) + { + size_type start = position; + position += m_torrent_file.file_at(i).size; + // is the file selected for download? + if (!bitmask[i]) + { + // mark all pieces of the file as downloadable + int start_piece = int(start / piece_length); + int last_piece = int(position / piece_length); + // if one piece spans several files, we might + // come here several times with the same start_piece, end_piece + std::fill(piece_filter.begin() + start_piece, piece_filter.begin() + + last_piece + 1, false); + } + } + filter_pieces(piece_filter); + } + } + + void torrent::replace_trackers(std::vector const& urls) + { + assert(!urls.empty()); + m_trackers = urls; + if (m_currently_trying_tracker >= (int)m_trackers.size()) + m_currently_trying_tracker = (int)m_trackers.size()-1; + m_last_working_tracker = -1; + } + + tracker_request torrent::generate_tracker_request() + { + INVARIANT_CHECK; + + assert(!m_trackers.empty()); + + m_next_request = time_now() + seconds(tracker_retry_delay_max); + + tracker_request req; + req.info_hash = m_torrent_file.info_hash(); + req.pid = m_ses.get_peer_id(); + req.downloaded = m_stat.total_payload_download(); + req.uploaded = m_stat.total_payload_upload(); + req.left = bytes_left(); + if (req.left == -1) req.left = 16*1024; + req.event = m_event; + + if (m_event != tracker_request::stopped) + m_event = tracker_request::none; + req.url = m_trackers[m_currently_trying_tracker].url; + req.num_want = m_settings.num_want; + // if we are aborting. we don't want any new peers + if (req.event == tracker_request::stopped) + req.num_want = 0; + + // default initialize, these should be set by caller + // before passing the request to the tracker_manager + req.listen_port = 0; + req.key = 0; + + return req; + } + + void torrent::remove_peer(peer_connection* p) try + { + INVARIANT_CHECK; + + assert(p != 0); + + peer_iterator i = m_connections.find(p->remote()); + if (i == m_connections.end()) + { + assert(false); + return; + } + + if (ready_for_connections()) + { + assert(p->associated_torrent().lock().get() == this); + + if (p->is_seed()) + { + if (m_picker.get()) + { + assert(!is_seed()); + m_picker->dec_refcount_all(); + } + } + else + { + // if we're a seed, we don't keep track of piece availability + if (!is_seed()) + { + const std::vector& pieces = p->get_bitfield(); + + for (std::vector::const_iterator i = pieces.begin(); + i != pieces.end(); ++i) + { + if (*i) peer_lost(static_cast(i - pieces.begin())); + } + } + } + } + + m_policy->connection_closed(*p); + p->set_peer_info(0); + m_connections.erase(i); +#ifndef NDEBUG + m_policy->check_invariant(); +#endif + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::string err = e.what(); +#endif + assert(false); + }; + + void torrent::connect_to_url_seed(std::string const& url) + { + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << time_now_string() << " resolving: " << url << "\n"; +#endif + + m_resolving_web_seeds.insert(url); + proxy_settings const& ps = m_ses.web_seed_proxy(); + if (ps.type == proxy_settings::http + || ps.type == proxy_settings::http_pw) + { + // use proxy + tcp::resolver::query q(ps.hostname + , boost::lexical_cast(ps.port)); + m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( + bind(&torrent::on_proxy_name_lookup, shared_from_this(), _1, _2, url))); + } + else + { + std::string protocol; + std::string auth; + std::string hostname; + int port; + std::string path; + boost::tie(protocol, auth, hostname, port, path) + = parse_url_components(url); + + // TODO: should auth be used here? + + tcp::resolver::query q(hostname, boost::lexical_cast(port)); + m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( + bind(&torrent::on_name_lookup, shared_from_this(), _1, _2, url + , tcp::endpoint()))); + } + + } + + void torrent::on_proxy_name_lookup(asio::error_code const& e, tcp::resolver::iterator host + , std::string url) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << time_now_string() << " completed resolve proxy hostname for: " << url << "\n"; +#endif + + if (e || host == tcp::resolver::iterator()) + { + if (m_ses.m_alerts.should_post(alert::warning)) + { + std::stringstream msg; + msg << "HTTP seed proxy hostname lookup failed: " << e.message(); + m_ses.m_alerts.post_alert( + url_seed_alert(get_handle(), url, msg.str())); + } + + // the name lookup failed for the http host. Don't try + // this host again + remove_url_seed(url); + return; + } + + if (m_ses.is_aborted()) return; + + tcp::endpoint a(host->endpoint()); + + using boost::tuples::ignore; + std::string hostname; + int port; + boost::tie(ignore, ignore, hostname, port, ignore) + = parse_url_components(url); + + if (m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) + { + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(peer_blocked_alert(a.address() + , "proxy (" + hostname + ") blocked by IP filter")); + } + return; + } + + tcp::resolver::query q(hostname, boost::lexical_cast(port)); + m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( + bind(&torrent::on_name_lookup, shared_from_this(), _1, _2, url, a))); + } + catch (std::exception& exc) + { + assert(false); + }; + + void torrent::on_name_lookup(asio::error_code const& e, tcp::resolver::iterator host + , std::string url, tcp::endpoint proxy) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << time_now_string() << " completed resolve: " << url << "\n"; +#endif + + std::set::iterator i = m_resolving_web_seeds.find(url); + if (i != m_resolving_web_seeds.end()) m_resolving_web_seeds.erase(i); + + if (e || host == tcp::resolver::iterator()) + { + if (m_ses.m_alerts.should_post(alert::warning)) + { + std::stringstream msg; + msg << "HTTP seed hostname lookup failed: " << e.message(); + m_ses.m_alerts.post_alert( + url_seed_alert(get_handle(), url, msg.str())); + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << " ** HOSTNAME LOOKUP FAILED!**: " << url << "\n"; +#endif + + // the name lookup failed for the http host. Don't try + // this host again + remove_url_seed(url); + return; + } + + if (m_ses.is_aborted()) return; + + tcp::endpoint a(host->endpoint()); + + if (m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) + { + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(peer_blocked_alert(a.address() + , "web seed (" + url + ") blocked by IP filter")); + } + return; + } + + peer_iterator conn = m_connections.find(a); + if (conn != m_connections.end()) + { + if (dynamic_cast(conn->second) == 0 + || conn->second->is_disconnecting()) conn->second->disconnect(); + else return; + } + + boost::shared_ptr s + = instantiate_connection(m_ses.m_io_service, m_ses.web_seed_proxy()); + if (m_ses.web_seed_proxy().type == proxy_settings::http + || m_ses.web_seed_proxy().type == proxy_settings::http_pw) + { + // the web seed connection will talk immediately to + // the proxy, without requiring CONNECT support + s->get().set_no_connect(true); + } + boost::intrusive_ptr c(new web_peer_connection( + m_ses, shared_from_this(), s, a, url, 0)); + +#ifndef NDEBUG + c->m_in_constructor = false; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr pp((*i)->new_connection(c.get())); + if (pp) c->add_extension(pp); + } +#endif + + try + { + assert(m_connections.find(a) == m_connections.end()); + + // add the newly connected peer to this torrent's peer list + m_connections.insert( + std::make_pair(a, boost::get_pointer(c))); + m_ses.m_connections.insert(std::make_pair(s, c)); + + m_ses.m_half_open.enqueue( + bind(&peer_connection::connect, c, _1) + , bind(&peer_connection::timed_out, c) + , seconds(settings().peer_connect_timeout)); + } + catch (std::exception& e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << " ** HOSTNAME LOOKUP FAILED!**: " << e.what() << "\n"; +#endif + + // TODO: post an error alert! + std::map::iterator i = m_connections.find(a); + if (i != m_connections.end()) m_connections.erase(i); + m_ses.connection_failed(s, a, e.what()); + c->disconnect(); + } + } + catch (std::exception& exc) + { +#ifndef NDEBUG + std::cerr << exc.what() << std::endl; +#endif + assert(false); + }; + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void torrent::resolve_peer_country(boost::intrusive_ptr const& p) const + { + if (m_resolving_country + || p->has_country() + || p->is_connecting() + || p->is_queued() + || p->in_handshake()) return; + + m_resolving_country = true; + tcp::resolver::query q(boost::lexical_cast(p->remote().address()) + + ".zz.countries.nerd.dk", "0"); + m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( + bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p))); + } + + namespace + { + struct country_entry + { + int code; + char const* name; + }; + } + + void torrent::on_country_lookup(asio::error_code const& error, tcp::resolver::iterator i + , intrusive_ptr p) const + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + m_resolving_country = false; + + // must be ordered in increasing order + country_entry country_map[] = + { + { 4, "AF"}, { 8, "AL"}, { 10, "AQ"}, { 12, "DZ"}, { 16, "AS"} + , { 20, "AD"}, { 24, "AO"}, { 28, "AG"}, { 31, "AZ"}, { 32, "AR"} + , { 36, "AU"}, { 40, "AT"}, { 44, "BS"}, { 48, "BH"}, { 50, "BD"} + , { 51, "AM"}, { 52, "BB"}, { 56, "BE"}, { 60, "BM"}, { 64, "BT"} + , { 68, "BO"}, { 70, "BA"}, { 72, "BW"}, { 74, "BV"}, { 76, "BR"} + , { 84, "BZ"}, { 86, "IO"}, { 90, "SB"}, { 92, "VG"}, { 96, "BN"} + , {100, "BG"}, {104, "MM"}, {108, "BI"}, {112, "BY"}, {116, "KH"} + , {120, "CM"}, {124, "CA"}, {132, "CV"}, {136, "KY"}, {140, "CF"} + , {144, "LK"}, {148, "TD"}, {152, "CL"}, {156, "CN"}, {158, "TW"} + , {162, "CX"}, {166, "CC"}, {170, "CO"}, {174, "KM"}, {175, "YT"} + , {178, "CG"}, {180, "CD"}, {184, "CK"}, {188, "CR"}, {191, "HR"} + , {192, "CU"}, {203, "CZ"}, {204, "BJ"}, {208, "DK"}, {212, "DM"} + , {214, "DO"}, {218, "EC"}, {222, "SV"}, {226, "GQ"}, {231, "ET"} + , {232, "ER"}, {233, "EE"}, {234, "FO"}, {238, "FK"}, {239, "GS"} + , {242, "FJ"}, {246, "FI"}, {248, "AX"}, {250, "FR"}, {254, "GF"} + , {258, "PF"}, {260, "TF"}, {262, "DJ"}, {266, "GA"}, {268, "GE"} + , {270, "GM"}, {275, "PS"}, {276, "DE"}, {288, "GH"}, {292, "GI"} + , {296, "KI"}, {300, "GR"}, {304, "GL"}, {308, "GD"}, {312, "GP"} + , {316, "GU"}, {320, "GT"}, {324, "GN"}, {328, "GY"}, {332, "HT"} + , {334, "HM"}, {336, "VA"}, {340, "HN"}, {344, "HK"}, {348, "HU"} + , {352, "IS"}, {356, "IN"}, {360, "ID"}, {364, "IR"}, {368, "IQ"} + , {372, "IE"}, {376, "IL"}, {380, "IT"}, {384, "CI"}, {388, "JM"} + , {392, "JP"}, {398, "KZ"}, {400, "JO"}, {404, "KE"}, {408, "KP"} + , {410, "KR"}, {414, "KW"}, {417, "KG"}, {418, "LA"}, {422, "LB"} + , {426, "LS"}, {428, "LV"}, {430, "LR"}, {434, "LY"}, {438, "LI"} + , {440, "LT"}, {442, "LU"}, {446, "MO"}, {450, "MG"}, {454, "MW"} + , {458, "MY"}, {462, "MV"}, {466, "ML"}, {470, "MT"}, {474, "MQ"} + , {478, "MR"}, {480, "MU"}, {484, "MX"}, {492, "MC"}, {496, "MN"} + , {498, "MD"}, {500, "MS"}, {504, "MA"}, {508, "MZ"}, {512, "OM"} + , {516, "NA"}, {520, "NR"}, {524, "NP"}, {528, "NL"}, {530, "AN"} + , {533, "AW"}, {540, "NC"}, {548, "VU"}, {554, "NZ"}, {558, "NI"} + , {562, "NE"}, {566, "NG"}, {570, "NU"}, {574, "NF"}, {578, "NO"} + , {580, "MP"}, {581, "UM"}, {583, "FM"}, {584, "MH"}, {585, "PW"} + , {586, "PK"}, {591, "PA"}, {598, "PG"}, {600, "PY"}, {604, "PE"} + , {608, "PH"}, {612, "PN"}, {616, "PL"}, {620, "PT"}, {624, "GW"} + , {626, "TL"}, {630, "PR"}, {634, "QA"}, {634, "QA"}, {638, "RE"} + , {642, "RO"}, {643, "RU"}, {646, "RW"}, {654, "SH"}, {659, "KN"} + , {660, "AI"}, {662, "LC"}, {666, "PM"}, {670, "VC"}, {674, "SM"} + , {678, "ST"}, {682, "SA"}, {686, "SN"}, {690, "SC"}, {694, "SL"} + , {702, "SG"}, {703, "SK"}, {704, "VN"}, {705, "SI"}, {706, "SO"} + , {710, "ZA"}, {716, "ZW"}, {724, "ES"}, {732, "EH"}, {736, "SD"} + , {740, "SR"}, {744, "SJ"}, {748, "SZ"}, {752, "SE"}, {756, "CH"} + , {760, "SY"}, {762, "TJ"}, {764, "TH"}, {768, "TG"}, {772, "TK"} + , {776, "TO"}, {780, "TT"}, {784, "AE"}, {788, "TN"}, {792, "TR"} + , {795, "TM"}, {796, "TC"}, {798, "TV"}, {800, "UG"}, {804, "UA"} + , {807, "MK"}, {818, "EG"}, {826, "GB"}, {834, "TZ"}, {840, "US"} + , {850, "VI"}, {854, "BF"}, {858, "UY"}, {860, "UZ"}, {862, "VE"} + , {876, "WF"}, {882, "WS"}, {887, "YE"}, {891, "CS"}, {894, "ZM"} + }; + + if (error || i == tcp::resolver::iterator()) + { + // this is used to indicate that we shouldn't + // try to resolve it again + p->set_country("--"); + return; + } + + while (i != tcp::resolver::iterator() + && !i->endpoint().address().is_v4()) ++i; + if (i != tcp::resolver::iterator()) + { + // country is an ISO 3166 country code + int country = i->endpoint().address().to_v4().to_ulong() & 0xffff; + + // look up the country code in the map + const int size = sizeof(country_map)/sizeof(country_map[0]); + country_entry tmp = {country, ""}; + country_entry* i = + std::lower_bound(country_map, country_map + size, tmp + , bind(&country_entry::code, _1) < bind(&country_entry::code, _2)); + if (i == country_map + size + || i->code != country) + { + // unknown country! + p->set_country("!!"); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "IP " << p->remote().address() << " was mapped to unknown country: " << country << "\n"; +#endif + return; + } + + p->set_country(i->name); + } + } +#endif + + peer_connection* torrent::connect_to_peer(policy::peer* peerinfo) + { + INVARIANT_CHECK; + + assert(peerinfo); + assert(peerinfo->connection == 0); +#ifndef NDEBUG + // this asserts that we don't have duplicates in the policy's peer list + peer_iterator i_ = m_connections.find(peerinfo->ip); + assert(i_ == m_connections.end() + || i_->second->is_disconnecting() + || dynamic_cast(i_->second) == 0 + || m_ses.settings().allow_multiple_connections_per_ip); +#endif + + assert(want_more_peers()); + + tcp::endpoint const& a(peerinfo->ip); + assert((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); + + boost::shared_ptr s + = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy()); + boost::intrusive_ptr c(new bt_peer_connection( + m_ses, shared_from_this(), s, a, peerinfo)); + +#ifndef NDEBUG + c->m_in_constructor = false; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr pp((*i)->new_connection(c.get())); + if (pp) c->add_extension(pp); + } +#endif + + try + { + // add the newly connected peer to this torrent's peer list + m_connections.insert( + std::make_pair(a, boost::get_pointer(c))); + m_ses.m_connections.insert(std::make_pair(s, c)); + + m_ses.m_half_open.enqueue( + bind(&peer_connection::connect, c, _1) + , bind(&peer_connection::timed_out, c) + , seconds(settings().peer_connect_timeout)); + } + catch (std::exception& e) + { + // TODO: post an error alert! + std::map::iterator i = m_connections.find(a); + if (i != m_connections.end()) m_connections.erase(i); + m_ses.connection_failed(s, a, e.what()); + c->disconnect(); + throw; + } + if (c->is_disconnecting()) throw protocol_error("failed to connect"); + return c.get(); + } + + void torrent::set_metadata(entry const& metadata) + { + INVARIANT_CHECK; + + assert(!m_torrent_file.is_valid()); + m_torrent_file.parse_info_section(metadata); + + init(); + + boost::mutex::scoped_lock(m_checker.m_mutex); + + boost::shared_ptr d( + new aux::piece_checker_data); + d->torrent_ptr = shared_from_this(); + d->save_path = m_save_path; + d->info_hash = m_torrent_file.info_hash(); + // add the torrent to the queue to be checked + m_checker.m_torrents.push_back(d); + typedef session_impl::torrent_map torrent_map; + torrent_map::iterator i = m_ses.m_torrents.find( + m_torrent_file.info_hash()); + assert(i != m_ses.m_torrents.end()); + m_ses.m_torrents.erase(i); + // and notify the thread that it got another + // job in its queue + m_checker.m_cond.notify_one(); + + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(metadata_received_alert( + get_handle(), "metadata successfully received from swarm")); + } + } + + void torrent::attach_peer(peer_connection* p) + { + INVARIANT_CHECK; + + assert(p != 0); + assert(!p->is_local()); + + std::map::iterator c + = m_connections.find(p->remote()); + if (c != m_connections.end()) + { + // we already have a peer_connection to this ip. + // It may currently be waiting for completing a + // connection attempt that might fail. So, + // prioritize this current connection since + // it has already succeeded. + if (!c->second->is_connecting()) + { + throw protocol_error("already connected to peer"); + } + c->second->disconnect(); + } + + if (m_ses.m_connections.find(p->get_socket()) + == m_ses.m_connections.end()) + { + throw protocol_error("peer is not properly constructed"); + } + + if (m_ses.is_aborted()) + { + throw protocol_error("session is closing"); + } + + peer_iterator ci = m_connections.insert( + std::make_pair(p->remote(), p)).first; + try + { + // if new_connection throws, we have to remove the + // it from the list. + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr pp((*i)->new_connection(p)); + if (pp) p->add_extension(pp); + } +#endif + m_policy->new_connection(*ci->second); + } + catch (std::exception& e) + { + m_connections.erase(ci); + throw; + } + assert(p->remote() == p->get_socket()->remote_endpoint()); + +#ifndef NDEBUG + m_policy->check_invariant(); +#endif + } + + bool torrent::want_more_peers() const + { + return int(m_connections.size()) < m_connections_quota.given + && m_ses.m_half_open.free_slots() + && !m_paused; + } + + void torrent::disconnect_all() + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + while (!m_connections.empty()) + { + peer_connection& p = *m_connections.begin()->second; + assert(p.associated_torrent().lock().get() == this); + +#if defined(TORRENT_VERBOSE_LOGGING) + if (m_abort) + (*p.m_logger) << "*** CLOSING CONNECTION 'aborting'\n"; + else + (*p.m_logger) << "*** CLOSING CONNECTION 'pausing'\n"; +#endif +#ifndef NDEBUG + std::size_t size = m_connections.size(); +#endif + p.disconnect(); + assert(m_connections.size() <= size); + } + } + + int torrent::bandwidth_throttle(int channel) const + { + return m_bandwidth_limit[channel].throttle(); + } + + void torrent::request_bandwidth(int channel + , boost::intrusive_ptr const& p + , bool non_prioritized) + { + int block_size = m_bandwidth_limit[channel].throttle() / 10; + + if (m_bandwidth_limit[channel].max_assignable() > 0) + { + perform_bandwidth_request(channel, p, block_size, non_prioritized); + } + else + { + // skip forward in the queue until we find a prioritized peer + // or hit the front of it. + queue_t::reverse_iterator i = m_bandwidth_queue[channel].rbegin(); + while (i != m_bandwidth_queue[channel].rend() && i->non_prioritized) ++i; + m_bandwidth_queue[channel].insert(i.base(), bw_queue_entry( + p, block_size, non_prioritized)); + } + } + + void torrent::expire_bandwidth(int channel, int amount) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + assert(amount > 0); + m_bandwidth_limit[channel].expire(amount); + + while (!m_bandwidth_queue[channel].empty()) + { + bw_queue_entry qe = m_bandwidth_queue[channel].front(); + if (m_bandwidth_limit[channel].max_assignable() == 0) + break; + m_bandwidth_queue[channel].pop_front(); + perform_bandwidth_request(channel, qe.peer + , qe.max_block_size, qe.non_prioritized); + } + } + + void torrent::perform_bandwidth_request(int channel + , boost::intrusive_ptr const& p + , int block_size + , bool non_prioritized) + { + assert(m_bandwidth_limit[channel].max_assignable() >= block_size); + + m_ses.m_bandwidth_manager[channel]->request_bandwidth(p + , block_size, non_prioritized); + m_bandwidth_limit[channel].assign(block_size); + } + + void torrent::assign_bandwidth(int channel, int amount, int blk) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + assert(amount > 0); + assert(amount <= blk); + if (amount < blk) + expire_bandwidth(channel, blk - amount); + } + + // called when torrent is finished (all interested pieces downloaded) + void torrent::finished() + { + INVARIANT_CHECK; + + if (alerts().should_post(alert::info)) + { + alerts().post_alert(torrent_finished_alert( + get_handle() + , "torrent has finished downloading")); + } + + // disconnect all seeds + // TODO: should disconnect all peers that have the pieces we have + // not just seeds + std::vector seeds; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + assert(i->second->associated_torrent().lock().get() == this); + if (i->second->is_seed()) + { +#if defined(TORRENT_VERBOSE_LOGGING) + (*i->second->m_logger) << "*** SEED, CLOSING CONNECTION\n"; +#endif + seeds.push_back(i->second); + } + } + std::for_each(seeds.begin(), seeds.end() + , bind(&peer_connection::disconnect, _1)); + + assert(m_storage); + m_storage->async_release_files(); + } + + // called when torrent is complete (all pieces downloaded) + void torrent::completed() + { + INVARIANT_CHECK; + + // make the next tracker request + // be a completed-event + m_event = tracker_request::completed; + force_tracker_request(); + } + + // this will move the tracker with the given index + // to a prioritized position in the list (move it towards + // the begining) and return the new index to the tracker. + int torrent::prioritize_tracker(int index) + { + INVARIANT_CHECK; + + assert(index >= 0); + if (index >= (int)m_trackers.size()) return (int)m_trackers.size()-1; + + while (index > 0 && m_trackers[index].tier == m_trackers[index-1].tier) + { + std::swap(m_trackers[index].url, m_trackers[index-1].url); + --index; + } + return index; + } + + void torrent::try_next_tracker() + { + INVARIANT_CHECK; + + ++m_currently_trying_tracker; + + if ((unsigned)m_currently_trying_tracker >= m_trackers.size()) + { + int delay = tracker_retry_delay_min + + std::min(m_failed_trackers, (int)tracker_failed_max) + * (tracker_retry_delay_max - tracker_retry_delay_min) + / tracker_failed_max; + + ++m_failed_trackers; + // if we've looped the tracker list, wait a bit before retrying + m_currently_trying_tracker = 0; + m_next_request = time_now() + seconds(delay); + +#ifndef TORRENT_DISABLE_DHT + // only start the announce if we want to announce with the dht + if (should_announce_dht()) + { + // force the DHT to reannounce + m_last_dht_announce = time_now() - minutes(15); + boost::weak_ptr self(shared_from_this()); + m_announce_timer.expires_from_now(seconds(1)); + m_announce_timer.async_wait(m_ses.m_strand.wrap( + bind(&torrent::on_announce_disp, self, _1))); + } +#endif + + } + else + { + // don't delay before trying the next tracker + m_next_request = time_now(); + } + + } + + bool torrent::check_fastresume(aux::piece_checker_data& data) + { + INVARIANT_CHECK; + + assert(valid_metadata()); + bool done = true; + try + { + assert(m_storage); + assert(m_owning_storage.get()); + done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces + , m_compact_mode); + } + catch (std::exception& e) + { + // probably means file permission failure or invalid filename + std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); + m_num_pieces = 0; + + if (m_ses.m_alerts.should_post(alert::fatal)) + { + m_ses.m_alerts.post_alert( + file_error_alert( + get_handle() + , e.what())); + } + pause(); + } +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif + return done; + } + + std::pair torrent::check_files() + { + INVARIANT_CHECK; + + assert(m_owning_storage.get()); + + std::pair progress(true, 1.f); + try + { + assert(m_storage); + progress = m_storage->check_files(m_have_pieces, m_num_pieces + , m_ses.m_mutex); + } + catch (std::exception& e) + { + // probably means file permission failure or invalid filename + std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); + m_num_pieces = 0; + + if (m_ses.m_alerts.should_post(alert::fatal)) + { + m_ses.m_alerts.post_alert( + file_error_alert( + get_handle() + , e.what())); + } + pause(); + } + +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif + return progress; + } + + void torrent::files_checked(std::vector const& + unfinished_pieces) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (!is_seed()) + { + // this is filled in with pieces that needs to be checked + // against its hashes. + std::vector verify_pieces; + m_picker->files_checked(m_have_pieces, unfinished_pieces, verify_pieces); + if (m_sequenced_download_threshold > 0) + picker().set_sequenced_download_threshold(m_sequenced_download_threshold); + while (!verify_pieces.empty()) + { + int piece = verify_pieces.back(); + verify_pieces.pop_back(); + async_verify_piece(piece, bind(&torrent::piece_finished + , shared_from_this(), piece, _1)); + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { (*i)->on_files_checked(); } catch (std::exception&) {} + } +#endif + + if (is_seed()) + { + m_picker.reset(); + m_torrent_file.seed_free(); + } + + if (!m_connections_initialized) + { + m_connections_initialized = true; + // all peer connections have to initialize themselves now that the metadata + // is available + typedef std::map conn_map; + for (conn_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end;) + { + try + { + i->second->on_metadata(); + i->second->init(); + ++i; + } + catch (std::exception& e) + { + // the connection failed, close it + conn_map::iterator j = i; + ++j; + m_ses.connection_failed(i->second->get_socket() + , i->first, e.what()); + i = j; + } + } + } +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif + } + + alert_manager& torrent::alerts() const + { + return m_ses.m_alerts; + } + + fs::path torrent::save_path() const + { + if (m_owning_storage.get()) + return m_owning_storage->save_path(); + else + return m_save_path; + } + + void torrent::move_storage(fs::path const& save_path) + { + INVARIANT_CHECK; + + if (m_owning_storage.get()) + { + m_owning_storage->async_move_storage(save_path + , bind(&torrent::on_storage_moved, shared_from_this(), _1, _2)); + } + else + { + m_save_path = save_path; + } + } + + void torrent::on_storage_moved(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(storage_moved_alert(get_handle(), j.str)); + } + } + + piece_manager& torrent::filesystem() + { + INVARIANT_CHECK; + + assert(m_owning_storage.get()); + return *m_owning_storage; + } + + + torrent_handle torrent::get_handle() const + { + INVARIANT_CHECK; + + return torrent_handle(&m_ses, &m_checker, m_torrent_file.info_hash()); + } + + session_settings const& torrent::settings() const + { +// INVARIANT_CHECK; + + return m_ses.settings(); + } + +#ifndef NDEBUG + void torrent::check_invariant() const + { +// size_type download = m_stat.total_payload_download(); +// size_type done = boost::get<0>(bytes_done()); +// assert(download >= done - m_initial_done); + for (const_peer_iterator i = begin(); i != end(); ++i) + { + peer_connection const& p = *i->second; + torrent* associated_torrent = p.associated_torrent().lock().get(); + if (associated_torrent != this) + assert(false); + } + + if (valid_metadata()) + { + assert(m_abort || int(m_have_pieces.size()) == m_torrent_file.num_pieces()); + } + else + { + assert(m_abort || m_have_pieces.empty()); + } + + size_type total_done = quantized_bytes_done(); + if (m_torrent_file.is_valid()) + { + if (is_seed()) + assert(total_done == m_torrent_file.total_size()); + else + assert(total_done != m_torrent_file.total_size()); + } + else + { + assert(total_done == 0); + } + +// This check is very expensive. + assert(m_num_pieces + == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); + assert(!valid_metadata() || m_block_size > 0); + assert(!valid_metadata() || (m_torrent_file.piece_length() % m_block_size) == 0); +// if (is_seed()) assert(m_picker.get() == 0); + } +#endif + + void torrent::set_sequenced_download_threshold(int threshold) + { + if (has_picker()) + { + picker().set_sequenced_download_threshold(threshold); + } + else + { + m_sequenced_download_threshold = threshold; + } + } + + + void torrent::set_max_uploads(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = std::numeric_limits::max(); + m_uploads_quota.max = std::max(m_uploads_quota.min, limit); + } + + void torrent::set_max_connections(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = std::numeric_limits::max(); + m_connections_quota.max = std::max(m_connections_quota.min, limit); + } + + void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) + { + assert(limit >= -1); + peer_connection* p = connection_for(ip); + if (p == 0) return; + p->set_upload_limit(limit); + } + + void torrent::set_peer_download_limit(tcp::endpoint ip, int limit) + { + assert(limit >= -1); + peer_connection* p = connection_for(ip); + if (p == 0) return; + p->set_download_limit(limit); + } + + void torrent::set_upload_limit(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = std::numeric_limits::max(); + if (limit < num_peers() * 10) limit = num_peers() * 10; + m_bandwidth_limit[peer_connection::upload_channel].throttle(limit); + } + + int torrent::upload_limit() const + { + int limit = m_bandwidth_limit[peer_connection::upload_channel].throttle(); + if (limit == std::numeric_limits::max()) limit = -1; + return limit; + } + + void torrent::set_download_limit(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = std::numeric_limits::max(); + if (limit < num_peers() * 10) limit = num_peers() * 10; + m_bandwidth_limit[peer_connection::download_channel].throttle(limit); + } + + int torrent::download_limit() const + { + int limit = m_bandwidth_limit[peer_connection::download_channel].throttle(); + if (limit == std::numeric_limits::max()) limit = -1; + return limit; + } + + void torrent::pause() + { + INVARIANT_CHECK; + + if (m_paused) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { if ((*i)->on_pause()) return; } catch (std::exception&) {} + } +#endif + + disconnect_all(); + m_paused = true; + // tell the tracker that we stopped + m_event = tracker_request::stopped; + m_just_paused = true; + // this will make the storage close all + // files and flush all cached data + if (m_owning_storage.get()) + { + assert(m_storage); + // TOOD: add a callback which posts + // an alert for the client to sync. with + m_storage->async_release_files( + bind(&torrent::on_files_released, shared_from_this(), _1, _2)); + } + } + + void torrent::resume() + { + INVARIANT_CHECK; + + if (!m_paused) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { if ((*i)->on_resume()) return; } catch (std::exception&) {} + } +#endif + + m_paused = false; + m_uploads_quota.min = 2; + m_connections_quota.min = 2; + m_uploads_quota.max = std::numeric_limits::max(); + m_connections_quota.max = std::numeric_limits::max(); + + // tell the tracker that we're back + m_event = tracker_request::started; + force_tracker_request(); + + // make pulse be called as soon as possible + m_time_scaler = 0; + } + + void torrent::second_tick(stat& accumulator, float tick_interval) + { + INVARIANT_CHECK; + + m_connections_quota.used = (int)m_connections.size(); + m_uploads_quota.used = m_policy->num_uploads(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + try { (*i)->tick(); } catch (std::exception&) {} + } +#endif + + if (m_paused) + { + // let the stats fade out to 0 + m_stat.second_tick(tick_interval); + m_connections_quota.min = 0; + m_connections_quota.max = 0; + m_uploads_quota.min = 0; + m_uploads_quota.max = 0; + return; + } + + // ---- WEB SEEDS ---- + + // if we have everything we want we don't need to connect to any web-seed + if (!is_finished() && !m_web_seeds.empty()) + { + // keep trying web-seeds if there are any + // first find out which web seeds we are connected to + std::set web_seeds; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + web_peer_connection* p + = dynamic_cast(i->second); + if (!p) continue; + web_seeds.insert(p->url()); + } + + for (std::set::iterator i = m_resolving_web_seeds.begin() + , end(m_resolving_web_seeds.end()); i != end; ++i) + web_seeds.insert(web_seeds.begin(), *i); + + // from the list of available web seeds, subtract the ones we are + // already connected to. + std::vector not_connected_web_seeds; + std::set_difference(m_web_seeds.begin(), m_web_seeds.end(), web_seeds.begin() + , web_seeds.end(), std::back_inserter(not_connected_web_seeds)); + + // connect to all of those that we aren't connected to + std::for_each(not_connected_web_seeds.begin(), not_connected_web_seeds.end() + , bind(&torrent::connect_to_url_seed, this, _1)); + } + + for (peer_iterator i = m_connections.begin(); + i != m_connections.end();) + { + peer_connection* p = i->second; + ++i; + m_stat += p->statistics(); + // updates the peer connection's ul/dl bandwidth + // resource requests + try + { + p->second_tick(tick_interval); + } + catch (std::exception& e) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*p->m_logger) << "**ERROR**: " << e.what() << "\n"; +#endif + p->set_failed(); + p->disconnect(); + } + } + accumulator += m_stat; + m_stat.second_tick(tick_interval); + } + + bool torrent::try_connect_peer() + { + assert(want_more_peers()); + return m_policy->connect_one_peer(); + } + + void torrent::distribute_resources(float tick_interval) + { + INVARIANT_CHECK; + + m_time_scaler--; + if (m_time_scaler <= 0) + { + m_time_scaler = settings().unchoke_interval; + m_policy->pulse(); + } + } + + void torrent::async_verify_piece(int piece_index, boost::function const& f) + { + INVARIANT_CHECK; + + assert(m_storage); + assert(m_storage->refcount() > 0); + assert(piece_index >= 0); + assert(piece_index < m_torrent_file.num_pieces()); + assert(piece_index < (int)m_have_pieces.size()); + + m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified + , shared_from_this(), _1, _2, f)); + } + + void torrent::on_piece_verified(int ret, disk_io_job const& j + , boost::function f) + { + sha1_hash h(j.str); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + f(m_torrent_file.hash_for_piece(j.piece) == h); + } + + const tcp::endpoint& torrent::current_tracker() const + { + return m_tracker_address; + } + + bool torrent::is_allocating() const + { return m_owning_storage.get() && m_owning_storage->is_allocating(); } + + void torrent::file_progress(std::vector& fp) const + { + assert(valid_metadata()); + + fp.clear(); + fp.resize(m_torrent_file.num_files(), 0.f); + + for (int i = 0; i < m_torrent_file.num_files(); ++i) + { + peer_request ret = m_torrent_file.map_file(i, 0, 0); + size_type size = m_torrent_file.file_at(i).size; + +// zero sized files are considered +// 100% done all the time + if (size == 0) + { + fp[i] = 1.f; + continue; + } + + size_type done = 0; + while (size > 0) + { + size_type bytes_step = std::min(m_torrent_file.piece_size(ret.piece) + - ret.start, size); + if (m_have_pieces[ret.piece]) done += bytes_step; + ++ret.piece; + ret.start = 0; + size -= bytes_step; + } + assert(size == 0); + + fp[i] = static_cast(done) / m_torrent_file.file_at(i).size; + } + } + + torrent_status torrent::status() const + { + INVARIANT_CHECK; + + assert(std::accumulate( + m_have_pieces.begin() + , m_have_pieces.end() + , 0) == m_num_pieces); + + torrent_status st; + + st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end(), + !boost::bind(&peer_connection::is_connecting + , boost::bind(&std::map::value_type::second, _1))); + + st.num_complete = m_complete; + st.num_incomplete = m_incomplete; + st.paused = m_paused; + boost::tie(st.total_done, st.total_wanted_done) = bytes_done(); + + // payload transfer + st.total_payload_download = m_stat.total_payload_download(); + st.total_payload_upload = m_stat.total_payload_upload(); + + // total transfer + st.total_download = m_stat.total_payload_download() + + m_stat.total_protocol_download(); + st.total_upload = m_stat.total_payload_upload() + + m_stat.total_protocol_upload(); + + // failed bytes + st.total_failed_bytes = m_total_failed_bytes; + st.total_redundant_bytes = m_total_redundant_bytes; + + // transfer rate + st.download_rate = m_stat.download_rate(); + st.upload_rate = m_stat.upload_rate(); + st.download_payload_rate = m_stat.download_payload_rate(); + st.upload_payload_rate = m_stat.upload_payload_rate(); + + st.next_announce = boost::posix_time::seconds( + total_seconds(next_announce() - time_now())); + if (st.next_announce.is_negative()) + st.next_announce = boost::posix_time::seconds(0); + + st.announce_interval = boost::posix_time::seconds(m_duration); + + if (m_last_working_tracker >= 0) + { + st.current_tracker + = m_trackers[m_last_working_tracker].url; + } + + // if we don't have any metadata, stop here + + if (!valid_metadata()) + { + if (m_got_tracker_response == false) + st.state = torrent_status::connecting_to_tracker; + else + st.state = torrent_status::downloading_metadata; + +// TODO: add a progress member to the torrent that will be used in this case +// and that may be set by a plugin +// if (m_metadata_size == 0) st.progress = 0.f; +// else st.progress = std::min(1.f, m_metadata_progress / (float)m_metadata_size); + st.progress = 0.f; + + st.block_size = 0; + + return st; + } + + st.block_size = block_size(); + + // fill in status that depends on metadata + + st.total_wanted = m_torrent_file.total_size(); + + if (m_picker.get() && (m_picker->num_filtered() > 0 + || m_picker->num_have_filtered() > 0)) + { + int filtered_pieces = m_picker->num_filtered() + + m_picker->num_have_filtered(); + int last_piece_index = m_torrent_file.num_pieces() - 1; + if (m_picker->piece_priority(last_piece_index) == 0) + { + st.total_wanted -= m_torrent_file.piece_size(last_piece_index); + --filtered_pieces; + } + + st.total_wanted -= filtered_pieces * m_torrent_file.piece_length(); + } + + assert(st.total_wanted >= st.total_wanted_done); + + if (st.total_wanted == 0) st.progress = 1.f; + else st.progress = st.total_wanted_done + / static_cast(st.total_wanted); + + st.pieces = &m_have_pieces; + st.num_pieces = m_num_pieces; + + if (m_got_tracker_response == false) + { + st.state = torrent_status::connecting_to_tracker; + } + else if (is_seed()) + { + assert(st.total_done == m_torrent_file.total_size()); + st.state = torrent_status::seeding; + } + else if (st.total_wanted_done == st.total_wanted) + { + st.state = torrent_status::finished; + } + else + { + st.state = torrent_status::downloading; + } + + st.num_seeds = num_seeds(); + if (m_picker.get()) + st.distributed_copies = m_picker->distributed_copies(); + else + st.distributed_copies = -1; + return st; + } + + int torrent::num_seeds() const + { + INVARIANT_CHECK; + + return (int)std::count_if(m_connections.begin(), m_connections.end() + , boost::bind(&peer_connection::is_seed + , boost::bind(&std::map::value_type::second, _1))); + } + + void torrent::tracker_request_timed_out( + tracker_request const&) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + debug_log("*** tracker timed out"); +#endif + + if (m_ses.m_alerts.should_post(alert::warning)) + { + std::stringstream s; + s << "tracker: \"" + << m_trackers[m_currently_trying_tracker].url + << "\" timed out"; + m_ses.m_alerts.post_alert(tracker_alert(get_handle() + , m_failed_trackers + 1, 0, s.str())); + } + try_next_tracker(); + } + + // TODO: with some response codes, we should just consider + // the tracker as a failure and not retry + // it anymore + void torrent::tracker_request_error(tracker_request const& + , int response_code, const std::string& str) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + debug_log(std::string("*** tracker error: ") + str); +#endif + if (m_ses.m_alerts.should_post(alert::warning)) + { + std::stringstream s; + s << "tracker: \"" + << m_trackers[m_currently_trying_tracker].url + << "\" " << str; + m_ses.m_alerts.post_alert(tracker_alert(get_handle() + , m_failed_trackers + 1, response_code, s.str())); + } + + try_next_tracker(); + } + + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + void torrent::debug_log(const std::string& line) + { + (*m_ses.m_logger) << line << "\n"; + } +#endif + +} + diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp new file mode 100755 index 000000000..c9ade14e9 --- /dev/null +++ b/libtorrent/src/torrent_handle.cpp @@ -0,0 +1,786 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/invariant_check.hpp" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std +{ + using ::srand; + using ::isalnum; +}; +#endif + +using boost::bind; +using boost::mutex; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + namespace fs = boost::filesystem; + + namespace + { + void throw_invalid_handle() + { + throw invalid_handle(); + } + + template + Ret call_member( + session_impl* ses + , aux::checker_impl* chk + , sha1_hash const& hash + , F f) + { + if (ses == 0) throw_invalid_handle(); + + if (chk) + { + mutex::scoped_lock l(chk->m_mutex); + aux::piece_checker_data* d = chk->find_torrent(hash); + if (d != 0) return f(*d->torrent_ptr); + } + + { + session_impl::mutex_t::scoped_lock l(ses->m_mutex); + boost::shared_ptr t = ses->find_torrent(hash).lock(); + if (t) return f(*t); + } + + // throwing directly instead of calling + // the throw_invalid_handle() function + // avoids a warning in gcc + throw invalid_handle(); + } + } + +#ifndef NDEBUG + + void torrent_handle::check_invariant() const + { + assert((m_ses == 0 && m_chk == 0) || (m_ses != 0)); + } + +#endif + + void torrent_handle::set_max_uploads(int max_uploads) const + { + INVARIANT_CHECK; + + assert(max_uploads >= 2 || max_uploads == -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_max_uploads, _1, max_uploads)); + } + + void torrent_handle::use_interface(const char* net_interface) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::use_interface, _1, net_interface)); + } + + void torrent_handle::set_max_connections(int max_connections) const + { + INVARIANT_CHECK; + + assert(max_connections >= 2 || max_connections == -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_max_connections, _1, max_connections)); + } + + void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const + { + INVARIANT_CHECK; + assert(limit >= -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_peer_upload_limit, _1, ip, limit)); + } + + void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const + { + INVARIANT_CHECK; + assert(limit >= -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_peer_download_limit, _1, ip, limit)); + } + + void torrent_handle::set_upload_limit(int limit) const + { + INVARIANT_CHECK; + + assert(limit >= -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_upload_limit, _1, limit)); + } + + int torrent_handle::upload_limit() const + { + INVARIANT_CHECK; + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::upload_limit, _1)); + } + + void torrent_handle::set_download_limit(int limit) const + { + INVARIANT_CHECK; + + assert(limit >= -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_download_limit, _1, limit)); + } + + int torrent_handle::download_limit() const + { + INVARIANT_CHECK; + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::download_limit, _1)); + } + + void torrent_handle::move_storage( + fs::path const& save_path) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::move_storage, _1, save_path)); + } + + bool torrent_handle::has_metadata() const + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::valid_metadata, _1)); + } + + bool torrent_handle::is_seed() const + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::is_seed, _1)); + } + + bool torrent_handle::is_paused() const + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::is_paused, _1)); + } + + void torrent_handle::pause() const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::pause, _1)); + } + + void torrent_handle::resume() const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::resume, _1)); + } + + void torrent_handle::set_tracker_login(std::string const& name + , std::string const& password) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_tracker_login, _1, name, password)); + } + + void torrent_handle::file_progress(std::vector& progress) + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + + if (m_chk) + { + mutex::scoped_lock l(m_chk->m_mutex); + + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) + { + if (!d->processing) + { + torrent_info const& info = d->torrent_ptr->torrent_file(); + progress.clear(); + progress.resize(info.num_files(), 0.f); + return; + } + d->torrent_ptr->file_progress(progress); + return; + } + } + + { + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->file_progress(progress); + } + + throw_invalid_handle(); + } + + torrent_status torrent_handle::status() const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + + if (m_chk) + { + mutex::scoped_lock l(m_chk->m_mutex); + + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) + { + torrent_status st; + + if (d->processing) + { + if (d->torrent_ptr->is_allocating()) + st.state = torrent_status::allocating; + else + st.state = torrent_status::checking_files; + } + else + st.state = torrent_status::queued_for_checking; + st.progress = d->progress; + st.paused = d->torrent_ptr->is_paused(); + return st; + } + } + + { + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->status(); + } + + throw_invalid_handle(); + return torrent_status(); + } + + void torrent_handle::set_sequenced_download_threshold(int threshold) const + { + INVARIANT_CHECK; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_sequenced_download_threshold, _1, threshold)); + } + + std::string torrent_handle::name() const + { + INVARIANT_CHECK; + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::name, _1)); + } + + + void torrent_handle::piece_availability(std::vector& avail) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::piece_availability, _1, boost::ref(avail))); + } + + void torrent_handle::piece_priority(int index, int priority) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_piece_priority, _1, index, priority)); + } + + int torrent_handle::piece_priority(int index) const + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::piece_priority, _1, index)); + } + + void torrent_handle::prioritize_pieces(std::vector const& pieces) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::prioritize_pieces, _1, boost::cref(pieces))); + } + + std::vector torrent_handle::piece_priorities() const + { + INVARIANT_CHECK; + std::vector ret; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::piece_priorities, _1, boost::ref(ret))); + return ret; + } + + void torrent_handle::prioritize_files(std::vector const& files) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::prioritize_files, _1, boost::cref(files))); + } + +// ============ start deprecation =============== + + void torrent_handle::filter_piece(int index, bool filter) const + { + INVARIANT_CHECK; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filter_piece, _1, index, filter)); + } + + void torrent_handle::filter_pieces(std::vector const& pieces) const + { + INVARIANT_CHECK; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filter_pieces, _1, boost::cref(pieces))); + } + + bool torrent_handle::is_piece_filtered(int index) const + { + INVARIANT_CHECK; + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::is_piece_filtered, _1, index)); + } + + std::vector torrent_handle::filtered_pieces() const + { + INVARIANT_CHECK; + std::vector ret; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filtered_pieces, _1, boost::ref(ret))); + return ret; + } + + void torrent_handle::filter_files(std::vector const& files) const + { + INVARIANT_CHECK; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filter_files, _1, files)); + } + +// ============ end deprecation =============== + + + std::vector const& torrent_handle::trackers() const + { + INVARIANT_CHECK; + + return call_member const&>(m_ses + , m_chk, m_info_hash, bind(&torrent::trackers, _1)); + } + + void torrent_handle::add_url_seed(std::string const& url) + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::add_url_seed, _1, url)); + } + + void torrent_handle::replace_trackers( + std::vector const& urls) const + { + INVARIANT_CHECK; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::replace_trackers, _1, urls)); + } + + torrent_info const& torrent_handle::get_torrent_info() const + { + INVARIANT_CHECK; + + if (!has_metadata()) throw_invalid_handle(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::torrent_file, _1)); + } + + bool torrent_handle::is_valid() const + { + INVARIANT_CHECK; + + if (m_ses == 0) return false; + + if (m_chk) + { + mutex::scoped_lock l(m_chk->m_mutex); + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) return true; + } + + { + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::weak_ptr t = m_ses->find_torrent(m_info_hash); + if (!t.expired()) return true; + } + + return false; + } + + entry torrent_handle::write_resume_data() const + { + INVARIANT_CHECK; + + std::vector piece_index; + if (m_ses == 0) return entry(); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) return entry(); + + if (!t->valid_metadata()) return entry(); + + t->filesystem().export_piece_map(piece_index); + + entry ret(entry::dictionary_t); + + ret["file-format"] = "libtorrent resume file"; + ret["file-version"] = 1; + + ret["allocation"] = t->filesystem().compact_allocation()?"compact":"full"; + + const sha1_hash& info_hash = t->torrent_file().info_hash(); + ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); + + ret["slots"] = entry(entry::list_t); + entry::list_type& slots = ret["slots"].list(); + std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); + + // blocks per piece + int num_blocks_per_piece = + static_cast(t->torrent_file().piece_length()) / t->block_size(); + ret["blocks per piece"] = num_blocks_per_piece; + + // if this torrent is a seed, we won't have a piece picker + // and there will be no half-finished pieces. + if (!t->is_seed()) + { + const piece_picker& p = t->picker(); + + const std::vector& q + = p.get_download_queue(); + + // unfinished pieces + ret["unfinished"] = entry::list_type(); + entry::list_type& up = ret["unfinished"].list(); + + // info for each unfinished piece + for (std::vector::const_iterator i + = q.begin(); i != q.end(); ++i) + { + if (i->finished == 0) continue; + + entry piece_struct(entry::dictionary_t); + + // the unfinished piece's index + piece_struct["piece"] = i->index; + + std::string bitmask; + const int num_bitmask_bytes + = std::max(num_blocks_per_piece / 8, 1); + + for (int j = 0; j < num_bitmask_bytes; ++j) + { + unsigned char v = 0; + int bits = std::min(num_blocks_per_piece - j*8, 8); + for (int k = 0; k < bits; ++k) + v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) + ? (1 << k) : 0; + bitmask.insert(bitmask.end(), v); + assert(bits == 8 || j == num_bitmask_bytes - 1); + } + piece_struct["bitmask"] = bitmask; + + assert(t->filesystem().slot_for_piece(i->index) >= 0); + unsigned long adler + = t->filesystem().piece_crc( + t->filesystem().slot_for_piece(i->index) + , t->block_size() + , i->info); + + piece_struct["adler32"] = adler; + + // push the struct onto the unfinished-piece list + up.push_back(piece_struct); + } + } + // write local peers + + ret["peers"] = entry::list_type(); + entry::list_type& peer_list = ret["peers"].list(); + + policy& pol = t->get_policy(); + + for (policy::iterator i = pol.begin_peer() + , end(pol.end_peer()); i != end; ++i) + { + // we cannot save remote connection + // since we don't know their listen port + // unless they gave us their listen port + // through the extension handshake + // so, if the peer is not connectable (i.e. we + // don't know its listen port) or if it has + // been banned, don't save it. + if (i->type == policy::peer::not_connectable + || i->banned) continue; + + tcp::endpoint ip = i->ip; + entry peer(entry::dictionary_t); + peer["ip"] = ip.address().to_string(); + peer["port"] = ip.port(); + peer_list.push_back(peer); + } + + t->filesystem().write_resume_data(ret); + + return ret; + } + + + fs::path torrent_handle::save_path() const + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::save_path, _1)); + } + + void torrent_handle::connect_peer(tcp::endpoint const& adr, int source) const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + + if (!t) + { + // the torrent is being checked. Add the peer to its + // peer list. The entries in there will be connected + // once the checking is complete. + mutex::scoped_lock l2(m_chk->m_mutex); + + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d == 0) throw_invalid_handle(); + d->peers.push_back(adr); + return; + } + + peer_id id; + std::fill(id.begin(), id.end(), 0); + t->get_policy().peer_from_tracker(adr, id, source, 0); + } + + void torrent_handle::force_reannounce( + boost::posix_time::time_duration duration) const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) throw_invalid_handle(); + + t->force_tracker_request(time_now() + + seconds(duration.total_seconds())); + } + + void torrent_handle::force_reannounce() const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) throw_invalid_handle(); + + t->force_tracker_request(); + } + + void torrent_handle::set_ratio(float ratio) const + { + INVARIANT_CHECK; + + assert(ratio >= 0.f); + + if (ratio < 1.f && ratio > 0.f) + ratio = 1.f; + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_ratio, _1, ratio)); + } + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void torrent_handle::resolve_countries(bool r) + { + INVARIANT_CHECK; + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::resolve_countries, _1, r)); + } + + bool torrent_handle::resolve_countries() const + { + INVARIANT_CHECK; + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::resolving_countries, _1)); + } +#endif + + void torrent_handle::get_peer_info(std::vector& v) const + { + INVARIANT_CHECK; + + v.clear(); + if (m_ses == 0) throw_invalid_handle(); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) return; + + for (torrent::const_peer_iterator i = t->begin(); + i != t->end(); ++i) + { + peer_connection* peer = i->second; + + // incoming peers that haven't finished the handshake should + // not be included in this list + if (peer->associated_torrent().expired()) continue; + + v.push_back(peer_info()); + peer_info& p = v.back(); + + peer->get_peer_info(p); +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + if (t->resolving_countries()) + t->resolve_peer_country(intrusive_ptr(peer)); +#endif + } + } + + void torrent_handle::get_download_queue(std::vector& queue) const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + + queue.clear(); + if (!t) return; + if (!t->valid_metadata()) return; + // if we're a seed, the piece picker has been removed + if (t->is_seed()) return; + + const piece_picker& p = t->picker(); + + const std::vector& q + = p.get_download_queue(); + + for (std::vector::const_iterator i + = q.begin(); i != q.end(); ++i) + { + partial_piece_info pi; + pi.piece_state = (partial_piece_info::state_t)i->state; + pi.blocks_in_piece = p.blocks_in_piece(i->index); + for (int j = 0; j < pi.blocks_in_piece; ++j) + { + pi.blocks[j].peer = i->info[j].peer; + pi.blocks[j].num_downloads = i->info[j].num_downloads; + pi.blocks[j].state = i->info[j].state; + } + pi.piece_index = i->index; + queue.push_back(pi); + } + } + +} + diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp new file mode 100755 index 000000000..4ea09aefd --- /dev/null +++ b/libtorrent/src/torrent_info.cpp @@ -0,0 +1,878 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" + +namespace pt = boost::posix_time; +namespace gr = boost::gregorian; + +using namespace libtorrent; + +namespace +{ + + namespace fs = boost::filesystem; + + void convert_to_utf8(std::string& str, unsigned char chr) + { + str += 0xc0 | ((chr & 0xff) >> 6); + str += 0x80 | (chr & 0x3f); + } + + void verify_encoding(file_entry& target) + { + std::string tmp_path; + std::string file_path = target.path.string(); + bool valid_encoding = true; + for (std::string::iterator i = file_path.begin() + , end(file_path.end()); i != end; ++i) + { + // valid ascii-character + if ((*i & 0x80) == 0) + { + tmp_path += *i; + continue; + } + + if (std::distance(i, end) < 2) + { + convert_to_utf8(tmp_path, *i); + valid_encoding = false; + continue; + } + + // valid 2-byte utf-8 character + if ((i[0] & 0xe0) == 0xc0 + && (i[1] & 0xc0) == 0x80) + { + tmp_path += i[0]; + tmp_path += i[1]; + i += 1; + continue; + } + + if (std::distance(i, end) < 3) + { + convert_to_utf8(tmp_path, *i); + valid_encoding = false; + continue; + } + + // valid 3-byte utf-8 character + if ((i[0] & 0xf0) == 0xe0 + && (i[1] & 0xc0) == 0x80 + && (i[2] & 0xc0) == 0x80) + { + tmp_path += i[0]; + tmp_path += i[1]; + tmp_path += i[2]; + i += 2; + continue; + } + + if (std::distance(i, end) < 4) + { + convert_to_utf8(tmp_path, *i); + valid_encoding = false; + continue; + } + + // valid 4-byte utf-8 character + if ((i[0] & 0xf0) == 0xe0 + && (i[1] & 0xc0) == 0x80 + && (i[2] & 0xc0) == 0x80 + && (i[3] & 0xc0) == 0x80) + { + tmp_path += i[0]; + tmp_path += i[1]; + tmp_path += i[2]; + tmp_path += i[3]; + i += 3; + continue; + } + + convert_to_utf8(tmp_path, *i); + valid_encoding = false; + } + // the encoding was not valid utf-8 + // save the original encoding and replace the + // commonly used path with the correctly + // encoded string + if (!valid_encoding) + { + target.orig_path.reset(new fs::path(target.path)); + target.path = tmp_path; + } + } + + void extract_single_file(const entry& dict, file_entry& target + , std::string const& root_dir) + { + target.size = dict["length"].integer(); + target.path = root_dir; + + + // prefer the name.utf-8 + // because if it exists, it is more + // likely to be correctly encoded + + const entry::list_type* list = 0; + if (entry const* p = dict.find_key("path.utf-8")) + { + list = &p->list(); + } + else + { + list = &dict["path"].list(); + } + + for (entry::list_type::const_iterator i = list->begin(); + i != list->end(); ++i) + { + if (i->string() != "..") + target.path /= i->string(); + } + verify_encoding(target); + if (target.path.is_complete()) throw std::runtime_error("torrent contains " + "a file with an absolute path: '" + + target.path.native_file_string() + "'"); + } + + void extract_files(const entry::list_type& list, std::vector& target + , std::string const& root_dir) + { + size_type offset = 0; + for (entry::list_type::const_iterator i = list.begin(); i != list.end(); ++i) + { + target.push_back(file_entry()); + extract_single_file(*i, target.back(), root_dir); + target.back().offset = offset; + offset += target.back().size; + } + } +/* + void remove_dir(fs::path& p) + { + assert(p.begin() != p.end()); + path tmp; + for (path::iterator i = boost::next(p.begin()); i != p.end(); ++i) + tmp /= *i; + p = tmp; + } +*/ +} + +namespace libtorrent +{ + + // standard constructor that parses a torrent file + torrent_info::torrent_info(const entry& torrent_file) + : m_num_pieces(0) + , m_creation_date(pt::ptime(pt::not_a_date_time)) + , m_multifile(false) + , m_private(false) + , m_extra_info(entry::dictionary_t) +#ifndef NDEBUG + , m_half_metadata(false) +#endif + { + try + { + read_torrent_info(torrent_file); + } + catch(type_error&) + { + throw invalid_torrent_file(); + } + } + + // constructor used for creating new torrents + // will not contain any hashes, comments, creation date + // just the necessary to use it with piece manager + // used for torrents with no metadata + torrent_info::torrent_info(sha1_hash const& info_hash) + : m_piece_length(0) + , m_total_size(0) + , m_num_pieces(0) + , m_info_hash(info_hash) + , m_name() + , m_creation_date(pt::second_clock::universal_time()) + , m_multifile(false) + , m_private(false) + , m_extra_info(entry::dictionary_t) +#ifndef NDEBUG + , m_half_metadata(false) +#endif + { + } + + torrent_info::torrent_info() + : m_piece_length(0) + , m_total_size(0) + , m_num_pieces(0) + , m_info_hash(0) + , m_name() + , m_creation_date(pt::second_clock::universal_time()) + , m_multifile(false) + , m_private(false) + , m_extra_info(entry::dictionary_t) +#ifndef NDEBUG + , m_half_metadata(false) +#endif + { + } + + torrent_info::~torrent_info() + {} + + void torrent_info::swap(torrent_info& ti) + { + using std::swap; + m_urls.swap(ti.m_urls); + m_url_seeds.swap(ti.m_url_seeds); + swap(m_piece_length, ti.m_piece_length); + m_piece_hash.swap(ti.m_piece_hash); + m_files.swap(ti.m_files); + m_nodes.swap(ti.m_nodes); + swap(m_num_pieces, ti.m_num_pieces); + swap(m_info_hash, ti.m_info_hash); + m_name.swap(ti.m_name); + swap(m_creation_date, ti.m_creation_date); + m_comment.swap(ti.m_comment); + m_created_by.swap(ti.m_created_by); + swap(m_multifile, ti.m_multifile); + swap(m_private, ti.m_private); + m_extra_info.swap(ti.m_extra_info); +#ifndef NDEBUG + swap(m_half_metadata, ti.m_half_metadata); +#endif + } + + void torrent_info::set_piece_size(int size) + { + // make sure the size is an even power of 2 +#ifndef NDEBUG + for (int i = 0; i < 32; ++i) + { + if (size & (1 << i)) + { + assert((size & ~(1 << i)) == 0); + break; + } + } +#endif + assert(!m_half_metadata); + m_piece_length = size; + + m_num_pieces = static_cast( + (m_total_size + m_piece_length - 1) / m_piece_length); + int old_num_pieces = static_cast(m_piece_hash.size()); + + m_piece_hash.resize(m_num_pieces); + for (int i = old_num_pieces; i < m_num_pieces; ++i) + { + m_piece_hash[i].clear(); + } + } + + void torrent_info::parse_info_section(entry const& info) + { + // encode the info-field in order to calculate it's sha1-hash + std::vector buf; + bencode(std::back_inserter(buf), info); + hasher h; + h.update(&buf[0], (int)buf.size()); + m_info_hash = h.final(); + + // extract piece length + m_piece_length = (int)info["piece length"].integer(); + if (m_piece_length <= 0) throw std::runtime_error("invalid torrent. piece length <= 0"); + + // extract file name (or the directory name if it's a multifile libtorrent) + if (entry const* e = info.find_key("name.utf-8")) + { m_name = e->string(); } + else + { m_name = info["name"].string(); } + + fs::path tmp = m_name; + if (tmp.is_complete()) throw std::runtime_error("torrent contains " + "a file with an absolute path: '" + m_name + "'"); + if (tmp.has_branch_path()) throw std::runtime_error( + "torrent contains name with directories: '" + m_name + "'"); + + // extract file list + entry const* i = info.find_key("files"); + if (i == 0) + { + // if there's no list of files, there has to be a length + // field. + file_entry e; + e.path = m_name; + e.offset = 0; + e.size = info["length"].integer(); + m_files.push_back(e); + } + else + { + extract_files(i->list(), m_files, m_name); + m_multifile = true; + } + + // calculate total size of all pieces + m_total_size = 0; + for (std::vector::iterator i = m_files.begin(); i != m_files.end(); ++i) + m_total_size += i->size; + + // extract sha-1 hashes for all pieces + // we want this division to round upwards, that's why we have the + // extra addition + + m_num_pieces = static_cast((m_total_size + m_piece_length - 1) / m_piece_length); + m_piece_hash.resize(m_num_pieces); + const std::string& hash_string = info["pieces"].string(); + + if ((int)hash_string.length() != m_num_pieces * 20) + throw invalid_torrent_file(); + + for (int i = 0; i < m_num_pieces; ++i) + std::copy( + hash_string.begin() + i*20 + , hash_string.begin() + (i+1)*20 + , m_piece_hash[i].begin()); + + for (entry::dictionary_type::const_iterator i = info.dict().begin() + , end(info.dict().end()); i != end; ++i) + { + if (i->first == "pieces" + || i->first == "piece length" + || i->first == "length") + continue; + m_extra_info[i->first] = i->second; + } + + if (entry const* priv = info.find_key("private")) + { + if (priv->type() != entry::int_t + || priv->integer() != 0) + { + // this key exists and it's not 0. + // consider the torrent private + m_private = true; + } + } + +#ifndef NDEBUG + std::vector info_section_buf; + entry gen_info_section = create_info_metadata(); + bencode(std::back_inserter(info_section_buf), gen_info_section); + assert(hasher(&info_section_buf[0], info_section_buf.size()).final() + == m_info_hash); +#endif + } + + // extracts information from a libtorrent file and fills in the structures in + // the torrent object + void torrent_info::read_torrent_info(const entry& torrent_file) + { + // extract the url of the tracker + if (entry const* i = torrent_file.find_key("announce-list")) + { + const entry::list_type& l = i->list(); + for (entry::list_type::const_iterator j = l.begin(); j != l.end(); ++j) + { + const entry::list_type& ll = j->list(); + for (entry::list_type::const_iterator k = ll.begin(); k != ll.end(); ++k) + { + announce_entry e(k->string()); + e.tier = (int)std::distance(l.begin(), j); + m_urls.push_back(e); + } + } + + if (m_urls.size() == 0) + { + // the announce-list is empty + // fall back to look for announce + m_urls.push_back(announce_entry( + torrent_file["announce"].string())); + } + // shuffle each tier + std::vector::iterator start = m_urls.begin(); + std::vector::iterator stop; + int current_tier = m_urls.front().tier; + for (stop = m_urls.begin(); stop != m_urls.end(); ++stop) + { + if (stop->tier != current_tier) + { + std::random_shuffle(start, stop); + start = stop; + current_tier = stop->tier; + } + } + std::random_shuffle(start, stop); + } + else if (entry const* i = torrent_file.find_key("announce")) + { + m_urls.push_back(announce_entry(i->string())); + } + + if (entry const* i = torrent_file.find_key("nodes")) + { + entry::list_type const& list = i->list(); + for (entry::list_type::const_iterator i(list.begin()) + , end(list.end()); i != end; ++i) + { + if (i->type() != entry::list_t) continue; + entry::list_type const& l = i->list(); + entry::list_type::const_iterator iter = l.begin(); + if (l.size() < 1) continue; + std::string const& hostname = iter->string(); + ++iter; + int port = 6881; + if (l.end() != iter) port = iter->integer(); + m_nodes.push_back(std::make_pair(hostname, port)); + } + } + + // extract creation date + try + { + m_creation_date = pt::ptime(gr::date(1970, gr::Jan, 1)) + + pt::seconds(long(torrent_file["creation date"].integer())); + } + catch (type_error) {} + + // if there are any url-seeds, extract them + try + { + entry const& url_seeds = torrent_file["url-list"]; + if (url_seeds.type() == entry::string_t) + { + m_url_seeds.push_back(url_seeds.string()); + } + else if (url_seeds.type() == entry::list_t) + { + entry::list_type const& l = url_seeds.list(); + for (entry::list_type::const_iterator i = l.begin(); + i != l.end(); ++i) + { + m_url_seeds.push_back(i->string()); + } + } + } + catch (type_error&) {} + + // extract comment + if (entry const* e = torrent_file.find_key("comment.utf-8")) + { m_comment = e->string(); } + else if (entry const* e = torrent_file.find_key("comment")) + { m_comment = e->string(); } + + if (entry const* e = torrent_file.find_key("created by.utf-8")) + { m_created_by = e->string(); } + else if (entry const* e = torrent_file.find_key("created by")) + { m_created_by = e->string(); } + + parse_info_section(torrent_file["info"]); + } + + boost::optional + torrent_info::creation_date() const + { + if (m_creation_date != pt::ptime(gr::date(pt::not_a_date_time))) + { + return boost::optional(m_creation_date); + } + return boost::optional(); + } + + void torrent_info::add_tracker(std::string const& url, int tier) + { + announce_entry e(url); + e.tier = tier; + m_urls.push_back(e); + + using boost::bind; + std::sort(m_urls.begin(), m_urls.end(), boost::bind(std::less() + , bind(&announce_entry::tier, _1), bind(&announce_entry::tier, _2))); + } + + void torrent_info::add_file(fs::path file, size_type size) + { +// assert(file.begin() != file.end()); + + if (!file.has_branch_path()) + { + // you have already added at least one file with a + // path to the file (branch_path), which means that + // all the other files need to be in the same top + // directory as the first file. + assert(m_files.empty()); + assert(!m_multifile); + m_name = file.string(); + } + else + { +#ifndef NDEBUG + if (!m_files.empty()) + assert(m_name == *file.begin()); +#endif + m_multifile = true; + m_name = *file.begin(); + } + + file_entry e; + e.path = file; + e.size = size; + e.offset = m_files.empty() ? 0 : m_files.back().offset + + m_files.back().size; + m_files.push_back(e); + + m_total_size += size; + + if (m_piece_length == 0) + m_piece_length = 256 * 1024; + + m_num_pieces = static_cast( + (m_total_size + m_piece_length - 1) / m_piece_length); + int old_num_pieces = static_cast(m_piece_hash.size()); + + m_piece_hash.resize(m_num_pieces); + if (m_num_pieces > old_num_pieces) + std::for_each(m_piece_hash.begin() + old_num_pieces + , m_piece_hash.end(), boost::bind(&sha1_hash::clear, _1)); + } + + void torrent_info::add_url_seed(std::string const& url) + { + m_url_seeds.push_back(url); + } + + void torrent_info::set_comment(char const* str) + { + m_comment = str; + } + + void torrent_info::set_creator(char const* str) + { + m_created_by = str; + } + + entry torrent_info::create_info_metadata() const + { + // you have to add files to the torrent first + assert(!m_files.empty()); + + entry info(m_extra_info); + + if (!info.find_key("name")) + info["name"] = m_name; + + if (!m_multifile) + { + info["length"] = m_files.front().size; + } + else + { + if (!info.find_key("files")) + { + entry& files = info["files"]; + + for (std::vector::const_iterator i = m_files.begin(); + i != m_files.end(); ++i) + { + files.list().push_back(entry()); + entry& file_e = files.list().back(); + file_e["length"] = i->size; + entry& path_e = file_e["path"]; + + fs::path const* file_path; + if (i->orig_path) file_path = &(*i->orig_path); + else file_path = &i->path; + assert(file_path->has_branch_path()); + assert(*file_path->begin() == m_name); + + for (fs::path::iterator j = boost::next(file_path->begin()); + j != file_path->end(); ++j) + { + path_e.list().push_back(entry(*j)); + } + } + } + } + + info["piece length"] = piece_length(); + entry& pieces = info["pieces"]; + + std::string& p = pieces.string(); + + for (std::vector::const_iterator i = m_piece_hash.begin(); + i != m_piece_hash.end(); ++i) + { + p.append((char*)i->begin(), (char*)i->end()); + } + + return info; + } + + entry torrent_info::create_torrent() const + { + assert(m_piece_length > 0); + + if ((m_urls.empty() && m_nodes.empty()) || m_files.empty()) + { + // TODO: throw something here + // throw + return entry(); + } + + entry dict; + + if (m_private) dict["private"] = 1; + + if (!m_urls.empty()) + dict["announce"] = m_urls.front().url; + + if (!m_nodes.empty()) + { + entry& nodes = dict["nodes"]; + entry::list_type& nodes_list = nodes.list(); + for (nodes_t::const_iterator i = m_nodes.begin() + , end(m_nodes.end()); i != end; ++i) + { + entry::list_type node; + node.push_back(entry(i->first)); + node.push_back(entry(i->second)); + nodes_list.push_back(entry(node)); + } + } + + if (m_urls.size() > 1) + { + entry trackers(entry::list_t); + entry tier(entry::list_t); + int current_tier = m_urls.front().tier; + for (std::vector::const_iterator i = m_urls.begin(); + i != m_urls.end(); ++i) + { + if (i->tier != current_tier) + { + current_tier = i->tier; + trackers.list().push_back(tier); + tier.list().clear(); + } + tier.list().push_back(entry(i->url)); + } + trackers.list().push_back(tier); + dict["announce-list"] = trackers; + } + + if (!m_comment.empty()) + dict["comment"] = m_comment; + + dict["creation date"] = + (m_creation_date - pt::ptime(gr::date(1970, gr::Jan, 1))).total_seconds(); + + if (!m_created_by.empty()) + dict["created by"] = m_created_by; + + if (!m_url_seeds.empty()) + { + if (m_url_seeds.size() == 1) + { + dict["url-list"] = m_url_seeds.front(); + } + else + { + entry& list = dict["url-list"]; + for (std::vector::const_iterator i + = m_url_seeds.begin(); i != m_url_seeds.end(); ++i) + { + list.list().push_back(entry(*i)); + } + } + } + + dict["info"] = create_info_metadata(); + + entry const& info_section = dict["info"]; + std::vector buf; + bencode(std::back_inserter(buf), info_section); + m_info_hash = hasher(&buf[0], buf.size()).final(); + + return dict; + } + + void torrent_info::set_hash(int index, const sha1_hash& h) + { + assert(index >= 0); + assert(index < (int)m_piece_hash.size()); + m_piece_hash[index] = h; + } + + void torrent_info::convert_file_names() + { + assert(false); + } + + void torrent_info::seed_free() + { + std::vector().swap(m_url_seeds); + nodes_t().swap(m_nodes); + std::vector().swap(m_piece_hash); +#ifndef NDEBUG + m_half_metadata = true; +#endif + } + +// ------- start deprecation ------- + + void torrent_info::print(std::ostream& os) const + { + os << "trackers:\n"; + for (std::vector::const_iterator i = trackers().begin(); + i != trackers().end(); ++i) + { + os << i->tier << ": " << i->url << "\n"; + } + if (!m_comment.empty()) + os << "comment: " << m_comment << "\n"; +// if (m_creation_date != pt::ptime(gr::date(pt::not_a_date_time))) +// os << "creation date: " << to_simple_string(m_creation_date) << "\n"; + os << "private: " << (m_private?"yes":"no") << "\n"; + os << "number of pieces: " << num_pieces() << "\n"; + os << "piece length: " << piece_length() << "\n"; + os << "files:\n"; + for (file_iterator i = begin_files(); i != end_files(); ++i) + os << " " << std::setw(11) << i->size << " " << i->path.string() << "\n"; + } + +// ------- end deprecation ------- + + size_type torrent_info::piece_size(int index) const + { + assert(index >= 0 && index < num_pieces()); + if (index == num_pieces()-1) + { + size_type size = total_size() + - (num_pieces() - 1) * piece_length(); + assert(size > 0); + assert(size <= piece_length()); + return size; + } + else + return piece_length(); + } + + void torrent_info::add_node(std::pair const& node) + { + m_nodes.push_back(node); + } + + std::vector torrent_info::map_block(int piece, size_type offset + , int size) const + { + assert(num_files() > 0); + std::vector ret; + + size_type start = piece * (size_type)m_piece_length + offset; + assert(start + size <= m_total_size); + + // find the file iterator and file offset + // TODO: make a vector that can map piece -> file index in O(1) + size_type file_offset = start; + std::vector::const_iterator file_iter; + + int counter = 0; + for (file_iter = begin_files();; ++counter, ++file_iter) + { + assert(file_iter != end_files()); + if (file_offset < file_iter->size) + { + file_slice f; + f.file_index = counter; + f.offset = file_offset; + f.size = (std::min)(file_iter->size - file_offset, (size_type)size); + size -= f.size; + file_offset += f.size; + ret.push_back(f); + } + + assert(size >= 0); + if (size <= 0) break; + + file_offset -= file_iter->size; + } + return ret; + } + + peer_request torrent_info::map_file(int file_index, size_type file_offset + , int size) const + { + assert(file_index < (int)m_files.size()); + assert(file_index >= 0); + size_type offset = file_offset + m_files[file_index].offset; + + peer_request ret; + ret.piece = offset / piece_length(); + ret.start = offset - ret.piece * piece_length(); + ret.length = size; + return ret; + } + +} diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp new file mode 100755 index 000000000..7bd511588 --- /dev/null +++ b/libtorrent/src/tracker_manager.cpp @@ -0,0 +1,587 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#include "zlib.h" + +#include + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_connection.hpp" + +using namespace libtorrent; +using boost::tuples::make_tuple; +using boost::tuples::tuple; +using boost::bind; + +namespace +{ + enum + { + minimum_tracker_response_length = 3, + http_buffer_size = 2048 + }; + + + enum + { + FTEXT = 0x01, + FHCRC = 0x02, + FEXTRA = 0x04, + FNAME = 0x08, + FCOMMENT = 0x10, + FRESERVED = 0xe0, + + GZIP_MAGIC0 = 0x1f, + GZIP_MAGIC1 = 0x8b + }; + +} + +namespace libtorrent +{ + // returns -1 if gzip header is invalid or the header size in bytes + int gzip_header(const char* buf, int size) + { + assert(buf != 0); + assert(size > 0); + + const unsigned char* buffer = reinterpret_cast(buf); + const int total_size = size; + + // The zip header cannot be shorter than 10 bytes + if (size < 10) return -1; + + // check the magic header of gzip + if ((buffer[0] != GZIP_MAGIC0) || (buffer[1] != GZIP_MAGIC1)) return -1; + + int method = buffer[2]; + int flags = buffer[3]; + + // check for reserved flag and make sure it's compressed with the correct metod + if (method != Z_DEFLATED || (flags & FRESERVED) != 0) return -1; + + // skip time, xflags, OS code + size -= 10; + buffer += 10; + + if (flags & FEXTRA) + { + int extra_len; + + if (size < 2) return -1; + + extra_len = (buffer[1] << 8) | buffer[0]; + + if (size < (extra_len+2)) return -1; + size -= (extra_len + 2); + buffer += (extra_len + 2); + } + + if (flags & FNAME) + { + while (size && *buffer) + { + --size; + ++buffer; + } + if (!size || *buffer) return -1; + + --size; + ++buffer; + } + + if (flags & FCOMMENT) + { + while (size && *buffer) + { + --size; + ++buffer; + } + if (!size || *buffer) return -1; + + --size; + ++buffer; + } + + if (flags & FHCRC) + { + if (size < 2) return -1; + + size -= 2; + buffer += 2; + } + + return total_size - size; + } + + bool inflate_gzip( + std::vector& buffer + , tracker_request const& req + , request_callback* requester + , int maximum_tracker_response_length) + { + assert(maximum_tracker_response_length > 0); + + int header_len = gzip_header(&buffer[0], (int)buffer.size()); + if (header_len < 0) + { + requester->tracker_request_error(req, 200, "invalid gzip header in tracker response"); + return true; + } + + // start off wth one kilobyte and grow + // if needed + std::vector inflate_buffer(1024); + + // initialize the zlib-stream + z_stream str; + + // subtract 8 from the end of the buffer since that's CRC32 and input size + // and those belong to the gzip file + str.avail_in = (int)buffer.size() - header_len - 8; + str.next_in = reinterpret_cast(&buffer[header_len]); + str.next_out = reinterpret_cast(&inflate_buffer[0]); + str.avail_out = (int)inflate_buffer.size(); + str.zalloc = Z_NULL; + str.zfree = Z_NULL; + str.opaque = 0; + // -15 is really important. It will make inflate() not look for a zlib header + // and just deflate the buffer + if (inflateInit2(&str, -15) != Z_OK) + { + requester->tracker_request_error(req, 200, "gzip out of memory"); + return true; + } + + // inflate and grow inflate_buffer as needed + int ret = inflate(&str, Z_SYNC_FLUSH); + while (ret == Z_OK) + { + if (str.avail_out == 0) + { + if (inflate_buffer.size() >= (unsigned)maximum_tracker_response_length) + { + inflateEnd(&str); + requester->tracker_request_error(req, 200 + , "tracker response too large"); + return true; + } + int new_size = (int)inflate_buffer.size() * 2; + if (new_size > maximum_tracker_response_length) new_size = maximum_tracker_response_length; + int old_size = (int)inflate_buffer.size(); + + inflate_buffer.resize(new_size); + str.next_out = reinterpret_cast(&inflate_buffer[old_size]); + str.avail_out = new_size - old_size; + } + + ret = inflate(&str, Z_SYNC_FLUSH); + } + + inflate_buffer.resize(inflate_buffer.size() - str.avail_out); + inflateEnd(&str); + + if (ret != Z_STREAM_END) + { + requester->tracker_request_error(req, 200, "gzip error"); + return true; + } + + // commit the resulting buffer + std::swap(buffer, inflate_buffer); + return false; + } + + std::string base64encode(const std::string& s) + { + static const char base64_table[] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + unsigned char inbuf[3]; + unsigned char outbuf[4]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + // available input is 1,2 or 3 bytes + // since we read 3 bytes at a time at most + int available_input = std::min(3, (int)std::distance(i, s.end())); + + // clear input buffer + std::fill(inbuf, inbuf+3, 0); + + // read a chunk of input into inbuf + for (int j = 0; j < available_input; ++j) + { + inbuf[j] = *i; + ++i; + } + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xfc) >> 2; + outbuf[1] = ((inbuf[0] & 0x03) << 4) | ((inbuf [1] & 0xf0) >> 4); + outbuf[2] = ((inbuf[1] & 0x0f) << 2) | ((inbuf [2] & 0xc0) >> 6); + outbuf[3] = inbuf[2] & 0x3f; + + // write output + for (int j = 0; j < available_input+1; ++j) + { + ret += base64_table[outbuf[j]]; + } + + // write pad + for (int j = 0; j < 3 - available_input; ++j) + { + ret += '='; + } + } + return ret; + } + + timeout_handler::timeout_handler(asio::strand& str) + : m_strand(str) + , m_start_time(time_now()) + , m_read_time(time_now()) + , m_timeout(str.io_service()) + , m_completion_timeout(0) + , m_read_timeout(0) + {} + + void timeout_handler::set_timeout(int completion_timeout, int read_timeout) + { + m_completion_timeout = completion_timeout; + m_read_timeout = read_timeout; + m_start_time = time_now(); + m_read_time = time_now(); + + m_timeout.expires_at(std::min( + m_read_time + seconds(m_read_timeout) + , m_start_time + seconds(m_completion_timeout))); + m_timeout.async_wait(m_strand.wrap(bind( + &timeout_handler::timeout_callback, self(), _1))); + } + + void timeout_handler::restart_read_timeout() + { + m_read_time = time_now(); + } + + void timeout_handler::cancel() + { + m_completion_timeout = 0; + m_timeout.cancel(); + } + + void timeout_handler::timeout_callback(asio::error_code const& error) try + { + if (error) return; + if (m_completion_timeout == 0) return; + + ptime now(time_now()); + time_duration receive_timeout = now - m_read_time; + time_duration completion_timeout = now - m_start_time; + + if (m_read_timeout + < total_seconds(receive_timeout) + || m_completion_timeout + < total_seconds(completion_timeout)) + { + on_timeout(); + return; + } + + m_timeout.expires_at(std::min( + m_read_time + seconds(m_read_timeout) + , m_start_time + seconds(m_completion_timeout))); + m_timeout.async_wait(m_strand.wrap( + bind(&timeout_handler::timeout_callback, self(), _1))); + } + catch (std::exception& e) + { + assert(false); + } + + tracker_connection::tracker_connection( + tracker_manager& man + , tracker_request req + , asio::strand& str + , address bind_interface_ + , boost::weak_ptr r) + : timeout_handler(str) + , m_requester(r) + , m_bind_interface(bind_interface_) + , m_man(man) + , m_req(req) + {} + + request_callback& tracker_connection::requester() + { + boost::shared_ptr r = m_requester.lock(); + assert(r); + return *r; + } + + void tracker_connection::fail(int code, char const* msg) + { + if (has_requester()) requester().tracker_request_error( + m_req, code, msg); + close(); + } + + void tracker_connection::fail_timeout() + { + if (has_requester()) requester().tracker_request_timed_out(m_req); + close(); + } + + void tracker_connection::close() + { + cancel(); + m_man.remove_request(this); + } + + void tracker_manager::remove_request(tracker_connection const* c) + { + mutex_t::scoped_lock l(m_mutex); + + tracker_connections_t::iterator i = std::find(m_connections.begin() + , m_connections.end(), boost::intrusive_ptr(c)); + if (i == m_connections.end()) return; + + m_connections.erase(i); + } + + // returns protocol, auth, hostname, port, path + tuple + parse_url_components(std::string url) + { + std::string hostname; // hostname only + std::string auth; // user:pass + std::string protocol; // should be http + int port = 80; + + // PARSE URL + std::string::iterator start = url.begin(); + // remove white spaces in front of the url + while (start != url.end() && (*start == ' ' || *start == '\t')) + ++start; + std::string::iterator end + = std::find(url.begin(), url.end(), ':'); + protocol.assign(start, end); + + if (end == url.end()) throw std::runtime_error("invalid url"); + ++end; + if (end == url.end()) throw std::runtime_error("invalid url"); + if (*end != '/') throw std::runtime_error("invalid url"); + ++end; + if (end == url.end()) throw std::runtime_error("invalid url"); + if (*end != '/') throw std::runtime_error("invalid url"); + ++end; + start = end; + + std::string::iterator at = std::find(start, url.end(), '@'); + std::string::iterator colon = std::find(start, url.end(), ':'); + end = std::find(start, url.end(), '/'); + + if (at != url.end() + && colon != url.end() + && colon < at + && at < end) + { + auth.assign(start, at); + start = at; + ++start; + } + + std::string::iterator port_pos; + + // this is for IPv6 addresses + if (start != url.end() && *start == '[') + { + port_pos = std::find(start, url.end(), ']'); + if (port_pos == url.end()) throw std::runtime_error("invalid hostname syntax"); + port_pos = std::find(port_pos, url.end(), ':'); + } + else + { + port_pos = std::find(start, url.end(), ':'); + } + + if (port_pos < end) + { + hostname.assign(start, port_pos); + ++port_pos; + try + { + port = boost::lexical_cast(std::string(port_pos, end)); + } + catch(boost::bad_lexical_cast&) + { + throw std::runtime_error("invalid url: \"" + url + + "\", port number expected"); + } + } + else + { + hostname.assign(start, end); + } + + start = end; + return make_tuple(protocol, auth, hostname, port + , std::string(start, url.end())); + } + + void tracker_manager::queue_request( + asio::strand& str + , connection_queue& cc + , tracker_request req + , std::string const& auth + , address bind_infc + , boost::weak_ptr c) + { + mutex_t::scoped_lock l(m_mutex); + assert(req.num_want >= 0); + if (req.event == tracker_request::stopped) + req.num_want = 0; + + assert(!m_abort || req.event == tracker_request::stopped); + if (m_abort && req.event != tracker_request::stopped) + return; + + try + { + std::string protocol; + std::string hostname; + int port; + std::string request_string; + + using boost::tuples::ignore; + // TODO: should auth be used here? + boost::tie(protocol, ignore, hostname, port, request_string) + = parse_url_components(req.url); + + boost::intrusive_ptr con; + + if (protocol == "http") + { + con = new http_tracker_connection( + str + , cc + , *this + , req + , hostname + , port + , request_string + , bind_infc + , c + , m_settings + , m_proxy + , auth); + } + else if (protocol == "udp") + { + con = new udp_tracker_connection( + str + , *this + , req + , hostname + , port + , bind_infc + , c + , m_settings); + } + else + { + throw std::runtime_error("unkown protocol in tracker url"); + } + + m_connections.push_back(con); + + if (con->has_requester()) con->requester().m_manager = this; + } + catch (std::exception& e) + { + if (boost::shared_ptr r = c.lock()) + r->tracker_request_error(req, -1, e.what()); + } + } + + void tracker_manager::abort_all_requests() + { + // removes all connections from m_connections + // except those with a requester == 0 (since those are + // 'event=stopped'-requests) + mutex_t::scoped_lock l(m_mutex); + + m_abort = true; + tracker_connections_t keep_connections; + + for (tracker_connections_t::const_iterator i = + m_connections.begin(); i != m_connections.end(); ++i) + { + tracker_request const& req = (*i)->tracker_req(); + if (req.event == tracker_request::stopped) + keep_connections.push_back(*i); + } + + std::swap(m_connections, keep_connections); + } + + bool tracker_manager::empty() const + { + mutex_t::scoped_lock l(m_mutex); + return m_connections.empty(); + } + +} diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp new file mode 100755 index 000000000..d08abd359 --- /dev/null +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -0,0 +1,555 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include + +#include "zlib.h" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/io.hpp" + +namespace +{ + enum + { + udp_connection_retries = 4, + udp_announce_retries = 15, + udp_connect_timeout = 15, + udp_announce_timeout = 10, + udp_buffer_size = 2048 + }; +} + +using boost::bind; +using boost::lexical_cast; + +namespace libtorrent +{ + + udp_tracker_connection::udp_tracker_connection( + asio::strand& str + , tracker_manager& man + , tracker_request const& req + , std::string const& hostname + , unsigned short port + , address bind_infc + , boost::weak_ptr c + , session_settings const& stn) + : tracker_connection(man, req, str, bind_infc, c) + , m_man(man) + , m_strand(str) + , m_name_lookup(m_strand.io_service()) + , m_transaction_id(0) + , m_connection_id(0) + , m_settings(stn) + , m_attempts(0) + { + udp::resolver::query q(hostname, boost::lexical_cast(port)); + m_name_lookup.async_resolve(q + , m_strand.wrap(boost::bind( + &udp_tracker_connection::name_lookup, self(), _1, _2))); + set_timeout(m_settings.tracker_completion_timeout + , m_settings.tracker_receive_timeout); + } + + void udp_tracker_connection::name_lookup(asio::error_code const& error + , udp::resolver::iterator i) try + { + if (error == asio::error::operation_aborted) return; + if (!m_socket) return; // the operation was aborted + if (error || i == udp::resolver::iterator()) + { + fail(-1, error.message().c_str()); + return; + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("udp tracker name lookup successful"); +#endif + restart_read_timeout(); + + // look for an address that has the same kind as the one + // we're listening on. To make sure the tracker get our + // correct listening address. + udp::resolver::iterator target = i; + udp::resolver::iterator end; + udp::endpoint target_address = *i; + for (; target != end && target->endpoint().address().is_v4() + != bind_interface().is_v4(); ++target); + if (target == end) + { + assert(target_address.address().is_v4() != bind_interface().is_v4()); + if (has_requester()) + { + std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; + std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; + requester().tracker_warning("the tracker only resolves to an " + + tracker_address_type + " address, and you're listening on an " + + bind_address_type + " socket. This may prevent you from receiving incoming connections."); + } + } + else + { + target_address = *target; + } + + if (has_requester()) requester().m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); + m_target = target_address; + m_socket.reset(new datagram_socket(m_name_lookup.io_service())); + m_socket->open(target_address.protocol()); + m_socket->bind(udp::endpoint(bind_interface(), 0)); + m_socket->connect(target_address); + send_udp_connect(); + } + catch (std::exception& e) + { + fail(-1, e.what()); + }; + + void udp_tracker_connection::on_timeout() + { + m_socket.reset(); + m_name_lookup.cancel(); + fail_timeout(); + } + + void udp_tracker_connection::send_udp_connect() + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("==> UDP_TRACKER_CONNECT [" + + lexical_cast(tracker_req().info_hash) + "]"); + } +#endif + if (!m_socket) return; // the operation was aborted + + char send_buf[16]; + char* ptr = send_buf; + + if (m_transaction_id == 0) + m_transaction_id = rand() ^ (rand() << 16); + + // connection_id + detail::write_uint32(0x417, ptr); + detail::write_uint32(0x27101980, ptr); + // action (connect) + detail::write_int32(action_connect, ptr); + // transaction_id + detail::write_int32(m_transaction_id, ptr); + + m_socket->send(asio::buffer((void*)send_buf, 16), 0); + ++m_attempts; + m_buffer.resize(udp_buffer_size); + m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + } + + void udp_tracker_connection::connect_response(asio::error_code const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (!m_socket) return; // the operation was aborted + if (error) + { + fail(-1, error.message().c_str()); + return; + } + + if (m_target != m_sender) + { + // this packet was not received from the tracker + m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + return; + } + + if (bytes_transferred >= udp_buffer_size) + { + fail(-1, "udp response too big"); + return; + } + + if (bytes_transferred < 8) + { + fail(-1, "got a message with size < 8"); + return; + } + + restart_read_timeout(); + + const char* ptr = &m_buffer[0]; + int action = detail::read_int32(ptr); + int transaction = detail::read_int32(ptr); + + if (action == action_error) + { + fail(-1, std::string(ptr, bytes_transferred - 8).c_str()); + return; + } + + if (action != action_connect) + { + fail(-1, "invalid action in connect reply"); + return; + } + + if (m_transaction_id != transaction) + { + fail(-1, "incorrect transaction id"); + return; + } + + if (bytes_transferred < 16) + { + fail(-1, "udp_tracker_connection: " + "got a message with size < 16"); + return; + } + // reset transaction + m_transaction_id = 0; + m_attempts = 0; + m_connection_id = detail::read_int64(ptr); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + + lexical_cast(m_connection_id) + "]"); + } +#endif + + if (tracker_req().kind == tracker_request::announce_request) + send_udp_announce(); + else if (tracker_req().kind == tracker_request::scrape_request) + send_udp_scrape(); + } + catch (std::exception& e) + { + fail(-1, e.what()); + } + + void udp_tracker_connection::send_udp_announce() + { + if (m_transaction_id == 0) + m_transaction_id = rand() ^ (rand() << 16); + + if (!m_socket) return; // the operation was aborted + + std::vector buf; + std::back_insert_iterator > out(buf); + + tracker_request const& req = tracker_req(); + + // connection_id + detail::write_int64(m_connection_id, out); + // action (announce) + detail::write_int32(action_announce, out); + // transaction_id + detail::write_int32(m_transaction_id, out); + // info_hash + std::copy(req.info_hash.begin(), req.info_hash.end(), out); + // peer_id + std::copy(req.pid.begin(), req.pid.end(), out); + // downloaded + detail::write_int64(req.downloaded, out); + // left + detail::write_int64(req.left, out); + // uploaded + detail::write_int64(req.uploaded, out); + // event + detail::write_int32(req.event, out); + // ip address + if (m_settings.announce_ip != address() && m_settings.announce_ip.is_v4()) + detail::write_uint32(m_settings.announce_ip.to_v4().to_ulong(), out); + else + detail::write_int32(0, out); + // key + detail::write_int32(req.key, out); + // num_want + detail::write_int32(req.num_want, out); + // port + detail::write_uint16(req.listen_port, out); + // extensions + detail::write_uint16(0, out); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("==> UDP_TRACKER_ANNOUNCE [" + + lexical_cast(req.info_hash) + "]"); + } +#endif + + m_socket->send(asio::buffer(buf), 0); + ++m_attempts; + + m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + , bind(&udp_tracker_connection::announce_response, self(), _1, _2)); + } + + void udp_tracker_connection::send_udp_scrape() + { + if (m_transaction_id == 0) + m_transaction_id = rand() ^ (rand() << 16); + + if (!m_socket) return; // the operation was aborted + + std::vector buf; + std::back_insert_iterator > out(buf); + + // connection_id + detail::write_int64(m_connection_id, out); + // action (scrape) + detail::write_int32(action_scrape, out); + // transaction_id + detail::write_int32(m_transaction_id, out); + // info_hash + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end(), out); + + m_socket->send(asio::buffer(&buf[0], buf.size()), 0); + ++m_attempts; + + m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + , bind(&udp_tracker_connection::scrape_response, self(), _1, _2)); + } + + void udp_tracker_connection::announce_response(asio::error_code const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (!m_socket) return; // the operation was aborted + if (error) + { + fail(-1, error.message().c_str()); + return; + } + + if (m_target != m_sender) + { + // this packet was not received from the tracker + m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + , bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + return; + } + + if (bytes_transferred >= udp_buffer_size) + { + fail(-1, "udp response too big"); + return; + } + + if (bytes_transferred < 8) + { + fail(-1, "got a message with size < 8"); + return; + } + + restart_read_timeout(); + char* buf = &m_buffer[0]; + int action = detail::read_int32(buf); + int transaction = detail::read_int32(buf); + + if (transaction != m_transaction_id) + { + fail(-1, "incorrect transaction id"); + return; + } + + if (action == action_error) + { + fail(-1, std::string(buf, bytes_transferred - 8).c_str()); + return; + } + + if (action != action_announce) + { + fail(-1, "invalid action in announce response"); + return; + } + + if (bytes_transferred < 20) + { + fail(-1, "got a message with size < 20"); + return; + } + + int interval = detail::read_int32(buf); + int incomplete = detail::read_int32(buf); + int complete = detail::read_int32(buf); + int num_peers = (bytes_transferred - 20) / 6; + if ((bytes_transferred - 20) % 6 != 0) + { + fail(-1, "invalid udp tracker response length"); + return; + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); + } +#endif + + if (!has_requester()) + { + m_man.remove_request(this); + return; + } + + std::vector peer_list; + for (int i = 0; i < num_peers; ++i) + { + peer_entry e; + std::stringstream s; + s << (int)detail::read_uint8(buf) << "."; + s << (int)detail::read_uint8(buf) << "."; + s << (int)detail::read_uint8(buf) << "."; + s << (int)detail::read_uint8(buf); + e.ip = s.str(); + e.port = detail::read_uint16(buf); + e.pid.clear(); + peer_list.push_back(e); + } + + requester().tracker_response(tracker_req(), peer_list, interval + , complete, incomplete); + + m_man.remove_request(this); + return; + } + catch (std::exception& e) + { + fail(-1, e.what()); + }; // msvc 7.1 seems to require this + + void udp_tracker_connection::scrape_response(asio::error_code const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (!m_socket) return; // the operation was aborted + if (error) + { + fail(-1, error.message().c_str()); + return; + } + + if (m_target != m_sender) + { + // this packet was not received from the tracker + m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + , bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + return; + } + + if (bytes_transferred >= udp_buffer_size) + { + fail(-1, "udp response too big"); + return; + } + + if (bytes_transferred < 8) + { + fail(-1, "got a message with size < 8"); + return; + } + + restart_read_timeout(); + char* buf = &m_buffer[0]; + int action = detail::read_int32(buf); + int transaction = detail::read_int32(buf); + + if (transaction != m_transaction_id) + { + fail(-1, "incorrect transaction id"); + return; + } + + if (action == action_error) + { + fail(-1, std::string(buf, bytes_transferred - 8).c_str()); + return; + } + + if (action != action_scrape) + { + fail(-1, "invalid action in announce response"); + return; + } + + if (bytes_transferred < 20) + { + fail(-1, "got a message with size < 20"); + return; + } + + int complete = detail::read_int32(buf); + /*int downloaded = */detail::read_int32(buf); + int incomplete = detail::read_int32(buf); + + if (!has_requester()) + { + m_man.remove_request(this); + return; + } + + std::vector peer_list; + requester().tracker_response(tracker_req(), peer_list, 0 + , complete, incomplete); + + m_man.remove_request(this); + } + catch (std::exception& e) + { + fail(-1, e.what()); + } + +} + diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp new file mode 100644 index 000000000..c60725e9d --- /dev/null +++ b/libtorrent/src/upnp.cpp @@ -0,0 +1,1038 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/connection_queue.hpp" + +#include +#include +#include +#include +#include +#include + +#if (defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)) && !defined(TORRENT_UPNP_LOGGING) +#define TORRENT_UPNP_LOGGING +#endif + +using boost::bind; +using namespace libtorrent; + +address_v4 upnp::upnp_multicast_address; +udp::endpoint upnp::upnp_multicast_endpoint; + +namespace libtorrent +{ + bool is_local(address const& a) + { + if (a.is_v6()) return false; + address_v4 a4 = a.to_v4(); + return ((a4.to_ulong() & 0xff000000) == 0x0a000000 + || (a4.to_ulong() & 0xfff00000) == 0xac100000 + || (a4.to_ulong() & 0xffff0000) == 0xc0a80000); + } + + address_v4 guess_local_address(asio::io_service& ios) + { + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + // ignore the loopback + if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; + // ignore addresses that are not on a local network + if (!is_local(i->endpoint().address())) continue; + // ignore non-IPv4 addresses + if (i->endpoint().address().is_v4()) break; + } + if (i == udp::resolver_iterator()) return address_v4::any(); + return i->endpoint().address().to_v4(); + } +} + +upnp::upnp(io_service& ios, connection_queue& cc + , address const& listen_interface, std::string const& user_agent + , portmap_callback_t const& cb) + : m_udp_local_port(0) + , m_tcp_local_port(0) + , m_user_agent(user_agent) + , m_callback(cb) + , m_retry_count(0) + , m_socket(ios) + , m_broadcast_timer(ios) + , m_refresh_timer(ios) + , m_strand(ios) + , m_disabled(false) + , m_closing(false) + , m_cc(cc) +{ + // UPnP multicast address and port + upnp_multicast_address = address_v4::from_string("239.255.255.250"); + upnp_multicast_endpoint = udp::endpoint(upnp_multicast_address, 1900); + +#ifdef TORRENT_UPNP_LOGGING + m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); +#endif + rebind(listen_interface); +} + +upnp::~upnp() +{ +} + +void upnp::rebind(address const& listen_interface) try +{ + address_v4 bind_to = address_v4::any(); + if (listen_interface.is_v4() && listen_interface != address_v4::any()) + { + m_local_ip = listen_interface.to_v4(); + bind_to = listen_interface.to_v4(); + if (!is_local(m_local_ip)) + { + // the local address seems to be an external + // internet address. Assume it is not behind a NAT + throw std::runtime_error("local IP is not on a local network"); + } + } + else + { + m_local_ip = guess_local_address(m_socket.io_service()); + bind_to = address_v4::any(); + } + + if (!is_local(m_local_ip)) + { + throw std::runtime_error("local host is probably not on a NATed " + "network. disabling UPnP"); + } + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " local ip: " << m_local_ip.to_string() + << " bind to: " << bind_to.to_string() << std::endl; +#endif + + // the local interface hasn't changed + if (m_socket.is_open() + && m_socket.local_endpoint().address() == m_local_ip) + return; + + m_socket.close(); + + using namespace asio::ip::multicast; + + m_socket.open(udp::v4()); + m_socket.set_option(datagram_socket::reuse_address(true)); + m_socket.bind(udp::endpoint(bind_to, 0)); + + m_socket.set_option(join_group(upnp_multicast_address)); + m_socket.set_option(outbound_interface(bind_to)); + m_socket.set_option(hops(255)); + m_disabled = false; + + m_retry_count = 0; + discover_device(); +} +catch (std::exception& e) +{ + disable(); + std::stringstream msg; + msg << "UPnP portmapping disabled: " << e.what(); + m_callback(0, 0, msg.str()); +}; + +void upnp::discover_device() try +{ + const char msearch[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "ST:upnp:rootdevice\r\n" + "MAN:\"ssdp:discover\"\r\n" + "MX:3\r\n" + "\r\n\r\n"; + + m_socket.async_receive_from(asio::buffer(m_receive_buffer + , sizeof(m_receive_buffer)), m_remote, m_strand.wrap(bind( + &upnp::on_reply, this, _1, _2))); + + asio::error_code ec; +#ifdef TORRENT_DEBUG_UPNP + // simulate packet loss + if (m_retry_count & 1) +#endif + m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1) + , upnp_multicast_endpoint, 0, ec); + + if (ec) + { + disable(); + return; + } + + ++m_retry_count; + m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_broadcast_timer.async_wait(m_strand.wrap(bind(&upnp::resend_request + , this, _1))); + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> Broadcasting search for rootdevice" << std::endl; +#endif +} +catch (std::exception&) +{ + disable(); +} + +void upnp::set_mappings(int tcp, int udp) +{ +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " *** set mappings " << tcp << " " << udp; + if (m_disabled) m_log << " DISABLED"; + m_log << std::endl; +#endif + + if (m_disabled) return; + if (udp != 0) m_udp_local_port = udp; + if (tcp != 0) m_tcp_local_port = tcp; + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + if (d.mapping[0].local_port != m_tcp_local_port) + { + if (d.mapping[0].external_port == 0) + d.mapping[0].external_port = m_tcp_local_port; + d.mapping[0].local_port = m_tcp_local_port; + d.mapping[0].need_update = true; + } + if (d.mapping[1].local_port != m_udp_local_port) + { + if (d.mapping[1].external_port == 0) + d.mapping[1].external_port = m_udp_local_port; + d.mapping[1].local_port = m_udp_local_port; + d.mapping[1].need_update = true; + } + if (d.service_namespace + && (d.mapping[0].need_update || d.mapping[1].need_update)) + map_port(d, 0); + } +} + +void upnp::resend_request(asio::error_code const& e) +#ifndef NDEBUG +try +#endif +{ + if (e) return; + if (m_retry_count < 9 + && (m_devices.empty() || m_retry_count < 4)) + { + discover_device(); + return; + } + + if (m_devices.empty()) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " *** Got no response in 9 retries. Giving up, " + "disabling UPnP." << std::endl; +#endif + disable(); + return; + } + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + if (i->control_url.empty() && !i->upnp_connection && !i->disabled) + { + // we don't have a WANIP or WANPPP url for this device, + // ask for it + rootdevice& d = const_cast(*i); + try + { + d.upnp_connection.reset(new http_connection(m_socket.io_service() + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 + , boost::ref(d))))); + d.upnp_connection->get(d.url); + } + catch (std::exception& e) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " *** Connection failed to: " << d.url + << " " << e.what() << std::endl; +#endif + d.disabled = true; + } + } + } +} +#ifndef NDEBUG +catch (std::exception&) +{ + assert(false); +} +#endif + +void upnp::on_reply(asio::error_code const& e + , std::size_t bytes_transferred) +#ifndef NDEBUG +try +#endif +{ + using namespace libtorrent::detail; + if (e) return; + + // parse out the url for the device + +/* + the response looks like this: + + HTTP/1.1 200 OK + ST:upnp:rootdevice + USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice + Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc + Server: Custom/1.0 UPnP/1.0 Proc/Ver + EXT: + Cache-Control:max-age=180 + DATE: Fri, 02 Jan 1970 08:10:38 GMT +*/ + http_parser p; + try + { + p.incoming(buffer::const_interval(m_receive_buffer + , m_receive_buffer + bytes_transferred)); + } + catch (std::exception& e) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice responded with incorrect HTTP packet: " + << e.what() << ". Ignoring device" << std::endl; +#endif + return; + } + + if (p.status_code() != 200) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice responded with HTTP status: " << p.status_code() + << ". Ignoring device" << std::endl; +#endif + return; + } + + if (!p.header_finished()) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice responded with incomplete HTTP " + "packet. Ignoring device" << std::endl; +#endif + return; + } + + std::string url = p.header("location"); + if (url.empty()) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice response is missing a location header. " + "Ignoring device" << std::endl; +#endif + return; + } + + rootdevice d; + d.url = url; + + std::set::iterator i = m_devices.find(d); + + if (i == m_devices.end()) + { + + std::string protocol; + std::string auth; + // we don't have this device in our list. Add it + boost::tie(protocol, auth, d.hostname, d.port, d.path) + = parse_url_components(d.url); + + // ignore the auth here. It will be re-parsed + // by the http connection later + + if (protocol != "http") + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice uses unsupported protocol: '" << protocol + << "'. Ignoring device" << std::endl; +#endif + return; + } + + if (d.port == 0) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice responded with a url with port 0. " + "Ignoring device" << std::endl; +#endif + return; + } +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Found rootdevice: " << d.url << std::endl; +#endif + + if (m_tcp_local_port != 0) + { + d.mapping[0].need_update = true; + d.mapping[0].local_port = m_tcp_local_port; +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl; +#endif + } + if (m_udp_local_port != 0) + { + d.mapping[1].need_update = true; + d.mapping[1].local_port = m_udp_local_port; +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl; +#endif + } + boost::tie(i, boost::tuples::ignore) = m_devices.insert(d); + } + + + // since we're using udp, send the query 4 times + // just to make sure we find all devices + if (m_retry_count >= 4 && !m_devices.empty()) + { + m_broadcast_timer.cancel(); + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + if (i->control_url.empty() && !i->upnp_connection && !i->disabled) + { + // we don't have a WANIP or WANPPP url for this device, + // ask for it + rootdevice& d = const_cast(*i); + try + { + d.upnp_connection.reset(new http_connection(m_socket.io_service() + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 + , boost::ref(d))))); + d.upnp_connection->get(d.url); + } + catch (std::exception& e) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " *** Connection failed to: " << d.url + << " " << e.what() << std::endl; +#endif + d.disabled = true; + } + } + } + } +} +#ifndef NDEBUG +catch (std::exception&) +{ + assert(false); +}; +#endif + +void upnp::post(rootdevice& d, std::stringstream const& soap + , std::string const& soap_action) +{ + std::stringstream header; + + header << "POST " << d.control_url << " HTTP/1.1\r\n" + "Host: " << d.hostname << ":" << d.port << "\r\n" + "Content-Type: text/xml; charset=\"utf-8\"\r\n" + "Content-Length: " << soap.str().size() << "\r\n" + "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str(); + + d.upnp_connection->sendbuffer = header.str(); + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); +} + +void upnp::map_port(rootdevice& d, int i) +{ + if (d.upnp_connection) return; + + if (!d.mapping[i].need_update) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() << " *** mapping (" << i + << ") does not need update, skipping" << std::endl; +#endif + if (i < num_mappings - 1) + map_port(d, i + 1); + return; + } + d.mapping[i].need_update = false; + assert(!d.upnp_connection); + assert(d.service_namespace); + + d.upnp_connection.reset(new http_connection(m_socket.io_service() + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2 + , boost::ref(d), i)))); + + std::string soap_action = "AddPortMapping"; + + std::stringstream soap; + + soap << "\n" + "" + ""; + + soap << "" + "" << d.mapping[i].external_port << "" + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" + "" << d.mapping[i].local_port << "" + "" << m_local_ip.to_string() << "" + "1" + "" << m_user_agent << "" + "" << d.lease_duration << ""; + soap << ""; + + post(d, soap, soap_action); +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> AddPortMapping: " << soap.str() << std::endl; +#endif + +} + +// requires the mutex to be locked +void upnp::unmap_port(rootdevice& d, int i) +{ + if (d.mapping[i].external_port == 0) + { + if (i < num_mappings - 1) + { + unmap_port(d, i + 1); + } + else + { + m_devices.erase(d); + } + return; + } + d.upnp_connection.reset(new http_connection(m_socket.io_service() + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2 + , boost::ref(d), i)))); + + std::string soap_action = "DeletePortMapping"; + + std::stringstream soap; + + soap << "\n" + "" + ""; + + soap << "" + "" << d.mapping[i].external_port << "" + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; + soap << ""; + + post(d, soap, soap_action); +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> DeletePortMapping: " << soap.str() << std::endl; +#endif +} + +namespace +{ + struct parse_state + { + parse_state(): found_service(false), exit(false) {} + void reset(char const* st) + { + found_service = false; + exit = false; + service_type = st; + } + bool found_service; + bool exit; + std::string top_tag; + std::string control_url; + char const* service_type; + }; + + void find_control_url(int type, char const* string, parse_state& state) + { + if (state.exit) return; + + if (type == xml_start_tag) + { + if ((!state.top_tag.empty() && state.top_tag == "service") + || !strcmp(string, "service")) + { + state.top_tag = string; + } + } + else if (type == xml_end_tag) + { + if (!strcmp(string, "service")) + { + state.top_tag.clear(); + if (state.found_service) state.exit = true; + } + else if (!state.top_tag.empty() && state.top_tag != "service") + state.top_tag = "service"; + } + else if (type == xml_string) + { + if (state.top_tag == "serviceType") + { + if (!strcmp(string, state.service_type)) + state.found_service = true; + } + else if (state.top_tag == "controlURL") + { + state.control_url = string; + if (state.found_service) state.exit = true; + } + } + } + +} + +void upnp::on_upnp_xml(asio::error_code const& e + , libtorrent::http_parser const& p, rootdevice& d) try +{ + if (d.upnp_connection) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (e) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while fetching control url: " << e.message() << std::endl; +#endif + return; + } + + if (!p.header_finished()) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== incomplete http message" << std::endl; +#endif + return; + } + + parse_state s; + s.reset("urn:schemas-upnp-org:service:WANIPConnection:1"); + xml_parse((char*)p.get_body().begin, (char*)p.get_body().end + , m_strand.wrap(bind(&find_control_url, _1, _2, boost::ref(s)))); + if (s.found_service) + { + d.service_namespace = s.service_type; + } + else + { + // we didn't find the WAN IP connection, look for + // a PPP connection + s.reset("urn:schemas-upnp-org:service:WANPPPConnection:1"); + xml_parse((char*)p.get_body().begin, (char*)p.get_body().end + , m_strand.wrap(bind(&find_control_url, _1, _2, boost::ref(s)))); + if (s.found_service) + { + d.service_namespace = s.service_type; + } + else + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice response, did not find a port mapping interface" << std::endl; +#endif + return; + } + } + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== Rootdevice response, found control URL: " << s.control_url + << " namespace: " << d.service_namespace << std::endl; +#endif + + d.control_url = s.control_url; + + map_port(d, 0); +} +catch (std::exception&) +{ + disable(); +}; + +void upnp::disable() +{ + m_disabled = true; + m_devices.clear(); + m_broadcast_timer.cancel(); + m_refresh_timer.cancel(); + m_socket.close(); +} + +namespace +{ + struct error_code_parse_state + { + error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {} + bool in_error_code; + bool exit; + int error_code; + }; + + void find_error_code(int type, char const* string, error_code_parse_state& state) + { + if (state.exit) return; + if (type == xml_start_tag && !strcmp("errorCode", string)) + { + state.in_error_code = true; + } + else if (type == xml_string && state.in_error_code) + { + state.error_code = std::atoi(string); + state.exit = true; + } + } +} + +namespace +{ + struct error_code_t + { + int code; + char const* msg; + }; + + error_code_t error_codes[] = + { + {402, "Invalid Arguments"} + , {501, "Action Failed"} + , {714, "The specified value does not exist in the array"} + , {715, "The source IP address cannot be wild-carded"} + , {716, "The external port cannot be wild-carded"} + , {718, "The port mapping entry specified conflicts with " + "a mapping assigned previously to another client"} + , {724, "Internal and External port values must be the same"} + , {725, "The NAT implementation only supports permanent " + "lease times on port mappings"} + , {726, "RemoteHost must be a wildcard and cannot be a " + "specific IP address or DNS name"} + , {727, "ExternalPort must be a wildcard and cannot be a specific port "} + }; + +} + +void upnp::on_upnp_map_response(asio::error_code const& e + , libtorrent::http_parser const& p, rootdevice& d, int mapping) try +{ + if (d.upnp_connection) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (e) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while adding portmap: " << e.message() << std::endl; +#endif + m_devices.erase(d); + return; + } + + if (m_closing) return; + +// error code response may look like this: +// +// +// +// s:Client +// UPnPError +// +// +// 402 +// Invalid Args +// +// +// +// +// + + if (!p.header_finished()) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== incomplete http message" << std::endl; +#endif + m_devices.erase(d); + return; + } + + error_code_parse_state s; + xml_parse((char*)p.get_body().begin, (char*)p.get_body().end + , m_strand.wrap(bind(&find_error_code, _1, _2, boost::ref(s)))); + +#ifdef TORRENT_UPNP_LOGGING + if (s.error_code != -1) + { + m_log << time_now_string() + << " <== got error message: " << s.error_code << std::endl; + } +#endif + + if (s.error_code == 725) + { + // only permanent leases supported + d.lease_duration = 0; + d.mapping[mapping].need_update = true; + map_port(d, mapping); + return; + } + else if (s.error_code == 718) + { + // conflict in mapping, try next external port + ++d.mapping[mapping].external_port; + d.mapping[mapping].need_update = true; + map_port(d, mapping); + return; + } + else if (s.error_code != -1) + { + int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); + error_code_t* end = error_codes + num_errors; + error_code_t tmp = {s.error_code, 0}; + error_code_t* e = std::lower_bound(error_codes, end, tmp + , bind(&error_code_t::code, _1) < bind(&error_code_t::code, _2)); + std::string error_string = "UPnP mapping error "; + error_string += boost::lexical_cast(s.error_code); + if (e != end && e->code == s.error_code) + { + error_string += ": "; + error_string += e->msg; + } + m_callback(0, 0, error_string); + } + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== map response: " << std::string(p.get_body().begin, p.get_body().end) + << std::endl; +#endif + + if (s.error_code == -1) + { + int tcp = 0; + int udp = 0; + + if (mapping == 0) + tcp = d.mapping[mapping].external_port; + else + udp = d.mapping[mapping].external_port; + + m_callback(tcp, udp, ""); + if (d.lease_duration > 0) + { + d.mapping[mapping].expires = time_now() + + seconds(int(d.lease_duration * 0.75f)); + ptime next_expire = m_refresh_timer.expires_at(); + if (next_expire < time_now() + || next_expire > d.mapping[mapping].expires) + { + m_refresh_timer.expires_at(d.mapping[mapping].expires); + m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, this, _1))); + } + } + else + { + d.mapping[mapping].expires = max_time(); + } + } + + for (int i = 0; i < num_mappings; ++i) + { + if (d.mapping[i].need_update) + { + map_port(d, i); + return; + } + } +} +catch (std::exception&) +{ + disable(); +}; + +void upnp::on_upnp_unmap_response(asio::error_code const& e + , libtorrent::http_parser const& p, rootdevice& d, int mapping) try +{ + if (d.upnp_connection) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (e) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while deleting portmap: " << e.message() << std::endl; +#endif + } + + if (!p.header_finished()) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== incomplete http message" << std::endl; +#endif + return; + } + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end) + << std::endl; +#endif + + // ignore errors and continue with the next mapping for this device + if (mapping < num_mappings - 1) + { + unmap_port(d, mapping + 1); + return; + } + + // the main thread is likely to be waiting for + // all the unmap operations to complete + m_devices.erase(d); +} +catch (std::exception&) +{ + disable(); +}; + +void upnp::on_expire(asio::error_code const& e) try +{ + if (e) return; + + ptime now = time_now(); + ptime next_expire = max_time(); + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + for (int m = 0; m < num_mappings; ++m) + { + if (d.mapping[m].expires != max_time()) + continue; + + if (d.mapping[m].expires < now) + { + d.mapping[m].expires = max_time(); + map_port(d, m); + } + else if (d.mapping[m].expires < next_expire) + { + next_expire = d.mapping[m].expires; + } + } + } + if (next_expire != max_time()) + { + m_refresh_timer.expires_at(next_expire); + m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, this, _1))); + } +} +catch (std::exception&) +{ + disable(); +}; + +void upnp::close() +{ + m_refresh_timer.cancel(); + m_broadcast_timer.cancel(); + m_closing = true; + m_socket.close(); + + if (m_disabled) + { + m_devices.clear(); + return; + } + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end;) + { + rootdevice& d = const_cast(*i); + if (d.control_url.empty()) + { + m_devices.erase(i++); + continue; + } + ++i; + unmap_port(d, 0); + } +} + diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp new file mode 100644 index 000000000..41df77475 --- /dev/null +++ b/libtorrent/src/ut_pex.cpp @@ -0,0 +1,359 @@ +/* + +Copyright (c) 2006, MassaRoddel, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" + +#include "libtorrent/extensions/ut_pex.hpp" + +namespace libtorrent { namespace +{ + const char extension_name[] = "ut_pex"; + + enum + { + extension_index = 1, + max_peer_entries = 100 + }; + + bool send_peer(peer_connection const& p) + { + // don't send out peers that we haven't connected to + // (that have connected to us) + if (!p.is_local()) return false; + // don't send out peers that we haven't successfully connected to + if (p.is_connecting()) return false; + // ut pex does not support IPv6 + if (!p.remote().address().is_v4()) return false; + return true; + } + + struct ut_pex_plugin: torrent_plugin + { + ut_pex_plugin(torrent& t): m_torrent(t), m_1_minute(0) {} + + virtual boost::shared_ptr new_connection(peer_connection* pc); + + std::vector& get_ut_pex_msg() + { + return m_ut_pex_msg; + } + + // the second tick of the torrent + // each minute the new lists of "added" + "added.f" and "dropped" + // are calculated here and the pex message is created + // each peer connection will use this message + // max_peer_entries limits the packet size + virtual void tick() + { + if (++m_1_minute < 60) return; + + m_1_minute = 0; + + entry pex; + std::string& pla = pex["added"].string(); + std::string& pld = pex["dropped"].string(); + std::string& plf = pex["added.f"].string(); + std::back_insert_iterator pla_out(pla); + std::back_insert_iterator pld_out(pld); + std::back_insert_iterator plf_out(plf); + + std::set dropped; + m_old_peers.swap(dropped); + + int num_added = 0; + for (torrent::peer_iterator i = m_torrent.begin() + , end(m_torrent.end()); i != end; ++i) + { + if (!send_peer(*i->second)) continue; + + m_old_peers.insert(i->first); + + std::set::iterator di = dropped.find(i->first); + if (di == dropped.end()) + { + // don't write too big of a package + if (num_added >= max_peer_entries) break; + + // only send proper bittorrent peers + bt_peer_connection* p = dynamic_cast(i->second); + if (!p) continue; + + // i->first was added since the last time + detail::write_endpoint(i->first, pla_out); + // no supported flags to set yet + // 0x01 - peer supports encryption + // 0x02 - peer is a seed + int flags = p->is_seed() ? 2 : 0; +#ifndef TORRENT_DISABLE_ENCRYPTION + flags |= p->supports_encryption() ? 1 : 0; +#endif + detail::write_uint8(flags, plf_out); + ++num_added; + } + else + { + // this was in the previous message + // so, it wasn't dropped + dropped.erase(di); + } + } + + for (std::set::const_iterator i = dropped.begin() + , end(dropped.end());i != end; ++i) + { + if (!i->address().is_v4()) continue; + detail::write_endpoint(*i, pld_out); + } + + m_ut_pex_msg.clear(); + bencode(std::back_inserter(m_ut_pex_msg), pex); + } + + private: + torrent& m_torrent; + + std::set m_old_peers; + int m_1_minute; + std::vector m_ut_pex_msg; + }; + + + struct ut_pex_peer_plugin : peer_plugin + { + ut_pex_peer_plugin(torrent& t, peer_connection& pc, ut_pex_plugin& tp) + : m_torrent(t) + , m_pc(pc) + , m_tp(tp) + , m_1_minute(0) + , m_message_index(0) + , m_first_time(true) + {} + + virtual void add_handshake(entry& h) + { + entry& messages = h["m"]; + messages[extension_name] = extension_index; + } + + virtual bool on_extension_handshake(entry const& h) + { + entry const& messages = h["m"]; + + if (entry const* index = messages.find_key(extension_name)) + { + m_message_index = index->integer(); + return true; + } + else + { + m_message_index = 0; + return false; + } + } + + virtual bool on_extended(int length, int msg, buffer::const_interval body) + try + { + if (msg != extension_index) return false; + if (m_message_index == 0) return false; + + if (length > 500 * 1024) + throw protocol_error("ut peer exchange message larger than 500 kB"); + + if (body.left() < length) return true; + + entry pex_msg = bdecode(body.begin, body.end); + std::string const& peers = pex_msg["added"].string(); + std::string const& peer_flags = pex_msg["added.f"].string(); + + int num_peers = peers.length() / 6; + char const* in = peers.c_str(); + char const* fin = peer_flags.c_str(); + + if (int(peer_flags.size()) != num_peers) + return true; + + peer_id pid(0); + policy& p = m_torrent.get_policy(); + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v4_endpoint(in); + char flags = detail::read_uint8(fin); + p.peer_from_tracker(adr, pid, peer_info::pex, flags); + } + return true; + } + catch (std::exception&) + { + return true; + } + + // the peers second tick + // every minute we send a pex message + virtual void tick() + { + if (!m_message_index) return; // no handshake yet + if (++m_1_minute <= 60) return; + + if (m_first_time) + { + send_ut_peer_list(); + m_first_time = false; + } + else + { + send_ut_peer_diff(); + } + m_1_minute = 0; + } + + private: + + void send_ut_peer_diff() + { + std::vector const& pex_msg = m_tp.get_ut_pex_msg(); + + buffer::interval i = m_pc.allocate_send_buffer(6 + pex_msg.size()); + + detail::write_uint32(1 + 1 + pex_msg.size(), i.begin); + detail::write_uint8(bt_peer_connection::msg_extended, i.begin); + detail::write_uint8(m_message_index, i.begin); + std::copy(pex_msg.begin(), pex_msg.end(), i.begin); + i.begin += pex_msg.size(); + + assert(i.begin == i.end); + m_pc.setup_send(); + } + + void send_ut_peer_list() + { + entry pex; + // leave the dropped string empty + pex["dropped"].string(); + std::string& pla = pex["added"].string(); + std::string& plf = pex["added.f"].string(); + std::back_insert_iterator pla_out(pla); + std::back_insert_iterator plf_out(plf); + + int num_added = 0; + for (torrent::peer_iterator i = m_torrent.begin() + , end(m_torrent.end()); i != end; ++i) + { + if (!send_peer(*i->second)) continue; + + // don't write too big of a package + if (num_added >= max_peer_entries) break; + + // only send proper bittorrent peers + bt_peer_connection* p = dynamic_cast(i->second); + if (!p) continue; + + // i->first was added since the last time + detail::write_endpoint(i->first, pla_out); + // no supported flags to set yet + // 0x01 - peer supports encryption + // 0x02 - peer is a seed + int flags = p->is_seed() ? 2 : 0; +#ifndef TORRENT_DISABLE_ENCRYPTION + flags |= p->supports_encryption() ? 1 : 0; +#endif + detail::write_uint8(flags, plf_out); + ++num_added; + } + std::vector pex_msg; + bencode(std::back_inserter(pex_msg), pex); + + buffer::interval i = m_pc.allocate_send_buffer(6 + pex_msg.size()); + + detail::write_uint32(1 + 1 + pex_msg.size(), i.begin); + detail::write_uint8(bt_peer_connection::msg_extended, i.begin); + detail::write_uint8(m_message_index, i.begin); + std::copy(pex_msg.begin(), pex_msg.end(), i.begin); + i.begin += pex_msg.size(); + + assert(i.begin == i.end); + m_pc.setup_send(); + } + + torrent& m_torrent; + peer_connection& m_pc; + ut_pex_plugin& m_tp; + int m_1_minute; + int m_message_index; + + // this is initialized to true, and set to + // false after the first pex message has been sent. + // it is used to know if a diff message or a full + // message should be sent. + bool m_first_time; + }; + + boost::shared_ptr ut_pex_plugin::new_connection(peer_connection* pc) + { + bt_peer_connection* c = dynamic_cast(pc); + if (!c) return boost::shared_ptr(); + return boost::shared_ptr(new ut_pex_peer_plugin(m_torrent + , *pc, *this)); + } +}} + +namespace libtorrent +{ + + boost::shared_ptr create_ut_pex_plugin(torrent* t) + { + if (t->torrent_file().priv()) + { + return boost::shared_ptr(); + } + return boost::shared_ptr(new ut_pex_plugin(*t)); + } + +} + + diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp new file mode 100755 index 000000000..5a8acf512 --- /dev/null +++ b/libtorrent/src/web_peer_connection.cpp @@ -0,0 +1,639 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +using boost::bind; +using boost::shared_ptr; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + web_peer_connection::web_peer_connection( + session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote + , std::string const& url + , policy::peer* peerinfo) + : peer_connection(ses, t, s, remote, peerinfo) + , m_url(url) + , m_first_request(true) + { + INVARIANT_CHECK; + + // we always prefer downloading entire + // pieces from web seeds + prefer_whole_pieces(true); + // we want large blocks as well, so + // we can request more bytes at once + request_large_blocks(true); + // we only want left-over bandwidth + set_non_prioritized(true); + shared_ptr tor = t.lock(); + assert(tor); + int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); + + // multiply with the blocks per piece since that many requests are + // merged into one http request + m_max_out_request_queue = ses.settings().urlseed_pipeline_size + * blocks_per_piece; + + // since this is a web seed, change the timeout + // according to the settings. + set_timeout(ses.settings().urlseed_timeout); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "*** web_peer_connection\n"; +#endif + + std::string protocol; + boost::tie(protocol, m_auth, m_host, m_port, m_path) + = parse_url_components(url); + + if (!m_auth.empty()) + m_auth = base64encode(m_auth); + + m_server_string = "URL seed @ "; + m_server_string += m_host; + } + + web_peer_connection::~web_peer_connection() + {} + + boost::optional + web_peer_connection::downloading_piece_progress() const + { + if (m_requests.empty()) + return boost::optional(); + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + piece_block_progress ret; + + ret.piece_index = m_requests.front().piece; + if (!m_piece.empty()) + { + ret.bytes_downloaded = int(m_piece.size()); + } + else + { + if (!m_parser.header_finished()) + { + ret.bytes_downloaded = 0; + } + else + { + int receive_buffer_size = receive_buffer().left() - m_parser.body_start(); + ret.bytes_downloaded = receive_buffer_size % t->block_size(); + } + } + ret.block_index = (m_requests.front().start + ret.bytes_downloaded) / t->block_size(); + ret.full_block_bytes = t->block_size(); + const int last_piece = t->torrent_file().num_pieces() - 1; + if (ret.piece_index == last_piece && ret.block_index + == t->torrent_file().piece_size(last_piece) / t->block_size()) + ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size(); + return ret; + } + + void web_peer_connection::on_connected() + { + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + // this is always a seed + incoming_bitfield(std::vector( + t->torrent_file().num_pieces(), true)); + // it is always possible to request pieces + incoming_unchoke(); + + reset_recv_buffer(t->block_size() + 1024); + } + + void web_peer_connection::write_request(peer_request const& r) + { + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + assert(t->valid_metadata()); + + bool single_file_request = false; + if (!m_path.empty() && m_path[m_path.size() - 1] != '/') + single_file_request = true; + + torrent_info const& info = t->torrent_file(); + + std::string request; + request.reserve(400); + + int size = r.length; + const int block_size = t->block_size(); + while (size > 0) + { + int request_size = std::min(block_size, size); + peer_request pr = {r.piece, r.start + r.length - size + , request_size}; + m_requests.push_back(pr); + size -= request_size; + } + + proxy_settings const& ps = m_ses.web_seed_proxy(); + bool using_proxy = ps.type == proxy_settings::http + || ps.type == proxy_settings::http_pw; + + if (single_file_request) + { + request += "GET "; + // do not encode single file paths, they are + // assumed to be encoded in the torrent file + request += using_proxy ? m_url : m_path; + request += " HTTP/1.1\r\n"; + request += "Host: "; + request += m_host; + if (m_first_request) + { + request += "\r\nUser-Agent: "; + request += m_ses.settings().user_agent; + } + if (!m_auth.empty()) + { + request += "\r\nAuthorization: Basic "; + request += m_auth; + } + if (ps.type == proxy_settings::http_pw) + { + request += "\r\nProxy-Authorization: Basic "; + request += base64encode(ps.username + ":" + ps.password); + } + if (using_proxy) + { + request += "\r\nProxy-Connection: keep-alive"; + } + request += "\r\nRange: bytes="; + request += boost::lexical_cast(r.piece + * info.piece_length() + r.start); + request += "-"; + request += boost::lexical_cast(r.piece + * info.piece_length() + r.start + r.length - 1); + if (m_first_request || using_proxy) + request += "\r\nConnection: keep-alive"; + request += "\r\n\r\n"; + m_first_request = false; + m_file_requests.push_back(0); + } + else + { + std::vector files = info.map_block(r.piece, r.start + , r.length); + + for (std::vector::iterator i = files.begin(); + i != files.end(); ++i) + { + file_slice const& f = *i; + + request += "GET "; + if (using_proxy) + { + request += m_url; + std::string path = info.file_at(f.file_index).path.string(); + request += escape_path(path.c_str(), path.length()); + } + else + { + std::string path = m_path; + path += info.file_at(f.file_index).path.string(); + request += escape_path(path.c_str(), path.length()); + } + request += " HTTP/1.1\r\n"; + request += "Host: "; + request += m_host; + if (m_first_request) + { + request += "\r\nUser-Agent: "; + request += m_ses.settings().user_agent; + } + if (!m_auth.empty()) + { + request += "\r\nAuthorization: Basic "; + request += m_auth; + } + if (ps.type == proxy_settings::http_pw) + { + request += "\r\nProxy-Authorization: Basic "; + request += base64encode(ps.username + ":" + ps.password); + } + if (using_proxy) + { + request += "\r\nProxy-Connection: keep-alive"; + } + request += "\r\nRange: bytes="; + request += boost::lexical_cast(f.offset); + request += "-"; + request += boost::lexical_cast(f.offset + f.size - 1); + if (m_first_request || using_proxy) + request += "\r\nConnection: keep-alive"; + request += "\r\n\r\n"; + m_first_request = false; + assert(f.file_index >= 0); + m_file_requests.push_back(f.file_index); + } + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << request << "\n"; +#endif + + send_buffer(request.c_str(), request.c_str() + request.size()); + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + namespace + { + bool range_contains(peer_request const& range, peer_request const& req) + { + return range.start <= req.start + && range.start + range.length >= req.start + req.length; + } + } + + // throws exception when the client should be disconnected + void web_peer_connection::on_receive(asio::error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) return; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + incoming_piece_fragment(); + + for (;;) + { + buffer::const_interval recv_buffer = receive_buffer(); + + int payload; + int protocol; + bool header_finished = m_parser.header_finished(); + if (!header_finished) + { + boost::tie(payload, protocol) = m_parser.incoming(recv_buffer); + m_statistics.received_bytes(payload, protocol); + + assert(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); + + assert(recv_buffer.left() <= packet_size()); + + // this means the entire status line hasn't been received yet + if (m_parser.status_code() == -1) break; + + // if the status code is not one of the accepted ones, abort + if (m_parser.status_code() != 206 // partial content + && m_parser.status_code() != 200 // OK + && !(m_parser.status_code() >= 300 // redirect + && m_parser.status_code() < 400)) + { + // we should not try this server again. + t->remove_url_seed(m_url); + std::string error_msg = boost::lexical_cast(m_parser.status_code()) + + " " + m_parser.message(); + if (m_ses.m_alerts.should_post(alert::warning)) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.m_alerts.post_alert(url_seed_alert(t->get_handle(), url() + , error_msg)); + } + throw std::runtime_error(error_msg); + } + if (!m_parser.header_finished()) break; + + m_body_start = m_parser.body_start(); + m_received_body = 0; + } + else + { + m_statistics.received_bytes(bytes_transferred, 0); + } + + // we just completed reading the header + if (!header_finished) + { + if (m_parser.status_code() >= 300 && m_parser.status_code() < 400) + { + // this means we got a redirection request + // look for the location header + std::string location = m_parser.header("location"); + + if (location.empty()) + { + // we should not try this server again. + t->remove_url_seed(m_url); + throw std::runtime_error("got HTTP redirection status without location header"); + } + + bool single_file_request = false; + if (!m_path.empty() && m_path[m_path.size() - 1] != '/') + single_file_request = true; + + // add the redirected url and remove the current one + if (!single_file_request) + { + assert(!m_file_requests.empty()); + int file_index = m_file_requests.front(); + + torrent_info const& info = t->torrent_file(); + std::string path = info.file_at(file_index).path.string(); + path = escape_path(path.c_str(), path.length()); + size_t i = location.rfind(path); + if (i == std::string::npos) + { + t->remove_url_seed(m_url); + throw std::runtime_error("got invalid HTTP redirection location (\"" + location + "\") " + "expected it to end with: " + path); + } + location.resize(i); + } + t->add_url_seed(location); + t->remove_url_seed(m_url); + throw std::runtime_error("redirecting to " + location); + } + + std::string server_version = m_parser.header("server"); + if (!server_version.empty()) + { + m_server_string = "URL seed @ "; + m_server_string += m_host; + m_server_string += " ("; + m_server_string += server_version; + m_server_string += ")"; + } + + m_body_start = m_parser.body_start(); + m_received_body = 0; + } + + recv_buffer.begin += m_body_start; + // we only received the header, no data + if (recv_buffer.left() == 0) break; + + size_type range_start; + size_type range_end; + if (m_parser.status_code() == 206) + { + std::stringstream range_str(m_parser.header("content-range")); + char dummy; + std::string bytes; + range_str >> bytes >> range_start >> dummy >> range_end; + if (!range_str) + { + // we should not try this server again. + t->remove_url_seed(m_url); + throw std::runtime_error("invalid range in HTTP response: " + range_str.str()); + } + // the http range is inclusive + range_end++; + } + else + { + range_start = 0; + range_end = m_parser.header("content-length"); + if (range_end == -1) + { + // we should not try this server again. + t->remove_url_seed(m_url); + throw std::runtime_error("no content-length in HTTP response"); + } + } + + torrent_info const& info = t->torrent_file(); + + if (m_requests.empty() || m_file_requests.empty()) + throw std::runtime_error("unexpected HTTP response"); + + int file_index = m_file_requests.front(); + peer_request in_range = info.map_file(file_index, range_start + , range_end - range_start); + + peer_request front_request = m_requests.front(); + + if (in_range.piece != front_request.piece + || in_range.start > front_request.start + int(m_piece.size())) + { + throw std::runtime_error("invalid range in HTTP response"); + } + + // skip the http header and the blocks we've already read. The + // http_body.begin is now in sync with the request at the front + // of the request queue + assert(in_range.start - int(m_piece.size()) <= front_request.start); + + // the http response body consists of 3 parts + // 1. the middle of a block or the ending of a block + // 2. a number of whole blocks + // 3. the start of a block + // in that order, these parts are parsed. + + bool range_overlaps_request = in_range.start + in_range.length + > front_request.start + int(m_piece.size()); + + // if the request is contained in the range (i.e. the entire request + // fits in the range) we should not start a partial piece, since we soon + // will receive enough to call incoming_piece() and pass the read buffer + // directly (in the next loop below). + if (range_overlaps_request && !range_contains(in_range, front_request)) + { + // the start of the next block to receive is stored + // in m_piece. We need to append the rest of that + // block from the http receive buffer and then + // (if it completed) call incoming_piece() with + // m_piece as buffer. + + int piece_size = int(m_piece.size()); + int copy_size = std::min(std::min(front_request.length - piece_size + , recv_buffer.left()), int(range_end - range_start - m_received_body)); + m_piece.resize(piece_size + copy_size); + assert(copy_size > 0); + std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size); + assert(int(m_piece.size()) <= front_request.length); + recv_buffer.begin += copy_size; + m_received_body += copy_size; + m_body_start += copy_size; + assert(m_received_body <= range_end - range_start); + assert(int(m_piece.size()) <= front_request.length); + if (int(m_piece.size()) == front_request.length) + { + // each call to incoming_piece() may result in us becoming + // a seed. If we become a seed, all seeds we're connected to + // will be disconnected, including this web seed. We need to + // check for the disconnect condition after the call. + + m_requests.pop_front(); + incoming_piece(front_request, &m_piece[0]); + if (associated_torrent().expired()) return; + cut_receive_buffer(m_body_start, t->block_size() + 1024); + m_body_start = 0; + recv_buffer = receive_buffer(); + assert(m_received_body <= range_end - range_start); + m_piece.clear(); + assert(m_piece.empty()); + } + } + + // report all received blocks to the bittorrent engine + while (!m_requests.empty() + && range_contains(in_range, m_requests.front()) + && recv_buffer.left() >= m_requests.front().length) + { + peer_request r = m_requests.front(); + m_requests.pop_front(); + assert(recv_buffer.left() >= r.length); + + incoming_piece(r, recv_buffer.begin); + if (associated_torrent().expired()) return; + m_received_body += r.length; + assert(receive_buffer().begin + m_body_start == recv_buffer.begin); + assert(m_received_body <= range_end - range_start); + cut_receive_buffer(r.length + m_body_start, t->block_size() + 1024); + m_body_start = 0; + recv_buffer = receive_buffer(); + } + + if (!m_requests.empty()) + { + range_overlaps_request = in_range.start + in_range.length + > m_requests.front().start + int(m_piece.size()); + + if (in_range.start + in_range.length < m_requests.front().start + m_requests.front().length + && (m_received_body + recv_buffer.left() >= range_end - range_start)) + { + int piece_size = int(m_piece.size()); + int copy_size = std::min(std::min(m_requests.front().length - piece_size + , recv_buffer.left()), int(range_end - range_start - m_received_body)); + assert(copy_size >= 0); + if (copy_size > 0) + { + m_piece.resize(piece_size + copy_size); + std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size); + recv_buffer.begin += copy_size; + m_received_body += copy_size; + m_body_start += copy_size; + } + assert(m_received_body == range_end - range_start); + } + } + + assert(m_received_body <= range_end - range_start); + if (m_received_body == range_end - range_start) + { + cut_receive_buffer(recv_buffer.begin - receive_buffer().begin + , t->block_size() + 1024); + recv_buffer = receive_buffer(); + m_file_requests.pop_front(); + m_parser.reset(); + m_body_start = 0; + m_received_body = 0; + continue; + } + break; + } + } + + void web_peer_connection::get_specific_peer_info(peer_info& p) const + { + if (is_interesting()) p.flags |= peer_info::interesting; + if (is_choked()) p.flags |= peer_info::choked; + if (is_peer_interested()) p.flags |= peer_info::remote_interested; + if (has_peer_choked()) p.flags |= peer_info::remote_choked; + if (is_local()) p.flags |= peer_info::local_connection; + if (!is_connecting() && m_server_string.empty()) + p.flags |= peer_info::handshake; + if (is_connecting() && !is_queued()) p.flags |= peer_info::connecting; + if (is_queued()) p.flags |= peer_info::queued; + + p.client = m_server_string; + p.connection_type = peer_info::web_seed; + } + + bool web_peer_connection::in_handshake() const + { + return m_server_string.empty(); + } + + // throws exception when the client should be disconnected + void web_peer_connection::on_sent(asio::error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) return; + m_statistics.sent_bytes(0, bytes_transferred); + } + + +#ifndef NDEBUG + void web_peer_connection::check_invariant() const + { +/* + assert(m_num_pieces == std::count( + m_have_piece.begin() + , m_have_piece.end() + , true)); +*/ } +#endif + +} + diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..7a27d2ee0 --- /dev/null +++ b/setup.py @@ -0,0 +1,115 @@ +# setup.py +# +# Copyright (c) 2007 Andrew Resch ('andar') +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import ez_setup +ez_setup.use_setuptools() + +from setuptools import setup, find_packages, Extension +import platform +import glob + +python_version = platform.python_version()[0:3] + +# The libtorrent extension +__extra_compile_args = [ + "-Wno-missing-braces", + "-DHAVE_INCLUDE_LIBTORRENT_ASIO____ASIO_HPP=1", + "-DHAVE_INCLUDE_LIBTORRENT_ASIO_SSL_STREAM_HPP=1", + "-DHAVE_INCLUDE_LIBTORRENT_ASIO_IP_TCP_HPP=1", + "-DHAVE_PTHREAD=1", + "-DTORRENT_USE_OPENSSL=1", + "-DHAVE_SSL=1" +] + +__include_dirs = [ + './libtorrent', + './libtorrent/include', + './libtorrent/include/libtorrent', + '/usr/include/python' + python_version +] + +__libraries = [ + 'boost_filesystem', + 'boost_date_time', + 'boost_thread', + 'z', + 'pthread', + 'ssl' +] + +__sources = glob.glob("./libtorrent/src/*.cpp") + glob.glob("./libtorrent/src/kademelia/*.cpp") + glob.glob("./libtorrent/bindings/python/src/*.cpp") + +# Remove file_win.cpp as it is only for Windows builds +for source in __sources: + if "file_win.cpp" in source: + __sources.remove(source) + break + +libtorrent = Extension( + 'libtorrent', + include_dirs = __include_dirs, + libraries = __libraries, + extra_compile_args = __extra_compile_args, + sources = __sources +) + +print find_packages("deluge") +# Main setup + +__data_files = [ + # ('share/deluge/glade', glob.glob("share/deluge/glade/*.glade")), + # ('share/deluge/pixmaps', glob.glob('share/deluge/pixmaps/*.png')), + ('share/applications' , ["deluge/share/applications/deluge.desktop"]), + ('share/pixmaps' , ["deluge/share/pixmaps/deluge.xpm"]) +] + +setup( + name = "deluge", + fullname = "Deluge Bittorent Client", + version = "0.6", + author = "Zach Tibbitts, Alon Zakai, Marcos Pinto, Andrew Resch", + author_email = "zach@collegegeek.org, kripkensteiner@gmail.com, marcospinto@dipconsultants.com, andrewresch@gmail.com", + description = "GTK+ bittorrent client", + url = "http://deluge-torrent.org", + license = "GPLv2", + +# packages = find_packages("deluge"), + include_package_data = True, + #scripts = ["scripts/deluge"], + data_files = __data_files, + ext_package = "deluge", + ext_modules = [libtorrent], + packages=['deluge'], + package_dir = {'deluge': 'deluge/src'}, + entry_points = """ + [console_scripts] + deluge = deluge.main:main + """ +) From 29c4b6aee1c687c2b1529011382ddb26080043b8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 5 Jul 2007 19:35:59 +0000 Subject: [PATCH 0002/1009] Just some updates.. Probably going to change to DBUS instead of Pyro in next revision. --- deluge/src/common.py | 54 ++++++++++++++++++++++++ deluge/src/config.py | 98 ++++++++++++++++++++++++++++++++++++++++++++ deluge/src/core.py | 11 ++++- deluge/src/daemon.py | 10 +++-- deluge/src/main.py | 22 +++++----- deluge/src/ui.py | 12 +++--- 6 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 deluge/src/common.py create mode 100644 deluge/src/config.py diff --git a/deluge/src/common.py b/deluge/src/common.py new file mode 100644 index 000000000..d0d04510d --- /dev/null +++ b/deluge/src/common.py @@ -0,0 +1,54 @@ +# +# common.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging +import pkg_resources +import xdg, xdg.BaseDirectory +import os.path + +# Get the logger +log = logging.getLogger("deluge") + +def get_version(): + """Returns the program version from the egg metadata""" + return pkg_resources.require("Deluge")[0].version + +def get_config_dir(filename=None): + """ Returns the CONFIG_DIR path if no filename is specified + Returns the CONFIG_DIR + filename as a path if filename is specified + """ + if filename != None: + return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename) + else: + return xdg.BaseDirectory.save_config_path("deluge") + diff --git a/deluge/src/config.py b/deluge/src/config.py new file mode 100644 index 000000000..4b7af2d1d --- /dev/null +++ b/deluge/src/config.py @@ -0,0 +1,98 @@ +# +# config.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging +import pickle + +import deluge.common + +# Get the logger +log = logging.getLogger("deluge") + +class Config: + def __init__(self, filename, defaults=None): + log.debug("Config created with filename: %s", filename) + log.debug("Config defaults: %s", defaults) + self.config = {} + # If defaults is not None then we need to use "defaults". + if defaults != None: + self.config = defaults + + # Load the config from file in the config_dir + self.config_file = deluge.common.get_config_dir(filename) + self.load(self.config_file) + + def load(self, filename=None): + # Use self.config_file if filename is None + if filename is None: + filename = self.config_file + try: + # Un-pickle the file and update the config dictionary + log.debug("Opening pickled file for load..") + pkl_file = open(filename, "rb") + filedump = pickle.load(pkl_file) + self.config.update(filedump) + pkl_file.close() + except IOError: + log.warning("IOError: Unable to load file '%s'", filename) + except EOFError: + log.debug("Closing pickled file..") + pkl_file.close() + + def save(self, filename=None): + # Saves the config dictionary + if filename is None: + filename = self.config_file + try: + log.debug("Opening pickled file for save..") + pkl_file = open(filename, "wb") + pickle.dump(self.config, pkl_file) + log.debug("Closing pickled file..") + pkl_file.close() + except IOError: + log.warning("IOError: Unable to save file '%s'", filename) + + def set(self, key, value): + # Sets the "key" with "value" in the config dict + log.debug("Setting '%s' to %s", key, value) + self.config[key] = value + + def get(self, key): + # Attempts to get the "key" value and returns None if the key is invalid + try: + value = self.config[key] + log.debug("Getting '%s' as %s", key, value) + return value + except KeyError: + log.warning("Key does not exist, returning None") + return None diff --git a/deluge/src/core.py b/deluge/src/core.py index 9e73020df..6019f67c1 100644 --- a/deluge/src/core.py +++ b/deluge/src/core.py @@ -31,13 +31,22 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -# Instantiate the logger import logging + +from deluge.config import Config +import deluge.common + +# Get the logger log = logging.getLogger("deluge") +DEFAULT_PREFS = { +} + class Core: def __init__(self): log.debug("Core init..") + + self.config = Config("core.conf", DEFAULT_PREFS) def test(self): print "test" diff --git a/deluge/src/daemon.py b/deluge/src/daemon.py index 3158938f0..0a756a485 100644 --- a/deluge/src/daemon.py +++ b/deluge/src/daemon.py @@ -31,20 +31,22 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -# Instantiate the logger import logging -log = logging.getLogger("deluge") import Pyro.core + from deluge.core import Core +# Get the logger +log = logging.getLogger("deluge") + class Daemon: def __init__(self): # Instantiate the Manager class self.core = Core() # Initialize the Pyro core and daemon Pyro.core.initServer(banner=0) - log.info("Pyro server initiliazed..") + log.debug("Pyro server initiliazed..") self.daemon = Pyro.core.Daemon() # Connect the Manager to the Pyro server obj = Pyro.core.ObjBase() @@ -56,6 +58,6 @@ class Daemon: # Start the main loop for the pyro daemon self.daemon.requestLoop() - def getURI(self): + def get_uri(self): # Return the URI for the Pyro server return self.uri diff --git a/deluge/src/main.py b/deluge/src/main.py index 13d8b680e..9f5110d6b 100644 --- a/deluge/src/main.py +++ b/deluge/src/main.py @@ -1,9 +1,6 @@ -#!/usr/bin/env python # # main.py # -# Copyright (C) Zach Tibbitts 2006 -# Copyright (C) Alon Zakai 2006 # Copyright (C) Andrew Resch 2007 # # Deluge is free software. @@ -37,16 +34,16 @@ # The main starting point for the program. This function is called when the # user runs the command 'deluge'. +import logging import os import signal - from optparse import OptionParser -import deluge.common + from deluge.daemon import Daemon from deluge.ui import Ui +import deluge.common # Setup the logger -import logging logging.basicConfig( level=logging.DEBUG, format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" @@ -55,10 +52,10 @@ logging.basicConfig( log = logging.getLogger("deluge") def main(): - log.info("Starting Deluge..") - # Setup the argument parser - parser = OptionParser(usage="%prog [options] [actions]", version=deluge.common.PROGRAM_VERSION) + # FIXME: need to use deluge.common to fill in version + parser = OptionParser(usage="%prog [options] [actions]", + version=deluge.common.get_version()) parser.add_option("--daemon", dest="daemon", help="Start Deluge daemon", metavar="DAEMON", action="store_true", default=False) parser.add_option("--ui", dest="ui", help="Start Deluge UI", @@ -66,10 +63,12 @@ def main(): # Get the options and args from the OptionParser (options, args) = parser.parse_args() + + log.info("Deluge %s", deluge.common.get_version()) log.debug("options: %s", options) log.debug("args: %s", args) - + daemon = None pid = None uri = None @@ -78,8 +77,9 @@ def main(): if options.daemon: log.info("Starting daemon..") daemon = Daemon() - uri = daemon.getURI() + uri = daemon.get_uri() # We need to fork() the process to run it in the background... + # FIXME: We cannot use fork() on Windows pid = os.fork() if not pid: daemon.start() diff --git a/deluge/src/ui.py b/deluge/src/ui.py index ef8113bc2..1d16110a7 100644 --- a/deluge/src/ui.py +++ b/deluge/src/ui.py @@ -31,18 +31,20 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -# Instantiate the logger import logging -log = logging.getLogger("deluge") import Pyro.core +# Get the logger +log = logging.getLogger("deluge") + class Ui: def __init__(self, core_uri): log.debug("Ui init..") log.debug("core_uri: %s", core_uri) # Get the core manager from the Pyro server - self.core = Pyro.core.getProxyForURI(core_uri) - # Test - self.core.test() + if core_uri != None: + self.core = Pyro.core.getProxyForURI(core_uri) + # Test + self.core.test() From 59656397d0bb57e30d78ba3cacc11b220ac3235f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 8 Jul 2007 00:28:17 +0000 Subject: [PATCH 0003/1009] Changed from Pyro to DBUS.. Still doesn't do anything. --- deluge/src/core.py | 30 +++++++++++++++++++++++++---- deluge/src/daemon.py | 45 +++++++++++++++++++++++--------------------- deluge/src/main.py | 9 +++++---- deluge/src/ui.py | 31 +++++++++++++++++++++--------- setup.py | 27 ++++++++++++-------------- 5 files changed, 89 insertions(+), 53 deletions(-) diff --git a/deluge/src/core.py b/deluge/src/core.py index 6019f67c1..78d706f8d 100644 --- a/deluge/src/core.py +++ b/deluge/src/core.py @@ -33,6 +33,21 @@ import logging +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True + +import gobject + from deluge.config import Config import deluge.common @@ -42,11 +57,18 @@ log = logging.getLogger("deluge") DEFAULT_PREFS = { } -class Core: - def __init__(self): +class Core(dbus.service.Object): + def __init__(self, path="/org/deluge_torrent/Core"): log.debug("Core init..") - + bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", + bus=dbus.SessionBus()) + dbus.service.Object.__init__(self, bus_name, path) self.config = Config("core.conf", DEFAULT_PREFS) - + log.debug("Starting main loop..") + loop = gobject.MainLoop() + loop.run() + + @dbus.service.method("org.deluge_torrent.Deluge") def test(self): print "test" + diff --git a/deluge/src/daemon.py b/deluge/src/daemon.py index 0a756a485..d21082ee2 100644 --- a/deluge/src/daemon.py +++ b/deluge/src/daemon.py @@ -30,11 +30,21 @@ # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True import logging -import Pyro.core - from deluge.core import Core # Get the logger @@ -42,22 +52,15 @@ log = logging.getLogger("deluge") class Daemon: def __init__(self): - # Instantiate the Manager class - self.core = Core() - # Initialize the Pyro core and daemon - Pyro.core.initServer(banner=0) - log.debug("Pyro server initiliazed..") - self.daemon = Pyro.core.Daemon() - # Connect the Manager to the Pyro server - obj = Pyro.core.ObjBase() - obj.delegateTo(self.core) - self.uri = self.daemon.connect(obj, "core") - log.debug("uri: %s", self.uri) - - def start(self): - # Start the main loop for the pyro daemon - self.daemon.requestLoop() - - def get_uri(self): - # Return the URI for the Pyro server - return self.uri + # Check to see if the daemon is already running and if not, start it + bus = dbus.SessionBus() + obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") + iface = dbus.Interface(obj, "org.freedesktop.DBus") + if iface.NameHasOwner("org.deluge_torrent.Deluge"): + # Daemon is running so lets tell the user + log.info("Daemon is already running..") + else: + # Daemon is not running so lets start up the core + log.debug("Daemon is not running..") + self.core = Core() + diff --git a/deluge/src/main.py b/deluge/src/main.py index 9f5110d6b..e0663ea4d 100644 --- a/deluge/src/main.py +++ b/deluge/src/main.py @@ -76,18 +76,19 @@ def main(): # Start the daemon if options.daemon: log.info("Starting daemon..") - daemon = Daemon() - uri = daemon.get_uri() # We need to fork() the process to run it in the background... # FIXME: We cannot use fork() on Windows pid = os.fork() if not pid: - daemon.start() + # Since we are starting daemon this process will not start a UI + options.ui = False + # Create the daemon object + daemon = Daemon() # Start the UI if options.ui: log.info("Starting ui..") - ui = Ui(uri) + ui = Ui() # Stop Deluge log.info ("Stopping Deluge..") diff --git a/deluge/src/ui.py b/deluge/src/ui.py index 1d16110a7..47d64a2a1 100644 --- a/deluge/src/ui.py +++ b/deluge/src/ui.py @@ -33,18 +33,31 @@ import logging -import Pyro.core +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True # Get the logger log = logging.getLogger("deluge") class Ui: - def __init__(self, core_uri): + def __init__(self): log.debug("Ui init..") - log.debug("core_uri: %s", core_uri) - # Get the core manager from the Pyro server - if core_uri != None: - self.core = Pyro.core.getProxyForURI(core_uri) - # Test - self.core.test() - + log.debug("Getting core proxy object from DBUS..") + # Get the proxy object from DBUS + bus = dbus.SessionBus() + proxy = bus.get_object("org.deluge_torrent.Deluge", + "/org/deluge_torrent/Core") + self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") + log.debug("Got core proxy object..") + # Test the interface.. this calls test() in Core + self.core.test() diff --git a/setup.py b/setup.py index 7a27d2ee0..1391ae213 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ import glob python_version = platform.python_version()[0:3] # The libtorrent extension -__extra_compile_args = [ +_extra_compile_args = [ "-Wno-missing-braces", "-DHAVE_INCLUDE_LIBTORRENT_ASIO____ASIO_HPP=1", "-DHAVE_INCLUDE_LIBTORRENT_ASIO_SSL_STREAM_HPP=1", @@ -48,14 +48,14 @@ __extra_compile_args = [ "-DHAVE_SSL=1" ] -__include_dirs = [ +_include_dirs = [ './libtorrent', './libtorrent/include', './libtorrent/include/libtorrent', '/usr/include/python' + python_version ] -__libraries = [ +_libraries = [ 'boost_filesystem', 'boost_date_time', 'boost_thread', @@ -64,26 +64,25 @@ __libraries = [ 'ssl' ] -__sources = glob.glob("./libtorrent/src/*.cpp") + glob.glob("./libtorrent/src/kademelia/*.cpp") + glob.glob("./libtorrent/bindings/python/src/*.cpp") +_sources = glob.glob("./libtorrent/src/*.cpp") + glob.glob("./libtorrent/src/kademelia/*.cpp") + glob.glob("./libtorrent/bindings/python/src/*.cpp") # Remove file_win.cpp as it is only for Windows builds -for source in __sources: +for source in _sources: if "file_win.cpp" in source: - __sources.remove(source) + _sources.remove(source) break libtorrent = Extension( 'libtorrent', - include_dirs = __include_dirs, - libraries = __libraries, - extra_compile_args = __extra_compile_args, - sources = __sources + include_dirs = _include_dirs, + libraries = _libraries, + extra_compile_args = _extra_compile_args, + sources = _sources ) -print find_packages("deluge") # Main setup -__data_files = [ +_data_files = [ # ('share/deluge/glade', glob.glob("share/deluge/glade/*.glade")), # ('share/deluge/pixmaps', glob.glob('share/deluge/pixmaps/*.png')), ('share/applications' , ["deluge/share/applications/deluge.desktop"]), @@ -100,10 +99,8 @@ setup( url = "http://deluge-torrent.org", license = "GPLv2", -# packages = find_packages("deluge"), include_package_data = True, - #scripts = ["scripts/deluge"], - data_files = __data_files, + data_files = _data_files, ext_package = "deluge", ext_modules = [libtorrent], packages=['deluge'], From 770cfa0a07c0d0484bac131fa3057937d50d2490 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 9 Jul 2007 02:50:20 +0000 Subject: [PATCH 0004/1009] Update to my branch.. --- deluge/src/config.py | 8 +++- deluge/src/core.py | 52 ++++++++++++++++++--- deluge/src/gtkui.py | 62 +++++++++++++++++++++++++ deluge/src/gtkui_mainwindow.py | 84 ++++++++++++++++++++++++++++++++++ deluge/src/main.py | 13 +----- deluge/src/torrent.py | 42 +++++++++++++++++ deluge/src/ui.py | 28 ++++++++++-- setup.py | 12 +++-- 8 files changed, 273 insertions(+), 28 deletions(-) create mode 100644 deluge/src/gtkui.py create mode 100644 deluge/src/gtkui_mainwindow.py create mode 100644 deluge/src/torrent.py diff --git a/deluge/src/config.py b/deluge/src/config.py index 4b7af2d1d..37eb13c62 100644 --- a/deluge/src/config.py +++ b/deluge/src/config.py @@ -95,4 +95,10 @@ class Config: return value except KeyError: log.warning("Key does not exist, returning None") - return None + return + + def __getitem__(self, key): + return self.config[key] + + def __setitem__(self, key, value): + self.config[key] = value diff --git a/deluge/src/core.py b/deluge/src/core.py index 78d706f8d..ea7fc4e8f 100644 --- a/deluge/src/core.py +++ b/deluge/src/core.py @@ -47,14 +47,19 @@ except: dbus_imported = False else: dbus_imported = True import gobject +import libtorrent as lt from deluge.config import Config import deluge.common +from deluge.torrent import Torrent # Get the logger log = logging.getLogger("deluge") DEFAULT_PREFS = { + "listen_ports": [6881, 6891], + "download_location": "/home/andrew/Downloads", + "compact_allocation": True } class Core(dbus.service.Object): @@ -64,11 +69,44 @@ class Core(dbus.service.Object): bus=dbus.SessionBus()) dbus.service.Object.__init__(self, bus_name, path) self.config = Config("core.conf", DEFAULT_PREFS) - log.debug("Starting main loop..") - loop = gobject.MainLoop() - loop.run() - - @dbus.service.method("org.deluge_torrent.Deluge") - def test(self): - print "test" + # Setup the libtorrent session and listen on the configured ports + log.debug("Starting libtorrent session..") + self.session = lt.session() + log.debug("Listening on %i-%i", self.config.get("listen_ports")[0], + self.config.get("listen_ports")[1]) + self.session.listen_on(self.config.get("listen_ports")[0], + self.config.get("listen_ports")[1]) + + log.debug("Starting main loop..") + self.loop = gobject.MainLoop() + self.loop.run() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def add_torrent_file(self, _filename): + """Adds a torrent file to the libtorrent session + """ + log.info("Adding torrent: %s", _filename) + torrent = Torrent(filename=_filename) + self.session.add_torrent(torrent.torrent_info, + self.config["download_location"], + self.config["compact_allocation"]) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def add_torrent_url(self, _url): + """Adds a torrent from url to the libtorrent session + """ + log.info("Adding torrent: %s", _url) + torrent = Torrent(url=_url) + self.session.add_torrent(torrent.torrent_info, + self.config["download_location"], + self.config["compact_allocation"]) + + + @dbus.service.method("org.deluge_torrent.Deluge") + def shutdown(self): + log.info("Shutting down core..") + self.loop.quit() + diff --git a/deluge/src/gtkui.py b/deluge/src/gtkui.py new file mode 100644 index 000000000..13837d689 --- /dev/null +++ b/deluge/src/gtkui.py @@ -0,0 +1,62 @@ +# +# gtkui.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import pkg_resources + +import gtkui_mainwindow + +# Get the logger +log = logging.getLogger("deluge") + +class GtkUI: + def __init__(self, core): + # Get the core proxy object from the args + self.core = core + + # Get the glade file for the main window + self.main_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge", "glade/main_window.glade")) + + # Initialize the main window + self.main_window = gtkui_mainwindow.GtkUIMainWindow(self.main_glade) + + # Show the main window + self.main_window.show() + + # Start the gtk main loop + gtk.main() diff --git a/deluge/src/gtkui_mainwindow.py b/deluge/src/gtkui_mainwindow.py new file mode 100644 index 000000000..b63d09e20 --- /dev/null +++ b/deluge/src/gtkui_mainwindow.py @@ -0,0 +1,84 @@ +# +# gtkui_mainwindow.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade + +# Get the logger +log = logging.getLogger("deluge") + +class GtkUIMainWindow: + def __init__(self, glade_xml): + self.main_glade = glade_xml + self.window = self.main_glade.get_widget("main_window") + + # Initialize various components of the gtkui + self.menubar = GtkUIMainWindow_MenuBar(self) + + def show(self): + self.window.show_all() + + def hide(self): + self.window.hide() + + def quit(self): + self.hide() + gtk.main_quit() + +class GtkUIMainWindow_MenuBar: + def __init__(self, mainwindow): + self.mainwindow = mainwindow + + ### Connect Signals ### + self.mainwindow.main_glade.signal_autoconnect({ + ## File Menu + "on_addtorrent_menuitem_activate": self.on_addtorrent_menuitem_activate, + "on_addurl_menuitem_activate": self.on_addurl_menuitem_activate, + "on_clearcompleted_menuitem_activate": \ + self.on_clearcompleted_menuitem_activate, + "on_quit_menuitem_activate": self.on_quit_menuitem_activate + }) + + ### Callbacks ### + def on_addtorrent_menuitem_activate(self, data=None): + log.debug("on_addtorrent_menuitem_activate") + def on_addurl_menuitem_activate(self, data=None): + log.debug("on_addurl_menuitem_activate") + def on_clearcompleted_menuitem_activate(self, data=None): + log.debug("on_clearcompleted_menuitem_activate") + def on_quit_menuitem_activate(self, data=None): + log.debug("on_quit_menuitem_activate") + self.mainwindow.quit() diff --git a/deluge/src/main.py b/deluge/src/main.py index e0663ea4d..87bc8eb71 100644 --- a/deluge/src/main.py +++ b/deluge/src/main.py @@ -40,7 +40,7 @@ import signal from optparse import OptionParser from deluge.daemon import Daemon -from deluge.ui import Ui +from deluge.ui import UI import deluge.common # Setup the logger @@ -88,13 +88,4 @@ def main(): # Start the UI if options.ui: log.info("Starting ui..") - ui = Ui() - - # Stop Deluge - log.info ("Stopping Deluge..") - - # Kill the daemon - if pid: - log.info("Killing daemon..") - os.kill(pid, signal.SIGTERM) - + ui = UI() diff --git a/deluge/src/torrent.py b/deluge/src/torrent.py new file mode 100644 index 000000000..1c4f6592c --- /dev/null +++ b/deluge/src/torrent.py @@ -0,0 +1,42 @@ +# +# torrent.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import libtorrent as lt + +class Torrent: + def __init__(self, filename=None, url=None): + # Load the torrent file + if filename is not None: + torrent_file = lt.bdecode(open(filename, 'rb').read()) + self.torrent_info = lt.torrent_info(torrent_file) + diff --git a/deluge/src/ui.py b/deluge/src/ui.py index 47d64a2a1..8fb13c189 100644 --- a/deluge/src/ui.py +++ b/deluge/src/ui.py @@ -46,18 +46,36 @@ try: except: dbus_imported = False else: dbus_imported = True +import time + +from deluge.config import Config + # Get the logger log = logging.getLogger("deluge") -class Ui: +DEFAULT_PREFS = { + "selected_ui": "gtk" +} + +class UI: def __init__(self): - log.debug("Ui init..") + log.debug("UI init..") + self.config = Config("ui.conf", DEFAULT_PREFS) log.debug("Getting core proxy object from DBUS..") # Get the proxy object from DBUS bus = dbus.SessionBus() proxy = bus.get_object("org.deluge_torrent.Deluge", "/org/deluge_torrent/Core") self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") - log.debug("Got core proxy object..") - # Test the interface.. this calls test() in Core - self.core.test() + log.debug("Got core proxy object..") + + if self.config["selected_ui"] == "gtk": + log.info("Starting GtkUI..") + from deluge.gtkui import GtkUI + ui = GtkUI(self.core) + + # Test the interface.. +# self.core.add_torrent_file("/home/andrew/Downloads/test.torrent", None) + # time.sleep(3) + # Shutdown the core thus stopping the daemon process +# self.core.shutdown() diff --git a/setup.py b/setup.py index 1391ae213..dc02e4df1 100644 --- a/setup.py +++ b/setup.py @@ -59,12 +59,15 @@ _libraries = [ 'boost_filesystem', 'boost_date_time', 'boost_thread', + 'boost_python', 'z', 'pthread', 'ssl' ] -_sources = glob.glob("./libtorrent/src/*.cpp") + glob.glob("./libtorrent/src/kademelia/*.cpp") + glob.glob("./libtorrent/bindings/python/src/*.cpp") +_sources = glob.glob("./libtorrent/src/*.cpp") + \ + glob.glob("./libtorrent/src/kademlia/*.cpp") + \ + glob.glob("./libtorrent/bindings/python/src/*.cpp") # Remove file_win.cpp as it is only for Windows builds for source in _sources: @@ -83,8 +86,8 @@ libtorrent = Extension( # Main setup _data_files = [ - # ('share/deluge/glade', glob.glob("share/deluge/glade/*.glade")), - # ('share/deluge/pixmaps', glob.glob('share/deluge/pixmaps/*.png')), + ('deluge/glade', glob.glob("deluge/glade/*.glade")), + ('deluge/pixmaps', glob.glob('deluge/pixmaps/*.png')), ('share/applications' , ["deluge/share/applications/deluge.desktop"]), ('share/pixmaps' , ["deluge/share/pixmaps/deluge.xpm"]) ] @@ -94,7 +97,8 @@ setup( fullname = "Deluge Bittorent Client", version = "0.6", author = "Zach Tibbitts, Alon Zakai, Marcos Pinto, Andrew Resch", - author_email = "zach@collegegeek.org, kripkensteiner@gmail.com, marcospinto@dipconsultants.com, andrewresch@gmail.com", + author_email = "zach@collegegeek.org, kripkensteiner@gmail.com, \ + marcospinto@dipconsultants.com, andrewresch@gmail.com", description = "GTK+ bittorrent client", url = "http://deluge-torrent.org", license = "GPLv2", From 65c4d72fd0bdb9a26db3057f55c591824cd96e99 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 9 Jul 2007 02:51:40 +0000 Subject: [PATCH 0005/1009] More updates --- .../{aboutdialog.glade => about_dialog.glade} | 0 .../{delugegtk.glade => main_window.glade} | 1970 ++++++++--------- 2 files changed, 985 insertions(+), 985 deletions(-) rename deluge/glade/{aboutdialog.glade => about_dialog.glade} (100%) rename deluge/glade/{delugegtk.glade => main_window.glade} (98%) diff --git a/deluge/glade/aboutdialog.glade b/deluge/glade/about_dialog.glade similarity index 100% rename from deluge/glade/aboutdialog.glade rename to deluge/glade/about_dialog.glade diff --git a/deluge/glade/delugegtk.glade b/deluge/glade/main_window.glade similarity index 98% rename from deluge/glade/delugegtk.glade rename to deluge/glade/main_window.glade index e28ae076e..a60828804 100644 --- a/deluge/glade/delugegtk.glade +++ b/deluge/glade/main_window.glade @@ -12,991 +12,6 @@ True 4 3 - - - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - - - - True - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - 1 - 2 - 10 - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 4 - 4 - 5 - - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - - - - False - False - - - - - 3 - 2 - 3 - - - - - True - - - 3 - 3 - 4 - - - - - - True - - - True - _File - True - - - - - True - _Add Torrent - True - - - - True - gtk-add - 1 - - - - - - - True - Add _URL - True - - - - - - True - _Clear Completed - True - - - - True - gtk-clear - 1 - - - - - - - True - - - - - True - gtk-quit - True - True - - - - - - - - - - True - _Edit - True - - - True - - - True - gtk-preferences - True - True - - - - - - True - Pl_ugins - True - - - - True - gtk-disconnect - 1 - - - - - - - - - - - True - _Torrent - True - - - - - True - _View - True - - - True - - - True - _Toolbar - True - True - - - - - - True - _Details - True - True - - - - - - True - Columns - True - - - True - - - True - Size - True - True - - - - - - True - Status - True - True - - - - - - True - Seeders - True - True - - - - - - True - Peers - True - True - - - - - - True - Download - True - True - - - - - - True - Upload - True - True - - - - - - True - Time Remaining - True - True - - - - - - True - Share Ratio - True - True - - - - - - - - - - - - - - True - _Help - True - - - - - True - gtk-about - True - True - - - - - - - - - - 3 - - - - - - True - False - - - 2 - 3 - 1 - 2 - - GTK_FILL - - - - - True - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - True @@ -1134,6 +149,991 @@ GTK_FILL + + + True + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + + + 2 + 3 + 1 + 2 + + GTK_FILL + + + + + True + + + True + _File + True + + + + + True + _Add Torrent + True + + + + True + gtk-add + 1 + + + + + + + True + Add _URL + True + + + + + + True + _Clear Completed + True + + + + True + gtk-clear + 1 + + + + + + + True + + + + + True + gtk-quit + True + True + + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-preferences + True + True + + + + + + True + Pl_ugins + True + + + + True + gtk-disconnect + 1 + + + + + + + + + + + True + _Torrent + True + + + + + True + _View + True + + + True + + + True + _Toolbar + True + True + + + + + + True + _Details + True + True + + + + + + True + Columns + True + + + True + + + True + Size + True + True + + + + + + True + Status + True + True + + + + + + True + Seeders + True + True + + + + + + True + Peers + True + True + + + + + + True + Download + True + True + + + + + + True + Upload + True + True + + + + + + True + Time Remaining + True + True + + + + + + True + Share Ratio + True + True + + + + + + + + + + + + + + True + _Help + True + + + + + True + gtk-about + True + True + + + + + + + + + + 3 + + + + + + True + + + 3 + 3 + 4 + + + + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + + + + True + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + + + + False + False + + + + + 3 + 2 + 3 + + From bc39371af6f1e9c490b80f7b96913c685010de64 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 12 Jul 2007 20:02:34 +0000 Subject: [PATCH 0006/1009] New directory structure --- deluge/{src => }/__init__.py | 0 deluge/{src => }/common.py | 0 deluge/{src => }/config.py | 0 deluge/core/__init__.py | 0 deluge/{src => core}/core.py | 4 +- deluge/{src => core}/daemon.py | 2 +- deluge/{src => core}/torrent.py | 2 +- deluge/glade/about_dialog.glade | 38 - deluge/glade/dgtkpopups.glade | 406 ----- deluge/glade/edit_trackers.glade | 120 -- deluge/glade/file_tab_menu.glade | 100 -- deluge/glade/main_window.glade | 1140 -------------- deluge/glade/plugin_dialog.glade | 114 -- deluge/glade/preferences_dialog.glade | 1461 ------------------ deluge/glade/torrent_menu.glade | 165 -- deluge/glade/tray_menu.glade | 158 -- deluge/{src => }/main.py | 4 +- deluge/pixmaps/deluge-about.png | Bin 9944 -> 0 bytes deluge/pixmaps/deluge128.png | Bin 13952 -> 0 bytes deluge/pixmaps/deluge192.png | Bin 24460 -> 0 bytes deluge/pixmaps/deluge22.png | Bin 1103 -> 0 bytes deluge/pixmaps/deluge256.png | Bin 36758 -> 0 bytes deluge/pixmaps/deluge32.png | Bin 1909 -> 0 bytes deluge/pixmaps/downloading16.png | Bin 662 -> 0 bytes deluge/pixmaps/inactive16.png | Bin 588 -> 0 bytes deluge/pixmaps/seeding16.png | Bin 612 -> 0 bytes deluge/share/applications/deluge.desktop | 13 - deluge/share/pixmaps/deluge.xpm | 415 ----- deluge/ui/__init__.py | 0 deluge/ui/gtkui/__init__.py | 0 deluge/{src => ui/gtkui}/gtkui.py | 0 deluge/{src => ui/gtkui}/gtkui_mainwindow.py | 0 deluge/{src => ui}/ui.py | 9 +- setup.py | 13 +- 34 files changed, 10 insertions(+), 4154 deletions(-) rename deluge/{src => }/__init__.py (100%) rename deluge/{src => }/common.py (100%) rename deluge/{src => }/config.py (100%) create mode 100644 deluge/core/__init__.py rename deluge/{src => core}/core.py (98%) rename deluge/{src => core}/daemon.py (98%) rename deluge/{src => core}/torrent.py (98%) delete mode 100644 deluge/glade/about_dialog.glade delete mode 100644 deluge/glade/dgtkpopups.glade delete mode 100644 deluge/glade/edit_trackers.glade delete mode 100644 deluge/glade/file_tab_menu.glade delete mode 100644 deluge/glade/main_window.glade delete mode 100644 deluge/glade/plugin_dialog.glade delete mode 100644 deluge/glade/preferences_dialog.glade delete mode 100644 deluge/glade/torrent_menu.glade delete mode 100644 deluge/glade/tray_menu.glade rename deluge/{src => }/main.py (97%) delete mode 100644 deluge/pixmaps/deluge-about.png delete mode 100644 deluge/pixmaps/deluge128.png delete mode 100644 deluge/pixmaps/deluge192.png delete mode 100644 deluge/pixmaps/deluge22.png delete mode 100644 deluge/pixmaps/deluge256.png delete mode 100644 deluge/pixmaps/deluge32.png delete mode 100644 deluge/pixmaps/downloading16.png delete mode 100644 deluge/pixmaps/inactive16.png delete mode 100644 deluge/pixmaps/seeding16.png delete mode 100644 deluge/share/applications/deluge.desktop delete mode 100644 deluge/share/pixmaps/deluge.xpm create mode 100644 deluge/ui/__init__.py create mode 100644 deluge/ui/gtkui/__init__.py rename deluge/{src => ui/gtkui}/gtkui.py (100%) rename deluge/{src => ui/gtkui}/gtkui_mainwindow.py (100%) rename deluge/{src => ui}/ui.py (90%) diff --git a/deluge/src/__init__.py b/deluge/__init__.py similarity index 100% rename from deluge/src/__init__.py rename to deluge/__init__.py diff --git a/deluge/src/common.py b/deluge/common.py similarity index 100% rename from deluge/src/common.py rename to deluge/common.py diff --git a/deluge/src/config.py b/deluge/config.py similarity index 100% rename from deluge/src/config.py rename to deluge/config.py diff --git a/deluge/core/__init__.py b/deluge/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/src/core.py b/deluge/core/core.py similarity index 98% rename from deluge/src/core.py rename to deluge/core/core.py index ea7fc4e8f..6ce6e690f 100644 --- a/deluge/src/core.py +++ b/deluge/core/core.py @@ -47,11 +47,11 @@ except: dbus_imported = False else: dbus_imported = True import gobject -import libtorrent as lt +import deluge.libtorrent as lt from deluge.config import Config import deluge.common -from deluge.torrent import Torrent +from deluge.core.torrent import Torrent # Get the logger log = logging.getLogger("deluge") diff --git a/deluge/src/daemon.py b/deluge/core/daemon.py similarity index 98% rename from deluge/src/daemon.py rename to deluge/core/daemon.py index d21082ee2..828a15927 100644 --- a/deluge/src/daemon.py +++ b/deluge/core/daemon.py @@ -45,7 +45,7 @@ else: dbus_imported = True import logging -from deluge.core import Core +from deluge.core.core import Core # Get the logger log = logging.getLogger("deluge") diff --git a/deluge/src/torrent.py b/deluge/core/torrent.py similarity index 98% rename from deluge/src/torrent.py rename to deluge/core/torrent.py index 1c4f6592c..1ee75a775 100644 --- a/deluge/src/torrent.py +++ b/deluge/core/torrent.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import libtorrent as lt +import deluge.libtorrent as lt class Torrent: def __init__(self, filename=None, url=None): diff --git a/deluge/glade/about_dialog.glade b/deluge/glade/about_dialog.glade deleted file mode 100644 index c8799dc0d..000000000 --- a/deluge/glade/about_dialog.glade +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - 5 - True - True - True - False - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - False - GTK_PACK_END - - - - - - diff --git a/deluge/glade/dgtkpopups.glade b/deluge/glade/dgtkpopups.glade deleted file mode 100644 index 63279d9a9..000000000 --- a/deluge/glade/dgtkpopups.glade +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - True - - - True - Size - True - True - - - - - - True - Status - True - True - - - - - - True - Seeders - True - True - - - - - - True - Peers - True - True - - - - - - True - Download Speed - True - True - - - - - - True - Upload Speed - True - True - - - - - - True - Time Remaining - True - True - - - - - - True - Share Ratio - True - True - - - - - - Remove Torrent - True - GDK_WINDOW_TYPE_HINT_DIALOG - True - True - False - - - True - - - True - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-dialog-warning - 6 - - - False - False - 5 - - - - - True - 0 - <span size="large"><b>Are you sure you want to remove the selected torrent(s) from Deluge?</b></span> - True - True - - - 10 - 1 - - - - - False - False - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 20 - - - True - Delete downloaded files - 0 - True - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 20 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete .torrent file - 0 - True - True - - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - - - - - False - False - 5 - 1 - - - - - True - GTK_BUTTONBOX_END - - - True - gtk-no - True - 0 - - - - - True - gtk-yes - True - 1 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - True - - - True - Show/Hide - True - - - - - - True - Add a Torrent... - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add - 1 - - - - - - - True - Clear Finished - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-clear - 1 - - - - - - - True - - - - - True - gtk-preferences - True - True - - - - - - True - Plugins - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-execute - 1 - - - - - - - True - - - - - True - gtk-quit - True - True - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - GTK_WIN_POS_MOUSE - True - GDK_WINDOW_TYPE_HINT_DIALOG - True - False - False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Rate: - - - False - - - - - True - True - True - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - 1 - 0 -1 10000 1 10 10 - True - - - False - False - 1 - - - - - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - 0 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-ok - True - 1 - - - 1 - - - - - False - GTK_PACK_END - - - - - - diff --git a/deluge/glade/edit_trackers.glade b/deluge/glade/edit_trackers.glade deleted file mode 100644 index 32053dcd5..000000000 --- a/deluge/glade/edit_trackers.glade +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - 300 - 200 - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Edit Trackers - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 36 - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Tracker Editing - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - 1 - - - - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - 0 - - - - False - False - 1 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-ok - True - 0 - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - 1 - - - - - - 3 - - - - - False - False - 2 - - - - - - diff --git a/deluge/glade/file_tab_menu.glade b/deluge/glade/file_tab_menu.glade deleted file mode 100644 index 8e3f34162..000000000 --- a/deluge/glade/file_tab_menu.glade +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - True - - - - True - Select All - True - - - - - True - gtk-select-all - 1 - 0.5 - 0.5 - 0 - 0 - - - - - - - - True - Unselect All - True - - - - - True - gtk-file - 1 - 0.5 - 0.5 - 0 - 0 - - - - - - - - True - - - - - - True - Check Selected - True - - - - - True - gtk-ok - 1 - 0.5 - 0.5 - 0 - 0 - - - - - - - - True - Uncheck Selected - True - - - - - True - gtk-remove - 1 - 0.5 - 0.5 - 0 - 0 - - - - - - - diff --git a/deluge/glade/main_window.glade b/deluge/glade/main_window.glade deleted file mode 100644 index a60828804..000000000 --- a/deluge/glade/main_window.glade +++ /dev/null @@ -1,1140 +0,0 @@ - - - - - - Deluge - - - - - - True - 4 - 3 - - - True - False - - - True - Add Torrent - Add Torrent - True - gtk-add - - - - False - - - - - True - False - Remove Torrent - Remove Torrent - True - gtk-remove - - - - False - - - - - True - Clear Finished Torrents - Clear Finished - True - gtk-clear - - - - False - - - - - True - - - False - False - - - - - True - False - Start / Pause - Start - True - gtk-media-play - - - - False - - - - - True - False - Queue Torrent Up - Move Up - True - gtk-go-up - - - - False - - - - - True - False - Queue Torrent Down - Move Down - True - gtk-go-down - - - - False - - - - - True - - - False - False - - - - - True - Preferences - Preferences - True - gtk-preferences - - - - False - - - - - True - Plugins - Plugins - True - gtk-disconnect - - - - False - - - - - 1 - 2 - GTK_FILL - - - - - True - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - False - - - 2 - 3 - 1 - 2 - - GTK_FILL - - - - - True - - - True - _File - True - - - - - True - _Add Torrent - True - - - - True - gtk-add - 1 - - - - - - - True - Add _URL - True - - - - - - True - _Clear Completed - True - - - - True - gtk-clear - 1 - - - - - - - True - - - - - True - gtk-quit - True - True - - - - - - - - - - True - _Edit - True - - - True - - - True - gtk-preferences - True - True - - - - - - True - Pl_ugins - True - - - - True - gtk-disconnect - 1 - - - - - - - - - - - True - _Torrent - True - - - - - True - _View - True - - - True - - - True - _Toolbar - True - True - - - - - - True - _Details - True - True - - - - - - True - Columns - True - - - True - - - True - Size - True - True - - - - - - True - Status - True - True - - - - - - True - Seeders - True - True - - - - - - True - Peers - True - True - - - - - - True - Download - True - True - - - - - - True - Upload - True - True - - - - - - True - Time Remaining - True - True - - - - - - True - Share Ratio - True - True - - - - - - - - - - - - - - True - _Help - True - - - - - True - gtk-about - True - True - - - - - - - - - - 3 - - - - - - True - - - 3 - 3 - 4 - - - - - - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - - - - True - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - 1 - 2 - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 4 - 4 - 5 - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - - - - False - False - - - - - 3 - 2 - 3 - - - - - - diff --git a/deluge/glade/plugin_dialog.glade b/deluge/glade/plugin_dialog.glade deleted file mode 100644 index 68fa97357..000000000 --- a/deluge/glade/plugin_dialog.glade +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - 480 - 5 - Plugin Manager - 583 - 431 - True - GDK_WINDOW_TYPE_HINT_DIALOG - True - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - 2 - - - True - False - - - True - True - - - True - - - - - True - - - True - False - GTK_WRAP_WORD - False - - - 10 - - - - - True - GTK_BUTTONBOX_SPREAD - - - True - False - gtk-preferences - True - - - - - - False - 1 - - - - - 10 - 1 - - - - - False - - - - - True - Plugins - - - tab - False - False - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - GTK_BUTTONBOX_END - - - True - gtk-close - True - - - - - False - GTK_PACK_END - - - - - - diff --git a/deluge/glade/preferences_dialog.glade b/deluge/glade/preferences_dialog.glade deleted file mode 100644 index 4e8eca460..000000000 --- a/deluge/glade/preferences_dialog.glade +++ /dev/null @@ -1,1461 +0,0 @@ - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - Deluge Preferences - GTK_WIN_POS_CENTER_ON_PARENT - 550 - True - GDK_WINDOW_TYPE_HINT_DIALOG - True - True - False - - - True - 1 - - - True - True - - - True - True - GTK_POLICY_NEVER - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_SHADOW_NONE - - - True - 2 - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - Ask where to save each download - True - 0 - True - - - - - True - 10 - - - True - Save all downloads to: - True - 0 - True - radio_ask_save - - - False - - - - - True - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - - - 1 - - - - - 1 - - - - - - - - - True - <b>Download Location</b> - True - - - label_item - - - - - False - False - 2 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - 10 - - - True - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 0 - Maximum simultaneous active torrents: - - - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - True - GTK_UPDATE_IF_VALID - - - False - 2 - 1 - - - - - - - - - True - <b>Torrents</b> - True - - - label_item - - - - - False - False - 2 - 1 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - Compact allocation will only allocate as much storage as it needs to keep the pieces downloaded so far. - Use compact storage allocation - True - 0 - True - - - - - - - True - <b>Compact Allocation</b> - True - - - label_item - - - - - False - False - 2 - 2 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - True - Queue torrents to bottom when they begin seeding - True - 0 - True - - - - - True - 10 - - - True - Stop seeding torrents when their share ratio reaches: - True - 0 - True - - - False - - - - - True - True - 1 - 0 0 10 0.050000000745099998 10 9 - 1 - 2 - True - - - False - 1 - - - - - 1 - - - - - - - - - True - <b>Seeding</b> - True - - - label_item - - - - - False - False - 2 - 3 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 - 2 - - - True - True - The maximum upload rate for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The maximum download rate for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - True - The maximum number of upload slots. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - True - The maximum number of connections allowed. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum number of upload slots. Set -1 for unlimited. - 0 - Upload Slots: - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum download rate for all torrents. Set -1 for unlimited. - 0 - Maximum Download Rate (KiB/s): - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum upload rate for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Rate (KiB/s): - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - <b>Bandwidth Usage</b> - True - - - label_item - - - - - False - False - 2 - 4 - - - - - - - - - False - - - - - True - Downloads - - - tab - False - False - - - - - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_SHADOW_NONE - - - True - - - True - - - True - 2 - - - True - gtk-dialog-warning - 6 - - - False - False - 10 - - - - - True - 0.20000000298023224 - <b>Please Note - Changes to these settings will only be applied the next time Deluge is restarted.</b> - True - True - 0 - - - False - False - 1 - - - - - False - False - 5 - - - - - False - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - - - True - From: - - - False - - - - - True - True - 0 0 65535 1 10 10 - 1 - - - False - 5 - 1 - - - - - True - 5 - To: - - - False - False - 2 - - - - - True - True - 0 0 65535 1 10 10 - 1 - - - False - 5 - 3 - - - - - True - 1 - Active Port: - GTK_JUSTIFY_RIGHT - - - False - 5 - 4 - - - - - True - 0 - 0000 - 5 - - - False - 5 - 5 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Test Active Port - 0 - - - - False - False - 6 - - - - - 5 - - - - - - - - - True - <b>TCP Port</b> - True - - - label_item - - - - - False - 2 - 1 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - True - Distributed hash table may improve the amount of active connections. - Enable Mainline DHT - True - 0 - True - - - - - - - - - - - - True - <b>DHT</b> - True - - - label_item - - - - - False - 2 - 2 - - - - - True - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - True - Universal Plug and Play - UPnP - True - 0 - True - True - - - 2 - - - - - True - True - NAT Port Mapping Protocol - NAT-PMP - True - 0 - True - True - - - 2 - 1 - - - - - True - True - µTorrent Peer-Exchange - µTorrent-PeX - True - 0 - True - True - - - 2 - 2 - - - - - - - - - True - <b>Network Extras</b> - True - - - label_item - - - - - 2 - - - - - False - False - 3 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - 2 - - - True - - - True - 1 - Inbound: - - - False - - - - - True - Disabled -Enabled -Forced - - - 5 - 1 - - - - - True - 1 - Outbound: - - - 2 - - - - - True - Disabled -Enabled -Forced - - - 5 - 3 - - - - - - - True - - - True - True - Prefer to encrypt the entire stream - True - 0 - True - - - False - - - - - True - 1 - Level: - - - 1 - - - - - True - Handshake -Either -Full Stream - - - 6 - 2 - - - - - 1 - - - - - - - - - True - <b>Encryption</b> - True - - - label_item - - - - - False - False - 2 - 4 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects regular bittorrent peers - Peer Proxy - 0 - True - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Only affects HTTP tracker connections (UDP tracker connections are affected if the given proxy supports UDP, e.g. SOCKS5). - Tracker Proxy - 0 - True - - - 1 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects the DHT messages. Since they are sent over UDP, it only has any effect if the proxy supports UDP. - DHT Proxy - 0 - True - - - 2 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 4 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Proxy type - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Username - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Password - - - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - None -Socksv4 -Socksv5 -Socksv5 W/ Auth -HTTP -HTTP W/ Auth - - - 1 - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - 1 - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - 1 - 2 - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Server - - - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Port - - - 2 - 3 - 1 - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - 4 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 10000 1 10 10 - - - 3 - 4 - 1 - 2 - - - - - 1 - - - - - - - True - <b>Proxy</b> - True - - - label_item - - - - - False - 2 - 5 - - - - - - - - - 1 - False - - - - - True - Network - - - tab - 1 - False - False - - - - - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_SHADOW_NONE - - - True - 2 - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - Enable system tray icon - True - 0 - True - True - - - - - True - 10 - - - True - Minimize to tray on close - True - 0 - True - - - - - 1 - - - - - True - 3 - 10 - - - True - - - True - True - Password protect system tray - True - 0 - True - - - - - True - - - True - 5 - - - True - 0 - Password: - - - - - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - 16 - - - False - 1 - - - - - 1 - - - - - - - False - 2 - - - - - - - - - True - <b>System Tray</b> - True - - - label_item - - - - - False - 2 - - - - - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - 15 - - - True - 0 - GUI update interval (seconds) - - - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0.5 0.5 5 0.5 0.5 1 - 1 - 1 - True - - - False - 1 - - - - - - - - - True - <b>Performance</b> - True - - - label_item - - - - - False - False - 2 - 1 - - - - - - - - - 2 - False - - - - - True - Other - - - tab - 2 - False - False - - - - - 2 - 2 - - - - - True - GTK_BUTTONBOX_END - - - True - gtk-cancel - True - 0 - - - - - True - gtk-ok - True - 1 - - - 1 - - - - - False - GTK_PACK_END - - - - - - diff --git a/deluge/glade/torrent_menu.glade b/deluge/glade/torrent_menu.glade deleted file mode 100644 index 70f9eebf9..000000000 --- a/deluge/glade/torrent_menu.glade +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-media-pause - True - True - - - - - - True - _Update Tracker - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-refresh - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Edit Trackers - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-edit - 1 - - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Remove Torrent - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-remove - 1 - - - - - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Queue - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Top - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-goto-top - 1 - - - - - - - True - _Up - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-go-up - 1 - - - - - - - True - _Down - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-go-down - 1 - - - - - - - True - _Bottom - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-goto-bottom - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-sort-ascending - 1 - - - - - - diff --git a/deluge/glade/tray_menu.glade b/deluge/glade/tray_menu.glade deleted file mode 100644 index 22411c91d..000000000 --- a/deluge/glade/tray_menu.glade +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Show Deluge - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Add Torrent - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Clear Finished - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-clear - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Download Limit - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-missing-image - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Upload Limit - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-missing-image - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-preferences - True - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Plu_gins - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-disconnect - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Quit - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-quit - 1 - - - - - - diff --git a/deluge/src/main.py b/deluge/main.py similarity index 97% rename from deluge/src/main.py rename to deluge/main.py index 87bc8eb71..f18fa4dca 100644 --- a/deluge/src/main.py +++ b/deluge/main.py @@ -39,8 +39,8 @@ import os import signal from optparse import OptionParser -from deluge.daemon import Daemon -from deluge.ui import UI +from deluge.core.daemon import Daemon +from deluge.ui.ui import UI import deluge.common # Setup the logger diff --git a/deluge/pixmaps/deluge-about.png b/deluge/pixmaps/deluge-about.png deleted file mode 100644 index 63aef4fbb7a260167e05378c74f0415b0de6f414..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9944 zcmV;}CMVg6P)CsvFFZ;w0sVN88H|K)K{mt(MOHMLOLP&o>$*X@<;Fk}xERBD9jno+pH$RIO32 z)i`Wvf-vU0U*4zWx#xaOH~4U*ndIf>A&0F##u$gx&TW1LiL&6DT*wAurV)&#>oaS*)m`NkOHz$XrT%Jn*!d4Bu*4Sx0Q z7u0Lzk8z3D9%^uaRbN5a_PL(7*Z2A9$qq_sk|-n?^wCPa@%b`M@zXBhXu#a^3eR5b z6U8ZC-oJHYVxI%f-A>Ii$@f2Q(P|Hf!+`E#0^qajxe*IPCFL_QuLdLq;bsdUYjS+_s;d&K3AM8 zfulI(cYk=va1;^@2l%}%rr7tM1!)}9Zylnf;>X9^BuRFjmwBzBm89zL;yM(eH61_#9z(H>i3R57yVH*DB2GJ~|wH&^fnvTWs&P$dZJ9 z^YGNF`zH9kE?JVWx!t7Q`LMr}&mHGNV68PjJ>DisQbxTFNqBmb`EAIOI9Ku@=IQhE zQIhKn*=sQGWGpVsurOce!TKtdvdetZz{vEy&e>`Y*xYWCr3r&}^R*D*`(oH_qm|_G z(_Q@F>}r049Opvdr_c8pjv|85kTiJpGg7rwZ=O2xCN47biqzR@V+@v}XYk_c{=GFcf~~za(g-vHg+-KUb`JVjw!`|}RUFsG^LO895-X*7@_Y|t zG(o?2VgDDupNAZuJl{ippqKk|!g~=|O2x*@1GG|v!w(BloE6b%h|DrxZZ$u9Q`+DB z5!-@0_g3*r9*x$3QIw&vP)1Bv-;Zhad@7X^cUG4u*#d@*6VJK5+agIaf_@*ZFOEn? zYexMZX)4)xd3a&3`>Er-2)wn^LMn|v=v|5_>-V~7t$F%<@3S?fhmCC<6)`tA%kt7Z zeweb?8lo*5g+Q5{#L@_)vDj~q2%?OI`B~=YXDE}Rg5wGsMhUxnZL%~WKBrLNS&&8{ zNfdI>?9uJrn9`@gnF$<5344tWS(04H{^%sgEG6i%tP*Wu+}mnfqk zt&OB0EmlEhfkKcd*l6^@viNd+8QZZjK^v_Vt@eQaARrEWRCamqr_s4nG|goGw|0ZK z;!FhY_IW$zHA7^FNE6+EKSd&}&n!RCsXj7Kzk6-|wM?T`X(zINt+Xh5A}K z!O4Gq&|jYI2gI5mOy54uEAN;n*Tl&qZh zMKI`NwC3g3;g$L0`xwZwl!MLZV7vV6t2+qW;pt9~L=}5pVHYY}kayaIKK6Bfd~q-$ zmYTb(^VI4U)^F7*J6GrZQ&N(I0lj{36B~XDPDS8MDz>(pC@G2j(Z{}~Gr<@`I2sbg zDTl58Wna!m!_Kp(Xaf)K-J({hvfK9Q`dO~F)2mR~BSIHnPsnSdi+%Fby&>8NzPh`F z<9Yn*+XuML`8ELf5QM|QM1`;O&G|!cDgtlqG?7XZ46Y_YqQFOM#pZVNTISO>_xJIA zpPAV@ch{Ei6UB?hhzyG`O6hg`v=3WTtI4oGBucYl51f;BY-XE6nZwMmap>c^F5j## zQL2>r_PZ}H`@cJZ)|$v4;Ri9T_VwQ)y${~mQYQ=(_8VQ&Bqoios6)XR!r>6hTA*>* zRH%f6y>LllPW?(E`uCBA)l2ZhDo{q7n2driV1z_tXAZQ(jLGqo}$&%w4WEFrKh z3(K*o&de~kv`o2HM*vtL3PJ0P66)oMrMWU+tS*pg!53fLW^3!@M30L`G#XN=)p@yn z$nw%GmUYvHXBzJ$@Qcj@j4=fLtEs+Y#DPzBW`?bu7HhW_ulQQf3kYE_Mj!-i zHGQgOhdawP`eA}xUt=%~==UyP^^7qD!vVrtpmEr}RyXJKz+3;yyS)*;zE2VcD0%f` zt-)}Jl46^8_7FHoTN0`0}k5*EMVL>P6$B~hcvb}*?#_n zEKLzbTVDh`-5;Q|;b(W}sg+&6{`mvEkJM`TtcU|2rDTx-c$3^QjW-fF4;vj^sj$(z zFuu|3?6p7nES`RUK(pOP%Z%GKLl{Q<^N*XaJx>U59E+0YP%V2@${y9Shg6zocZd)I z*RgQz+|+h#i!6%Re)5R!;UU5TAuKX!c-|OcTNdBmU!>%@eEst;uT1C2a4OC_b`+EKA_I zHsz8_wd7JQyHv{gF(RoQCB73!NTLv#rR?stNz)fM`?Zi(O`^WV{?!Ftu1cPI;(Z*1#c+Ax+%+FL_@0o97f zOs&LRz07RA%xppAxnj-K%4h?$418i2QQyv4EV9+<>8Q$>*ORZmsnlDe6v+%8A%k;?v5DwS2Qo-0&&cQ zR%%LpKe4^w|7+GtQIyUbKe%v4KM zD;~9qN4exsEqOSOO{ecO3Q|1J;okjqR@YWBZkZraliiOo21C9ZDmjAG?~x39$TZ6L z$>dHkV+=Zr=pQziSy|(t8&NO0EY6hqYIT+u+A!+R;twu2J{$}Oc$Eq}yDh%`#r;dY zmdnO51>QgClF6%4Qg$8)hlAYQ+HPL5O&#`Iue55igEza;~fA;Tr__K#Zlt?5vc3~bLTNVgGd3KSRyI(PX z=PRnUDxT}$Iu@SmP;wm7VH=s```u^z15&B^^7b6{ipR>GHSBX9n08iZB}oev4#Ugc zmtQs}1RjT8l#-XyPUT}jW*JEw(e4cKuduoG_{Ya2am25Gevfj=<@=|5#7RcA;!>-6 z)C=XURr8}UnpQu+u`O1X=lRe7=}+*=75bqr_O;f8u#?9{3|NSxg;&8_UdM7>T*sM& zm^{Z~c(9YJGVpA_k8KIQ{bB*vb6CE!cG+(kp`Tj;pK`eHJTM{fUZaDQig0-O>2v3a zaYf746&<9dl;YW=CsfNG_tzHb4gxlIT9it8Vn(&>QLPlK?4q=xKZ+@N4y&t6{Kx0WNnkr3mDMk?+)}<5b}dS-gG)*>>|qJPFvvLU zN6gnsd~s`r`rItl+4|+aZ&{Xaky?G?&$zjv1ykx<)L>pGC@D$fh)#Dz5MI3*JkK6K zBTW*1_Fxqu1dm@H;5Zi5l2dq-9m<|drQ}lb9Q+_BZ?)p_?|$I_Ja$MO zBeQ*^z>brLoP;%w8w*_5qq6oTrP>T7FMpkq>!3z0jFt#Xu+bQjWSXz<%uy{lEUm3# zU2?v8SWE)h-T(Zuv&Jc<7QkVvk5c;D?0sLw_=Dcry?W->)1MwwavdJrU1H!zG+F~n zu0zQ?B5!_l@WTY#u_?I@Uw^gET)n~|*0`>HL|)g%bzfEDoVbo{;aJurrE;91l~**` zc+2Zl>ob!?5yt`<46uYi8^hDR+?4+A!6LTfu(-N>sqdXk(>z>w*!!$zD`~Cy@zFNM zn4G`|%^pf-B+>O8rad;dn(e`r>LuN2wTZ%zhxb>oEsH0`Xc5n`@m#0Kw93hAfGCl8 zu1&30=9{nYgJlt>OvviF$CcM9(kC5zdKJXAL_ywMiEYcm5*DVuoSWNjuIMF-q6Z=9 z`w0jAh=qEI^_4ocnHkEL?A;GXLyR%krhw%%IBfOFomf1@{~-3;Q8JFtqtodGXT^sc`uwLah^I`_;9W}j4^CaS(279 z1|_pg>+yV1$jz;o!)A}<3M9iDW7yf;W^t~H=h*BX=JnRDZDTt*dB>!49h)rEljMv0 z_g1kjiy#$8gq>F3;&-Nrdz`!i+kTzA;2*F`LzuB)XRmOA@j)&ymiu%XNBm+xbmoWpM^$4DDi+NUn+nsS1TmrOJtBSuD)W z;M#fP_KC;2Ah92Q3tyx>B0Z^@7 zE`cGxIZLUp^`wq=CvV;ujfNPL->5z-0z*E^QeOe5N4L{O2*J{Pjs7S?7RoX1RV4}{ z2_Z-`NwEcGVSWbNw$O-)$9cT}b&8PDYi6@$;Y_KoC33=!No-BM*3nVmEvylVD&_iI zan-r4tTnU;F}1Qwz3fshdzW7LpM(KgDR%bSX#MHt@V^P;Tn3}{u{7t;j=*UgA+wCV zM)y*k9=&#l#knfBWzp;&wdyq-FCh^E(o7YP1@i@g4bBOvl6J9AJ|eGEnBvxy(`p?5 zeU-2|2akwoJ1*tL>8vYL?7a*Jy#O%WUaBF4pjvT1-kWtwgrgy8CTU*7_QKs>o;Di! zqhl?0a{^z#8`fJ9jfNfk>%#bg^+jP`S9ozi~r45TSWiWxE`0NxI^7bsb)1z}KP7j$$wAPa*>hY9UTx)|;2Bi(U_!>dcNy8we#I`I- zPTrAYn!Mw*$q9K2wauMXQ% zCf}2VLu*{L;7ww^_QZ6a?03f`7ANd=VX~$%QCTxZUNc^%C{4_dQYma(fH9XIN*W_? z@~_$2xjs$)x5w&x!c>&cmca4Y>9Kq9PLDBxor1t}iojYMGO5XwB9j_bD78}g;~3+~_Pd;{w7@H7=Mhm2rs%Vk6ImAzq}1g6)mMhVV--$Qc`a19KL{9( zLgK(bvAN`DOJF#1dR+8OrK}juI&M*;juBXsNlBJUGO5Tig=1NT`K;M%w6JZ9vL_}v z&f|iG*W$a@*kpdyQFVkP#Wj;-zBsn@C z1Pq3``f7c0#N@LkFvbx1xznTBzPMa)&&wwsj0vn>Q3V9jOJ*X z!}f0Llg-Y0m-@nlz}L&=eip=j?)2E)ZeExQj|m(Wl~MKT@roWP6-lZ{Qc0X-M2W~)VR?pSpvk8|>R zwxC+F=?=rZ6|GcgbA=?B@u(fi_9r-M=QQdIHv|R>6~17n$L!oJ-9g^{Uu;_J$Z~a=;G!u z2@{V8rD0f_ErHSW`aWn)nq5sZH7rK$Tz2a0Suhx$PJQ8qz!QYS0cj>LEIVsqX^~+N zBb9z5fitNHqXa)n@uQS5N|91?Yhi{YP5Gby`)^4?AG-cMB@{BJG2w@NiEGUW`igqpIFjPTqTja6d<+(C4lSEO1g(khi+z5yH=+xaS z)Xd>cI8xs{PvKp-A@CH!;Q%T1g)~W9Tv`P3X$qDQEX-D4dydwIEK~Sl!qATyg$bh| zCvq64%+)KnHvEsj`Wyb?@BbO0Vzkl4JR~z&w?9(Z>zQWM3Jp&v0Q zNIVGgHS{CmR5DX5VWauKfAjzN-9P^ey>=^CV5KK%m`WWbVVH3cSO6G=8LRG*IX!k> zfhm$TH0#TC>SdSRW{)6>K}$wqhS3VGF27aLn=l@Dx8LXzCs*@2&#Jxv_MJce^S{ti zUfYh8XN8gyw^SyGQkEBI&wakIk{L>-Xqn-;Hd~u}WKwZ&Z2`}7*l(VkP@>1?u+m4# z7e%s#DwNikyhEkRQucTEXdN7oW{PsPHc{U3bEh3xlT=Bqr((e4f(nB&8kE8nhVLFM zBD0L&J=sB}34>vPA7w{VXobk9K3qX4DJfTLBuUE3^4zT`8`}(;4V+31yE02pvG5t+6Z%jYYZSQ9q}iPdRb3oWwFk zNlB&b(rC14_k6y*x6JC&EZh6t*U~E|*Jkpr{HauE&1A1DRlYK%$udPI6=^1s%AmE% zhhv8WM(soV;gBRruxtlmWBn z*P1J@Kp-spN)i}y5|`_BhJM8D)rBi@o%@Y0&GvwBIG8l6dl$wEd_&+jK&A=hYHjjC zpT}@(t<*$;$IG<^aU9d@2c%N5zP7;X@+`ZDy*$M81}J4nQi&?atCY^m5Tzo`#v~pS zJ14wS8kuQimXHPkey_!--y=y94B$8}#^@pwEWa5jsrYKO#_h!#eh~8e$2+8PLKFrx z+nwARFo3YYvS506+7*N;g5{RuVOth+v#-bCFA`}c`RU0HGD{itPM?EtL*P?DD}`Gs zqm*W`X#e^_=hG6ca}t~4sI{V2EpgcF(dp;yTkowdaBDFq@!M*Zje#UnBw1d1s8n8l zm?=$~shnI=A(bJ`G^xx_`Nhu2}eHtPMc1%i64Y{=9cB4l;-QTI?JibTk|;1<&8^yv3bB~6fx>{ z(dzV`{2KzF0!n7Mr4nJBv9vIQ=Y3e;C#^L`tDL-AqqV|ljX+Z^J9N4}&F%=tvADOo z!20b)dc%Mye!JIHYeSMLq9h}gIeDd2NS!NkCUf$pS-vuvD|9LqX)5!Ir%VwiDWM2IKiMUT6S5>>Z)cMzPEk7dPal!i#ww0iRe{G)Sw^Xvr=?z& zLu)jOc)rmfjiNJop5ZkDU;oJr?+clxlxuZ@Fk$W1{0Hrs@)Er9Qeg7^sWAr6v#C}b zy8V#D_7JHQcW%w|;LZ|42)cvtoz+sN=u?$RRqTa}eesyI#lARBWI^09p;cjq=MJrV zs|$Sl)f%o)JbSUrPn)eg-#L!h-P#}sBa{L?QC|D_@e1#O$Iwb)I}WKd%+6G)l)Vdm zeWf&i_-TtQlMFlUNuTG_;sgc0A@E5sI`2tkgpKDp%+xFIeJ&xa35hZKSa%C!3?)KISYDjr-s%Eht}i2i!6=$a#5ft_v`kZ^N=l^*HO}|SQsqvn>8d2l60F}| z;8)+=V`;udnks(WXz=X z$l9%i6Vq8g7B;pH==FVqL7yx+``FtX0!kD@d;kCjUP(kjRG$G^l2EGF7>*)VSKfcm zG%5F+5VuIGFiegm&t|Uf5ydG(KVfUHL#7lgb;*L`kE@|J+P)* z>%7v1lH&xgmObvR&GV~o?y@*rBTh3Oy=d^SkGARbe3a5S4z%|-b0wdc%}&0i$SZ6I zVY^peeaA3G?AvlYq*N>|ez>3GH0bqxUc5XYjbnnr`?Yhri36Vzqh6Q!N0mS0;}}8+W@ZI<)+8G*574+g-8|&^c9SpfF7wS7w|H=8i3fLrYK2yzTIMLzk^hHvWM+B zWMPY;KSCLS(gMTSOm++MPOVRfyko?Hk5?+wIE00TI?Ic*ANBR5R6KdMhtV4U3|{9q z;fBDcgiI6SFrXs@hpj%VEA#JtF55Zo49jzmbLAA4O`J;hn!fPju$&E zUhK4(t5>*lYmVD1^QeM|V==I-r;z>`YWx8$)jxG8jd42LX-t0IkM((iTRg z^jh2Kg1FKY&Z-Gv?c>C?FMqts`(o7ZvQS#%>9aj%ep8|JfzHae;n~JMX(sXey?567 zz7@rd`G&w}KrrazmdZSRzDK3(F+1~m1<`3?J8n)`VHL!+uq+#;VQa4smPZs05Vnia z8S$_Q2&%L5M2XF#jRPKS9N;=O^=gTkT6wa{C3iBG)GnqJ#;IiJ_Zf_$Lb3CGSKd}9 zzaGoANroMwQ6GaqYJoC&4U%Q&gymHd+itGLpD1}D_j8Q8UFMdS`RVZvzy9vQq-Xbe zVspDmt34zRgL6w=z9H}#V2okdX)%9ml^-5$@ta>i{2=e8W!ZS;Dl&7h3Sw-%XrPQk z93~hnEW3oU9MI}0)y{OmawwInRO$<)Qn7Q;o9u_jdtckOCPU2f45^%CLgYyswqs+o zB#nIhPA4x{Fcumo&y^mBh%EbvxMNSV?G`5U)eaY(7FiPKL*Rwr@v~jN{rUR2-jo^# zUAA{yWJ!E3rt=s#1U@5_%-#O8ODp{F(-!~cR}VfoKJ2(2w&Sw3w}T&>JV8Qfgi}Hk z#5G1u9#eR-(>SCP)IG`3wNSlAY5g-jx%$e%dP#tmULE?Vc_ zWe{K&FC8P@Cs!7-h zqb6afaY2IC8hx+RYDhTActPL_y%I*^lhX zBZ)$kQp_za^Zk!6S--o&`rYMIJ3x#vZ0)r2K3q~V=(LVyXJSvj7KG*)DhlT{Rj2$ntdv(FU9FlaTISy<$-1!0u%-7oH+x;ESx!;8%W z_8Z;FmP^Tx)3M}6fj`+*>iqs$6ZIV!^}0wY!5IGOU!QXK&N6pz zFXFmyrcIjW)kVWmNE+uw3TI7|JPU3Jd=5}jGH4xAuGXl{&GYE#F0N~{yg19!;tb`I zHwo87amvAAFYl*JuHvKa`!~^>s3=E=$f@O z;kr`EBgSE{r-OE_xqjSIYpFG`2X^)D|DxCU?=1aZ+qXZFMQ>FWT)@rT99u!0=)4p|6yos zaOm@Q-*xCe?DV}{3-T>TfPq^1qhaWe*J_nF-}kfcf6Y$c&rZv?6ajwXU;oy>s#VLc zV3fdPlnxG7KKSN${>H(bzOS8>Zy5r-{%!BOd1R>i?F-q_&a17YA7D5V%59Ym#E1fvw?Qt{CK+ot~ePT%8B$+rdp?mYBc z)pGF=B?OgH0TW|`*tdHeLEwWBf`MA)AH3!M|NJj^`aX9;p7R8F{ab(a`-TUr|CA5{ z$|$C%MnMRH=P~S=8imIwD5DsvSKs%qe&*M|eW&mDYRhw)06*{}KQlZ&H2AxWQ33!{ z6C((X)>A@IDHky@J_Je#eBTQPh6X?NlRx<%2X^|tuckbw2{1K1_3>gM7y*EKt%QMk z83fQ9As~ccbf}Jctqe*CiiKdhG+O=S&ffLamFFx0UU%QGymfe>`f>p9eGj|FN8Edh z%WGI&Z-G)xf~m<7grT+!)k^8*Z+Z9c{KAgi_*ItYBmrLYhF`pOY;54Y3V>1y(~~1$ zi~l-&o3yYFfv?6xl{m@f>08tXXYWLgj5Qr&(4DjJs4If1elr}1)&Vo^Vndm`r$YK z^arMQ_~&f9Jm&~-)AhT5t6DDHsFXq>3@|>TeL4l;{OmG1aRQ|Pr3gBSz}eYFC_ zP^*Tc_uqfNx3j-!d*wMrfFJy^pMTZRK<$l60fYd1rpG`Cg;Ky; zqm6~-29zR@ppXjs{CY>92LTAd#P|@3g#r`^isjNBYu>)!-s#`9o${O^z}>HT+t}zx z{dXxPL@I@e@nICg5Q+eakT`vA5fXp`fmDD5AQXWl1m_o4(P~GA1XSk-d>;TorCfT$ zeee0;kL>Wz+ctU55a8ghk&hJ$;jjWhwOYpL@SryBis1CQCB$56@Qt~bia-IH42t07 zxg{iA0wC~wAG>#r0EB^1Myi#{`+xlBK5)xU|Hkc*=L7+M;C27@?e%*7UI_xmJnY>) zVfuAoVWolP^%f*j;3WVFqAh?q=0f4rxkV@dAV8&D#Mnq3pcIU<@<6Tjhi|(7cS<|@ zL$^Vm^#pjuKl!=ahern9t3aR##h%^c&U|+wfivfqp+KQPAPfoYv9VB(6)->_ zKf3^~;6Mi`r11fS0wgFD(Z>oDN)arsw6M}>JEw5(t`P)*4-krCq44T|{+^G%eW!o- zR?4%Q0OJ$Gzga34Z%~9F2m|cdH3mhr_Rh?$pwW&25DhvA1Yk_QCWOo!r`-Z@YIX%t zBA@_JMzL>dR7Vqppj;}x_x1OG^u;^+!?#eLwFG#@KY7ph*K5@`DncPaaoxUMV2l9( z8tn*Y=GU}o2hEs#3PorNy0R7_^#v56kdolU%rc||KnRM(5K|LFI!VM>P%DQYfBnyX zVsu9vV2k8gNr3PC!FNv72de*#Fb|3lOid1>Qr5A%kigN?ivWmA@JIlXSfJsogbfER zF%(jAv?7jEbE}3p1jB-S9pM1!`p+Q!^cOFMQ% z11_wWZ@hj5of}X#3&o5Pq?*U;@AlScq9AP1VA_TDzIC6GLn|jmdTTQk(0AZs5P)MRH z1Jb0B{X6Q8K{R=e&8+A_BSH`cKK4xx0fYjSkz&#N(2u|OGy8YCZ7x@yH3Yc(hkvnL ztJFTmnC~l44A!f9pob7Z5j=Bh39(SA!)N<+M*@~11%Xl&!jgs9@d4uU0;y#nh`7SB zbE}RR1GN&yhpSMapp*?zpZwt~-~7MBoo=Jck!KA7N~Pj&28HlCMJc?%$94NAoM%6? z*uY9N(m)dmNto7jLNX*s&Al+-E0wVbnQ$?^o;ZhbwUuCIu>oCA1Wb+9Q7sps2m$rj z^Cl_4_8^dki2H%}#>jGpo*gyNxgnKFOMO z`<}4?S-{y|VgYVAg9X=4|A_HqNr1J$Bq|$$H zeF3Qmj-6XYlt_ROc%FyrriUDXnD77k55MPuJ9fIwE=8_s0=)P~e~kuV@L}q+0YxZE z#Uc(&k7^Aj6erHDqS@ibN`;9JSswwNP=f( z)*#HNQMDLi@5BH=CZt56`A7_MNom0)4D1wz^Wke5FK;QMx{)hr!WeGcGlG3nLm)&~^E~v_d32%}QM-dAY9nd4Aw&X7h^zM1;On`71cnf% z9Gnm!2nz_yB@{~)l&TeYe(w}P+dj%v5J~7AA%Xo9HH_3kNCj{q@yOA6v?2~E1Y*wr z&BxyPeZP2>o9&Y2nk2wW-uV71q4Z(qc?TF{7^;=<{F|r1C<6h(-#m2&k3Vq~&GmI8 zT!0G+0zGiUC;_7sj8POqAA{8r7^991gb?Tu-yZAlB!r?;ucJOPib5$r>@T(A15l=v zOqp{MH}4%rF<_7Yn(YJ+A6o$D5>g1kx%losc-QxS@v1i7CCar)fH(cl?-N4)F=Y(C z=ix=S??o~65hpR8ID8Uc{_5kK{wzWWs^tO(szp>vp#vV!7QtRaGS?SMWekjsqFNt- zP8KOZ0a7V#31D8Og6~n>x^D=C5-6!Kx6;DlGpmqNf^#_=w{XX&e&KJQ+u|nLGP&jm zaL+&cz&`?^9|oZyjN*9*Coozops}`w<)vl(>4Q&z3wc?e9rzwbhbkDTmRuK+1zzuH zQx7P3et_|*X;cOV4e)?cQg;PPIZIG42e@I^fIbI>BWG7Jv)F`?3Y?1v=T_VIeCd6! z5nIqaTQ1jB6mZW!|8M(2z4wE9Ak4$qKm}!*VDbDpbeau({jn351iqg1LEte8&!Yeo z7M2@WSY8LE(Eb6@U50+u#~vkMghKKJGbfJY?6ITZNen{Z{0IH2eT&sbjG3hdK(#Y@ zV6uj4DbU>xqj!%C6yCROjdq1{%~*hYe&T~32>mi4^gBQ)7$LZ2A^=rdah^Ici^rZh zd(qFy_Vrf73DrJ+R^v;>0F$G2FiKND09h-bV7hq|4G;`XPGMkVRJ#LGK{$UvK}m%h zcMqak^dX_pi8#J-WC3x)!37XW{@Oo!*Y|#6i<|9=<(jbognI8L)cX!F51n=gyNZzm ze`URe$B)ch@b4KV@H`6NW4SHx83Oa_dsH6>9=MR0U08#V(ybUmw8kR4iU?ywPb-7M z;>;PGdipSuD2B2QB+=wjgy5N(RU}-3fFKM!+&HCu2toj#u>bn%_x#C?+uo3uDA$Aq zcj$442zWF1AVBUv88C^U-gLwHoZaFyV-v8#)XZwQBC~;aI z1`J`~p%5?>0#9$DhcNIE2C3%fEP}@n_#QluVRgL&0;T)@Oc?3*DFr3wm{AZVasJpd zSY6VUM=+KEgkZg$;N)CG-y|W}Gg`q=CDh3#N^bE8{lHae*iFig6M#_mJ4%t==T98R z+WaiO{kHusOgq20hGVDabJtT!5Cjax&_}7@qg)D5E`}%<1C&eVwHTmW2v92cC>8W^ zv7oOn1Ret4ZkiCxBWSiFXZEQj0;8ZzTM}kosX6a4f~7Mjv2<=mTMcJj2+l3Hu(%%C z6eDifJ&1zu!PFo8!&klgv;X31xAR7_qg}wue(K|IkidUE`^;fP%?572aT?d{83O=B z3CHIjd;*=A>l-CN;4yecdp)0N@18NOxr~@Z5Y1%)b>4?K$IgPHY)qf|`L#*`#n1=f z@;;E}0x5OaC!`4jrGgL=TuON58g}hFpv%gnf|L@35ZtnF2nC-)N`sHerg+N6%&*Ptp$sXhIHs$MXm+Lt-A(85!vmm#Et5aLuI#GRl1r5{+l zT5aA>c7y;gd+P%Mq3kP5XHVa`cDFRPt z1_PhM^O)9t6XQFRZ5_gl!$(Z{7qM0V*>tXgP+DuH&?Kl;i_ZCz(pUf!>PZ8zkkSAt zA%%pH3WfSGhIZ{X1W}MmVyIHU^}A~NoKkr5)H2R4G$Dk5kn&HsP=Dw1zx@3>W`=iU zG>}sE3+qb@cP^eg0|0pb&3hgAjaGz54xa&|#P!vMfWbH6TVQ~medNx(n} zbYE@=Af-aH6C;W_xDeo6f(vcReWuCedlbIUz$k^3629j_DFs5b#UM&S5d}gOAfy>R z)QKQL00@Ql>H?NMANA335USr;SnHr%@Gw4D0)fH}Qv+CSL|AD`D3HHR2!0O0&+h~d zzz$e|zx9qk{H{)O{r{YL@-d>6!oFR@c>XPW0cfrK;@6(Sdb5MTV+cb{fY1OR2A($k zK85djPWyevoTFyDfKJ4*+UTI!j?j)0m(VE`ix?gmMx|N-VIG+0fifTMC`PN<24swo zDwLQ2q|kjoOsHqO0I5<7V0-}~6u8vnDvwQ}IyjPA0x5CpzG0LL9;B3Lb~wKJ)OkdS zgp^83sb2Z{Uw-xfxvD@IxvB*CuAlzHK_174k3as%m`Dx@#Yw<0XBww&{*ltNf2V9(xZjEs%I4~vKr ziFT~P1B}CGd;{AWJ|o~!8>F)am9)`mcU<4^NFWrrk|_a%fDjU$gkxy;btsgpkW#8HXb>)sNa`rUP|?CU-*^pzi_r|8|12nfl~0doqqa>F_9zyfEV1h z&w;^YkfBUD|)x-n1iax-- zKlKOI^CynIJ!VK`7@_Jvc9tH1HfRdgq!wHxc448d>ZXIc6s_R^by!>p6??J zS!xLa-(~o?5I8ftf+*oG(^o2nc>eQm!!5U5k1+7C*yLF2NKmJ#nT^_PA92StV-a-D zKs%C@076kD!5~KW97MHNbykD2bj%?8IjXacwZ(ZCff7P+X1UdG*b--TV1dOhIts|lh+YrSVdqM40lQoS@y{o0J5 zN%5IcLaO%(!Fobb7}<@X(GmUG<}*BIUkQBkIqk(6#GQ5~Bn6&2y^5IYDyCBCXyP+MeHNF6U2jB(Se`Sy?j` zzzrm|IlxP!7@eBdSxGM)FCb=Q(T1qa`5Dm6V3d#wPo7@Z^9=!v4HPjwTmb+OkO_eN z{?)(*xC#WAzV{u)_0^Rhu*WxMO#fp?&O!)@(B$NO^P1g!*Z*rP5GN8#t1S}_TC-ok zU3WbX)k+Z_qnKTdAr*1p_L%-I^KUbVHk;^%gKg=U9Z|IZOuAW0f)=e{e0s0eeB&V6 zOr;G|DeoX|HgZ;At(oBD?7ACEx?y?%m7;I!6~FssZ++mk+l>uy6$lU(i$5bc4*+0l zd;rx_0RYhI#5i?s8I+o=p2rY))FotcqjpRm6QUP=a`C?j3GcY$X4GqC_>5wHog)%3 zCf{ydrr(->mIB|c`B`!R*#SS|JV#}^h*eR*=+v~11kAm8T_lKC&pRt%{fblb4XiX` zfDlkdaO3nqT9g9(`;dx$9 ziq|uQ@EF6wa#NRVdFgn-zI{^|8LGo)6zxQ!5vdHQY5&}<{Z8v$zn`fka9bJ{5}Inh zt2ZF}JP5F^$`~Ay zm=K~00OP|o4AykHb(C3##ueUl#xG)6K4O=M}al`fdoVL%c zi<~po?U}PI8LWn9_OsgGLx>FE)R6#Y(|vn~)lnZA%X~jgYZ4hnw6^Gc3Nt!^C=ob4 z-*BPg_0t2X&jI}OOMmiXySEddZ6yHnyq~c9H||dz+oPxEAf(je>ZXBQ?Y=qoGg{6l z#d@>juDAL7n-3k-Swcp!(h{cMPm?+x?FKj71<*cV(;m&wRu`qCj7E#IgHGDMBPk4` zQmZ+CAh)RmGHzw+2Q(Q@%{S0V1VDgt!NZ=>3QVU{0YCsMjkP9~%Z^y73pA!fBcVzZ(EYv=rd( ze!pAeQ*EbRnx82?&5S$>38=~#3SpS~AsOofG1Y-o#}@&_atK>Y}HZL z!Hc~Lf^sdBb1=yzrI57rLK7yn0Lz18Gi$E-b`4idGKv7eYhL;<|NA7iLAHee)Azn( zFO<3qfCC=@&d#o&6X}5ytp-vsLeXv~spoA{tb3-%byq+MBB68jdFL$l@ zpTMIS8+3CNeE@IT4y?Az0yy)ZBf#8pdIfZ z_!u0hIn8b-Nc-AMMz6Qe&t&|%$d>l?+554xj5gBz3+=-JD*YO05T@Tr2$DFqt&npD z0pQ$n3*6Kx3|2x^3J&uL06)0ZKy5hzrtf_R18^q*jE~e@I(2So-OcNB8Uv(d_2%!> zv@6hwMXpgPAs87N&|niiY|FDL-+rTf8S~%U*VA5aKNI4)>1A1G4;KL6nSYXV@{(}S zB$7;Kk$U;xoM}HL70xX+)Ak(8S%H7Hbx3L14hwJyKnVaQMpE-{D|`Xu8dT5OL#BaJ zFa#Gl@F^u28Lm5^Vqv`NY;vbx=+~v~&LU*vd)h^Wt}w6P>>`qJ9c>)C9udhtu%^nopXu;q3T;Q4mPX8r(Reret9=e*y{kZX6F zBtpQwYb;5rQ~)#PITl&%&Sc(bR}#q93cAwq4KjUM67*<)ud`=Oy#qe?+w#9hmWDwH z(25mkA*a#QL`v0}sW}gBgHefZEb}fo&rIr3AII9{Axx_D=6I11f1VDRZ0- zm~bv#!&)g73r;H)LEik+H16JLtiK!ltR=`gdD&2JqkcY}XP7tnPKf&tEGVGOY6!;n z!3!?-w0ec5^^U7i9Ih4IJVgMn*c=R-lWikFy;5}Gt*rN%3rUDuZDS+J`3%qvTq=gn zqyuT@uRG`8Z>-;W_dOneAJdP1fajgOoF(Y`ZN0x2@M|MrthWl!tw58Jg|$>+hHJ&_ z1=lVCj05;>05E9k{QzKPt(O4GtfrwmY+a06zOKWY{B15$z(lKbY=Ou6aPd9Di zqc}*!GWfowo2{2@L!XQmrb>bvm|^?W-8m78@Vp6J%-?gpe*T?|BrE#N-l4 z=JSm;QHDT@fEcnVyTlOHB(OkI0zLK10dAeTJm{(D_bw#8@2V`noyE&tVVR(mo7@5g z6hoFJ$iWMK@g>N%5x`Ef27pe)doI)_UFuEd382g|A;itXm1b6M!nqrd_o!aJwO9Lz zYe=PtTiy*4P*&>!fatO=qEx28SDF4?b@lZZ2R-}ydiET+nRha?l%0iFw%ro<8O6X) z$)Z#`jrs~@t9<}RfWX(LD@r)Jr=KYcwn(RDOJQf!Dn|;+togR&0Fp$w^sLV=T`y>fY(<@2#I>FHxC4$U24Xh{M{5V(1}V&_L~b@sIx6 zmdqmB76OF6=R&}g#i`ieVZrA@TLLbnn@cC9X1$%3truoch~n4*mapvTb^2H~}dRR|bt*8rt_K<&8%TsD-g zCcq#7cwS0QvBA22R*cM3Yl&~2osfACN^O#z7nZFiKpRNc)dA8RdAG=n1>iz} z3$x~q(1hSzcLk-;Hx@v@x3s*DMzaG#DSWDODc+t!kM8WAP3~mk`>gis8{XtE&4TMr zsjLF0TJM0(o=;w?*|dgDdpocd@|$TCm1;Oxnhu4s;9DO-$;(?Xxl6W%09gw_FVM2e zth~jAK*9wQV*z4r2_V4;LAe+>KY&Y#qbFuegA*{4Z-;)PdRb@B+IwgA`;0i@R>B54iS!ms>zD z*zO&b`Kyd}r<&ho1(4z?GujWOm!h{KM2`<}?OFgKC89(pf5N~+_o56|TsI)4BS4Y} zBwQjfU?)O=0H~Hj-Bn24Rs8u^AJspAD0t{?i}Ya-nS6dfRaeMqb=vD@-|MHveZb5p zXJ4>$fcIW!8%1{^Qco6D3V=kT*~xVUN~vXRfozWt0HE25+(|nfyq`$p-6vckPBaOk zgd>hQ;)Ekkv{k583Q!8#5=ey;r)F{D^c)m`fb`}XfDN3yj963bjx&9+eLt(kS%)sK z`57>=5#Y*c`NqVN*#lH90TgsSg?mqOD0%E6SZqqRngEWK>#cMEsC*Fu*cG?(RzL%9 zErC=DzzXf^Z<$+*m*DBK31mp{S$bheSG}EkG-=$>n~BZngCD0v~8;$yOS5P06^XfL>BzS zY_YZoNh07eg5iO(a~jXhF5%Dq;$Z*;K1q;=bz}Ukr=8Ve^Z}{IRA0!8?_<(s<~n<8 zx;s*7Nb1HMlm>&C2i6lE0s}fn(r(A5E0R!39ljJ$E+kt@fHPnGl@$O-0OU%*vbBNz z#ICz5gmh-#f*vOvaV*^VLg=AdE|`!};-N>L#>0;v1tma8`j0v~lb$u(JzCtSf4)FJ zFLNO0uiXf2doF#?jxCtXBqXTPL>Q?SK#VzBZ$-{B7%TDArDC&@Y{x2LUjuOc!b&>9 z!Oji1kO(P3B?0FIojB3c#28O&_gFe_V4zw6B?OID1Q6g)zw{7>2ddaTH3q3T68p2d z`eB7rVED*&BqDVV~Q6dn<939ggM6p1_a1s30F}U)W2x=34f-#y$*6_PN%X8I{#)mD6xyYYGhd)P|*c z-6>Oul3EK2xTR$QU~X|8dMw-CnEc#Ufwdh3a7&!;_+*j}y zUwsTAVleQ&fLjn{mKd@NY}p06v>LCifi>YkS}8=7)qI0mZ3JX|m5>I63urQnuf??fD( zSX+W9<|z>(a~$jUIhPn7C}VuM0;Pa2fAvv(@_&9Aacd18(Il{Shq=n5ZVm5Cf|MjM zU5VCWmGB}Lm1p%W))$1xbAm>R30 zRxaSk@w52Q|M(L;@%UjBs16Zr>Dk3ekg*WbYP?3xdgd1R^u^9zQb>CP)kOl8sVXKJ z%OIWKvVWqky9A=OztxG1=~qxnoLqbAvriL3e6rCf+a=0YS^&>%e(U^W57=(th*=N< z0QOCdTySh4E9R-2;6kEpNYH9q0wid65_BStb`%?8aI_=sAXdsD_Dl?-S_<)(UwaG> z{LvTCT$)3`1eBCHBG?>+J66g)9mJYm^U&}ONMQT>T&zpQ-$k5TNO z9@+HgS`s8YB|*ExHTW?{+gOH9#}K0vrxqhl5CjanMyl|Lz~{d36@2bfM^6`!B}!@`tUsZ&_7er76y}!J(QKLgzm!mtFSlm? z`XlXj+qJW4_bZYs{s2M>6Z7ZTNF0HAd2Z(_;eT8PQv3X~L(O5*tG`AqUhLdA{W zYcHRU<2bf$tNy-AXtW|!N+F72 zfLpJd!Z)8jv+3^YmE%2Y}LC?S}g znZb$UCowcUgo&vs6sm(745jBKXb$8p0Nno>G*uefrR}0$DYPGoh; zv!+DU_+zh7`w;+m`0#1>RIuyzjN;7fGL}}G8+>f9XopQe#R^hL-IpsJYJ5065<|3qvAG>9)1#G7@{&Xipt;!DD|L}G(g+5VV(pq z`!-vbjsJ5~2MBWW5DcmyW&?nm_6#BLD3lavwmW#{)cKtD^Um5Q*5^+&I-L&95I|kE?1O`iL{UULosMmH3%n)5WyzIT0O#*ywj2SPXC69Vp1AEper5Dc z0PxMH&R}AshGH0?S}NdqH%#N2F#i@C^ZzxDwHmk6W;_dFy*BJd=*N8xh`j|sD? zm~hV)%B~o~?P_4mJh*`;kYWW(>&uXYf`vsCDs>bpHT^R!3yke2HaSF;d0JT44QIki z-M2_UN{rSEIIwF#&ry>K$4<{tai>nQsoLPia63TRONps<2 zOUM4|@gzw|9LHpReH|BR@2g6HO^N8N#_h!;zfl^y>6ZZD{K6WJotejhX+4qnmg{zb z3yCAA<}Ua-8-BjjlPI|`HH5y0M0g0eFprkwX0>q&#uykOU`&{&jp^qGQ=+GqyGmkE zP@SZQo=lh~eBxvkt@TyN*|VUY53f*0p$K10BS20szGl|+1x zfN>8V6JUw80wljGD6s=McAcWMiibc!C;%~sY__mGKL??Jpin}gTtTr~17*H>BB{+d zN+=;PSPgLfuDb3$gur^UgKr!@17Rk^=qH#ajpd_%_3z?NlXp5D6-ALs^pn;$YU9h6 ztssEf%(i~w!QZG~|6L!n$&0T)b^;!wZXwA#Z{7n!367sxxcnPYdSQi_$M6%1*kkZ{ z=J^9mKae6}48ErqoAAsEo-L_J2;0mSC>8xOBVF^+x1f>$n z^+6OWH3&tZq{N=F3Z{ljI^iS)*4r^2K70m(b4Y3WeZku+$G`sWwewG&isM*v&gI(L znk{|aXhY`plH{@<9GS`f`J3H_Z+p$(Ees!gi~S5r3GV*3gBZ^Y6`!43!9$OqM8f;j z_+5rDO3X6{m?MDaGx#)pCV^eG+;#!B@Ptx5&D@4}b`c4sbZ4tF14vS){k$D#unS;R z2M2N2Z3i(tSb-8c9lhFU;gP4#pwo#UIfvj0B#+~j6A!(2?#P4R;G9RTRx4^W8WHC_ z29R8AQ%YTOqg@FBZ1QK*1OVWV-uaK;5Y#4rI0cB}?mG@*EK@bq>csf^V<)h%oK_WG zsaU5^KbFQmc2+-m-1E|B58D+>VWI?^!6SsYwFF%KK-YJ;S^}B&{X_LK4jr6C=zE}y zp-?JesS)Auu~{TMflLxe!NGYfR!=_ss|!cI{9vckiIOCVS65d%n*|?0d^yRWDu*ZYr5l3WtxM$Km7WFDX}WA+e!f`owWFr_j^R zVRmw9Zh=Zh0F}-yly*44%{@RmegNGq6apVN>>I<#VAT;rNQp!HIKXS)E zd|gIR~i^TBg{Pge* zd;xE>`{iA?AFS@Z`=7F~a$ELuCr9fzbYQZlq&(&VXU{KVW`0Fi9Bs0DaMQ*!8t%+} z_f+zprQ=cyVt%vx+|j`brYDCmGEf1K9ZCjf7uWFEGiT9kN9mBWc?}ppyz=M+zsTcO zE720Kt*u2u2;1jp0pDmpQA$O)0@;$s$M@+9`t5h0%bg#%;d@?E96$6r%7W4CXHSgO zaOgn#py{k|Au+eOhI8|)I5)rQ<|FpGzx?truAhe^fHm!QHPP&hV(SY4fbV%29xP*I zpn}n%3PL~423p{k*PD3i_$=lY*PKsatBnXH32UDEz11Us_Gg^)IIH=b^NqmXhya&u z`d!;>1pxp@f{j4$_u9ShQNL6gy7^T<7z|8*KOr{tq4gXT#<)K+aotk3_!UUpi&G`DupN) z1GjE)-+Y?_n(Y`T&n@88xn(2?&&(MxwFOea_i?&&>MwsYT0DA&a~>O$pBR(h5BQDt z?dz6+4{R|3fS&H)VqkZl&s7ywrti9;IDY8metG0YgnB)7<$=#IF;d5_u>p(?RX40R zOhYyeWG>YU_#itrVhKDI)NgGwI#V9Lak1W-!MZ6{!@O?HH|gg!N_z^D7N3ueKcEKpu3Z z0OqrZBRO2IvG)IFEb%M1&fJU|q4G}{qcoj6^p zwVz~2KpBe}m8bd2vCp|v0ib?)v|ieE`>lRycz<0E3kS#guSBL0|B3PkbERNo|FQ`rIFV!b*^a|Aj zLeX)l5Ol}&+3?O1ftU#%e&9>r_aer~Duq~3ygeh@E5|zvPd^?n9-otX;XY|VcUv6C zTnND%jRxnOXTjcx0R60ijq$e~oU29v_l0@?V6(t)3VfucQr)iv0HJ=-4@>pX3rhtr zDEiC`1LhY3<_7`w!hkZ*N77iLo%uN#HzK5^*9w3ngeW0|eHBtlA%u`Z3-5F~f^#l1 zz)SQJ;6g;WYQS5$>I86Kth2ySrB!Y8p!L)^%+2f5!kTvz&(#Ky+c8mb-2GZ){ED?J8du$E(wreK< z;CUV~8|*R6C~qI|73W-X&SjD$GD#9!q#?VuZW448z#hlYqj)X6jD;Y9myMs#s zKMQ;}3DC8Vu6+dnzVBNSs3b|cD{o}izQpE9;2gZIO5$vVTvG(-7Sm1ivj!Iee^c7u zXJ7T%->3wu8~A=Cuz*Vda;G(YBe|vt&@EjS;zC`(W(crBa<}JoGbHHNcy+A;-Yw56 z0&FB1Vq~pBmJGm!&A*jyE085Zw}$KCglBmJJ|_sUS?RN=f<5kOgl9Xjo0V@V0{nmJ amH!8JLiw(f=*ETs0000UYaMD9{L?&?lf?p~&@mVlR+7mMu|dpC1aXG<0*SL>`ZL1F-a0CJLI8s6C_9bVa% zx|*!7hJJZ&=HdGMp4kPoY!ZoVY}oLDC6lH931Ynn;Yvw~N#Y^}O6tSHFq~gX%#{9k zknE<2LKL|TzG4G7HU_)Hax-#sbI;1rQ?@(qSKRIbgHHU{^_kpT6xPG;c3$yE3owoo)-VsW`Wl@dTU~EgXKH zhlcZ!QBbn=+x)26zc+sAe|zn%>a4LheE*zir*!S%Ebs8JI*`#E_IewuX3nl7=z2(- ztkU83#c;SD2Kt#v)yF1(za239&OH+RUZ^DOOcLs37VX1@Fc*uWm*%ys>9e(SFN#Uhq|m-hF9 ztcZLu*kAN!-goV(-1y(izSHfC(bxZV`;j$h_1u5p!sJ{T6AKZ*DkyCWDR?zH$srNL zbx9A%Zt(t5b6lM#@PP0~KVW@6^MB0`-v67(LZFF2SR3G{1putAD05eTM9DBSivH?o zQ#?$!T~hgQ@1Hd~|FV|P{LvgQPmR_8*B7Mi{^VDM0RyVQF@{1s8f6UM419iDMeJrz zW(-_9a!ecJuC>tpVoRre#rd1wH&Vc%{@;Op(EdKcv#*hC5gI=h{FCp*s$reR;6Lez zCzYyH5780P*opP&+i8*K&t3Nc`cIhii6?*w&aR*F<#9`F0(v{C-XoG|8KJrRPh&hb zvYuzB`3KA`aac+;^nRQCn)8mDz-zzi^?HQX2nz%t>toC6W9IL(%gzLRWS7Y=AAj#W zBo00NjMH13WsT?aOu$%X!ZHt|+Y<`tm{soFp+-Q_zhs*K_TJ_9oz$$m_R_g!kM4CI z4h@Oc1NU@*OP|?7T!$DINO8q7r-E6Mpo3}*_kFlq6S#aGem~dz|DEd&bN`h)Xs7sVa%#L=LL=isBQg`0Duh*3xjpGv%(rJ3U6wnvD!-&WGJe9T{r-c z!*z0cq4{$!%QZgRZFxSedB=K5q<}%uO1t$*b+vpeIZ&h#BP=Wc<%`$Uak0e-aIyIUUD4$jVF<;(4PoHLz{Mt@g%H9- z1xFRA&?$J{CyV?FPw<@`dT&mp^Y(fDYUgsYrv+5oF0w^}V;kQ7 zLM?Wbt&)jE@`)XVLc>(Ov!xUTCP0XRjt1Tg)o}L;#proX4 zNm3I~gee1u^B@oS(M0wT*&Zt=0(M?S04 zrvXz?Q`DyFqYp+esrP};bAq?C^q+vAz+#K_&Hb})TlIa}_=G5H`D{IdsC;XdT+E@m ze5)qg$#3v2l`PT-Yn7>~kK#0B!R`GHg7*c;fMB;V`;OVJhfChq%bnf8y$a3uppkp! z$okTEPD=LUB}&c8@^QGBvR@h@CB%L=4V#Zg2!*1k`3#oguU;ThNa8O+ETwEC(Zb0o zZPndEy5H5&6qxJ*M&h_qF989wT>*Dr*MGO{p&1yy!|*!s9h9R7I}6=@{M+(n9%Mn| zXR7Ja=zCtSo>&#PlKMf#56}`3Y)3LN=*2@cf04L2v5aV#U2$wufsuz1DPfv?c&Xs^ zUxZhi&qlvBX+8mbGOed$c?BJ91HuMnT2GZJ2&VoHh{a};x`BZGv0`?%uG-M~B=I(q zs6uFWj*NW%;*j&d9YNq~2>b_z`^MeJFf#fb)=!h#5PCVpQkIC9D@U~Mu-Xv9vlBssX6RybP49b^Ye2dhS4jpo5%k(ED_oya#H1S z&gm`{!nQCUx0YKi%Ku#}qHx-chc9*R=o7L9LyrQ4DZfERUl@XBraWk{BH>@`6yobMKh1kS%YFtu1`$jeoU#xiKvKO4f0J;=pl?;m8<1($arBZn7AbL#pIeC;8O6aT%y>i>Kqe+S{WQfptxn@D-tw z0K!Vf*A(UZt=#^|jbMw=uOH#WLkuqee$|m#qG+M ztA}rhv#v-uQ~@z}-YI&K38OkgFl@uKikJ?9!Eyml`4^164*P(5|5pMvB$A9=nUX`C z{4a&26=9pfUyeOQQNzXEpGW5t-p+Odt+n5CGK6;g@ilb(sOGk_uZS4LZT9EZZ=jLZ zx##Fo{i}{sk*U?d-mT$)am58bBFi=vn0P1ibNVk3Or|uY^G)x66_8LndI4b_39n*@ z7EqX7*~L@2R|Gp@cgonhc@rDu_J!kUg(nVFwa<0m zjMQssz@dV4lueQi7OriOC5^K3n;o#2q7n<`AiuZlU`h$J98c>nNuFt*u=O%=qz26Az)fXAo3|WL^;!woKJ|O`UmSedUoe zNV_-Qf$kG9;W~iPacbGIy2_ejq-akfwa{fx7{~;PzF3%PKidGeDcdkIN0p^_H#`CA zspPezbOzUmr7J97dku=y5n%tkkqgy9waV41`#)$f!krLTFY1W$<^^u;$ZjjM-nX@X zdj~)8CFpSZ&O@_p{SQ@&7T1u^%bKcWoqYlZrWL_FdqZQjy^mr4Kq(6ZpRU0c#v&y* zRvZSO4rGJ+!=R0VK$)#kZDTN6YG0R2lbHBG+E|(T=eimDYY(~4^MH{@hVdaD)x03^ zMa|~+!-pe@s#37XPlsj>5@F#|yxc{?p62`UQl3)*l%Yk`1f=ZQ z^yun9(YIJywE@n3YD66)CsR95v_Jze(h2FYL>Z>fp{jT*=Gz$~`s?U)%NetiaqAvP zC1%_Bv|KBfUJNrcDD5-cNe&?t&B{Y-uVBDB=_uC#5F8tYaGg5K6Yt%Ica3j9^T1ei zgcj%tryJ}IdLu!dT?5fjD$tfQbiQRenY`^lPc>@jRijf53t0soE|+!#yOs^BZ2|x7 zpy#T6t@2&oMOC8rrV;BS&swB) zMY4)SJdlxTI_;h*r~+4}`8x^cvrQ3^fLYyae=^Z43qiz6@VqBDsA)cPbW(1$ z0$s$tosu@m7vF%4qKL3sg1672K^lz3(A3H*TJMvaZ~rkRZ@Nj{K6S^W-Z?ZZ}?>L?RDLRI=wx#7>#*#^di88aQk?GZqCRlr$Y6tLkeShLQ zR4sVyDrSL|{d?jQqQv&N(360nAU((mvuF3cBZ)dbSFg{GD*O@^B2E$GyeipsYw!)R z*S5BMD?#|Ji9D+LfYaeaYYA|XP41&jA{CAyovkfaE`t|k?wybD=Vg@#JrUB#5iK#W z*YumQ7PBrf!Cv01sy%I0OdoRuf|v@gD|`<6eeUEyp9mu1ug$Q3zJ(XZ<^V91nF~_n zYQSg*RjIZ1KV+;E04T-QKO!6nq4Twv^V*!9%z8lb->D156FeLIU%s%9$=iqso)~R0 z^lB10Bmz8j3fkKBT;{$xe__tcb)nP^7<&)zVJ^0K^SWyzS>>4ZN_%<_j*zbD!hgYU_5 z#R^l(PQ}He9I8@ES^_SZ*FDzvI!+m7s*wP}`++siLB6)M2AdJO&Ld53=rQf{Y#45@ zC@j}^RAZlmk73C+)$^*x#!}{vc!M0k#CQGgr6&}KBdDyg>=h4emLmctJ))9PC~(OY zs|V$?DrZLm+YPWV=knVGMZ_iq|Ks-`(NB&foQm@qCuayt>~HbUggCw`$fDs#GbxhG z>mMt(9dF!Mec7__qTE@^7wvsZj!GzdZHW2grTKY*cMU;qga!{3;fJAY?t27_Z*4s( zuQwPLh6%Q%SyK8SMG_uG70(|NPH4DGi42D>8^SgX0kJVjeJkYqh(`GFn-z{+L-cMq ze{UbQU1;dR(J9RKDFrc>`xtrcjx57OnHV9`N01zLWNDi7@LFK6fl%Smf_iu7q>&&; zhs7hV`6MW&A_#pW^H{=p)(^V>+WY2@$L@<^jvDKNN#)#sS*hG5>-Z~5zD^P=!MuOGvl2#`myKgx1dr^=74pVyN!B{F*CvcmWT3X6Y#M4 zq^QzY1)|EvEvElUjifj6eSsVFBGxW46sI@%Hk;KQlZ;z?ct)bsueE zA5}2Qat4q4Z-Odkv(k2q7>dCW2&kBY{aAp2FVHXu=+xzU!W8XJND|Jxq(tnz;hh&0a9QG62LO85fpKn*BIzOn78 zLV;W2c$Fsg{){BZ)dWjXj249y7JNoYgV3fRfrN>ORr<-!Fns;z zk2KftK~0(9-20>ZxR3)QFj`M@mgxbb+)0&G;uPG^I4hLA*WWL8|NXkg#oxK_aHngZ zq5XXdoIIHr^V+Riwv9%Kl(rX&(<~nHb-t3X2D}hvX2b3$p(J`R%#4WI9)rUqowTtK zXzW0a*y3Aj1ADcNnWFLqU`q;%%&D)!o)^o)E^Dm&$lofEfpNdvJe>C`T~ucuU01Sb zz$RC&j|rhFOVvZo+a;pXD0ANR<1#~AyT`+`yY2%Ns_xz(JfSaD)nXaF$qGs6rDol8 zf72~cPCMN|6E}*`kCgBkpADTYqA|tnC@Io;T2eXdO62O{&`m2)!iCTJj=)9AW5m_r z?$9;$HT^1;xb^gNg$Gkrb%nY6ZYC(HD?0&egoF23ZDVHg58u;;1m@Mo7?O&W-rYF(sFOS35{TrC0ir3Vuw75P`5(QW#38 zHJ_!Uqw6(FBM4zb6O<-~#JU^iz77kqu|>=+A(Q!S1QyAGf!uovCqtnuJFe2m^{Goy zr()=_v>%tkM?%oIgTIG%w-`LteEVT)lBB^SM|dq&3U_X zGQTnr+bifast|*;SI6I~lHdG82&CC$BiLF6fBmftvIzX~jgBf@voZDPz+w=vZ+XgT z4?P#+vldO=7!18H=xna8>{|alhQgy~8xdK8PvM3kEUeO_Qssa#l`<>xPpIq8OQeoy z-LCDgCJ11A6^W|*&_kF?u;+3$Dbr1~nbC5Fc6|(-`B@NuWxS0qy)0A0WyhtAHzO&~ z*`ULtOh(3v=|hxyY0-#PnF#s{7ylkrDfXhMA{M;C7`S$Ah2nFcog^>g-^le-o%E9I2HTivZQpBmeM!GYKN# z+is4{199}!*xo@rm}{%h?if155(mRX9o@kJf2f+Z2&YJ`hlEV)BVMaR9OW3OYxR^b z$Rn+jO2pxM%RL`uu_mX_1s4|aH?-7H!e)M+3-Lt#{9(z z?K#E}jcvqx_zj#nHE5S7grV&3ZcMR?#dNbaZEfzfaJ>|GXdKOMvb?)K=Sex--SD`= zhAoZC`540oZ#SEdA-ZrUvvemy6IzJ7Uc@Ce(MgK~?#m5~JzKL+AP`0jEBE{9aSq6{ z&yO<9L@STZyR2_t|001vARLcnrI&A4h6os2zdtBAQni|aoCn5N`$SL3#oPz}-Qeyi zlOW{V@pc{FWvRygiKu=HJjWstX)>92+)Y7OLnAd+xb*1HzNIbUWC^CdI0oFA%k$1Y zg3BhWNXBW3E1`8Ool;(E43Y3SKoCI;}$@jl~j4W=^UQR14<7A^I{*p_4(fAH-aRH?ZWO-}+9 zS7%ReG>WyuGgEtzeEJt99^(^k;aVixk+C^>C?!NjpC3SQmvEckhTZeT4OXMT2)yr6 z4w$C?_bW}YS{B23;8^5If%49Y@w#F1uMBBvSKvKcuKynk70-^Ojp@8DWBTM0MjFi9 z=_EllqFg#P&}GkbFQXc(+@n4U76 z+LA^<_Y0aL7DwZbntqV^CTD}{*&M3yO-fn%UqU~czO1H34|^5@;DdLsGd2EebnE-TI1?iicUjjsrtCJz9XZG zIaaG$e266uJmd3!9~KsT2PQzB3^5)Ks(^@sXd0qhpzmV^7FRD)KuoriASBLftP`av zE~mynqHH-K6)h$#)3XTZXx)W>y}qa{(TuQ)9{-B!<-ga``3beM8q&Ju8^+!rSkv0< zx;pa*LvPnQ_99mQ9&QK&z#tWhYwgoiL?A@QlE9Uce=NMq7Mibv+AdhZn4bOYl9Ty% z2M_(Mzn3M1iCD!njZBqTLc~EoX@_JP$M1t1OCOFyg569Hj`~PQzgF#HaZxM;5<^BB zBMM7|AUPLMVwj$Vvt!B$g(66pyTD(c^r68!icuL?yVGwxQe{xD&pO;*&^78O9X z>V30Qe?+6ALu-D!UOi)mS zH7)EtaH$*748!^k+~-%!IhCnfmElaNN4s3-3X^iY)0wBEP2FsxYJG;HnwP2Ze|uiW zN!#&8?7%|_Z=ioo@>PY*d0R_qU8hwG#;wPukFp46@@QDM$jfu6P7SR|=3Vcv5QeR! zr{kvCIz0B`1m1IJ^SQIIp>DMX8=oLFxPU^VjUj3q z%mGrQo)NOngqNdGb(%@yA~w;5fm;NpF^e*m9mC=GgIKPqJZe}siJNt+Yv^Oq8|`tP zRZdTvxJCdOS(AAa0#7te(w~r|k3_F86ZBl)Mi8#cldm%J9;*n1oOM&oaCTyV?)S*% z#E12AeX|+qWSq@!*)o7Nz`GuZGt?)p@+yKs`e(9?U-^-$##0Lv%&8dKe)r6RZ(_1? z4gQr8y!DDq0@T*e^{#9Y6GvZ5^BB?xcfJxwL`44YI@ENVWbB&5UNU&ksrgRpI@!Qx z-+r*NhI|#6>KCYILSgvLv6`?5-(`f!#Dob|ut@wJTsSGu?!P)pmnvU<)(t<_J=$jf zL-T7nx~O&|a}D*w(4W#ItFdQ9`Yx7m|2>h;ZK<4!#g;MyEOx;BrapYS6ugT7b^7V! zm35Vut>14xVBN#tFhsta{I0WvL%}Xta9c@3k1__D@ns5$6{z3U%uLJT!*@NF$JU%| z8K@^jRQ|jk-xs#b!2q&wwQ9`?r0LO?G14OzyI>z2MZ2^r>kww-RNCGu2;}` z+WuTs6LDO<{8cYVQ60ymHP@^-a1WiAqh%)u`+B4#VyQ$8q(K=ycLYw|vr24KV}(y_ zo*MFjHI4Mf`mMrZIoqHB^}01T@oyQ$C6Z5;i>{^|_Y=nJI{Q}V?xUic)3RJqM+NWb1eoI z6_*}%X(~=7Pk43q(Z7c0e?!b&@nQqSCFtGUUs84KL2`8%O@{O4Cf+&&Y$GoUPjg(G z7sQj{^CmV`Bc=hCSkU>f%w!K^+ahlZ-$kBMo_pu*YT*aJIqx9jk(i+Sar$pUp5 z^`}DJajd@2Q%6&3302>2lSc zHu(AV_Sw;YM8ryW_JSiI8>PNj!$mxZNQaL)F?N6aL)J4}<33Y!r zdKzolOL>dvpmH4Q`nA?OPo7!NEP(O|nb!##bBx3*{I{YPh~JB-+RNVZGeZ5kqCC1T zjh$oo`R=@*Gxri#gOOU&Afp@SD-yAxYJbI2-n4T@Gx!s(p+UTOxztVh9YxeZc&d%+c0}|-j5#cR`{q3 z4WP^KFRnR`r9O;FfX}1Ht8Z0lNLCFw#*i$%%l&+UsAxGvtK}rtytLR&R7>jqU))*9lr3O-9{U{43z<4h$f`K}N z1dBVzQ?VHQl51qqhzpq3u}s81*B<{0h(bv^McCjycPtiUOcpuWlqlhGo4o(%~hz8v_Y&k+Sm8}N4CS*B`?q41*qCB2cE zB~I|-0Eww;Mssa#ilmFY`VKyuB3^SkQhb7UUOTWD_gz4$YtH>S#Et@%D$Tu0rZsc7 zP?;yhAuj8k5eG-dF$tgqb(-yGYQT;%`YY#W83PwLMs{D1U*Hmpz;2_I()UJ@XJ_S( z{iE%fU%Ubq_1ne_H!}lsTB++BH3?{UW)PWxy|lTDjim1o>A9FJtI@i3NA@e?PWEl0b}LMpAJh@* zZj3*8^(l6vN%FsR+)AUO-BmDQ(H0RoyOzyb-7w)Up`u~f70WoIiO})f+yDAfJ+9yvb3VxoF4>=FYnz#vS9|Nt-b@XEF%1>X zCkX3m-`*GYPnESfw$KNgyybNLdl~iZ7+=5ZrEOHtV_o;++Nj5xui|U1*)b~rp0xWM z9Jvt|<(8dkEXFBU)wfTUPbvNLu!Ppc!9liEHs!Y>YcmjrJVKx5XshnMm&v|=s1#rN zEJ2^SH`&b61N^@7@My<~*!?A{@u6?xBC&%uEhPjlr0kgOT$M{JiDwS{83Q9MGM@Fn z$tlH(Cg1sTDFcy7`<6qUtwo6M;-1~$-rr_eDDD>T%k8T9%fYAzrjLU!dc`psW+5bq z+QX?mV;>ZTn^&hWBJ(XXgi6BN0`$jswMa#L&-$57tQj z%TsxzsS?OL#ZsG1T+~E_L9ZPWcYg|oa*DfcT8IXXP)BLt z#6KsDRUG@It#hu#<;tJ@!liqR`xJOq&8h5PD^-6Ausw$G`uj1;-DP@E{>E_&cA|kj z6Y&RIX~6EatZw$hIIw+5FtYZ{pt3F2(JB8VWkN$;o@{Ah0r=f3(D`;FW`j!8)VcIH ztXJc}IWj=@@C|HTFdB3cK8$B+`>jc_jx=1?U>tM4e_~!6g)ZUr`b3rT8dbLeUDde0 z%s?W18fY$gp@ zPuMg&{NWmYK|^%-5XgGa0Adby>C>g^2V}EzjnUL1Qjysr%-H)Cd5xTGF|5OGCCyqX zz|>#1pR5NT9zaf|Gu8_@BMq-sS-V$=)kNr%vd!V;1+rg{iDCk94o**9&ig{3g6DgJ z66TwY!va3+QJ|H}MQ)~Q8qn+0uNcCzl+a{mXkObpRc$&2Z#lw2{}QxsE{9<%a~w;~ z@Kh^*v*L5#DSDdtWi|-!mQ=`GSImLS<2mdDokpp^REtO+cwTij5gFPIv4fPR?98ZT z9+Pjt(3UQI2o9NXDVUY9dC^70&q}>Xi~EKn5Yo*LZgQ$iDp(MSe>be4!;W1&uhL5~ ziUqjLeT6Y#bHM$#Stj*4Jk~&Dt%8=E9pOLNjTYAEErT!zMgZ_G?BZZIkSHTWfI}zc zkJid88=@b#l$lHUGwk4<95(3b#KmP(H7Qw`0nhK62ZMouVdC$a}>iVy< zi555>#ht#k@9`>6raU1#YEH4s#O`=_6nQTA#A9gBBHoM!&=_AtxB%n#EvM_;gvqwW zMCc;IFrppa({fU1sFrqI8RfGx$ma<4sqlxWBUAE*p}#+vD*8pgJFiB04NLjEo-DD` z4M!_E+=f7<-)GkcQ4+670<-cwi@h3Ubc4KiCqSqLTMdFLH*NY3;Fm%irO6Jz?c5e~ zNz5u(4qMo|$Xiz5F_BKd*IUy)IcX9mkI~FTlFPORLHv4*<=~AN8HgwL_8! zSQQH%P##(KJa5kW&$!iGBzyc`(Jzp*F}mLFI1bj9Ce-{N;Cx@rp8W3aa;LBkQy(r% z%lOO9jwmQzYD_zpcMU`=u{DAUs;uVp$3we~$M-K4r4c04?Ij5biI7B1?85)#r;Amx zhRx8*Tuf_h)TuN2Dh5-Up}tp-e~BM0(1IT76lb%)CZ57V(;gU~z0jKQyl>K*U!Oub zq)nVxGTe$+SQ<8e@n%BO>?=NXX<#I?pMyM4$=n8#?2c-elH)0T@TVs;Id@+>PU4%M zm{b;;p{W$LH)C5)uo38u2t4HL&5E+oRt6HJqRfX)6*U*g~0~-_%h^jK-bQvQT3VjNLCCsX~vJvOAAlt26%lnvGoW!^{Kx z46U|^N%gAL%e8?B`M*87BsgW-zHyl1Rg@VV(40^hUwmZ`+rWc{Yz%6BC&@>n*#YOT zYG4dy*2Pg8Klc}Jr`I18fJiOYsSL|90K_=6N(t zc2gf8Ts_={K-Tjw?H(1jvYC&eiNepR-+AY}tgih4eMlAJHulcP@wJG1It%oO`qKZv z{Q(d~0xs>`F6#{e%!$1ATPGU&5+&9p4|s2K=~9W-Of2b1N-M)(F2~X?`cRoZSLy?!%bQ{UTyw zJ`4SmWiA@VJs0Py-Qvi(vt9!NPr)iZb{y{t@0pishj@hiC%%dA|K~dBDQGp&^r+sa zeiumh@A*IjED)$h@x%L8h-DPg?rnIjn8wde`S+{gUI_fR+-l-5z9f8_+iv`rqi&9~ghy8mJ&0>h-(H1e2=p!5NCF zM#+o;Vd({-U3UOQ0FLd35V)e+(zH1G){596i{xkldy9L&lsSu4A05~^7(XzndGVGm z5ju7fE@?6uaq%veRjL~u@ER(OkpGxyy{e#4(2RyY1GaQP%1?SqrXAQH4}N-(-?@If zYZkV7{;+d<4RK~zO*)PQT%KpX-WdHdvXMjAI`m?fj>i5AiszXARde_s5sR&EXuRD95CpP3^ZyP1o^|?*MYnrUDNCd#Q)1E z`eNf&k!aUi)AFHD{KN+{KX)*@e%~(OlkTgMQFqJhTHsJsi)v|{=!Q^@rtejh94+ex z^K(zFPb5&Fc_3}T;B^Cr1zRe5{TCMoJ#6LX7EYr5F0uOfjUAdN_}$gOZczW|6 zZ$#j`ZF5Q-Y})<#&~cs#L-8iuxf2-g-Rr<3<)y2lw9m~;FoGy+ejh3|m+M7nt;B%` z)*K%!LaYYbE1$Yg5w&YJ;WuJ=BXkA-iLgBENZJ&gUP_w0eA<2eq#J~xA&f}kd>u62 zeP8v>51%Bb18VwuC-lfF*fQEa|L)l7qedeMf|x+r*YF=AhR2Rch9@E2-of3Rv7(b3 z%t=-7-P8Y4E;sdR)n%jA8&1_Th^YY*>WNS8k(dkl>6P@bdbY&QrYK-Q6}O;f4rk(9 zDgY}$-(h_#*Md!Le7Ss#EI{um8Y-ak#u?hJYt_&(in0*EZ{N|w(}p3cRSuUs;T>mI z7B`Tky-rhci;qA5JKfYjzkDd-#Aetoq6H7Q`WIvs*cabq+gp6wPB+#PGZBP_i5dV* z*je7e>@y#V2C?$-CQIpdL==lW@`x17woGU3aSG#xb+4qWC4MhAUvT7CZL;CyTaIlUg+s z1D}`&VzI~oM;tS>s(1rnifV@*BHz>=`rkgQ80?;T@ zE$l(N=>STop8l`k(&5H;A>! zbCx71hVJ#>AF52^c}MZEZisf%)jZ^;U~mZQT?&!%28~% zq>JqkeGVS061v`g+?mc3lS(TP2VrL4T06$9@o$?auik?{!2en^K5Fa=b@*vdWyE^z z#KC0J6{2nPrR)sv`;Sz!RY`$#)J?c?pT2X$IlaxQD}eWbv14^JU)vfLx_J6VluS2Tgps!P4DQ ze!U9bal)O<)^dQGGdgE{u3ffjQ;ko(kFZtlc;ZZKic1O?&jdc5n_jut-Lc%NZ@e*m zmu-3566)@=Y3XNTPwT!WBQLXh>-oKdeUYr`ga|fU5fOAzIa~w0&jt@UIC`>i={;Jr=rOd!Snh7=aU`hM3y`U8b{1E&n89s z=&b6Q=R@-;F1W%FG}+cO43m(*aj2y)a~4hm#eAmF4STBK9w-29x>YL`oW+NON7E4p zzzbB%Ev-U#XJbT&*4qB8UaT)wq)b6Nuu#dulmc8%KZRDpmOeD#F7HK&SGgG(5KF63 zV{ebM!uBi~{0iItSYuKOPRYcUJ0*(!jZ#LFpiVBo#3B7yoNUuH1-Z3htQ2IJ@+6;{ z#LoJPu;lt;`YLD3?{i#ah+vCJ#0rVq^4niQL?!DSTRv(bSU33`*f8SA0S{MD$ANPD zQHtJktY1~Gxwhh+?ybA09PEyo`sR+y)M99$wK*B!C%V(B4V#{j{p-{ zYB$h^$nB#QNixbALXhim5f}JE&C=tFYuLtCc?XmRONE{wA2#$LNew4ElaNs4^>Sk6 zZAX!H!j_z=u;-!x6({{MQo`=%aPf&BoZOkZ&IYZ7J}z0&e_3h}7Rs3t;h>qrj3+S8 z(ir(~nHl4=BS&DX2#*g7$h&YRCFtSA+~RN(!cuC_--}Wz+J`iqA4!b_c5aui{`Ck; zRJd}t(;RQ_K7e2sNkpoUoF>Qj_dW9(8vD?BzFghQ={J8*tBpB>`^~}EO{%G^m)n#` z*Gt0k$#FPNXg_k@XR#l3$rZpwIaNA1Ap?#`kqd%>szE!NUIj#{xRc6#C?X_03-o>-m%Cin>F@7x zc>X%(6CDq^oQq2fubG$MJRaq0k53kO{T|5w+Y&Xbg#-vk2W&oC$Ac8CWat|*PIQ&g z9dSRYxTfs05^S5a;hypGcE#p5ryx$ko4*oc7Ekh3nkM zJp|>Jw&1O+l$}m)E;>9duBUz~=(L!U4|RoVd=K*5z2Rs505nUXCZ?fv<}6uPisUB? ze;X`PF))@DB4A-cl%#?1h2moGnw=Djub%9Q%PZtez&K?f(}2y;i|P$u96l?%4_#N} zYcDh=R`-RY`xh&@c@ADAc8vz1>|;%Q(Wt~x<_)^?U7NA`1JC+2jLG- z_-N2=#Bol1-O4~8e4>%p$8+JZ!4yuCQ;xq{pl)H4(z{)nXV;R@I9j%tY$D>1a2z>! zLya;Q*0S100E-QNJPT=-a8*i5Fk)1OL!m&RQ;q*Q3pwlsXmghOA!bcFz@J}c?fd@L zV>c)H$ddWU81PXEE0m+B%u?xcHs&0(Y?CM)DR=+0oQ!~2vpUIOM7hWSEF@J62p#HX zt^2x&mNR#OLu%JVz2#ZC3BxdX&<6!K5J~ySvoh<3YRmv4E-N=t#7J)5-A}jcn&aP5 z-0NH-_D3+tSe$I=ic7nX!WaL%h;lFN8U99y(@pcG2wLlg(eXW8k8138S=U_3drT#J zbK18^Q)y;^uLTZvKx0b5`S14pVO^R;%W@?Mj<`>Vg3hiIC(_tQH5OGSXEy2PI~_JS zRC>f|+WiT;VjfPZ#3)(H>yxl5Wr#Nl;4UfZyQnZDyOYTsHjrtKnf)A+6QpE&eVhZ< zB&zhZluwWdK^ZlO?fAM?m18QQnA*n(I1WcWn*jj13+S)t9pP53Z; z$TOR&i@J1)0%Mbq@6Rnm)wW5O;Jq|tsia>FaFSRk2MM4*1%C!+A3*?YJm>AlVQzDt zqmyC*-jRK(m7spw)Sf&Qk;-qwCLM~Zqb?jw<_VWKW0Pe2VAQzFaqG7x0mqumQZ4`$ z?rIbTjIoi{VNiby9N@xUyMdqrb5=fFpwwdS_{#`1O^$}wYz z^hN}smUmfrP(&lfvZ>*GMI=I%r%^N zL|91ZYG#Jfvr zc`<}*Hin@<{SxrXiluiOcl8#$f63h1vZ8sHDe{?Ht;LT}+eke3G2H1t2Om`62Rd7) z2ID?FG{0F}JzJ|hSy+*i6vxIXQGT3?E{02`0)B427b`MB!Te~avO`H>QMUsSWX4WX~JqciHf32M; z1brkExo`1usoy07fVnvdz&(fReKYJRnZq6J`!B#^5nOm6x|s0y?N2+pK{AoAw_lA1 z9({wy7(aX-!(Wbj6rFwErY|bJdgHX_ z4He%YdRMrsD{qG6T@F^(Hu{E}f$pO|zyyXh(TcQPG}ZPvPd6VG!#z0aCU6@GHeNBW zzfeZG8wwHlN&q;6v_xjk+W!L!7eh6Fv3Yxh|6z)S5g)u?9)8O0X*oH2I@1&88;^X& zrHUBTa6ZC$H!BKE=4Ffr9i)L@ekRvz0_peKR{-7dqtCdefM2izW7BhS=2oMiiYRzn zVihW>zjt8V`M^zDROZM3oq$h&H-~kO?A^5sB4~Ud={E8;5o){AO+5DwdaoI^n=~d z!h+F_Aj6H{K35!6aiIu>7=5t>$Fj$wdLo$o??{w&TTx{>|$F$Mb2-5G2 zRpmo~wK@LDeu`MnmfHMaDX{@-)Tiz*3oVk&U*R$Se8;cpN861`iOF?8VL8JmQ$DS% zUADoJe{NkhiP|0p|9ch#x}6s=fCYH2P0#v1x2r!U{Wnh2Nl<%3tC&<0fsNFtMvBrH zc8g78M2AO(I#bTWXknM!b=Qrx5-=XhM&2dKyfc!YqNKkJqnS7Kqi`%$ZDmlct)8D4 zoY1vj>Dzt!^0gbJQ|wt>x^jlfQbyfTILIK_pnp>f6xsD4_`L+z-VBmA!>Ego$@f}GkieWs53Jb47@Mnoeol2MCS=6qX7<;gHN?J{=$lpvo{-xbWVGEOX`gfoH8jBRfPuhpqf+el@3=o!sWkxz zlX3{NP(l#%ppElz%J?1+e^A$aobw@-c#IKt$Z?FEF3|c3X4o!!xA`>HM6H#SY>=*H zbzha@3beKKY0*tw^CSAbHZv)QH6M4&f8B*n1_BTRPT=RV@n7X|jpOQk!aQSrA^J^^ z7#EOBW{HjMu9Ibkz7nV6SNH5hdS}#IFVp;%a)$AnSLufEgn%RCcUVJV3%b?V!cN2d zhfiu+;HvpQJ8n#TB?UclgcUGy3O^O?;Nq|R#&xGPJuK;c2P zE>K%!$7CT}q>oi2n(@WreqpH5Y1ACMU&9e`s5&4ax3CS@!`^Q)!f}NIN+8%|I#3yU z#@N>0LjY}OE{4YV;4Uw${IjrVMX45B_auR<-f?{z#6G-A@AJ6&j!ohvu7#!n>2Q9% z%*YpK<<2hvKo0-E{{_(8h4*9J5WolMYjZO7;forpBt|jW6R)-jZhBb7n|)-9FWc;&&&hZTVKj?f(nI9z5ZC zf{%RSbGYYgkANUiq6AELyziuxfx_l3cL0A+N-(@)s=t&*4p zh}6gEbParZ;|0S(44|*C6`IVAcmOe+f26-^;{r&DEX}a6xMsP(xd)PRO-lKh3;N&} zAzKCjA|ffWFZa!Tzie%M6W*?q(L?K!>Nk7@g4*CpIkQjj(qRE!t~`>M*yYnL;-xC$YK9ss}q zRRB_c`U@XE*gk~xC6+A#01+_&T%^sf_}fp-x5okdCI>J0Izdif2yOL+jsa3@{gakJ zVqyXf4&x>Kc%fV@VE1SZ-Bq0>5)xne>O=U*CqIX!m35R^ikQ+cXw6w_KOhD=DEHYw z;q6nWFluK`Dm`_Tu9d1siQp-|Wu0Y*Ij_gxiAk;G;XIss#(KfYqeK!o zJ-cF_0(RU=DM1jQ)OvAQ<+5MUC_jK`;ek(nMM`Na#OjS!+srU~#s^S{IWAO8EMNlJ zR+{S6Xoe)wAZR&ilq8-v??Zo&FaPHu z#7I$MsTzw2&Qb^TW+TP7!R(nqp~U;E`88l{xKnfkG%`tk4>YZOSaX^|ocS1I8`q6y7Ma<@+v}b$NOR z1Z%a)fyCu~)Om>j>2>3j+7sCGl7E~5h~e-8!~h9JZ}yJ$BJzU{2|Rjy)_OrQA4f_^ zA-;(DdzY7&mpK4_IYDsQE|gb(^GlYeKj@b`c5(srX4p39`rX6l)z~aA1_TBOi5C=G z5Tq%RM01zc0)L+IN z77G!>_)u>U=&se9I@N7xpy>%FfOPdw>h(HrHk(#2E*A(c%SA|OFVnvO0CV?#_#r7W z+c-{2iANrv363unV@!_JF6thngzX69gTV0pM8^fKmI{2ECR5ZBNHVjW*;v73kP;m{ zmrF4wM{5`v=tjgCzIy+o_{)#{10J~lVN`g60uw)h{&FR+aO`M)vR^G@a22^=!th^`7GM1((6GF5Z$x97_%i#eSIw(CnFsvN?!Y8DZ zwuM=m2|PA68=R!Cr-H%0u8Y1IONTTNnu${CW-CQANfijZHblxR=_GLn~6M=$BgHIK_Hf7`b8lV4wvX)~wP@@PtE0?D;s?f= z5H97JW(ovJ3rVYmW~+r}qU#D$2b83xL7~CZ(%`WBbY>|@G6*42Efp~_(u?7~DvlnV z!ry-KpK$Lz_aIrGLy`NVS-UvFaC>7dmFnauk(Z;FmcNJZ#}9)83S_BM7(D0Mjx-I{ zEY382lD-E~cd&b?8?~yM0iJZMv$kHx>DguYDNkelTlK|1ZZ@09vW%^*tmy^&z*^dcrl+&H>D)e>&LZX92KcPfF!! zxTG-XjP*`qfc1A1LLiGcgy0a7Qdlk|gis?(aK;b`2Ej_NKX-+R8 zpsl~@2q#p{vKqfkYQ+|M*)#zWsFow_AMdl?$OQWj9671ZnzR0?Oi!#n{<%*IA(#+? zNhysk7&Q9Jl8B_tTf|L}OF1;634?q^-w+h@_y6tZ$8LYs3^8se(Ei#(Qy3lWMZ}!} zIIw3JQ>PZrc_#U@NhTD}kRZsGw!jgGfKu`(l4>H3NDYytyaq!SaYRyikVtTFE|3W| znMNc8I57|>a3a-8QqL>E83Pd^jv~~0A}}H>&Ys4J<5TGF=|OF12z^7t$Wp*qdf^3` zPE7$%?{lIno?zPQ=pz}a8FM^O0PP8ae+{n}`f45!4c)N;tyCbpY4@NDw)MUv$7iv; z+OP&iIO+qjw(w{5<>gEW!5WPQWcsSgIf{_n>cZ z5X55`z49%rQ%Zl@RX@x%8f4sqg9W@LSPSiR%%xvFt<#oYO6ir8*gw{bYB@49mqS8L zbpF`%9E8YV)1D^tleLa4KK$8FNGT~v5=bdoy10j?^&J=bA?MR7<#N{}}FjWO~!?SqKS95>itlGxd@X zOwTXCl}F$lGLqmzDL&^68E5Km#=w~X=M0=nFcNxZd4ZG|7y}px#B~#5=;775+Rm5& zh*01Jw3OXa1&WG@N>$Vb22re3Y*4Ij+*o_3=r$>1*PYie{e6W25CNp)3Ce+s z0Aw0;(iQjkyue^@36n!TPQd|WS%&)_IbjuEb$t|o5D+3u*QOshm?UehBuPjJ!J5rx z)@U@K^}r}S<=08`piTSexO5(X&hX1CzulKCO|2(OQ|~VJ?fw=2WM8@OaeULWZwjc; z4SPp0b$U^Ct=lA#5^7E{2*{QmCn!?ZKg%QpXUHOB`8hH!5OIc#b3|M#K4ajS1n~?J zGBp2q8Tgx!z{2zf=BNR}nYw8FchaTmM6kokpXXIO^tAdNtSP z-nV?>o>GdjPgqpE z#XskDyN2)-j|u^bBK@Dc524=Jcuuhke+Jk|@Cp$}6mi6yA&NL6&JafooO48+z`0U% z&KV-E>5id2-ZFu}xckdUgMu;jb(tG&8k!#2Mr$T7DYbS;iDoJhmntY!tLW+-0OQ=& zCqM`GW|~+j!e$jJ6Q&e8oG(H3gEkqmEYlMXW&%=VDjv!*bXQ_Laqobpj*8byDRF9k z8DD#73L;G*rGU&*WLX9&GNg^Q`)BU`$WN`WuO~^8Bw3cF>+9?3>gs9&rP$_|ZnP|= z+*AN;x(A?eFwCPVO@TvgR7r8f#dYISCdO5ml-vRk~5Gc3Ce#fXkX{Q5hn? zsfA@MHiM(6Aao$%GCp197*E_c2u^^^gw67-tT*uBqbC8O<^xuMQI=n(S!3zg-M`Un zHbtr?C8s1wkR(Z7A8?lCO_fWX1-hW+i03@* z3HawPKZK;Usm1|-M~hN5^t#|28CPJ)1Vbhi^+|Ocz=@D@3C;k{SvajKV~8RGF#!$+ z&PmTTrl=5+@Mf7|f%SJ4Qv}z#S*J9GqA4P2pw(E%+Wags5DKL-O66{pdwLNCvvIoo zkR?ohC7h-*rYVM$nSK!hl_JM2`-Z?x{eVtItu+$-=L5&lN;5kv52Tr)Q=+~+{YR^( z9-eBoT1eAWW?6<-tA!*<{Foi*xV&ky=@iIo`p}8?oC__A0PtdMcmKeX{_6*b@t|Yy zU|$!W^9|R7=O)ouSYF3JfBB)dCKQ`3jFF-_hVF2N*rPz4Gh2p{-li=)=Q=yYxSMW? zySaz$1bliMpn_vp1#9|e`3)eHnAW6bhizS|DGiGYD0g+ETVhDN4OAsVLki5pv0)ikgg2YH1 zAO{2+hG)vo1@yZR9XoVYor&1DeQ8R(END$lqh!&tS(Jdh*vc)orl`4wW3Ye3H5Gqt`pwj25roI{GR0#*nP?sjgjUi$YxX>BcS zNGZAJdEE0nztidPAP6`{1U+IO;81Pdqa?u4Hldl!+%Ny$_o$OQoxIM%Yz@Ek`BOb* z(kmOgc;|y_V;>2OnBC+#VGO{KG$OZQNN2f*|lbj|(9J*L8WP z(-}qkKhOj?5co~I^2D#4ubh1KZwMio#HH}~{3KpF|3uF{v|S$;e!hs!#9~|HlrYO1 zI>5H=A4Z9^>BCq!GLUc|wD5EBh+u1erafS4B0^C$rv|v0j1B-q6987~G|IllM+1HU zvELwN7eO+)JPOqsiW47#@I_ucLF zU%dU@-Md$A`@SD&^C#e(^G2g#M}780g8QBTKokZ&lu-ZxtDpPzFBfLdzL{1Ud+M=i zeDU1lJ@=rL!lkRr_;~T|q5r-?Vl+2p1k8ZjVbLg&aYAj+qLBdlNFy@{CQ+t=PmSiM z_M~-P02jFj1X9=Qgd;+jwUWxz@x=cZWsa)11Y4;vDOf6G@yZKlQJl;VW>74Z!5G8FP7{}|uE6*GaP$ydJKFll19$!EPyT%O*87(OEkX$4d7kgOt^xn3 zpieV_4%N0D7Xb)H2mQ$J&A#-zf1D{z{aV^J=cgy|!ugY_%PD#R$9o^$!1DS|%3qF8 zjM)-~J2l{0sL`22K@s{4#F#`RCIY31cB3~Srqm?Jq6m@(IN}YK2~{ zuarW$n8TS2QV4o`14WOL?R&uK<}e$Wrwx;H&Y>_s8I?5 z;^kCgyWepro__oo7G@`+FN+BR1YF?#E4Q$`wgXBTCZ}r1W}OiHf`=wh;59d|zx%ga zi$A@n!5^7EtyU{YF?;qxgt26Sf$e2`UdP>oi3o!57pNq>Q!tak(XEsi-m{8f1pR29+|3@ z@bYt~P|Rmj?@cO&8%rCwvbfT-OXXomQv1iCM!Asg0(_`2m-5l4OYtr)h@(TcEW zLx(-n7wMuPl6LOA)wuef|272tqk?`I_{Wyj96JGkfu_*DK%e%S{MN5h6XobHLQ1S{HL<+5gXOjDJ^Ko__8w&a434;nFeFwQ{uAa`N%oz~@5Z_R1zcSzLh| za9t6auJs8ZGh+J(JD30EAGF3d&aTkTZs2*|FhK8#2;;K*ZMi1|03v^2P-JLipbvY$ zcIJ&=EzCUgJJiVzTup0kx{9-><}g(oeZ3*q3$U@%!fvyJX4}JV%SE&0cGVswft(~j z(g#SsmmCGsG(Q1QtrRd*D`R@HjC!@yQyd6bfCr+4SF`Qm#?l6EEpH+aG8Oo?)~6)6 z<=y!2zw)-1)|FCRN-1=FUl^?~gh+x|;s8Xr7vKl(Apw9?5*%3T_v<4plc}G5#3pA;YlguYUMF zclqK6TI&lbr7?Mg5Q4j|%PsKl2lPjx`HkFD0s#9WLBG$YtumKvQ<^E{YiHkhBVRxH z8-zL&!|rdoUdH^)1Qup$kr}b)-w3U#HISu}dV+m8N|;SMlvy-aJDkSxrj(+rf$p%- z8HT0#yBmE(Kh}=#wlz_2J})py3#UvlHlz} zfRTxDFToES0Re!%KEWVT3<7&!@Y_e0uNEhs`tnzEwZ~qkPCmVGC?SAs8Aw~R54ZwjI^me~eN;y>B_P4M7Gr#lBJ3x<4a<=JZW z)N5bLRu{fRGx^?{>LHTPI;fQLD3|i66!WN*a;TK@DCM(J9N#Wq=#Ji`lR*DTDiM62 zqt)@y>iGKTVPmTWpLbggHsHI?yR<#)FtK}~bK^(C9614i9+POlfFDGFG!k^Z3NvRGigV{*$&_bbrrFXNa)1R{DJ3Bx%6Sy>*=UAX zIPrx=v&0-4&l00%ITaGlCFr;T+8tkm-9@YIkCdFVA7|3!fr89r$SuD4i|>1PF8oY} zB4TCV1$@Cd=bq;Y&bbZTegxPnA&$i4Ny>de03(Uf;6P?hpY}+vO!?SE`NWGaW~&RY zuw3Q*pyb+s-_mXO4~Xe^hc{!pr#s_2+I5rKpE9<~ROjPhYw>OG?)yKdN(6&|ZyK)a z8sOW&?$_Q6Ar5_F`$6QsB7hOAd}6<5PCwA6^^<{pgwo=%XCKc`o;vH4rp`F|>RG}v z^P@m=FC+**pLBXHCtws&0Aa*+h`b&&mwzJeesDp0TRTDsMF=sVmqG|h2r=jvdYO_W z@NJ;muR#PD1pXs8chcm(B!CfJLUB-SKWqd@5`d(%iQRTe)8*3Kb7!3L^l6qYFHom2 zPcylBLY=OvyU4Z&;+b3n5?khP^vAl+d;T|oWDebRs9I6Ny}{e-m;I%SA1c4mkm0Jq zQigF?C8ZPyJ0h*Gon1i)k*@1X&bh7e2Z4SN5&99}$ei3^a$gew=<*MSI<*7N4-Nb& z?datc>V@3Y>EoHw%mT|-k5QJbFqW-ki-kI9rc6LfgwlcpD^jW$Koyiylu#Ac1?jEq z2$hV{z;T#IiE2Ue4eqyAz1G%}Y;CO)Z}YBb-d-hy=tVcf_46&wZq9`vfdM^$Fn!Cp z_bva-*`$`4>Ff#NzzpsIBkpvMA0{UTq-xv5{De;tZFvcij42&_tSh$6p zIoIzKLWrVzs9=nN#QU5o{XbBMr~bn3)8LOtz&VddVBXhSUIjs5w7m`bQ3)^#`0sn* z8+l*|V2kA}?-%&v0DqDINFEtu)N#T&%ZxGNI8Fq9eFOmL{xtBL^DaUNAtGXEEgu2j zXnM}M41ypH><1-5mz{sV+4#fcfh2&DL^AED!9SD$rtLV683kYnVaQ;tBru?VAhdm&JkSKNB{3?D7U3B`_`86gY#Y$q;Ll_-_5)G6 zk3fUl^czA1L7)IA19S`UNw6Q31a=<>ARjbspC%6t0c^3zFo*z$3I0*Ee_A-l?u#CT z2E5(Zb+kZk`i8ysN`ii)zyoLE43meF0JbE|q<)}3DELPyy_AnU+esk=K`aA;p-sBMh#wZq9qOLyYX%8Xldj#t7$s+002ovPDHLkV1fx?1kV5f diff --git a/deluge/pixmaps/deluge22.png b/deluge/pixmaps/deluge22.png deleted file mode 100644 index 29319b3f38e02265d74a2bfe065ec1aec9ce5320..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103 zcmV-V1hD&wP)T z(@Th3RUF6h@44@rG zl)7=DBDDBeNO57e;$tCg!4ccB_F>IrrcFD|qe&){C+{g132U$r;6+&{3C_rO2HPdvLbJF-0UUaP$zZd^6| z*sD9GyU$>;h zDY4@zs>RckOGOI9Bl$$k*#~UuTSfkXS4NUCYpiskMj;!=HciHFT8ZaHNyQ`ho=YrZ z6OBfR7sl}PF0+++3InmH@7;XxmcEJoc=9=27f7XuIu5yPiu6F5b^s{og_3rs2S6_p zr5Q`3Nr4bxn)*|~D-6_1ecTw<<;Ynxc6j-hKfi43}~ zQEPVbqxOS+6E~WzNXl~vLP;+SsMcC!5)NIZ)9D5Pbb0~0rU3#4NU7-b0yGt-`gYt4 zpx5n$ePK{P3LyX}U93@WcKGJo zBNU581Q1F|5Qg-EkZuqXh9OESQW2eMrT$&tDmu;ikF}xXUenM;ryFqMOc~oW2*abK z(@7FT!z}bPfd`Fu z!8q}Vi|M!ox!-ej>iIRIV~t zZ32++910m9FX|w4K|JE(CwgCf!?-JL^x z^ZwTQ=AS!vt$Xh|JI=HBe)fK+`9hV1kbw{Y0FtLql(Yc=g#8Nwpm^AeiEE(^_JZT4 z@bo1V`}2odMPl#i-IWd8b)0P6z06&$0WU8vf!B`qZkFcG)&fqhwrP9P3;+NJo+`<| z^!~Eb^A$9_`&D{7CgSkf0pA+pS-$-onQ8fB zyI(WG@+R{}cK?{v{w;+<7MILsUmnkn0urI&I~q^KB?D|c)etZz5In&>S-I{dsa@#H zxcokFcfHc_z8o{VzW4go#`b34SsV*s!hRH@(Q>310tslLFd<(7uK$HKXJj~#ZPo+H zsKzyM*^7>l)lo(|a^9P~NOY zyZh?%)&qAsoNmvFeBR2`ABz`5VL5u%mL!(dVE2C9lKt5v}ap>uFZM)0PV-d z#XK*Pp%V?d1F0<1IItD()|OKhCWNPb_bpL`cU4x%f(0hT&QN+1aN^-~*JLj^iyVzV z=8*yFpetO+%M5!PuAccGi|?#JcR0Ql|B4xN&}Ax9IeX?n;UFODD9=er6EV<%Y+8=- zi=y2a+3fR4U;%cV|I>7}pUpR9|9bV~+H@#4%`qK)8Gs{rpsEA~ho=^4RiK{c10rxu z{*|*NE-pDg^V_c(Uo5O}5Aazm*@$x%MOZPW%bbM=Q#MKkZdO^2kh;6Nca z%EV-eGO%C5=#NWBetX_CtPB3P|E=lt-U9m2~iOT1aLiz#~kX2jc@q7bU zcX4g*+qBPgDzIGIlXMeQ}%icYM`DLrhv9|duCTFmEZJtZ*lUS@)U;{2NPQ8v8gg= z8akSN%NvO6v+fU3ya_rVO`eaJp$$T647Z#BW)ZzaL3_J`sTp)Wi71f+1;og@gqTRA z!i&w^xHsjezSeIki|rXHh(l4DYAruZJK9Ho&MIdrQA2U$QLl7z-t-DVBGGgZ1UL1O z{tKkkss8#d*?!;SM6Bq*f1>wz?K5ENVn*$&9{>qakE37m<(F&Ou>E@D;ki8TQ40vLrzu#An-LHciJw4NPY0=>lqlbwG)cnN#jVqlw z9d|PDYt0B|ZvL7itp+a?Zg~hoDT;vRS}0wZ9yb^M3zw|Uict=ISIJ9w*=S^&FVh=Z zpS^UH9CZ1-#c5Mj!}AMP%Zvlh1p^088}cI--hMr7H@a#=%MvGH7M%k=n=(i7*|Tj+)27_Uatr|eDm4dnkvHd?X`DtIuI+%pJyQ!g%^-Vf=CIEjT0@}rcf@$=} zD}QQGbNdg>ljiz!u zCREM{szsO0BQw5AknyO zi-=8~7T=Lfjm%7`vH0ryGQgzGf3Pqdtr2r=C>9@9d;t$vV2geCo2@9ts50@eQ&J1r;|V}QT-?2JeOBRDZe4e$JIV8Z z(hR-vvElE>$Jv9Nmf`$vRXJmp4L%}kbPc#bI_=sD7F z&p#3qJ8>wU726e)c^qvbyNwDa1yQUePhia#A+@5fMvQ}F0W5`tNtyQ^hEY3=8*6g8 z4C7NBKUR+>R?}-uStlhvp*6jk_LIGQwti)Ly0Y^GyYY9k7x7*)_HX;*SSJrP5qtlNQg@xsQ&X!l<*sjH z{EflZSPq-Yr?rJ!w9+T4SNZ{CsOUjawOb-GC zB%a*&LLh3~+(GYBR=q}SO~C9-pN&&i4B{zlj z)HF5h3USig%1%VZiMJz30|5bLHGeak8oFB};gOd#hCoEDVD?nZmqjKDnn}{DLs3Xs z&3Xk*X0Zp0{X_tVz(HXQzMVemPs_R8AtdL8Zk;Z?)!AW-s_)W^%vh4LiJj^GH~OLcS}2=qMh%;nf>x#k zeK5~0|Mt%qz!3VEue-MSsD*_kaUNaGXa7M|4;@ZcDho2NYx_ApDc7?g5y;rjq=3Ly zlJPnScqL!A=hZOK{2gp>=im$#f>!!{+v);EKZ_rdKL30Z&nlgHUe8s4HTf4?pR;H4 zTJ$Uo7ikK@Bf#3_Er_VX%kw2qbNRIbj76pj^}@;?Bu%98`6`}%pOtKHGnjez7{j9= zm$~1K^AA@^^+n0o@ePUfS^;?^TWnEflrs5-tz2vJ^L8--OmQ+(xIERH78-evz=jFEDhMvG>x6N_d+ohYgj@2trF*cl1;CB zZ9#i&@qQHcYVgQ<`qML)*-)^1!-XU#W7UFwXXsCmQ0BM>EeRqoheu9qXy*U761Vu> zYy(opgxULDOwzW#hbxGfzKS6(XF5s?HFh`V5bD{))vf5Pn{B3uv zx0C2zU)V>iCb@jhM&{iv&GPOzj>6KLYlf^uCq-olnIDSwYv2syvdBIZEDdbb>~(`M z!4M*Ri^lE|qkcSy%=_+^xW1Sz6-uroF?Bt_IVv<5@XP=9M$N?rr3hn7BPma%PrJIP zZ`qc4oQTcJrF-2>ulCe~c+OLswX?Ys7RU_lH#&H#5AUjq)#U&2idak1viB_6e<8Bv z=yoe+Bv>jDZIM=GYMoa)vuJQqSJe{|sw6}mveE;VfU44tSDM;0;MO{TxL2wt9|^Bi z<9n2b`Jb(}9PN#??3pZJG8`Vr01{-MhQjw5OA<2I40Wujxq9%lcxdx1px8iTW}OpL zA4XCa5~4n~P}6T!)KWRhsme+;CQo}&h%z@>`mjLy=;#+S*J5x?%~dRmv3&8+gI=g# z^PBp}k8QH+lY5ADNC-d|JqTIjd&H` zq33gI_2nMdh4;!Yi$R8g%=ZoBUwXAviVqwQhAY55Y7MUyw`nFBK$OoSduSSCqpjW9 z(IGv_>Gr|(Eb^hLm4s?x(dV^9ut+UgWPjkH^11(4EW+YqqtF*cpW%@vue$P=*W3QG z8Una9_ZI5hE;C%>mM5N_#`Sw1Y)}}Hi@$DvnK`f3ANt7athxW--++mW{2{l3@O$TXb2a+KP_ ze@@*Kuwbm*R}lVRfi&wY&nf2YY7Kc`{K$!QfQ9Ls#wOu@HH4FT=G_B8=ze$it6$Xb zu9oX@_thm6!=(fH>r#e7Cq)etC%R|dC|##^VjGWSkiO^j*AIL@31H2q z>bg9zaCha3-0aCt{B(1|F}3K-%2 zzJ)JmwqcFu&eUE?crqH+hsq`*j2$lhIv>TKRfu7W)D&11ek5Yn7QN#ooE*?2IDZ_* z`k1xlxyFB$H&d@0^7hsqi_k5!;n{?~hHA{=Y-jr{_>1E!@jTlB8^6Hpq;{-x)hpjx zUJ!H-yp_rN1<&ShdPm^B7eYxj*1uDMzEG72%1JjE`yk(Y(aE8j-1gQN+>J!FD%Fs! zYRifl3xlGA71+)B9%?H^%bJ1xm- zm-fp^hA?x7y687~3-mAS6N8$@+n3^twS8XYohQ(svM==dZcNG&34Q%8Cq@6A8iF z%#h5S%Q$4LujIKXND-43*-L5!9T4U9M#)G5tByrWxw{NO6Sv`@x@RU>bjy@* z!!N9|;~2%N_ui~ph0amxp3oABIjf9MKPVX&uKoTRY?J>@19OCjLzPvRwWxPjf09WB z^EL#za+1|V?OYwqN+n52X_nv^!0`yT_9il<4_}OMy%^JL2F(T<#OsJN-A>49Pye&A)x#ivC=EiCLI(bz(#M6rl zPEkaqBw&B};j1N)G8b)D$`ElO#sVYa4D`^5S5{4(EK{4I;44+2v*)^0rhS+KLj}5U z!OO6P+nI*p%~)>&Y+9EQI(zX=j=Ktek*3Z$==;Z)k1%j*ko)K|y3)T4S5B-YChs)= zrKtf5&aOjkcXNG@&-st-^nJk0t|engA3{3Onr#?fvG_^PXr~KemV+sSmj7OuC})n_ z>+a~$|83`1@G6CB#S96G{~JiQ@x38#2xmWNQwG_5`1H<9=IIl`g)6Jj$q2Wp!Nh!c z#MGy$k8Kp3;h3SHdYbI*HaQ`xF{KJ9xTv{rW8;_aA;R?BRMD-i2po4w@~@tZFs3hV z&{NPlEI0&=*A1?KBVd#J@OXL7WQZk=|3lA0-3@LTp04ktAv3?DcFV`DKQC17<*PxJ z#Kd4O?dTl7I$uv{G*C-Na8F2dH1^b&h>!6BDs;$BD5=ldEi+(qM(%EG0880eQ7w=L*PmxU9{bE( zWE$NX$)hMxYE$r6Va)OCU%Rh!4qyMgQ8;*#uF4X=>GnxiM_cR6n^9PSGhUe!i4e$C zOc~n zb>HP*$tyxbVzL$ab`gBGmF*tEOqR}@SixY`Vd)4HRM<|^VfH@^(0Kav1|=hmrG{;} z#hcc0h?Rme2||qJiPc+ZV?oh$Hr>2+{+#eW$f zRjW-!)g0*|=*oLg7!zOl_^xNVU3Je>Yn&LGnE>deHbK`BaK%dZnBoR*dyX> zTafL`gZ3&g3Dv|P>3`(rGJ%^Ji2k9!yW^|f4z@b!Pm3lGr?0z^-V?C_r_tDBn~N<} zhBY0p{>t4vps85+9W_EH+HcBh<XF=UeL|+IwXt2@ zv%x;A3a&QmVYB;Oj#9iNo)T)UX($$Px47D}Mn{mAn+4X8f_mGO388h1d_y!Xx9`ud zC)*wfmT(nd>DVvm*%PGfSqS{fw%fqY-8c-`3T6Qy6^@;sz&|5jDA ze8T0&o)25HJW@EKS(~Na!7kXd01R&0!`!X#&zuRxEBn|%A%A(>)oETQ@7>D$*rBa- zGH>>98b+%jX)p!&gZ%et?%}Wt9HfU36+W85QE>X3cuZ>Ev#ICXItsKBo+ciGD>ubW9hx!-={@utkJuZhTS$jN883U>8Q z4|uucCXuaWhw`s?aj}n9G}eX2tHzq$u7H~Z7A)7Vucp(X@A=ZaR}Q4tX!L&XDRqBx z9Ugj*_zor%kkjnH7wtL~IQ#m&M9=88H0it-YYeL|Q>o3LjRqb=?Y$Tj9$A$&C|{+u zTlgd+VqLsa&t9G*RBFgunAT#U69sv3W-=w@zxsaR#;e8Gaet@lfefBAgX^*V-M@AD zlh?M18ms|4z(y8VA-9qUzeQe++tiSYNT_E{-2iPGXsI%R7 z(1~5|)}Md?elFwI`X>$(BdoLtq->{d177+csN3KZ=6gubhA}J&Z`?anj$s8w-V!K{xKJajQHO6>GphXP7`KZH%S(MQ zWhD~_r%tlojtbRP}K_yx^&kt5Zk*RxBC_$r^O-*kwi&xcV`%p=_kun_hg>%r&8fW^sJIvvsMt zT)sP4K+gJQjUar5QKg}kUg$};NV>=(o>JW@M{FCzn)1fri&T>4ZKT`vwim0^cK-++ zn-D8K$|{--nljxD6S4Wxu=_zTw4*0kv%FS)HM?7+n+o!)+EZ$=Z4AG1SMn8iE#&mn zL;9=A7?N08!SQ~R~P@40!2CSh`;PnTj&(7WK!IKkjdj$T1{ckZ9f4J z%x|@zGiKYEF6)LdKzk;~uJ@)#DHt@7b?yQE( zxmfV&CI}HVdmXN!2R&hi?Yi>FMYc+j_fK|O$3u!R!w*XSW$p*8JWXH7JEtng>nv(* zWwGM(MfO??%bMU38BE9?qENJpZVa=FEbv5f08d3lSX!7sTa=?i%f0No;eK~w__l<+ zEaT$z)Hf@p7r5ECxAT_I3{1+OO~`NXKYQgXfQW)s#-b+$ zI(&48TPh7ZwhntLyuN<NI|+@pDQ)t`F-PVWvw0}zM)3(f`u%IGFJ9uJ38OVL zi4ymU>mxNXtC?Bp_7!b!tJkIXtH5?iz{wh9g%h#c?~{x};B=rRqLCI1Uz3YK0h9=MA}-UDtG(~Oq0`9V<=Zc9!Qd_gU}p5rqpw;s=k^`?wM{#q^g#M{ z5h4+IA+)1Zpm#a65u`&4Z;@|pH-fkgJlYOKgpmQEY#rDWyXsLW&ib0BgU*p|W5oX= zIwEY*R*OcAoo78c2wzG53y4Vh=B%_P@t2J?<;`&~fq)4L+KF4epGH@iP<+X2hJRs3%~l_!_EwyvMEnkR&R zDGU1lSpY-)qkky)Kp-w`t-|k)8Y*J0o;vsa>w)Xdlsc+lhee4_h$|eRMEGy?^2P|h z{g#y_WCq6(JMuXzY4O=6+LtS3U86|IAi}oW&S90daYvU@>7+4C7qVN7+r8Eb(#xOc ziW&&m4unz7`>Z?Zmb(>}K zOfO=&_4gyG5ZL3z`%nz$05^3NuPN{(IT*+)|GkLxURG^RW%<5|Z9xEwN#X%(K1w@| zyT`T0Z<%Bc0Had|VDZY{=4~qv=Yrpggx>fYV-_S|^YD;yo=vZ{qUf0T%LVOv#ri$} zs#MLuh-{p9*!{z2s^1hk(_!1X6BD(l#c|>kcn*Z3)HC#hQHPaG-qDO!tfUQBn|usv zZ1>Ycayp1X7KX6nWlHL0WGigxAY`GVd+VFKlZCwO9Qf6*d#JxgMA$g&9<5nLDv zDns|5@_8(7w)f-RjpUp-t6A*(>K!Hc4)U~~?@J``z%;cT;1pe{G%B90(JuMw2j z6yID`hG15d`?-bQlBW|ruGWPuktPD$r{@vZTex0?LiNf8tM0?5qdp7wg?OZfg&)d> z^lucpaMZb(=t!2}^AIfdZxZqb82UM^OTV`#!3M}rn|u<^Ndw)-NqJYX3##<1Cih?X}pRSq+R zQ_E=XTD2m0T@gngBtX?_f?SNhD?!iW(|KC68z7E)s-U;Adj<)el1_2BUQOyst7gP(-IpHpo%Xc)}^sR%5u@pG7e>nK82Ew-8$e%=X72RiWlet{E;{@R0n>CnW2<2tH2VQUlpG#rte zPyOf8<5{}L;)oUGAtOrLv-DR?nSB<_V;17j zm)-%-KhrCT@Y;bxv^wSMWe4|Km^yofaAXXgY{`nJhA7B()-;5V)zlL!(oo&3=MWhf zIT@{Gwc%ZFoS3bpvicnp#d`VN6L?&21zH?mxM=?i1Q02OVaqC2mPX`@>e7|10Lr>I z`$}8QsxSDL)9x+{C-MU&>ac{E2!y!uWcrddep*c7Gefn$+i@F~=^q~Qc?5;JbB6@z zBS|U{EU|8<3?kdwcD{1jeU|&)Q*L|@q2H&O79@otRGwvs{yQ^3U0fMwEZhYfJ2jmS z5{Y34XYT!P_X?y+cWRww_K&tCj&3k8kDX)9(gf!LQ2~v-zEc|_B&Hi;qr|XCdxWEI z=>MvhvJlSHH(_DF&1$DiMg_~aDB$$SoS9Oy475We?w&?o(~Fx`@b9mB z+r=O%`^cz20N1Jm!Q3vR$}eC>B{vLqmqi-Dg?12$2jBKdX&22&{mL7sO{!ubPgGiE zk{7GqiG<@5uEj17UrxMvFpKGx{@VLPHHrX6xsO@E&V=kAIQ`1wNw%RuP2xvRdZc?& zs{$k;vgr}L?ARB_r{zUWE6habCRh|v`anlo- z=zT>DTH&Wq?rWm*iBkFFUHT)iFkj}n^YCyI4H5DI;#sKj|7l|G$1))mOaJN(cDvX> zxoNBG94vqrD!&2F*v^^2f3Jct%eI)x>36Pu!l;?RbtDNo73#uqtqBFGn$$1@-_t;tLldIYNyBC`FM>7mBK7s!yg{9JkM%(4a68^rbWk=>W`!^-Qwy$c%Jr@3ckZisk z+8_Aolb4mP<3K6$sr;XI)KCh^t#GX;691Nt4c<#1=5Y|4Tb#Ao?6Z!dgyAd#=ofxm zI{sqKswysMgT-MjrTRAkUZQeY+Kaaqrs73$ zq^zh8d>NoEf~ph>X12O=hV&OQ1ZDd)17>BYitqE-{QJ2&S#oQbsuv6=)ZUZqpD|gw zQmzgqA?3S`*RX%FK;znAX5o890eS$g((=3 z^!d6;FHlFjn7;uByb0Mxz;#klnufmYe_4e%E={^7)L42a2#T_Q?s)_Dhi?H*wbJ=^ zmC|<~e2Iqi7}Fy;wwzy|&H}I0`#R#-Xv2HgtaL5J7};s@sv5=M2q7ht(RFoL3RU_u zp-@8pPyG_m-CCle!FyvVK;mzh34jK(0P0T&M-`ig#-+6c9l!$;JMD!v*$HWe% z5Uo%Ct~a;<+cY@gN1`L=WP=Z1IhHn+;>Vp{ScS8@3254XuQYVBsQ%q)^Xhrh>y&z3 z&zqrOAWQ*dE*9b-7NX9{*Ya0{PBG`VQmLNe+v*sVrkFTT7_oU)f}#N9*SLYn67;sL zM3LmO4fp+;M6~a;i#Z|sdsw)tK`W7F8Gxts|bybZ#u74#^}IneD}9aXBZE4;0J?d zsneLTfysQQ@AGCDm$0(FI?uB_ifFV>pwx(&HawznkZG6THczp;M&S+ z{((0>rS$8yS~xonyK4R1&A-i7ZqxCB-TGH8^B3n#YXNxD6m}l$#QXK&yyj1YEI-lm zi~}2dR?c%7Y($z@M(|dS#Tl~9&VCg>noJlBj9(AaxWkY&+MSk2drK(^_|l@t1M2>~ zG4)t!4e5IueGoFS`&2g&s5?h1^7ISh&)}$&cAKkT-I>$T-LEA^(dUU*AJMfem;;}; zM2k<*Ps^XaZ~1B3^G|I^U4kM-OlDnJ|6NdP@4!MMh5)r&ENqQ5xJMJD9 zSr#~8PCP{LKnjYKP6*B@!X$SQu=DdsGBh<>iSt#RGEn5*;amDab$z_TwuG28IK#*Q zilRG6nA7z?ZlkoTd2CjNhlAL;|EmXdJ;=jS^j=Ni4cTzNRkyDN z#v@t7@@~g&t2vex+1a#!B^U!k5$kJ^PaJy+Ydk?&)AYxx|6Z%KMxt>MS(dbd;I}bhg=fl+Xnp|kLHyIzq`0^=8NuD}A(AFdF z(cFP2f1Dzvr^0m|18t90NVK4AaQrj6w~wm>5-Y&I+Z65lnZ*g_{^VEeg0X6Z5|%_`>js^zZry{vCPqsPZyWfDNp zzh~k0rZR*~&gr#);gyW!SZrF^Lgvo}o2GX`7i;HjxauD@IEl%HdiN3e*CJcSd_gdY zMAWwv@dFc($ZPp>_FuWUeF+yC1|}U_DjlRpzrX6&bdk~J+vW>3*y2q&3~Nc6<@|2* zV z8U7EPiY9-_RiJ+Li)nyKpCYtiuk>pi35n*<&HkRX@q^yOnz|!J=)*AB$Q5QG;P-uU zvN}7^;+fa=ZnfT;M&PnD;pvDC`1+u_Tx>tzOBss3!|Xx~Ov|5sF5mFywCX;JFgH88 z4V)fH0@;hV0RXu{Z^D9L;;UGgl`oTwaLEKiit%h5#7aF|ql)*VaClMb)4{z|d4V*y zGS*24&kF7Bi}KxhHKpmV`$j1=f8z-9(%3C-px_&y@Utt5HH{3Vu_aErbv&}iwXIn; z`;}_t)Sm~aN{EQbX*b*@@Vs7y`xhZ`fqFNS0g0n2Kx0OHhg!KFxMYnTv~+1QKl4B> zqG127%J25x$A3R$kAE+Qr)iAzyZfk0n(OE{t=piT$QoCEY-C#8mY$*$f~5@=RrLCF z2`Wf&e(K3N2Lvr2&k)Ggj8evvtyG5TIyn3oj8xE{wa8prVl(kMU7b$}019hylEi21 zl))$lnYu8#IZuNy=I02Tc%iw_*Rc|?(7+M^wisM74a-u;rS#*qD-$p?50oGN2MLBh zmf(WkV}v{Y2@YP7COA@%km&1Es|V=tCFDO8!AqF@m^l2(aV*iIz3&Dk@Da_17p=c_ zO@wvOFq~0K_{%TLpQqTagBIk>)f%2}QB0Ek>H)6X$Gx1_2bBAO|JZ{Jw~JQAJ0`(n zT+~!wcG(}l2j!0cu(WI|&P}ioLhL1ZATvN*nn$L{X^0v=-Ht}3QNz7Lvz7@Z;?e&Z z4>^tK410G5*)wCUc@f%_yxYCA6U~D^9=LPu%UH9vIrMb$`FD@++w(y-TIHK98lsSf zIU9BnkdO%ujmDSOf7iiJi5R?EvlqU&J-htC2Os2XW%rVzHR}=yFF5a&osLR#wHXi- zNaMD&D6|R)n{RC*8}XwD6{!5vZ@Fy{ynWB*p;KFLk`RJ^!Q^}GG@8(oGV%|< zEamB30i}!o_HB=KrzG5X(2_6^Eb1|?*YET`2IU~?tapK%nNwJnF%J+;SqkHO0 ztEBfy!Ql$Rv9{93_!VCJ0y@@(&)nAaj0A5jw$K0PVW=31ugA)&VcIq9-z%-r1PUUPLso7+peN8M4GSenq-A7;fiAQ@^ zt%Og`J9@?|=I#0(RpF|`v!*jUG7hUC-+kqPtk~JJG-UHxiA>6jrhp`HfzG@;&~Pj% z%N;+tUDWa9pu~HfhE1Gfvc~QDqGUx4IVtw1tD{PmftBx5Y^|wqO^^a38I;)zO@!v) z(*I@W`DIzk?N&LkX;!oVKC#g!d7LD1+dkc>+L$Y`fX* z^JFZ+u_AoI$Ha8{8>9$!wy60anOJeRHK5hIuD-_MoG{{nO2)Rm)T)Qk_rL&sydq}Vli#A(5*YkDx{ zb^~wwOWhz`Zj8W8NP*l95BEwH^xa^=_&Xj`TG3OrO(UdPqE zw+f}g*B+v(AJ+}0>$}3TP!j5RzL&i0SIb<7PaN&*3xUO-$pHru;uGVZJu^g?#AUNm z89z00rn;NRm_F592yd}Ue4iKGI$UU8#>Jd8%R3AL@kw$~xkqkn(YPmkIB$W&D84iE z{rOjKw8`@(LFRcZOPz0?b%U6?G-}QYE-o;J&Ih*lgA20uBoi3TzN^_My>Tnxh2Th% z_bern^2S%EqwWo*Vk}_!E&mX6n3khj6~Jz0Uh{7GS=#)5 zx!{9j66c{EvuH*-pVd;Q%TdAfLr6VjyES3SYE0fIGVbyS9*+XbrQSa3)%|#8_MOq7zd;MlhC9=bc*?sfqXyE*X~ZQn zf>>++6&%0ABIWiCS>st95kh4tz;8d9vVPzVKIn^yeNH-K=ia~>TYMj#ew%^M#*rQM zwprP~8T3nEcbo5((JzXrbjqWP2LgZqJh7hOyT)VjXj0m`EHdP^xLuH!^cSpM`=|{0)f{&4_Yn_h<5%2 zdIL+Nfj1&rb4XHx@QDcGzn(Kp+r(zoU6qsl|El=P5tS+W%PbFJldW0J85bBL5_`A; zC)`1RMW>{r;81`F2Cln1ysw)?Ox7BS3W$erCs)lk^Ok9X4<>5oPkmUoJ?pS?w9?&} z8)0fN1%r@vwd%RH0Q?w+?W_mQ#a{$Y&%Yj%-b}bJxZ$gZWzDwCy>s4K`Nn%yNwXk3 z`11!!NLV;(KyZO?M0Nc9tq0DU-b_r_i>sj`@;huvl+4?p7&Dh;>~AqTX5IO3&6IoS z((|EqVc4yVNh)E7L@sXzGM)E}N@qbWF5@U+h_Z`&R# zeF=GDf)8&XmXP930Hx!QO_t&eP4h@sYu zR)8+{Y7bO*myK>G(_z#;=N@ZUn&-2g^EXqP9fR5qJ1hrVWipxGE*tw7o@6l~3-=n| zaHPuvlH|_bMIU4^f{FrZmE)NwG8OQ};Cv!*^wZj`W_R{(umgXqhw+OtF=vbVHSYQ_ zv_EGnwGQ>v&UJZn(E50T?82Q==HFdTtT;m5^C5MNgo`}M8hK|6CE|y@OFsMAV&ll< z6Om#wkYe;z&m^Fu==Z!UGl~^v9_R!4;Ts%=+BF8w$See8jnvh z26qLeSb%(|20yv=>w@Lo)WBl{OQ zgE)}Cm2~C-)$fdQRt*Ffwq{V~sN(W^6B5DkTLBo%+%fNi&Am@u!Y*N+rI$1_v%BvM z{pfwv<{lhr2(QLMKv~TnSfA@PeDOx@a-45EFHU}rL$?V%JefDZw6?G$lhb9Hg{~bv zr7ZZhUMbLYlSzceup*N}BF3thcrmLw)PkgoC*FTboo-F8iobfL?05sf0cq|v^TeJ; z?JeK72a{y;z0%^Na!A$v22c@G3uD&twFg%OOHQuMo?Q2@O(g@9sfycTEh7U>t;{Ar zuk4oc!lOAFIfp;+!yBoKh9$-)o;hwDwcx?c@xRZ-z)&9_OzEhkqMK zlY0#E%ox=9-*FSQ<7bZPG(5Lbg|wc$_1echcz<~Iic*nogD-c_?XJVs$O+7J-+fdA zR9(I{`tnPPZQF;h@fm-6x`C547Y+V0h|8+&=Wj;#x&p}+e&-3gFK^%>&K9NV6_Z6T zWtD#$I>;@&I!K&8-zT>}Hbi3wKpJm7FZu&`VW^fME((dMUb8oza2y$cnlt`Ob7N93g#rUHFLB)n1z%Ez}=Z!wvFJ82wTYWq_23g0`^!3`Q+8)4?k+ zUhL>DPZ~;5Q5I;OFd^p6qf1lHL4!U~FezupYw@c{oGJ2`gh!N#< z`fBz0`zxLPh*XW)9*xm;_s^{RlnXS~TPj62Vjh3uuZ>PC-)xw+%#QCke|MuMeu-Bi zmH74G^&_&zzrZxfe!^N%PLEX*tL)EDlJC^$U;=6C8e9Q9add2On-=0w^v_@q{=fwc`_3Y{GO~Vl6 zK_W7^)#jO56PtysjP%_?&N&ZR;H*eKPjm6)Tn^?|^4a&E8=rOmND#!LXPinp(6qDn zF*&v(;wFh5IDtP-Shz8^G|PJhYOep1b(29-TOi*=y|mGu|7T`T^s$LTecZdqxoIqnHVRxDz?@>lrE0Pt%vbIf!hgYALSX%AktQ-t)o8cZwUB zg*3?u0^w<;q+3Ht=Zk)7#Z=N}#MRZ5vLEz&%4%?o_9g>0w9Iw<54WFPdLztG-LdqY zrOmYZ&Je%KztMu| zZ72QMEBK2y@WonuUvL|;b2a*A&WL@@SVf#MZopbtSnOZo@*8A89u2*9to~(}4yqvP z789_@`u<^DYju`miv)~rVBhdMcU2=8W$(QkXmr=h-GoQfAQKp4qYCFG&dxG@xg}D1 zQPZ%<_;;dh@Y0OtA@H#U_rD>~DZ7p^f}19dCRb@?Q}==Npmf=}F|j0OeBe**oDAM# zk~e04(|tSWWQCBSG{!KQ3}3X~voveafPAeJO!W-T%oRz!RM<0KbNl+?*iA+%OY%OI zGgT&etNiGBEKh^R@`I4WNyS0hvw%~plLc!*g6&ycsK2IjfTu0%$hR~p#}a8CzVbT_ z2!)ehG%gJF7f;pPj(v&d?WvbTSvflu^S(g_?EViXiJzapZ3Ki7<6&G$?S(0Tlb>S| z&Wr;w5TIAQ)_Wp-AT))cIM>rNBnVZt6{Bp4lXKmD<@=rPld#YL)%Q6?7yOQ|)%A|$9K!44?)OSz;QYkCk zrQ$F*C#c-lrwPi$MLZC!1t%192;))Xd#4} zj>X>oMR9H8Qn~A-d=(`KPJWZg6u;m2e!#aBeQAC7Xz|J%|Ic{xW3v0X6rb^Xi(pD# z_lsI^1swl>7C?NOH0(aE$ck1Fd;3Z!x9e0izQE^zlCiq-9UcMTagW4}aeGv=XQ-$IO6S7tJ4V=ls$a=@G7sF+gatN>QcL ziN0ff#1orops_2W$HUI}Q#Qwt0q@}W6##HJh|>b?R*MplxlRg=qtF3!uE=@Zvh0!EgfG(LjL*c ztfD$kNxk5EGqX<--BJ@(Ix(eeQ)rN%dUMT!jP}AA?XHa^Gu4m1?`a<%dbK({ns{PD zJXLRcQ~b)myme{yDgPZ07dhrEivI#J zT+D~H*936wEH_uc*Ve(2I*ePPvYN9AGh1~`mozORJTMBY=_}$BH|F!_oqrB|PlLRa z=}HLC8FEMccp{`K2i;#D2&YBe7kHS19q&$xGJNWqo`>5%$$`&7Ho)P2y?M((90;*9 zf0zS`hfg^QJ~oC+8|=&B^k;Z0{3-Y)@a8+Ia&U0*4SnrP>g=dHGNM1fL|0^?IJy{< z-QR;1ctl=SY@LoCx zlCQHOIh!g=F7=>!#DwzI-;mijAC>Ok#1Pa`ioT0(L@sD@lX6)rwERNkHr-4=x~|2j zdx9+c0f6~I{T_$7y96IF_$^_1EAy45GvhLZnj9J6ZoFxZRQS1LsQ&Ysr|P?fb|m_b z_nn;j1uRdoCqG*3bI;TD+~=h$QuIB7G!=E{DT|NBr7D%v#41idt3$!G?AINU82G-O zj&38V#ZkDNjG4UNF2r|tv+Kfb4X+Xc$cTQnySqUm2GK}CMREx7@23}z+9RL;Lk9eyynkattdYRjA?*O&I z&2-t(HcB7#nE=ja%8#rtuJyL7pR_Nc=5L~0u6^rd;Zc*)9MCL|-MGFr9Uw99u;BN}(q@)zW3sOr zt)Ah^|6TIv%^~-x2#xs9(XRv^dwT2H1G*BFTFc@RlTD_Z(;62N2m|4K+*!X8Z?e9c z9OnMR^qX{CW(HxgLe0C$wj_%;T2g~3U6q=-32oEs6lMOnIQ5GOfX6=M#x~?8pqyy= zPUqIXq`$Si7<%aI?SLT?YcH^FMNTvQ` zV(d^p`qKV4-^(;%R2ZPSRX*eR^d$gtd>yyfU)W==GRiH>$Y}Z0YzyQ$<-yn~QLCf+ zLpw~nmi?0iHk3I9074MvN^KatCRAg#{gDsNDn~lhngVh%1D>gWbCv(_YR|f05jZ!r z+!f9oK(WUHVqBY1O!cq%=jfL0L{8}=+1an@4^}izOti z@qfx@e?wjLLp|WT&R^4+mmRfnQ6;$B$wWcFjn;{5pgc-FS00g1t|BD^PTA5lMFMo% z3!5_2f}bgbCMmfmo9d(&)1Rm_cb|_y_3R}A^KTCy+?-Md z$=+W{O#XdG#AOuyNp4AKce#X{;xzm`{hONZ+7ao5CXF*jzK@i$`(*;M?RTibW$&kfjAFrG=|h5!33-=u3I3x7LO>z^gpZa_?Ov+1 zB-ZZx8oLKH4t<(y$UdbnRL+k-<7nhhI7O5QUfVq9i_(2;3XsTMu6xppzgp;$yYf0p zDVq4&$z~iZip^KM{0(9xl9$=$HA;*U`fZ~2{wej_&dIp_ih1tAPf89*rVv&G@rkuV!_gh73N$o_V7OMzi7tg zj(x@__mBnjme|Ez`%+%a=}&*d;nzQG?*flWC8rJpo{r=9^Xnwbs5|2_Csav~uy>xb z-Q2R3A-TCs)XV2^K<@(@d+gE6VaW!f8>1(AZYzcoudfU6kf+>qS&TX@NwQf5k3{c> zam40E%s82HZp32}6NAkiYCt){S84t;sct8l z-;(9uVmvw$do%mKq$&tHY>oL4$^v6hyD^HIX=oU38dcpJPHscwySCqfn6a-1 zxp`N>1NAn5Qa&n$=k2uj$>>g$21-+S24l*n>*9S0v2vbhILWD)(8_OoPnXrdo{^aL zDd^`vT0H4i>KsUKyo^IDzIMzv-^>gBx#?Ffc*GPzHl=?MUN*QTaSh^>kD=j#fi1C& z755#uyY5a4XX7*#VG%Lrc@&j*{1|ifj>tGDqSMtEeZ?Js!x*Hib9L(ntx^-Qm}7Ca zJ)LmDi&alZoSO;#>QJSg`-kLC=?|~qj<4tmi9!PsUrB!byGJ3PwdGWLzJYO19dKz} zIHP$QRM#;+ru`o=B2(*e+yeu+n=icH9=PCXc6tS`+p9Dz&*>|3@v>TDB??%k zeE_?ZA-wyN8i(9i=U<}p158MWNMVW5;_QlkE)%sE_ai^0%IcI*vof6?g8#83uh?9+o3(=Qw2ACR(WxI=JnoBP14Dr)ITg0#uY3s$44%7CC?KEw0gKZ!#V&{gJfP+zBADkq$md|E}?_HyteZem50sXFEIWfZyTESGYlV3KT`-JO$0zS|1fV47wOT$fg99y}i z3fjaU4JJO@urzD|;wz0$3**)4%$wR>e3K-5K7tyZ?*kR@n&ehr5O|Zx+Wz955J>mT zw{2eO;>3c{nZnH*-E$s)CmASu=(6`}s*`BQKMjF%luEtX8cqn8#2?bx z!)uH~n^?Be{0<)ZkJ`o4_pf{VXLrdhGB#Bx)?)VBVKl+a_XdkHUY1XbW=5Nfkm#ga7Ll} zkwchz?#STP)gbYzqicY#O~Kw7RrvJ5q70I~q3VxA7zxSDdDQ!tWUT()&Ys$Lec0uH zefteZ@c4R+#DhNH#{K8Nm;*pca&PTkWNYQs%=$?YZF&iYLSP$5TDlMVzSRo4cFg5P*0GdPBJf zLHL#@HGA=szr+uh3Ki4;_U%G;(9;G8J&EwIzy@A4paa~u+XP?$BaD-MW1$&T@h$T_ z_XcLw$BL_+xJV0zhnP8Mg)kKp;@UsbA7zrFz8x_58v6pY?kL*B3r`!)%e5 zNd4)6@QxC{4r)j93bsQ&=I-mV)Gne@CBu2oQ%t$Cp3>{mUOQheKIb7hEWRjj2$Yih zP%DYm7$ML3HmBgpfXa@E9xseW;e7kVl{0dN5rml$gvdFAktCDTj2}8yH0(8hHzID0 zyAfg?&$-NOb>Wi0ur+qN<`QQXUac4pFg8qo`z2MIZk4_LkWgG!rHr>4FE^1`A0-vL zp^7hid&7T?n6pe?i`KY)x9hfhd7hm}7!X9B(7az5@ampofcS+W%|=b|boZzR%jI0A zZ#B7wXAbsQEGUZ+oj%z*BkI$s1(0XQc00U z#;)WZ)|xJ9nQzebe_rXgl`?Bc*Ml1E(tbWI_ajxmdS=Uj5>Gi7-&U#3EspBxntEu~ zckC#@9xQ7bJXxGUI6XWhH98_RFqlKo(57X7(>__BB3un{R$hY7$*0%_V%y=jPNzaqpkkKLHCq&EU8sv*ZosRY4~QujLsp|!&%+U;mw&g zQk{M9=q1LShx1L?Ab5cv56|ZK261T>c6{5rxeq=6%XLg<*zoOyv51DBFX}&tI*P_i zhBDiJ?fmI9^zBw-+~tdLcw&8h0$Fy^U$D@ZU;$tqhD8{!$fGz80U(UVt`!&W7991l zg&v8fs1vL3f*-iDNCT?$KVAn5Sp`BQ<4fk~rZm*gDT1!jX9OSwEyW5o*-%CX4ZdJI^Es`K z-6z(F#;yEUa;184z*`2%Mg>fKi;T=jrqlhyTj+|-@LD_HByyKctBNlZbJ%U85A@>W zm|1nT{+A{|GXY$5Z>V-r#K$DoCtY#8^ER->QM!UMsiZ?JP5GbEYn)WD_f&tZV^1K&j5g?#YBpP=z>;=_x|F3lACB1=77UPZ! zv+Fs0D2iDIDD=GkbwpJOKJV+jE2B4%%D~EJkLpf{!)H}Go0!B z)3tcyemxm|UaAn!ZC8t~$&U8w26Mz^Kfv{!uL(6G9*C)7D7_TY7Wt_et(UZ1K`}$t zKYhR&$0Wa|XI!anoE0Y?J%g5M06-`YE9M7rXGZuD@>qPakB|~+$<+IV&Cuu1A6e?~ zS2;U0E%9KiorrSxd?<*5@3)np?F;c0BZLxyzn=vW!4{uE?pESvg2%Pr>C+NiC~RuS0UK)vfoo> zRl?&KH(8yr!8i7{Tj*mIVJ;9YznaCq-MQk6>J)7}%rYcf@AlP?K#?-KntSxuFT|Y- zZlor!^jnVV*q#NQF6Hbzd=-&w%CUA)us65*FyQO9x z1o*^qD2(4F5c%BciKsTc`{boL^1K!0zo87lGyh6+MZcNREa_=Zh!UnOYCEU7i?UkX)TC%grN&`UEMP`RG1k|h$?cQHV7gYkiX@#%Mx?LRbjtv|5wKeOnTr>D1qXa!0I?M_2 zCZ$QEuBW#R-4!;d`w!V0CF}k8(3qc~2yc{Im&@mS;%vH<+Cu0Zh8ak8#sgOyK?VtR zLgWk4L&-~zYL0)S1E7%U9u4mE%GmqN8-uk~oDtf3>Y?G5jER(N+d z*{cB}j@fEzOttqAPjC<1Of(`C>I`N<2SY)>L|xuL8{nN+HtUxmvI-9F`O2-2WtI7r zOTYN_+d|vuHO7(ntbn8R!<_&h)yXzC=|h8yGLw$!@%i`w#I&}@$I6N$@ntbD4wc|` zKg-;qJ}EI~fEi6vaJ27i>670+cFS+in-vtv zB8XrQeJEg4g7=iD+j<=O3`f|umuFsv6|w0q69^kaIb5I8_041N2*DBwG-czpHgj6p z{?22*!jljCRFP5pr*jLs5I8vaQP0TWg1H&&_?ts$qG9{O$7?HBpGY|o0N8dU6P?TN zeS_SU@3M#gmB3YZF1C2B9_3m()yVQZ|C^^j%63Mm`32#Bv2NenSlo6OjKj$~e$5F{ ze#Z8zg^m*u_%jo5B?0K@p;i4MrVcj9=Ra-O@~L3TuVBnY@1XQ`KRQh+$s{ZB=PQsA z`0}DbH^ms5DV^K#X2;U}U$r30PPyPVmm_0LX+^BZR{eSwu`l-GH*JTJ^9M~eSoh7> zt;{B_QbB%y(nMUB?~`yD(o}ciZd$B@bRIg=2LcZ!S`vx{jzlA|^3)Ag$Tt#fI+WoS z5&5wi#FT}yMDH22C+PqVC0)p_Ll~&87E_voMInL{?_Pl$iA^5md>FEees~BscKdx= z&ezg%UA!T!$shEI1Si8y@Rm!NY`*?=Xw3Hmz$ReF9Bo^ac2 z$A<&a8Vr|jM-(!VC5H8G3N7d>6$`=OjjxnuQm#W4!CVKcE#-HAIqq9y0!>GUr8?}@ z7urIv>TMF`Pa6Z2AE0@D{f9fMgHlUmCmk66K|$KMws>D#8AQ@zf@D=+Mnd@zEM16J z#fEI(4`;kWIflEQfTn5y#MKa2siFfhwi-fDOr{hgTxLSsSqjv&#fOP3x8op>`UVO3 zf)90t?r3TLEj|iU{*M0FF!T@q_MfkjUA_D0z+cF*H?yv1k|N{csg$G>Jc|ZZ@+Bca z;)r60V(A?oT<0K((`&%a3xCN4PYwa%B1He4b<6=n?})X$T1lQr&47i;a}^mMo*w34 zv6IRrV+EIxW%=?6ft}ht1}^c=P0PWz9|!(`<{iiiQ*`9(9B%CP63~88l?0>cS7sXR!Blju#8QtifOXA1VxPz705jx@{{(*HW zm+<_yp?ltYXE-c9{x*vY7KtYVXazTWQJ4E06`iz9 z9_{W7EMF%dFWXcP!!CwB`}p@go>m8lixfy+z#|x9WL$C3t4Mowwm(Ie?Nyo~IEEggVr}EPL)#e~$sT zsi9pAoRwiiRFolOt%X4o4Ke^+6OFy}OW3x^Sv9C5J}MkR0G3thkqtKb$%Fu{Gc97u zNC#j4)LrVjRUnqqlKW~;Tvg{WeRSnkxpVV#cP)wH4fiwujs1DtKAb9_vkzvTbWE6< z;san2mWG3}$H91%=MwL}fzI8u+XiaR`gyO`lGtQ84G|KUDguO0N#GutQ259l)z+vc zNPz=ZPGZyoX#1@s2F3b5t8?WSXJBVBIKayZ+QEIuX*3Ad%;iMJEsWQ_AInsK{Y$qb zctdp~>tMK$Y7Ea`zp&>6-(d5e6ZBgUDHSVOCufQ*6xs&K1Zi)@C%Rf@E;loC28`bu!`B*xj26OTr;2) zzb>OvbS@`tjLyIUu-L(#76LweWA2)ZxQc%1ME4+pk7)+?6E}`02iuEa0NzWWXHCGk z{%=))vZ{nQM3&VJElY6dHO{;Xy1hpMYm)fqa_>v^jv}hK?;^>9(FBkRB{sFyh zKLxL&-)ZXK1UO;?xHJy=ajs*@ht>PiAGnkY|{n@ zS0j?rT zT}~TZ{pPZXQzl=zQyrFJnMa33(gh@~%Pq#noZsJk7CDJc?I>Bszv%kvEny~0@Gm3) z{h7v^x-iAr7N`)BibI}ghn%le1u+--3Ot)Oe9SEM`1hJdo%=a~o5IQ#`=!MXYeVJ< z#KAY*uTTj;#I{|5RXovZ9)N`G_5*LgH}N7Zg+#-wZe!>ml_?3AX{8dzCAF!#=9u zEJ79gs?`yYx+_yC^RKVO^uuu|4oKApINPKwXQ@cuy$BWmSC_@tewG36$4N4ZV4Z<5 zeJY#7V(#xA6lNQ3+PDh4?=o;Ew*M>c-@EKO@C63TscvaCHGfML%7JI%JP45sU3Oa$ zieDBUM9BS*oF3B|-%_mvOH2yGH_>0K%C05^8%PTYhxyiR5PdT7upq&fI4dHr{`JdT znckAIBUl5SBIzuLEj;NP=4j3_Qh()gB6z#GhR8YE!ZBaex9^PdJVs3Y=*q%%ktXKY zYlX#HsWUzdA`YT&!4G|~uKB4sIUpTEm7bp9rS=M|7u^3(qc;(H?)DyVip8!*^fUHh>MwOm=qin`DU4tC#?qO`rUU_&Bx!Ny4x#T z)*8$#U!96ip!r3Di(gEqf5urTL6z@}YM8A3xPKYmLZeT_{_A^r`mq{L%zx^J{({Oc zYm+ywS_8h`d#$J^hUz;3Som&3s+)o}Nf=sZse%vS=VZqu2vEb>3Jwr?c{mm=Ek}L% z0g~1207<$YCs#slDB&7KexNTQ(-ASStz-OY^rQa@g}FTJyL>r+hz9nSG#;~mrFT(o zSWDUktTWZMNSW)(@{wZo$ijR2TFN(nKRLHC$qVI~p<(p$5zsJ7_{P742nYDa7ZJXW zsu1b>KcAy4%uNT0KCgH&qT^HzVeb#9iN+VPc%*`>?mYY~e(f#tw~&T-ajE+-xB9ZK ziXsW)aT?q91TEMF`m}fuvn>}Qn#2O|H?)7YAg|R`T%qP$N!6{D0%ql;Z(=(q^xlMH zUfvPyjy|zm-;iaDbxPnr*k!)J{(mh1^&@~Wc1Q~Tw+nZ({HqjcK;t^A;YtOwUl zB8Ub1 z5hFa2yOaFZ&z-VF9aSFL)_uG$M#mnUYRKXr*RR|Mjk5 zs+p-N^I^z(+`sUFmqTz}GcD+IVKB52zNzobm5R&z+*DtiBh5$4(X&x<)Nt`*sPjXU zDIw6F&eM^dgpnroOAs25hxWQsis>w6TUQ9wr3zEwAXF_Pc+WxDE31pW%lS&3<%xc@ zK3>2cCLsfsRa!`E3q{>ERlau&d?sJkpT`hvhgzC}{19=Pw&H}TpOz>w>NCYd zJ3z6wt_i|h+1*axY~cS`z?eMkUZCKYIVZh0wV{geeh{E3;Zw~h#YT7 z$mdiE&%RkprvuOUBcA@IPnt}Y7(7cEEb2KK&Li`k6p;tTEL=QaJL#UYsvNv?h zr6FvejhDB}dGq|z{S@(hv9sy;ELh6~lIq+}mjcw@EJH0gD@2y))RbSxO(wPRUc^2T zPJW8nN!cZvxU zx$K#N9`)| znL8dW*%*MC97vjuK8*5`Lm+1{0$P#57l(@(C7({mGvhD&BP!p7W4-6Uw-cPNsMOK| zF@hfdQeFR@Hc_BfMSw_Y877!lBB6^*GhsLzr4jdKqS(P!hAUOy4Ig!>nM%f@`EE5@0l`fSCNuz=cs z7C%JhXY4(w`QzU*`3fo@wVpSr(=?JH`tV+W`1(YFcGp!g%(20p3m+du`T!wG7ron} z=g=0UH6if54ClzSi8Rs6JExD(+U;pgQ=h;$IMybO4{vNck$hspVRx)lFj4hdk?SSH z)MGzWf4t`(KVc6_$OiW+J8yfhL(Rtbh!K~vpj--~o>gM(A0fhDPxHb<=X4m2hp~uoWvV@SAC2@*VjEm9zhr zuDCA9y)DgsStTl7Mczk&4W!Yr5lW{nSvwRI&4^f;BTtakOya(hnzskwLRJtaA@=i4 zCUxAjUFjARrE-e*Kk1Ds5L#;*NpLQxN-sE0{fpU;x7DWXpIBCJ_i65@Y}&()S|9Pk zlG$B?iOAKqAWC5%eV2d`E}Xms=!;)+kUDPtUwr*Kl%ehgRKJuZtT`{0`wwof>q{L& zL|^#K`0(O`=*ArV+?Exd&HQLvj{jYGtD3wMKs3Dq(u>4kD~=#^-3n+nmL%a|saQ0b zS`Fb*-ynez{IpTem7@7~b%UG@W&(=25Y578*x%}AhkPf>Yd)v<`yBW57>i)380uHGe+lR+ z;bUdNP2z*s`+}pZqwOc0PaMUIZ*Ud@`oyNC++RR89`;`qxT@VufRO}VOEwcW1^kx3 z0}rG$2+W6!LTcLpCSpCJc)LC(!mXQtF9hciOCguH(E@)z2i7lrv@e_-jNUO11G(YL zs~09W`dff>F1wk8{#r8jrtTdGFE1>-ZXL&nhT@Tc=rQiCP5ORT^pX5j%PI%cN{Q7y zC;yNX>^Vj-t}+Jb|Le*5jB==;CyQcC5_C3~>91!eA*deYkgqP+;Ube;5D|+hAjVDp zoPX$}i+U52+d~HRE{IwmV^bYKpQ^sEy{EcSY9wEf&pc@wdSJ+|6^K6Q83e?I#O;4As`nkuNWE@y7&N94#o)zQ(S zctTGKm6w?X&1r6IAgjP_Qe8Q3R_wPZ#kW0dtgQY-X14*$Ug8Ag=D!{cAHFX6UScW# zHz{W8QIKNDRAtCX{{@Pp^waCh8`1cEDQf*!c_|1TthYbpFp|iRwL%Ue>9vkJ`Bu`X z%h!9(VY?}eVabiY7I?V3$M#ax08)jfrC zasS92snTsPq%;lPqSB+ywFY~K=0aM87~vsE7J7Qn2w}fK0{y!{KBfI6wW?s4rIeEb z-~vVi!N7?W;I()o<-hbWI5Uz3;)16D9id=V`1o|!cyHAfei19oW1QWa^09$WP%;i* z;so}jdaqhyt(7;}wIP&qH8E~DbH}kXptmK>HU%;MuD<9?25mw_dZUfpL#s zaB!g?oUF2(t;B7r9V8~2h!_`RhVE=T84#3p`cK@b)6mFJ@LeeTx?XXF_iSeJTsG{r zS`m8Xn6_Nse_();%f=Ig#JksD7C*C)4^*#^tPfxT@b9HC&zZwwGb;lE|4sBia#(P= z95j&YRR3D)ac)bL!gyMP(bLOK?a!~^P#7argBf1xxwR3x{P^2YM>SE4TaE${(13&} zzhB9cLx<;qc$E=?5%eqZiAXWXHjZZ5j%`86e+r~O>v#Em*0*RD8tV{D7Tw;b(UC6h z)>wNEdCyn|*ab0G`bTK<_Sq;Mu%&-l6%N`nzHzc{D*zQHTtP2}EQzYI)`t8vt@Y~~ z*bW{7j>SR{Ea<9ImT}*S3UNNQK2stl+Nnn&m41d8h?1dKH?C!8oRblL&dS=YIbpksq?|RDLDQKYJ?(m>zwKq z)I_a67a>ak0yrsf$Az%A z(oDW-5JHq3n~Kz9ZhaT?k@gWG&i|g{jHMMAiE@T$S57OUnVd55Fk*Q=jb3 zsLqdq()a31?`2k9Pzd=j?CGtO_DJhyHDLj*UW=0AlglE*7H`$t`C}vF@9{wIUr=<2 z{7^Y1nM*v>63Go%>px4NWbwg)uqFhCX z_5~vZV{pqe5_H7-_J{SeI-E)Cd`a2V7b2r~KEO=i%z2U>U-uQ3=A{t>rtaH73IU6gX_HcHIn5HT+5Tc|FbJC5`Q|>UUk)clp`ssTVieY1uUa z48Yfa`A_qJS61oAXW^}&l9{v{uN{lr7J%~2g2hf%k4E!r|M(NM_MD@XXDR09Xh<{s~)gG zzQIy^(?#fCmmdG7Mp;gmbcZEhkL(Q2vRUD%OS+)&mR0HreT<`(!=VX|E5tjEZ3R}o z{bA^Vf%Iw*@&Y0RnC6C53c^2*Wg(eC&fG-e3CFG&JT}N@-5PZ<1Jy6Vvz;l}+b45_ zycU;h;(DeA2+AP(pBit3M@JdrbA3WDrVJEXS^>9yXKJ%6m&FicU1PTwhUnhIfy3wq zgpD!5eI@d)SyCcS@Q0a!rJJh3Bgc=-ES3vWHSQ;)9Uqvb39EI= zdozZMoALset|a@{bDkrTua2%d-5=J5d(~Q-uEoRW5}q0bS(y7kwkt?J7DFSDts3JkwFmV`YTJ3d$JOz7YA0&hs59@b0*q2gJmg4&l>21jCA zpAy(tSJvG2eF;_CL}Mnp)36JAKqQW85aZVLdkH~?q!SxDuvPbEB-FDRK|nt%B}Hkf zOri9yc_+7vjTMcEBd_1mGLuNDdnWU4?bgV>0AaOSNvLQXVL$&kv)N%qDpplTn-1uX z5bXUVzS*neIWhFzn_bSi9vgjpvEHe7%j}fX61~IJeMgB!p&AL*RMo|lRl?dwlY?dv z#P8Vm5I|X(>7Qn=P`(xdw@|Y)YsUy-X_ghdcMl}_e*ZXN?mI*&YlZ`JXRyJsj0V|B z0(}%%42ldmNWGY;zuZPyZE<-TvLC(&C!DyD3gBb8U^Ah;Ke9?y_XPe7P2Fz6ru%>8v7~*l+>4;%J_JAC8Es~jY`S5va+u*;s1C%~>&dv5t5{%>I zPx|xg>vvz=jS-k=kNcYy>iCBlMU(JIW-#O4rnbpbC$Gu|hV6LGI_wpri`Pj+uv#!d z7J+S*YB7?|*%6~>WdSi4@dm6oqQ9_(#kmy6X%63C0=J0gRY)dR@c4 zJ_xt46b78wxIC#BU~a|WRva>Id1>q7I<8}?tOeMzje{-b|btb zF@3RFZ7d>=6^s7iX_Uki88o8X_+SKl^e`}#0b)aKj&vx z5c{KLg~|{{%;5uR*w{F4ZC?ofJW2`q&x!pVrqnyX$!KU8w9A!r(*Pn@P_QDCm&g3Z zf(;@SDwGl@$Usnw^-D#TGezE{>G+hBdGZ%#X%s@Q%pg@y1s)Wcpk((xo}NL5S%W+L z8W~FL8Lu9a_zrzvWc%yMRM9*=!vudJpfu-0M5Ni5IjGxyb2a6Z-N70bf8Z(pXQH%c0tcD!l^TFuL&nVTMI$%* z&fJ`fwICG8u(>b%+LG*O!5BF4)C{@|Xf`;qp^HR-V7K%0Jk>>DrkQ;{&U9LZ5Bw@~ z#%K=tL@5m`68JR9@U-sVvX^bEM@yTynZ4^964X4aSNZP?`}qrohq|kVyJ8}bcCShj zxmHv#CKQCBTp9PybkMpyv?g_1D<)Uz@&nO-R#d7Yfo>ODv-%8BVp-(X<69DtVMoV5 z{zc;Xk7z3*do(@cS4$Bm$N%sj)|c1JPgZ}N(LVcW69Mis@w=nsG0I$yHeC4n%xM_1 zxd<82j-hjY>fOgzx1OUsAz5^z zE$-*&d(Kbut%WazA8zp9=+h%aj`RcAYwbXA!IvIRfzLFtr)>W&!6)l5#A zNDba$yU$Va4!57Spgu*diN;x#j7P7DlDSa#za<%njm2HQ+vY$RS-~x-jE>3$K}{GV0JI%6ob>0wYHHIE^xo z5Em$eBK$QACM1Q*j2adOoet|PvLoq%LhxZm>{We6bB~Z1Y^ZD!>_g=!MWaS+cp3vS z+{diXqpjIa(dq9=v1z+O(fs&)^?iEcRr@*_|F>9D_bJ^f=k+p8jFgvUa=2P{LaF%9 zVzQF|nJgCH#iU05eBrE?;hu{%&kxFwfzy+KS03=yzL7YmGCAXfv41+5GZY!K_o%%} z)mT2~D=biw$Et32Ke<8WiQmG#E1SxG_yNn9J+f=J2Sqf^`<+8y+|n(D&*+rm5X4aBuq-*( z2t9%>ILK50u}2AZ+>}NZxi%n4CeLI`A{ZpRkKrK&PQ@#62!n*`E&F< zST}hBS#7aP{F3@f@Y(6xmT(76I8fi;UO3v*NtAI1CT1z$=5K; zh9)#whNlq^#!UVkj9&ki4*D-4!&Kaln2K9_nYYa!K?jDb*mGr})Q5Yi23@EP!347O zaTb50rO(*!cY&bN|8|p8)x=RFjUV z*_)K?rqvcc;?T&f;7DrFy2>L)^CsQ8FdH(q7JuUUT0)~>!;Smljca@j+=0q@5iqv8 zgG`!GWbOQS@dgGYVUJn8WcSM5{@ZBjTyXzytF5?swB2+lczWL!$RW z12P%$7Gntfm+sT>Wt$S0>RwxlWh%RK#8oz>VjZ=C5L_0NsZ@9FcX6<%XR@e;NaZ|X z3r~22GNxSb{owV#=qv9)mCM2yielyu>Ir0f3?4rZ4-Zp>syK%)Zd>Q2xXF+9J)w^@wWRicp(xVs_4h6+v2XYV-X`DXL3{|$crea zg|%zUr9(V|Q4U_mwt{0vHF}0&VhA8NK@=8b3quiDIaAX6L&-^vpS>6)eote@rX+B??E|6Q}L;m8=?TI_jm#J72iOh#o;#as8Ay)K8f4J0FwVzlo)kPTzCl&;{OfsGYV*b z3zoW0vGKHg%cZN~5R)e{0pMfn+zJxVW<2XlSp777N;O7B^c2LLW*X~FIzS|LoVs8f z49fJ54n`RP>$9e|m7|p^9D+qbHM0v9=c8GN+Tw@@TUhOZf^_KXJvOOZ+x4f!1(St@ z7jwKy6GC0$fxT?={h`qRE zpn3B+@JPbB0;=fIMn$j*Eix@zFn2D2vQ;?MOrk{%Hg^x0QD=vh_0@dE!ep1@NRt2) z=4<`TmwFkI@E@T>;Cqru%}(o+fu4tAOkw4$rVpVMwPcEGT$+;JQ#S2D+j1a?fskCJ0SdovI8Y5y9Vf~EN* zyiD4_fe+b(q~EQS9kUZrL&20);Lc5QiQ#x~#|PnToM#iKJxVlm-uKLQavsT?+Vefr zwNNPG%eOItFyOS8w`FnPTC!O-;PW~ z`3dtNbBF+-u>H2i*!imM;5sR2k8RMRxp1ST;X0`j~C5CA?{b;+`F(z?zY=tO4W$(o&L1hPEYhmTGx{0gf)`Ku@?rR_#EMJR+|ghk zss2>w&7QpRdfYdy9DNGDib%56*e)^iChem2^mP`3bg@RZ7AIo_$y9PCvPxYkdLiPzK{Hw6wK3jB7_0zuvX(u;><&@U>GBxnOOmOtt+n zxvMv~zmh$87Eie$%RZZXd2eT2+vI*ph4S3l$|NsLX8iO`PxmfbG%j2nm=Rk!^0~*~ z#|NeHtvDtl&E|j6XWjl=Mv0g=orJbk{3JlX{+(39*Ven;D`H6Fe>K#p1;MPMR3Ay# z6-0V`mR_nG0VGy$%rCf)t)%8?#>$9tsC^i@yy)f0=d;W6*` zE+xN?{CIrZigmAX3yt(t zQLWFue!RiIv-&X-^K_G#k3$iw!Ry4LKJE)GdzRv0$fKutm)D3<=h~m*pT71uB785j zw4-H=t!!I6Z0oiTjl5TSk_xH&^pMWrMP)XDM?SV+n!^IYL%}8^Y~hO73_dP5Y80;qSTVR;A0!ceR4y?87+2v$ulCv)LFNj z)zdBHEzaa0vg^nCt-Uc;AMc%8@Q6{?BvFUqD<#I)YB5jc3HtT4j6so{rjt3r+#=Dq zqGT76SM@1h;_lWxTeuJZp3#XO;qUW;a1ya+$j1DJ@ukn1)F-XmQKoz&Nx4LSs^0a- ztqdr}Ih3LUc{II^-VU_nmBR&-v*NO&r;qxl(w~9E*~bGyK;Vx*Bj4WH(C94$t2SvJ zH!F>{cgA@boA|WtvFr5ZNLFs|Q-i%7vWUO6X!NOHLg!~Vxw>z(TNMEF>W_03aHrwD$~^1#p$ z{{IDy4RZ2knIyv1*+rbcGKZD*W?%cdNT~wyf(25| zwa;Ij!})7-5Q4S_zkc?Gkd`(sf9+Gz()HU?$~X)B?RGmhu|N6)^|py&zq~(iFbJSm zD6lJ_&srXNC7A;9Ag9K{32CzAExeOM&+NAq z_`?D=vGmS=S-bM`s|NgD%rCbI{+%FzeFflwAON6?1a<=GTP^374?pxHPuGq<`SUC& zbbW6i%c|uf9=U%CCuhd{N))Dzh_JA-f%(NX%q^~AVR>U9bucIa0LZKb=oJR2eR5)d zB!Ta90OuTIjVi`R>o_t}N26ZW_niMM(%v2bR@R%ibo~yl-C7z5_yAErYcH&w{(t|S zh+?PqrAXKD$8juE;O_NFC!NHH;0{^xz}i=D&NCnjxsse`e=baQXTImezWEe6vp?(Z|GM=f=yw z7B8K@ES1Hckx9GWpnjL-+sC zPluJ!kFl)+UC)MUWgMR##qsH}Ejt=*$ApwXtmTdiX1X`=toHK(Z>0X*!Wu5$T)^Donl8`r z?#KatfS9)y{&?f!pZvaV)RRc`rNQ57sgb?VAFy|V2o4nZfdfhaK+l}Oc7cCa1fYMc zdi2TrhfaL-Z-&n;uS7+_cs*dxm+-Ho*PN!pIeeLySj@KUq{DX!I2NL|i z0Ve>($VeQV;FEuj16IGyn%_aq1?er z^Q;$u(<;Cx0Knx365iZDMq)Xe2r{q7vz7HG=9ktnzqpFU)y>q`e)|KxDc3Q6i+5IE z+`RnUXJmVI!+3lE(jx&O)Oh{I>!&mPJN4^H82iz|XAnE^1OQkP*h>P)J72ooUuHx1 zf9%QX@;(i-L$*Rim?j>zjJnbq+}%~MYm00MC?uE#5vo{6r1`(-I3VZhE9lR^mcfG>^Y z*V%o)MIT{r2w-12_Y7+H1OXV!2?_3o1hyiUOs~JiJS#S4Mu+bIp}$gWOn-uh z?_t0(JXFE(P!+@VGOFbwD&-=os;+gRIdW3$yxTzggnpY*yM(1QoO$)eSl+Sk7EdD&cC@?AV*jEvm(LC+W? zuMdrO9?O{o;7=Q274VSr*7pjZqM1ObX6S7n6&T-Ru(NdRzaf`}BbTODjR+iJeq z!FscWW-A)FDRY+W_sdR^K zX0-=^IXO!v|J3&b4Eo8ItL;;sP03z>^G-poVPiPF3I)V-)h}@gFxDp#>;~v}4E){?{xSF{Uqs z2a*wyPe$Iu13tQez0)MnivaHFtp1?(o+1F>bd}p6VDFa%_8a_}BmgKP z@I1KzII_sV1G6djpIsODfdsIv_c!47mL&j0QACU}ilWE|eJ{Z0m2DUN>FK*x=J&Gq zECCqvRKZp%;2?m%FX``1k+qx!{48S7&nNwYWP(3U3P!$bkehNJ@V0D=-`$cxFYhk_ z(0dX1#@_P;;9E|Ju%F=H%Hwb4^?MQl;J{ue6w>RcP)PK0-ZB)3q9{QE#PC9ZpZzD#Q*(}LDoyLWgei%^ltM3hmwAm?2rKV$UIW$R}o~*r_0ji>9V}# zL2QsbfSKPe2_&iq4*|dXc1QsGY~Bb#+?)0j_wSbXH?~tMAYGP-;SWK+zjjCf2iQDv z=#>!kr|J2uQ51Qg15;2I8X7wnJ#-bI!(-e5nq0%^?-H#@ttv%B+tXYRen4?8rm*WHdzIx!!!iK_k+L2(X853YoZO1Iu zJ8axI@~LnC=y&_xGk`r`dMxW^bHC0H49K(dH9}z#Xp?rVn6Gxx%3^3J|KocP{Qj2r z1VH=AUv1ekS~xXZB@hV$ECS58$Nw(0=+9q@j=qiUS^RoGHD0W_t0CqfgzlZgOM21-E|f>21kO z_NivQ$H+SUhvN7u`L)h)Q~zC)Ha z5-;XGnRxa1H~>mYeAg!JCy1h$PB$cBDH@dp&QFZ9RBh3XG-HFQ{h$5*k-Og&Kqpp* z@`-5MTN5(`-H?2r&z7-%wvG)jQpixqr?G7t*Rk;(8^_R;rzSXeVVP{wkxAeA&F$ZP zv3NC&E7rh{y-)7(MDXW)+Tq{hGYk);8Oos678ij^=5-w zpvjKjz{bHO$KEQvTyM7T_{S4pSX1bV4s@IKCpQmgdE?X!g*e@L@Z2_u8r^7_>P5}FtH2+!${x_Z6e*5!U}6FH#%5`BA0Rwe&V4Q z*4&CU0lHy(e{nFwxeFE2Nr$wbpc{tl+_9NhT9=61xQ>NuTO@1)+frDjh1EAi#EoTAqH)^!2I?jIVKA)U%{1Ar)sQBwA*9a25(J;%baO>EP^G8BfA zC@GObAOImHXwVvJ%?_@uAdYvuQ}42qaD&m>V5p=BqX>Xj5Yp*J2%!nuZPEjIVyy|I z7%2sDtm%XiQVK-RU=3(Av0jZ%7^8%OILfT+KqR9@DFt!we5f@7s`WOeGMJuOAn6#m zj)iR*m`e48SCzE}tu=AH8c`e*MKNI*A&Jp(m|hoP?k_)Sv^!xdyo$~CfShjh}#cTALNpyLYNFeZ#OTQ`rUa3>1cMFXU2`%2lRj7io7w04!6HuuMG1 z#YW$9V`G{I?M|1ug%!#x%^<9u`{TLS4j(R;%k#hzPy*((*3r5E zJqWW0kpWUbN+~P7>#nbVl0@z!jX)O?wyn}BS0QEG4cgUit2WoJOdKtpJocWRiIv7qOv<;CDHT zd(ZiC&-plvh;UT-Qch9C=xZmiZ<*Ob4-?{(4>6uxi_7|wsoz`&rZC)~zhchSriivRpDC8b_Efo%lj z0RRYu1Gg@xm6`YXQZ6wDJlR-Xl?C-$$7#W8^heRNdvJpONli+>IRwP`^0uB-ra!jb zJ!xmXiMix?XfU9`!1iVpkxYn~lsNNc@7?Yt<9bnu^7xV$I+2hh*89N0zP@2j@wu?vxV|#m)1(RPobUh+1o~iR3pN1!0sn&H^`hXPu>b%707*qoM6N<$f~$@n9RL6T diff --git a/deluge/pixmaps/inactive16.png b/deluge/pixmaps/inactive16.png deleted file mode 100644 index 10342be186f9e3f4a2b700a54e6d5346e7b3143a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmV-S0<-;zP)Pg-{@ty9Tdk*Ko zy_PWshY&(AO6kWu&!0q5l%WJtCKzLHJ&A}=60vkIlYkJy6*e^e*aet09lsNAPBw)A+DS= z#BuzT5YpN0cIV=YqClSKb>H_topF^`t929vK`+ZPr`#6+wAOGO=jM1ko`qp}YEw$@ zYpuP}XoNIPkt7MEl<4>SNYfO<;Sf?vFveaBAqa6@_u+cIM!jA~v)ROCGJ#SGjYb2< z$HyRqz!(Fq^>xQ_9@|Ax+$taMUyxG5b=?;>fc2$n0LB;?V{BsE_AdaK&*w;z1dGK2 zVHjdMoq|$|*=&Ydt%gda0%OciYcLpmUaeN$%WC<)52e&QTT1zcbFTjFKfmkU_1a=zY#Rt#5{x9e7>o(Bb#QQC zGDxFF!(^kziLkgB{sZ@_QKMke&B4VO{{f8z2T2455lUMuExlf^z4q?sV5nG4^t(MT zpFGd=%_F3g99AZWKcus0In3s>SoBHlPsD;eNDMH&XCZ-^{+lkB)41f*g(n|n;=vn>BH;b@3wR{Li&fRs?GqYp=UmB|tL z4Y>Z8n*oifY7?A#0XFiqiYv6cU^}GZA(jpR-{;|l_)vs5JNsy@{cV!oI_J|<=w6Lt zp+MI^NG! yJY;kz!n~?*!SzT0M%UwA+2pyDyv}E70{j42#Jhng#r9qR0000 c #8794A9", -", c #8592AA", -"' c #919FB5", -") c #A5B0C3", -"! c #7688A4", -"~ c #7687A1", -"{ c #8A99B1", -"] c #768AA7", -"^ c #7E92AE", -"/ c #8397B3", -"( c #92A4BD", -"_ c #96A8C0", -": c #5B7295", -"< c #8B9DB7", -"[ c #6A83A4", -"} c #728BAD", -"| c #7791B3", -"1 c #7B95B6", -"2 c #7C96B8", -"3 c #95ABC6", -"4 c #829CBC", -"5 c #45618C", -"6 c #7F96B3", -"7 c #6483A9", -"8 c #6384AC", -"9 c #6B8BB3", -"0 c #7091B8", -"a c #7495BC", -"b c #7596BD", -"c c #7496BD", -"d c #95AECC", -"e c #6F91B9", -"f c #6081A9", -"g c #3C6292", -"h c #365989", -"i c #4B6B97", -"j c #5676A1", -"k c #5F81AB", -"l c #6E93BF", -"m c #7097C2", -"n c #7297C3", -"o c #8FADCE", -"p c #618AB7", -"q c #476D9D", -"r c #5A779F", -"s c #1B4075", -"t c #1F4377", -"u c #365785", -"v c #4A6791", -"w c #4D6B94", -"x c #5D80AA", -"y c #6C93BF", -"z c #779DC6", -"A c #84A6CB", -"B c #5382B6", -"C c #3A659A", -"D c #5C789F", -"E c #224579", -"F c #1E4277", -"G c #274A7C", -"H c #2E4F7F", -"I c #335483", -"J c #44628D", -"K c #557197", -"L c #526E95", -"M c #506F99", -"N c #668FBD", -"O c #7FA3CA", -"P c #7299C4", -"Q c #36639A", -"R c #4F6D97", -"S c #345484", -"T c #305181", -"U c #42618A", -"V c #4E6992", -"W c #4A678F", -"X c #4B6992", -"Y c #4B6994", -"Z c #5F7A9E", -"` c #5E779C", -" . c #597398", -".. c #527099", -"+. c #618ABA", -"@. c #84A6CD", -"#. c #5D89BB", -"$. c #375A89", -"%. c #4C6992", -"&. c #3A5986", -"*. c #536E94", -"=. c #577197", -"-. c #6F92BB", -";. c #759ECA", -">. c #739CCA", -",. c #759DC9", -"'. c #6880A4", -"). c #5C7699", -"!. c #4F6F98", -"~. c #618DBD", -"{. c #81A4CA", -"]. c #4B7CB3", -"^. c #274F84", -"/. c #5A759B", -"(. c #375784", -"_. c #577196", -":. c #5A7498", -"<. c #6F89AB", -"[. c #8BADD1", -"}. c #7EA5D0", -"|. c #7FA6D0", -"1. c #7CA4CF", -"2. c #7CA4CE", -"3. c #789FC7", -"4. c #6984A7", -"5. c #5A7398", -"6. c #5076A5", -"7. c #6791C0", -"8. c #7198C3", -"9. c #2D5081", -"0. c #25477A", -"a. c #4C6890", -"b. c #627A9D", -"c. c #657DA1", -"d. c #A0BBD7", -"e. c #89AED6", -"f. c #8BAFD7", -"g. c #8BB0D7", -"h. c #8AAED6", -"i. c #86ACD4", -"j. c #82A8D2", -"k. c #7CA3CF", -"l. c #749DC8", -"m. c #6984A6", -"n. c #567298", -"o. c #5786BA", -"p. c #779CC6", -"q. c #5583B7", -"r. c #295086", -"s. c #2C4E7F", -"t. c #345483", -"u. c #5B7498", -"v. c #5D7699", -"w. c #93AAC4", -"x. c #9EBDDD", -"y. c #96B8DD", -"z. c #97B9DE", -"A. c #94B7DC", -"B. c #90B3DA", -"C. c #8AAFD6", -"D. c #83A9D2", -"E. c #7AA2CE", -"F. c #6F95BF", -"G. c #667E9F", -"H. c #577FAF", -"I. c #5585B8", -"J. c #759AC5", -"K. c #3F6FA7", -"L. c #254A80", -"M. c #5C769C", -"N. c #3C5B87", -"O. c #60789C", -"P. c #B0C3D8", -"Q. c #A1C0E3", -"R. c #A3C2E4", -"S. c #A3C3E5", -"T. c #A2C2E4", -"U. c #9EBFE2", -"V. c #98BADE", -"W. c #91B4DA", -"X. c #88AED6", -"Y. c #759ECB", -"Z. c #6885AD", -"`. c #5780B0", -" + c #5383B8", -".+ c #7299C3", -"++ c #4675AB", -"@+ c #395A89", -"#+ c #46648F", -"$+ c #3E5D88", -"%+ c #617A9C", -"&+ c #637B9D", -"*+ c #BFD1E4", -"=+ c #ADCBEA", -"-+ c #B0CDEB", -";+ c #AFCCEB", -">+ c #ACCAE9", -",+ c #A7C6E6", -"'+ c #9FC0E2", -")+ c #8DB1D8", -"!+ c #82A9D2", -"~+ c #77A0CC", -"{+ c #6A94C4", -"]+ c #5F8DBF", -"^+ c #5C89BA", -"/+ c #5C85B4", -"(+ c #3F5E8B", -"_+ c #3B5A86", -":+ c #647C9E", -"<+ c #61799D", -"[+ c #C1D2E4", -"}+ c #BBD5F1", -"|+ c #BCD6F2", -"1+ c #BAD5F1", -"2+ c #B5D1EE", -"3+ c #AECBEA", -"4+ c #A5C4E5", -"5+ c #9BBCE0", -"6+ c #84AAD3", -"7+ c #78A0CC", -"8+ c #6B96C6", -"9+ c #5F8CBF", -"0+ c #5282B8", -"a+ c #4F7FB4", -"b+ c #668CB7", -"c+ c #43618D", -"d+ c #325382", -"e+ c #5E779B", -"f+ c #5F789A", -"g+ c #BACADC", -"h+ c #C9E0F9", -"i+ c #C7DFF8", -"j+ c #C3DCF6", -"k+ c #BCD7F2", -"l+ c #B3CFED", -"m+ c #A8C7E7", -"n+ c #9DBEE1", -"o+ c #6A96C5", -"p+ c #5D8BBE", -"q+ c #5081B7", -"r+ c #497BB2", -"s+ c #698DB7", -"t+ c #45638E", -"u+ c #2E5080", -"v+ c #25487B", -"w+ c #506B93", -"x+ c #647B9E", -"y+ c #9DAFC7", -"z+ c #D6E9FD", -"A+ c #D1E8FE", -"B+ c #CAE2FA", -"C+ c #C1DAF5", -"D+ c #A9C8E8", -"E+ c #6894C4", -"F+ c #5A89BC", -"G+ c #4D7EB5", -"H+ c #4577AF", -"I+ c #6B8DB5", -"J+ c #3B6090", -"K+ c #3D5D8A", -"L+ c #3C5B89", -"M+ c #1D4276", -"N+ c #647C9C", -"O+ c #657D9E", -"P+ c #C2D4E6", -"Q+ c #D3E9FF", -"R+ c #CEE5FC", -"S+ c #C2DBF5", -"T+ c #80A7D1", -"U+ c #729CC9", -"V+ c #6491C2", -"W+ c #5686BA", -"X+ c #497BB3", -"Y+ c #5580B3", -"Z+ c #5B7EAA", -"`+ c #335686", -" @ c #4D6993", -".@ c #26497B", -"+@ c #4C6990", -"@@ c #6B82A2", -"#@ c #7D92B0", -"$@ c #C1D4E9", -"%@ c #C0DAF4", -"&@ c #B2CFED", -"*@ c #7BA3CE", -"=@ c #6F99C7", -"-@ c #6390C0", -";@ c #5886B9", -">@ c #4A7BB2", -",@ c #698CB7", -"'@ c #4C6F9C", -")@ c #284C7F", -"!@ c #577299", -"~@ c #2F517F", -"{@ c #536E93", -"]@ c #7086A5", -"^@ c #7B8FAC", -"/@ c #9FB3CD", -"(@ c #B8D2EE", -"_@ c #91B5DB", -":@ c #85ABD4", -"<@ c #7CA3CD", -"[@ c #729AC8", -"}@ c #6791C1", -"|@ c #5A87BA", -"1@ c #5280B4", -"2@ c #7A97BB", -"3@ c #416290", -"4@ c #2F5080", -"5@ c #4F6B91", -"6@ c #687F9F", -"7@ c #7B8FAB", -"8@ c #8396B0", -"9@ c #8AA1BF", -"0@ c #8CACCE", -"a@ c #8FB2D8", -"b@ c #86AAD2", -"c@ c #7DA3CC", -"d@ c #749BC7", -"e@ c #6993C1", -"f@ c #5B88B9", -"g@ c #7999C0", -"h@ c #587AA5", -"i@ c #21416E", -"j@ c #516D96", -"k@ c #415F8A", -"l@ c #567196", -"m@ c #637B9E", -"n@ c #6F84A3", -"o@ c #778BA7", -"p@ c #758BAA", -"q@ c #7490B3", -"r@ c #749BC5", -"s@ c #6992C0", -"t@ c #799DC4", -"u@ c #7291B6", -"v@ c #46658C", -"w@ c #264878", -"x@ c #4F6C94", -"y@ c #42608C", -"z@ c #294B7D", -"A@ c #4A6790", -"B@ c #567095", -"C@ c #597599", -"D@ c #587398", -"E@ c #536F96", -"F@ c #4F6D95", -"G@ c #7690B4", -"H@ c #7090B7", -"I@ c #4A6991", -"J@ c #2B4871", -"K@ c #41608C", -"L@ c #59749A", -"M@ c #234679", -"N@ c #214578", -"O@ c #3A5A87", -"P@ c #47648F", -"Q@ c #667EA2", -"R@ c #7188A9", -"S@ c #4B6892", -"T@ c #395985", -"U@ c #1C314F", -"V@ c #2E4F7D", -"W@ c #395986", -"X@ c #526E96", -"Y@ c #58739A", -"Z@ c #5B759B", -"`@ c #6A82A5", -" # c #627CA0", -".# c #365887", -"+# c #324B69", -"@# c #344B69", -"## c #34537D", -"$# c #294C7C", -"%# c #315383", -"&# c #40618D", -"*# c #40638F", -"=# c #3B5C87", -"-# c #36537A", -";# c #2E4156", -" ", -" . ", -" + @ # ", -" $ % & * = ", -" - ; > , ' ) ! ", -" ~ { ] ^ / ( _ ", -" : < [ } | 1 2 3 4 ", -" 5 6 7 8 9 0 a b c d e ", -" 7 f g h i j k l m n o p ", -" q r s s s t u v w x y z A B ", -" C D E s F G H I J K L M N O P ", -" Q R S s T U V W X Y Z ` ...+.@.#. ", -" $.%.t &.*.=.r -.;.>.,.0 '.).!.~.{.]. ", -" ^./.F (._.:.<.[.}.|.}.1.2.3.4.5.6.7.8. ", -" 9.v 0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q. ", -" r.v s.t.u.v.w.x.y.z.z.A.B.C.D.E.F.G.H.I.J.K. ", -" L.M.s N.b.O.P.Q.R.S.T.U.V.W.X.|.Y.Z.`. +.+++ ", -" @+#+s $+%+&+*+=+-+;+>+,+'+z.)+!+~+{+]+ +^+/+ ", -" (+S s _+:+<+[+}+|+1+2+3+4+5+B.6+7+8+9+0+a+b+ ", -" c+T s d+e+f+g+h+i+j+k+l+m+n+W.6+~+o+p+q+r+s+ ", -" t+u+s v+w+x+y+z+A+B+C+2+D+n+B.D.Y.E+F+G+H+I+J+ ", -" K+L+s M+N.N+O+P+Q+R+S+2+m+5+)+T+U+V+W+X+Y+Z+ ", -" `+ @s s .@+@@@#@$@R+%@&@4+z.e.*@=@-@;@>@,@'@ ", -" )@!@t s s ~@{@]@^@/@(@=+'+_@:@<@[@}@|@1@2@3@ ", -" S #+s s s 4@5@6@7@8@9@0@a@b@c@d@e@f@g@h@ ", -" i@j@u s s s G k@l@m@n@o@p@q@a r@s@t@u@v@ ", -" w@x@y@s s s s z@_+A@B@C@D@E@F@G@H@I@ ", -" J@K@L@y@M@s s N@z@S O@P@Q@R@S@T@ ", -" U@V@W@X@L@Y@Z@L@c.`@ #v .#+# ", -" @###$#%#&#*#=#-#;# ", -" ", -" "}; diff --git a/deluge/ui/__init__.py b/deluge/ui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/gtkui/__init__.py b/deluge/ui/gtkui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/src/gtkui.py b/deluge/ui/gtkui/gtkui.py similarity index 100% rename from deluge/src/gtkui.py rename to deluge/ui/gtkui/gtkui.py diff --git a/deluge/src/gtkui_mainwindow.py b/deluge/ui/gtkui/gtkui_mainwindow.py similarity index 100% rename from deluge/src/gtkui_mainwindow.py rename to deluge/ui/gtkui/gtkui_mainwindow.py diff --git a/deluge/src/ui.py b/deluge/ui/ui.py similarity index 90% rename from deluge/src/ui.py rename to deluge/ui/ui.py index 8fb13c189..2b47042e9 100644 --- a/deluge/src/ui.py +++ b/deluge/ui/ui.py @@ -71,11 +71,6 @@ class UI: if self.config["selected_ui"] == "gtk": log.info("Starting GtkUI..") - from deluge.gtkui import GtkUI + from deluge.ui.gtkui.gtkui import GtkUI ui = GtkUI(self.core) - - # Test the interface.. -# self.core.add_torrent_file("/home/andrew/Downloads/test.torrent", None) - # time.sleep(3) - # Shutdown the core thus stopping the daemon process -# self.core.shutdown() + diff --git a/setup.py b/setup.py index dc02e4df1..cf0460170 100644 --- a/setup.py +++ b/setup.py @@ -84,14 +84,7 @@ libtorrent = Extension( ) # Main setup - -_data_files = [ - ('deluge/glade', glob.glob("deluge/glade/*.glade")), - ('deluge/pixmaps', glob.glob('deluge/pixmaps/*.png')), - ('share/applications' , ["deluge/share/applications/deluge.desktop"]), - ('share/pixmaps' , ["deluge/share/pixmaps/deluge.xpm"]) -] - + setup( name = "deluge", fullname = "Deluge Bittorent Client", @@ -104,11 +97,9 @@ setup( license = "GPLv2", include_package_data = True, - data_files = _data_files, ext_package = "deluge", ext_modules = [libtorrent], - packages=['deluge'], - package_dir = {'deluge': 'deluge/src'}, + packages = find_packages(), entry_points = """ [console_scripts] deluge = deluge.main:main From 49e5e9b093c2f6866b7a592fd0b8e8c7f34cb671 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 12 Jul 2007 22:34:33 +0000 Subject: [PATCH 0007/1009] Updates --- deluge/common.py | 9 ++++++--- deluge/core/core.py | 3 ++- deluge/ui/gtkui/gtkui.py | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index d0d04510d..a351154b1 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -34,7 +34,7 @@ import logging import pkg_resources import xdg, xdg.BaseDirectory -import os.path +import os # Get the logger log = logging.getLogger("deluge") @@ -44,11 +44,14 @@ def get_version(): return pkg_resources.require("Deluge")[0].version def get_config_dir(filename=None): - """ Returns the CONFIG_DIR path if no filename is specified - Returns the CONFIG_DIR + filename as a path if filename is specified + """ Returns the config path if no filename is specified + Returns the config directory + filename as a path if filename is specified """ if filename != None: return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename) else: return xdg.BaseDirectory.save_config_path("deluge") +def get_default_download_dir(): + """Returns the default download directory""" + return os.environ.get("HOME") diff --git a/deluge/core/core.py b/deluge/core/core.py index 6ce6e690f..b01780093 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -56,9 +56,10 @@ from deluge.core.torrent import Torrent # Get the logger log = logging.getLogger("deluge") +#_default_download_dir = deluge.common.get_default_download_dir() DEFAULT_PREFS = { "listen_ports": [6881, 6891], - "download_location": "/home/andrew/Downloads", + "download_location": deluge.common.get_default_download_dir(), "compact_allocation": True } diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 13837d689..8a6103faf 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -38,7 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import gtkui_mainwindow +from mainwindow import MainWindow # Get the logger log = logging.getLogger("deluge") @@ -53,7 +53,7 @@ class GtkUI: pkg_resources.resource_filename("deluge", "glade/main_window.glade")) # Initialize the main window - self.main_window = gtkui_mainwindow.GtkUIMainWindow(self.main_glade) + self.main_window = MainWindow(self.main_glade) # Show the main window self.main_window.show() From e08ad9ddaf74ae5704725f9aa8237bc5579372ea Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 12 Jul 2007 22:43:23 +0000 Subject: [PATCH 0008/1009] Updates --- .../{gtkui_mainwindow.py => mainwindow.py} | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) rename deluge/ui/gtkui/{gtkui_mainwindow.py => mainwindow.py} (69%) diff --git a/deluge/ui/gtkui/gtkui_mainwindow.py b/deluge/ui/gtkui/mainwindow.py similarity index 69% rename from deluge/ui/gtkui/gtkui_mainwindow.py rename to deluge/ui/gtkui/mainwindow.py index b63d09e20..e9bb24ab7 100644 --- a/deluge/ui/gtkui/gtkui_mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -40,13 +40,13 @@ import gtk, gtk.glade # Get the logger log = logging.getLogger("deluge") -class GtkUIMainWindow: +class MainWindow: def __init__(self, glade_xml): self.main_glade = glade_xml self.window = self.main_glade.get_widget("main_window") # Initialize various components of the gtkui - self.menubar = GtkUIMainWindow_MenuBar(self) + self.menubar = MainWindowMenuBar(self) def show(self): self.window.show_all() @@ -58,27 +58,33 @@ class GtkUIMainWindow: self.hide() gtk.main_quit() -class GtkUIMainWindow_MenuBar: +class MainWindowMenuBar: def __init__(self, mainwindow): + log.debug("MainWindowMenuBar init..") self.mainwindow = mainwindow ### Connect Signals ### self.mainwindow.main_glade.signal_autoconnect({ ## File Menu - "on_addtorrent_menuitem_activate": self.on_addtorrent_menuitem_activate, - "on_addurl_menuitem_activate": self.on_addurl_menuitem_activate, - "on_clearcompleted_menuitem_activate": \ - self.on_clearcompleted_menuitem_activate, - "on_quit_menuitem_activate": self.on_quit_menuitem_activate + "on_menuitem_addtorrent_activate": self.on_menuitem_addtorrent_activate, + "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, + "on_menuitem_clear_activate": \ + self.on_menuitem_clear_activate, + "on_menuitem_quit_activate": self.on_menuitem_quit_activate }) ### Callbacks ### - def on_addtorrent_menuitem_activate(self, data=None): - log.debug("on_addtorrent_menuitem_activate") - def on_addurl_menuitem_activate(self, data=None): - log.debug("on_addurl_menuitem_activate") - def on_clearcompleted_menuitem_activate(self, data=None): - log.debug("on_clearcompleted_menuitem_activate") - def on_quit_menuitem_activate(self, data=None): - log.debug("on_quit_menuitem_activate") + def on_menuitem_addtorrent_activate(self, data=None): + log.debug("on_menuitem_addtorrent_activate") + def on_menuitem_addurl_activate(self, data=None): + log.debug("on_menuitem_addurl_activate") + def on_menuitem_clear_activate(self, data=None): + log.debug("on_menuitem_clear_activate") + def on_menuitem_quit_activate(self, data=None): + log.debug("on_menuitem_quit_activate") self.mainwindow.quit() + +class MainWindowToolBar: + def __init__(self, mainwindow): + self.mainwindow = mainwindow + From 0b621fd58176c49490f331ce8c8afde962ea958d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 12 Jul 2007 23:15:38 +0000 Subject: [PATCH 0009/1009] Updates --- deluge/ui/gtkui/glade/main_window.glade | 1140 +++++++++++++++++++++++ deluge/ui/gtkui/gtkui.py | 2 +- setup.py | 4 + 3 files changed, 1145 insertions(+), 1 deletion(-) create mode 100644 deluge/ui/gtkui/glade/main_window.glade diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade new file mode 100644 index 000000000..c8981d2f9 --- /dev/null +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -0,0 +1,1140 @@ + + + + + + Deluge + + + + + + True + 4 + 3 + + + True + False + + + True + Add Torrent + Add Torrent + True + gtk-add + + + + False + + + + + True + False + Remove Torrent + Remove Torrent + True + gtk-remove + + + + False + + + + + True + Clear Finished Torrents + Clear Finished + True + gtk-clear + + + + False + + + + + True + + + False + False + + + + + True + False + Start / Pause + Start + True + gtk-media-play + + + + False + + + + + True + False + Queue Torrent Up + Move Up + True + gtk-go-up + + + + False + + + + + True + False + Queue Torrent Down + Move Down + True + gtk-go-down + + + + False + + + + + True + + + False + False + + + + + True + Preferences + Preferences + True + gtk-preferences + + + + False + + + + + True + Plugins + Plugins + True + gtk-disconnect + + + + False + + + + + 1 + 2 + GTK_FILL + + + + + True + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + + + 2 + 3 + 1 + 2 + + GTK_FILL + + + + + True + + + True + _File + True + + + + + True + _Add Torrent + True + + + + True + gtk-add + 1 + + + + + + + True + Add _URL + True + + + + + + True + _Clear Completed + True + + + + True + gtk-clear + 1 + + + + + + + True + + + + + True + gtk-quit + True + True + + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-preferences + True + True + + + + + + True + Pl_ugins + True + + + + True + gtk-disconnect + 1 + + + + + + + + + + + True + _Torrent + True + + + + + True + _View + True + + + True + + + True + _Toolbar + True + True + + + + + + True + _Details + True + True + + + + + + True + Columns + True + + + True + + + True + Size + True + True + + + + + + True + Status + True + True + + + + + + True + Seeders + True + True + + + + + + True + Peers + True + True + + + + + + True + Download + True + True + + + + + + True + Upload + True + True + + + + + + True + Time Remaining + True + True + + + + + + True + Share Ratio + True + True + + + + + + + + + + + + + + True + _Help + True + + + + + True + gtk-about + True + True + + + + + + + + + + 3 + + + + + + True + + + 3 + 3 + 4 + + + + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + + + + True + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + + + + False + False + + + + + 3 + 2 + 3 + + + + + + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 8a6103faf..dafb01097 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -50,7 +50,7 @@ class GtkUI: # Get the glade file for the main window self.main_glade = gtk.glade.XML( - pkg_resources.resource_filename("deluge", "glade/main_window.glade")) + pkg_resources.resource_filename("deluge.ui.gtkui", "glade/main_window.glade")) # Initialize the main window self.main_window = MainWindow(self.main_glade) diff --git a/setup.py b/setup.py index cf0460170..a89e59b2b 100644 --- a/setup.py +++ b/setup.py @@ -84,6 +84,9 @@ libtorrent = Extension( ) # Main setup + +_datafiles = [ +] setup( name = "deluge", @@ -97,6 +100,7 @@ setup( license = "GPLv2", include_package_data = True, + package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png"]}, ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(), From 61ca73dde1c901b27be96a556051e622795ea568 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 12 Jul 2007 23:16:39 +0000 Subject: [PATCH 0010/1009] Updates --- deluge/data/pixmaps/deluge-about.png | Bin 0 -> 9944 bytes deluge/data/pixmaps/deluge128.png | Bin 0 -> 13952 bytes deluge/data/pixmaps/deluge192.png | Bin 0 -> 24460 bytes deluge/data/pixmaps/deluge22.png | Bin 0 -> 1103 bytes deluge/data/pixmaps/deluge256.png | Bin 0 -> 36758 bytes deluge/data/pixmaps/deluge32.png | Bin 0 -> 1909 bytes deluge/data/pixmaps/downloading16.png | Bin 0 -> 662 bytes deluge/data/pixmaps/inactive16.png | Bin 0 -> 588 bytes deluge/data/pixmaps/seeding16.png | Bin 0 -> 612 bytes deluge/data/share/applications/deluge.desktop | 13 + deluge/data/share/pixmaps/deluge.xpm | 415 +++++ deluge/ui/gtkui/glade/aboutdialog.glade | 38 + deluge/ui/gtkui/glade/dgtkpopups.glade | 406 +++++ deluge/ui/gtkui/glade/edit_trackers.glade | 120 ++ deluge/ui/gtkui/glade/file_tab_menu.glade | 100 ++ deluge/ui/gtkui/glade/plugin_dialog.glade | 114 ++ .../ui/gtkui/glade/preferences_dialog.glade | 1461 +++++++++++++++++ deluge/ui/gtkui/glade/torrent_menu.glade | 165 ++ deluge/ui/gtkui/glade/tray_menu.glade | 158 ++ 19 files changed, 2990 insertions(+) create mode 100644 deluge/data/pixmaps/deluge-about.png create mode 100644 deluge/data/pixmaps/deluge128.png create mode 100644 deluge/data/pixmaps/deluge192.png create mode 100644 deluge/data/pixmaps/deluge22.png create mode 100644 deluge/data/pixmaps/deluge256.png create mode 100644 deluge/data/pixmaps/deluge32.png create mode 100644 deluge/data/pixmaps/downloading16.png create mode 100644 deluge/data/pixmaps/inactive16.png create mode 100644 deluge/data/pixmaps/seeding16.png create mode 100644 deluge/data/share/applications/deluge.desktop create mode 100644 deluge/data/share/pixmaps/deluge.xpm create mode 100644 deluge/ui/gtkui/glade/aboutdialog.glade create mode 100644 deluge/ui/gtkui/glade/dgtkpopups.glade create mode 100644 deluge/ui/gtkui/glade/edit_trackers.glade create mode 100644 deluge/ui/gtkui/glade/file_tab_menu.glade create mode 100644 deluge/ui/gtkui/glade/plugin_dialog.glade create mode 100644 deluge/ui/gtkui/glade/preferences_dialog.glade create mode 100644 deluge/ui/gtkui/glade/torrent_menu.glade create mode 100644 deluge/ui/gtkui/glade/tray_menu.glade diff --git a/deluge/data/pixmaps/deluge-about.png b/deluge/data/pixmaps/deluge-about.png new file mode 100644 index 0000000000000000000000000000000000000000..63aef4fbb7a260167e05378c74f0415b0de6f414 GIT binary patch literal 9944 zcmV;}CMVg6P)CsvFFZ;w0sVN88H|K)K{mt(MOHMLOLP&o>$*X@<;Fk}xERBD9jno+pH$RIO32 z)i`Wvf-vU0U*4zWx#xaOH~4U*ndIf>A&0F##u$gx&TW1LiL&6DT*wAurV)&#>oaS*)m`NkOHz$XrT%Jn*!d4Bu*4Sx0Q z7u0Lzk8z3D9%^uaRbN5a_PL(7*Z2A9$qq_sk|-n?^wCPa@%b`M@zXBhXu#a^3eR5b z6U8ZC-oJHYVxI%f-A>Ii$@f2Q(P|Hf!+`E#0^qajxe*IPCFL_QuLdLq;bsdUYjS+_s;d&K3AM8 zfulI(cYk=va1;^@2l%}%rr7tM1!)}9Zylnf;>X9^BuRFjmwBzBm89zL;yM(eH61_#9z(H>i3R57yVH*DB2GJ~|wH&^fnvTWs&P$dZJ9 z^YGNF`zH9kE?JVWx!t7Q`LMr}&mHGNV68PjJ>DisQbxTFNqBmb`EAIOI9Ku@=IQhE zQIhKn*=sQGWGpVsurOce!TKtdvdetZz{vEy&e>`Y*xYWCr3r&}^R*D*`(oH_qm|_G z(_Q@F>}r049Opvdr_c8pjv|85kTiJpGg7rwZ=O2xCN47biqzR@V+@v}XYk_c{=GFcf~~za(g-vHg+-KUb`JVjw!`|}RUFsG^LO895-X*7@_Y|t zG(o?2VgDDupNAZuJl{ippqKk|!g~=|O2x*@1GG|v!w(BloE6b%h|DrxZZ$u9Q`+DB z5!-@0_g3*r9*x$3QIw&vP)1Bv-;Zhad@7X^cUG4u*#d@*6VJK5+agIaf_@*ZFOEn? zYexMZX)4)xd3a&3`>Er-2)wn^LMn|v=v|5_>-V~7t$F%<@3S?fhmCC<6)`tA%kt7Z zeweb?8lo*5g+Q5{#L@_)vDj~q2%?OI`B~=YXDE}Rg5wGsMhUxnZL%~WKBrLNS&&8{ zNfdI>?9uJrn9`@gnF$<5344tWS(04H{^%sgEG6i%tP*Wu+}mnfqk zt&OB0EmlEhfkKcd*l6^@viNd+8QZZjK^v_Vt@eQaARrEWRCamqr_s4nG|goGw|0ZK z;!FhY_IW$zHA7^FNE6+EKSd&}&n!RCsXj7Kzk6-|wM?T`X(zINt+Xh5A}K z!O4Gq&|jYI2gI5mOy54uEAN;n*Tl&qZh zMKI`NwC3g3;g$L0`xwZwl!MLZV7vV6t2+qW;pt9~L=}5pVHYY}kayaIKK6Bfd~q-$ zmYTb(^VI4U)^F7*J6GrZQ&N(I0lj{36B~XDPDS8MDz>(pC@G2j(Z{}~Gr<@`I2sbg zDTl58Wna!m!_Kp(Xaf)K-J({hvfK9Q`dO~F)2mR~BSIHnPsnSdi+%Fby&>8NzPh`F z<9Yn*+XuML`8ELf5QM|QM1`;O&G|!cDgtlqG?7XZ46Y_YqQFOM#pZVNTISO>_xJIA zpPAV@ch{Ei6UB?hhzyG`O6hg`v=3WTtI4oGBucYl51f;BY-XE6nZwMmap>c^F5j## zQL2>r_PZ}H`@cJZ)|$v4;Ri9T_VwQ)y${~mQYQ=(_8VQ&Bqoios6)XR!r>6hTA*>* zRH%f6y>LllPW?(E`uCBA)l2ZhDo{q7n2driV1z_tXAZQ(jLGqo}$&%w4WEFrKh z3(K*o&de~kv`o2HM*vtL3PJ0P66)oMrMWU+tS*pg!53fLW^3!@M30L`G#XN=)p@yn z$nw%GmUYvHXBzJ$@Qcj@j4=fLtEs+Y#DPzBW`?bu7HhW_ulQQf3kYE_Mj!-i zHGQgOhdawP`eA}xUt=%~==UyP^^7qD!vVrtpmEr}RyXJKz+3;yyS)*;zE2VcD0%f` zt-)}Jl46^8_7FHoTN0`0}k5*EMVL>P6$B~hcvb}*?#_n zEKLzbTVDh`-5;Q|;b(W}sg+&6{`mvEkJM`TtcU|2rDTx-c$3^QjW-fF4;vj^sj$(z zFuu|3?6p7nES`RUK(pOP%Z%GKLl{Q<^N*XaJx>U59E+0YP%V2@${y9Shg6zocZd)I z*RgQz+|+h#i!6%Re)5R!;UU5TAuKX!c-|OcTNdBmU!>%@eEst;uT1C2a4OC_b`+EKA_I zHsz8_wd7JQyHv{gF(RoQCB73!NTLv#rR?stNz)fM`?Zi(O`^WV{?!Ftu1cPI;(Z*1#c+Ax+%+FL_@0o97f zOs&LRz07RA%xppAxnj-K%4h?$418i2QQyv4EV9+<>8Q$>*ORZmsnlDe6v+%8A%k;?v5DwS2Qo-0&&cQ zR%%LpKe4^w|7+GtQIyUbKe%v4KM zD;~9qN4exsEqOSOO{ecO3Q|1J;okjqR@YWBZkZraliiOo21C9ZDmjAG?~x39$TZ6L z$>dHkV+=Zr=pQziSy|(t8&NO0EY6hqYIT+u+A!+R;twu2J{$}Oc$Eq}yDh%`#r;dY zmdnO51>QgClF6%4Qg$8)hlAYQ+HPL5O&#`Iue55igEza;~fA;Tr__K#Zlt?5vc3~bLTNVgGd3KSRyI(PX z=PRnUDxT}$Iu@SmP;wm7VH=s```u^z15&B^^7b6{ipR>GHSBX9n08iZB}oev4#Ugc zmtQs}1RjT8l#-XyPUT}jW*JEw(e4cKuduoG_{Ya2am25Gevfj=<@=|5#7RcA;!>-6 z)C=XURr8}UnpQu+u`O1X=lRe7=}+*=75bqr_O;f8u#?9{3|NSxg;&8_UdM7>T*sM& zm^{Z~c(9YJGVpA_k8KIQ{bB*vb6CE!cG+(kp`Tj;pK`eHJTM{fUZaDQig0-O>2v3a zaYf746&<9dl;YW=CsfNG_tzHb4gxlIT9it8Vn(&>QLPlK?4q=xKZ+@N4y&t6{Kx0WNnkr3mDMk?+)}<5b}dS-gG)*>>|qJPFvvLU zN6gnsd~s`r`rItl+4|+aZ&{Xaky?G?&$zjv1ykx<)L>pGC@D$fh)#Dz5MI3*JkK6K zBTW*1_Fxqu1dm@H;5Zi5l2dq-9m<|drQ}lb9Q+_BZ?)p_?|$I_Ja$MO zBeQ*^z>brLoP;%w8w*_5qq6oTrP>T7FMpkq>!3z0jFt#Xu+bQjWSXz<%uy{lEUm3# zU2?v8SWE)h-T(Zuv&Jc<7QkVvk5c;D?0sLw_=Dcry?W->)1MwwavdJrU1H!zG+F~n zu0zQ?B5!_l@WTY#u_?I@Uw^gET)n~|*0`>HL|)g%bzfEDoVbo{;aJurrE;91l~**` zc+2Zl>ob!?5yt`<46uYi8^hDR+?4+A!6LTfu(-N>sqdXk(>z>w*!!$zD`~Cy@zFNM zn4G`|%^pf-B+>O8rad;dn(e`r>LuN2wTZ%zhxb>oEsH0`Xc5n`@m#0Kw93hAfGCl8 zu1&30=9{nYgJlt>OvviF$CcM9(kC5zdKJXAL_ywMiEYcm5*DVuoSWNjuIMF-q6Z=9 z`w0jAh=qEI^_4ocnHkEL?A;GXLyR%krhw%%IBfOFomf1@{~-3;Q8JFtqtodGXT^sc`uwLah^I`_;9W}j4^CaS(279 z1|_pg>+yV1$jz;o!)A}<3M9iDW7yf;W^t~H=h*BX=JnRDZDTt*dB>!49h)rEljMv0 z_g1kjiy#$8gq>F3;&-Nrdz`!i+kTzA;2*F`LzuB)XRmOA@j)&ymiu%XNBm+xbmoWpM^$4DDi+NUn+nsS1TmrOJtBSuD)W z;M#fP_KC;2Ah92Q3tyx>B0Z^@7 zE`cGxIZLUp^`wq=CvV;ujfNPL->5z-0z*E^QeOe5N4L{O2*J{Pjs7S?7RoX1RV4}{ z2_Z-`NwEcGVSWbNw$O-)$9cT}b&8PDYi6@$;Y_KoC33=!No-BM*3nVmEvylVD&_iI zan-r4tTnU;F}1Qwz3fshdzW7LpM(KgDR%bSX#MHt@V^P;Tn3}{u{7t;j=*UgA+wCV zM)y*k9=&#l#knfBWzp;&wdyq-FCh^E(o7YP1@i@g4bBOvl6J9AJ|eGEnBvxy(`p?5 zeU-2|2akwoJ1*tL>8vYL?7a*Jy#O%WUaBF4pjvT1-kWtwgrgy8CTU*7_QKs>o;Di! zqhl?0a{^z#8`fJ9jfNfk>%#bg^+jP`S9ozi~r45TSWiWxE`0NxI^7bsb)1z}KP7j$$wAPa*>hY9UTx)|;2Bi(U_!>dcNy8we#I`I- zPTrAYn!Mw*$q9K2wauMXQ% zCf}2VLu*{L;7ww^_QZ6a?03f`7ANd=VX~$%QCTxZUNc^%C{4_dQYma(fH9XIN*W_? z@~_$2xjs$)x5w&x!c>&cmca4Y>9Kq9PLDBxor1t}iojYMGO5XwB9j_bD78}g;~3+~_Pd;{w7@H7=Mhm2rs%Vk6ImAzq}1g6)mMhVV--$Qc`a19KL{9( zLgK(bvAN`DOJF#1dR+8OrK}juI&M*;juBXsNlBJUGO5Tig=1NT`K;M%w6JZ9vL_}v z&f|iG*W$a@*kpdyQFVkP#Wj;-zBsn@C z1Pq3``f7c0#N@LkFvbx1xznTBzPMa)&&wwsj0vn>Q3V9jOJ*X z!}f0Llg-Y0m-@nlz}L&=eip=j?)2E)ZeExQj|m(Wl~MKT@roWP6-lZ{Qc0X-M2W~)VR?pSpvk8|>R zwxC+F=?=rZ6|GcgbA=?B@u(fi_9r-M=QQdIHv|R>6~17n$L!oJ-9g^{Uu;_J$Z~a=;G!u z2@{V8rD0f_ErHSW`aWn)nq5sZH7rK$Tz2a0Suhx$PJQ8qz!QYS0cj>LEIVsqX^~+N zBb9z5fitNHqXa)n@uQS5N|91?Yhi{YP5Gby`)^4?AG-cMB@{BJG2w@NiEGUW`igqpIFjPTqTja6d<+(C4lSEO1g(khi+z5yH=+xaS z)Xd>cI8xs{PvKp-A@CH!;Q%T1g)~W9Tv`P3X$qDQEX-D4dydwIEK~Sl!qATyg$bh| zCvq64%+)KnHvEsj`Wyb?@BbO0Vzkl4JR~z&w?9(Z>zQWM3Jp&v0Q zNIVGgHS{CmR5DX5VWauKfAjzN-9P^ey>=^CV5KK%m`WWbVVH3cSO6G=8LRG*IX!k> zfhm$TH0#TC>SdSRW{)6>K}$wqhS3VGF27aLn=l@Dx8LXzCs*@2&#Jxv_MJce^S{ti zUfYh8XN8gyw^SyGQkEBI&wakIk{L>-Xqn-;Hd~u}WKwZ&Z2`}7*l(VkP@>1?u+m4# z7e%s#DwNikyhEkRQucTEXdN7oW{PsPHc{U3bEh3xlT=Bqr((e4f(nB&8kE8nhVLFM zBD0L&J=sB}34>vPA7w{VXobk9K3qX4DJfTLBuUE3^4zT`8`}(;4V+31yE02pvG5t+6Z%jYYZSQ9q}iPdRb3oWwFk zNlB&b(rC14_k6y*x6JC&EZh6t*U~E|*Jkpr{HauE&1A1DRlYK%$udPI6=^1s%AmE% zhhv8WM(soV;gBRruxtlmWBn z*P1J@Kp-spN)i}y5|`_BhJM8D)rBi@o%@Y0&GvwBIG8l6dl$wEd_&+jK&A=hYHjjC zpT}@(t<*$;$IG<^aU9d@2c%N5zP7;X@+`ZDy*$M81}J4nQi&?atCY^m5Tzo`#v~pS zJ14wS8kuQimXHPkey_!--y=y94B$8}#^@pwEWa5jsrYKO#_h!#eh~8e$2+8PLKFrx z+nwARFo3YYvS506+7*N;g5{RuVOth+v#-bCFA`}c`RU0HGD{itPM?EtL*P?DD}`Gs zqm*W`X#e^_=hG6ca}t~4sI{V2EpgcF(dp;yTkowdaBDFq@!M*Zje#UnBw1d1s8n8l zm?=$~shnI=A(bJ`G^xx_`Nhu2}eHtPMc1%i64Y{=9cB4l;-QTI?JibTk|;1<&8^yv3bB~6fx>{ z(dzV`{2KzF0!n7Mr4nJBv9vIQ=Y3e;C#^L`tDL-AqqV|ljX+Z^J9N4}&F%=tvADOo z!20b)dc%Mye!JIHYeSMLq9h}gIeDd2NS!NkCUf$pS-vuvD|9LqX)5!Ir%VwiDWM2IKiMUT6S5>>Z)cMzPEk7dPal!i#ww0iRe{G)Sw^Xvr=?z& zLu)jOc)rmfjiNJop5ZkDU;oJr?+clxlxuZ@Fk$W1{0Hrs@)Er9Qeg7^sWAr6v#C}b zy8V#D_7JHQcW%w|;LZ|42)cvtoz+sN=u?$RRqTa}eesyI#lARBWI^09p;cjq=MJrV zs|$Sl)f%o)JbSUrPn)eg-#L!h-P#}sBa{L?QC|D_@e1#O$Iwb)I}WKd%+6G)l)Vdm zeWf&i_-TtQlMFlUNuTG_;sgc0A@E5sI`2tkgpKDp%+xFIeJ&xa35hZKSa%C!3?)KISYDjr-s%Eht}i2i!6=$a#5ft_v`kZ^N=l^*HO}|SQsqvn>8d2l60F}| z;8)+=V`;udnks(WXz=X z$l9%i6Vq8g7B;pH==FVqL7yx+``FtX0!kD@d;kCjUP(kjRG$G^l2EGF7>*)VSKfcm zG%5F+5VuIGFiegm&t|Uf5ydG(KVfUHL#7lgb;*L`kE@|J+P)* z>%7v1lH&xgmObvR&GV~o?y@*rBTh3Oy=d^SkGARbe3a5S4z%|-b0wdc%}&0i$SZ6I zVY^peeaA3G?AvlYq*N>|ez>3GH0bqxUc5XYjbnnr`?Yhri36Vzqh6Q!N0mS0;}}8+W@ZI<)+8G*574+g-8|&^c9SpfF7wS7w|H=8i3fLrYK2yzTIMLzk^hHvWM+B zWMPY;KSCLS(gMTSOm++MPOVRfyko?Hk5?+wIE00TI?Ic*ANBR5R6KdMhtV4U3|{9q z;fBDcgiI6SFrXs@hpj%VEA#JtF55Zo49jzmbLAA4O`J;hn!fPju$&E zUhK4(t5>*lYmVD1^QeM|V==I-r;z>`YWx8$)jxG8jd42LX-t0IkM((iTRg z^jh2Kg1FKY&Z-Gv?c>C?FMqts`(o7ZvQS#%>9aj%ep8|JfzHae;n~JMX(sXey?567 zz7@rd`G&w}KrrazmdZSRzDK3(F+1~m1<`3?J8n)`VHL!+uq+#;VQa4smPZs05Vnia z8S$_Q2&%L5M2XF#jRPKS9N;=O^=gTkT6wa{C3iBG)GnqJ#;IiJ_Zf_$Lb3CGSKd}9 zzaGoANroMwQ6GaqYJoC&4U%Q&gymHd+itGLpD1}D_j8Q8UFMdS`RVZvzy9vQq-Xbe zVspDmt34zRgL6w=z9H}#V2okdX)%9ml^-5$@ta>i{2=e8W!ZS;Dl&7h3Sw-%XrPQk z93~hnEW3oU9MI}0)y{OmawwInRO$<)Qn7Q;o9u_jdtckOCPU2f45^%CLgYyswqs+o zB#nIhPA4x{Fcumo&y^mBh%EbvxMNSV?G`5U)eaY(7FiPKL*Rwr@v~jN{rUR2-jo^# zUAA{yWJ!E3rt=s#1U@5_%-#O8ODp{F(-!~cR}VfoKJ2(2w&Sw3w}T&>JV8Qfgi}Hk z#5G1u9#eR-(>SCP)IG`3wNSlAY5g-jx%$e%dP#tmULE?Vc_ zWe{K&FC8P@Cs!7-h zqb6afaY2IC8hx+RYDhTActPL_y%I*^lhX zBZ)$kQp_za^Zk!6S--o&`rYMIJ3x#vZ0)r2K3q~V=(LVyXJSvj7KG*)DhlT{Rj2$ntdv(FU9FlaTISy<$-1!0u%-7oH+x;ESx!;8%W z_8Z;FmP^Tx)3M}6fj`+*>iqs$6ZIV!^}0wY!5IGOU!QXK&N6pz zFXFmyrcIjW)kVWmNE+uw3TI7|JPU3Jd=5}jGH4xAuGXl{&GYE#F0N~{yg19!;tb`I zHwo87amvAAFYl*JuHvKa`!~^>s3=E=$f@O z;kr`EBgSE{r-OE_xqjSIYpFG`2X^)D|DxCU?=1aZ+qXZFMQ>FWT)@rT99u!0=)4p|6yos zaOm@Q-*xCe?DV}{3-T>TfPq^1qhaWe*J_nF-}kfcf6Y$c&rZv?6ajwXU;oy>s#VLc zV3fdPlnxG7KKSN${>H(bzOS8>Zy5r-{%!BOd1R>i?F-q_&a17YA7D5V%59Ym#E1fvw?Qt{CK+ot~ePT%8B$+rdp?mYBc z)pGF=B?OgH0TW|`*tdHeLEwWBf`MA)AH3!M|NJj^`aX9;p7R8F{ab(a`-TUr|CA5{ z$|$C%MnMRH=P~S=8imIwD5DsvSKs%qe&*M|eW&mDYRhw)06*{}KQlZ&H2AxWQ33!{ z6C((X)>A@IDHky@J_Je#eBTQPh6X?NlRx<%2X^|tuckbw2{1K1_3>gM7y*EKt%QMk z83fQ9As~ccbf}Jctqe*CiiKdhG+O=S&ffLamFFx0UU%QGymfe>`f>p9eGj|FN8Edh z%WGI&Z-G)xf~m<7grT+!)k^8*Z+Z9c{KAgi_*ItYBmrLYhF`pOY;54Y3V>1y(~~1$ zi~l-&o3yYFfv?6xl{m@f>08tXXYWLgj5Qr&(4DjJs4If1elr}1)&Vo^Vndm`r$YK z^arMQ_~&f9Jm&~-)AhT5t6DDHsFXq>3@|>TeL4l;{OmG1aRQ|Pr3gBSz}eYFC_ zP^*Tc_uqfNx3j-!d*wMrfFJy^pMTZRK<$l60fYd1rpG`Cg;Ky; zqm6~-29zR@ppXjs{CY>92LTAd#P|@3g#r`^isjNBYu>)!-s#`9o${O^z}>HT+t}zx z{dXxPL@I@e@nICg5Q+eakT`vA5fXp`fmDD5AQXWl1m_o4(P~GA1XSk-d>;TorCfT$ zeee0;kL>Wz+ctU55a8ghk&hJ$;jjWhwOYpL@SryBis1CQCB$56@Qt~bia-IH42t07 zxg{iA0wC~wAG>#r0EB^1Myi#{`+xlBK5)xU|Hkc*=L7+M;C27@?e%*7UI_xmJnY>) zVfuAoVWolP^%f*j;3WVFqAh?q=0f4rxkV@dAV8&D#Mnq3pcIU<@<6Tjhi|(7cS<|@ zL$^Vm^#pjuKl!=ahern9t3aR##h%^c&U|+wfivfqp+KQPAPfoYv9VB(6)->_ zKf3^~;6Mi`r11fS0wgFD(Z>oDN)arsw6M}>JEw5(t`P)*4-krCq44T|{+^G%eW!o- zR?4%Q0OJ$Gzga34Z%~9F2m|cdH3mhr_Rh?$pwW&25DhvA1Yk_QCWOo!r`-Z@YIX%t zBA@_JMzL>dR7Vqppj;}x_x1OG^u;^+!?#eLwFG#@KY7ph*K5@`DncPaaoxUMV2l9( z8tn*Y=GU}o2hEs#3PorNy0R7_^#v56kdolU%rc||KnRM(5K|LFI!VM>P%DQYfBnyX zVsu9vV2k8gNr3PC!FNv72de*#Fb|3lOid1>Qr5A%kigN?ivWmA@JIlXSfJsogbfER zF%(jAv?7jEbE}3p1jB-S9pM1!`p+Q!^cOFMQ% z11_wWZ@hj5of}X#3&o5Pq?*U;@AlScq9AP1VA_TDzIC6GLn|jmdTTQk(0AZs5P)MRH z1Jb0B{X6Q8K{R=e&8+A_BSH`cKK4xx0fYjSkz&#N(2u|OGy8YCZ7x@yH3Yc(hkvnL ztJFTmnC~l44A!f9pob7Z5j=Bh39(SA!)N<+M*@~11%Xl&!jgs9@d4uU0;y#nh`7SB zbE}RR1GN&yhpSMapp*?zpZwt~-~7MBoo=Jck!KA7N~Pj&28HlCMJc?%$94NAoM%6? z*uY9N(m)dmNto7jLNX*s&Al+-E0wVbnQ$?^o;ZhbwUuCIu>oCA1Wb+9Q7sps2m$rj z^Cl_4_8^dki2H%}#>jGpo*gyNxgnKFOMO z`<}4?S-{y|VgYVAg9X=4|A_HqNr1J$Bq|$$H zeF3Qmj-6XYlt_ROc%FyrriUDXnD77k55MPuJ9fIwE=8_s0=)P~e~kuV@L}q+0YxZE z#Uc(&k7^Aj6erHDqS@ibN`;9JSswwNP=f( z)*#HNQMDLi@5BH=CZt56`A7_MNom0)4D1wz^Wke5FK;QMx{)hr!WeGcGlG3nLm)&~^E~v_d32%}QM-dAY9nd4Aw&X7h^zM1;On`71cnf% z9Gnm!2nz_yB@{~)l&TeYe(w}P+dj%v5J~7AA%Xo9HH_3kNCj{q@yOA6v?2~E1Y*wr z&BxyPeZP2>o9&Y2nk2wW-uV71q4Z(qc?TF{7^;=<{F|r1C<6h(-#m2&k3Vq~&GmI8 zT!0G+0zGiUC;_7sj8POqAA{8r7^991gb?Tu-yZAlB!r?;ucJOPib5$r>@T(A15l=v zOqp{MH}4%rF<_7Yn(YJ+A6o$D5>g1kx%losc-QxS@v1i7CCar)fH(cl?-N4)F=Y(C z=ix=S??o~65hpR8ID8Uc{_5kK{wzWWs^tO(szp>vp#vV!7QtRaGS?SMWekjsqFNt- zP8KOZ0a7V#31D8Og6~n>x^D=C5-6!Kx6;DlGpmqNf^#_=w{XX&e&KJQ+u|nLGP&jm zaL+&cz&`?^9|oZyjN*9*Coozops}`w<)vl(>4Q&z3wc?e9rzwbhbkDTmRuK+1zzuH zQx7P3et_|*X;cOV4e)?cQg;PPIZIG42e@I^fIbI>BWG7Jv)F`?3Y?1v=T_VIeCd6! z5nIqaTQ1jB6mZW!|8M(2z4wE9Ak4$qKm}!*VDbDpbeau({jn351iqg1LEte8&!Yeo z7M2@WSY8LE(Eb6@U50+u#~vkMghKKJGbfJY?6ITZNen{Z{0IH2eT&sbjG3hdK(#Y@ zV6uj4DbU>xqj!%C6yCROjdq1{%~*hYe&T~32>mi4^gBQ)7$LZ2A^=rdah^Ici^rZh zd(qFy_Vrf73DrJ+R^v;>0F$G2FiKND09h-bV7hq|4G;`XPGMkVRJ#LGK{$UvK}m%h zcMqak^dX_pi8#J-WC3x)!37XW{@Oo!*Y|#6i<|9=<(jbognI8L)cX!F51n=gyNZzm ze`URe$B)ch@b4KV@H`6NW4SHx83Oa_dsH6>9=MR0U08#V(ybUmw8kR4iU?ywPb-7M z;>;PGdipSuD2B2QB+=wjgy5N(RU}-3fFKM!+&HCu2toj#u>bn%_x#C?+uo3uDA$Aq zcj$442zWF1AVBUv88C^U-gLwHoZaFyV-v8#)XZwQBC~;aI z1`J`~p%5?>0#9$DhcNIE2C3%fEP}@n_#QluVRgL&0;T)@Oc?3*DFr3wm{AZVasJpd zSY6VUM=+KEgkZg$;N)CG-y|W}Gg`q=CDh3#N^bE8{lHae*iFig6M#_mJ4%t==T98R z+WaiO{kHusOgq20hGVDabJtT!5Cjax&_}7@qg)D5E`}%<1C&eVwHTmW2v92cC>8W^ zv7oOn1Ret4ZkiCxBWSiFXZEQj0;8ZzTM}kosX6a4f~7Mjv2<=mTMcJj2+l3Hu(%%C z6eDifJ&1zu!PFo8!&klgv;X31xAR7_qg}wue(K|IkidUE`^;fP%?572aT?d{83O=B z3CHIjd;*=A>l-CN;4yecdp)0N@18NOxr~@Z5Y1%)b>4?K$IgPHY)qf|`L#*`#n1=f z@;;E}0x5OaC!`4jrGgL=TuON58g}hFpv%gnf|L@35ZtnF2nC-)N`sHerg+N6%&*Ptp$sXhIHs$MXm+Lt-A(85!vmm#Et5aLuI#GRl1r5{+l zT5aA>c7y;gd+P%Mq3kP5XHVa`cDFRPt z1_PhM^O)9t6XQFRZ5_gl!$(Z{7qM0V*>tXgP+DuH&?Kl;i_ZCz(pUf!>PZ8zkkSAt zA%%pH3WfSGhIZ{X1W}MmVyIHU^}A~NoKkr5)H2R4G$Dk5kn&HsP=Dw1zx@3>W`=iU zG>}sE3+qb@cP^eg0|0pb&3hgAjaGz54xa&|#P!vMfWbH6TVQ~medNx(n} zbYE@=Af-aH6C;W_xDeo6f(vcReWuCedlbIUz$k^3629j_DFs5b#UM&S5d}gOAfy>R z)QKQL00@Ql>H?NMANA335USr;SnHr%@Gw4D0)fH}Qv+CSL|AD`D3HHR2!0O0&+h~d zzz$e|zx9qk{H{)O{r{YL@-d>6!oFR@c>XPW0cfrK;@6(Sdb5MTV+cb{fY1OR2A($k zK85djPWyevoTFyDfKJ4*+UTI!j?j)0m(VE`ix?gmMx|N-VIG+0fifTMC`PN<24swo zDwLQ2q|kjoOsHqO0I5<7V0-}~6u8vnDvwQ}IyjPA0x5CpzG0LL9;B3Lb~wKJ)OkdS zgp^83sb2Z{Uw-xfxvD@IxvB*CuAlzHK_174k3as%m`Dx@#Yw<0XBww&{*ltNf2V9(xZjEs%I4~vKr ziFT~P1B}CGd;{AWJ|o~!8>F)am9)`mcU<4^NFWrrk|_a%fDjU$gkxy;btsgpkW#8HXb>)sNa`rUP|?CU-*^pzi_r|8|12nfl~0doqqa>F_9zyfEV1h z&w;^YkfBUD|)x-n1iax-- zKlKOI^CynIJ!VK`7@_Jvc9tH1HfRdgq!wHxc448d>ZXIc6s_R^by!>p6??J zS!xLa-(~o?5I8ftf+*oG(^o2nc>eQm!!5U5k1+7C*yLF2NKmJ#nT^_PA92StV-a-D zKs%C@076kD!5~KW97MHNbykD2bj%?8IjXacwZ(ZCff7P+X1UdG*b--TV1dOhIts|lh+YrSVdqM40lQoS@y{o0J5 zN%5IcLaO%(!Fobb7}<@X(GmUG<}*BIUkQBkIqk(6#GQ5~Bn6&2y^5IYDyCBCXyP+MeHNF6U2jB(Se`Sy?j` zzzrm|IlxP!7@eBdSxGM)FCb=Q(T1qa`5Dm6V3d#wPo7@Z^9=!v4HPjwTmb+OkO_eN z{?)(*xC#WAzV{u)_0^Rhu*WxMO#fp?&O!)@(B$NO^P1g!*Z*rP5GN8#t1S}_TC-ok zU3WbX)k+Z_qnKTdAr*1p_L%-I^KUbVHk;^%gKg=U9Z|IZOuAW0f)=e{e0s0eeB&V6 zOr;G|DeoX|HgZ;At(oBD?7ACEx?y?%m7;I!6~FssZ++mk+l>uy6$lU(i$5bc4*+0l zd;rx_0RYhI#5i?s8I+o=p2rY))FotcqjpRm6QUP=a`C?j3GcY$X4GqC_>5wHog)%3 zCf{ydrr(->mIB|c`B`!R*#SS|JV#}^h*eR*=+v~11kAm8T_lKC&pRt%{fblb4XiX` zfDlkdaO3nqT9g9(`;dx$9 ziq|uQ@EF6wa#NRVdFgn-zI{^|8LGo)6zxQ!5vdHQY5&}<{Z8v$zn`fka9bJ{5}Inh zt2ZF}JP5F^$`~Ay zm=K~00OP|o4AykHb(C3##ueUl#xG)6K4O=M}al`fdoVL%c zi<~po?U}PI8LWn9_OsgGLx>FE)R6#Y(|vn~)lnZA%X~jgYZ4hnw6^Gc3Nt!^C=ob4 z-*BPg_0t2X&jI}OOMmiXySEddZ6yHnyq~c9H||dz+oPxEAf(je>ZXBQ?Y=qoGg{6l z#d@>juDAL7n-3k-Swcp!(h{cMPm?+x?FKj71<*cV(;m&wRu`qCj7E#IgHGDMBPk4` zQmZ+CAh)RmGHzw+2Q(Q@%{S0V1VDgt!NZ=>3QVU{0YCsMjkP9~%Z^y73pA!fBcVzZ(EYv=rd( ze!pAeQ*EbRnx82?&5S$>38=~#3SpS~AsOofG1Y-o#}@&_atK>Y}HZL z!Hc~Lf^sdBb1=yzrI57rLK7yn0Lz18Gi$E-b`4idGKv7eYhL;<|NA7iLAHee)Azn( zFO<3qfCC=@&d#o&6X}5ytp-vsLeXv~spoA{tb3-%byq+MBB68jdFL$l@ zpTMIS8+3CNeE@IT4y?Az0yy)ZBf#8pdIfZ z_!u0hIn8b-Nc-AMMz6Qe&t&|%$d>l?+554xj5gBz3+=-JD*YO05T@Tr2$DFqt&npD z0pQ$n3*6Kx3|2x^3J&uL06)0ZKy5hzrtf_R18^q*jE~e@I(2So-OcNB8Uv(d_2%!> zv@6hwMXpgPAs87N&|niiY|FDL-+rTf8S~%U*VA5aKNI4)>1A1G4;KL6nSYXV@{(}S zB$7;Kk$U;xoM}HL70xX+)Ak(8S%H7Hbx3L14hwJyKnVaQMpE-{D|`Xu8dT5OL#BaJ zFa#Gl@F^u28Lm5^Vqv`NY;vbx=+~v~&LU*vd)h^Wt}w6P>>`qJ9c>)C9udhtu%^nopXu;q3T;Q4mPX8r(Reret9=e*y{kZX6F zBtpQwYb;5rQ~)#PITl&%&Sc(bR}#q93cAwq4KjUM67*<)ud`=Oy#qe?+w#9hmWDwH z(25mkA*a#QL`v0}sW}gBgHefZEb}fo&rIr3AII9{Axx_D=6I11f1VDRZ0- zm~bv#!&)g73r;H)LEik+H16JLtiK!ltR=`gdD&2JqkcY}XP7tnPKf&tEGVGOY6!;n z!3!?-w0ec5^^U7i9Ih4IJVgMn*c=R-lWikFy;5}Gt*rN%3rUDuZDS+J`3%qvTq=gn zqyuT@uRG`8Z>-;W_dOneAJdP1fajgOoF(Y`ZN0x2@M|MrthWl!tw58Jg|$>+hHJ&_ z1=lVCj05;>05E9k{QzKPt(O4GtfrwmY+a06zOKWY{B15$z(lKbY=Ou6aPd9Di zqc}*!GWfowo2{2@L!XQmrb>bvm|^?W-8m78@Vp6J%-?gpe*T?|BrE#N-l4 z=JSm;QHDT@fEcnVyTlOHB(OkI0zLK10dAeTJm{(D_bw#8@2V`noyE&tVVR(mo7@5g z6hoFJ$iWMK@g>N%5x`Ef27pe)doI)_UFuEd382g|A;itXm1b6M!nqrd_o!aJwO9Lz zYe=PtTiy*4P*&>!fatO=qEx28SDF4?b@lZZ2R-}ydiET+nRha?l%0iFw%ro<8O6X) z$)Z#`jrs~@t9<}RfWX(LD@r)Jr=KYcwn(RDOJQf!Dn|;+togR&0Fp$w^sLV=T`y>fY(<@2#I>FHxC4$U24Xh{M{5V(1}V&_L~b@sIx6 zmdqmB76OF6=R&}g#i`ieVZrA@TLLbnn@cC9X1$%3truoch~n4*mapvTb^2H~}dRR|bt*8rt_K<&8%TsD-g zCcq#7cwS0QvBA22R*cM3Yl&~2osfACN^O#z7nZFiKpRNc)dA8RdAG=n1>iz} z3$x~q(1hSzcLk-;Hx@v@x3s*DMzaG#DSWDODc+t!kM8WAP3~mk`>gis8{XtE&4TMr zsjLF0TJM0(o=;w?*|dgDdpocd@|$TCm1;Oxnhu4s;9DO-$;(?Xxl6W%09gw_FVM2e zth~jAK*9wQV*z4r2_V4;LAe+>KY&Y#qbFuegA*{4Z-;)PdRb@B+IwgA`;0i@R>B54iS!ms>zD z*zO&b`Kyd}r<&ho1(4z?GujWOm!h{KM2`<}?OFgKC89(pf5N~+_o56|TsI)4BS4Y} zBwQjfU?)O=0H~Hj-Bn24Rs8u^AJspAD0t{?i}Ya-nS6dfRaeMqb=vD@-|MHveZb5p zXJ4>$fcIW!8%1{^Qco6D3V=kT*~xVUN~vXRfozWt0HE25+(|nfyq`$p-6vckPBaOk zgd>hQ;)Ekkv{k583Q!8#5=ey;r)F{D^c)m`fb`}XfDN3yj963bjx&9+eLt(kS%)sK z`57>=5#Y*c`NqVN*#lH90TgsSg?mqOD0%E6SZqqRngEWK>#cMEsC*Fu*cG?(RzL%9 zErC=DzzXf^Z<$+*m*DBK31mp{S$bheSG}EkG-=$>n~BZngCD0v~8;$yOS5P06^XfL>BzS zY_YZoNh07eg5iO(a~jXhF5%Dq;$Z*;K1q;=bz}Ukr=8Ve^Z}{IRA0!8?_<(s<~n<8 zx;s*7Nb1HMlm>&C2i6lE0s}fn(r(A5E0R!39ljJ$E+kt@fHPnGl@$O-0OU%*vbBNz z#ICz5gmh-#f*vOvaV*^VLg=AdE|`!};-N>L#>0;v1tma8`j0v~lb$u(JzCtSf4)FJ zFLNO0uiXf2doF#?jxCtXBqXTPL>Q?SK#VzBZ$-{B7%TDArDC&@Y{x2LUjuOc!b&>9 z!Oji1kO(P3B?0FIojB3c#28O&_gFe_V4zw6B?OID1Q6g)zw{7>2ddaTH3q3T68p2d z`eB7rVED*&BqDVV~Q6dn<939ggM6p1_a1s30F}U)W2x=34f-#y$*6_PN%X8I{#)mD6xyYYGhd)P|*c z-6>Oul3EK2xTR$QU~X|8dMw-CnEc#Ufwdh3a7&!;_+*j}y zUwsTAVleQ&fLjn{mKd@NY}p06v>LCifi>YkS}8=7)qI0mZ3JX|m5>I63urQnuf??fD( zSX+W9<|z>(a~$jUIhPn7C}VuM0;Pa2fAvv(@_&9Aacd18(Il{Shq=n5ZVm5Cf|MjM zU5VCWmGB}Lm1p%W))$1xbAm>R30 zRxaSk@w52Q|M(L;@%UjBs16Zr>Dk3ekg*WbYP?3xdgd1R^u^9zQb>CP)kOl8sVXKJ z%OIWKvVWqky9A=OztxG1=~qxnoLqbAvriL3e6rCf+a=0YS^&>%e(U^W57=(th*=N< z0QOCdTySh4E9R-2;6kEpNYH9q0wid65_BStb`%?8aI_=sAXdsD_Dl?-S_<)(UwaG> z{LvTCT$)3`1eBCHBG?>+J66g)9mJYm^U&}ONMQT>T&zpQ-$k5TNO z9@+HgS`s8YB|*ExHTW?{+gOH9#}K0vrxqhl5CjanMyl|Lz~{d36@2bfM^6`!B}!@`tUsZ&_7er76y}!J(QKLgzm!mtFSlm? z`XlXj+qJW4_bZYs{s2M>6Z7ZTNF0HAd2Z(_;eT8PQv3X~L(O5*tG`AqUhLdA{W zYcHRU<2bf$tNy-AXtW|!N+F72 zfLpJd!Z)8jv+3^YmE%2Y}LC?S}g znZb$UCowcUgo&vs6sm(745jBKXb$8p0Nno>G*uefrR}0$DYPGoh; zv!+DU_+zh7`w;+m`0#1>RIuyzjN;7fGL}}G8+>f9XopQe#R^hL-IpsJYJ5065<|3qvAG>9)1#G7@{&Xipt;!DD|L}G(g+5VV(pq z`!-vbjsJ5~2MBWW5DcmyW&?nm_6#BLD3lavwmW#{)cKtD^Um5Q*5^+&I-L&95I|kE?1O`iL{UULosMmH3%n)5WyzIT0O#*ywj2SPXC69Vp1AEper5Dc z0PxMH&R}AshGH0?S}NdqH%#N2F#i@C^ZzxDwHmk6W;_dFy*BJd=*N8xh`j|sD? zm~hV)%B~o~?P_4mJh*`;kYWW(>&uXYf`vsCDs>bpHT^R!3yke2HaSF;d0JT44QIki z-M2_UN{rSEIIwF#&ry>K$4<{tai>nQsoLPia63TRONps<2 zOUM4|@gzw|9LHpReH|BR@2g6HO^N8N#_h!;zfl^y>6ZZD{K6WJotejhX+4qnmg{zb z3yCAA<}Ua-8-BjjlPI|`HH5y0M0g0eFprkwX0>q&#uykOU`&{&jp^qGQ=+GqyGmkE zP@SZQo=lh~eBxvkt@TyN*|VUY53f*0p$K10BS20szGl|+1x zfN>8V6JUw80wljGD6s=McAcWMiibc!C;%~sY__mGKL??Jpin}gTtTr~17*H>BB{+d zN+=;PSPgLfuDb3$gur^UgKr!@17Rk^=qH#ajpd_%_3z?NlXp5D6-ALs^pn;$YU9h6 ztssEf%(i~w!QZG~|6L!n$&0T)b^;!wZXwA#Z{7n!367sxxcnPYdSQi_$M6%1*kkZ{ z=J^9mKae6}48ErqoAAsEo-L_J2;0mSC>8xOBVF^+x1f>$n z^+6OWH3&tZq{N=F3Z{ljI^iS)*4r^2K70m(b4Y3WeZku+$G`sWwewG&isM*v&gI(L znk{|aXhY`plH{@<9GS`f`J3H_Z+p$(Ees!gi~S5r3GV*3gBZ^Y6`!43!9$OqM8f;j z_+5rDO3X6{m?MDaGx#)pCV^eG+;#!B@Ptx5&D@4}b`c4sbZ4tF14vS){k$D#unS;R z2M2N2Z3i(tSb-8c9lhFU;gP4#pwo#UIfvj0B#+~j6A!(2?#P4R;G9RTRx4^W8WHC_ z29R8AQ%YTOqg@FBZ1QK*1OVWV-uaK;5Y#4rI0cB}?mG@*EK@bq>csf^V<)h%oK_WG zsaU5^KbFQmc2+-m-1E|B58D+>VWI?^!6SsYwFF%KK-YJ;S^}B&{X_LK4jr6C=zE}y zp-?JesS)Auu~{TMflLxe!NGYfR!=_ss|!cI{9vckiIOCVS65d%n*|?0d^yRWDu*ZYr5l3WtxM$Km7WFDX}WA+e!f`owWFr_j^R zVRmw9Zh=Zh0F}-yly*44%{@RmegNGq6apVN>>I<#VAT;rNQp!HIKXS)E zd|gIR~i^TBg{Pge* zd;xE>`{iA?AFS@Z`=7F~a$ELuCr9fzbYQZlq&(&VXU{KVW`0Fi9Bs0DaMQ*!8t%+} z_f+zprQ=cyVt%vx+|j`brYDCmGEf1K9ZCjf7uWFEGiT9kN9mBWc?}ppyz=M+zsTcO zE720Kt*u2u2;1jp0pDmpQA$O)0@;$s$M@+9`t5h0%bg#%;d@?E96$6r%7W4CXHSgO zaOgn#py{k|Au+eOhI8|)I5)rQ<|FpGzx?truAhe^fHm!QHPP&hV(SY4fbV%29xP*I zpn}n%3PL~423p{k*PD3i_$=lY*PKsatBnXH32UDEz11Us_Gg^)IIH=b^NqmXhya&u z`d!;>1pxp@f{j4$_u9ShQNL6gy7^T<7z|8*KOr{tq4gXT#<)K+aotk3_!UUpi&G`DupN) z1GjE)-+Y?_n(Y`T&n@88xn(2?&&(MxwFOea_i?&&>MwsYT0DA&a~>O$pBR(h5BQDt z?dz6+4{R|3fS&H)VqkZl&s7ywrti9;IDY8metG0YgnB)7<$=#IF;d5_u>p(?RX40R zOhYyeWG>YU_#itrVhKDI)NgGwI#V9Lak1W-!MZ6{!@O?HH|gg!N_z^D7N3ueKcEKpu3Z z0OqrZBRO2IvG)IFEb%M1&fJU|q4G}{qcoj6^p zwVz~2KpBe}m8bd2vCp|v0ib?)v|ieE`>lRycz<0E3kS#guSBL0|B3PkbERNo|FQ`rIFV!b*^a|Aj zLeX)l5Ol}&+3?O1ftU#%e&9>r_aer~Duq~3ygeh@E5|zvPd^?n9-otX;XY|VcUv6C zTnND%jRxnOXTjcx0R60ijq$e~oU29v_l0@?V6(t)3VfucQr)iv0HJ=-4@>pX3rhtr zDEiC`1LhY3<_7`w!hkZ*N77iLo%uN#HzK5^*9w3ngeW0|eHBtlA%u`Z3-5F~f^#l1 zz)SQJ;6g;WYQS5$>I86Kth2ySrB!Y8p!L)^%+2f5!kTvz&(#Ky+c8mb-2GZ){ED?J8du$E(wreK< z;CUV~8|*R6C~qI|73W-X&SjD$GD#9!q#?VuZW448z#hlYqj)X6jD;Y9myMs#s zKMQ;}3DC8Vu6+dnzVBNSs3b|cD{o}izQpE9;2gZIO5$vVTvG(-7Sm1ivj!Iee^c7u zXJ7T%->3wu8~A=Cuz*Vda;G(YBe|vt&@EjS;zC`(W(crBa<}JoGbHHNcy+A;-Yw56 z0&FB1Vq~pBmJGm!&A*jyE085Zw}$KCglBmJJ|_sUS?RN=f<5kOgl9Xjo0V@V0{nmJ amH!8JLiw(f=*ETs0000UYaMD9{L?&?lf?p~&@mVlR+7mMu|dpC1aXG<0*SL>`ZL1F-a0CJLI8s6C_9bVa% zx|*!7hJJZ&=HdGMp4kPoY!ZoVY}oLDC6lH931Ynn;Yvw~N#Y^}O6tSHFq~gX%#{9k zknE<2LKL|TzG4G7HU_)Hax-#sbI;1rQ?@(qSKRIbgHHU{^_kpT6xPG;c3$yE3owoo)-VsW`Wl@dTU~EgXKH zhlcZ!QBbn=+x)26zc+sAe|zn%>a4LheE*zir*!S%Ebs8JI*`#E_IewuX3nl7=z2(- ztkU83#c;SD2Kt#v)yF1(za239&OH+RUZ^DOOcLs37VX1@Fc*uWm*%ys>9e(SFN#Uhq|m-hF9 ztcZLu*kAN!-goV(-1y(izSHfC(bxZV`;j$h_1u5p!sJ{T6AKZ*DkyCWDR?zH$srNL zbx9A%Zt(t5b6lM#@PP0~KVW@6^MB0`-v67(LZFF2SR3G{1putAD05eTM9DBSivH?o zQ#?$!T~hgQ@1Hd~|FV|P{LvgQPmR_8*B7Mi{^VDM0RyVQF@{1s8f6UM419iDMeJrz zW(-_9a!ecJuC>tpVoRre#rd1wH&Vc%{@;Op(EdKcv#*hC5gI=h{FCp*s$reR;6Lez zCzYyH5780P*opP&+i8*K&t3Nc`cIhii6?*w&aR*F<#9`F0(v{C-XoG|8KJrRPh&hb zvYuzB`3KA`aac+;^nRQCn)8mDz-zzi^?HQX2nz%t>toC6W9IL(%gzLRWS7Y=AAj#W zBo00NjMH13WsT?aOu$%X!ZHt|+Y<`tm{soFp+-Q_zhs*K_TJ_9oz$$m_R_g!kM4CI z4h@Oc1NU@*OP|?7T!$DINO8q7r-E6Mpo3}*_kFlq6S#aGem~dz|DEd&bN`h)Xs7sVa%#L=LL=isBQg`0Duh*3xjpGv%(rJ3U6wnvD!-&WGJe9T{r-c z!*z0cq4{$!%QZgRZFxSedB=K5q<}%uO1t$*b+vpeIZ&h#BP=Wc<%`$Uak0e-aIyIUUD4$jVF<;(4PoHLz{Mt@g%H9- z1xFRA&?$J{CyV?FPw<@`dT&mp^Y(fDYUgsYrv+5oF0w^}V;kQ7 zLM?Wbt&)jE@`)XVLc>(Ov!xUTCP0XRjt1Tg)o}L;#proX4 zNm3I~gee1u^B@oS(M0wT*&Zt=0(M?S04 zrvXz?Q`DyFqYp+esrP};bAq?C^q+vAz+#K_&Hb})TlIa}_=G5H`D{IdsC;XdT+E@m ze5)qg$#3v2l`PT-Yn7>~kK#0B!R`GHg7*c;fMB;V`;OVJhfChq%bnf8y$a3uppkp! z$okTEPD=LUB}&c8@^QGBvR@h@CB%L=4V#Zg2!*1k`3#oguU;ThNa8O+ETwEC(Zb0o zZPndEy5H5&6qxJ*M&h_qF989wT>*Dr*MGO{p&1yy!|*!s9h9R7I}6=@{M+(n9%Mn| zXR7Ja=zCtSo>&#PlKMf#56}`3Y)3LN=*2@cf04L2v5aV#U2$wufsuz1DPfv?c&Xs^ zUxZhi&qlvBX+8mbGOed$c?BJ91HuMnT2GZJ2&VoHh{a};x`BZGv0`?%uG-M~B=I(q zs6uFWj*NW%;*j&d9YNq~2>b_z`^MeJFf#fb)=!h#5PCVpQkIC9D@U~Mu-Xv9vlBssX6RybP49b^Ye2dhS4jpo5%k(ED_oya#H1S z&gm`{!nQCUx0YKi%Ku#}qHx-chc9*R=o7L9LyrQ4DZfERUl@XBraWk{BH>@`6yobMKh1kS%YFtu1`$jeoU#xiKvKO4f0J;=pl?;m8<1($arBZn7AbL#pIeC;8O6aT%y>i>Kqe+S{WQfptxn@D-tw z0K!Vf*A(UZt=#^|jbMw=uOH#WLkuqee$|m#qG+M ztA}rhv#v-uQ~@z}-YI&K38OkgFl@uKikJ?9!Eyml`4^164*P(5|5pMvB$A9=nUX`C z{4a&26=9pfUyeOQQNzXEpGW5t-p+Odt+n5CGK6;g@ilb(sOGk_uZS4LZT9EZZ=jLZ zx##Fo{i}{sk*U?d-mT$)am58bBFi=vn0P1ibNVk3Or|uY^G)x66_8LndI4b_39n*@ z7EqX7*~L@2R|Gp@cgonhc@rDu_J!kUg(nVFwa<0m zjMQssz@dV4lueQi7OriOC5^K3n;o#2q7n<`AiuZlU`h$J98c>nNuFt*u=O%=qz26Az)fXAo3|WL^;!woKJ|O`UmSedUoe zNV_-Qf$kG9;W~iPacbGIy2_ejq-akfwa{fx7{~;PzF3%PKidGeDcdkIN0p^_H#`CA zspPezbOzUmr7J97dku=y5n%tkkqgy9waV41`#)$f!krLTFY1W$<^^u;$ZjjM-nX@X zdj~)8CFpSZ&O@_p{SQ@&7T1u^%bKcWoqYlZrWL_FdqZQjy^mr4Kq(6ZpRU0c#v&y* zRvZSO4rGJ+!=R0VK$)#kZDTN6YG0R2lbHBG+E|(T=eimDYY(~4^MH{@hVdaD)x03^ zMa|~+!-pe@s#37XPlsj>5@F#|yxc{?p62`UQl3)*l%Yk`1f=ZQ z^yun9(YIJywE@n3YD66)CsR95v_Jze(h2FYL>Z>fp{jT*=Gz$~`s?U)%NetiaqAvP zC1%_Bv|KBfUJNrcDD5-cNe&?t&B{Y-uVBDB=_uC#5F8tYaGg5K6Yt%Ica3j9^T1ei zgcj%tryJ}IdLu!dT?5fjD$tfQbiQRenY`^lPc>@jRijf53t0soE|+!#yOs^BZ2|x7 zpy#T6t@2&oMOC8rrV;BS&swB) zMY4)SJdlxTI_;h*r~+4}`8x^cvrQ3^fLYyae=^Z43qiz6@VqBDsA)cPbW(1$ z0$s$tosu@m7vF%4qKL3sg1672K^lz3(A3H*TJMvaZ~rkRZ@Nj{K6S^W-Z?ZZ}?>L?RDLRI=wx#7>#*#^di88aQk?GZqCRlr$Y6tLkeShLQ zR4sVyDrSL|{d?jQqQv&N(360nAU((mvuF3cBZ)dbSFg{GD*O@^B2E$GyeipsYw!)R z*S5BMD?#|Ji9D+LfYaeaYYA|XP41&jA{CAyovkfaE`t|k?wybD=Vg@#JrUB#5iK#W z*YumQ7PBrf!Cv01sy%I0OdoRuf|v@gD|`<6eeUEyp9mu1ug$Q3zJ(XZ<^V91nF~_n zYQSg*RjIZ1KV+;E04T-QKO!6nq4Twv^V*!9%z8lb->D156FeLIU%s%9$=iqso)~R0 z^lB10Bmz8j3fkKBT;{$xe__tcb)nP^7<&)zVJ^0K^SWyzS>>4ZN_%<_j*zbD!hgYU_5 z#R^l(PQ}He9I8@ES^_SZ*FDzvI!+m7s*wP}`++siLB6)M2AdJO&Ld53=rQf{Y#45@ zC@j}^RAZlmk73C+)$^*x#!}{vc!M0k#CQGgr6&}KBdDyg>=h4emLmctJ))9PC~(OY zs|V$?DrZLm+YPWV=knVGMZ_iq|Ks-`(NB&foQm@qCuayt>~HbUggCw`$fDs#GbxhG z>mMt(9dF!Mec7__qTE@^7wvsZj!GzdZHW2grTKY*cMU;qga!{3;fJAY?t27_Z*4s( zuQwPLh6%Q%SyK8SMG_uG70(|NPH4DGi42D>8^SgX0kJVjeJkYqh(`GFn-z{+L-cMq ze{UbQU1;dR(J9RKDFrc>`xtrcjx57OnHV9`N01zLWNDi7@LFK6fl%Smf_iu7q>&&; zhs7hV`6MW&A_#pW^H{=p)(^V>+WY2@$L@<^jvDKNN#)#sS*hG5>-Z~5zD^P=!MuOGvl2#`myKgx1dr^=74pVyN!B{F*CvcmWT3X6Y#M4 zq^QzY1)|EvEvElUjifj6eSsVFBGxW46sI@%Hk;KQlZ;z?ct)bsueE zA5}2Qat4q4Z-Odkv(k2q7>dCW2&kBY{aAp2FVHXu=+xzU!W8XJND|Jxq(tnz;hh&0a9QG62LO85fpKn*BIzOn78 zLV;W2c$Fsg{){BZ)dWjXj249y7JNoYgV3fRfrN>ORr<-!Fns;z zk2KftK~0(9-20>ZxR3)QFj`M@mgxbb+)0&G;uPG^I4hLA*WWL8|NXkg#oxK_aHngZ zq5XXdoIIHr^V+Riwv9%Kl(rX&(<~nHb-t3X2D}hvX2b3$p(J`R%#4WI9)rUqowTtK zXzW0a*y3Aj1ADcNnWFLqU`q;%%&D)!o)^o)E^Dm&$lofEfpNdvJe>C`T~ucuU01Sb zz$RC&j|rhFOVvZo+a;pXD0ANR<1#~AyT`+`yY2%Ns_xz(JfSaD)nXaF$qGs6rDol8 zf72~cPCMN|6E}*`kCgBkpADTYqA|tnC@Io;T2eXdO62O{&`m2)!iCTJj=)9AW5m_r z?$9;$HT^1;xb^gNg$Gkrb%nY6ZYC(HD?0&egoF23ZDVHg58u;;1m@Mo7?O&W-rYF(sFOS35{TrC0ir3Vuw75P`5(QW#38 zHJ_!Uqw6(FBM4zb6O<-~#JU^iz77kqu|>=+A(Q!S1QyAGf!uovCqtnuJFe2m^{Goy zr()=_v>%tkM?%oIgTIG%w-`LteEVT)lBB^SM|dq&3U_X zGQTnr+bifast|*;SI6I~lHdG82&CC$BiLF6fBmftvIzX~jgBf@voZDPz+w=vZ+XgT z4?P#+vldO=7!18H=xna8>{|alhQgy~8xdK8PvM3kEUeO_Qssa#l`<>xPpIq8OQeoy z-LCDgCJ11A6^W|*&_kF?u;+3$Dbr1~nbC5Fc6|(-`B@NuWxS0qy)0A0WyhtAHzO&~ z*`ULtOh(3v=|hxyY0-#PnF#s{7ylkrDfXhMA{M;C7`S$Ah2nFcog^>g-^le-o%E9I2HTivZQpBmeM!GYKN# z+is4{199}!*xo@rm}{%h?if155(mRX9o@kJf2f+Z2&YJ`hlEV)BVMaR9OW3OYxR^b z$Rn+jO2pxM%RL`uu_mX_1s4|aH?-7H!e)M+3-Lt#{9(z z?K#E}jcvqx_zj#nHE5S7grV&3ZcMR?#dNbaZEfzfaJ>|GXdKOMvb?)K=Sex--SD`= zhAoZC`540oZ#SEdA-ZrUvvemy6IzJ7Uc@Ce(MgK~?#m5~JzKL+AP`0jEBE{9aSq6{ z&yO<9L@STZyR2_t|001vARLcnrI&A4h6os2zdtBAQni|aoCn5N`$SL3#oPz}-Qeyi zlOW{V@pc{FWvRygiKu=HJjWstX)>92+)Y7OLnAd+xb*1HzNIbUWC^CdI0oFA%k$1Y zg3BhWNXBW3E1`8Ool;(E43Y3SKoCI;}$@jl~j4W=^UQR14<7A^I{*p_4(fAH-aRH?ZWO-}+9 zS7%ReG>WyuGgEtzeEJt99^(^k;aVixk+C^>C?!NjpC3SQmvEckhTZeT4OXMT2)yr6 z4w$C?_bW}YS{B23;8^5If%49Y@w#F1uMBBvSKvKcuKynk70-^Ojp@8DWBTM0MjFi9 z=_EllqFg#P&}GkbFQXc(+@n4U76 z+LA^<_Y0aL7DwZbntqV^CTD}{*&M3yO-fn%UqU~czO1H34|^5@;DdLsGd2EebnE-TI1?iicUjjsrtCJz9XZG zIaaG$e266uJmd3!9~KsT2PQzB3^5)Ks(^@sXd0qhpzmV^7FRD)KuoriASBLftP`av zE~mynqHH-K6)h$#)3XTZXx)W>y}qa{(TuQ)9{-B!<-ga``3beM8q&Ju8^+!rSkv0< zx;pa*LvPnQ_99mQ9&QK&z#tWhYwgoiL?A@QlE9Uce=NMq7Mibv+AdhZn4bOYl9Ty% z2M_(Mzn3M1iCD!njZBqTLc~EoX@_JP$M1t1OCOFyg569Hj`~PQzgF#HaZxM;5<^BB zBMM7|AUPLMVwj$Vvt!B$g(66pyTD(c^r68!icuL?yVGwxQe{xD&pO;*&^78O9X z>V30Qe?+6ALu-D!UOi)mS zH7)EtaH$*748!^k+~-%!IhCnfmElaNN4s3-3X^iY)0wBEP2FsxYJG;HnwP2Ze|uiW zN!#&8?7%|_Z=ioo@>PY*d0R_qU8hwG#;wPukFp46@@QDM$jfu6P7SR|=3Vcv5QeR! zr{kvCIz0B`1m1IJ^SQIIp>DMX8=oLFxPU^VjUj3q z%mGrQo)NOngqNdGb(%@yA~w;5fm;NpF^e*m9mC=GgIKPqJZe}siJNt+Yv^Oq8|`tP zRZdTvxJCdOS(AAa0#7te(w~r|k3_F86ZBl)Mi8#cldm%J9;*n1oOM&oaCTyV?)S*% z#E12AeX|+qWSq@!*)o7Nz`GuZGt?)p@+yKs`e(9?U-^-$##0Lv%&8dKe)r6RZ(_1? z4gQr8y!DDq0@T*e^{#9Y6GvZ5^BB?xcfJxwL`44YI@ENVWbB&5UNU&ksrgRpI@!Qx z-+r*NhI|#6>KCYILSgvLv6`?5-(`f!#Dob|ut@wJTsSGu?!P)pmnvU<)(t<_J=$jf zL-T7nx~O&|a}D*w(4W#ItFdQ9`Yx7m|2>h;ZK<4!#g;MyEOx;BrapYS6ugT7b^7V! zm35Vut>14xVBN#tFhsta{I0WvL%}Xta9c@3k1__D@ns5$6{z3U%uLJT!*@NF$JU%| z8K@^jRQ|jk-xs#b!2q&wwQ9`?r0LO?G14OzyI>z2MZ2^r>kww-RNCGu2;}` z+WuTs6LDO<{8cYVQ60ymHP@^-a1WiAqh%)u`+B4#VyQ$8q(K=ycLYw|vr24KV}(y_ zo*MFjHI4Mf`mMrZIoqHB^}01T@oyQ$C6Z5;i>{^|_Y=nJI{Q}V?xUic)3RJqM+NWb1eoI z6_*}%X(~=7Pk43q(Z7c0e?!b&@nQqSCFtGUUs84KL2`8%O@{O4Cf+&&Y$GoUPjg(G z7sQj{^CmV`Bc=hCSkU>f%w!K^+ahlZ-$kBMo_pu*YT*aJIqx9jk(i+Sar$pUp5 z^`}DJajd@2Q%6&3302>2lSc zHu(AV_Sw;YM8ryW_JSiI8>PNj!$mxZNQaL)F?N6aL)J4}<33Y!r zdKzolOL>dvpmH4Q`nA?OPo7!NEP(O|nb!##bBx3*{I{YPh~JB-+RNVZGeZ5kqCC1T zjh$oo`R=@*Gxri#gOOU&Afp@SD-yAxYJbI2-n4T@Gx!s(p+UTOxztVh9YxeZc&d%+c0}|-j5#cR`{q3 z4WP^KFRnR`r9O;FfX}1Ht8Z0lNLCFw#*i$%%l&+UsAxGvtK}rtytLR&R7>jqU))*9lr3O-9{U{43z<4h$f`K}N z1dBVzQ?VHQl51qqhzpq3u}s81*B<{0h(bv^McCjycPtiUOcpuWlqlhGo4o(%~hz8v_Y&k+Sm8}N4CS*B`?q41*qCB2cE zB~I|-0Eww;Mssa#ilmFY`VKyuB3^SkQhb7UUOTWD_gz4$YtH>S#Et@%D$Tu0rZsc7 zP?;yhAuj8k5eG-dF$tgqb(-yGYQT;%`YY#W83PwLMs{D1U*Hmpz;2_I()UJ@XJ_S( z{iE%fU%Ubq_1ne_H!}lsTB++BH3?{UW)PWxy|lTDjim1o>A9FJtI@i3NA@e?PWEl0b}LMpAJh@* zZj3*8^(l6vN%FsR+)AUO-BmDQ(H0RoyOzyb-7w)Up`u~f70WoIiO})f+yDAfJ+9yvb3VxoF4>=FYnz#vS9|Nt-b@XEF%1>X zCkX3m-`*GYPnESfw$KNgyybNLdl~iZ7+=5ZrEOHtV_o;++Nj5xui|U1*)b~rp0xWM z9Jvt|<(8dkEXFBU)wfTUPbvNLu!Ppc!9liEHs!Y>YcmjrJVKx5XshnMm&v|=s1#rN zEJ2^SH`&b61N^@7@My<~*!?A{@u6?xBC&%uEhPjlr0kgOT$M{JiDwS{83Q9MGM@Fn z$tlH(Cg1sTDFcy7`<6qUtwo6M;-1~$-rr_eDDD>T%k8T9%fYAzrjLU!dc`psW+5bq z+QX?mV;>ZTn^&hWBJ(XXgi6BN0`$jswMa#L&-$57tQj z%TsxzsS?OL#ZsG1T+~E_L9ZPWcYg|oa*DfcT8IXXP)BLt z#6KsDRUG@It#hu#<;tJ@!liqR`xJOq&8h5PD^-6Ausw$G`uj1;-DP@E{>E_&cA|kj z6Y&RIX~6EatZw$hIIw+5FtYZ{pt3F2(JB8VWkN$;o@{Ah0r=f3(D`;FW`j!8)VcIH ztXJc}IWj=@@C|HTFdB3cK8$B+`>jc_jx=1?U>tM4e_~!6g)ZUr`b3rT8dbLeUDde0 z%s?W18fY$gp@ zPuMg&{NWmYK|^%-5XgGa0Adby>C>g^2V}EzjnUL1Qjysr%-H)Cd5xTGF|5OGCCyqX zz|>#1pR5NT9zaf|Gu8_@BMq-sS-V$=)kNr%vd!V;1+rg{iDCk94o**9&ig{3g6DgJ z66TwY!va3+QJ|H}MQ)~Q8qn+0uNcCzl+a{mXkObpRc$&2Z#lw2{}QxsE{9<%a~w;~ z@Kh^*v*L5#DSDdtWi|-!mQ=`GSImLS<2mdDokpp^REtO+cwTij5gFPIv4fPR?98ZT z9+Pjt(3UQI2o9NXDVUY9dC^70&q}>Xi~EKn5Yo*LZgQ$iDp(MSe>be4!;W1&uhL5~ ziUqjLeT6Y#bHM$#Stj*4Jk~&Dt%8=E9pOLNjTYAEErT!zMgZ_G?BZZIkSHTWfI}zc zkJid88=@b#l$lHUGwk4<95(3b#KmP(H7Qw`0nhK62ZMouVdC$a}>iVy< zi555>#ht#k@9`>6raU1#YEH4s#O`=_6nQTA#A9gBBHoM!&=_AtxB%n#EvM_;gvqwW zMCc;IFrppa({fU1sFrqI8RfGx$ma<4sqlxWBUAE*p}#+vD*8pgJFiB04NLjEo-DD` z4M!_E+=f7<-)GkcQ4+670<-cwi@h3Ubc4KiCqSqLTMdFLH*NY3;Fm%irO6Jz?c5e~ zNz5u(4qMo|$Xiz5F_BKd*IUy)IcX9mkI~FTlFPORLHv4*<=~AN8HgwL_8! zSQQH%P##(KJa5kW&$!iGBzyc`(Jzp*F}mLFI1bj9Ce-{N;Cx@rp8W3aa;LBkQy(r% z%lOO9jwmQzYD_zpcMU`=u{DAUs;uVp$3we~$M-K4r4c04?Ij5biI7B1?85)#r;Amx zhRx8*Tuf_h)TuN2Dh5-Up}tp-e~BM0(1IT76lb%)CZ57V(;gU~z0jKQyl>K*U!Oub zq)nVxGTe$+SQ<8e@n%BO>?=NXX<#I?pMyM4$=n8#?2c-elH)0T@TVs;Id@+>PU4%M zm{b;;p{W$LH)C5)uo38u2t4HL&5E+oRt6HJqRfX)6*U*g~0~-_%h^jK-bQvQT3VjNLCCsX~vJvOAAlt26%lnvGoW!^{Kx z46U|^N%gAL%e8?B`M*87BsgW-zHyl1Rg@VV(40^hUwmZ`+rWc{Yz%6BC&@>n*#YOT zYG4dy*2Pg8Klc}Jr`I18fJiOYsSL|90K_=6N(t zc2gf8Ts_={K-Tjw?H(1jvYC&eiNepR-+AY}tgih4eMlAJHulcP@wJG1It%oO`qKZv z{Q(d~0xs>`F6#{e%!$1ATPGU&5+&9p4|s2K=~9W-Of2b1N-M)(F2~X?`cRoZSLy?!%bQ{UTyw zJ`4SmWiA@VJs0Py-Qvi(vt9!NPr)iZb{y{t@0pishj@hiC%%dA|K~dBDQGp&^r+sa zeiumh@A*IjED)$h@x%L8h-DPg?rnIjn8wde`S+{gUI_fR+-l-5z9f8_+iv`rqi&9~ghy8mJ&0>h-(H1e2=p!5NCF zM#+o;Vd({-U3UOQ0FLd35V)e+(zH1G){596i{xkldy9L&lsSu4A05~^7(XzndGVGm z5ju7fE@?6uaq%veRjL~u@ER(OkpGxyy{e#4(2RyY1GaQP%1?SqrXAQH4}N-(-?@If zYZkV7{;+d<4RK~zO*)PQT%KpX-WdHdvXMjAI`m?fj>i5AiszXARde_s5sR&EXuRD95CpP3^ZyP1o^|?*MYnrUDNCd#Q)1E z`eNf&k!aUi)AFHD{KN+{KX)*@e%~(OlkTgMQFqJhTHsJsi)v|{=!Q^@rtejh94+ex z^K(zFPb5&Fc_3}T;B^Cr1zRe5{TCMoJ#6LX7EYr5F0uOfjUAdN_}$gOZczW|6 zZ$#j`ZF5Q-Y})<#&~cs#L-8iuxf2-g-Rr<3<)y2lw9m~;FoGy+ejh3|m+M7nt;B%` z)*K%!LaYYbE1$Yg5w&YJ;WuJ=BXkA-iLgBENZJ&gUP_w0eA<2eq#J~xA&f}kd>u62 zeP8v>51%Bb18VwuC-lfF*fQEa|L)l7qedeMf|x+r*YF=AhR2Rch9@E2-of3Rv7(b3 z%t=-7-P8Y4E;sdR)n%jA8&1_Th^YY*>WNS8k(dkl>6P@bdbY&QrYK-Q6}O;f4rk(9 zDgY}$-(h_#*Md!Le7Ss#EI{um8Y-ak#u?hJYt_&(in0*EZ{N|w(}p3cRSuUs;T>mI z7B`Tky-rhci;qA5JKfYjzkDd-#Aetoq6H7Q`WIvs*cabq+gp6wPB+#PGZBP_i5dV* z*je7e>@y#V2C?$-CQIpdL==lW@`x17woGU3aSG#xb+4qWC4MhAUvT7CZL;CyTaIlUg+s z1D}`&VzI~oM;tS>s(1rnifV@*BHz>=`rkgQ80?;T@ zE$l(N=>STop8l`k(&5H;A>! zbCx71hVJ#>AF52^c}MZEZisf%)jZ^;U~mZQT?&!%28~% zq>JqkeGVS061v`g+?mc3lS(TP2VrL4T06$9@o$?auik?{!2en^K5Fa=b@*vdWyE^z z#KC0J6{2nPrR)sv`;Sz!RY`$#)J?c?pT2X$IlaxQD}eWbv14^JU)vfLx_J6VluS2Tgps!P4DQ ze!U9bal)O<)^dQGGdgE{u3ffjQ;ko(kFZtlc;ZZKic1O?&jdc5n_jut-Lc%NZ@e*m zmu-3566)@=Y3XNTPwT!WBQLXh>-oKdeUYr`ga|fU5fOAzIa~w0&jt@UIC`>i={;Jr=rOd!Snh7=aU`hM3y`U8b{1E&n89s z=&b6Q=R@-;F1W%FG}+cO43m(*aj2y)a~4hm#eAmF4STBK9w-29x>YL`oW+NON7E4p zzzbB%Ev-U#XJbT&*4qB8UaT)wq)b6Nuu#dulmc8%KZRDpmOeD#F7HK&SGgG(5KF63 zV{ebM!uBi~{0iItSYuKOPRYcUJ0*(!jZ#LFpiVBo#3B7yoNUuH1-Z3htQ2IJ@+6;{ z#LoJPu;lt;`YLD3?{i#ah+vCJ#0rVq^4niQL?!DSTRv(bSU33`*f8SA0S{MD$ANPD zQHtJktY1~Gxwhh+?ybA09PEyo`sR+y)M99$wK*B!C%V(B4V#{j{p-{ zYB$h^$nB#QNixbALXhim5f}JE&C=tFYuLtCc?XmRONE{wA2#$LNew4ElaNs4^>Sk6 zZAX!H!j_z=u;-!x6({{MQo`=%aPf&BoZOkZ&IYZ7J}z0&e_3h}7Rs3t;h>qrj3+S8 z(ir(~nHl4=BS&DX2#*g7$h&YRCFtSA+~RN(!cuC_--}Wz+J`iqA4!b_c5aui{`Ck; zRJd}t(;RQ_K7e2sNkpoUoF>Qj_dW9(8vD?BzFghQ={J8*tBpB>`^~}EO{%G^m)n#` z*Gt0k$#FPNXg_k@XR#l3$rZpwIaNA1Ap?#`kqd%>szE!NUIj#{xRc6#C?X_03-o>-m%Cin>F@7x zc>X%(6CDq^oQq2fubG$MJRaq0k53kO{T|5w+Y&Xbg#-vk2W&oC$Ac8CWat|*PIQ&g z9dSRYxTfs05^S5a;hypGcE#p5ryx$ko4*oc7Ekh3nkM zJp|>Jw&1O+l$}m)E;>9duBUz~=(L!U4|RoVd=K*5z2Rs505nUXCZ?fv<}6uPisUB? ze;X`PF))@DB4A-cl%#?1h2moGnw=Djub%9Q%PZtez&K?f(}2y;i|P$u96l?%4_#N} zYcDh=R`-RY`xh&@c@ADAc8vz1>|;%Q(Wt~x<_)^?U7NA`1JC+2jLG- z_-N2=#Bol1-O4~8e4>%p$8+JZ!4yuCQ;xq{pl)H4(z{)nXV;R@I9j%tY$D>1a2z>! zLya;Q*0S100E-QNJPT=-a8*i5Fk)1OL!m&RQ;q*Q3pwlsXmghOA!bcFz@J}c?fd@L zV>c)H$ddWU81PXEE0m+B%u?xcHs&0(Y?CM)DR=+0oQ!~2vpUIOM7hWSEF@J62p#HX zt^2x&mNR#OLu%JVz2#ZC3BxdX&<6!K5J~ySvoh<3YRmv4E-N=t#7J)5-A}jcn&aP5 z-0NH-_D3+tSe$I=ic7nX!WaL%h;lFN8U99y(@pcG2wLlg(eXW8k8138S=U_3drT#J zbK18^Q)y;^uLTZvKx0b5`S14pVO^R;%W@?Mj<`>Vg3hiIC(_tQH5OGSXEy2PI~_JS zRC>f|+WiT;VjfPZ#3)(H>yxl5Wr#Nl;4UfZyQnZDyOYTsHjrtKnf)A+6QpE&eVhZ< zB&zhZluwWdK^ZlO?fAM?m18QQnA*n(I1WcWn*jj13+S)t9pP53Z; z$TOR&i@J1)0%Mbq@6Rnm)wW5O;Jq|tsia>FaFSRk2MM4*1%C!+A3*?YJm>AlVQzDt zqmyC*-jRK(m7spw)Sf&Qk;-qwCLM~Zqb?jw<_VWKW0Pe2VAQzFaqG7x0mqumQZ4`$ z?rIbTjIoi{VNiby9N@xUyMdqrb5=fFpwwdS_{#`1O^$}wYz z^hN}smUmfrP(&lfvZ>*GMI=I%r%^N zL|91ZYG#Jfvr zc`<}*Hin@<{SxrXiluiOcl8#$f63h1vZ8sHDe{?Ht;LT}+eke3G2H1t2Om`62Rd7) z2ID?FG{0F}JzJ|hSy+*i6vxIXQGT3?E{02`0)B427b`MB!Te~avO`H>QMUsSWX4WX~JqciHf32M; z1brkExo`1usoy07fVnvdz&(fReKYJRnZq6J`!B#^5nOm6x|s0y?N2+pK{AoAw_lA1 z9({wy7(aX-!(Wbj6rFwErY|bJdgHX_ z4He%YdRMrsD{qG6T@F^(Hu{E}f$pO|zyyXh(TcQPG}ZPvPd6VG!#z0aCU6@GHeNBW zzfeZG8wwHlN&q;6v_xjk+W!L!7eh6Fv3Yxh|6z)S5g)u?9)8O0X*oH2I@1&88;^X& zrHUBTa6ZC$H!BKE=4Ffr9i)L@ekRvz0_peKR{-7dqtCdefM2izW7BhS=2oMiiYRzn zVihW>zjt8V`M^zDROZM3oq$h&H-~kO?A^5sB4~Ud={E8;5o){AO+5DwdaoI^n=~d z!h+F_Aj6H{K35!6aiIu>7=5t>$Fj$wdLo$o??{w&TTx{>|$F$Mb2-5G2 zRpmo~wK@LDeu`MnmfHMaDX{@-)Tiz*3oVk&U*R$Se8;cpN861`iOF?8VL8JmQ$DS% zUADoJe{NkhiP|0p|9ch#x}6s=fCYH2P0#v1x2r!U{Wnh2Nl<%3tC&<0fsNFtMvBrH zc8g78M2AO(I#bTWXknM!b=Qrx5-=XhM&2dKyfc!YqNKkJqnS7Kqi`%$ZDmlct)8D4 zoY1vj>Dzt!^0gbJQ|wt>x^jlfQbyfTILIK_pnp>f6xsD4_`L+z-VBmA!>Ego$@f}GkieWs53Jb47@Mnoeol2MCS=6qX7<;gHN?J{=$lpvo{-xbWVGEOX`gfoH8jBRfPuhpqf+el@3=o!sWkxz zlX3{NP(l#%ppElz%J?1+e^A$aobw@-c#IKt$Z?FEF3|c3X4o!!xA`>HM6H#SY>=*H zbzha@3beKKY0*tw^CSAbHZv)QH6M4&f8B*n1_BTRPT=RV@n7X|jpOQk!aQSrA^J^^ z7#EOBW{HjMu9Ibkz7nV6SNH5hdS}#IFVp;%a)$AnSLufEgn%RCcUVJV3%b?V!cN2d zhfiu+;HvpQJ8n#TB?UclgcUGy3O^O?;Nq|R#&xGPJuK;c2P zE>K%!$7CT}q>oi2n(@WreqpH5Y1ACMU&9e`s5&4ax3CS@!`^Q)!f}NIN+8%|I#3yU z#@N>0LjY}OE{4YV;4Uw${IjrVMX45B_auR<-f?{z#6G-A@AJ6&j!ohvu7#!n>2Q9% z%*YpK<<2hvKo0-E{{_(8h4*9J5WolMYjZO7;forpBt|jW6R)-jZhBb7n|)-9FWc;&&&hZTVKj?f(nI9z5ZC zf{%RSbGYYgkANUiq6AELyziuxfx_l3cL0A+N-(@)s=t&*4p zh}6gEbParZ;|0S(44|*C6`IVAcmOe+f26-^;{r&DEX}a6xMsP(xd)PRO-lKh3;N&} zAzKCjA|ffWFZa!Tzie%M6W*?q(L?K!>Nk7@g4*CpIkQjj(qRE!t~`>M*yYnL;-xC$YK9ss}q zRRB_c`U@XE*gk~xC6+A#01+_&T%^sf_}fp-x5okdCI>J0Izdif2yOL+jsa3@{gakJ zVqyXf4&x>Kc%fV@VE1SZ-Bq0>5)xne>O=U*CqIX!m35R^ikQ+cXw6w_KOhD=DEHYw z;q6nWFluK`Dm`_Tu9d1siQp-|Wu0Y*Ij_gxiAk;G;XIss#(KfYqeK!o zJ-cF_0(RU=DM1jQ)OvAQ<+5MUC_jK`;ek(nMM`Na#OjS!+srU~#s^S{IWAO8EMNlJ zR+{S6Xoe)wAZR&ilq8-v??Zo&FaPHu z#7I$MsTzw2&Qb^TW+TP7!R(nqp~U;E`88l{xKnfkG%`tk4>YZOSaX^|ocS1I8`q6y7Ma<@+v}b$NOR z1Z%a)fyCu~)Om>j>2>3j+7sCGl7E~5h~e-8!~h9JZ}yJ$BJzU{2|Rjy)_OrQA4f_^ zA-;(DdzY7&mpK4_IYDsQE|gb(^GlYeKj@b`c5(srX4p39`rX6l)z~aA1_TBOi5C=G z5Tq%RM01zc0)L+IN z77G!>_)u>U=&se9I@N7xpy>%FfOPdw>h(HrHk(#2E*A(c%SA|OFVnvO0CV?#_#r7W z+c-{2iANrv363unV@!_JF6thngzX69gTV0pM8^fKmI{2ECR5ZBNHVjW*;v73kP;m{ zmrF4wM{5`v=tjgCzIy+o_{)#{10J~lVN`g60uw)h{&FR+aO`M)vR^G@a22^=!th^`7GM1((6GF5Z$x97_%i#eSIw(CnFsvN?!Y8DZ zwuM=m2|PA68=R!Cr-H%0u8Y1IONTTNnu${CW-CQANfijZHblxR=_GLn~6M=$BgHIK_Hf7`b8lV4wvX)~wP@@PtE0?D;s?f= z5H97JW(ovJ3rVYmW~+r}qU#D$2b83xL7~CZ(%`WBbY>|@G6*42Efp~_(u?7~DvlnV z!ry-KpK$Lz_aIrGLy`NVS-UvFaC>7dmFnauk(Z;FmcNJZ#}9)83S_BM7(D0Mjx-I{ zEY382lD-E~cd&b?8?~yM0iJZMv$kHx>DguYDNkelTlK|1ZZ@09vW%^*tmy^&z*^dcrl+&H>D)e>&LZX92KcPfF!! zxTG-XjP*`qfc1A1LLiGcgy0a7Qdlk|gis?(aK;b`2Ej_NKX-+R8 zpsl~@2q#p{vKqfkYQ+|M*)#zWsFow_AMdl?$OQWj9671ZnzR0?Oi!#n{<%*IA(#+? zNhysk7&Q9Jl8B_tTf|L}OF1;634?q^-w+h@_y6tZ$8LYs3^8se(Ei#(Qy3lWMZ}!} zIIw3JQ>PZrc_#U@NhTD}kRZsGw!jgGfKu`(l4>H3NDYytyaq!SaYRyikVtTFE|3W| znMNc8I57|>a3a-8QqL>E83Pd^jv~~0A}}H>&Ys4J<5TGF=|OF12z^7t$Wp*qdf^3` zPE7$%?{lIno?zPQ=pz}a8FM^O0PP8ae+{n}`f45!4c)N;tyCbpY4@NDw)MUv$7iv; z+OP&iIO+qjw(w{5<>gEW!5WPQWcsSgIf{_n>cZ z5X55`z49%rQ%Zl@RX@x%8f4sqg9W@LSPSiR%%xvFt<#oYO6ir8*gw{bYB@49mqS8L zbpF`%9E8YV)1D^tleLa4KK$8FNGT~v5=bdoy10j?^&J=bA?MR7<#N{}}FjWO~!?SqKS95>itlGxd@X zOwTXCl}F$lGLqmzDL&^68E5Km#=w~X=M0=nFcNxZd4ZG|7y}px#B~#5=;775+Rm5& zh*01Jw3OXa1&WG@N>$Vb22re3Y*4Ij+*o_3=r$>1*PYie{e6W25CNp)3Ce+s z0Aw0;(iQjkyue^@36n!TPQd|WS%&)_IbjuEb$t|o5D+3u*QOshm?UehBuPjJ!J5rx z)@U@K^}r}S<=08`piTSexO5(X&hX1CzulKCO|2(OQ|~VJ?fw=2WM8@OaeULWZwjc; z4SPp0b$U^Ct=lA#5^7E{2*{QmCn!?ZKg%QpXUHOB`8hH!5OIc#b3|M#K4ajS1n~?J zGBp2q8Tgx!z{2zf=BNR}nYw8FchaTmM6kokpXXIO^tAdNtSP z-nV?>o>GdjPgqpE z#XskDyN2)-j|u^bBK@Dc524=Jcuuhke+Jk|@Cp$}6mi6yA&NL6&JafooO48+z`0U% z&KV-E>5id2-ZFu}xckdUgMu;jb(tG&8k!#2Mr$T7DYbS;iDoJhmntY!tLW+-0OQ=& zCqM`GW|~+j!e$jJ6Q&e8oG(H3gEkqmEYlMXW&%=VDjv!*bXQ_Laqobpj*8byDRF9k z8DD#73L;G*rGU&*WLX9&GNg^Q`)BU`$WN`WuO~^8Bw3cF>+9?3>gs9&rP$_|ZnP|= z+*AN;x(A?eFwCPVO@TvgR7r8f#dYISCdO5ml-vRk~5Gc3Ce#fXkX{Q5hn? zsfA@MHiM(6Aao$%GCp197*E_c2u^^^gw67-tT*uBqbC8O<^xuMQI=n(S!3zg-M`Un zHbtr?C8s1wkR(Z7A8?lCO_fWX1-hW+i03@* z3HawPKZK;Usm1|-M~hN5^t#|28CPJ)1Vbhi^+|Ocz=@D@3C;k{SvajKV~8RGF#!$+ z&PmTTrl=5+@Mf7|f%SJ4Qv}z#S*J9GqA4P2pw(E%+Wags5DKL-O66{pdwLNCvvIoo zkR?ohC7h-*rYVM$nSK!hl_JM2`-Z?x{eVtItu+$-=L5&lN;5kv52Tr)Q=+~+{YR^( z9-eBoT1eAWW?6<-tA!*<{Foi*xV&ky=@iIo`p}8?oC__A0PtdMcmKeX{_6*b@t|Yy zU|$!W^9|R7=O)ouSYF3JfBB)dCKQ`3jFF-_hVF2N*rPz4Gh2p{-li=)=Q=yYxSMW? zySaz$1bliMpn_vp1#9|e`3)eHnAW6bhizS|DGiGYD0g+ETVhDN4OAsVLki5pv0)ikgg2YH1 zAO{2+hG)vo1@yZR9XoVYor&1DeQ8R(END$lqh!&tS(Jdh*vc)orl`4wW3Ye3H5Gqt`pwj25roI{GR0#*nP?sjgjUi$YxX>BcS zNGZAJdEE0nztidPAP6`{1U+IO;81Pdqa?u4Hldl!+%Ny$_o$OQoxIM%Yz@Ek`BOb* z(kmOgc;|y_V;>2OnBC+#VGO{KG$OZQNN2f*|lbj|(9J*L8WP z(-}qkKhOj?5co~I^2D#4ubh1KZwMio#HH}~{3KpF|3uF{v|S$;e!hs!#9~|HlrYO1 zI>5H=A4Z9^>BCq!GLUc|wD5EBh+u1erafS4B0^C$rv|v0j1B-q6987~G|IllM+1HU zvELwN7eO+)JPOqsiW47#@I_ucLF zU%dU@-Md$A`@SD&^C#e(^G2g#M}780g8QBTKokZ&lu-ZxtDpPzFBfLdzL{1Ud+M=i zeDU1lJ@=rL!lkRr_;~T|q5r-?Vl+2p1k8ZjVbLg&aYAj+qLBdlNFy@{CQ+t=PmSiM z_M~-P02jFj1X9=Qgd;+jwUWxz@x=cZWsa)11Y4;vDOf6G@yZKlQJl;VW>74Z!5G8FP7{}|uE6*GaP$ydJKFll19$!EPyT%O*87(OEkX$4d7kgOt^xn3 zpieV_4%N0D7Xb)H2mQ$J&A#-zf1D{z{aV^J=cgy|!ugY_%PD#R$9o^$!1DS|%3qF8 zjM)-~J2l{0sL`22K@s{4#F#`RCIY31cB3~Srqm?Jq6m@(IN}YK2~{ zuarW$n8TS2QV4o`14WOL?R&uK<}e$Wrwx;H&Y>_s8I?5 z;^kCgyWepro__oo7G@`+FN+BR1YF?#E4Q$`wgXBTCZ}r1W}OiHf`=wh;59d|zx%ga zi$A@n!5^7EtyU{YF?;qxgt26Sf$e2`UdP>oi3o!57pNq>Q!tak(XEsi-m{8f1pR29+|3@ z@bYt~P|Rmj?@cO&8%rCwvbfT-OXXomQv1iCM!Asg0(_`2m-5l4OYtr)h@(TcEW zLx(-n7wMuPl6LOA)wuef|272tqk?`I_{Wyj96JGkfu_*DK%e%S{MN5h6XobHLQ1S{HL<+5gXOjDJ^Ko__8w&a434;nFeFwQ{uAa`N%oz~@5Z_R1zcSzLh| za9t6auJs8ZGh+J(JD30EAGF3d&aTkTZs2*|FhK8#2;;K*ZMi1|03v^2P-JLipbvY$ zcIJ&=EzCUgJJiVzTup0kx{9-><}g(oeZ3*q3$U@%!fvyJX4}JV%SE&0cGVswft(~j z(g#SsmmCGsG(Q1QtrRd*D`R@HjC!@yQyd6bfCr+4SF`Qm#?l6EEpH+aG8Oo?)~6)6 z<=y!2zw)-1)|FCRN-1=FUl^?~gh+x|;s8Xr7vKl(Apw9?5*%3T_v<4plc}G5#3pA;YlguYUMF zclqK6TI&lbr7?Mg5Q4j|%PsKl2lPjx`HkFD0s#9WLBG$YtumKvQ<^E{YiHkhBVRxH z8-zL&!|rdoUdH^)1Qup$kr}b)-w3U#HISu}dV+m8N|;SMlvy-aJDkSxrj(+rf$p%- z8HT0#yBmE(Kh}=#wlz_2J})py3#UvlHlz} zfRTxDFToES0Re!%KEWVT3<7&!@Y_e0uNEhs`tnzEwZ~qkPCmVGC?SAs8Aw~R54ZwjI^me~eN;y>B_P4M7Gr#lBJ3x<4a<=JZW z)N5bLRu{fRGx^?{>LHTPI;fQLD3|i66!WN*a;TK@DCM(J9N#Wq=#Ji`lR*DTDiM62 zqt)@y>iGKTVPmTWpLbggHsHI?yR<#)FtK}~bK^(C9614i9+POlfFDGFG!k^Z3NvRGigV{*$&_bbrrFXNa)1R{DJ3Bx%6Sy>*=UAX zIPrx=v&0-4&l00%ITaGlCFr;T+8tkm-9@YIkCdFVA7|3!fr89r$SuD4i|>1PF8oY} zB4TCV1$@Cd=bq;Y&bbZTegxPnA&$i4Ny>de03(Uf;6P?hpY}+vO!?SE`NWGaW~&RY zuw3Q*pyb+s-_mXO4~Xe^hc{!pr#s_2+I5rKpE9<~ROjPhYw>OG?)yKdN(6&|ZyK)a z8sOW&?$_Q6Ar5_F`$6QsB7hOAd}6<5PCwA6^^<{pgwo=%XCKc`o;vH4rp`F|>RG}v z^P@m=FC+**pLBXHCtws&0Aa*+h`b&&mwzJeesDp0TRTDsMF=sVmqG|h2r=jvdYO_W z@NJ;muR#PD1pXs8chcm(B!CfJLUB-SKWqd@5`d(%iQRTe)8*3Kb7!3L^l6qYFHom2 zPcylBLY=OvyU4Z&;+b3n5?khP^vAl+d;T|oWDebRs9I6Ny}{e-m;I%SA1c4mkm0Jq zQigF?C8ZPyJ0h*Gon1i)k*@1X&bh7e2Z4SN5&99}$ei3^a$gew=<*MSI<*7N4-Nb& z?datc>V@3Y>EoHw%mT|-k5QJbFqW-ki-kI9rc6LfgwlcpD^jW$Koyiylu#Ac1?jEq z2$hV{z;T#IiE2Ue4eqyAz1G%}Y;CO)Z}YBb-d-hy=tVcf_46&wZq9`vfdM^$Fn!Cp z_bva-*`$`4>Ff#NzzpsIBkpvMA0{UTq-xv5{De;tZFvcij42&_tSh$6p zIoIzKLWrVzs9=nN#QU5o{XbBMr~bn3)8LOtz&VddVBXhSUIjs5w7m`bQ3)^#`0sn* z8+l*|V2kA}?-%&v0DqDINFEtu)N#T&%ZxGNI8Fq9eFOmL{xtBL^DaUNAtGXEEgu2j zXnM}M41ypH><1-5mz{sV+4#fcfh2&DL^AED!9SD$rtLV683kYnVaQ;tBru?VAhdm&JkSKNB{3?D7U3B`_`86gY#Y$q;Ll_-_5)G6 zk3fUl^czA1L7)IA19S`UNw6Q31a=<>ARjbspC%6t0c^3zFo*z$3I0*Ee_A-l?u#CT z2E5(Zb+kZk`i8ysN`ii)zyoLE43meF0JbE|q<)}3DELPyy_AnU+esk=K`aA;p-sBMh#wZq9qOLyYX%8Xldj#t7$s+002ovPDHLkV1fx?1kV5f literal 0 HcmV?d00001 diff --git a/deluge/data/pixmaps/deluge22.png b/deluge/data/pixmaps/deluge22.png new file mode 100644 index 0000000000000000000000000000000000000000..29319b3f38e02265d74a2bfe065ec1aec9ce5320 GIT binary patch literal 1103 zcmV-V1hD&wP)T z(@Th3RUF6h@44@rG zl)7=DBDDBeNO57e;$tCg!4ccB_F>IrrcFD|qe&){C+{g132U$r;6+&{3C_rO2HPdvLbJF-0UUaP$zZd^6| z*sD9GyU$>;h zDY4@zs>RckOGOI9Bl$$k*#~UuTSfkXS4NUCYpiskMj;!=HciHFT8ZaHNyQ`ho=YrZ z6OBfR7sl}PF0+++3InmH@7;XxmcEJoc=9=27f7XuIu5yPiu6F5b^s{og_3rs2S6_p zr5Q`3Nr4bxn)*|~D-6_1ecTw<<;Ynxc6j-hKfi43}~ zQEPVbqxOS+6E~WzNXl~vLP;+SsMcC!5)NIZ)9D5Pbb0~0rU3#4NU7-b0yGt-`gYt4 zpx5n$ePK{P3LyX}U93@WcKGJo zBNU581Q1F|5Qg-EkZuqXh9OESQW2eMrT$&tDmu;ikF}xXUenM;ryFqMOc~oW2*abK z(@7FT!z}bPfd`Fu z!8q}Vi|M!ox!-ej>iIRIV~t zZ32++910m9FX|w4K|JE(CwgCf!?-JL^x z^ZwTQ=AS!vt$Xh|JI=HBe)fK+`9hV1kbw{Y0FtLql(Yc=g#8Nwpm^AeiEE(^_JZT4 z@bo1V`}2odMPl#i-IWd8b)0P6z06&$0WU8vf!B`qZkFcG)&fqhwrP9P3;+NJo+`<| z^!~Eb^A$9_`&D{7CgSkf0pA+pS-$-onQ8fB zyI(WG@+R{}cK?{v{w;+<7MILsUmnkn0urI&I~q^KB?D|c)etZz5In&>S-I{dsa@#H zxcokFcfHc_z8o{VzW4go#`b34SsV*s!hRH@(Q>310tslLFd<(7uK$HKXJj~#ZPo+H zsKzyM*^7>l)lo(|a^9P~NOY zyZh?%)&qAsoNmvFeBR2`ABz`5VL5u%mL!(dVE2C9lKt5v}ap>uFZM)0PV-d z#XK*Pp%V?d1F0<1IItD()|OKhCWNPb_bpL`cU4x%f(0hT&QN+1aN^-~*JLj^iyVzV z=8*yFpetO+%M5!PuAccGi|?#JcR0Ql|B4xN&}Ax9IeX?n;UFODD9=er6EV<%Y+8=- zi=y2a+3fR4U;%cV|I>7}pUpR9|9bV~+H@#4%`qK)8Gs{rpsEA~ho=^4RiK{c10rxu z{*|*NE-pDg^V_c(Uo5O}5Aazm*@$x%MOZPW%bbM=Q#MKkZdO^2kh;6Nca z%EV-eGO%C5=#NWBetX_CtPB3P|E=lt-U9m2~iOT1aLiz#~kX2jc@q7bU zcX4g*+qBPgDzIGIlXMeQ}%icYM`DLrhv9|duCTFmEZJtZ*lUS@)U;{2NPQ8v8gg= z8akSN%NvO6v+fU3ya_rVO`eaJp$$T647Z#BW)ZzaL3_J`sTp)Wi71f+1;og@gqTRA z!i&w^xHsjezSeIki|rXHh(l4DYAruZJK9Ho&MIdrQA2U$QLl7z-t-DVBGGgZ1UL1O z{tKkkss8#d*?!;SM6Bq*f1>wz?K5ENVn*$&9{>qakE37m<(F&Ou>E@D;ki8TQ40vLrzu#An-LHciJw4NPY0=>lqlbwG)cnN#jVqlw z9d|PDYt0B|ZvL7itp+a?Zg~hoDT;vRS}0wZ9yb^M3zw|Uict=ISIJ9w*=S^&FVh=Z zpS^UH9CZ1-#c5Mj!}AMP%Zvlh1p^088}cI--hMr7H@a#=%MvGH7M%k=n=(i7*|Tj+)27_Uatr|eDm4dnkvHd?X`DtIuI+%pJyQ!g%^-Vf=CIEjT0@}rcf@$=} zD}QQGbNdg>ljiz!u zCREM{szsO0BQw5AknyO zi-=8~7T=Lfjm%7`vH0ryGQgzGf3Pqdtr2r=C>9@9d;t$vV2geCo2@9ts50@eQ&J1r;|V}QT-?2JeOBRDZe4e$JIV8Z z(hR-vvElE>$Jv9Nmf`$vRXJmp4L%}kbPc#bI_=sD7F z&p#3qJ8>wU726e)c^qvbyNwDa1yQUePhia#A+@5fMvQ}F0W5`tNtyQ^hEY3=8*6g8 z4C7NBKUR+>R?}-uStlhvp*6jk_LIGQwti)Ly0Y^GyYY9k7x7*)_HX;*SSJrP5qtlNQg@xsQ&X!l<*sjH z{EflZSPq-Yr?rJ!w9+T4SNZ{CsOUjawOb-GC zB%a*&LLh3~+(GYBR=q}SO~C9-pN&&i4B{zlj z)HF5h3USig%1%VZiMJz30|5bLHGeak8oFB};gOd#hCoEDVD?nZmqjKDnn}{DLs3Xs z&3Xk*X0Zp0{X_tVz(HXQzMVemPs_R8AtdL8Zk;Z?)!AW-s_)W^%vh4LiJj^GH~OLcS}2=qMh%;nf>x#k zeK5~0|Mt%qz!3VEue-MSsD*_kaUNaGXa7M|4;@ZcDho2NYx_ApDc7?g5y;rjq=3Ly zlJPnScqL!A=hZOK{2gp>=im$#f>!!{+v);EKZ_rdKL30Z&nlgHUe8s4HTf4?pR;H4 zTJ$Uo7ikK@Bf#3_Er_VX%kw2qbNRIbj76pj^}@;?Bu%98`6`}%pOtKHGnjez7{j9= zm$~1K^AA@^^+n0o@ePUfS^;?^TWnEflrs5-tz2vJ^L8--OmQ+(xIERH78-evz=jFEDhMvG>x6N_d+ohYgj@2trF*cl1;CB zZ9#i&@qQHcYVgQ<`qML)*-)^1!-XU#W7UFwXXsCmQ0BM>EeRqoheu9qXy*U761Vu> zYy(opgxULDOwzW#hbxGfzKS6(XF5s?HFh`V5bD{))vf5Pn{B3uv zx0C2zU)V>iCb@jhM&{iv&GPOzj>6KLYlf^uCq-olnIDSwYv2syvdBIZEDdbb>~(`M z!4M*Ri^lE|qkcSy%=_+^xW1Sz6-uroF?Bt_IVv<5@XP=9M$N?rr3hn7BPma%PrJIP zZ`qc4oQTcJrF-2>ulCe~c+OLswX?Ys7RU_lH#&H#5AUjq)#U&2idak1viB_6e<8Bv z=yoe+Bv>jDZIM=GYMoa)vuJQqSJe{|sw6}mveE;VfU44tSDM;0;MO{TxL2wt9|^Bi z<9n2b`Jb(}9PN#??3pZJG8`Vr01{-MhQjw5OA<2I40Wujxq9%lcxdx1px8iTW}OpL zA4XCa5~4n~P}6T!)KWRhsme+;CQo}&h%z@>`mjLy=;#+S*J5x?%~dRmv3&8+gI=g# z^PBp}k8QH+lY5ADNC-d|JqTIjd&H` zq33gI_2nMdh4;!Yi$R8g%=ZoBUwXAviVqwQhAY55Y7MUyw`nFBK$OoSduSSCqpjW9 z(IGv_>Gr|(Eb^hLm4s?x(dV^9ut+UgWPjkH^11(4EW+YqqtF*cpW%@vue$P=*W3QG z8Una9_ZI5hE;C%>mM5N_#`Sw1Y)}}Hi@$DvnK`f3ANt7athxW--++mW{2{l3@O$TXb2a+KP_ ze@@*Kuwbm*R}lVRfi&wY&nf2YY7Kc`{K$!QfQ9Ls#wOu@HH4FT=G_B8=ze$it6$Xb zu9oX@_thm6!=(fH>r#e7Cq)etC%R|dC|##^VjGWSkiO^j*AIL@31H2q z>bg9zaCha3-0aCt{B(1|F}3K-%2 zzJ)JmwqcFu&eUE?crqH+hsq`*j2$lhIv>TKRfu7W)D&11ek5Yn7QN#ooE*?2IDZ_* z`k1xlxyFB$H&d@0^7hsqi_k5!;n{?~hHA{=Y-jr{_>1E!@jTlB8^6Hpq;{-x)hpjx zUJ!H-yp_rN1<&ShdPm^B7eYxj*1uDMzEG72%1JjE`yk(Y(aE8j-1gQN+>J!FD%Fs! zYRifl3xlGA71+)B9%?H^%bJ1xm- zm-fp^hA?x7y687~3-mAS6N8$@+n3^twS8XYohQ(svM==dZcNG&34Q%8Cq@6A8iF z%#h5S%Q$4LujIKXND-43*-L5!9T4U9M#)G5tByrWxw{NO6Sv`@x@RU>bjy@* z!!N9|;~2%N_ui~ph0amxp3oABIjf9MKPVX&uKoTRY?J>@19OCjLzPvRwWxPjf09WB z^EL#za+1|V?OYwqN+n52X_nv^!0`yT_9il<4_}OMy%^JL2F(T<#OsJN-A>49Pye&A)x#ivC=EiCLI(bz(#M6rl zPEkaqBw&B};j1N)G8b)D$`ElO#sVYa4D`^5S5{4(EK{4I;44+2v*)^0rhS+KLj}5U z!OO6P+nI*p%~)>&Y+9EQI(zX=j=Ktek*3Z$==;Z)k1%j*ko)K|y3)T4S5B-YChs)= zrKtf5&aOjkcXNG@&-st-^nJk0t|engA3{3Onr#?fvG_^PXr~KemV+sSmj7OuC})n_ z>+a~$|83`1@G6CB#S96G{~JiQ@x38#2xmWNQwG_5`1H<9=IIl`g)6Jj$q2Wp!Nh!c z#MGy$k8Kp3;h3SHdYbI*HaQ`xF{KJ9xTv{rW8;_aA;R?BRMD-i2po4w@~@tZFs3hV z&{NPlEI0&=*A1?KBVd#J@OXL7WQZk=|3lA0-3@LTp04ktAv3?DcFV`DKQC17<*PxJ z#Kd4O?dTl7I$uv{G*C-Na8F2dH1^b&h>!6BDs;$BD5=ldEi+(qM(%EG0880eQ7w=L*PmxU9{bE( zWE$NX$)hMxYE$r6Va)OCU%Rh!4qyMgQ8;*#uF4X=>GnxiM_cR6n^9PSGhUe!i4e$C zOc~n zb>HP*$tyxbVzL$ab`gBGmF*tEOqR}@SixY`Vd)4HRM<|^VfH@^(0Kav1|=hmrG{;} z#hcc0h?Rme2||qJiPc+ZV?oh$Hr>2+{+#eW$f zRjW-!)g0*|=*oLg7!zOl_^xNVU3Je>Yn&LGnE>deHbK`BaK%dZnBoR*dyX> zTafL`gZ3&g3Dv|P>3`(rGJ%^Ji2k9!yW^|f4z@b!Pm3lGr?0z^-V?C_r_tDBn~N<} zhBY0p{>t4vps85+9W_EH+HcBh<XF=UeL|+IwXt2@ zv%x;A3a&QmVYB;Oj#9iNo)T)UX($$Px47D}Mn{mAn+4X8f_mGO388h1d_y!Xx9`ud zC)*wfmT(nd>DVvm*%PGfSqS{fw%fqY-8c-`3T6Qy6^@;sz&|5jDA ze8T0&o)25HJW@EKS(~Na!7kXd01R&0!`!X#&zuRxEBn|%A%A(>)oETQ@7>D$*rBa- zGH>>98b+%jX)p!&gZ%et?%}Wt9HfU36+W85QE>X3cuZ>Ev#ICXItsKBo+ciGD>ubW9hx!-={@utkJuZhTS$jN883U>8Q z4|uucCXuaWhw`s?aj}n9G}eX2tHzq$u7H~Z7A)7Vucp(X@A=ZaR}Q4tX!L&XDRqBx z9Ugj*_zor%kkjnH7wtL~IQ#m&M9=88H0it-YYeL|Q>o3LjRqb=?Y$Tj9$A$&C|{+u zTlgd+VqLsa&t9G*RBFgunAT#U69sv3W-=w@zxsaR#;e8Gaet@lfefBAgX^*V-M@AD zlh?M18ms|4z(y8VA-9qUzeQe++tiSYNT_E{-2iPGXsI%R7 z(1~5|)}Md?elFwI`X>$(BdoLtq->{d177+csN3KZ=6gubhA}J&Z`?anj$s8w-V!K{xKJajQHO6>GphXP7`KZH%S(MQ zWhD~_r%tlojtbRP}K_yx^&kt5Zk*RxBC_$r^O-*kwi&xcV`%p=_kun_hg>%r&8fW^sJIvvsMt zT)sP4K+gJQjUar5QKg}kUg$};NV>=(o>JW@M{FCzn)1fri&T>4ZKT`vwim0^cK-++ zn-D8K$|{--nljxD6S4Wxu=_zTw4*0kv%FS)HM?7+n+o!)+EZ$=Z4AG1SMn8iE#&mn zL;9=A7?N08!SQ~R~P@40!2CSh`;PnTj&(7WK!IKkjdj$T1{ckZ9f4J z%x|@zGiKYEF6)LdKzk;~uJ@)#DHt@7b?yQE( zxmfV&CI}HVdmXN!2R&hi?Yi>FMYc+j_fK|O$3u!R!w*XSW$p*8JWXH7JEtng>nv(* zWwGM(MfO??%bMU38BE9?qENJpZVa=FEbv5f08d3lSX!7sTa=?i%f0No;eK~w__l<+ zEaT$z)Hf@p7r5ECxAT_I3{1+OO~`NXKYQgXfQW)s#-b+$ zI(&48TPh7ZwhntLyuN<NI|+@pDQ)t`F-PVWvw0}zM)3(f`u%IGFJ9uJ38OVL zi4ymU>mxNXtC?Bp_7!b!tJkIXtH5?iz{wh9g%h#c?~{x};B=rRqLCI1Uz3YK0h9=MA}-UDtG(~Oq0`9V<=Zc9!Qd_gU}p5rqpw;s=k^`?wM{#q^g#M{ z5h4+IA+)1Zpm#a65u`&4Z;@|pH-fkgJlYOKgpmQEY#rDWyXsLW&ib0BgU*p|W5oX= zIwEY*R*OcAoo78c2wzG53y4Vh=B%_P@t2J?<;`&~fq)4L+KF4epGH@iP<+X2hJRs3%~l_!_EwyvMEnkR&R zDGU1lSpY-)qkky)Kp-w`t-|k)8Y*J0o;vsa>w)Xdlsc+lhee4_h$|eRMEGy?^2P|h z{g#y_WCq6(JMuXzY4O=6+LtS3U86|IAi}oW&S90daYvU@>7+4C7qVN7+r8Eb(#xOc ziW&&m4unz7`>Z?Zmb(>}K zOfO=&_4gyG5ZL3z`%nz$05^3NuPN{(IT*+)|GkLxURG^RW%<5|Z9xEwN#X%(K1w@| zyT`T0Z<%Bc0Had|VDZY{=4~qv=Yrpggx>fYV-_S|^YD;yo=vZ{qUf0T%LVOv#ri$} zs#MLuh-{p9*!{z2s^1hk(_!1X6BD(l#c|>kcn*Z3)HC#hQHPaG-qDO!tfUQBn|usv zZ1>Ycayp1X7KX6nWlHL0WGigxAY`GVd+VFKlZCwO9Qf6*d#JxgMA$g&9<5nLDv zDns|5@_8(7w)f-RjpUp-t6A*(>K!Hc4)U~~?@J``z%;cT;1pe{G%B90(JuMw2j z6yID`hG15d`?-bQlBW|ruGWPuktPD$r{@vZTex0?LiNf8tM0?5qdp7wg?OZfg&)d> z^lucpaMZb(=t!2}^AIfdZxZqb82UM^OTV`#!3M}rn|u<^Ndw)-NqJYX3##<1Cih?X}pRSq+R zQ_E=XTD2m0T@gngBtX?_f?SNhD?!iW(|KC68z7E)s-U;Adj<)el1_2BUQOyst7gP(-IpHpo%Xc)}^sR%5u@pG7e>nK82Ew-8$e%=X72RiWlet{E;{@R0n>CnW2<2tH2VQUlpG#rte zPyOf8<5{}L;)oUGAtOrLv-DR?nSB<_V;17j zm)-%-KhrCT@Y;bxv^wSMWe4|Km^yofaAXXgY{`nJhA7B()-;5V)zlL!(oo&3=MWhf zIT@{Gwc%ZFoS3bpvicnp#d`VN6L?&21zH?mxM=?i1Q02OVaqC2mPX`@>e7|10Lr>I z`$}8QsxSDL)9x+{C-MU&>ac{E2!y!uWcrddep*c7Gefn$+i@F~=^q~Qc?5;JbB6@z zBS|U{EU|8<3?kdwcD{1jeU|&)Q*L|@q2H&O79@otRGwvs{yQ^3U0fMwEZhYfJ2jmS z5{Y34XYT!P_X?y+cWRww_K&tCj&3k8kDX)9(gf!LQ2~v-zEc|_B&Hi;qr|XCdxWEI z=>MvhvJlSHH(_DF&1$DiMg_~aDB$$SoS9Oy475We?w&?o(~Fx`@b9mB z+r=O%`^cz20N1Jm!Q3vR$}eC>B{vLqmqi-Dg?12$2jBKdX&22&{mL7sO{!ubPgGiE zk{7GqiG<@5uEj17UrxMvFpKGx{@VLPHHrX6xsO@E&V=kAIQ`1wNw%RuP2xvRdZc?& zs{$k;vgr}L?ARB_r{zUWE6habCRh|v`anlo- z=zT>DTH&Wq?rWm*iBkFFUHT)iFkj}n^YCyI4H5DI;#sKj|7l|G$1))mOaJN(cDvX> zxoNBG94vqrD!&2F*v^^2f3Jct%eI)x>36Pu!l;?RbtDNo73#uqtqBFGn$$1@-_t;tLldIYNyBC`FM>7mBK7s!yg{9JkM%(4a68^rbWk=>W`!^-Qwy$c%Jr@3ckZisk z+8_Aolb4mP<3K6$sr;XI)KCh^t#GX;691Nt4c<#1=5Y|4Tb#Ao?6Z!dgyAd#=ofxm zI{sqKswysMgT-MjrTRAkUZQeY+Kaaqrs73$ zq^zh8d>NoEf~ph>X12O=hV&OQ1ZDd)17>BYitqE-{QJ2&S#oQbsuv6=)ZUZqpD|gw zQmzgqA?3S`*RX%FK;znAX5o890eS$g((=3 z^!d6;FHlFjn7;uByb0Mxz;#klnufmYe_4e%E={^7)L42a2#T_Q?s)_Dhi?H*wbJ=^ zmC|<~e2Iqi7}Fy;wwzy|&H}I0`#R#-Xv2HgtaL5J7};s@sv5=M2q7ht(RFoL3RU_u zp-@8pPyG_m-CCle!FyvVK;mzh34jK(0P0T&M-`ig#-+6c9l!$;JMD!v*$HWe% z5Uo%Ct~a;<+cY@gN1`L=WP=Z1IhHn+;>Vp{ScS8@3254XuQYVBsQ%q)^Xhrh>y&z3 z&zqrOAWQ*dE*9b-7NX9{*Ya0{PBG`VQmLNe+v*sVrkFTT7_oU)f}#N9*SLYn67;sL zM3LmO4fp+;M6~a;i#Z|sdsw)tK`W7F8Gxts|bybZ#u74#^}IneD}9aXBZE4;0J?d zsneLTfysQQ@AGCDm$0(FI?uB_ifFV>pwx(&HawznkZG6THczp;M&S+ z{((0>rS$8yS~xonyK4R1&A-i7ZqxCB-TGH8^B3n#YXNxD6m}l$#QXK&yyj1YEI-lm zi~}2dR?c%7Y($z@M(|dS#Tl~9&VCg>noJlBj9(AaxWkY&+MSk2drK(^_|l@t1M2>~ zG4)t!4e5IueGoFS`&2g&s5?h1^7ISh&)}$&cAKkT-I>$T-LEA^(dUU*AJMfem;;}; zM2k<*Ps^XaZ~1B3^G|I^U4kM-OlDnJ|6NdP@4!MMh5)r&ENqQ5xJMJD9 zSr#~8PCP{LKnjYKP6*B@!X$SQu=DdsGBh<>iSt#RGEn5*;amDab$z_TwuG28IK#*Q zilRG6nA7z?ZlkoTd2CjNhlAL;|EmXdJ;=jS^j=Ni4cTzNRkyDN z#v@t7@@~g&t2vex+1a#!B^U!k5$kJ^PaJy+Ydk?&)AYxx|6Z%KMxt>MS(dbd;I}bhg=fl+Xnp|kLHyIzq`0^=8NuD}A(AFdF z(cFP2f1Dzvr^0m|18t90NVK4AaQrj6w~wm>5-Y&I+Z65lnZ*g_{^VEeg0X6Z5|%_`>js^zZry{vCPqsPZyWfDNp zzh~k0rZR*~&gr#);gyW!SZrF^Lgvo}o2GX`7i;HjxauD@IEl%HdiN3e*CJcSd_gdY zMAWwv@dFc($ZPp>_FuWUeF+yC1|}U_DjlRpzrX6&bdk~J+vW>3*y2q&3~Nc6<@|2* zV z8U7EPiY9-_RiJ+Li)nyKpCYtiuk>pi35n*<&HkRX@q^yOnz|!J=)*AB$Q5QG;P-uU zvN}7^;+fa=ZnfT;M&PnD;pvDC`1+u_Tx>tzOBss3!|Xx~Ov|5sF5mFywCX;JFgH88 z4V)fH0@;hV0RXu{Z^D9L;;UGgl`oTwaLEKiit%h5#7aF|ql)*VaClMb)4{z|d4V*y zGS*24&kF7Bi}KxhHKpmV`$j1=f8z-9(%3C-px_&y@Utt5HH{3Vu_aErbv&}iwXIn; z`;}_t)Sm~aN{EQbX*b*@@Vs7y`xhZ`fqFNS0g0n2Kx0OHhg!KFxMYnTv~+1QKl4B> zqG127%J25x$A3R$kAE+Qr)iAzyZfk0n(OE{t=piT$QoCEY-C#8mY$*$f~5@=RrLCF z2`Wf&e(K3N2Lvr2&k)Ggj8evvtyG5TIyn3oj8xE{wa8prVl(kMU7b$}019hylEi21 zl))$lnYu8#IZuNy=I02Tc%iw_*Rc|?(7+M^wisM74a-u;rS#*qD-$p?50oGN2MLBh zmf(WkV}v{Y2@YP7COA@%km&1Es|V=tCFDO8!AqF@m^l2(aV*iIz3&Dk@Da_17p=c_ zO@wvOFq~0K_{%TLpQqTagBIk>)f%2}QB0Ek>H)6X$Gx1_2bBAO|JZ{Jw~JQAJ0`(n zT+~!wcG(}l2j!0cu(WI|&P}ioLhL1ZATvN*nn$L{X^0v=-Ht}3QNz7Lvz7@Z;?e&Z z4>^tK410G5*)wCUc@f%_yxYCA6U~D^9=LPu%UH9vIrMb$`FD@++w(y-TIHK98lsSf zIU9BnkdO%ujmDSOf7iiJi5R?EvlqU&J-htC2Os2XW%rVzHR}=yFF5a&osLR#wHXi- zNaMD&D6|R)n{RC*8}XwD6{!5vZ@Fy{ynWB*p;KFLk`RJ^!Q^}GG@8(oGV%|< zEamB30i}!o_HB=KrzG5X(2_6^Eb1|?*YET`2IU~?tapK%nNwJnF%J+;SqkHO0 ztEBfy!Ql$Rv9{93_!VCJ0y@@(&)nAaj0A5jw$K0PVW=31ugA)&VcIq9-z%-r1PUUPLso7+peN8M4GSenq-A7;fiAQ@^ zt%Og`J9@?|=I#0(RpF|`v!*jUG7hUC-+kqPtk~JJG-UHxiA>6jrhp`HfzG@;&~Pj% z%N;+tUDWa9pu~HfhE1Gfvc~QDqGUx4IVtw1tD{PmftBx5Y^|wqO^^a38I;)zO@!v) z(*I@W`DIzk?N&LkX;!oVKC#g!d7LD1+dkc>+L$Y`fX* z^JFZ+u_AoI$Ha8{8>9$!wy60anOJeRHK5hIuD-_MoG{{nO2)Rm)T)Qk_rL&sydq}Vli#A(5*YkDx{ zb^~wwOWhz`Zj8W8NP*l95BEwH^xa^=_&Xj`TG3OrO(UdPqE zw+f}g*B+v(AJ+}0>$}3TP!j5RzL&i0SIb<7PaN&*3xUO-$pHru;uGVZJu^g?#AUNm z89z00rn;NRm_F592yd}Ue4iKGI$UU8#>Jd8%R3AL@kw$~xkqkn(YPmkIB$W&D84iE z{rOjKw8`@(LFRcZOPz0?b%U6?G-}QYE-o;J&Ih*lgA20uBoi3TzN^_My>Tnxh2Th% z_bern^2S%EqwWo*Vk}_!E&mX6n3khj6~Jz0Uh{7GS=#)5 zx!{9j66c{EvuH*-pVd;Q%TdAfLr6VjyES3SYE0fIGVbyS9*+XbrQSa3)%|#8_MOq7zd;MlhC9=bc*?sfqXyE*X~ZQn zf>>++6&%0ABIWiCS>st95kh4tz;8d9vVPzVKIn^yeNH-K=ia~>TYMj#ew%^M#*rQM zwprP~8T3nEcbo5((JzXrbjqWP2LgZqJh7hOyT)VjXj0m`EHdP^xLuH!^cSpM`=|{0)f{&4_Yn_h<5%2 zdIL+Nfj1&rb4XHx@QDcGzn(Kp+r(zoU6qsl|El=P5tS+W%PbFJldW0J85bBL5_`A; zC)`1RMW>{r;81`F2Cln1ysw)?Ox7BS3W$erCs)lk^Ok9X4<>5oPkmUoJ?pS?w9?&} z8)0fN1%r@vwd%RH0Q?w+?W_mQ#a{$Y&%Yj%-b}bJxZ$gZWzDwCy>s4K`Nn%yNwXk3 z`11!!NLV;(KyZO?M0Nc9tq0DU-b_r_i>sj`@;huvl+4?p7&Dh;>~AqTX5IO3&6IoS z((|EqVc4yVNh)E7L@sXzGM)E}N@qbWF5@U+h_Z`&R# zeF=GDf)8&XmXP930Hx!QO_t&eP4h@sYu zR)8+{Y7bO*myK>G(_z#;=N@ZUn&-2g^EXqP9fR5qJ1hrVWipxGE*tw7o@6l~3-=n| zaHPuvlH|_bMIU4^f{FrZmE)NwG8OQ};Cv!*^wZj`W_R{(umgXqhw+OtF=vbVHSYQ_ zv_EGnwGQ>v&UJZn(E50T?82Q==HFdTtT;m5^C5MNgo`}M8hK|6CE|y@OFsMAV&ll< z6Om#wkYe;z&m^Fu==Z!UGl~^v9_R!4;Ts%=+BF8w$See8jnvh z26qLeSb%(|20yv=>w@Lo)WBl{OQ zgE)}Cm2~C-)$fdQRt*Ffwq{V~sN(W^6B5DkTLBo%+%fNi&Am@u!Y*N+rI$1_v%BvM z{pfwv<{lhr2(QLMKv~TnSfA@PeDOx@a-45EFHU}rL$?V%JefDZw6?G$lhb9Hg{~bv zr7ZZhUMbLYlSzceup*N}BF3thcrmLw)PkgoC*FTboo-F8iobfL?05sf0cq|v^TeJ; z?JeK72a{y;z0%^Na!A$v22c@G3uD&twFg%OOHQuMo?Q2@O(g@9sfycTEh7U>t;{Ar zuk4oc!lOAFIfp;+!yBoKh9$-)o;hwDwcx?c@xRZ-z)&9_OzEhkqMK zlY0#E%ox=9-*FSQ<7bZPG(5Lbg|wc$_1echcz<~Iic*nogD-c_?XJVs$O+7J-+fdA zR9(I{`tnPPZQF;h@fm-6x`C547Y+V0h|8+&=Wj;#x&p}+e&-3gFK^%>&K9NV6_Z6T zWtD#$I>;@&I!K&8-zT>}Hbi3wKpJm7FZu&`VW^fME((dMUb8oza2y$cnlt`Ob7N93g#rUHFLB)n1z%Ez}=Z!wvFJ82wTYWq_23g0`^!3`Q+8)4?k+ zUhL>DPZ~;5Q5I;OFd^p6qf1lHL4!U~FezupYw@c{oGJ2`gh!N#< z`fBz0`zxLPh*XW)9*xm;_s^{RlnXS~TPj62Vjh3uuZ>PC-)xw+%#QCke|MuMeu-Bi zmH74G^&_&zzrZxfe!^N%PLEX*tL)EDlJC^$U;=6C8e9Q9add2On-=0w^v_@q{=fwc`_3Y{GO~Vl6 zK_W7^)#jO56PtysjP%_?&N&ZR;H*eKPjm6)Tn^?|^4a&E8=rOmND#!LXPinp(6qDn zF*&v(;wFh5IDtP-Shz8^G|PJhYOep1b(29-TOi*=y|mGu|7T`T^s$LTecZdqxoIqnHVRxDz?@>lrE0Pt%vbIf!hgYALSX%AktQ-t)o8cZwUB zg*3?u0^w<;q+3Ht=Zk)7#Z=N}#MRZ5vLEz&%4%?o_9g>0w9Iw<54WFPdLztG-LdqY zrOmYZ&Je%KztMu| zZ72QMEBK2y@WonuUvL|;b2a*A&WL@@SVf#MZopbtSnOZo@*8A89u2*9to~(}4yqvP z789_@`u<^DYju`miv)~rVBhdMcU2=8W$(QkXmr=h-GoQfAQKp4qYCFG&dxG@xg}D1 zQPZ%<_;;dh@Y0OtA@H#U_rD>~DZ7p^f}19dCRb@?Q}==Npmf=}F|j0OeBe**oDAM# zk~e04(|tSWWQCBSG{!KQ3}3X~voveafPAeJO!W-T%oRz!RM<0KbNl+?*iA+%OY%OI zGgT&etNiGBEKh^R@`I4WNyS0hvw%~plLc!*g6&ycsK2IjfTu0%$hR~p#}a8CzVbT_ z2!)ehG%gJF7f;pPj(v&d?WvbTSvflu^S(g_?EViXiJzapZ3Ki7<6&G$?S(0Tlb>S| z&Wr;w5TIAQ)_Wp-AT))cIM>rNBnVZt6{Bp4lXKmD<@=rPld#YL)%Q6?7yOQ|)%A|$9K!44?)OSz;QYkCk zrQ$F*C#c-lrwPi$MLZC!1t%192;))Xd#4} zj>X>oMR9H8Qn~A-d=(`KPJWZg6u;m2e!#aBeQAC7Xz|J%|Ic{xW3v0X6rb^Xi(pD# z_lsI^1swl>7C?NOH0(aE$ck1Fd;3Z!x9e0izQE^zlCiq-9UcMTagW4}aeGv=XQ-$IO6S7tJ4V=ls$a=@G7sF+gatN>QcL ziN0ff#1orops_2W$HUI}Q#Qwt0q@}W6##HJh|>b?R*MplxlRg=qtF3!uE=@Zvh0!EgfG(LjL*c ztfD$kNxk5EGqX<--BJ@(Ix(eeQ)rN%dUMT!jP}AA?XHa^Gu4m1?`a<%dbK({ns{PD zJXLRcQ~b)myme{yDgPZ07dhrEivI#J zT+D~H*936wEH_uc*Ve(2I*ePPvYN9AGh1~`mozORJTMBY=_}$BH|F!_oqrB|PlLRa z=}HLC8FEMccp{`K2i;#D2&YBe7kHS19q&$xGJNWqo`>5%$$`&7Ho)P2y?M((90;*9 zf0zS`hfg^QJ~oC+8|=&B^k;Z0{3-Y)@a8+Ia&U0*4SnrP>g=dHGNM1fL|0^?IJy{< z-QR;1ctl=SY@LoCx zlCQHOIh!g=F7=>!#DwzI-;mijAC>Ok#1Pa`ioT0(L@sD@lX6)rwERNkHr-4=x~|2j zdx9+c0f6~I{T_$7y96IF_$^_1EAy45GvhLZnj9J6ZoFxZRQS1LsQ&Ysr|P?fb|m_b z_nn;j1uRdoCqG*3bI;TD+~=h$QuIB7G!=E{DT|NBr7D%v#41idt3$!G?AINU82G-O zj&38V#ZkDNjG4UNF2r|tv+Kfb4X+Xc$cTQnySqUm2GK}CMREx7@23}z+9RL;Lk9eyynkattdYRjA?*O&I z&2-t(HcB7#nE=ja%8#rtuJyL7pR_Nc=5L~0u6^rd;Zc*)9MCL|-MGFr9Uw99u;BN}(q@)zW3sOr zt)Ah^|6TIv%^~-x2#xs9(XRv^dwT2H1G*BFTFc@RlTD_Z(;62N2m|4K+*!X8Z?e9c z9OnMR^qX{CW(HxgLe0C$wj_%;T2g~3U6q=-32oEs6lMOnIQ5GOfX6=M#x~?8pqyy= zPUqIXq`$Si7<%aI?SLT?YcH^FMNTvQ` zV(d^p`qKV4-^(;%R2ZPSRX*eR^d$gtd>yyfU)W==GRiH>$Y}Z0YzyQ$<-yn~QLCf+ zLpw~nmi?0iHk3I9074MvN^KatCRAg#{gDsNDn~lhngVh%1D>gWbCv(_YR|f05jZ!r z+!f9oK(WUHVqBY1O!cq%=jfL0L{8}=+1an@4^}izOti z@qfx@e?wjLLp|WT&R^4+mmRfnQ6;$B$wWcFjn;{5pgc-FS00g1t|BD^PTA5lMFMo% z3!5_2f}bgbCMmfmo9d(&)1Rm_cb|_y_3R}A^KTCy+?-Md z$=+W{O#XdG#AOuyNp4AKce#X{;xzm`{hONZ+7ao5CXF*jzK@i$`(*;M?RTibW$&kfjAFrG=|h5!33-=u3I3x7LO>z^gpZa_?Ov+1 zB-ZZx8oLKH4t<(y$UdbnRL+k-<7nhhI7O5QUfVq9i_(2;3XsTMu6xppzgp;$yYf0p zDVq4&$z~iZip^KM{0(9xl9$=$HA;*U`fZ~2{wej_&dIp_ih1tAPf89*rVv&G@rkuV!_gh73N$o_V7OMzi7tg zj(x@__mBnjme|Ez`%+%a=}&*d;nzQG?*flWC8rJpo{r=9^Xnwbs5|2_Csav~uy>xb z-Q2R3A-TCs)XV2^K<@(@d+gE6VaW!f8>1(AZYzcoudfU6kf+>qS&TX@NwQf5k3{c> zam40E%s82HZp32}6NAkiYCt){S84t;sct8l z-;(9uVmvw$do%mKq$&tHY>oL4$^v6hyD^HIX=oU38dcpJPHscwySCqfn6a-1 zxp`N>1NAn5Qa&n$=k2uj$>>g$21-+S24l*n>*9S0v2vbhILWD)(8_OoPnXrdo{^aL zDd^`vT0H4i>KsUKyo^IDzIMzv-^>gBx#?Ffc*GPzHl=?MUN*QTaSh^>kD=j#fi1C& z755#uyY5a4XX7*#VG%Lrc@&j*{1|ifj>tGDqSMtEeZ?Js!x*Hib9L(ntx^-Qm}7Ca zJ)LmDi&alZoSO;#>QJSg`-kLC=?|~qj<4tmi9!PsUrB!byGJ3PwdGWLzJYO19dKz} zIHP$QRM#;+ru`o=B2(*e+yeu+n=icH9=PCXc6tS`+p9Dz&*>|3@v>TDB??%k zeE_?ZA-wyN8i(9i=U<}p158MWNMVW5;_QlkE)%sE_ai^0%IcI*vof6?g8#83uh?9+o3(=Qw2ACR(WxI=JnoBP14Dr)ITg0#uY3s$44%7CC?KEw0gKZ!#V&{gJfP+zBADkq$md|E}?_HyteZem50sXFEIWfZyTESGYlV3KT`-JO$0zS|1fV47wOT$fg99y}i z3fjaU4JJO@urzD|;wz0$3**)4%$wR>e3K-5K7tyZ?*kR@n&ehr5O|Zx+Wz955J>mT zw{2eO;>3c{nZnH*-E$s)CmASu=(6`}s*`BQKMjF%luEtX8cqn8#2?bx z!)uH~n^?Be{0<)ZkJ`o4_pf{VXLrdhGB#Bx)?)VBVKl+a_XdkHUY1XbW=5Nfkm#ga7Ll} zkwchz?#STP)gbYzqicY#O~Kw7RrvJ5q70I~q3VxA7zxSDdDQ!tWUT()&Ys$Lec0uH zefteZ@c4R+#DhNH#{K8Nm;*pca&PTkWNYQs%=$?YZF&iYLSP$5TDlMVzSRo4cFg5P*0GdPBJf zLHL#@HGA=szr+uh3Ki4;_U%G;(9;G8J&EwIzy@A4paa~u+XP?$BaD-MW1$&T@h$T_ z_XcLw$BL_+xJV0zhnP8Mg)kKp;@UsbA7zrFz8x_58v6pY?kL*B3r`!)%e5 zNd4)6@QxC{4r)j93bsQ&=I-mV)Gne@CBu2oQ%t$Cp3>{mUOQheKIb7hEWRjj2$Yih zP%DYm7$ML3HmBgpfXa@E9xseW;e7kVl{0dN5rml$gvdFAktCDTj2}8yH0(8hHzID0 zyAfg?&$-NOb>Wi0ur+qN<`QQXUac4pFg8qo`z2MIZk4_LkWgG!rHr>4FE^1`A0-vL zp^7hid&7T?n6pe?i`KY)x9hfhd7hm}7!X9B(7az5@ampofcS+W%|=b|boZzR%jI0A zZ#B7wXAbsQEGUZ+oj%z*BkI$s1(0XQc00U z#;)WZ)|xJ9nQzebe_rXgl`?Bc*Ml1E(tbWI_ajxmdS=Uj5>Gi7-&U#3EspBxntEu~ zckC#@9xQ7bJXxGUI6XWhH98_RFqlKo(57X7(>__BB3un{R$hY7$*0%_V%y=jPNzaqpkkKLHCq&EU8sv*ZosRY4~QujLsp|!&%+U;mw&g zQk{M9=q1LShx1L?Ab5cv56|ZK261T>c6{5rxeq=6%XLg<*zoOyv51DBFX}&tI*P_i zhBDiJ?fmI9^zBw-+~tdLcw&8h0$Fy^U$D@ZU;$tqhD8{!$fGz80U(UVt`!&W7991l zg&v8fs1vL3f*-iDNCT?$KVAn5Sp`BQ<4fk~rZm*gDT1!jX9OSwEyW5o*-%CX4ZdJI^Es`K z-6z(F#;yEUa;184z*`2%Mg>fKi;T=jrqlhyTj+|-@LD_HByyKctBNlZbJ%U85A@>W zm|1nT{+A{|GXY$5Z>V-r#K$DoCtY#8^ER->QM!UMsiZ?JP5GbEYn)WD_f&tZV^1K&j5g?#YBpP=z>;=_x|F3lACB1=77UPZ! zv+Fs0D2iDIDD=GkbwpJOKJV+jE2B4%%D~EJkLpf{!)H}Go0!B z)3tcyemxm|UaAn!ZC8t~$&U8w26Mz^Kfv{!uL(6G9*C)7D7_TY7Wt_et(UZ1K`}$t zKYhR&$0Wa|XI!anoE0Y?J%g5M06-`YE9M7rXGZuD@>qPakB|~+$<+IV&Cuu1A6e?~ zS2;U0E%9KiorrSxd?<*5@3)np?F;c0BZLxyzn=vW!4{uE?pESvg2%Pr>C+NiC~RuS0UK)vfoo> zRl?&KH(8yr!8i7{Tj*mIVJ;9YznaCq-MQk6>J)7}%rYcf@AlP?K#?-KntSxuFT|Y- zZlor!^jnVV*q#NQF6Hbzd=-&w%CUA)us65*FyQO9x z1o*^qD2(4F5c%BciKsTc`{boL^1K!0zo87lGyh6+MZcNREa_=Zh!UnOYCEU7i?UkX)TC%grN&`UEMP`RG1k|h$?cQHV7gYkiX@#%Mx?LRbjtv|5wKeOnTr>D1qXa!0I?M_2 zCZ$QEuBW#R-4!;d`w!V0CF}k8(3qc~2yc{Im&@mS;%vH<+Cu0Zh8ak8#sgOyK?VtR zLgWk4L&-~zYL0)S1E7%U9u4mE%GmqN8-uk~oDtf3>Y?G5jER(N+d z*{cB}j@fEzOttqAPjC<1Of(`C>I`N<2SY)>L|xuL8{nN+HtUxmvI-9F`O2-2WtI7r zOTYN_+d|vuHO7(ntbn8R!<_&h)yXzC=|h8yGLw$!@%i`w#I&}@$I6N$@ntbD4wc|` zKg-;qJ}EI~fEi6vaJ27i>670+cFS+in-vtv zB8XrQeJEg4g7=iD+j<=O3`f|umuFsv6|w0q69^kaIb5I8_041N2*DBwG-czpHgj6p z{?22*!jljCRFP5pr*jLs5I8vaQP0TWg1H&&_?ts$qG9{O$7?HBpGY|o0N8dU6P?TN zeS_SU@3M#gmB3YZF1C2B9_3m()yVQZ|C^^j%63Mm`32#Bv2NenSlo6OjKj$~e$5F{ ze#Z8zg^m*u_%jo5B?0K@p;i4MrVcj9=Ra-O@~L3TuVBnY@1XQ`KRQh+$s{ZB=PQsA z`0}DbH^ms5DV^K#X2;U}U$r30PPyPVmm_0LX+^BZR{eSwu`l-GH*JTJ^9M~eSoh7> zt;{B_QbB%y(nMUB?~`yD(o}ciZd$B@bRIg=2LcZ!S`vx{jzlA|^3)Ag$Tt#fI+WoS z5&5wi#FT}yMDH22C+PqVC0)p_Ll~&87E_voMInL{?_Pl$iA^5md>FEees~BscKdx= z&ezg%UA!T!$shEI1Si8y@Rm!NY`*?=Xw3Hmz$ReF9Bo^ac2 z$A<&a8Vr|jM-(!VC5H8G3N7d>6$`=OjjxnuQm#W4!CVKcE#-HAIqq9y0!>GUr8?}@ z7urIv>TMF`Pa6Z2AE0@D{f9fMgHlUmCmk66K|$KMws>D#8AQ@zf@D=+Mnd@zEM16J z#fEI(4`;kWIflEQfTn5y#MKa2siFfhwi-fDOr{hgTxLSsSqjv&#fOP3x8op>`UVO3 zf)90t?r3TLEj|iU{*M0FF!T@q_MfkjUA_D0z+cF*H?yv1k|N{csg$G>Jc|ZZ@+Bca z;)r60V(A?oT<0K((`&%a3xCN4PYwa%B1He4b<6=n?})X$T1lQr&47i;a}^mMo*w34 zv6IRrV+EIxW%=?6ft}ht1}^c=P0PWz9|!(`<{iiiQ*`9(9B%CP63~88l?0>cS7sXR!Blju#8QtifOXA1VxPz705jx@{{(*HW zm+<_yp?ltYXE-c9{x*vY7KtYVXazTWQJ4E06`iz9 z9_{W7EMF%dFWXcP!!CwB`}p@go>m8lixfy+z#|x9WL$C3t4Mowwm(Ie?Nyo~IEEggVr}EPL)#e~$sT zsi9pAoRwiiRFolOt%X4o4Ke^+6OFy}OW3x^Sv9C5J}MkR0G3thkqtKb$%Fu{Gc97u zNC#j4)LrVjRUnqqlKW~;Tvg{WeRSnkxpVV#cP)wH4fiwujs1DtKAb9_vkzvTbWE6< z;san2mWG3}$H91%=MwL}fzI8u+XiaR`gyO`lGtQ84G|KUDguO0N#GutQ259l)z+vc zNPz=ZPGZyoX#1@s2F3b5t8?WSXJBVBIKayZ+QEIuX*3Ad%;iMJEsWQ_AInsK{Y$qb zctdp~>tMK$Y7Ea`zp&>6-(d5e6ZBgUDHSVOCufQ*6xs&K1Zi)@C%Rf@E;loC28`bu!`B*xj26OTr;2) zzb>OvbS@`tjLyIUu-L(#76LweWA2)ZxQc%1ME4+pk7)+?6E}`02iuEa0NzWWXHCGk z{%=))vZ{nQM3&VJElY6dHO{;Xy1hpMYm)fqa_>v^jv}hK?;^>9(FBkRB{sFyh zKLxL&-)ZXK1UO;?xHJy=ajs*@ht>PiAGnkY|{n@ zS0j?rT zT}~TZ{pPZXQzl=zQyrFJnMa33(gh@~%Pq#noZsJk7CDJc?I>Bszv%kvEny~0@Gm3) z{h7v^x-iAr7N`)BibI}ghn%le1u+--3Ot)Oe9SEM`1hJdo%=a~o5IQ#`=!MXYeVJ< z#KAY*uTTj;#I{|5RXovZ9)N`G_5*LgH}N7Zg+#-wZe!>ml_?3AX{8dzCAF!#=9u zEJ79gs?`yYx+_yC^RKVO^uuu|4oKApINPKwXQ@cuy$BWmSC_@tewG36$4N4ZV4Z<5 zeJY#7V(#xA6lNQ3+PDh4?=o;Ew*M>c-@EKO@C63TscvaCHGfML%7JI%JP45sU3Oa$ zieDBUM9BS*oF3B|-%_mvOH2yGH_>0K%C05^8%PTYhxyiR5PdT7upq&fI4dHr{`JdT znckAIBUl5SBIzuLEj;NP=4j3_Qh()gB6z#GhR8YE!ZBaex9^PdJVs3Y=*q%%ktXKY zYlX#HsWUzdA`YT&!4G|~uKB4sIUpTEm7bp9rS=M|7u^3(qc;(H?)DyVip8!*^fUHh>MwOm=qin`DU4tC#?qO`rUU_&Bx!Ny4x#T z)*8$#U!96ip!r3Di(gEqf5urTL6z@}YM8A3xPKYmLZeT_{_A^r`mq{L%zx^J{({Oc zYm+ywS_8h`d#$J^hUz;3Som&3s+)o}Nf=sZse%vS=VZqu2vEb>3Jwr?c{mm=Ek}L% z0g~1207<$YCs#slDB&7KexNTQ(-ASStz-OY^rQa@g}FTJyL>r+hz9nSG#;~mrFT(o zSWDUktTWZMNSW)(@{wZo$ijR2TFN(nKRLHC$qVI~p<(p$5zsJ7_{P742nYDa7ZJXW zsu1b>KcAy4%uNT0KCgH&qT^HzVeb#9iN+VPc%*`>?mYY~e(f#tw~&T-ajE+-xB9ZK ziXsW)aT?q91TEMF`m}fuvn>}Qn#2O|H?)7YAg|R`T%qP$N!6{D0%ql;Z(=(q^xlMH zUfvPyjy|zm-;iaDbxPnr*k!)J{(mh1^&@~Wc1Q~Tw+nZ({HqjcK;t^A;YtOwUl zB8Ub1 z5hFa2yOaFZ&z-VF9aSFL)_uG$M#mnUYRKXr*RR|Mjk5 zs+p-N^I^z(+`sUFmqTz}GcD+IVKB52zNzobm5R&z+*DtiBh5$4(X&x<)Nt`*sPjXU zDIw6F&eM^dgpnroOAs25hxWQsis>w6TUQ9wr3zEwAXF_Pc+WxDE31pW%lS&3<%xc@ zK3>2cCLsfsRa!`E3q{>ERlau&d?sJkpT`hvhgzC}{19=Pw&H}TpOz>w>NCYd zJ3z6wt_i|h+1*axY~cS`z?eMkUZCKYIVZh0wV{geeh{E3;Zw~h#YT7 z$mdiE&%RkprvuOUBcA@IPnt}Y7(7cEEb2KK&Li`k6p;tTEL=QaJL#UYsvNv?h zr6FvejhDB}dGq|z{S@(hv9sy;ELh6~lIq+}mjcw@EJH0gD@2y))RbSxO(wPRUc^2T zPJW8nN!cZvxU zx$K#N9`)| znL8dW*%*MC97vjuK8*5`Lm+1{0$P#57l(@(C7({mGvhD&BP!p7W4-6Uw-cPNsMOK| zF@hfdQeFR@Hc_BfMSw_Y877!lBB6^*GhsLzr4jdKqS(P!hAUOy4Ig!>nM%f@`EE5@0l`fSCNuz=cs z7C%JhXY4(w`QzU*`3fo@wVpSr(=?JH`tV+W`1(YFcGp!g%(20p3m+du`T!wG7ron} z=g=0UH6if54ClzSi8Rs6JExD(+U;pgQ=h;$IMybO4{vNck$hspVRx)lFj4hdk?SSH z)MGzWf4t`(KVc6_$OiW+J8yfhL(Rtbh!K~vpj--~o>gM(A0fhDPxHb<=X4m2hp~uoWvV@SAC2@*VjEm9zhr zuDCA9y)DgsStTl7Mczk&4W!Yr5lW{nSvwRI&4^f;BTtakOya(hnzskwLRJtaA@=i4 zCUxAjUFjARrE-e*Kk1Ds5L#;*NpLQxN-sE0{fpU;x7DWXpIBCJ_i65@Y}&()S|9Pk zlG$B?iOAKqAWC5%eV2d`E}Xms=!;)+kUDPtUwr*Kl%ehgRKJuZtT`{0`wwof>q{L& zL|^#K`0(O`=*ArV+?Exd&HQLvj{jYGtD3wMKs3Dq(u>4kD~=#^-3n+nmL%a|saQ0b zS`Fb*-ynez{IpTem7@7~b%UG@W&(=25Y578*x%}AhkPf>Yd)v<`yBW57>i)380uHGe+lR+ z;bUdNP2z*s`+}pZqwOc0PaMUIZ*Ud@`oyNC++RR89`;`qxT@VufRO}VOEwcW1^kx3 z0}rG$2+W6!LTcLpCSpCJc)LC(!mXQtF9hciOCguH(E@)z2i7lrv@e_-jNUO11G(YL zs~09W`dff>F1wk8{#r8jrtTdGFE1>-ZXL&nhT@Tc=rQiCP5ORT^pX5j%PI%cN{Q7y zC;yNX>^Vj-t}+Jb|Le*5jB==;CyQcC5_C3~>91!eA*deYkgqP+;Ube;5D|+hAjVDp zoPX$}i+U52+d~HRE{IwmV^bYKpQ^sEy{EcSY9wEf&pc@wdSJ+|6^K6Q83e?I#O;4As`nkuNWE@y7&N94#o)zQ(S zctTGKm6w?X&1r6IAgjP_Qe8Q3R_wPZ#kW0dtgQY-X14*$Ug8Ag=D!{cAHFX6UScW# zHz{W8QIKNDRAtCX{{@Pp^waCh8`1cEDQf*!c_|1TthYbpFp|iRwL%Ue>9vkJ`Bu`X z%h!9(VY?}eVabiY7I?V3$M#ax08)jfrC zasS92snTsPq%;lPqSB+ywFY~K=0aM87~vsE7J7Qn2w}fK0{y!{KBfI6wW?s4rIeEb z-~vVi!N7?W;I()o<-hbWI5Uz3;)16D9id=V`1o|!cyHAfei19oW1QWa^09$WP%;i* z;so}jdaqhyt(7;}wIP&qH8E~DbH}kXptmK>HU%;MuD<9?25mw_dZUfpL#s zaB!g?oUF2(t;B7r9V8~2h!_`RhVE=T84#3p`cK@b)6mFJ@LeeTx?XXF_iSeJTsG{r zS`m8Xn6_Nse_();%f=Ig#JksD7C*C)4^*#^tPfxT@b9HC&zZwwGb;lE|4sBia#(P= z95j&YRR3D)ac)bL!gyMP(bLOK?a!~^P#7argBf1xxwR3x{P^2YM>SE4TaE${(13&} zzhB9cLx<;qc$E=?5%eqZiAXWXHjZZ5j%`86e+r~O>v#Em*0*RD8tV{D7Tw;b(UC6h z)>wNEdCyn|*ab0G`bTK<_Sq;Mu%&-l6%N`nzHzc{D*zQHTtP2}EQzYI)`t8vt@Y~~ z*bW{7j>SR{Ea<9ImT}*S3UNNQK2stl+Nnn&m41d8h?1dKH?C!8oRblL&dS=YIbpksq?|RDLDQKYJ?(m>zwKq z)I_a67a>ak0yrsf$Az%A z(oDW-5JHq3n~Kz9ZhaT?k@gWG&i|g{jHMMAiE@T$S57OUnVd55Fk*Q=jb3 zsLqdq()a31?`2k9Pzd=j?CGtO_DJhyHDLj*UW=0AlglE*7H`$t`C}vF@9{wIUr=<2 z{7^Y1nM*v>63Go%>px4NWbwg)uqFhCX z_5~vZV{pqe5_H7-_J{SeI-E)Cd`a2V7b2r~KEO=i%z2U>U-uQ3=A{t>rtaH73IU6gX_HcHIn5HT+5Tc|FbJC5`Q|>UUk)clp`ssTVieY1uUa z48Yfa`A_qJS61oAXW^}&l9{v{uN{lr7J%~2g2hf%k4E!r|M(NM_MD@XXDR09Xh<{s~)gG zzQIy^(?#fCmmdG7Mp;gmbcZEhkL(Q2vRUD%OS+)&mR0HreT<`(!=VX|E5tjEZ3R}o z{bA^Vf%Iw*@&Y0RnC6C53c^2*Wg(eC&fG-e3CFG&JT}N@-5PZ<1Jy6Vvz;l}+b45_ zycU;h;(DeA2+AP(pBit3M@JdrbA3WDrVJEXS^>9yXKJ%6m&FicU1PTwhUnhIfy3wq zgpD!5eI@d)SyCcS@Q0a!rJJh3Bgc=-ES3vWHSQ;)9Uqvb39EI= zdozZMoALset|a@{bDkrTua2%d-5=J5d(~Q-uEoRW5}q0bS(y7kwkt?J7DFSDts3JkwFmV`YTJ3d$JOz7YA0&hs59@b0*q2gJmg4&l>21jCA zpAy(tSJvG2eF;_CL}Mnp)36JAKqQW85aZVLdkH~?q!SxDuvPbEB-FDRK|nt%B}Hkf zOri9yc_+7vjTMcEBd_1mGLuNDdnWU4?bgV>0AaOSNvLQXVL$&kv)N%qDpplTn-1uX z5bXUVzS*neIWhFzn_bSi9vgjpvEHe7%j}fX61~IJeMgB!p&AL*RMo|lRl?dwlY?dv z#P8Vm5I|X(>7Qn=P`(xdw@|Y)YsUy-X_ghdcMl}_e*ZXN?mI*&YlZ`JXRyJsj0V|B z0(}%%42ldmNWGY;zuZPyZE<-TvLC(&C!DyD3gBb8U^Ah;Ke9?y_XPe7P2Fz6ru%>8v7~*l+>4;%J_JAC8Es~jY`S5va+u*;s1C%~>&dv5t5{%>I zPx|xg>vvz=jS-k=kNcYy>iCBlMU(JIW-#O4rnbpbC$Gu|hV6LGI_wpri`Pj+uv#!d z7J+S*YB7?|*%6~>WdSi4@dm6oqQ9_(#kmy6X%63C0=J0gRY)dR@c4 zJ_xt46b78wxIC#BU~a|WRva>Id1>q7I<8}?tOeMzje{-b|btb zF@3RFZ7d>=6^s7iX_Uki88o8X_+SKl^e`}#0b)aKj&vx z5c{KLg~|{{%;5uR*w{F4ZC?ofJW2`q&x!pVrqnyX$!KU8w9A!r(*Pn@P_QDCm&g3Z zf(;@SDwGl@$Usnw^-D#TGezE{>G+hBdGZ%#X%s@Q%pg@y1s)Wcpk((xo}NL5S%W+L z8W~FL8Lu9a_zrzvWc%yMRM9*=!vudJpfu-0M5Ni5IjGxyb2a6Z-N70bf8Z(pXQH%c0tcD!l^TFuL&nVTMI$%* z&fJ`fwICG8u(>b%+LG*O!5BF4)C{@|Xf`;qp^HR-V7K%0Jk>>DrkQ;{&U9LZ5Bw@~ z#%K=tL@5m`68JR9@U-sVvX^bEM@yTynZ4^964X4aSNZP?`}qrohq|kVyJ8}bcCShj zxmHv#CKQCBTp9PybkMpyv?g_1D<)Uz@&nO-R#d7Yfo>ODv-%8BVp-(X<69DtVMoV5 z{zc;Xk7z3*do(@cS4$Bm$N%sj)|c1JPgZ}N(LVcW69Mis@w=nsG0I$yHeC4n%xM_1 zxd<82j-hjY>fOgzx1OUsAz5^z zE$-*&d(Kbut%WazA8zp9=+h%aj`RcAYwbXA!IvIRfzLFtr)>W&!6)l5#A zNDba$yU$Va4!57Spgu*diN;x#j7P7DlDSa#za<%njm2HQ+vY$RS-~x-jE>3$K}{GV0JI%6ob>0wYHHIE^xo z5Em$eBK$QACM1Q*j2adOoet|PvLoq%LhxZm>{We6bB~Z1Y^ZD!>_g=!MWaS+cp3vS z+{diXqpjIa(dq9=v1z+O(fs&)^?iEcRr@*_|F>9D_bJ^f=k+p8jFgvUa=2P{LaF%9 zVzQF|nJgCH#iU05eBrE?;hu{%&kxFwfzy+KS03=yzL7YmGCAXfv41+5GZY!K_o%%} z)mT2~D=biw$Et32Ke<8WiQmG#E1SxG_yNn9J+f=J2Sqf^`<+8y+|n(D&*+rm5X4aBuq-*( z2t9%>ILK50u}2AZ+>}NZxi%n4CeLI`A{ZpRkKrK&PQ@#62!n*`E&F< zST}hBS#7aP{F3@f@Y(6xmT(76I8fi;UO3v*NtAI1CT1z$=5K; zh9)#whNlq^#!UVkj9&ki4*D-4!&Kaln2K9_nYYa!K?jDb*mGr})Q5Yi23@EP!347O zaTb50rO(*!cY&bN|8|p8)x=RFjUV z*_)K?rqvcc;?T&f;7DrFy2>L)^CsQ8FdH(q7JuUUT0)~>!;Smljca@j+=0q@5iqv8 zgG`!GWbOQS@dgGYVUJn8WcSM5{@ZBjTyXzytF5?swB2+lczWL!$RW z12P%$7Gntfm+sT>Wt$S0>RwxlWh%RK#8oz>VjZ=C5L_0NsZ@9FcX6<%XR@e;NaZ|X z3r~22GNxSb{owV#=qv9)mCM2yielyu>Ir0f3?4rZ4-Zp>syK%)Zd>Q2xXF+9J)w^@wWRicp(xVs_4h6+v2XYV-X`DXL3{|$crea zg|%zUr9(V|Q4U_mwt{0vHF}0&VhA8NK@=8b3quiDIaAX6L&-^vpS>6)eote@rX+B??E|6Q}L;m8=?TI_jm#J72iOh#o;#as8Ay)K8f4J0FwVzlo)kPTzCl&;{OfsGYV*b z3zoW0vGKHg%cZN~5R)e{0pMfn+zJxVW<2XlSp777N;O7B^c2LLW*X~FIzS|LoVs8f z49fJ54n`RP>$9e|m7|p^9D+qbHM0v9=c8GN+Tw@@TUhOZf^_KXJvOOZ+x4f!1(St@ z7jwKy6GC0$fxT?={h`qRE zpn3B+@JPbB0;=fIMn$j*Eix@zFn2D2vQ;?MOrk{%Hg^x0QD=vh_0@dE!ep1@NRt2) z=4<`TmwFkI@E@T>;Cqru%}(o+fu4tAOkw4$rVpVMwPcEGT$+;JQ#S2D+j1a?fskCJ0SdovI8Y5y9Vf~EN* zyiD4_fe+b(q~EQS9kUZrL&20);Lc5QiQ#x~#|PnToM#iKJxVlm-uKLQavsT?+Vefr zwNNPG%eOItFyOS8w`FnPTC!O-;PW~ z`3dtNbBF+-u>H2i*!imM;5sR2k8RMRxp1ST;X0`j~C5CA?{b;+`F(z?zY=tO4W$(o&L1hPEYhmTGx{0gf)`Ku@?rR_#EMJR+|ghk zss2>w&7QpRdfYdy9DNGDib%56*e)^iChem2^mP`3bg@RZ7AIo_$y9PCvPxYkdLiPzK{Hw6wK3jB7_0zuvX(u;><&@U>GBxnOOmOtt+n zxvMv~zmh$87Eie$%RZZXd2eT2+vI*ph4S3l$|NsLX8iO`PxmfbG%j2nm=Rk!^0~*~ z#|NeHtvDtl&E|j6XWjl=Mv0g=orJbk{3JlX{+(39*Ven;D`H6Fe>K#p1;MPMR3Ay# z6-0V`mR_nG0VGy$%rCf)t)%8?#>$9tsC^i@yy)f0=d;W6*` zE+xN?{CIrZigmAX3yt(t zQLWFue!RiIv-&X-^K_G#k3$iw!Ry4LKJE)GdzRv0$fKutm)D3<=h~m*pT71uB785j zw4-H=t!!I6Z0oiTjl5TSk_xH&^pMWrMP)XDM?SV+n!^IYL%}8^Y~hO73_dP5Y80;qSTVR;A0!ceR4y?87+2v$ulCv)LFNj z)zdBHEzaa0vg^nCt-Uc;AMc%8@Q6{?BvFUqD<#I)YB5jc3HtT4j6so{rjt3r+#=Dq zqGT76SM@1h;_lWxTeuJZp3#XO;qUW;a1ya+$j1DJ@ukn1)F-XmQKoz&Nx4LSs^0a- ztqdr}Ih3LUc{II^-VU_nmBR&-v*NO&r;qxl(w~9E*~bGyK;Vx*Bj4WH(C94$t2SvJ zH!F>{cgA@boA|WtvFr5ZNLFs|Q-i%7vWUO6X!NOHLg!~Vxw>z(TNMEF>W_03aHrwD$~^1#p$ z{{IDy4RZ2knIyv1*+rbcGKZD*W?%cdNT~wyf(25| zwa;Ij!})7-5Q4S_zkc?Gkd`(sf9+Gz()HU?$~X)B?RGmhu|N6)^|py&zq~(iFbJSm zD6lJ_&srXNC7A;9Ag9K{32CzAExeOM&+NAq z_`?D=vGmS=S-bM`s|NgD%rCbI{+%FzeFflwAON6?1a<=GTP^374?pxHPuGq<`SUC& zbbW6i%c|uf9=U%CCuhd{N))Dzh_JA-f%(NX%q^~AVR>U9bucIa0LZKb=oJR2eR5)d zB!Ta90OuTIjVi`R>o_t}N26ZW_niMM(%v2bR@R%ibo~yl-C7z5_yAErYcH&w{(t|S zh+?PqrAXKD$8juE;O_NFC!NHH;0{^xz}i=D&NCnjxsse`e=baQXTImezWEe6vp?(Z|GM=f=yw z7B8K@ES1Hckx9GWpnjL-+sC zPluJ!kFl)+UC)MUWgMR##qsH}Ejt=*$ApwXtmTdiX1X`=toHK(Z>0X*!Wu5$T)^Donl8`r z?#KatfS9)y{&?f!pZvaV)RRc`rNQ57sgb?VAFy|V2o4nZfdfhaK+l}Oc7cCa1fYMc zdi2TrhfaL-Z-&n;uS7+_cs*dxm+-Ho*PN!pIeeLySj@KUq{DX!I2NL|i z0Ve>($VeQV;FEuj16IGyn%_aq1?er z^Q;$u(<;Cx0Knx365iZDMq)Xe2r{q7vz7HG=9ktnzqpFU)y>q`e)|KxDc3Q6i+5IE z+`RnUXJmVI!+3lE(jx&O)Oh{I>!&mPJN4^H82iz|XAnE^1OQkP*h>P)J72ooUuHx1 zf9%QX@;(i-L$*Rim?j>zjJnbq+}%~MYm00MC?uE#5vo{6r1`(-I3VZhE9lR^mcfG>^Y z*V%o)MIT{r2w-12_Y7+H1OXV!2?_3o1hyiUOs~JiJS#S4Mu+bIp}$gWOn-uh z?_t0(JXFE(P!+@VGOFbwD&-=os;+gRIdW3$yxTzggnpY*yM(1QoO$)eSl+Sk7EdD&cC@?AV*jEvm(LC+W? zuMdrO9?O{o;7=Q274VSr*7pjZqM1ObX6S7n6&T-Ru(NdRzaf`}BbTODjR+iJeq z!FscWW-A)FDRY+W_sdR^K zX0-=^IXO!v|J3&b4Eo8ItL;;sP03z>^G-poVPiPF3I)V-)h}@gFxDp#>;~v}4E){?{xSF{Uqs z2a*wyPe$Iu13tQez0)MnivaHFtp1?(o+1F>bd}p6VDFa%_8a_}BmgKP z@I1KzII_sV1G6djpIsODfdsIv_c!47mL&j0QACU}ilWE|eJ{Z0m2DUN>FK*x=J&Gq zECCqvRKZp%;2?m%FX``1k+qx!{48S7&nNwYWP(3U3P!$bkehNJ@V0D=-`$cxFYhk_ z(0dX1#@_P;;9E|Ju%F=H%Hwb4^?MQl;J{ue6w>RcP)PK0-ZB)3q9{QE#PC9ZpZzD#Q*(}LDoyLWgei%^ltM3hmwAm?2rKV$UIW$R}o~*r_0ji>9V}# zL2QsbfSKPe2_&iq4*|dXc1QsGY~Bb#+?)0j_wSbXH?~tMAYGP-;SWK+zjjCf2iQDv z=#>!kr|J2uQ51Qg15;2I8X7wnJ#-bI!(-e5nq0%^?-H#@ttv%B+tXYRen4?8rm*WHdzIx!!!iK_k+L2(X853YoZO1Iu zJ8axI@~LnC=y&_xGk`r`dMxW^bHC0H49K(dH9}z#Xp?rVn6Gxx%3^3J|KocP{Qj2r z1VH=AUv1ekS~xXZB@hV$ECS58$Nw(0=+9q@j=qiUS^RoGHD0W_t0CqfgzlZgOM21-E|f>21kO z_NivQ$H+SUhvN7u`L)h)Q~zC)Ha z5-;XGnRxa1H~>mYeAg!JCy1h$PB$cBDH@dp&QFZ9RBh3XG-HFQ{h$5*k-Og&Kqpp* z@`-5MTN5(`-H?2r&z7-%wvG)jQpixqr?G7t*Rk;(8^_R;rzSXeVVP{wkxAeA&F$ZP zv3NC&E7rh{y-)7(MDXW)+Tq{hGYk);8Oos678ij^=5-w zpvjKjz{bHO$KEQvTyM7T_{S4pSX1bV4s@IKCpQmgdE?X!g*e@L@Z2_u8r^7_>P5}FtH2+!${x_Z6e*5!U}6FH#%5`BA0Rwe&V4Q z*4&CU0lHy(e{nFwxeFE2Nr$wbpc{tl+_9NhT9=61xQ>NuTO@1)+frDjh1EAi#EoTAqH)^!2I?jIVKA)U%{1Ar)sQBwA*9a25(J;%baO>EP^G8BfA zC@GObAOImHXwVvJ%?_@uAdYvuQ}42qaD&m>V5p=BqX>Xj5Yp*J2%!nuZPEjIVyy|I z7%2sDtm%XiQVK-RU=3(Av0jZ%7^8%OILfT+KqR9@DFt!we5f@7s`WOeGMJuOAn6#m zj)iR*m`e48SCzE}tu=AH8c`e*MKNI*A&Jp(m|hoP?k_)Sv^!xdyo$~CfShjh}#cTALNpyLYNFeZ#OTQ`rUa3>1cMFXU2`%2lRj7io7w04!6HuuMG1 z#YW$9V`G{I?M|1ug%!#x%^<9u`{TLS4j(R;%k#hzPy*((*3r5E zJqWW0kpWUbN+~P7>#nbVl0@z!jX)O?wyn}BS0QEG4cgUit2WoJOdKtpJocWRiIv7qOv<;CDHT zd(ZiC&-plvh;UT-Qch9C=xZmiZ<*Ob4-?{(4>6uxi_7|wsoz`&rZC)~zhchSriivRpDC8b_Efo%lj z0RRYu1Gg@xm6`YXQZ6wDJlR-Xl?C-$$7#W8^heRNdvJpONli+>IRwP`^0uB-ra!jb zJ!xmXiMix?XfU9`!1iVpkxYn~lsNNc@7?Yt<9bnu^7xV$I+2hh*89N0zP@2j@wu?vxV|#m)1(RPobUh+1o~iR3pN1!0sn&H^`hXPu>b%707*qoM6N<$f~$@n9RL6T literal 0 HcmV?d00001 diff --git a/deluge/data/pixmaps/inactive16.png b/deluge/data/pixmaps/inactive16.png new file mode 100644 index 0000000000000000000000000000000000000000..10342be186f9e3f4a2b700a54e6d5346e7b3143a GIT binary patch literal 588 zcmV-S0<-;zP)Pg-{@ty9Tdk*Ko zy_PWshY&(AO6kWu&!0q5l%WJtCKzLHJ&A}=60vkIlYkJy6*e^e*aet09lsNAPBw)A+DS= z#BuzT5YpN0cIV=YqClSKb>H_topF^`t929vK`+ZPr`#6+wAOGO=jM1ko`qp}YEw$@ zYpuP}XoNIPkt7MEl<4>SNYfO<;Sf?vFveaBAqa6@_u+cIM!jA~v)ROCGJ#SGjYb2< z$HyRqz!(Fq^>xQ_9@|Ax+$taMUyxG5b=?;>fc2$n0LB;?V{BsE_AdaK&*w;z1dGK2 zVHjdMoq|$|*=&Ydt%gda0%OciYcLpmUaeN$%WC<)52e&QTT1zcbFTjFKfmkU_1a=zY#Rt#5{x9e7>o(Bb#QQC zGDxFF!(^kziLkgB{sZ@_QKMke&B4VO{{f8z2T2455lUMuExlf^z4q?sV5nG4^t(MT zpFGd=%_F3g99AZWKcus0In3s>SoBHlPsD;eNDMH&XCZ-^{+lkB)41f*g(n|n;=vn>BH;b@3wR{Li&fRs?GqYp=UmB|tL z4Y>Z8n*oifY7?A#0XFiqiYv6cU^}GZA(jpR-{;|l_)vs5JNsy@{cV!oI_J|<=w6Lt zp+MI^NG! yJY;kz!n~?*!SzT0M%UwA+2pyDyv}E70{j42#Jhng#r9qR0000 c #8794A9", +", c #8592AA", +"' c #919FB5", +") c #A5B0C3", +"! c #7688A4", +"~ c #7687A1", +"{ c #8A99B1", +"] c #768AA7", +"^ c #7E92AE", +"/ c #8397B3", +"( c #92A4BD", +"_ c #96A8C0", +": c #5B7295", +"< c #8B9DB7", +"[ c #6A83A4", +"} c #728BAD", +"| c #7791B3", +"1 c #7B95B6", +"2 c #7C96B8", +"3 c #95ABC6", +"4 c #829CBC", +"5 c #45618C", +"6 c #7F96B3", +"7 c #6483A9", +"8 c #6384AC", +"9 c #6B8BB3", +"0 c #7091B8", +"a c #7495BC", +"b c #7596BD", +"c c #7496BD", +"d c #95AECC", +"e c #6F91B9", +"f c #6081A9", +"g c #3C6292", +"h c #365989", +"i c #4B6B97", +"j c #5676A1", +"k c #5F81AB", +"l c #6E93BF", +"m c #7097C2", +"n c #7297C3", +"o c #8FADCE", +"p c #618AB7", +"q c #476D9D", +"r c #5A779F", +"s c #1B4075", +"t c #1F4377", +"u c #365785", +"v c #4A6791", +"w c #4D6B94", +"x c #5D80AA", +"y c #6C93BF", +"z c #779DC6", +"A c #84A6CB", +"B c #5382B6", +"C c #3A659A", +"D c #5C789F", +"E c #224579", +"F c #1E4277", +"G c #274A7C", +"H c #2E4F7F", +"I c #335483", +"J c #44628D", +"K c #557197", +"L c #526E95", +"M c #506F99", +"N c #668FBD", +"O c #7FA3CA", +"P c #7299C4", +"Q c #36639A", +"R c #4F6D97", +"S c #345484", +"T c #305181", +"U c #42618A", +"V c #4E6992", +"W c #4A678F", +"X c #4B6992", +"Y c #4B6994", +"Z c #5F7A9E", +"` c #5E779C", +" . c #597398", +".. c #527099", +"+. c #618ABA", +"@. c #84A6CD", +"#. c #5D89BB", +"$. c #375A89", +"%. c #4C6992", +"&. c #3A5986", +"*. c #536E94", +"=. c #577197", +"-. c #6F92BB", +";. c #759ECA", +">. c #739CCA", +",. c #759DC9", +"'. c #6880A4", +"). c #5C7699", +"!. c #4F6F98", +"~. c #618DBD", +"{. c #81A4CA", +"]. c #4B7CB3", +"^. c #274F84", +"/. c #5A759B", +"(. c #375784", +"_. c #577196", +":. c #5A7498", +"<. c #6F89AB", +"[. c #8BADD1", +"}. c #7EA5D0", +"|. c #7FA6D0", +"1. c #7CA4CF", +"2. c #7CA4CE", +"3. c #789FC7", +"4. c #6984A7", +"5. c #5A7398", +"6. c #5076A5", +"7. c #6791C0", +"8. c #7198C3", +"9. c #2D5081", +"0. c #25477A", +"a. c #4C6890", +"b. c #627A9D", +"c. c #657DA1", +"d. c #A0BBD7", +"e. c #89AED6", +"f. c #8BAFD7", +"g. c #8BB0D7", +"h. c #8AAED6", +"i. c #86ACD4", +"j. c #82A8D2", +"k. c #7CA3CF", +"l. c #749DC8", +"m. c #6984A6", +"n. c #567298", +"o. c #5786BA", +"p. c #779CC6", +"q. c #5583B7", +"r. c #295086", +"s. c #2C4E7F", +"t. c #345483", +"u. c #5B7498", +"v. c #5D7699", +"w. c #93AAC4", +"x. c #9EBDDD", +"y. c #96B8DD", +"z. c #97B9DE", +"A. c #94B7DC", +"B. c #90B3DA", +"C. c #8AAFD6", +"D. c #83A9D2", +"E. c #7AA2CE", +"F. c #6F95BF", +"G. c #667E9F", +"H. c #577FAF", +"I. c #5585B8", +"J. c #759AC5", +"K. c #3F6FA7", +"L. c #254A80", +"M. c #5C769C", +"N. c #3C5B87", +"O. c #60789C", +"P. c #B0C3D8", +"Q. c #A1C0E3", +"R. c #A3C2E4", +"S. c #A3C3E5", +"T. c #A2C2E4", +"U. c #9EBFE2", +"V. c #98BADE", +"W. c #91B4DA", +"X. c #88AED6", +"Y. c #759ECB", +"Z. c #6885AD", +"`. c #5780B0", +" + c #5383B8", +".+ c #7299C3", +"++ c #4675AB", +"@+ c #395A89", +"#+ c #46648F", +"$+ c #3E5D88", +"%+ c #617A9C", +"&+ c #637B9D", +"*+ c #BFD1E4", +"=+ c #ADCBEA", +"-+ c #B0CDEB", +";+ c #AFCCEB", +">+ c #ACCAE9", +",+ c #A7C6E6", +"'+ c #9FC0E2", +")+ c #8DB1D8", +"!+ c #82A9D2", +"~+ c #77A0CC", +"{+ c #6A94C4", +"]+ c #5F8DBF", +"^+ c #5C89BA", +"/+ c #5C85B4", +"(+ c #3F5E8B", +"_+ c #3B5A86", +":+ c #647C9E", +"<+ c #61799D", +"[+ c #C1D2E4", +"}+ c #BBD5F1", +"|+ c #BCD6F2", +"1+ c #BAD5F1", +"2+ c #B5D1EE", +"3+ c #AECBEA", +"4+ c #A5C4E5", +"5+ c #9BBCE0", +"6+ c #84AAD3", +"7+ c #78A0CC", +"8+ c #6B96C6", +"9+ c #5F8CBF", +"0+ c #5282B8", +"a+ c #4F7FB4", +"b+ c #668CB7", +"c+ c #43618D", +"d+ c #325382", +"e+ c #5E779B", +"f+ c #5F789A", +"g+ c #BACADC", +"h+ c #C9E0F9", +"i+ c #C7DFF8", +"j+ c #C3DCF6", +"k+ c #BCD7F2", +"l+ c #B3CFED", +"m+ c #A8C7E7", +"n+ c #9DBEE1", +"o+ c #6A96C5", +"p+ c #5D8BBE", +"q+ c #5081B7", +"r+ c #497BB2", +"s+ c #698DB7", +"t+ c #45638E", +"u+ c #2E5080", +"v+ c #25487B", +"w+ c #506B93", +"x+ c #647B9E", +"y+ c #9DAFC7", +"z+ c #D6E9FD", +"A+ c #D1E8FE", +"B+ c #CAE2FA", +"C+ c #C1DAF5", +"D+ c #A9C8E8", +"E+ c #6894C4", +"F+ c #5A89BC", +"G+ c #4D7EB5", +"H+ c #4577AF", +"I+ c #6B8DB5", +"J+ c #3B6090", +"K+ c #3D5D8A", +"L+ c #3C5B89", +"M+ c #1D4276", +"N+ c #647C9C", +"O+ c #657D9E", +"P+ c #C2D4E6", +"Q+ c #D3E9FF", +"R+ c #CEE5FC", +"S+ c #C2DBF5", +"T+ c #80A7D1", +"U+ c #729CC9", +"V+ c #6491C2", +"W+ c #5686BA", +"X+ c #497BB3", +"Y+ c #5580B3", +"Z+ c #5B7EAA", +"`+ c #335686", +" @ c #4D6993", +".@ c #26497B", +"+@ c #4C6990", +"@@ c #6B82A2", +"#@ c #7D92B0", +"$@ c #C1D4E9", +"%@ c #C0DAF4", +"&@ c #B2CFED", +"*@ c #7BA3CE", +"=@ c #6F99C7", +"-@ c #6390C0", +";@ c #5886B9", +">@ c #4A7BB2", +",@ c #698CB7", +"'@ c #4C6F9C", +")@ c #284C7F", +"!@ c #577299", +"~@ c #2F517F", +"{@ c #536E93", +"]@ c #7086A5", +"^@ c #7B8FAC", +"/@ c #9FB3CD", +"(@ c #B8D2EE", +"_@ c #91B5DB", +":@ c #85ABD4", +"<@ c #7CA3CD", +"[@ c #729AC8", +"}@ c #6791C1", +"|@ c #5A87BA", +"1@ c #5280B4", +"2@ c #7A97BB", +"3@ c #416290", +"4@ c #2F5080", +"5@ c #4F6B91", +"6@ c #687F9F", +"7@ c #7B8FAB", +"8@ c #8396B0", +"9@ c #8AA1BF", +"0@ c #8CACCE", +"a@ c #8FB2D8", +"b@ c #86AAD2", +"c@ c #7DA3CC", +"d@ c #749BC7", +"e@ c #6993C1", +"f@ c #5B88B9", +"g@ c #7999C0", +"h@ c #587AA5", +"i@ c #21416E", +"j@ c #516D96", +"k@ c #415F8A", +"l@ c #567196", +"m@ c #637B9E", +"n@ c #6F84A3", +"o@ c #778BA7", +"p@ c #758BAA", +"q@ c #7490B3", +"r@ c #749BC5", +"s@ c #6992C0", +"t@ c #799DC4", +"u@ c #7291B6", +"v@ c #46658C", +"w@ c #264878", +"x@ c #4F6C94", +"y@ c #42608C", +"z@ c #294B7D", +"A@ c #4A6790", +"B@ c #567095", +"C@ c #597599", +"D@ c #587398", +"E@ c #536F96", +"F@ c #4F6D95", +"G@ c #7690B4", +"H@ c #7090B7", +"I@ c #4A6991", +"J@ c #2B4871", +"K@ c #41608C", +"L@ c #59749A", +"M@ c #234679", +"N@ c #214578", +"O@ c #3A5A87", +"P@ c #47648F", +"Q@ c #667EA2", +"R@ c #7188A9", +"S@ c #4B6892", +"T@ c #395985", +"U@ c #1C314F", +"V@ c #2E4F7D", +"W@ c #395986", +"X@ c #526E96", +"Y@ c #58739A", +"Z@ c #5B759B", +"`@ c #6A82A5", +" # c #627CA0", +".# c #365887", +"+# c #324B69", +"@# c #344B69", +"## c #34537D", +"$# c #294C7C", +"%# c #315383", +"&# c #40618D", +"*# c #40638F", +"=# c #3B5C87", +"-# c #36537A", +";# c #2E4156", +" ", +" . ", +" + @ # ", +" $ % & * = ", +" - ; > , ' ) ! ", +" ~ { ] ^ / ( _ ", +" : < [ } | 1 2 3 4 ", +" 5 6 7 8 9 0 a b c d e ", +" 7 f g h i j k l m n o p ", +" q r s s s t u v w x y z A B ", +" C D E s F G H I J K L M N O P ", +" Q R S s T U V W X Y Z ` ...+.@.#. ", +" $.%.t &.*.=.r -.;.>.,.0 '.).!.~.{.]. ", +" ^./.F (._.:.<.[.}.|.}.1.2.3.4.5.6.7.8. ", +" 9.v 0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q. ", +" r.v s.t.u.v.w.x.y.z.z.A.B.C.D.E.F.G.H.I.J.K. ", +" L.M.s N.b.O.P.Q.R.S.T.U.V.W.X.|.Y.Z.`. +.+++ ", +" @+#+s $+%+&+*+=+-+;+>+,+'+z.)+!+~+{+]+ +^+/+ ", +" (+S s _+:+<+[+}+|+1+2+3+4+5+B.6+7+8+9+0+a+b+ ", +" c+T s d+e+f+g+h+i+j+k+l+m+n+W.6+~+o+p+q+r+s+ ", +" t+u+s v+w+x+y+z+A+B+C+2+D+n+B.D.Y.E+F+G+H+I+J+ ", +" K+L+s M+N.N+O+P+Q+R+S+2+m+5+)+T+U+V+W+X+Y+Z+ ", +" `+ @s s .@+@@@#@$@R+%@&@4+z.e.*@=@-@;@>@,@'@ ", +" )@!@t s s ~@{@]@^@/@(@=+'+_@:@<@[@}@|@1@2@3@ ", +" S #+s s s 4@5@6@7@8@9@0@a@b@c@d@e@f@g@h@ ", +" i@j@u s s s G k@l@m@n@o@p@q@a r@s@t@u@v@ ", +" w@x@y@s s s s z@_+A@B@C@D@E@F@G@H@I@ ", +" J@K@L@y@M@s s N@z@S O@P@Q@R@S@T@ ", +" U@V@W@X@L@Y@Z@L@c.`@ #v .#+# ", +" @###$#%#&#*#=#-#;# ", +" ", +" "}; diff --git a/deluge/ui/gtkui/glade/aboutdialog.glade b/deluge/ui/gtkui/glade/aboutdialog.glade new file mode 100644 index 000000000..c8799dc0d --- /dev/null +++ b/deluge/ui/gtkui/glade/aboutdialog.glade @@ -0,0 +1,38 @@ + + + + + + 5 + True + True + True + False + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/ui/gtkui/glade/dgtkpopups.glade b/deluge/ui/gtkui/glade/dgtkpopups.glade new file mode 100644 index 000000000..63279d9a9 --- /dev/null +++ b/deluge/ui/gtkui/glade/dgtkpopups.glade @@ -0,0 +1,406 @@ + + + + + + True + + + True + Size + True + True + + + + + + True + Status + True + True + + + + + + True + Seeders + True + True + + + + + + True + Peers + True + True + + + + + + True + Download Speed + True + True + + + + + + True + Upload Speed + True + True + + + + + + True + Time Remaining + True + True + + + + + + True + Share Ratio + True + True + + + + + + Remove Torrent + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + True + False + + + True + + + True + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-dialog-warning + 6 + + + False + False + 5 + + + + + True + 0 + <span size="large"><b>Are you sure you want to remove the selected torrent(s) from Deluge?</b></span> + True + True + + + 10 + 1 + + + + + False + False + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 20 + + + True + Delete downloaded files + 0 + True + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 20 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete .torrent file + 0 + True + True + + + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + + + + + False + False + 5 + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + gtk-no + True + 0 + + + + + True + gtk-yes + True + 1 + + + 1 + + + + + False + GTK_PACK_END + + + + + + + True + + + True + Show/Hide + True + + + + + + True + Add a Torrent... + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + 1 + + + + + + + True + Clear Finished + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-clear + 1 + + + + + + + True + + + + + True + gtk-preferences + True + True + + + + + + True + Plugins + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-execute + 1 + + + + + + + True + + + + + True + gtk-quit + True + True + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + GTK_WIN_POS_MOUSE + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + False + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Rate: + + + False + + + + + True + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + 1 + 0 -1 10000 1 10 10 + True + + + False + False + 1 + + + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 1 + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/ui/gtkui/glade/edit_trackers.glade b/deluge/ui/gtkui/glade/edit_trackers.glade new file mode 100644 index 000000000..32053dcd5 --- /dev/null +++ b/deluge/ui/gtkui/glade/edit_trackers.glade @@ -0,0 +1,120 @@ + + + + + + 300 + 200 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Edit Trackers + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 36 + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Tracker Editing + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + 1 + + + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + False + False + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 0 + + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + 1 + + + + + + 3 + + + + + False + False + 2 + + + + + + diff --git a/deluge/ui/gtkui/glade/file_tab_menu.glade b/deluge/ui/gtkui/glade/file_tab_menu.glade new file mode 100644 index 000000000..8e3f34162 --- /dev/null +++ b/deluge/ui/gtkui/glade/file_tab_menu.glade @@ -0,0 +1,100 @@ + + + + + + + True + + + + True + Select All + True + + + + + True + gtk-select-all + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Unselect All + True + + + + + True + gtk-file + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + Check Selected + True + + + + + True + gtk-ok + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Uncheck Selected + True + + + + + True + gtk-remove + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + diff --git a/deluge/ui/gtkui/glade/plugin_dialog.glade b/deluge/ui/gtkui/glade/plugin_dialog.glade new file mode 100644 index 000000000..68fa97357 --- /dev/null +++ b/deluge/ui/gtkui/glade/plugin_dialog.glade @@ -0,0 +1,114 @@ + + + + + + 480 + 5 + Plugin Manager + 583 + 431 + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + 2 + + + True + False + + + True + True + + + True + + + + + True + + + True + False + GTK_WRAP_WORD + False + + + 10 + + + + + True + GTK_BUTTONBOX_SPREAD + + + True + False + gtk-preferences + True + + + + + + False + 1 + + + + + 10 + 1 + + + + + False + + + + + True + Plugins + + + tab + False + False + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + GTK_BUTTONBOX_END + + + True + gtk-close + True + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade new file mode 100644 index 000000000..4e8eca460 --- /dev/null +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -0,0 +1,1461 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Deluge Preferences + GTK_WIN_POS_CENTER_ON_PARENT + 550 + True + GDK_WINDOW_TYPE_HINT_DIALOG + True + True + False + + + True + 1 + + + True + True + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + 2 + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + Ask where to save each download + True + 0 + True + + + + + True + 10 + + + True + Save all downloads to: + True + 0 + True + radio_ask_save + + + False + + + + + True + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + 1 + + + + + 1 + + + + + + + + + True + <b>Download Location</b> + True + + + label_item + + + + + False + False + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 10 + + + True + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 0 + Maximum simultaneous active torrents: + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + True + GTK_UPDATE_IF_VALID + + + False + 2 + 1 + + + + + + + + + True + <b>Torrents</b> + True + + + label_item + + + + + False + False + 2 + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + Compact allocation will only allocate as much storage as it needs to keep the pieces downloaded so far. + Use compact storage allocation + True + 0 + True + + + + + + + True + <b>Compact Allocation</b> + True + + + label_item + + + + + False + False + 2 + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Queue torrents to bottom when they begin seeding + True + 0 + True + + + + + True + 10 + + + True + Stop seeding torrents when their share ratio reaches: + True + 0 + True + + + False + + + + + True + True + 1 + 0 0 10 0.050000000745099998 10 9 + 1 + 2 + True + + + False + 1 + + + + + 1 + + + + + + + + + True + <b>Seeding</b> + True + + + label_item + + + + + False + False + 2 + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 2 + + + True + True + The maximum upload rate for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download rate for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + The maximum number of upload slots. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections allowed. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum number of upload slots. Set -1 for unlimited. + 0 + Upload Slots: + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum download rate for all torrents. Set -1 for unlimited. + 0 + Maximum Download Rate (KiB/s): + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum upload rate for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Rate (KiB/s): + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + <b>Bandwidth Usage</b> + True + + + label_item + + + + + False + False + 2 + 4 + + + + + + + + + False + + + + + True + Downloads + + + tab + False + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + + + True + + + True + 2 + + + True + gtk-dialog-warning + 6 + + + False + False + 10 + + + + + True + 0.20000000298023224 + <b>Please Note - Changes to these settings will only be applied the next time Deluge is restarted.</b> + True + True + 0 + + + False + False + 1 + + + + + False + False + 5 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + + + True + From: + + + False + + + + + True + True + 0 0 65535 1 10 10 + 1 + + + False + 5 + 1 + + + + + True + 5 + To: + + + False + False + 2 + + + + + True + True + 0 0 65535 1 10 10 + 1 + + + False + 5 + 3 + + + + + True + 1 + Active Port: + GTK_JUSTIFY_RIGHT + + + False + 5 + 4 + + + + + True + 0 + 0000 + 5 + + + False + 5 + 5 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Test Active Port + 0 + + + + False + False + 6 + + + + + 5 + + + + + + + + + True + <b>TCP Port</b> + True + + + label_item + + + + + False + 2 + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Distributed hash table may improve the amount of active connections. + Enable Mainline DHT + True + 0 + True + + + + + + + + + + + + True + <b>DHT</b> + True + + + label_item + + + + + False + 2 + 2 + + + + + True + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Universal Plug and Play + UPnP + True + 0 + True + True + + + 2 + + + + + True + True + NAT Port Mapping Protocol + NAT-PMP + True + 0 + True + True + + + 2 + 1 + + + + + True + True + µTorrent Peer-Exchange + µTorrent-PeX + True + 0 + True + True + + + 2 + 2 + + + + + + + + + True + <b>Network Extras</b> + True + + + label_item + + + + + 2 + + + + + False + False + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 2 + + + True + + + True + 1 + Inbound: + + + False + + + + + True + Disabled +Enabled +Forced + + + 5 + 1 + + + + + True + 1 + Outbound: + + + 2 + + + + + True + Disabled +Enabled +Forced + + + 5 + 3 + + + + + + + True + + + True + True + Prefer to encrypt the entire stream + True + 0 + True + + + False + + + + + True + 1 + Level: + + + 1 + + + + + True + Handshake +Either +Full Stream + + + 6 + 2 + + + + + 1 + + + + + + + + + True + <b>Encryption</b> + True + + + label_item + + + + + False + False + 2 + 4 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects regular bittorrent peers + Peer Proxy + 0 + True + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Only affects HTTP tracker connections (UDP tracker connections are affected if the given proxy supports UDP, e.g. SOCKS5). + Tracker Proxy + 0 + True + + + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects the DHT messages. Since they are sent over UDP, it only has any effect if the proxy supports UDP. + DHT Proxy + 0 + True + + + 2 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 4 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Proxy type + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Username + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Password + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + None +Socksv4 +Socksv5 +Socksv5 W/ Auth +HTTP +HTTP W/ Auth + + + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + 1 + 2 + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Server + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port + + + 2 + 3 + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + 4 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 8080 0 10000 1 10 10 + + + 3 + 4 + 1 + 2 + + + + + 1 + + + + + + + True + <b>Proxy</b> + True + + + label_item + + + + + False + 2 + 5 + + + + + + + + + 1 + False + + + + + True + Network + + + tab + 1 + False + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + 2 + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + Enable system tray icon + True + 0 + True + True + + + + + True + 10 + + + True + Minimize to tray on close + True + 0 + True + + + + + 1 + + + + + True + 3 + 10 + + + True + + + True + True + Password protect system tray + True + 0 + True + + + + + True + + + True + 5 + + + True + 0 + Password: + + + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + 16 + + + False + 1 + + + + + 1 + + + + + + + False + 2 + + + + + + + + + True + <b>System Tray</b> + True + + + label_item + + + + + False + 2 + + + + + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 15 + + + True + 0 + GUI update interval (seconds) + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.5 0.5 5 0.5 0.5 1 + 1 + 1 + True + + + False + 1 + + + + + + + + + True + <b>Performance</b> + True + + + label_item + + + + + False + False + 2 + 1 + + + + + + + + + 2 + False + + + + + True + Other + + + tab + 2 + False + False + + + + + 2 + 2 + + + + + True + GTK_BUTTONBOX_END + + + True + gtk-cancel + True + 0 + + + + + True + gtk-ok + True + 1 + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade new file mode 100644 index 000000000..70f9eebf9 --- /dev/null +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -0,0 +1,165 @@ + + + + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-media-pause + True + True + + + + + + True + _Update Tracker + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-refresh + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Edit Trackers + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-edit + 1 + + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Remove Torrent + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + 1 + + + + + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Queue + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Top + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-top + 1 + + + + + + + True + _Up + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-up + 1 + + + + + + + True + _Down + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-down + 1 + + + + + + + True + _Bottom + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-bottom + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-sort-ascending + 1 + + + + + + diff --git a/deluge/ui/gtkui/glade/tray_menu.glade b/deluge/ui/gtkui/glade/tray_menu.glade new file mode 100644 index 000000000..22411c91d --- /dev/null +++ b/deluge/ui/gtkui/glade/tray_menu.glade @@ -0,0 +1,158 @@ + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Show Deluge + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Add Torrent + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Clear Finished + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-clear + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Download Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Upload Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-preferences + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Plu_gins + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-disconnect + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Quit + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-quit + 1 + + + + + + From a7c93458c81d29a998ac59a1b6cd7849aa21cc0d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 13 Jul 2007 00:48:21 +0000 Subject: [PATCH 0011/1009] Updates! --- deluge/core/core.py | 13 +++- deluge/ui/gtkui/glade/main_window.glade | 99 +++--------------------- deluge/ui/gtkui/glade/torrent_menu.glade | 28 +++---- deluge/ui/gtkui/gtkui.py | 7 +- deluge/ui/gtkui/mainwindow.py | 85 +++++++++++++++++++- 5 files changed, 118 insertions(+), 114 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index b01780093..154eee60c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -81,7 +81,9 @@ class Core(dbus.service.Object): log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() - + + + # Exported Methods @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def add_torrent_file(self, _filename): @@ -92,6 +94,8 @@ class Core(dbus.service.Object): self.session.add_torrent(torrent.torrent_info, self.config["download_location"], self.config["compact_allocation"]) + # Emit the torrent_added signal + self.torrent_added() @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -110,4 +114,9 @@ class Core(dbus.service.Object): log.info("Shutting down core..") self.loop.quit() - + # Signals + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="") + def torrent_added(self): + """Emitted when a new torrent is added to the core""" + pass diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index c8981d2f9..267b5c0f1 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -251,20 +251,20 @@ True - + True gtk-preferences True True - + - + True Pl_ugins True - + True @@ -294,105 +294,28 @@ True - + True _Toolbar True True - + - + True _Details True True - + - + True Columns True - - - True - - - True - Size - True - True - - - - - - True - Status - True - True - - - - - - True - Seeders - True - True - - - - - - True - Peers - True - True - - - - - - True - Download - True - True - - - - - - True - Upload - True - True - - - - - - True - Time Remaining - True - True - - - - - - True - Share Ratio - True - True - - - - - @@ -407,12 +330,12 @@ - + True gtk-about True True - + diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 70f9eebf9..21b1a7f12 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -5,21 +5,21 @@ True - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-media-pause True True - + - + True _Update Tracker True - + True @@ -31,12 +31,12 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Edit Trackers True - + True @@ -53,7 +53,7 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Remove Torrent @@ -75,7 +75,7 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Queue @@ -85,12 +85,12 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Top True - + True @@ -102,7 +102,7 @@ - + True _Up True @@ -118,7 +118,7 @@ - + True _Down True @@ -134,11 +134,11 @@ - + True _Bottom True - + True diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index dafb01097..d79d38fb7 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -36,7 +36,6 @@ import logging import pygtk pygtk.require('2.0') import gtk, gtk.glade -import pkg_resources from mainwindow import MainWindow @@ -48,12 +47,8 @@ class GtkUI: # Get the core proxy object from the args self.core = core - # Get the glade file for the main window - self.main_glade = gtk.glade.XML( - pkg_resources.resource_filename("deluge.ui.gtkui", "glade/main_window.glade")) - # Initialize the main window - self.main_window = MainWindow(self.main_glade) + self.main_window = MainWindow(self.core) # Show the main window self.main_window.show() diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index e9bb24ab7..345145710 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -36,13 +36,20 @@ import logging import pygtk pygtk.require('2.0') import gtk, gtk.glade +import pkg_resources # Get the logger log = logging.getLogger("deluge") class MainWindow: - def __init__(self, glade_xml): - self.main_glade = glade_xml + def __init__(self, core): + self.core = core + + # Get the glade file for the main window + self.main_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/main_window.glade")) + self.window = self.main_glade.get_widget("main_window") # Initialize various components of the gtkui @@ -62,7 +69,14 @@ class MainWindowMenuBar: def __init__(self, mainwindow): log.debug("MainWindowMenuBar init..") self.mainwindow = mainwindow - + self.torrentmenu = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/torrent_menu.glade")) + + # Attach the torrent_menu to the Torrent file menu + self.mainwindow.main_glade.get_widget("menu_torrent").set_submenu( + self.torrentmenu.get_widget("torrent_menu")) + ### Connect Signals ### self.mainwindow.main_glade.signal_autoconnect({ ## File Menu @@ -70,10 +84,39 @@ class MainWindowMenuBar: "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, "on_menuitem_clear_activate": \ self.on_menuitem_clear_activate, - "on_menuitem_quit_activate": self.on_menuitem_quit_activate + "on_menuitem_quit_activate": self.on_menuitem_quit_activate, + + ## Edit Menu + "on_menuitem_preferences_activate": \ + self.on_menuitem_preferences_activate, + "on_menuitem_plugins_activate": self.on_menuitem_plugins_activate, + + ## View Menu + "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, + "on_menuitem_infopane_toggled": self.on_menuitem_infopane_toggled, + + ## Help Menu + "on_menuitem_about_activate": self.on_menuitem_about_activate + }) + + self.torrentmenu.signal_autoconnect({ + ## Torrent Menu + "on_menuitem_pause_activate": self.on_menuitem_pause_activate, + "on_menuitem_updatetracker_activate": \ + self.on_menuitem_updatetracker_activate, + "on_menuitem_edittrackers_activate": \ + self.on_menuitem_edittrackers_activate, + "on_menuitem_remove_activate": self.on_menuitem_remove_activate, + "on_menuitem_queuetop_activate": self.on_menuitem_queuetop_activate, + "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, + "on_menuitem_queuedown_activate": self.on_menuitem_queuedown_activate, + "on_menuitem_queuebottom_activate": \ + self.on_menuitem_queuebottom_activate }) ### Callbacks ### + + ## File Menu ## def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") def on_menuitem_addurl_activate(self, data=None): @@ -83,6 +126,40 @@ class MainWindowMenuBar: def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") self.mainwindow.quit() + + ## Edit Menu ## + def on_menuitem_preferences_activate(self, data=None): + log.debug("on_menuitem_preferences_activate") + def on_menuitem_plugins_activate(self, data=None): + log.debug("on_menuitem_plugins_activate") + + ## Torrent Menu ## + def on_menuitem_pause_activate(self, data=None): + log.debug("on_menuitem_pause_activate") + def on_menuitem_updatetracker_activate(self, data=None): + log.debug("on_menuitem_updatetracker_activate") + def on_menuitem_edittrackers_activate(self, data=None): + log.debug("on_menuitem_edittrackers_activate") + def on_menuitem_remove_activate(self, data=None): + log.debug("on_menuitem_remove_activate") + def on_menuitem_queuetop_activate(self, data=None): + log.debug("on_menuitem_queuetop_activate") + def on_menuitem_queueup_activate(self, data=None): + log.debug("on_menuitem_queueup_activate") + def on_menuitem_queuedown_activate(self, data=None): + log.debug("on_menuitem_queuedown_activate") + def on_menuitem_queuebottom_activate(self, data=None): + log.debug("on_menuitem_queuebottom_activate") + + ## View Menu ## + def on_menuitem_toolbar_toggled(self, data=None): + log.debug("on_menuitem_toolbar_toggled") + def on_menuitem_infopane_toggled(self, data=None): + log.debug("on_menuitem_infopane_toggled") + + ## Help Menu ## + def on_menuitem_about_activate(self, data=None): + log.debug("on_menuitem_about_activate") class MainWindowToolBar: def __init__(self, mainwindow): From 702967a2c650a2eafaf9eb4b439435406cbc8b21 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 13 Jul 2007 01:34:18 +0000 Subject: [PATCH 0012/1009] Changed from 2 spaces ident to 4 --- deluge/common.py | 49 +++---- deluge/config.py | 141 +++++++++---------- deluge/core/core.py | 130 +++++++++--------- deluge/core/daemon.py | 66 ++++----- deluge/core/torrent.py | 36 ++--- deluge/main.py | 96 ++++++------- deluge/ui/gtkui/gtkui.py | 48 +++---- deluge/ui/gtkui/mainwindow.py | 245 +++++++++++++++++----------------- deluge/ui/ui.py | 74 +++++----- setup.py | 124 ++++++++--------- 10 files changed, 508 insertions(+), 501 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index a351154b1..d8d6d139b 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -1,7 +1,7 @@ # # common.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,20 +16,20 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import logging import pkg_resources @@ -40,18 +40,19 @@ import os log = logging.getLogger("deluge") def get_version(): - """Returns the program version from the egg metadata""" - return pkg_resources.require("Deluge")[0].version - + """Returns the program version from the egg metadata""" + return pkg_resources.require("Deluge")[0].version + def get_config_dir(filename=None): - """ Returns the config path if no filename is specified - Returns the config directory + filename as a path if filename is specified - """ - if filename != None: - return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename) - else: - return xdg.BaseDirectory.save_config_path("deluge") + """ Returns the config path if no filename is specified + Returns the config directory + filename as a path if filename is specified + """ + if filename != None: + return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), + filename) + else: + return xdg.BaseDirectory.save_config_path("deluge") def get_default_download_dir(): - """Returns the default download directory""" - return os.environ.get("HOME") + """Returns the default download directory""" + return os.environ.get("HOME") diff --git a/deluge/config.py b/deluge/config.py index 37eb13c62..b89ac210f 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -1,7 +1,7 @@ # # config.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,20 +16,20 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import logging import pickle @@ -40,65 +40,66 @@ import deluge.common log = logging.getLogger("deluge") class Config: - def __init__(self, filename, defaults=None): - log.debug("Config created with filename: %s", filename) - log.debug("Config defaults: %s", defaults) - self.config = {} - # If defaults is not None then we need to use "defaults". - if defaults != None: - self.config = defaults + def __init__(self, filename, defaults=None): + log.debug("Config created with filename: %s", filename) + log.debug("Config defaults: %s", defaults) + self.config = {} + # If defaults is not None then we need to use "defaults". + if defaults != None: + self.config = defaults - # Load the config from file in the config_dir - self.config_file = deluge.common.get_config_dir(filename) - self.load(self.config_file) - - def load(self, filename=None): - # Use self.config_file if filename is None - if filename is None: - filename = self.config_file - try: - # Un-pickle the file and update the config dictionary - log.debug("Opening pickled file for load..") - pkl_file = open(filename, "rb") - filedump = pickle.load(pkl_file) - self.config.update(filedump) - pkl_file.close() - except IOError: - log.warning("IOError: Unable to load file '%s'", filename) - except EOFError: - log.debug("Closing pickled file..") - pkl_file.close() - - def save(self, filename=None): - # Saves the config dictionary - if filename is None: - filename = self.config_file - try: - log.debug("Opening pickled file for save..") - pkl_file = open(filename, "wb") - pickle.dump(self.config, pkl_file) - log.debug("Closing pickled file..") - pkl_file.close() - except IOError: - log.warning("IOError: Unable to save file '%s'", filename) - - def set(self, key, value): - # Sets the "key" with "value" in the config dict - log.debug("Setting '%s' to %s", key, value) - self.config[key] = value + # Load the config from file in the config_dir + self.config_file = deluge.common.get_config_dir(filename) + self.load(self.config_file) + + def load(self, filename=None): + # Use self.config_file if filename is None + if filename is None: + filename = self.config_file + try: + # Un-pickle the file and update the config dictionary + log.debug("Opening pickled file for load..") + pkl_file = open(filename, "rb") + filedump = pickle.load(pkl_file) + self.config.update(filedump) + pkl_file.close() + except IOError: + log.warning("IOError: Unable to load file '%s'", filename) + except EOFError: + log.debug("Closing pickled file..") + pkl_file.close() + + def save(self, filename=None): + # Saves the config dictionary + if filename is None: + filename = self.config_file + try: + log.debug("Opening pickled file for save..") + pkl_file = open(filename, "wb") + pickle.dump(self.config, pkl_file) + log.debug("Closing pickled file..") + pkl_file.close() + except IOError: + log.warning("IOError: Unable to save file '%s'", filename) + + def set(self, key, value): + # Sets the "key" with "value" in the config dict + log.debug("Setting '%s' to %s", key, value) + self.config[key] = value - def get(self, key): - # Attempts to get the "key" value and returns None if the key is invalid - try: - value = self.config[key] - log.debug("Getting '%s' as %s", key, value) - return value - except KeyError: - log.warning("Key does not exist, returning None") - return + def get(self, key): + # Attempts to get the "key" value and returns None if the key is + # invalid + try: + value = self.config[key] + log.debug("Getting '%s' as %s", key, value) + return value + except KeyError: + log.warning("Key does not exist, returning None") + return - def __getitem__(self, key): - return self.config[key] + def __getitem__(self, key): + return self.config[key] - def __setitem__(self, key, value): - self.config[key] = value + def __setitem__(self, key, value): + self.config[key] = value diff --git a/deluge/core/core.py b/deluge/core/core.py index 154eee60c..9d3873ec7 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -1,7 +1,7 @@ # # core.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,20 +16,20 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import logging @@ -58,65 +58,65 @@ log = logging.getLogger("deluge") #_default_download_dir = deluge.common.get_default_download_dir() DEFAULT_PREFS = { - "listen_ports": [6881, 6891], - "download_location": deluge.common.get_default_download_dir(), - "compact_allocation": True + "listen_ports": [6881, 6891], + "download_location": deluge.common.get_default_download_dir(), + "compact_allocation": True } class Core(dbus.service.Object): - def __init__(self, path="/org/deluge_torrent/Core"): - log.debug("Core init..") - bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", - bus=dbus.SessionBus()) - dbus.service.Object.__init__(self, bus_name, path) - self.config = Config("core.conf", DEFAULT_PREFS) - # Setup the libtorrent session and listen on the configured ports - log.debug("Starting libtorrent session..") - self.session = lt.session() - log.debug("Listening on %i-%i", self.config.get("listen_ports")[0], - self.config.get("listen_ports")[1]) - self.session.listen_on(self.config.get("listen_ports")[0], - self.config.get("listen_ports")[1]) + def __init__(self, path="/org/deluge_torrent/Core"): + log.debug("Core init..") + bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", + bus=dbus.SessionBus()) + dbus.service.Object.__init__(self, bus_name, path) + self.config = Config("core.conf", DEFAULT_PREFS) + # Setup the libtorrent session and listen on the configured ports + log.debug("Starting libtorrent session..") + self.session = lt.session() + log.debug("Listening on %i-%i", self.config.get("listen_ports")[0], + self.config.get("listen_ports")[1]) + self.session.listen_on(self.config.get("listen_ports")[0], + self.config.get("listen_ports")[1]) - log.debug("Starting main loop..") - self.loop = gobject.MainLoop() - self.loop.run() + log.debug("Starting main loop..") + self.loop = gobject.MainLoop() + self.loop.run() - # Exported Methods - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def add_torrent_file(self, _filename): - """Adds a torrent file to the libtorrent session - """ - log.info("Adding torrent: %s", _filename) - torrent = Torrent(filename=_filename) - self.session.add_torrent(torrent.torrent_info, - self.config["download_location"], - self.config["compact_allocation"]) - # Emit the torrent_added signal - self.torrent_added() + # Exported Methods + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def add_torrent_file(self, _filename): + """Adds a torrent file to the libtorrent session + """ + log.info("Adding torrent: %s", _filename) + torrent = Torrent(filename=_filename) + self.session.add_torrent(torrent.torrent_info, + self.config["download_location"], + self.config["compact_allocation"]) + # Emit the torrent_added signal + self.torrent_added() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def add_torrent_url(self, _url): - """Adds a torrent from url to the libtorrent session - """ - log.info("Adding torrent: %s", _url) - torrent = Torrent(url=_url) - self.session.add_torrent(torrent.torrent_info, - self.config["download_location"], - self.config["compact_allocation"]) + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def add_torrent_url(self, _url): + """Adds a torrent from url to the libtorrent session + """ + log.info("Adding torrent: %s", _url) + torrent = Torrent(url=_url) + self.session.add_torrent(torrent.torrent_info, + self.config["download_location"], + self.config["compact_allocation"]) - - @dbus.service.method("org.deluge_torrent.Deluge") - def shutdown(self): - log.info("Shutting down core..") - self.loop.quit() - # Signals - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="") - def torrent_added(self): - """Emitted when a new torrent is added to the core""" - pass + @dbus.service.method("org.deluge_torrent.Deluge") + def shutdown(self): + log.info("Shutting down core..") + self.loop.quit() + + # Signals + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="") + def torrent_added(self): + """Emitted when a new torrent is added to the core""" + pass diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index 828a15927..d9f171eab 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -1,7 +1,7 @@ # # daemon.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,30 +16,30 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass except: dbus_imported = False else: dbus_imported = True @@ -51,16 +51,16 @@ from deluge.core.core import Core log = logging.getLogger("deluge") class Daemon: - def __init__(self): - # Check to see if the daemon is already running and if not, start it - bus = dbus.SessionBus() - obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") - iface = dbus.Interface(obj, "org.freedesktop.DBus") - if iface.NameHasOwner("org.deluge_torrent.Deluge"): - # Daemon is running so lets tell the user - log.info("Daemon is already running..") - else: - # Daemon is not running so lets start up the core - log.debug("Daemon is not running..") - self.core = Core() + def __init__(self): + # Check to see if the daemon is already running and if not, start it + bus = dbus.SessionBus() + obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") + iface = dbus.Interface(obj, "org.freedesktop.DBus") + if iface.NameHasOwner("org.deluge_torrent.Deluge"): + # Daemon is running so lets tell the user + log.info("Daemon is already running..") + else: + # Daemon is not running so lets start up the core + log.debug("Daemon is not running..") + self.core = Core() diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 1ee75a775..c164e906a 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -1,7 +1,7 @@ # # torrent.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,27 +16,27 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import deluge.libtorrent as lt class Torrent: - def __init__(self, filename=None, url=None): - # Load the torrent file - if filename is not None: - torrent_file = lt.bdecode(open(filename, 'rb').read()) - self.torrent_info = lt.torrent_info(torrent_file) - + def __init__(self, filename=None, url=None): + # Load the torrent file + if filename is not None: + torrent_file = lt.bdecode(open(filename, 'rb').read()) + self.torrent_info = lt.torrent_info(torrent_file) + diff --git a/deluge/main.py b/deluge/main.py index f18fa4dca..fa09278a7 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -1,7 +1,7 @@ # # main.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,22 +16,22 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. -# The main starting point for the program. This function is called when the +# The main starting point for the program. This function is called when the # user runs the command 'deluge'. import logging @@ -45,47 +45,47 @@ import deluge.common # Setup the logger logging.basicConfig( - level=logging.DEBUG, - format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" + level=logging.DEBUG, + format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" ) # Get the logger for deluge log = logging.getLogger("deluge") def main(): - # Setup the argument parser - # FIXME: need to use deluge.common to fill in version - parser = OptionParser(usage="%prog [options] [actions]", - version=deluge.common.get_version()) - parser.add_option("--daemon", dest="daemon", help="Start Deluge daemon", - metavar="DAEMON", action="store_true", default=False) - parser.add_option("--ui", dest="ui", help="Start Deluge UI", - metavar="UI", action="store_true", default=False) + # Setup the argument parser + # FIXME: need to use deluge.common to fill in version + parser = OptionParser(usage="%prog [options] [actions]", + version=deluge.common.get_version()) + parser.add_option("--daemon", dest="daemon", help="Start Deluge daemon", + metavar="DAEMON", action="store_true", default=False) + parser.add_option("--ui", dest="ui", help="Start Deluge UI", + metavar="UI", action="store_true", default=False) - # Get the options and args from the OptionParser - (options, args) = parser.parse_args() + # Get the options and args from the OptionParser + (options, args) = parser.parse_args() - log.info("Deluge %s", deluge.common.get_version()) - - log.debug("options: %s", options) - log.debug("args: %s", args) + log.info("Deluge %s", deluge.common.get_version()) - daemon = None - pid = None - uri = None - - # Start the daemon - if options.daemon: - log.info("Starting daemon..") - # We need to fork() the process to run it in the background... - # FIXME: We cannot use fork() on Windows - pid = os.fork() - if not pid: - # Since we are starting daemon this process will not start a UI - options.ui = False - # Create the daemon object - daemon = Daemon() + log.debug("options: %s", options) + log.debug("args: %s", args) + + daemon = None + pid = None + uri = None + + # Start the daemon + if options.daemon: + log.info("Starting daemon..") + # We need to fork() the process to run it in the background... + # FIXME: We cannot use fork() on Windows + pid = os.fork() + if not pid: + # Since we are starting daemon this process will not start a UI + options.ui = False + # Create the daemon object + daemon = Daemon() - # Start the UI - if options.ui: - log.info("Starting ui..") - ui = UI() + # Start the UI + if options.ui: + log.info("Starting ui..") + ui = UI() diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index d79d38fb7..8dba0123a 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -1,7 +1,7 @@ # # gtkui.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,20 +16,20 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import logging @@ -43,15 +43,15 @@ from mainwindow import MainWindow log = logging.getLogger("deluge") class GtkUI: - def __init__(self, core): - # Get the core proxy object from the args - self.core = core - - # Initialize the main window - self.main_window = MainWindow(self.core) - - # Show the main window - self.main_window.show() - - # Start the gtk main loop - gtk.main() + def __init__(self, core): + # Get the core proxy object from the args + self.core = core + + # Initialize the main window + self.main_window = MainWindow(self.core) + + # Show the main window + self.main_window.show() + + # Start the gtk main loop + gtk.main() diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 345145710..2693c915f 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -1,7 +1,7 @@ # # gtkui_mainwindow.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,20 +16,20 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import logging @@ -42,126 +42,129 @@ import pkg_resources log = logging.getLogger("deluge") class MainWindow: - def __init__(self, core): - self.core = core - - # Get the glade file for the main window - self.main_glade = gtk.glade.XML( - pkg_resources.resource_filename("deluge.ui.gtkui", - "glade/main_window.glade")) + def __init__(self, core): + self.core = core + + # Get the glade file for the main window + self.main_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/main_window.glade")) - self.window = self.main_glade.get_widget("main_window") + self.window = self.main_glade.get_widget("main_window") + + # Initialize various components of the gtkui + self.menubar = MainWindowMenuBar(self) - # Initialize various components of the gtkui - self.menubar = MainWindowMenuBar(self) - - def show(self): - self.window.show_all() - - def hide(self): - self.window.hide() - - def quit(self): - self.hide() - gtk.main_quit() + def show(self): + self.window.show_all() + def hide(self): + self.window.hide() + + def quit(self): + self.hide() + gtk.main_quit() + class MainWindowMenuBar: - def __init__(self, mainwindow): - log.debug("MainWindowMenuBar init..") - self.mainwindow = mainwindow - self.torrentmenu = gtk.glade.XML( - pkg_resources.resource_filename("deluge.ui.gtkui", - "glade/torrent_menu.glade")) + def __init__(self, mainwindow): + log.debug("MainWindowMenuBar init..") + self.mainwindow = mainwindow + self.torrentmenu = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/torrent_menu.glade")) - # Attach the torrent_menu to the Torrent file menu - self.mainwindow.main_glade.get_widget("menu_torrent").set_submenu( - self.torrentmenu.get_widget("torrent_menu")) + # Attach the torrent_menu to the Torrent file menu + self.mainwindow.main_glade.get_widget("menu_torrent").set_submenu( + self.torrentmenu.get_widget("torrent_menu")) - ### Connect Signals ### - self.mainwindow.main_glade.signal_autoconnect({ - ## File Menu - "on_menuitem_addtorrent_activate": self.on_menuitem_addtorrent_activate, - "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, - "on_menuitem_clear_activate": \ - self.on_menuitem_clear_activate, - "on_menuitem_quit_activate": self.on_menuitem_quit_activate, + ### Connect Signals ### + self.mainwindow.main_glade.signal_autoconnect({ + ## File Menu + "on_menuitem_addtorrent_activate": \ + self.on_menuitem_addtorrent_activate, + "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, + "on_menuitem_clear_activate": \ + self.on_menuitem_clear_activate, + "on_menuitem_quit_activate": self.on_menuitem_quit_activate, - ## Edit Menu - "on_menuitem_preferences_activate": \ + ## Edit Menu + "on_menuitem_preferences_activate": \ self.on_menuitem_preferences_activate, - "on_menuitem_plugins_activate": self.on_menuitem_plugins_activate, - - ## View Menu - "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, - "on_menuitem_infopane_toggled": self.on_menuitem_infopane_toggled, - - ## Help Menu - "on_menuitem_about_activate": self.on_menuitem_about_activate - }) - - self.torrentmenu.signal_autoconnect({ - ## Torrent Menu - "on_menuitem_pause_activate": self.on_menuitem_pause_activate, - "on_menuitem_updatetracker_activate": \ - self.on_menuitem_updatetracker_activate, - "on_menuitem_edittrackers_activate": \ - self.on_menuitem_edittrackers_activate, - "on_menuitem_remove_activate": self.on_menuitem_remove_activate, - "on_menuitem_queuetop_activate": self.on_menuitem_queuetop_activate, - "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, - "on_menuitem_queuedown_activate": self.on_menuitem_queuedown_activate, - "on_menuitem_queuebottom_activate": \ + "on_menuitem_plugins_activate": self.on_menuitem_plugins_activate, + + ## View Menu + "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, + "on_menuitem_infopane_toggled": self.on_menuitem_infopane_toggled, + + ## Help Menu + "on_menuitem_about_activate": self.on_menuitem_about_activate + }) + + self.torrentmenu.signal_autoconnect({ + ## Torrent Menu + "on_menuitem_pause_activate": self.on_menuitem_pause_activate, + "on_menuitem_updatetracker_activate": \ + self.on_menuitem_updatetracker_activate, + "on_menuitem_edittrackers_activate": \ + self.on_menuitem_edittrackers_activate, + "on_menuitem_remove_activate": self.on_menuitem_remove_activate, + "on_menuitem_queuetop_activate": \ + self.on_menuitem_queuetop_activate, + "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, + "on_menuitem_queuedown_activate": \ + self.on_menuitem_queuedown_activate, + "on_menuitem_queuebottom_activate": \ self.on_menuitem_queuebottom_activate - }) + }) + + ### Callbacks ### - ### Callbacks ### - - ## File Menu ## - def on_menuitem_addtorrent_activate(self, data=None): - log.debug("on_menuitem_addtorrent_activate") - def on_menuitem_addurl_activate(self, data=None): - log.debug("on_menuitem_addurl_activate") - def on_menuitem_clear_activate(self, data=None): - log.debug("on_menuitem_clear_activate") - def on_menuitem_quit_activate(self, data=None): - log.debug("on_menuitem_quit_activate") - self.mainwindow.quit() - - ## Edit Menu ## - def on_menuitem_preferences_activate(self, data=None): - log.debug("on_menuitem_preferences_activate") - def on_menuitem_plugins_activate(self, data=None): - log.debug("on_menuitem_plugins_activate") + ## File Menu ## + def on_menuitem_addtorrent_activate(self, data=None): + log.debug("on_menuitem_addtorrent_activate") + def on_menuitem_addurl_activate(self, data=None): + log.debug("on_menuitem_addurl_activate") + def on_menuitem_clear_activate(self, data=None): + log.debug("on_menuitem_clear_activate") + def on_menuitem_quit_activate(self, data=None): + log.debug("on_menuitem_quit_activate") + self.mainwindow.quit() + + ## Edit Menu ## + def on_menuitem_preferences_activate(self, data=None): + log.debug("on_menuitem_preferences_activate") + def on_menuitem_plugins_activate(self, data=None): + log.debug("on_menuitem_plugins_activate") - ## Torrent Menu ## - def on_menuitem_pause_activate(self, data=None): - log.debug("on_menuitem_pause_activate") - def on_menuitem_updatetracker_activate(self, data=None): - log.debug("on_menuitem_updatetracker_activate") - def on_menuitem_edittrackers_activate(self, data=None): - log.debug("on_menuitem_edittrackers_activate") - def on_menuitem_remove_activate(self, data=None): - log.debug("on_menuitem_remove_activate") - def on_menuitem_queuetop_activate(self, data=None): - log.debug("on_menuitem_queuetop_activate") - def on_menuitem_queueup_activate(self, data=None): - log.debug("on_menuitem_queueup_activate") - def on_menuitem_queuedown_activate(self, data=None): - log.debug("on_menuitem_queuedown_activate") - def on_menuitem_queuebottom_activate(self, data=None): - log.debug("on_menuitem_queuebottom_activate") + ## Torrent Menu ## + def on_menuitem_pause_activate(self, data=None): + log.debug("on_menuitem_pause_activate") + def on_menuitem_updatetracker_activate(self, data=None): + log.debug("on_menuitem_updatetracker_activate") + def on_menuitem_edittrackers_activate(self, data=None): + log.debug("on_menuitem_edittrackers_activate") + def on_menuitem_remove_activate(self, data=None): + log.debug("on_menuitem_remove_activate") + def on_menuitem_queuetop_activate(self, data=None): + log.debug("on_menuitem_queuetop_activate") + def on_menuitem_queueup_activate(self, data=None): + log.debug("on_menuitem_queueup_activate") + def on_menuitem_queuedown_activate(self, data=None): + log.debug("on_menuitem_queuedown_activate") + def on_menuitem_queuebottom_activate(self, data=None): + log.debug("on_menuitem_queuebottom_activate") + + ## View Menu ## + def on_menuitem_toolbar_toggled(self, data=None): + log.debug("on_menuitem_toolbar_toggled") + def on_menuitem_infopane_toggled(self, data=None): + log.debug("on_menuitem_infopane_toggled") - ## View Menu ## - def on_menuitem_toolbar_toggled(self, data=None): - log.debug("on_menuitem_toolbar_toggled") - def on_menuitem_infopane_toggled(self, data=None): - log.debug("on_menuitem_infopane_toggled") - - ## Help Menu ## - def on_menuitem_about_activate(self, data=None): - log.debug("on_menuitem_about_activate") + ## Help Menu ## + def on_menuitem_about_activate(self, data=None): + log.debug("on_menuitem_about_activate") class MainWindowToolBar: - def __init__(self, mainwindow): - self.mainwindow = mainwindow - + def __init__(self, mainwindow): + self.mainwindow = mainwindow + diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 2b47042e9..d9b2a6d96 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -1,7 +1,7 @@ # # ui.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) Andrew Resch 2007 # # Deluge is free software. # @@ -16,33 +16,33 @@ # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: +# along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import logging try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass except: dbus_imported = False else: dbus_imported = True @@ -54,23 +54,23 @@ from deluge.config import Config log = logging.getLogger("deluge") DEFAULT_PREFS = { - "selected_ui": "gtk" + "selected_ui": "gtk" } class UI: - def __init__(self): - log.debug("UI init..") - self.config = Config("ui.conf", DEFAULT_PREFS) - log.debug("Getting core proxy object from DBUS..") - # Get the proxy object from DBUS - bus = dbus.SessionBus() - proxy = bus.get_object("org.deluge_torrent.Deluge", - "/org/deluge_torrent/Core") - self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") - log.debug("Got core proxy object..") - - if self.config["selected_ui"] == "gtk": - log.info("Starting GtkUI..") - from deluge.ui.gtkui.gtkui import GtkUI - ui = GtkUI(self.core) + def __init__(self): + log.debug("UI init..") + self.config = Config("ui.conf", DEFAULT_PREFS) + log.debug("Getting core proxy object from DBUS..") + # Get the proxy object from DBUS + bus = dbus.SessionBus() + proxy = bus.get_object("org.deluge_torrent.Deluge", + "/org/deluge_torrent/Core") + self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") + log.debug("Got core proxy object..") + + if self.config["selected_ui"] == "gtk": + log.info("Starting GtkUI..") + from deluge.ui.gtkui.gtkui import GtkUI + ui = GtkUI(self.core) diff --git a/setup.py b/setup.py index a89e59b2b..de554e630 100644 --- a/setup.py +++ b/setup.py @@ -9,24 +9,24 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, write to: +# along with this program. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1301, USA. # -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. import ez_setup ez_setup.use_setuptools() @@ -39,73 +39,75 @@ python_version = platform.python_version()[0:3] # The libtorrent extension _extra_compile_args = [ - "-Wno-missing-braces", - "-DHAVE_INCLUDE_LIBTORRENT_ASIO____ASIO_HPP=1", - "-DHAVE_INCLUDE_LIBTORRENT_ASIO_SSL_STREAM_HPP=1", - "-DHAVE_INCLUDE_LIBTORRENT_ASIO_IP_TCP_HPP=1", - "-DHAVE_PTHREAD=1", - "-DTORRENT_USE_OPENSSL=1", - "-DHAVE_SSL=1" + "-Wno-missing-braces", + "-DHAVE_INCLUDE_LIBTORRENT_ASIO____ASIO_HPP=1", + "-DHAVE_INCLUDE_LIBTORRENT_ASIO_SSL_STREAM_HPP=1", + "-DHAVE_INCLUDE_LIBTORRENT_ASIO_IP_TCP_HPP=1", + "-DHAVE_PTHREAD=1", + "-DTORRENT_USE_OPENSSL=1", + "-DHAVE_SSL=1" ] _include_dirs = [ - './libtorrent', - './libtorrent/include', - './libtorrent/include/libtorrent', - '/usr/include/python' + python_version + './libtorrent', + './libtorrent/include', + './libtorrent/include/libtorrent', + '/usr/include/python' + python_version ] - + _libraries = [ - 'boost_filesystem', - 'boost_date_time', - 'boost_thread', - 'boost_python', - 'z', - 'pthread', - 'ssl' + 'boost_filesystem', + 'boost_date_time', + 'boost_thread', + 'boost_python', + 'z', + 'pthread', + 'ssl' ] _sources = glob.glob("./libtorrent/src/*.cpp") + \ - glob.glob("./libtorrent/src/kademlia/*.cpp") + \ - glob.glob("./libtorrent/bindings/python/src/*.cpp") + glob.glob("./libtorrent/src/kademlia/*.cpp") + \ + glob.glob("./libtorrent/bindings/python/src/*.cpp") # Remove file_win.cpp as it is only for Windows builds for source in _sources: - if "file_win.cpp" in source: - _sources.remove(source) - break + if "file_win.cpp" in source: + _sources.remove(source) + break libtorrent = Extension( - 'libtorrent', - include_dirs = _include_dirs, - libraries = _libraries, - extra_compile_args = _extra_compile_args, - sources = _sources + 'libtorrent', + include_dirs = _include_dirs, + libraries = _libraries, + extra_compile_args = _extra_compile_args, + sources = _sources ) # Main setup _datafiles = [ ] - + setup( - name = "deluge", - fullname = "Deluge Bittorent Client", - version = "0.6", - author = "Zach Tibbitts, Alon Zakai, Marcos Pinto, Andrew Resch", - author_email = "zach@collegegeek.org, kripkensteiner@gmail.com, \ - marcospinto@dipconsultants.com, andrewresch@gmail.com", - description = "GTK+ bittorrent client", - url = "http://deluge-torrent.org", - license = "GPLv2", - - include_package_data = True, - package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png"]}, - ext_package = "deluge", - ext_modules = [libtorrent], - packages = find_packages(), - entry_points = """ - [console_scripts] - deluge = deluge.main:main - """ + name = "deluge", + fullname = "Deluge Bittorent Client", + version = "0.6", + author = "Zach Tibbitts, Alon Zakai, Marcos Pinto, Andrew Resch", + author_email = "zach@collegegeek.org, kripkensteiner@gmail.com, \ + marcospinto@dipconsultants.com, \ + andrewresch@gmail.com", + description = "GTK+ bittorrent client", + url = "http://deluge-torrent.org", + license = "GPLv2", + + include_package_data = True, + package_data = {"deluge": ["ui/gtkui/glade/*.glade", + "data/pixmaps/*.png"]}, + ext_package = "deluge", + ext_modules = [libtorrent], + packages = find_packages(), + entry_points = """ + [console_scripts] + deluge = deluge.main:main + """ ) From 02b189f5a507ee8cabba7dbee375730952fe2d67 Mon Sep 17 00:00:00 2001 From: Zach Tibbitts Date: Fri, 13 Jul 2007 16:47:22 +0000 Subject: [PATCH 0013/1009] rename branch to deluge-0.6 From 9280781dd38d434299425aa6aa02fe942af5b387 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 14 Jul 2007 01:33:16 +0000 Subject: [PATCH 0014/1009] Updates --- deluge/common.py | 75 ++++++++++- deluge/core/core.py | 1 - deluge/ui/gtkui/columns.py | 152 +++++++++++++++++++++++ deluge/ui/gtkui/glade/main_window.glade | 26 ++-- deluge/ui/gtkui/gtkui.py | 13 ++ deluge/ui/gtkui/mainwindow.py | 114 ++--------------- deluge/ui/gtkui/menubar.py | 143 +++++++++++++++++++++ deluge/ui/gtkui/po/deluge.pot | 0 deluge/ui/gtkui/toolbar.py | 81 ++++++++++++ deluge/ui/gtkui/torrentview.py | 158 ++++++++++++++++++++++++ setup.py | 9 +- 11 files changed, 646 insertions(+), 126 deletions(-) create mode 100644 deluge/ui/gtkui/columns.py create mode 100644 deluge/ui/gtkui/menubar.py create mode 100644 deluge/ui/gtkui/po/deluge.pot create mode 100644 deluge/ui/gtkui/toolbar.py create mode 100644 deluge/ui/gtkui/torrentview.py diff --git a/deluge/common.py b/deluge/common.py index d8d6d139b..49ece9747 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -32,9 +32,11 @@ # statement from all source files in the program, then also delete it here. import logging +import os + import pkg_resources import xdg, xdg.BaseDirectory -import os +import gettext # Get the logger log = logging.getLogger("deluge") @@ -56,3 +58,74 @@ def get_config_dir(filename=None): def get_default_download_dir(): """Returns the default download directory""" return os.environ.get("HOME") + +## Formatting text functions + +def estimate_eta(total_size, total_done, download_rate): + """Returns a string with the estimated ETA and will return 'Unlimited' + if the torrent is complete + """ + try: + return ftime(get_eta(total_size, total_done, download_rate)) + except ZeroDivisionError: + return _("Infinity") + +def get_eta(size, done, speed): + """Returns the ETA in seconds + Will raise an exception if the torrent is completed + """ + if (size - done) == 0: + raise ZeroDivisionError + return (size - done) / speed + +def fsize(fsize_b): + """Returns formatted string describing filesize + fsize_b should be in bytes + Returned value will be in either KB, MB, or GB + """ + fsize_kb = float (fsize_b / 1024.0) + if fsize_kb < 1000: + return _("%.1f KiB")%fsize_kb + fsize_mb = float (fsize_kb / 1024.0) + if fsize_mb < 1000: + return _("%.1f MiB")%fsize_mb + fsize_gb = float (fsize_mb / 1024.0) + return _("%.1f GiB")%fsize_gb + +def fpcnt(dec): + """Returns a formatted string representing a percentage""" + return '%.2f%%'%(dec * 100) + +def fspeed(bps): + """Returns a formatted string representing transfer speed""" + return '%s/s'%(fsize(bps)) + +def fseed(num_seeds, total_seeds): + """Returns a formatted string num_seeds (total_seeds)""" + return str(str(num_seeds) + " (" + str(total_seeds) + ")") + +def fpeer(num_peers, total_peers): + """Returns a formatted string num_peers (total_peers)""" + return str(str(num_peers) + " (" + str(total_peers) + ")") + +def ftime(seconds): + """Returns a formatted time string""" + if seconds < 60: + return '%ds'%(seconds) + minutes = int(seconds/60) + seconds = seconds % 60 + if minutes < 60: + return '%dm %ds'%(minutes, seconds) + hours = int(minutes/60) + minutes = minutes % 60 + if hours < 24: + return '%dh %dm'%(hours, minutes) + days = int(hours/24) + hours = hours % 24 + if days < 7: + return '%dd %dh'%(days, hours) + weeks = int(days/7) + days = days % 7 + if weeks < 10: + return '%dw %dd'%(weeks, days) + return 'unknown' diff --git a/deluge/core/core.py b/deluge/core/core.py index 9d3873ec7..1ebe30539 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -56,7 +56,6 @@ from deluge.core.torrent import Torrent # Get the logger log = logging.getLogger("deluge") -#_default_download_dir = deluge.common.get_default_download_dir() DEFAULT_PREFS = { "listen_ports": [6881, 6891], "download_location": deluge.common.get_default_download_dir(), diff --git a/deluge/ui/gtkui/columns.py b/deluge/ui/gtkui/columns.py new file mode 100644 index 000000000..c6c2ac3e0 --- /dev/null +++ b/deluge/ui/gtkui/columns.py @@ -0,0 +1,152 @@ +# +# columns.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import pygtk +pygtk.require('2.0') +import gtk + +import deluge.common + +# Cell data functions to pass to add_func_column() + +def cell_data_speed(column, cell, model, iter, data): + speed = int(model.get_value(iter, data)) + speed_str = deluge.common.fspeed(speed) + cell.set_property('text', speed_str) + +def cell_data_size(column, cell, model, iter, data): + size = long(model.get_value(iter, data)) + size_str = deluge.common.fsize(size) + cell.set_property('text', size_str) + +def cell_data_peer(column, cell, model, iter, data): + c1, c2 = data + a = int(model.get_value(iter, c1)) + b = int(model.get_value(iter, c2)) + cell.set_property('text', '%d (%d)'%(a, b)) + +def cell_data_time(column, cell, model, iter, data): + time = int(model.get_value(iter, data)) + if time < 0 or time == 0: + time_str = _("Infinity") + else: + time_str = deluge.common.ftime(time) + cell.set_property('text', time_str) + +def cell_data_ratio(column, cell, model, iter, data): + ratio = float(model.get_value(iter, data)) + if ratio == -1: + ratio_str = _("Unknown") + else: + ratio_str = "%.3f"%ratio + cell.set_property('text', ratio_str) + +## Functions to create columns + +def add_func_column(view, header, func, data, sortid=None): + column = gtk.TreeViewColumn(header) + render = gtk.CellRendererText() + column.pack_start(render, True) + column.set_cell_data_func(render, func, data) + if sortid is not None: + column.set_clickable(True) + column.set_sort_column_id(sortid) + else: + try: + if len(data) == 1: + column.set_clickable(True) + column.set_sort_column_id(data[0]) + except TypeError: + column.set_clickable(True) + column.set_sort_column_id(data) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + view.append_column(column) + return column + + +def add_text_column(view, header, cid): + render = gtk.CellRendererText() + column = gtk.TreeViewColumn(header, render, text=cid) + column.set_clickable(True) + column.set_sort_column_id(cid) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + view.append_column(column) + return column + +def add_progress_column(view, header, pid, mid): + render = gtk.CellRendererProgress() + column = gtk.TreeViewColumn(header, render, value=pid, text=mid) + column.set_clickable(True) + column.set_sort_column_id(pid) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + view.append_column(column) + return column + +def add_toggle_column(view, header, cid, toggled_signal=None): + render = gtk.CellRendererToggle() + render.set_property('activatable', True) + column = gtk.TreeViewColumn(header, render, active=cid) + column.set_clickable(True) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + view.append_column(column) + if toggled_signal is not None: + render.connect("toggled", toggled_signal) + return column + +def add_texticon_column(view, header, icon_col, text_col): + column = gtk.TreeViewColumn(header) + column.set_clickable(True) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + render = gtk.CellRendererPixbuf() + column.pack_start(render, expand=False) + column.add_attribute(render, 'pixbuf', icon_col) + render = gtk.CellRendererText() + column.pack_start(render, expand=True) + column.add_attribute(render, 'text', text_col) + view.append_column(column) + return column diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 267b5c0f1..e29ef7cc4 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -23,7 +23,7 @@ Add Torrent True gtk-add - + False @@ -37,20 +37,20 @@ Remove Torrent True gtk-remove - + False - + True Clear Finished Torrents Clear Finished True gtk-clear - + False @@ -73,35 +73,35 @@ Start True gtk-media-play - + False - + True False Queue Torrent Up Move Up True gtk-go-up - + False - + True False Queue Torrent Down Move Down True gtk-go-down - + False @@ -117,26 +117,26 @@ - + True Preferences Preferences True gtk-preferences - + False - + True Plugins Plugins True gtk-disconnect - + False diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 8dba0123a..ec3f5cd2c 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -36,6 +36,8 @@ import logging import pygtk pygtk.require('2.0') import gtk, gtk.glade +import gettext +import pkg_resources from mainwindow import MainWindow @@ -46,6 +48,17 @@ class GtkUI: def __init__(self, core): # Get the core proxy object from the args self.core = core + + # Initialize gettext + gettext.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge.ui.gtkui", + "po")) + gettext.textdomain("deluge") + gettext.install("deluge", + pkg_resources.resource_filename( + "deluge.ui.gtkui", + "po")) # Initialize the main window self.main_window = MainWindow(self.core) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 2693c915f..8f39cf9c9 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -1,5 +1,5 @@ # -# gtkui_mainwindow.py +# mainwindow.py # # Copyright (C) Andrew Resch 2007 # @@ -38,6 +38,10 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources +from menubar import MenuBar +from toolbar import ToolBar +from torrentview import TorrentView + # Get the logger log = logging.getLogger("deluge") @@ -53,7 +57,9 @@ class MainWindow: self.window = self.main_glade.get_widget("main_window") # Initialize various components of the gtkui - self.menubar = MainWindowMenuBar(self) + self.menubar = MenuBar(self) + self.toolbar = ToolBar(self) + self.torrentview = TorrentView(self) def show(self): self.window.show_all() @@ -64,107 +70,3 @@ class MainWindow: def quit(self): self.hide() gtk.main_quit() - -class MainWindowMenuBar: - def __init__(self, mainwindow): - log.debug("MainWindowMenuBar init..") - self.mainwindow = mainwindow - self.torrentmenu = gtk.glade.XML( - pkg_resources.resource_filename("deluge.ui.gtkui", - "glade/torrent_menu.glade")) - - # Attach the torrent_menu to the Torrent file menu - self.mainwindow.main_glade.get_widget("menu_torrent").set_submenu( - self.torrentmenu.get_widget("torrent_menu")) - - ### Connect Signals ### - self.mainwindow.main_glade.signal_autoconnect({ - ## File Menu - "on_menuitem_addtorrent_activate": \ - self.on_menuitem_addtorrent_activate, - "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, - "on_menuitem_clear_activate": \ - self.on_menuitem_clear_activate, - "on_menuitem_quit_activate": self.on_menuitem_quit_activate, - - ## Edit Menu - "on_menuitem_preferences_activate": \ - self.on_menuitem_preferences_activate, - "on_menuitem_plugins_activate": self.on_menuitem_plugins_activate, - - ## View Menu - "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, - "on_menuitem_infopane_toggled": self.on_menuitem_infopane_toggled, - - ## Help Menu - "on_menuitem_about_activate": self.on_menuitem_about_activate - }) - - self.torrentmenu.signal_autoconnect({ - ## Torrent Menu - "on_menuitem_pause_activate": self.on_menuitem_pause_activate, - "on_menuitem_updatetracker_activate": \ - self.on_menuitem_updatetracker_activate, - "on_menuitem_edittrackers_activate": \ - self.on_menuitem_edittrackers_activate, - "on_menuitem_remove_activate": self.on_menuitem_remove_activate, - "on_menuitem_queuetop_activate": \ - self.on_menuitem_queuetop_activate, - "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, - "on_menuitem_queuedown_activate": \ - self.on_menuitem_queuedown_activate, - "on_menuitem_queuebottom_activate": \ - self.on_menuitem_queuebottom_activate - }) - - ### Callbacks ### - - ## File Menu ## - def on_menuitem_addtorrent_activate(self, data=None): - log.debug("on_menuitem_addtorrent_activate") - def on_menuitem_addurl_activate(self, data=None): - log.debug("on_menuitem_addurl_activate") - def on_menuitem_clear_activate(self, data=None): - log.debug("on_menuitem_clear_activate") - def on_menuitem_quit_activate(self, data=None): - log.debug("on_menuitem_quit_activate") - self.mainwindow.quit() - - ## Edit Menu ## - def on_menuitem_preferences_activate(self, data=None): - log.debug("on_menuitem_preferences_activate") - def on_menuitem_plugins_activate(self, data=None): - log.debug("on_menuitem_plugins_activate") - - ## Torrent Menu ## - def on_menuitem_pause_activate(self, data=None): - log.debug("on_menuitem_pause_activate") - def on_menuitem_updatetracker_activate(self, data=None): - log.debug("on_menuitem_updatetracker_activate") - def on_menuitem_edittrackers_activate(self, data=None): - log.debug("on_menuitem_edittrackers_activate") - def on_menuitem_remove_activate(self, data=None): - log.debug("on_menuitem_remove_activate") - def on_menuitem_queuetop_activate(self, data=None): - log.debug("on_menuitem_queuetop_activate") - def on_menuitem_queueup_activate(self, data=None): - log.debug("on_menuitem_queueup_activate") - def on_menuitem_queuedown_activate(self, data=None): - log.debug("on_menuitem_queuedown_activate") - def on_menuitem_queuebottom_activate(self, data=None): - log.debug("on_menuitem_queuebottom_activate") - - ## View Menu ## - def on_menuitem_toolbar_toggled(self, data=None): - log.debug("on_menuitem_toolbar_toggled") - def on_menuitem_infopane_toggled(self, data=None): - log.debug("on_menuitem_infopane_toggled") - - ## Help Menu ## - def on_menuitem_about_activate(self, data=None): - log.debug("on_menuitem_about_activate") - -class MainWindowToolBar: - def __init__(self, mainwindow): - self.mainwindow = mainwindow - diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py new file mode 100644 index 000000000..15dbede6e --- /dev/null +++ b/deluge/ui/gtkui/menubar.py @@ -0,0 +1,143 @@ +# +# menubar.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import pkg_resources + +# Get the logger +log = logging.getLogger("deluge") + +class MenuBar: + def __init__(self, window): + log.debug("MenuBar init..") + self.window = window + # Get the torrent menu from the glade file + self.torrentmenu = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/torrent_menu.glade")) + + # Attach the torrent_menu to the Torrent file menu + self.window.main_glade.get_widget("menu_torrent").set_submenu( + self.torrentmenu.get_widget("torrent_menu")) + + ### Connect Signals ### + self.window.main_glade.signal_autoconnect({ + ## File Menu + "on_menuitem_addtorrent_activate": \ + self.on_menuitem_addtorrent_activate, + "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, + "on_menuitem_clear_activate": \ + self.on_menuitem_clear_activate, + "on_menuitem_quit_activate": self.on_menuitem_quit_activate, + + ## Edit Menu + "on_menuitem_preferences_activate": \ + self.on_menuitem_preferences_activate, + "on_menuitem_plugins_activate": self.on_menuitem_plugins_activate, + + ## View Menu + "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, + "on_menuitem_infopane_toggled": self.on_menuitem_infopane_toggled, + + ## Help Menu + "on_menuitem_about_activate": self.on_menuitem_about_activate + }) + + self.torrentmenu.signal_autoconnect({ + ## Torrent Menu + "on_menuitem_pause_activate": self.on_menuitem_pause_activate, + "on_menuitem_updatetracker_activate": \ + self.on_menuitem_updatetracker_activate, + "on_menuitem_edittrackers_activate": \ + self.on_menuitem_edittrackers_activate, + "on_menuitem_remove_activate": self.on_menuitem_remove_activate, + "on_menuitem_queuetop_activate": \ + self.on_menuitem_queuetop_activate, + "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, + "on_menuitem_queuedown_activate": \ + self.on_menuitem_queuedown_activate, + "on_menuitem_queuebottom_activate": \ + self.on_menuitem_queuebottom_activate + }) + + ### Callbacks ### + + ## File Menu ## + def on_menuitem_addtorrent_activate(self, data=None): + log.debug("on_menuitem_addtorrent_activate") + def on_menuitem_addurl_activate(self, data=None): + log.debug("on_menuitem_addurl_activate") + def on_menuitem_clear_activate(self, data=None): + log.debug("on_menuitem_clear_activate") + def on_menuitem_quit_activate(self, data=None): + log.debug("on_menuitem_quit_activate") + self.window.quit() + + ## Edit Menu ## + def on_menuitem_preferences_activate(self, data=None): + log.debug("on_menuitem_preferences_activate") + def on_menuitem_plugins_activate(self, data=None): + log.debug("on_menuitem_plugins_activate") + + ## Torrent Menu ## + def on_menuitem_pause_activate(self, data=None): + log.debug("on_menuitem_pause_activate") + def on_menuitem_updatetracker_activate(self, data=None): + log.debug("on_menuitem_updatetracker_activate") + def on_menuitem_edittrackers_activate(self, data=None): + log.debug("on_menuitem_edittrackers_activate") + def on_menuitem_remove_activate(self, data=None): + log.debug("on_menuitem_remove_activate") + def on_menuitem_queuetop_activate(self, data=None): + log.debug("on_menuitem_queuetop_activate") + def on_menuitem_queueup_activate(self, data=None): + log.debug("on_menuitem_queueup_activate") + def on_menuitem_queuedown_activate(self, data=None): + log.debug("on_menuitem_queuedown_activate") + def on_menuitem_queuebottom_activate(self, data=None): + log.debug("on_menuitem_queuebottom_activate") + + ## View Menu ## + def on_menuitem_toolbar_toggled(self, data=None): + log.debug("on_menuitem_toolbar_toggled") + def on_menuitem_infopane_toggled(self, data=None): + log.debug("on_menuitem_infopane_toggled") + + ## Help Menu ## + def on_menuitem_about_activate(self, data=None): + log.debug("on_menuitem_about_activate") + diff --git a/deluge/ui/gtkui/po/deluge.pot b/deluge/ui/gtkui/po/deluge.pot new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py new file mode 100644 index 000000000..2bb60891e --- /dev/null +++ b/deluge/ui/gtkui/toolbar.py @@ -0,0 +1,81 @@ +# +# toolbar.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade + +# Get the logger +log = logging.getLogger("deluge") + +class ToolBar: + def __init__(self, window): + log.debug("ToolBar Init..") + self.window = window + + ### Connect Signals ### + self.window.main_glade.signal_autoconnect({ + "on_toolbutton_add_clicked": self.on_toolbutton_add_clicked, + "on_toolbutton_remove_clicked": self.on_toolbutton_remove_clicked, + "on_toolbutton_clear_clicked": self.on_toolbutton_clear_clicked, + "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked, + "on_toolbutton_queueup_clicked": \ + self.on_toolbutton_queueup_clicked, + "on_toolbutton_queuedown_clicked": \ + self.on_toolbutton_queuedown_clicked, + "on_toolbutton_preferences_clicked": \ + self.on_toolbutton_preferences_clicked, + "on_toolbutton_plugins_clicked": \ + self.on_toolbutton_plugins_clicked, + }) + + ### Callbacks ### + def on_toolbutton_add_clicked(self, data): + log.debug("on_toolbutton_add_clicked") + def on_toolbutton_remove_clicked(self, data): + log.debug("on_toolbutton_remove_clicked") + def on_toolbutton_clear_clicked(self, data): + log.debug("on_toolbutton_clear_clicked") + def on_toolbutton_pause_clicked(self, data): + log.debug("on_toolbutton_pause_clicked") + def on_toolbutton_queueup_clicked(self, data): + log.debug("on_toolbutton_queueup_clicked") + def on_toolbutton_queuedown_clicked(self, data): + log.debug("on_toolbutton_queuedown_clicked") + def on_toolbutton_preferences_clicked(self, data): + log.debug("on_toolbutton_preferences_clicked") + def on_toolbutton_plugins_clicked(self, data): + log.debug("on_toolbutton_plugins_clicked") + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py new file mode 100644 index 000000000..c34f4b054 --- /dev/null +++ b/deluge/ui/gtkui/torrentview.py @@ -0,0 +1,158 @@ +# +# torrentview.py +# +# Copyright (C) Andrew Resch 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import gobject +import gettext + +import columns + +# Get the logger +log = logging.getLogger("deluge") + +class TorrentView: + def __init__(self, window): + log.debug("TorrentView Init..") + self.window = window + # Get the torrent_view widget + self.torrent_view = self.window.main_glade.get_widget("torrent_view") + + ## TreeModel setup ## + # UID, Q#, Status Icon, Name, Size, Progress, Message, Seeders, Peers, + # DL, UL, ETA, Share + self.torrent_model = gtk.ListStore(int, gobject.TYPE_UINT, + gtk.gdk.Pixbuf, str, gobject.TYPE_UINT64, float, str, int, int, + int, int, int, int, gobject.TYPE_UINT, float) + + ## TreeView setup ## + self.torrent_view.set_model(self.torrent_model) + self.torrent_view.set_rules_hint(True) + self.torrent_view.set_reorderable(True) + self.torrent_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + # Initializes the columns for the torrent_view + (TORRENT_VIEW_COL_UID, + TORRENT_VIEW_COL_QUEUE, + TORRENT_VIEW_COL_STATUSICON, + TORRENT_VIEW_COL_NAME, + TORRENT_VIEW_COL_SIZE, + TORRENT_VIEW_COL_PROGRESS, + TORRENT_VIEW_COL_STATUS, + TORRENT_VIEW_COL_CONNECTED_SEEDS, + TORRENT_VIEW_COL_SEEDS, + TORRENT_VIEW_COL_CONNECTED_PEERS, + TORRENT_VIEW_COL_PEERS, + TORRENT_VIEW_COL_DOWNLOAD, + TORRENT_VIEW_COL_UPLOAD, + TORRENT_VIEW_COL_ETA, + TORRENT_VIEW_COL_RATIO) = range(15) + + self.queue_column = columns.add_text_column( + self.torrent_view, "#", + TORRENT_VIEW_COL_QUEUE) + self.name_column = columns.add_texticon_column( + self.torrent_view, + _("Name"), + TORRENT_VIEW_COL_STATUSICON, + TORRENT_VIEW_COL_NAME) + self.size_column = columns.add_func_column( + self.torrent_view, + _("Size"), + columns.cell_data_size, + TORRENT_VIEW_COL_SIZE) + self.status_column = columns.add_progress_column( + self.torrent_view, + _("Status"), + TORRENT_VIEW_COL_PROGRESS, + TORRENT_VIEW_COL_STATUS) + self.seed_column = columns.add_func_column( + self.torrent_view, + _("Seeders"), + columns.cell_data_peer, + (TORRENT_VIEW_COL_CONNECTED_SEEDS, TORRENT_VIEW_COL_SEEDS)) + self.peer_column = columns.add_func_column( + self.torrent_view, + _("Peers"), + columns.cell_data_peer, + (TORRENT_VIEW_COL_CONNECTED_PEERS, TORRENT_VIEW_COL_PEERS)) + self.dl_column = columns.add_func_column( + self.torrent_view, + _("Down Speed"), + columns.cell_data_speed, + TORRENT_VIEW_COL_DOWNLOAD) + self.ul_column = columns.add_func_column( + self.torrent_view, + _("Up Speed"), + columns.cell_data_speed, + TORRENT_VIEW_COL_UPLOAD) + self.eta_column = columns.add_func_column( + self.torrent_view, + _("ETA"), + columns.cell_data_time, + TORRENT_VIEW_COL_ETA) + self.share_column = columns.add_func_column( + self.torrent_view, + _("Ratio"), + columns.cell_data_ratio, + TORRENT_VIEW_COL_RATIO) + + # Set some column settings + self.status_column.set_expand(True) + self.name_column.set_sort_column_id(TORRENT_VIEW_COL_NAME) + self.seed_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_SEEDS) + self.peer_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_PEERS) + + # Set the default sort column to the queue column + self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, gtk.SORT_ASCENDING) + + ### Connect Signals ### + # Connect to the 'button-press-event' to know when to bring up the + # torrent menu popup. + self.torrent_view.connect("button-press-event", + self.on_button_press_event) + # Connect to the 'changed' event of TreeViewSelection to get selection + # changes. + self.torrent_view.get_selection().connect("changed", + self.on_selection_changed) + + ### Callbacks ### + def on_button_press_event(self, widget, event): + log.debug("on_button_press_event") + + def on_selection_changed(self, treeselection, data): + log.debug("on_selection_changed") + diff --git a/setup.py b/setup.py index de554e630..747ce88e8 100644 --- a/setup.py +++ b/setup.py @@ -84,10 +84,7 @@ libtorrent = Extension( ) # Main setup - -_datafiles = [ -] - + setup( name = "deluge", fullname = "Deluge Bittorent Client", @@ -102,7 +99,9 @@ setup( include_package_data = True, package_data = {"deluge": ["ui/gtkui/glade/*.glade", - "data/pixmaps/*.png"]}, + "data/pixmaps/*.png", + "ui/gtkui/po/*.po?" + ]}, ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(), From be081ae1035c869907fbfd0d0505da5e56f2e869 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 21 Jul 2007 00:50:13 +0000 Subject: [PATCH 0015/1009] Can now add torrents from the UI. Various other updates. --- deluge/common.py | 2 +- deluge/config.py | 6 +- deluge/core/core.py | 4 +- deluge/core/daemon.py | 2 +- deluge/core/torrent.py | 2 +- deluge/main.py | 2 +- deluge/ui/gtkui/addtorrentdialog.py | 88 +++++++++++++++++++++++++++++ deluge/ui/gtkui/columns.py | 3 +- deluge/ui/gtkui/functions.py | 77 +++++++++++++++++++++++++ deluge/ui/gtkui/gtkui.py | 9 +-- deluge/ui/gtkui/mainwindow.py | 6 +- deluge/ui/gtkui/menubar.py | 5 +- deluge/ui/gtkui/toolbar.py | 5 +- deluge/ui/gtkui/torrentview.py | 11 ++-- deluge/ui/ui.py | 25 +------- setup.py | 2 +- 16 files changed, 200 insertions(+), 49 deletions(-) create mode 100644 deluge/ui/gtkui/addtorrentdialog.py create mode 100644 deluge/ui/gtkui/functions.py diff --git a/deluge/common.py b/deluge/common.py index 49ece9747..36e249496 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -1,7 +1,7 @@ # # common.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # diff --git a/deluge/config.py b/deluge/config.py index b89ac210f..4981a1775 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -1,7 +1,7 @@ # # config.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -51,6 +51,10 @@ class Config: # Load the config from file in the config_dir self.config_file = deluge.common.get_config_dir(filename) self.load(self.config_file) + + def __del__(self): + log.debug("Config object deconstructing..") + self.save() def load(self, filename=None): # Use self.config_file if filename is None diff --git a/deluge/core/core.py b/deluge/core/core.py index 1ebe30539..05b2a07c6 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -1,7 +1,7 @@ # # core.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -118,4 +118,4 @@ class Core(dbus.service.Object): signature="") def torrent_added(self): """Emitted when a new torrent is added to the core""" - pass + log.debug("torrent_added signal emitted") diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index d9f171eab..710947fcb 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -1,7 +1,7 @@ # # daemon.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index c164e906a..c9c5d6019 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -1,7 +1,7 @@ # # torrent.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # diff --git a/deluge/main.py b/deluge/main.py index fa09278a7..393cd9f02 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -1,7 +1,7 @@ # # main.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py new file mode 100644 index 000000000..7cde8358e --- /dev/null +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -0,0 +1,88 @@ +# +# addtorrentdialog.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade + +from deluge.config import Config + +# Get the logger +log = logging.getLogger("deluge") + +class AddTorrentDialog: + def __init__(self, parent=None): + # Setup the filechooserdialog + self.chooser = gtk.FileChooserDialog(_("Choose a .torrent file"), + parent, + gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, + gtk.RESPONSE_OK)) + + self.chooser.set_select_multiple(True) + self.chooser.set_property("skip-taskbar-hint", True) + + # Add .torrent and * file filters + f0 = gtk.FileFilter() + f0.set_name(_("Torrent files")) + f0.add_pattern("*." + "torrent") + self.chooser.add_filter(f0) + f1 = gtk.FileFilter() + f1.set_name(_("All files")) + f1.add_pattern("*") + self.chooser.add_filter(f1) + + # Load the 'default_load_path' from the config + self.config = Config("gtkui.conf") + if self.config.get("default_load_path") is not None: + self.chooser.set_current_folder( + self.config.get("default_load_path")) + + + def run(self): + """Returns a list of selected files or None if no files were selected. + """ + # Run the dialog + response = self.chooser.run() + + if response == gtk.RESPONSE_OK: + result = self.chooser.get_filenames() + self.config.set("default_load_path", + self.chooser.get_current_folder()) + else: + result = None + + self.chooser.destroy() + return result diff --git a/deluge/ui/gtkui/columns.py b/deluge/ui/gtkui/columns.py index c6c2ac3e0..23ceeda12 100644 --- a/deluge/ui/gtkui/columns.py +++ b/deluge/ui/gtkui/columns.py @@ -1,7 +1,8 @@ # # columns.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2006 Zach Tibbitts ('zachtib') +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py new file mode 100644 index 000000000..750e0e210 --- /dev/null +++ b/deluge/ui/gtkui/functions.py @@ -0,0 +1,77 @@ +# +# functions.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade + +from addtorrentdialog import AddTorrentDialog +from deluge.ui.ui import UI + +# Get the logger +log = logging.getLogger("deluge") + +def get_core(): + """Get the core object and return it""" + log.debug("Getting core proxy object from DBUS..") + # Get the proxy object from DBUS + bus = dbus.SessionBus() + proxy = bus.get_object("org.deluge_torrent.Deluge", + "/org/deluge_torrent/Core") + core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") + log.debug("Got core proxy object..") + return core + +def add_torrent_file(): + """Opens a file chooser dialog and adds any files selected to the core""" + at_dialog = AddTorrentDialog() + torrent_files = at_dialog.run() + log.debug("Attempting to add torrent files: %s", torrent_files) + core = get_core() + for torrent_file in torrent_files: + core.add_torrent_file(torrent_file) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index ec3f5cd2c..62e231983 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -1,7 +1,7 @@ # # gtkui.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -45,10 +45,7 @@ from mainwindow import MainWindow log = logging.getLogger("deluge") class GtkUI: - def __init__(self, core): - # Get the core proxy object from the args - self.core = core - + def __init__(self): # Initialize gettext gettext.bindtextdomain("deluge", pkg_resources.resource_filename( @@ -61,7 +58,7 @@ class GtkUI: "po")) # Initialize the main window - self.main_window = MainWindow(self.core) + self.main_window = MainWindow() # Show the main window self.main_window.show() diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 8f39cf9c9..f9641954c 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -1,7 +1,7 @@ # # mainwindow.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -46,9 +46,7 @@ from torrentview import TorrentView log = logging.getLogger("deluge") class MainWindow: - def __init__(self, core): - self.core = core - + def __init__(self): # Get the glade file for the main window self.main_glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 15dbede6e..0b3e6a036 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -1,7 +1,7 @@ # # menubar.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -38,6 +38,8 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources +import functions + # Get the logger log = logging.getLogger("deluge") @@ -99,6 +101,7 @@ class MenuBar: ## File Menu ## def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") + functions.add_torrent_file() def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") def on_menuitem_clear_activate(self, data=None): diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 2bb60891e..305f2faa4 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -1,7 +1,7 @@ # # toolbar.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -37,6 +37,8 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade +import functions + # Get the logger log = logging.getLogger("deluge") @@ -64,6 +66,7 @@ class ToolBar: ### Callbacks ### def on_toolbutton_add_clicked(self, data): log.debug("on_toolbutton_add_clicked") + functions.add_torrent_file() def on_toolbutton_remove_clicked(self, data): log.debug("on_toolbutton_remove_clicked") def on_toolbutton_clear_clicked(self, data): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index c34f4b054..cab25c937 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -1,7 +1,7 @@ # # torrentview.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -94,9 +94,9 @@ class TorrentView: _("Size"), columns.cell_data_size, TORRENT_VIEW_COL_SIZE) - self.status_column = columns.add_progress_column( + self.progress_column = columns.add_progress_column( self.torrent_view, - _("Status"), + _("Progress"), TORRENT_VIEW_COL_PROGRESS, TORRENT_VIEW_COL_STATUS) self.seed_column = columns.add_func_column( @@ -131,13 +131,14 @@ class TorrentView: TORRENT_VIEW_COL_RATIO) # Set some column settings - self.status_column.set_expand(True) + self.progress_column.set_expand(True) self.name_column.set_sort_column_id(TORRENT_VIEW_COL_NAME) self.seed_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_SEEDS) self.peer_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_PEERS) # Set the default sort column to the queue column - self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, gtk.SORT_ASCENDING) + self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, + gtk.SORT_ASCENDING) ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index d9b2a6d96..24edba815 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -1,7 +1,7 @@ # # ui.py # -# Copyright (C) Andrew Resch 2007 +# Copyright (C) 2007 Andrew Resch ('andar') # # Deluge is free software. # @@ -33,19 +33,6 @@ import logging -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True - import time from deluge.config import Config @@ -61,16 +48,8 @@ class UI: def __init__(self): log.debug("UI init..") self.config = Config("ui.conf", DEFAULT_PREFS) - log.debug("Getting core proxy object from DBUS..") - # Get the proxy object from DBUS - bus = dbus.SessionBus() - proxy = bus.get_object("org.deluge_torrent.Deluge", - "/org/deluge_torrent/Core") - self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") - log.debug("Got core proxy object..") if self.config["selected_ui"] == "gtk": log.info("Starting GtkUI..") from deluge.ui.gtkui.gtkui import GtkUI - ui = GtkUI(self.core) - + ui = GtkUI() diff --git a/setup.py b/setup.py index 747ce88e8..4f14bfcba 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # setup.py # -# Copyright (c) 2007 Andrew Resch ('andar') +# Copyright (C) 2007 Andrew Resch ('andar') # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 7ca17a3922d8f8a5009f3bf0345e1b98ffd91391 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 22 Jul 2007 03:01:38 +0000 Subject: [PATCH 0016/1009] Changed the way adding torrents is done. The core is now sent the torrent file data and saves it's own copy of the torrent file. Start of receiving signals in the UI. --- deluge/common.py | 4 ++ deluge/core/core.py | 72 ++++++++++++++++++++++++++++++------ deluge/core/torrent.py | 10 ++--- deluge/ui/gtkui/functions.py | 12 +++++- deluge/ui/gtkui/gtkui.py | 4 ++ deluge/ui/gtkui/signals.py | 66 +++++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 deluge/ui/gtkui/signals.py diff --git a/deluge/common.py b/deluge/common.py index 36e249496..4daa2aeef 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -59,6 +59,10 @@ def get_default_download_dir(): """Returns the default download directory""" return os.environ.get("HOME") +def get_default_torrent_dir(): + """Returns the default torrent directory""" + return os.path.join(get_config_dir(), "torrentfiles") + ## Formatting text functions def estimate_eta(total_size, total_done, download_rate): diff --git a/deluge/core/core.py b/deluge/core/core.py index 05b2a07c6..3ee9b8ed5 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -32,6 +32,7 @@ # statement from all source files in the program, then also delete it here. import logging +import os.path try: import dbus, dbus.service @@ -57,18 +58,27 @@ from deluge.core.torrent import Torrent log = logging.getLogger("deluge") DEFAULT_PREFS = { - "listen_ports": [6881, 6891], + "compact_allocation": True, "download_location": deluge.common.get_default_download_dir(), - "compact_allocation": True + "listen_ports": [6881, 6891], + "torrentfiles_location": deluge.common.get_default_torrent_dir() } class Core(dbus.service.Object): def __init__(self, path="/org/deluge_torrent/Core"): log.debug("Core init..") + + # A dictionary containing hash keys to Torrent objects + self.torrents = {} + + # Setup DBUS bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", bus=dbus.SessionBus()) dbus.service.Object.__init__(self, bus_name, path) + + # Get config self.config = Config("core.conf", DEFAULT_PREFS) + # Setup the libtorrent session and listen on the configured ports log.debug("Starting libtorrent session..") self.session = lt.session() @@ -81,20 +91,52 @@ class Core(dbus.service.Object): self.loop = gobject.MainLoop() self.loop.run() - # Exported Methods @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def add_torrent_file(self, _filename): + in_signature="say", out_signature="") + def add_torrent_file(self, filename, filedump): """Adds a torrent file to the libtorrent session + This requires the torrents filename and a dump of it's content """ - log.info("Adding torrent: %s", _filename) - torrent = Torrent(filename=_filename) - self.session.add_torrent(torrent.torrent_info, - self.config["download_location"], + log.info("Adding torrent: %s", filename) + + # Convert the filedump data array into a string of bytes + filedump = "".join(chr(b) for b in filedump) + + # Bdecode the filedata sent from the UI + torrent_filedump = lt.bdecode(filedump) + try: + handle = self.session.add_torrent(lt.torrent_info(torrent_filedump), + self.config["download_location"], self.config["compact_allocation"]) + except RuntimeError: + log.warning("Error adding torrent") + + if not handle or not handle.is_valid(): + # The torrent was not added to the session + # Emit the torrent_add_failed signal + self.torrent_add_failed() + return + + # Write the .torrent file to the torrent directory + log.debug("Attemping to save torrent file: %s", filename) + try: + f = open(os.path.join(self.config["torrentfiles_location"], + filename), + "wb") + f.write(filedump) + f.close() + except IOError: + log.warning("Unable to save torrent file: %s", filename) + + # Create a Torrent object + torrent = Torrent(handle) + + # Store the Torrent object in the dictionary + self.torrents[handle.info_hash()] = torrent + # Emit the torrent_added signal - self.torrent_added() + self.torrent_added(str(handle.info_hash())) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -115,7 +157,13 @@ class Core(dbus.service.Object): # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="") - def torrent_added(self): + signature="s") + def torrent_added(self, torrentid): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="") + def torrent_add_failed(self): + """Emitted when a new torrent fails addition to the session""" + log.debug("torrent_add_failed signal emitted") diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index c9c5d6019..4897ba468 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -34,9 +34,7 @@ import deluge.libtorrent as lt class Torrent: - def __init__(self, filename=None, url=None): - # Load the torrent file - if filename is not None: - torrent_file = lt.bdecode(open(filename, 'rb').read()) - self.torrent_info = lt.torrent_info(torrent_file) - + def __init__(self, handle): + # Set the libtorrent handle + self.handle = handle + diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py index 750e0e210..f06e8f332 100644 --- a/deluge/ui/gtkui/functions.py +++ b/deluge/ui/gtkui/functions.py @@ -32,6 +32,7 @@ # statement from all source files in the program, then also delete it here. import logging +import os.path try: import dbus, dbus.service @@ -71,7 +72,16 @@ def add_torrent_file(): """Opens a file chooser dialog and adds any files selected to the core""" at_dialog = AddTorrentDialog() torrent_files = at_dialog.run() + if torrent_files is None: + log.debug("No torrent files selected..") + return log.debug("Attempting to add torrent files: %s", torrent_files) core = get_core() for torrent_file in torrent_files: - core.add_torrent_file(torrent_file) + # Open the .torrent file for reading because we need to send it's + # contents to the core. + f = open(torrent_file, "rb") + # Get the filename because the core doesn't want a path. + (path, filename) = os.path.split(torrent_file) + core.add_torrent_file(filename, f.read()) + f.close() diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 62e231983..dfd64fcff 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -40,6 +40,7 @@ import gettext import pkg_resources from mainwindow import MainWindow +from signals import Signals # Get the logger log = logging.getLogger("deluge") @@ -60,6 +61,9 @@ class GtkUI: # Initialize the main window self.main_window = MainWindow() + # Start the signal receiver + self.signal_receiver = Signals() + # Show the main window self.main_window.show() diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py new file mode 100644 index 000000000..90ba2a654 --- /dev/null +++ b/deluge/ui/gtkui/signals.py @@ -0,0 +1,66 @@ +# +# signals.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade + +import functions +from deluge.config import Config + +# Get the logger +log = logging.getLogger("deluge") + +class Signals: + def __init__(self): + core = functions.get_core() + core.connect_to_signal("torrent_added", self.torrent_added_signal) + + def torrent_added_signal(self, torrentid): + log.debug("torrent_added signal received..") + log.debug("torrent id: %s", torrentid) From 5145dafeae0020720d61c77543f936150f192c71 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 23 Jul 2007 04:42:26 +0000 Subject: [PATCH 0017/1009] Added torrent queueing in the core. --- deluge/core/core.py | 83 +++++++++++++++++++---- deluge/core/torrent.py | 4 ++ deluge/core/torrentqueue.py | 132 ++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 deluge/core/torrentqueue.py diff --git a/deluge/core/core.py b/deluge/core/core.py index 3ee9b8ed5..86033b3db 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -53,6 +53,7 @@ import deluge.libtorrent as lt from deluge.config import Config import deluge.common from deluge.core.torrent import Torrent +from deluge.core.torrentqueue import TorrentQueue # Get the logger log = logging.getLogger("deluge") @@ -70,6 +71,8 @@ class Core(dbus.service.Object): # A dictionary containing hash keys to Torrent objects self.torrents = {} + # Instantiate the TorrentQueue + self.queue = TorrentQueue() # Setup DBUS bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", @@ -133,32 +136,62 @@ class Core(dbus.service.Object): torrent = Torrent(handle) # Store the Torrent object in the dictionary - self.torrents[handle.info_hash()] = torrent + self.torrents[str(handle.info_hash())] = torrent + + # Add the torrent id to the queue + self.queue.append(str(handle.info_hash())) # Emit the torrent_added signal self.torrent_added(str(handle.info_hash())) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def add_torrent_url(self, _url): - """Adds a torrent from url to the libtorrent session - """ - log.info("Adding torrent: %s", _url) - torrent = Torrent(url=_url) - self.session.add_torrent(torrent.torrent_info, - self.config["download_location"], - self.config["compact_allocation"]) - @dbus.service.method("org.deluge_torrent.Deluge") def shutdown(self): log.info("Shutting down core..") self.loop.quit() + + ## Queueing functions ###### + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def queue_top(self, torrent_id): + # If the queue method returns True, then we should emit a signal + if self.queue.top(torrent_id): + self.torrent_queue_top() + # Store the new torrent position in the torrent object + self.torrents[torrent_id].set_position(self.queue[torrent_id]) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def queue_up(self, torrent_id): + # If the queue method returns True, then we should emit a signal + if self.queue.up(torrent_id): + self.torrent_queue_up() + # Store the new torrent position in the torrent object + self.torrents[torrent_id].set_position(self.queue[torrent_id]) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def queue_down(self, torrent_id): + # If the queue method returns True, then we should emit a signal + if self.queue.down(torrent_id): + self.torrent_queue_down() + # Store the new torrent position in the torrent object + self.torrents[torrent_id].set_position(self.queue[torrent_id]) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def queue_bottom(self, torrent_id): + # If the queue method returns True, then we should emit a signal + if self.queue.bottom(torrent_id): + self.torrent_queue_bottom() + # Store the new torrent position in the torrent object + self.torrents[torrent_id].set_position(self.queue[torrent_id]) + # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") - def torrent_added(self, torrentid): + def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") @@ -167,3 +200,27 @@ class Core(dbus.service.Object): def torrent_add_failed(self): """Emitted when a new torrent fails addition to the session""" log.debug("torrent_add_failed signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") + def torrent_queue_top(self, torrent_id): + """Emitted when a torrent is queued to the top""" + log.debug("torrent_queue_top signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") + def torrent_queue_up(self, torrent_id): + """Emitted when a torrent is queued up""" + log.debug("torrent_queue_up signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") + def torrent_queue_down(self, torrent_id): + """Emitted when a torrent is queued down""" + log.debug("torrent_queue_down signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") + def torrent_queue_bottom(self, torrent_id): + """Emitted when a torrent is queued to the bottom""" + log.debug("torrent_queue_bottom signal emitted") diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 4897ba468..55ebfe9e7 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -37,4 +37,8 @@ class Torrent: def __init__(self, handle): # Set the libtorrent handle self.handle = handle + + def set_position(self, position): + """Store the torrents queue position""" + self.position = position diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py new file mode 100644 index 000000000..3e09cadcb --- /dev/null +++ b/deluge/core/torrentqueue.py @@ -0,0 +1,132 @@ +# +# torrentqueue.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +# Get the logger +log = logging.getLogger("deluge") + +class TorrentQueue: + def __init__(self): + log.debug("TorrentQueue init..") + self.queue = [] + + def __getitem__(self, torrent_id): + """Return the queue position of the torrent_id""" + return self.queue.index(torrent_id) + + def append(self, torrent_id): + """Append torrent_id to the bottom of the queue""" + log.debug("Append torrent %s to queue..", torrent_id) + self.queue.append(torrent_id) + + def prepend(self, torrent_id): + """Prepend torrent_id to the top of the queue""" + log.debug("Prepend torrent %s to queue..", torrent_id) + self.queue.insert(0, torrent_id) + + def up(self, torrent_id): + """Move torrent_id up one in the queue""" + if torrent_id not in self.queue: + # Raise KeyError if the torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s up..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue up if torrent is already at top + if index is 0: + return False + + # Pop and insert the torrent_id at index - 1 + self.queue.insert(index - 1, self.queue.pop(index)) + + return True + + def top(self, torrent_id): + """Move torrent_id to top of the queue""" + if torrent_id not in self.queue: + # Raise KeyError if the torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s to top..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue up if torrent is already at top + if index is 0: + return False + + # Pop and prepend the torrent_id + self.prepend(self.queue.pop(index)) + + return True + + def down(self, torrent_id): + """Move torrent_id down one in the queue""" + if torrent_id not in self.queue: + # Raise KeyError if torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s down..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue down of torrent_id is at bottom + if index is len(self.queue) - 1: + return False + + # Pop and insert the torrent_id at index + 1 + self.queue.insert(index + 1, self.queue.pop(index)) + + return True + + def bottom(self, torrent_id): + """Move torrent_id to bottom of the queue""" + if torrent_id not in self.queue: + # Raise KeyError if torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s to bottom..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue down of torrent_id is at bottom + if index is len(self.queue) - 1: + return False + + # Pop and append the torrent_id + self.append(self.queue.pop(index)) + + return True From 626e7657d05eb4777190142e5efcc886f8ffd9e2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 23 Jul 2007 05:36:20 +0000 Subject: [PATCH 0018/1009] Sync to libtorrent revision 1415 --- libtorrent/bindings/python/src/docstrings.cpp | 4 + libtorrent/bindings/python/src/session.cpp | 12 + .../bindings/python/src/torrent_handle.cpp | 34 +- .../bindings/python/src/torrent_info.cpp | 3 +- .../include/libtorrent/bandwidth_manager.hpp | 5 +- .../include/libtorrent/bt_peer_connection.hpp | 4 +- .../include/libtorrent/connection_queue.hpp | 3 + .../include/libtorrent/peer_connection.hpp | 6 + .../include/libtorrent/piece_picker.hpp | 43 ++- libtorrent/include/libtorrent/policy.hpp | 9 +- .../include/libtorrent/session_settings.hpp | 5 + libtorrent/include/libtorrent/stat.hpp | 2 + libtorrent/include/libtorrent/torrent.hpp | 14 + .../include/libtorrent/torrent_handle.hpp | 24 +- libtorrent/src/Makefile.am | 2 +- libtorrent/src/bt_peer_connection.cpp | 169 ++++++---- libtorrent/src/connection_queue.cpp | 16 + libtorrent/src/disk_io_thread.cpp | 1 + libtorrent/src/pe_crypto.cpp | 15 +- libtorrent/src/peer_connection.cpp | 96 ++---- libtorrent/src/piece_picker.cpp | 313 ++++++++++++------ libtorrent/src/policy.cpp | 238 ++++++------- libtorrent/src/session_impl.cpp | 18 + libtorrent/src/storage.cpp | 8 +- libtorrent/src/torrent.cpp | 131 +++++--- libtorrent/src/torrent_handle.cpp | 50 ++- 26 files changed, 756 insertions(+), 469 deletions(-) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index c9a0c8a85..f51487a23 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -130,6 +130,10 @@ char const* session_remove_torrent_doc = "Close all peer connections associated with the torrent and tell the\n" "tracker that we've stopped participating in the swarm."; +char const* session_download_rate_limit_doc = + ""; +char const* session_upload_rate_limit_doc = + ""; char const* session_set_download_rate_limit_doc = ""; char const* session_set_upload_rate_limit_doc = diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index e6050d1bb..9f7d530af 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -38,7 +38,9 @@ extern char const* session_dht_state_doc; extern char const* session_add_torrent_doc; extern char const* session_remove_torrent_doc; extern char const* session_set_download_rate_limit_doc; +extern char const* session_download_rate_limit_doc; extern char const* session_set_upload_rate_limit_doc; +extern char const* session_upload_rate_limit_doc; extern char const* session_set_max_uploads_doc; extern char const* session_set_max_connections_doc; extern char const* session_set_max_half_open_connections_doc; @@ -176,10 +178,20 @@ void bind_session() "set_download_rate_limit", allow_threads(&session::set_download_rate_limit) , session_set_download_rate_limit_doc ) + .def( + "download_rate_limit", allow_threads(&session::download_rate_limit) + , session_download_rate_limit_doc + ) + .def( "set_upload_rate_limit", allow_threads(&session::set_upload_rate_limit) , session_set_upload_rate_limit_doc ) + .def( + "upload_rate_limit", allow_threads(&session::upload_rate_limit) + , session_upload_rate_limit_doc + ) + .def( "set_max_uploads", allow_threads(&session::set_max_uploads) , session_set_max_uploads_doc diff --git a/libtorrent/bindings/python/src/torrent_handle.cpp b/libtorrent/bindings/python/src/torrent_handle.cpp index 6e0bc5034..98540a5f8 100755 --- a/libtorrent/bindings/python/src/torrent_handle.cpp +++ b/libtorrent/bindings/python/src/torrent_handle.cpp @@ -64,6 +64,30 @@ list get_peer_info(torrent_handle const& handle) return result; } + +void prioritize_files(torrent_handle& info, object o) +{ + + std::vector result; + try + { + object iter_obj = object( handle<>( PyObject_GetIter( o.ptr() ) )); + while( 1 ) + { + object obj = extract( iter_obj.attr( "next" )() ); + result.push_back(extract( obj )); + } + } + catch( error_already_set ) + { + PyErr_Clear(); + info.prioritize_files(result); + return; + } + +} + + void replace_trackers(torrent_handle& info, object trackers) { object iter(trackers.attr("__iter__")()); @@ -106,7 +130,9 @@ list get_download_queue(torrent_handle& handle) { dict block_info; block_info["state"] = i->blocks[k].state; - block_info["num_downloads"] = i->blocks[k].num_downloads; + block_info["num_peers"] = i->blocks[k].num_peers; + block_info["bytes_progress"] = i->blocks[k].bytes_progress; + block_info["block_size"] = i->blocks[k].block_size; // block_info["peer"] = i->info[k].peer; block_list.append(block_info); } @@ -124,6 +150,9 @@ void bind_torrent_handle() void (torrent_handle::*force_reannounce1)(boost::posix_time::time_duration) const = &torrent_handle::force_reannounce; + int (torrent_handle::*piece_priority0)(int) const = &torrent_handle::piece_priority; + void (torrent_handle::*piece_priority1)(int, int) const = &torrent_handle::piece_priority; + return_value_policy copy; #define _ allow_threads @@ -148,6 +177,8 @@ void bind_torrent_handle() .def("is_paused", _(&torrent_handle::is_paused)) .def("is_seed", _(&torrent_handle::is_seed)) .def("filter_piece", _(&torrent_handle::filter_piece)) + .def("piece_priority", _(piece_priority0)) + .def("piece_priority", _(piece_priority1)) .def("is_piece_filtered", _(&torrent_handle::is_piece_filtered)) .def("has_metadata", _(&torrent_handle::has_metadata)) .def("save_path", _(&torrent_handle::save_path)) @@ -156,6 +187,7 @@ void bind_torrent_handle() .def("file_progress", file_progress) .def("trackers", range(begin_trackers, end_trackers)) .def("replace_trackers", replace_trackers) + .def("prioritize_files", prioritize_files) .def("get_peer_info", get_peer_info) .def("get_download_queue", get_download_queue) ; diff --git a/libtorrent/bindings/python/src/torrent_info.cpp b/libtorrent/bindings/python/src/torrent_info.cpp index 1c31ec185..301c4a5bf 100755 --- a/libtorrent/bindings/python/src/torrent_info.cpp +++ b/libtorrent/bindings/python/src/torrent_info.cpp @@ -70,7 +70,8 @@ void bind_torrent_info() .def("hash_for_piece", &torrent_info::hash_for_piece, copy) .def("piece_size", &torrent_info::piece_size) - + + .def("num_files", &torrent_info::num_files) .def("file_at", &torrent_info::file_at, return_internal_reference<>()) .def("files", range(&torrent_info::begin_files, &torrent_info::end_files)) diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 30f99d0cf..4df9d4f2f 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -391,10 +391,6 @@ private: break; } - // don't hand out chunks larger than the throttle - // per second on the torrent - assert(qe.max_block_size <= t->bandwidth_throttle(m_channel)); - // so, hand out max_assignable, but no more than // the available bandwidth (amount) and no more // than the max_bandwidth_block_size @@ -402,6 +398,7 @@ private: , amount); assert(hand_out_amount > 0); amount -= hand_out_amount; + assert(hand_out_amount <= qe.max_block_size); t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); qe.peer->assign_bandwidth(m_channel, hand_out_amount); add_history_entry(history_entry( diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index 0f5e58e9d..beec94979 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -202,7 +202,7 @@ namespace libtorrent void write_metadata_request(std::pair req); void write_keepalive(); void write_dht_port(int listen_port); - void on_connected() {} + void on_connected(); void on_metadata(); #ifndef NDEBUG @@ -370,6 +370,8 @@ namespace libtorrent bool m_sent_bitfield; bool m_in_constructor; + + bool m_sent_handshake; #endif }; diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp index 05a8a61fe..17be248bf 100644 --- a/libtorrent/include/libtorrent/connection_queue.hpp +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -88,6 +88,9 @@ private: int m_half_open_limit; deadline_timer m_timer; +#ifndef NDEBUG + bool m_in_timeout_function; +#endif }; } diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index b459c491e..d32d250fc 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -395,6 +395,9 @@ namespace libtorrent #ifndef TORRENT_DISABLE_ENCRYPTION buffer::interval wr_recv_buffer() { +#if defined _SECURE_SCL && _SECURE_SCL > 0 + if (m_recv_buffer.empty()) return buffer::interval(0,0); +#endif return buffer::interval(&m_recv_buffer[0] , &m_recv_buffer[0] + m_recv_pos); } @@ -402,6 +405,9 @@ namespace libtorrent buffer::const_interval receive_buffer() const { +#if defined _SECURE_SCL && _SECURE_SCL > 0 + if (m_recv_buffer.empty()) return buffer::const_interval(0,0); +#endif return buffer::const_interval(&m_recv_buffer[0] , &m_recv_buffer[0] + m_recv_pos); } diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index c52521d0a..136e71c63 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -42,7 +42,6 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(push, 1) #endif -#include #include #ifdef _MSC_VER @@ -90,12 +89,15 @@ namespace libtorrent struct block_info { - block_info(): num_downloads(0), state(state_none) {} + block_info(): peer(0), num_peers(0), state(state_none) {} // the peer this block was requested or - // downloaded from - tcp::endpoint peer; - // the number of times this block has been downloaded - unsigned num_downloads:14; + // downloaded from. This is a pointer to + // a policy::peer object + void* peer; + // the number of peers that has this block in their + // download or request queues + unsigned num_peers:14; + // the state of this block enum { state_none, state_requested, state_writing, state_finished }; unsigned state:2; }; @@ -185,12 +187,17 @@ namespace libtorrent // decides to download a piece, it must mark it as being downloaded // itself, by using the mark_as_downloading() member function. // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! - // The last argument is the tcp::endpoint of the peer that we'll download - // from. + // The last argument is the policy::peer pointer for the peer that + // we'll download from. void pick_pieces(const std::vector& pieces , std::vector& interesting_blocks , int num_pieces, bool prefer_whole_pieces - , tcp::endpoint peer, piece_state_t speed) const; + , void* peer, piece_state_t speed + , bool rarest_first) const; + + // clears the peer pointer in all downloading pieces with this + // peer pointer + void clear_peer(void* peer); // returns true if any client is currently downloading this // piece-block, or if it's queued for downloading by some client @@ -202,10 +209,11 @@ namespace libtorrent bool is_finished(piece_block block) const; // marks this piece-block as queued for downloading - void mark_as_downloading(piece_block block, tcp::endpoint const& peer + void mark_as_downloading(piece_block block, void* peer , piece_state_t s); - void mark_as_writing(piece_block block, tcp::endpoint const& peer); - void mark_as_finished(piece_block block, tcp::endpoint const& peer); + void mark_as_writing(piece_block block, void* peer); + void mark_as_finished(piece_block block, void* peer); + int num_peers(piece_block block) const; // if a piece had a hash-failure, it must be restored and // made available for redownloading @@ -224,12 +232,12 @@ namespace libtorrent // the hash-check yet int unverified_blocks() const; - void get_downloaders(std::vector& d, int index) const; + void get_downloaders(std::vector& d, int index) const; std::vector const& get_download_queue() const { return m_downloads; } - boost::optional get_downloader(piece_block block) const; + void* get_downloader(piece_block block) const; // the number of filtered pieces we don't have int num_filtered() const { return m_num_filtered; } @@ -271,7 +279,8 @@ namespace libtorrent assert(index_ >= 0); } - // selects which vector to look in + // the number of peers that has this piece + // (availability) unsigned peer_count : 10; // is 1 if the piece is marked as being downloaded unsigned downloading : 1; @@ -342,13 +351,15 @@ namespace libtorrent void add(int index); void move(int vec_index, int elem_index); + void sort_piece(std::vector::iterator dp); int add_interesting_blocks(const std::vector& piece_list , const std::vector& pieces , std::vector& interesting_blocks , std::vector& backup_blocks , int num_blocks, bool prefer_whole_pieces - , tcp::endpoint peer, piece_state_t speed) const; + , void* peer, piece_state_t speed + , bool ignore_downloading_pieces) const; downloading_piece& add_download_piece(); void erase_download_piece(std::vector::iterator i); diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index fffc3bfa2..e087ccb75 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -68,10 +68,7 @@ namespace libtorrent free_upload_amount = 4 * 16 * 1024 }; - void request_a_block( - torrent& t - , peer_connection& c - , std::vector ignore = std::vector()); + void request_a_block(torrent& t, peer_connection& c); class TORRENT_EXPORT policy { @@ -121,9 +118,9 @@ namespace libtorrent struct peer { - enum connection_type { not_connectable,connectable }; + enum connection_type { not_connectable, connectable }; - peer(const tcp::endpoint& ip, connection_type t, int src); + peer(tcp::endpoint const& ip, connection_type t, int src); size_type total_download() const; size_type total_upload() const; diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index d7a4f88c1..783492227 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -107,6 +107,7 @@ namespace libtorrent , inactivity_timeout(600) , unchoke_interval(20) , num_want(200) + , initial_picker_threshold(4) #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) #endif @@ -246,6 +247,10 @@ namespace libtorrent // the num want sent to trackers int num_want; + // while we have fewer pieces than this, pick + // random pieces instead of rarest first. + int initial_picker_threshold; + #ifndef TORRENT_DISABLE_DHT // while this is true, the dht will note be used unless the // tracker is online diff --git a/libtorrent/include/libtorrent/stat.hpp b/libtorrent/include/libtorrent/stat.hpp index 8ef8da1b4..2424d5d6c 100755 --- a/libtorrent/include/libtorrent/stat.hpp +++ b/libtorrent/include/libtorrent/stat.hpp @@ -131,6 +131,8 @@ namespace libtorrent // transfers from earlier connections. void add_stat(size_type downloaded, size_type uploaded) { + assert(downloaded >= 0); + assert(uploaded >= 0); m_total_download_payload += downloaded; m_total_upload_payload += uploaded; } diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 1274c46fc..bed076fe9 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -262,6 +262,8 @@ namespace libtorrent // decreased in the piece_picker void remove_peer(peer_connection* p); + void cancel_block(piece_block block); + bool want_more_peers() const; bool try_connect_peer(); @@ -272,6 +274,18 @@ namespace libtorrent return i->second; } +#ifndef NDEBUG + void connection_for(address const& a, std::vector& pc) + { + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + if (i->first.address() == a) pc.push_back(i->second); + } + return; + } +#endif + // the number of peers that belong to this torrent int num_peers() const { return (int)m_connections.size(); } int num_seeds() const; diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index b5e6bdc17..a4c8af923 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -101,6 +101,11 @@ namespace libtorrent , num_seeds(0) , distributed_copies(0.f) , block_size(0) + , num_uploads(0) + , num_connections(0) + , uploads_limit(0) + , connections_limit(0) + , compact_mode(false) {} enum state_t @@ -202,6 +207,15 @@ namespace libtorrent // the number of bytes each piece request asks for // and each bit in the download queue bitfield represents int block_size; + + int num_uploads; + int num_connections; + int uploads_limit; + int connections_limit; + + // true if the torrent is saved in compact mode + // false if it is saved in full allocation mode + bool compact_mode; }; struct TORRENT_EXPORT block_info @@ -210,8 +224,16 @@ namespace libtorrent { none, requested, writing, finished }; tcp::endpoint peer; + // number of bytes downloaded in this block + unsigned bytes_progress:16; + // the total number of bytes in this block + unsigned block_size:16; + // the state this block is in (see block_state_t) unsigned state:2; - unsigned num_downloads:14; + // the number of peers that has requested this block + // typically 0 or 1. If > 1, this block is in + // end game mode + unsigned num_peers:14; }; struct TORRENT_EXPORT partial_piece_info diff --git a/libtorrent/src/Makefile.am b/libtorrent/src/Makefile.am index 97e74ee25..ef834018b 100644 --- a/libtorrent/src/Makefile.am +++ b/libtorrent/src/Makefile.am @@ -13,7 +13,7 @@ kademlia/traversal_algorithm.cpp endif libtorrent_la_SOURCES = allocate_resources.cpp \ -bandwidth_manager.cpp entry.cpp escape_string.cpp \ +entry.cpp escape_string.cpp \ peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \ stat.cpp storage.cpp torrent.cpp torrent_handle.cpp pe_crypto.cpp \ diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 2483b4a2a..5b63f26cd 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -102,12 +102,73 @@ namespace libtorrent #ifndef NDEBUG , m_sent_bitfield(false) , m_in_constructor(true) + , m_sent_handshake(false) #endif { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "*** bt_peer_connection\n"; #endif +#ifndef NDEBUG + m_in_constructor = false; +#endif + } + + bt_peer_connection::bt_peer_connection( + session_impl& ses + , boost::shared_ptr s + , policy::peer* peerinfo) + : peer_connection(ses, s, peerinfo) + , m_state(read_protocol_identifier) +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_supports_extensions(false) +#endif + , m_supports_dht_port(false) +#ifndef TORRENT_DISABLE_ENCRYPTION + , m_encrypted(false) + , m_rc4_encrypted(false) + , m_sync_bytes_read(0) + , m_enc_send_buffer(0, 0) +#endif +#ifndef NDEBUG + , m_sent_bitfield(false) + , m_in_constructor(true) + , m_sent_handshake(false) +#endif + { + + // we are not attached to any torrent yet. + // we have to wait for the handshake to see + // which torrent the connector want's to connect to + + + // upload bandwidth will only be given to connections + // that are part of a torrent. Since this is an incoming + // connection, we have to give it some initial bandwidth + // to send the handshake. +#ifndef TORRENT_DISABLE_ENCRYPTION + m_bandwidth_limit[download_channel].assign(2048); + m_bandwidth_limit[upload_channel].assign(2048); +#else + m_bandwidth_limit[download_channel].assign(80); + m_bandwidth_limit[upload_channel].assign(80); +#endif + + // start in the state where we are trying to read the + // handshake from the other side + reset_recv_buffer(20); + setup_receive(); +#ifndef NDEBUG + m_in_constructor = false; +#endif + } + + bt_peer_connection::~bt_peer_connection() + { + } + + void bt_peer_connection::on_connected() + { #ifndef TORRENT_DISABLE_ENCRYPTION pe_settings::enc_policy const& out_enc_policy = m_ses.get_pe_settings().out_enc_policy; @@ -158,64 +219,8 @@ namespace libtorrent reset_recv_buffer(20); setup_receive(); } - -#ifndef NDEBUG - m_in_constructor = false; -#endif } - - bt_peer_connection::bt_peer_connection( - session_impl& ses - , boost::shared_ptr s - , policy::peer* peerinfo) - : peer_connection(ses, s, peerinfo) - , m_state(read_protocol_identifier) -#ifndef TORRENT_DISABLE_EXTENSIONS - , m_supports_extensions(false) -#endif - , m_supports_dht_port(false) -#ifndef TORRENT_DISABLE_ENCRYPTION - , m_encrypted(false) - , m_rc4_encrypted(false) - , m_sync_bytes_read(0) - , m_enc_send_buffer(0, 0) -#endif -#ifndef NDEBUG - , m_sent_bitfield(false) - , m_in_constructor(true) -#endif - { - - // we are not attached to any torrent yet. - // we have to wait for the handshake to see - // which torrent the connector want's to connect to - - - // upload bandwidth will only be given to connections - // that are part of a torrent. Since this is an incoming - // connection, we have to give it some initial bandwidth - // to send the handshake. -#ifndef TORRENT_DISABLE_ENCRYPTION - m_bandwidth_limit[download_channel].assign(2048); - m_bandwidth_limit[upload_channel].assign(2048); -#else - m_bandwidth_limit[download_channel].assign(80); - m_bandwidth_limit[upload_channel].assign(80); -#endif - - // start in the state where we are trying to read the - // handshake from the other side - reset_recv_buffer(20); - setup_receive(); -#ifndef NDEBUG - m_in_constructor = false; -#endif - } - - bt_peer_connection::~bt_peer_connection() - { - } - + void bt_peer_connection::on_metadata() { boost::shared_ptr t = associated_torrent().lock(); @@ -226,6 +231,9 @@ namespace libtorrent void bt_peer_connection::write_dht_port(int listen_port) { INVARIANT_CHECK; + + assert(m_sent_handshake && m_sent_bitfield); + #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " ==> DHT_PORT [ " << listen_port << " ]\n"; @@ -282,6 +290,7 @@ namespace libtorrent assert(!m_encrypted); assert(!m_rc4_encrypted); assert(!m_DH_key_exchange.get()); + assert(!m_sent_handshake); #ifdef TORRENT_VERBOSE_LOGGING if (is_local()) @@ -314,9 +323,10 @@ namespace libtorrent { INVARIANT_CHECK; - assert (!m_encrypted); - assert (!m_rc4_encrypted); - assert (is_local()); + assert(!m_encrypted); + assert(!m_rc4_encrypted); + assert(is_local()); + assert(!m_sent_handshake); boost::shared_ptr t = associated_torrent().lock(); assert(t); @@ -398,6 +408,7 @@ namespace libtorrent assert(!m_encrypted); assert(!m_rc4_encrypted); assert(crypto_select == 0x02 || crypto_select == 0x01); + assert(!m_sent_handshake); int pad_size = 0; // rand() % 512; // Keep 0 for now @@ -423,8 +434,8 @@ namespace libtorrent #endif } - void bt_peer_connection::write_pe_vc_cryptofield(buffer::interval& write_buf, - int crypto_field, int pad_size) + void bt_peer_connection::write_pe_vc_cryptofield(buffer::interval& write_buf + , int crypto_field, int pad_size) { INVARIANT_CHECK; @@ -433,6 +444,7 @@ namespace libtorrent // vc,crypto_field,len(pad),pad, (len(ia)) assert( (write_buf.left() == 8+4+2+pad_size+2 && is_local()) || (write_buf.left() == 8+4+2+pad_size && !is_local()) ); + assert(!m_sent_handshake); // encrypt(vc, crypto_provide/select, len(Pad), len(IA)) // len(pad) is zero for now, len(IA) only for outgoing connections @@ -587,6 +599,11 @@ namespace libtorrent { INVARIANT_CHECK; + assert(!m_sent_handshake); +#ifndef NDEBUG + m_sent_handshake = true; +#endif + boost::shared_ptr t = associated_torrent().lock(); assert(t); @@ -1093,6 +1110,8 @@ namespace libtorrent { INVARIANT_CHECK; + assert(m_sent_handshake && m_sent_bitfield); + char buf[] = {0,0,0,0}; send_buffer(buf, buf + sizeof(buf)); } @@ -1100,8 +1119,8 @@ namespace libtorrent void bt_peer_connection::write_cancel(peer_request const& r) { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + assert(m_sent_handshake && m_sent_bitfield); assert(associated_torrent().lock()->valid_metadata()); char buf[] = {0,0,0,13, msg_cancel}; @@ -1125,8 +1144,8 @@ namespace libtorrent void bt_peer_connection::write_request(peer_request const& r) { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + assert(m_sent_handshake && m_sent_bitfield); assert(associated_torrent().lock()->valid_metadata()); char buf[] = {0,0,0,13, msg_request}; @@ -1153,7 +1172,7 @@ namespace libtorrent boost::shared_ptr t = associated_torrent().lock(); assert(t); - assert(m_sent_bitfield == false); + assert(m_sent_handshake && !m_sent_bitfield); assert(t->valid_metadata()); int num_pieces = bitfield.size(); @@ -1243,6 +1262,7 @@ namespace libtorrent (*m_logger) << time_now_string() << " ==> EXTENSIONS\n"; #endif assert(m_supports_extensions); + assert(m_sent_handshake); entry handshake(entry::dictionary_t); entry extension_list(entry::dictionary_t); @@ -1257,7 +1277,7 @@ namespace libtorrent std::string remote_address; std::back_insert_iterator out(remote_address); detail::write_address(remote().address(), out); - handshake["ip"] = remote_address; + handshake["yourip"] = remote_address; handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; // loop backwards, to make the first extension be the last @@ -1297,7 +1317,8 @@ namespace libtorrent void bt_peer_connection::write_choke() { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + + assert(m_sent_handshake && m_sent_bitfield); if (is_choked()) return; char msg[] = {0,0,0,1,msg_choke}; @@ -1307,7 +1328,8 @@ namespace libtorrent void bt_peer_connection::write_unchoke() { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + + assert(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_unchoke}; send_buffer(msg, msg + sizeof(msg)); @@ -1316,7 +1338,8 @@ namespace libtorrent void bt_peer_connection::write_interested() { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + + assert(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_interested}; send_buffer(msg, msg + sizeof(msg)); @@ -1325,7 +1348,8 @@ namespace libtorrent void bt_peer_connection::write_not_interested() { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + + assert(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_not_interested}; send_buffer(msg, msg + sizeof(msg)); @@ -1337,7 +1361,7 @@ namespace libtorrent assert(associated_torrent().lock()->valid_metadata()); assert(index >= 0); assert(index < associated_torrent().lock()->torrent_file().num_pieces()); - assert(m_sent_bitfield == true); + assert(m_sent_handshake && m_sent_bitfield); const int packet_size = 9; char msg[packet_size] = {0,0,0,5,msg_have}; @@ -1349,7 +1373,8 @@ namespace libtorrent void bt_peer_connection::write_piece(peer_request const& r, char const* buffer) { INVARIANT_CHECK; - assert(m_sent_bitfield == true); + + assert(m_sent_handshake && m_sent_bitfield); const int packet_size = 4 + 5 + 4 + r.length; diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index f83baa196..473a92920 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -42,6 +42,9 @@ namespace libtorrent , m_num_connecting(0) , m_half_open_limit(0) , m_timer(ios) +#ifndef NDEBUG + , m_in_timeout_function(false) +#endif {} bool connection_queue::free_slots() const @@ -133,9 +136,22 @@ namespace libtorrent } } +#ifndef NDEBUG + struct function_guard + { + function_guard(bool& v): val(v) { assert(!val); val = true; } + ~function_guard() { val = false; } + + bool& val; + }; +#endif + void connection_queue::on_timeout(asio::error_code const& e) { INVARIANT_CHECK; +#ifndef NDEBUG + function_guard guard_(m_in_timeout_function); +#endif assert(!e || e == asio::error::operation_aborted); if (e) return; diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 5398d5ae8..b02ab0012 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -216,6 +216,7 @@ namespace libtorrent break; case disk_io_job::move_storage: ret = j.storage->move_storage_impl(j.str) ? 1 : 0; + j.str = j.storage->save_path().string(); break; case disk_io_job::release_files: j.storage->release_files_impl(); diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index a763e5458..437c93e2c 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -50,6 +50,7 @@ namespace libtorrent { m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); + m_DH->length = 160l; assert (sizeof(m_dh_prime) == DH_size(m_DH)); @@ -81,7 +82,7 @@ namespace libtorrent { DH_free (m_DH); } - char const* DH_key_exchange::get_local_key () const + char const* DH_key_exchange::get_local_key () const { return m_dh_local_key; } @@ -92,9 +93,17 @@ namespace libtorrent { { assert (remote_pubkey); BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); + char dh_secret[96]; - int ret = - DH_compute_key ( (unsigned char*)m_dh_secret, bn_remote_pubkey, m_DH); // TODO Check for errors + int secret_size = DH_compute_key ( (unsigned char*)dh_secret, + bn_remote_pubkey, m_DH); // TODO Check for errors + + if (secret_size != 96) + { + assert(secret_size < 96 && secret_size > 0); + std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); + } + std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); BN_free (bn_remote_pubkey); } diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 8a2ac0ae3..7dbef4381 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -397,15 +397,6 @@ namespace libtorrent try { (*i)->on_piece_pass(index); } catch (std::exception&) {} } #endif - - if (peer_info_struct()) - { - peer_info_struct()->on_parole = false; - int& trust_points = peer_info_struct()->trust_points; - trust_points++; - // TODO: make this limit user settable - if (trust_points > 20) trust_points = 20; - } } void peer_connection::received_invalid_data(int index) @@ -1090,10 +1081,6 @@ namespace libtorrent , m_download_queue.end() , block_finished); - // if there's another peer that needs to do another - // piece request, this will point to it - peer_connection* request_peer = 0; - if (b != m_download_queue.end()) { if (m_assume_fifo) @@ -1122,48 +1109,32 @@ namespace libtorrent { m_download_queue.erase(b); } + + t->cancel_block(block_finished); } else { -/* // cancel the block from the - // peer that has taken over it. - boost::optional peer - = t->picker().get_downloader(block_finished); - if (peer && t->picker().is_requested(block_finished)) + if (t->alerts().should_post(alert::debug)) { - peer_connection* pc = t->connection_for(*peer); - if (pc && pc != this) - { - pc->cancel_request(block_finished); - request_peer = pc; - } + t->alerts().post_alert( + peer_error_alert( + m_remote + , m_peer_id + , "got a block that was not in the request queue")); } - else -*/ { - if (t->alerts().should_post(alert::debug)) - { - t->alerts().post_alert( - peer_error_alert( - m_remote - , m_peer_id - , "got a block that was not requested")); - } #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " *** The block we just got was not in the " - "request queue ***\n"; + (*m_logger) << " *** The block we just got was not in the " + "request queue ***\n"; #endif - t->received_redundant_data(p.length); - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } - return; + t->received_redundant_data(p.length); + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); } + return; } - assert(picker.is_requested(block_finished)); - // if the block we got is already finished, then ignore it if (picker.is_downloaded(block_finished)) { @@ -1174,25 +1145,12 @@ namespace libtorrent request_a_block(*t, *this); send_block_requests(); } - - if (request_peer && !request_peer->has_peer_choked() && !t->is_seed()) - { - request_a_block(*t, *request_peer); - request_peer->send_block_requests(); - } return; } fs.async_write(p, data, bind(&peer_connection::on_disk_write_complete , self(), _1, _2, p, t)); - picker.mark_as_writing(block_finished, m_remote); - - if (request_peer && !request_peer->has_peer_choked() && !t->is_seed()) - { - request_a_block(*t, *request_peer); - request_peer->send_block_requests(); - } - + picker.mark_as_writing(block_finished, peer_info_struct()); } void peer_connection::on_disk_write_complete(int ret, disk_io_job const& j @@ -1224,7 +1182,7 @@ namespace libtorrent assert(p.piece == j.piece); assert(p.start == j.offset); piece_block block_finished(p.piece, p.start / t->block_size()); - picker.mark_as_finished(block_finished, m_remote); + picker.mark_as_finished(block_finished, peer_info_struct()); if (!has_peer_choked() && !t->is_seed() && !m_torrent.expired()) { @@ -1328,7 +1286,7 @@ namespace libtorrent assert(block.piece_index < t->torrent_file().num_pieces()); assert(block.block_index >= 0); assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); - assert(!t->picker().is_requested(block)); + assert(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); @@ -1336,7 +1294,7 @@ namespace libtorrent else if (speed == medium) state = piece_picker::medium; else state = piece_picker::slow; - t->picker().mark_as_downloading(block, m_remote, state); + t->picker().mark_as_downloading(block, peer_info_struct(), state); m_request_queue.push_back(block); } @@ -1354,17 +1312,22 @@ namespace libtorrent assert(block.piece_index < t->torrent_file().num_pieces()); assert(block.block_index >= 0); assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); - assert(t->picker().is_requested(block)); - t->picker().abort_download(block); + // if all the peers that requested this block has been + // cancelled, then just ignore the cancel. + if (!t->picker().is_requested(block)) return; std::deque::iterator it = std::find(m_download_queue.begin(), m_download_queue.end(), block); if (it == m_download_queue.end()) { it = std::find(m_request_queue.begin(), m_request_queue.end(), block); - assert(it != m_request_queue.end()); + // when a multi block is received, it is cancelled + // from all peers, so if this one hasn't requested + // the block, just ignore to cancel it. if (it == m_request_queue.end()) return; + + t->picker().abort_download(block); m_request_queue.erase(it); // since we found it in the request queue, it means it hasn't been // sent yet, so we don't have to send a cancel. @@ -1373,6 +1336,7 @@ namespace libtorrent else { m_download_queue.erase(it); + t->picker().abort_download(block); } int block_offset = block.block_index * t->block_size(); @@ -1858,8 +1822,6 @@ namespace libtorrent } } - m_statistics.second_tick(tick_interval); - // If the client sends more data // we send it data faster, otherwise, slower. // It will also depend on how much data the diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index bd568210f..61f3544b7 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -48,8 +48,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/torrent.hpp" #endif -#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK -//#define TORRENT_PIECE_PICKER_INVARIANT_CHECK +//#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK +#define TORRENT_PIECE_PICKER_INVARIANT_CHECK namespace libtorrent { @@ -116,11 +116,10 @@ namespace libtorrent for (std::vector::const_iterator i = unfinished.begin(); i != unfinished.end(); ++i) { - tcp::endpoint peer; for (int j = 0; j < m_blocks_per_piece; ++j) { if (i->info[j].state == block_info::state_finished) - mark_as_finished(piece_block(i->index, j), peer); + mark_as_finished(piece_block(i->index, j), 0); } if (is_piece_finished(i->index)) { @@ -197,12 +196,13 @@ namespace libtorrent int block_index = num_downloads * m_blocks_per_piece; if (int(m_block_info.size()) < block_index + m_blocks_per_piece) { + block_info* base = &m_block_info[0]; m_block_info.resize(block_index + m_blocks_per_piece); - if (!m_downloads.empty() && &m_block_info[0] != m_downloads.front().info) + if (!m_downloads.empty() && &m_block_info[0] != base) { // this means the memory was reallocated, update the pointers for (int i = 0; i < int(m_downloads.size()); ++i) - m_downloads[i].info = &m_block_info[i * m_blocks_per_piece]; + m_downloads[i].info = &m_block_info[m_downloads[i].info - base]; } } m_downloads.push_back(downloading_piece()); @@ -210,30 +210,27 @@ namespace libtorrent ret.info = &m_block_info[block_index]; for (int i = 0; i < m_blocks_per_piece; ++i) { - ret.info[i].num_downloads = 0; + ret.info[i].num_peers = 0; ret.info[i].state = block_info::state_none; - ret.info[i].peer = tcp::endpoint(); + ret.info[i].peer = 0; } return ret; } void piece_picker::erase_download_piece(std::vector::iterator i) { - if (i != m_downloads.end() - 1) + std::vector::iterator other = std::find_if( + m_downloads.begin(), m_downloads.end() + , bind(&downloading_piece::info, _1) + == &m_block_info[(m_downloads.size() - 1) * m_blocks_per_piece]); + assert(other != m_downloads.end()); + + if (i != other) { - int remove_index = i - m_downloads.begin(); - int last_index = m_downloads.size() - 1; - assert(remove_index < last_index); - - assert(int(m_block_info.size()) >= last_index * m_blocks_per_piece + m_blocks_per_piece); - std::copy(m_block_info.begin() + (last_index * m_blocks_per_piece) - , m_block_info.begin() + (last_index * m_blocks_per_piece + m_blocks_per_piece) - , m_block_info.begin() + (remove_index * m_blocks_per_piece)); - m_downloads[remove_index] = m_downloads[last_index]; - m_downloads[remove_index].info = &m_block_info[remove_index * m_blocks_per_piece]; + std::copy(other->info, other->info + m_blocks_per_piece, i->info); + other->info = i->info; } - - m_downloads.pop_back(); + m_downloads.erase(i); } #ifndef NDEBUG @@ -243,6 +240,17 @@ namespace libtorrent assert(m_piece_info.empty() || m_piece_info[0].empty()); + if (!m_downloads.empty()) + { + for (std::vector::const_iterator i = m_downloads.begin(); + i != m_downloads.end() - 1; ++i) + { + downloading_piece const& dp = *i; + downloading_piece const& next = *(i + 1); + assert(dp.finished + dp.writing >= next.finished + next.writing); + } + } + if (t != 0) assert((int)m_piece_map.size() == t->torrent_file().num_pieces()); @@ -594,6 +602,20 @@ namespace libtorrent } } + void piece_picker::sort_piece(std::vector::iterator dp) + { + assert(m_piece_map[dp->index].downloading); + int complete = dp->writing + dp->finished; + for (std::vector::iterator i = dp, j(dp-1); + i != m_downloads.begin(); --i, --j) + { + assert(j >= m_downloads.begin()); + if (j->finished + j->writing >= complete) return; + using std::swap; + swap(*j, *i); + } + } + void piece_picker::restore_piece(int index) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1001,10 +1023,10 @@ namespace libtorrent // prefer_whole_pieces can be set if this peer should download // whole pieces rather than trying to download blocks from the // same piece as other peers. - // the endpoint is the address of the peer we're picking pieces - // from. This is used when downloading whole pieces, to only - // pick from the same piece the same peer is downloading from. - // state is supposed to be set to fast if the peer is downloading + // the void* is the pointer to the policy::peer of the peer we're + // picking pieces from. This is used when downloading whole pieces, + // to only pick from the same piece the same peer is downloading + // from. state is supposed to be set to fast if the peer is downloading // relatively fast, by some notion. Slow peers will prefer not // to pick blocks from the same pieces as fast peers, and vice // versa. Downloading pieces are marked as being fast, medium @@ -1012,7 +1034,7 @@ namespace libtorrent void piece_picker::pick_pieces(const std::vector& pieces , std::vector& interesting_blocks , int num_blocks, bool prefer_whole_pieces - , tcp::endpoint peer, piece_state_t speed) const + , void* peer, piece_state_t speed, bool rarest_first) const { TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(num_blocks > 0); @@ -1028,6 +1050,22 @@ namespace libtorrent // blocks belonging to a piece that others have // downloaded to std::vector backup_blocks; + + bool ignore_downloading_pieces = false; + if (!prefer_whole_pieces) + { + std::vector downloading_pieces; + downloading_pieces.reserve(m_downloads.size()); + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + downloading_pieces.push_back(i->index); + } + num_blocks = add_interesting_blocks(downloading_pieces, pieces + , interesting_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); + ignore_downloading_pieces = true; + } // this loop will loop from pieces with priority 1 and up // until we either reach the end of the piece list or @@ -1050,9 +1088,40 @@ namespace libtorrent if (bucket->empty()) continue; num_blocks = add_interesting_blocks(*bucket, pieces , interesting_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer, speed); + , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); assert(num_blocks >= 0); if (num_blocks == 0) return; + if (rarest_first) continue; + + // we're not using rarest first (only for the first + // bucket, since that's where the currently downloading + // pieces are) + while (num_blocks > 0) + { + int start_piece = rand() % m_piece_map.size(); + int piece = start_piece; + while (!pieces[piece] + || m_piece_map[piece].index == piece_pos::we_have_index + || m_piece_map[piece].priority(m_sequenced_download_threshold) < 2) + { + ++piece; + if (piece == int(m_piece_map.size())) piece = 0; + // could not find any more pieces + if (piece == start_piece) return; + } + + assert(m_piece_map[piece].downloading == false); + + int num_blocks_in_piece = blocks_in_piece(piece); + + if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) + interesting_blocks.push_back(piece_block(piece, j)); + num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); + } + if (num_blocks == 0) return; + break; } assert(num_blocks > 0); @@ -1063,22 +1132,40 @@ namespace libtorrent + (std::min)(num_blocks, (int)backup_blocks.size())); } + void piece_picker::clear_peer(void* peer) + { + for (std::vector::iterator i = m_block_info.begin() + , end(m_block_info.end()); i != end; ++i) + if (i->peer == peer) i->peer = 0; + } + namespace { - bool exclusively_requested_from(piece_picker::downloading_piece const& p - , int num_blocks_in_piece, tcp::endpoint peer) + // the first bool is true if this is the only peer that has requested and downloaded + // blocks from this piece. + // the second bool is true if this is the only active peer that is requesting + // and downloading blocks from this piece. Active means having a connection. + boost::tuple requested_from(piece_picker::downloading_piece const& p + , int num_blocks_in_piece, void* peer) { + bool exclusive = true; + bool exclusive_active = true; for (int j = 0; j < num_blocks_in_piece; ++j) { piece_picker::block_info const& info = p.info[j]; if (info.state != piece_picker::block_info::state_none - && info.peer != peer - && info.peer != tcp::endpoint()) + && info.peer != peer) { - return false; + exclusive = false; + if (info.state == piece_picker::block_info::state_requested + && info.peer != 0) + { + exclusive_active = false; + return boost::make_tuple(exclusive, exclusive_active); + } } } - return true; + return boost::make_tuple(exclusive, exclusive_active); } } @@ -1087,7 +1174,8 @@ namespace libtorrent , std::vector& interesting_blocks , std::vector& backup_blocks , int num_blocks, bool prefer_whole_pieces - , tcp::endpoint peer, piece_state_t speed) const + , void* peer, piece_state_t speed + , bool ignore_downloading_pieces) const { // if we have less than 1% of the pieces, ignore speed priorities and just try // to finish any downloading piece @@ -1106,6 +1194,7 @@ namespace libtorrent if (m_piece_map[*i].downloading == 1) { + if (ignore_downloading_pieces) continue; std::vector::const_iterator p = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); assert(p != m_downloads.end()); @@ -1113,8 +1202,10 @@ namespace libtorrent // is true if all the other pieces that are currently // requested from this piece are from the same // peer as 'peer'. - bool only_same_peer = exclusively_requested_from(*p - , num_blocks_in_piece, peer); + bool exclusive; + bool exclusive_active; + boost::tie(exclusive, exclusive_active) + = requested_from(*p, num_blocks_in_piece, peer); // this means that this partial piece has // been downloaded/requested partially from @@ -1123,7 +1214,7 @@ namespace libtorrent // blocks to the backup list. If the prioritized // blocks aren't enough, blocks from this list // will be picked. - if (prefer_whole_pieces && !only_same_peer) + if (prefer_whole_pieces && !exclusive) { if (int(backup_blocks.size()) >= num_blocks) continue; for (int j = 0; j < num_blocks_in_piece; ++j) @@ -1157,7 +1248,7 @@ namespace libtorrent // if the state of the piece is none (the // piece will in that case change state). if (p->state != none && p->state != speed - && !only_same_peer + && !exclusive_active && !ignore_speed_categories) { if (int(backup_blocks.size()) >= num_blocks) continue; @@ -1193,9 +1284,7 @@ namespace libtorrent if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; for (int j = 0; j < num_blocks_in_piece; ++j) - { interesting_blocks.push_back(piece_block(*i, j)); - } num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); } assert(num_blocks >= 0); @@ -1281,7 +1370,7 @@ namespace libtorrent void piece_picker::mark_as_downloading(piece_block block - , const tcp::endpoint& peer, piece_state_t state) + , void* peer, piece_state_t state) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1303,6 +1392,7 @@ namespace libtorrent block_info& info = dp.info[block.block_index]; info.state = block_info::state_requested; info.peer = peer; + info.num_peers = 1; ++dp.requested; } else @@ -1311,14 +1401,38 @@ namespace libtorrent = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); assert(i != m_downloads.end()); block_info& info = i->info[block.block_index]; - assert(info.state == block_info::state_none); + assert(info.state == block_info::state_none + || (info.state == block_info::state_requested + && (info.num_peers > 0))); info.peer = peer; - info.state = block_info::state_requested; - ++i->requested; + if (info.state != block_info::state_requested) + { + info.state = block_info::state_requested; + ++i->requested; + } + ++info.num_peers; if (i->state == none) i->state = state; } } + int piece_picker::num_peers(piece_block block) const + { + assert(block.piece_index >= 0); + assert(block.block_index >= 0); + assert(block.piece_index < (int)m_piece_map.size()); + assert(block.block_index < blocks_in_piece(block.piece_index)); + + piece_pos const& p = m_piece_map[block.piece_index]; + if (!p.downloading) return 0; + + std::vector::const_iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + + block_info const& info = i->info[block.block_index]; + return info.num_peers; + } + void piece_picker::get_availability(std::vector& avail) const { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1330,7 +1444,7 @@ namespace libtorrent *j = i->peer_count; } - void piece_picker::mark_as_writing(piece_block block, tcp::endpoint const& peer) + void piece_picker::mark_as_writing(piece_block block, void* peer) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1339,52 +1453,32 @@ namespace libtorrent assert(block.piece_index < (int)m_piece_map.size()); assert(block.block_index < blocks_in_piece(block.piece_index)); - piece_pos& p = m_piece_map[block.piece_index]; - assert(p.downloading); + assert(m_piece_map[block.piece_index].downloading); -/* if (p.downloading == 0) + std::vector::iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + assert(i != m_downloads.end()); + block_info& info = i->info[block.block_index]; + info.peer = peer; + assert(info.state == block_info::state_requested); + if (info.state == block_info::state_requested) --i->requested; + assert(i->requested >= 0); + assert(info.state != block_info::state_writing); + ++i->writing; + info.state = block_info::state_writing; + if (info.num_peers > 0) --info.num_peers; + assert(info.num_peers >= 0); + + if (i->requested == 0) { - int prio = p.priority(m_sequenced_download_threshold); - p.downloading = 1; - if (prio > 0) move(prio, p.index); - else assert(p.priority(m_sequenced_download_threshold) == 0); - - downloading_piece& dp = add_download_piece(); - dp.state = none; - dp.index = block.piece_index; - block_info& info = dp.info[block.block_index]; - info.peer = peer; - if (info.state == block_info::state_requested) --dp.requested; - assert(dp.requested >= 0); - assert (info.state != block_info::state_finished); - assert (info.state != block_info::state_writing); - if (info.state != block_info::state_requested) ++dp.writing; - info.state = block_info::state_writing; - } - else -*/ { - std::vector::iterator i - = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); - block_info& info = i->info[block.block_index]; - info.peer == peer; - assert(info.state == block_info::state_requested); - if (info.state == block_info::state_requested) --i->requested; - assert(i->requested >= 0); - assert (info.state != block_info::state_writing); - ++i->writing; - info.state = block_info::state_writing; - - if (i->requested == 0) - { - // there are no blocks requested in this piece. - // remove the fast/slow state from it - i->state = none; - } + // there are no blocks requested in this piece. + // remove the fast/slow state from it + i->state = none; } + sort_piece(i); } - void piece_picker::mark_as_finished(piece_block block, tcp::endpoint const& peer) + void piece_picker::mark_as_finished(piece_block block, void* peer) { assert(block.piece_index >= 0); assert(block.block_index >= 0); @@ -1397,7 +1491,7 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(peer == tcp::endpoint()); + assert(peer == 0); int prio = p.priority(m_sequenced_download_threshold); p.downloading = 1; if (prio > 0) move(prio, p.index); @@ -1409,9 +1503,11 @@ namespace libtorrent block_info& info = dp.info[block.block_index]; info.peer = peer; assert(info.state == block_info::state_none); -// if (info.state == block_info::state_writing) --dp.writing; -// assert(dp.writing >= 0); - if (info.state != block_info::state_finished) ++dp.finished; + if (info.state != block_info::state_finished) + { + ++dp.finished; + sort_piece(m_downloads.end() - 1); + } info.state = block_info::state_finished; } else @@ -1424,22 +1520,23 @@ namespace libtorrent block_info& info = i->info[block.block_index]; info.peer = peer; assert(info.state == block_info::state_writing - || peer == tcp::endpoint()); - if (info.state == block_info::state_writing) --i->writing; + || peer == 0); assert(i->writing >= 0); ++i->finished; - info.state = block_info::state_finished; - - if (i->requested == 0) + if (info.state == block_info::state_writing) { - // there are no blocks requested in this piece. - // remove the fast/slow state from it - i->state = none; + --i->writing; + info.state = block_info::state_finished; + } + else + { + info.state = block_info::state_finished; + sort_piece(i); } } } - void piece_picker::get_downloaders(std::vector& d, int index) const + void piece_picker::get_downloaders(std::vector& d, int index) const { assert(index >= 0 && index <= (int)m_piece_map.size()); std::vector::const_iterator i @@ -1453,22 +1550,21 @@ namespace libtorrent } } - boost::optional piece_picker::get_downloader(piece_block block) const + void* piece_picker::get_downloader(piece_block block) const { std::vector::const_iterator i = std::find_if( m_downloads.begin() , m_downloads.end() , has_index(block.piece_index)); - if (i == m_downloads.end()) - return boost::optional(); + if (i == m_downloads.end()) return 0; assert(block.block_index >= 0); if (i->info[block.block_index].state == block_info::state_none) - return boost::optional(); + return 0; - return boost::optional(i->info[block.block_index].peer); + return i->info[block.block_index].peer; } void piece_picker::abort_download(piece_block block) @@ -1491,6 +1587,11 @@ namespace libtorrent , m_downloads.end(), has_index(block.piece_index)); assert(i != m_downloads.end()); + block_info& info = i->info[block.block_index]; + --info.num_peers; + assert(info.num_peers >= 0); + if (info.num_peers > 0) return; + if (i->info[block.block_index].state == block_info::state_finished || i->info[block.block_index].state == block_info::state_writing) { @@ -1501,11 +1602,11 @@ namespace libtorrent assert(i->info[block.block_index].state == block_info::state_requested); // clear this block as being downloaded - i->info[block.block_index].state = block_info::state_none; + info.state = block_info::state_none; --i->requested; // clear the downloader of this block - i->info[block.block_index].peer = tcp::endpoint(); + info.peer = 0; // if there are no other blocks in this piece // that's being downloaded, remove it from the list diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index eca5ba613..2444deeb7 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -54,6 +54,11 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/invariant_check.hpp" #include "libtorrent/time.hpp" #include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/piece_picker.hpp" + +#ifndef NDEBUG +#include "libtorrent/bt_peer_connection.hpp" +#endif namespace libtorrent { @@ -135,14 +140,14 @@ namespace struct match_peer_ip { - match_peer_ip(tcp::endpoint const& ip) + match_peer_ip(address const& ip) : m_ip(ip) {} bool operator()(policy::peer const& p) const - { return p.ip.address() == m_ip.address(); } + { return p.ip.address() == m_ip; } - tcp::endpoint const& m_ip; + address const& m_ip; }; struct match_peer_id @@ -184,11 +189,11 @@ namespace libtorrent // infinite loop, fighting to request the same blocks. void request_a_block( torrent& t - , peer_connection& c - , std::vector ignore) + , peer_connection& c) { assert(!t.is_seed()); assert(!c.has_peer_choked()); + assert(c.peer_info_struct() != 0 || !dynamic_cast(&c)); int num_requests = c.desired_queue_size() - (int)c.download_queue().size() - (int)c.request_queue().size(); @@ -205,6 +210,8 @@ namespace libtorrent bool prefer_whole_pieces = c.prefer_whole_pieces() || (c.peer_info_struct() && c.peer_info_struct()->on_parole); + bool rarest_first = t.num_pieces() >= t.settings().initial_picker_threshold; + if (!prefer_whole_pieces) { prefer_whole_pieces = c.statistics().download_payload_rate() @@ -233,7 +240,8 @@ namespace libtorrent // for this peer. If we're downloading one piece in 20 seconds // then use this mode. p.pick_pieces(c.get_bitfield(), interesting_pieces - , num_requests, prefer_whole_pieces, c.remote(), state); + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, rarest_first); // this vector is filled with the interesting pieces // that some other peer is currently downloading @@ -248,10 +256,18 @@ namespace libtorrent { if (p.is_requested(*i)) { + // don't request pieces we already have in our request queue + const std::deque& dq = c.download_queue(); + const std::deque& rq = c.request_queue(); + if (std::find(dq.begin(), dq.end(), *i) != dq.end() + || std::find(rq.begin(), rq.end(), *i) != rq.end()) + continue; + busy_pieces.push_back(*i); continue; } + assert(p.num_peers(*i) == 0); // ok, we found a piece that's not being downloaded // by somebody else. request it from this peer // and return @@ -259,139 +275,26 @@ namespace libtorrent num_requests--; } - c.send_block_requests(); - // in this case, we could not find any blocks // that was free. If we couldn't find any busy // blocks as well, we cannot download anything // more from this peer. - if (busy_pieces.empty()) return; - - // first look for blocks that are just queued - // and not actually sent to us yet - // (then we can cancel those and request them - // from this peer instead) - - while (num_requests > 0) + if (busy_pieces.empty() || num_requests == 0) { - peer_connection* peer = 0; - - const int initial_queue_size = (int)c.download_queue().size() - + (int)c.request_queue().size(); - - // This peer's weight will be the minimum, to prevent - // cancelling requests from a faster peer. - float min_weight = initial_queue_size == 0 - ? std::numeric_limits::max() - : c.statistics().download_payload_rate() / initial_queue_size; - - // find the peer with the lowest download - // speed that also has a piece that this - // peer could send us - for (torrent::peer_iterator i = t.begin(); - i != t.end(); ++i) - { - // don't try to take over blocks from ourself - if (i->second == &c) - continue; - - // ignore all peers in the ignore list - if (std::find(ignore.begin(), ignore.end(), i->second) != ignore.end()) - continue; - - const std::deque& download_queue = i->second->download_queue(); - const std::deque& request_queue = i->second->request_queue(); - const int queue_size = (int)i->second->download_queue().size() - + (int)i->second->request_queue().size(); - - bool in_request_queue = std::find_first_of( - busy_pieces.begin() - , busy_pieces.end() - , request_queue.begin() - , request_queue.end()) != busy_pieces.end(); - - bool in_download_queue = std::find_first_of( - busy_pieces.begin() - , busy_pieces.end() - , download_queue.begin() - , download_queue.end()) != busy_pieces.end(); - - // if the block is in the request queue rather than the download queue - // (i.e. the request message hasn't been sent yet) lower the weight in - // order to prioritize it. Taking over a block in the request queue is - // free in terms of redundant download. A block that already has been - // requested is likely to be in transit already, and would in that case - // mean redundant data to receive. - const float weight = (queue_size == 0) - ? std::numeric_limits::max() - : i->second->statistics().download_payload_rate() / queue_size - * in_request_queue ? .1f : 1.f; - - // if the peer's (i) weight is less than the lowest we've found so - // far (weight == priority) and it has blocks in its request- - // or download queue that we could request from this peer (c), - // replace the currently lowest ranking peer. - if (weight < min_weight && (in_request_queue || in_download_queue)) - { - peer = i->second; - min_weight = weight; - } - } - - if (peer == 0) - { - // we probably couldn't request the block because - // we are ignoring some peers - break; - } - - // find a suitable block to take over from this peer - - std::deque::const_reverse_iterator common_block = - std::find_first_of( - peer->request_queue().rbegin() - , peer->request_queue().rend() - , busy_pieces.begin() - , busy_pieces.end()); - - if (common_block == peer->request_queue().rend()) - { - common_block = std::find_first_of( - peer->download_queue().rbegin() - , peer->download_queue().rend() - , busy_pieces.begin() - , busy_pieces.end()); - assert(common_block != peer->download_queue().rend()); - } - - piece_block block = *common_block; - - // the one we interrupted may need to request a new piece. - // make sure it doesn't take over a block from the peer - // that just took over its block (that would cause an - // infinite recursion) - peer->cancel_request(block); - c.add_request(block); - ignore.push_back(&c); - if (!peer->has_peer_choked() && !t.is_seed()) - { - request_a_block(t, *peer, ignore); - peer->send_block_requests(); - } - - num_requests--; - - const int queue_size = (int)c.download_queue().size() - + (int)c.request_queue().size(); - const float weight = queue_size == 0 - ? std::numeric_limits::max() - : c.statistics().download_payload_rate() / queue_size; - - // this peer doesn't have a faster connection than the - // slowest peer. Don't take over any blocks - if (weight <= min_weight) break; + c.send_block_requests(); + return; } + + std::random_shuffle(busy_pieces.begin(), busy_pieces.end()); + + // find the block with the fewest requests to it + std::vector::iterator i = std::min_element( + busy_pieces.begin(), busy_pieces.end() + , bind(&piece_picker::num_peers, boost::cref(p), _1) < + bind(&piece_picker::num_peers, boost::cref(p), _2)); + + c.add_request(*i); c.send_block_requests(); } @@ -680,6 +583,10 @@ namespace libtorrent if (m_torrent->is_paused()) return; + piece_picker* p = 0; + if (m_torrent->has_picker()) + p = &m_torrent->picker(); + ptime now = time_now(); // remove old disconnected peers from the list for (iterator i = m_peers.begin(); i != m_peers.end();) @@ -689,6 +596,7 @@ namespace libtorrent && i->connected != min_time() && now - i->connected > minutes(120)) { + if (p) p->clear_peer(&(*i)); m_peers.erase(i++); } else @@ -949,7 +857,7 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_ip(c.remote())); + , match_peer_ip(c.remote().address())); } if (i != m_peers.end()) @@ -974,19 +882,27 @@ namespace libtorrent "connection in favour of this one"); #endif i->connection->disconnect(); +#ifndef NDEBUG + check_invariant(); +#endif } } } else { - // we don't have ny info about this peer. + // we don't have any info about this peer. // add a new entry assert(c.remote() == c.get_socket()->remote_endpoint()); peer p(c.remote(), peer::not_connectable, 0); m_peers.push_back(p); i = boost::prior(m_peers.end()); +#ifndef NDEBUG + check_invariant(); +#endif } + + assert(m_torrent->connection_for(c.remote()) == &c); c.set_peer_info(&*i); assert(i->connection == 0); @@ -1037,7 +953,7 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_ip(remote)); + , match_peer_ip(remote.address())); } if (i == m_peers.end()) @@ -1324,7 +1240,7 @@ namespace libtorrent if (c.failed()) { ++i->failcount; - i->connected = time_now(); +// i->connected = time_now(); } // if the share ratio is 0 (infinite), the @@ -1393,17 +1309,26 @@ namespace libtorrent for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { + peer const& p = *i; if (!m_torrent->settings().allow_multiple_connections_per_ip) - assert(unique_test.find(i->ip.address()) == unique_test.end()); - unique_test.insert(i->ip.address()); + assert(unique_test.find(p.ip.address()) == unique_test.end()); + unique_test.insert(p.ip.address()); ++total_connections; - if (!i->connection) continue; - assert(i->connection->peer_info_struct() == 0 - || i->connection->peer_info_struct() == &*i); + if (!p.connection) continue; + if (!m_torrent->settings().allow_multiple_connections_per_ip) + { + std::vector conns; + m_torrent->connection_for(p.ip.address(), conns); + assert(std::find_if(conns.begin(), conns.end() + , boost::bind(std::equal_to(), _1, p.connection)) + != conns.end()); + } + assert(p.connection->peer_info_struct() == 0 + || p.connection->peer_info_struct() == &p); ++nonempty_connections; - if (!i->connection->is_disconnecting()) + if (!p.connection->is_disconnecting()) ++connected_peers; - if (!i->connection->is_choked()) ++actual_unchoked; + if (!p.connection->is_choked()) ++actual_unchoked; } // assert(actual_unchoked <= m_torrent->m_uploads_quota.given); assert(actual_unchoked == m_num_unchoked); @@ -1419,6 +1344,33 @@ namespace libtorrent ++num_torrent_peers; } + if (m_torrent->has_picker()) + { + piece_picker& p = m_torrent->picker(); + std::vector downloaders = p.get_download_queue(); + + std::set peer_set; + std::vector peers; + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + p.get_downloaders(peers, i->index); + std::copy(peers.begin(), peers.end() + , std::insert_iterator >(peer_set, peer_set.begin())); + } + + for (std::set::iterator i = peer_set.begin() + , end(peer_set.end()); i != end; ++i) + { + policy::peer* p = static_cast(*i); + if (p == 0) continue; + std::list::const_iterator k = m_peers.begin(); + for (; k != m_peers.end(); ++k) + if (&(*k) == p) break; + assert(k != m_peers.end()); + } + } + // this invariant is a bit complicated. // the usual case should be that connected_peers // == num_torrent_peers. But when there's an incoming diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index f07f48d62..f4323586c 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -75,6 +75,22 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/kademlia/dht_tracker.hpp" +#ifndef TORRENT_DISABLE_ENCRYPTION + +#include + +namespace +{ + // openssl requires this to clean up internal + // structures it allocates + struct openssl_cleanup + { + ~openssl_cleanup() { CRYPTO_cleanup_all_ex_data(); } + } openssl_global_destructor; +} + +#endif + using boost::shared_ptr; using boost::weak_ptr; using boost::bind; @@ -1584,6 +1600,8 @@ namespace detail boost::shared_ptr t = find_torrent(ih).lock(); if (!t) return; + // don't add peers from lsd to private torrents + if (t->torrent_file().priv()) return; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 863cc5c07..793f95365 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -1019,7 +1019,11 @@ namespace libtorrent return true; } - return true; + if (!strcmp(fsinfo.f_fstypename, "hfs") + || !strcmp(fsinfo.f_fstypename, "ufs")) + return true; + + return false; #endif #if defined(__linux__) @@ -1037,7 +1041,9 @@ namespace libtorrent case 0xEF53: // EXT2 and EXT3 case 0x00011954: // UFS case 0x52654973: // ReiserFS + case 0x52345362: // Reiser4 case 0x58465342: // XFS + case 0x65735546: // NTFS-3G return true; } } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index d173f8943..39f29172f 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -204,7 +204,7 @@ namespace libtorrent m_initial_done = 0; #endif - m_uploads_quota.min = 2; + m_uploads_quota.min = 0; m_connections_quota.min = 2; // this will be corrected the next time the main session // distributes resources, i.e. on average in 0.5 seconds @@ -277,7 +277,7 @@ namespace libtorrent if (name) m_name.reset(new std::string(name)); - m_uploads_quota.min = 2; + m_uploads_quota.min = 0; m_connections_quota.min = 2; // this will be corrected the next time the main session // distributes resources, i.e. on average in 0.5 seconds @@ -395,13 +395,22 @@ namespace libtorrent { boost::weak_ptr self(shared_from_this()); - // announce on local network every 5 minutes - m_announce_timer.expires_from_now(minutes(5)); - m_announce_timer.async_wait(m_ses.m_strand.wrap( - bind(&torrent::on_announce_disp, self, _1))); + if (!m_torrent_file.priv()) + { + // announce on local network every 5 minutes + m_announce_timer.expires_from_now(minutes(5)); + m_announce_timer.async_wait(m_ses.m_strand.wrap( + bind(&torrent::on_announce_disp, self, _1))); - // announce with the local discovery service - m_ses.announce_lsd(m_torrent_file.info_hash()); + // announce with the local discovery service + m_ses.announce_lsd(m_torrent_file.info_hash()); + } + else + { + m_announce_timer.expires_from_now(minutes(15)); + m_announce_timer.async_wait(m_ses.m_strand.wrap( + bind(&torrent::on_announce_disp, self, _1))); + } #ifndef TORRENT_DISABLE_DHT if (!m_ses.m_dht) return; @@ -914,13 +923,13 @@ namespace libtorrent // increase the total amount of failed bytes m_total_failed_bytes += m_torrent_file.piece_size(index); - std::vector downloaders; + std::vector downloaders; m_picker->get_downloaders(downloaders, index); // decrease the trust point of all peers that sent // parts of this piece. // first, build a set of all peers that participated - std::set peers; + std::set peers; std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); #ifndef TORRENT_DISABLE_EXTENSIONS @@ -931,19 +940,28 @@ namespace libtorrent } #endif - for (std::set::iterator i = peers.begin() + for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) { - peer_iterator p = m_connections.find(*i); - if (p == m_connections.end()) continue; - peer_connection& peer = *p->second; - peer.received_invalid_data(index); + policy::peer* p = static_cast(*i); + if (p == 0) continue; +#ifndef NDEBUG + if (!settings().allow_multiple_connections_per_ip) + { + std::vector conns; + connection_for(p->ip.address(), conns); + assert(p->connection == 0 + || std::find_if(conns.begin(), conns.end() + , boost::bind(std::equal_to(), _1, p->connection)) + != conns.end()); + } +#endif + if (p->connection) p->connection->received_invalid_data(index); // either, we have received too many failed hashes // or this was the only peer that sent us this piece. // TODO: make this a changable setting - if ((peer.peer_info_struct() - && peer.peer_info_struct()->trust_points <= -7) + if (p->trust_points <= -7 || peers.size() == 1) { // we don't trust this peer anymore @@ -951,31 +969,22 @@ namespace libtorrent if (m_ses.m_alerts.should_post(alert::info)) { m_ses.m_alerts.post_alert(peer_ban_alert( - p->first + p->ip , get_handle() , "banning peer because of too many corrupt pieces")); } // mark the peer as banned - policy::peer* peerinfo = p->second->peer_info_struct(); - if (peerinfo) - { - peerinfo->banned = true; - } - else - { - // it might be a web seed - if (web_peer_connection const* wpc - = dynamic_cast(p->second)) - { - remove_url_seed(wpc->url()); - } - } + p->banned = true; + if (p->connection) + { #if defined(TORRENT_VERBOSE_LOGGING) - (*p->second->m_logger) << "*** BANNING PEER 'too many corrupt pieces'\n"; + (*p->connection->m_logger) << "*** BANNING PEER [ " << p->ip + << " ] 'too many corrupt pieces'\n"; #endif - p->second->disconnect(); + p->connection->disconnect(); + } } } @@ -1028,12 +1037,12 @@ namespace libtorrent assert(index >= 0); assert(index < m_torrent_file.num_pieces()); - std::vector downloaders; + std::vector downloaders; m_picker->get_downloaders(downloaders, index); // increase the trust point of all peers that sent // parts of this piece. - std::set peers; + std::set peers; std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); if (!m_have_pieces[index]) @@ -1047,12 +1056,16 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) try { i->second->announce_piece(index); } catch (std::exception&) {} - for (std::set::iterator i = peers.begin() + for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) { - peer_iterator p = m_connections.find(*i); - if (p == m_connections.end()) continue; - p->second->received_valid_data(index); + policy::peer* p = static_cast(*i); + if (p == 0) continue; + p->on_parole = false; + ++p->trust_points; + // TODO: make this limit user settable + if (p->trust_points > 20) p->trust_points = 20; + if (p->connection) p->connection->received_valid_data(index); } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1364,6 +1377,15 @@ namespace libtorrent return req; } + void torrent::cancel_block(piece_block block) + { + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + i->second->cancel_request(block); + } + } + void torrent::remove_peer(peer_connection* p) try { INVARIANT_CHECK; @@ -1900,6 +1922,8 @@ namespace libtorrent if (pp) p->add_extension(pp); } #endif + assert(connection_for(p->remote()) == p); + assert(ci->second == p); m_policy->new_connection(*ci->second); } catch (std::exception& e) @@ -1995,8 +2019,6 @@ namespace libtorrent , int block_size , bool non_prioritized) { - assert(m_bandwidth_limit[channel].max_assignable() >= block_size); - m_ses.m_bandwidth_manager[channel]->request_bandwidth(p , block_size, non_prioritized); m_bandwidth_limit[channel].assign(block_size); @@ -2321,14 +2343,30 @@ namespace libtorrent // size_type download = m_stat.total_payload_download(); // size_type done = boost::get<0>(bytes_done()); // assert(download >= done - m_initial_done); + std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) { peer_connection const& p = *i->second; + for (std::deque::const_iterator i = p.request_queue().begin() + , end(p.request_queue().end()); i != end; ++i) + ++num_requests[*i]; + for (std::deque::const_iterator i = p.download_queue().begin() + , end(p.download_queue().end()); i != end; ++i) + ++num_requests[*i]; torrent* associated_torrent = p.associated_torrent().lock().get(); if (associated_torrent != this) assert(false); } + if (has_picker()) + { + for (std::map::iterator i = num_requests.begin() + , end(num_requests.end()); i != end; ++i) + { + assert(m_picker->num_peers(i->first) == i->second); + } + } + if (valid_metadata()) { assert(m_abort || int(m_have_pieces.size()) == m_torrent_file.num_pieces()); @@ -2479,7 +2517,7 @@ namespace libtorrent #endif m_paused = false; - m_uploads_quota.min = 2; + m_uploads_quota.min = 0; m_connections_quota.min = 2; m_uploads_quota.max = std::numeric_limits::max(); m_connections_quota.max = std::numeric_limits::max(); @@ -2498,6 +2536,7 @@ namespace libtorrent m_connections_quota.used = (int)m_connections.size(); m_uploads_quota.used = m_policy->num_uploads(); + m_uploads_quota.max = (int)m_connections.size(); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -2674,6 +2713,8 @@ namespace libtorrent !boost::bind(&peer_connection::is_connecting , boost::bind(&std::map::value_type::second, _1))); + st.compact_mode = m_compact_mode; + st.num_complete = m_complete; st.num_incomplete = m_incomplete; st.paused = m_paused; @@ -2712,6 +2753,10 @@ namespace libtorrent = m_trackers[m_last_working_tracker].url; } + st.num_uploads = m_uploads_quota.used; + st.uploads_limit = m_uploads_quota.given; + st.num_connections = m_connections_quota.used; + st.connections_limit = m_connections_quota.given; // if we don't have any metadata, stop here if (!valid_metadata()) diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index c9ade14e9..10e1172fa 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -765,17 +765,61 @@ namespace libtorrent const std::vector& q = p.get_download_queue(); + int block_size = t->block_size(); + for (std::vector::const_iterator i = q.begin(); i != q.end(); ++i) { partial_piece_info pi; pi.piece_state = (partial_piece_info::state_t)i->state; pi.blocks_in_piece = p.blocks_in_piece(i->index); + int piece_size = t->torrent_file().piece_size(i->index); for (int j = 0; j < pi.blocks_in_piece; ++j) { - pi.blocks[j].peer = i->info[j].peer; - pi.blocks[j].num_downloads = i->info[j].num_downloads; - pi.blocks[j].state = i->info[j].state; + block_info& bi = pi.blocks[j]; + bi.state = i->info[j].state; + bi.block_size = j < pi.blocks_in_piece - 1 ? block_size + : piece_size - (j * block_size); + bool complete = bi.state == block_info::writing + || bi.state == block_info::finished; + if (i->info[j].peer == 0) + { + bi.peer = tcp::endpoint(); + bi.bytes_progress = complete ? bi.block_size : 0; + } + else + { + policy::peer* p = static_cast(i->info[j].peer); + if (p->connection) + { + bi.peer = p->connection->remote(); + if (bi.state == block_info::requested) + { + boost::optional pbp + = p->connection->downloading_piece_progress(); + if (pbp && pbp->piece_index == i->index && pbp->block_index == j) + { + bi.bytes_progress = pbp->bytes_downloaded; + assert(bi.bytes_progress <= bi.block_size); + } + else + { + bi.bytes_progress = 0; + } + } + else + { + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + else + { + bi.peer = p->ip; + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + + pi.blocks[j].num_peers = i->info[j].num_peers; } pi.piece_index = i->index; queue.push_back(pi); From c4e688b450438961c6d4f13fda493f83f94a7dfe Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 23 Jul 2007 20:43:42 +0000 Subject: [PATCH 0019/1009] Added TorrentManager class for handling torrents. Added get_torrent_state() in the core, but this will be changed. --- deluge/core/core.py | 56 +++++++++++++++---------------- deluge/core/torrent.py | 51 +++++++++++++++++++++++++--- deluge/core/torrentmanager.py | 61 ++++++++++++++++++++++++++++++++++ deluge/ui/gtkui/gtkui.py | 2 +- deluge/ui/gtkui/signals.py | 15 ++++++--- deluge/ui/gtkui/torrentview.py | 29 +++++++++++++--- 6 files changed, 170 insertions(+), 44 deletions(-) create mode 100644 deluge/core/torrentmanager.py diff --git a/deluge/core/core.py b/deluge/core/core.py index 86033b3db..b8e51a3fe 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -52,8 +52,7 @@ import deluge.libtorrent as lt from deluge.config import Config import deluge.common -from deluge.core.torrent import Torrent -from deluge.core.torrentqueue import TorrentQueue +from deluge.core.torrentmanager import TorrentManager # Get the logger log = logging.getLogger("deluge") @@ -69,11 +68,9 @@ class Core(dbus.service.Object): def __init__(self, path="/org/deluge_torrent/Core"): log.debug("Core init..") - # A dictionary containing hash keys to Torrent objects - self.torrents = {} - # Instantiate the TorrentQueue - self.queue = TorrentQueue() - + # Start the TorrentManager + self.torrents = TorrentManager() + # Setup DBUS bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", bus=dbus.SessionBus()) @@ -95,6 +92,12 @@ class Core(dbus.service.Object): self.loop.run() # Exported Methods + @dbus.service.method("org.deluge_torrent.Deluge") + def shutdown(self): + """Shutdown the core""" + log.info("Shutting down core..") + self.loop.quit() + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="say", out_signature="") def add_torrent_file(self, filename, filedump): @@ -108,6 +111,7 @@ class Core(dbus.service.Object): # Bdecode the filedata sent from the UI torrent_filedump = lt.bdecode(filedump) + handle = None try: handle = self.session.add_torrent(lt.torrent_info(torrent_filedump), self.config["download_location"], @@ -132,24 +136,24 @@ class Core(dbus.service.Object): except IOError: log.warning("Unable to save torrent file: %s", filename) - # Create a Torrent object - torrent = Torrent(handle) - - # Store the Torrent object in the dictionary - self.torrents[str(handle.info_hash())] = torrent + # Add the torrent to the torrentmanager + torrent_id = self.torrents.add(handle) - # Add the torrent id to the queue - self.queue.append(str(handle.info_hash())) - # Emit the torrent_added signal - self.torrent_added(str(handle.info_hash())) + self.torrent_added(torrent_id) - - @dbus.service.method("org.deluge_torrent.Deluge") - def shutdown(self): - log.info("Shutting down core..") - self.loop.quit() - + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="(six)") + def get_torrent_info(self, torrent_id): + # Get the info tuple from the torrent and return it + return self.torrents[torrent_id].get_info() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", + out_signature="(ibdixxddiixii)") + def get_torrent_status(self, torrent_id): + # Get the status tuple from the torrent and return it + return self.torrents[torrent_id].get_status() ## Queueing functions ###### @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", @@ -159,7 +163,7 @@ class Core(dbus.service.Object): if self.queue.top(torrent_id): self.torrent_queue_top() # Store the new torrent position in the torrent object - self.torrents[torrent_id].set_position(self.queue[torrent_id]) +# self.torrents[torrent_id].set_position(self.queue[torrent_id]) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -167,8 +171,6 @@ class Core(dbus.service.Object): # If the queue method returns True, then we should emit a signal if self.queue.up(torrent_id): self.torrent_queue_up() - # Store the new torrent position in the torrent object - self.torrents[torrent_id].set_position(self.queue[torrent_id]) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -176,8 +178,6 @@ class Core(dbus.service.Object): # If the queue method returns True, then we should emit a signal if self.queue.down(torrent_id): self.torrent_queue_down() - # Store the new torrent position in the torrent object - self.torrents[torrent_id].set_position(self.queue[torrent_id]) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -185,8 +185,6 @@ class Core(dbus.service.Object): # If the queue method returns True, then we should emit a signal if self.queue.bottom(torrent_id): self.torrent_queue_bottom() - # Store the new torrent position in the torrent object - self.torrents[torrent_id].set_position(self.queue[torrent_id]) # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 55ebfe9e7..8b59dac59 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -34,11 +34,52 @@ import deluge.libtorrent as lt class Torrent: - def __init__(self, handle): + def __init__(self, handle, queue): # Set the libtorrent handle self.handle = handle + # Set the queue this torrent belongs too + self.queue = queue + # Set the torrent_id for this torrent + self.torrent_id = str(handle.info_hash()) + + def get_eta(self): + """Returns the ETA in seconds for this torrent""" + left = self.handle.status().total_wanted \ + - self.handle.status().total_done - def set_position(self, position): - """Store the torrents queue position""" - self.position = position - + # The torrent file is done + if left == 0: + return 0 + + # Calculate the ETA in seconds and return it + return (left / self.handle.status().download_payload_rate) + + def get_info(self): + """Returns the torrents info.. stuff that remains constant, such as + name.""" + + return ( + self.handle.torrent_info().name(), + self.handle.torrent_info().total_size(), + self.handle.status().num_pieces + ) + + def get_status(self): + """Returns the torrent status""" + status = self.handle.status() + + return ( + status.state, + status.paused, + status.progress, + status.next_announce.seconds, + status.total_payload_download, + status.total_payload_upload, + status.download_payload_rate, + status.upload_payload_rate, + status.num_peers, + status.num_seeds, + status.total_wanted, + self.get_eta(), + self.queue[self.torrent_id] + ) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py new file mode 100644 index 000000000..bb71fec54 --- /dev/null +++ b/deluge/core/torrentmanager.py @@ -0,0 +1,61 @@ +# +# torrentmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +from deluge.core.torrent import Torrent +from deluge.core.torrentqueue import TorrentQueue + +# Get the logger +log = logging.getLogger("deluge") + +class TorrentManager: + def __init__(self): + log.debug("TorrentManager init..") + # Create the torrents dict { torrent_id: Torrent } + self.torrents = {} + self.queue = TorrentQueue() + + def __getitem__(self, torrent_id): + """Return the Torrent with torrent_id""" + return self.torrents[torrent_id] + + def add(self, handle): + """Add a torrent to the manager and returns it's torrent_id""" + # Create a Torrent object + torrent = Torrent(handle, self.queue) + # Add the torrent object to the dictionary + self.torrents[torrent.torrent_id] = torrent + # Add the torrent to the queue + self.queue.append(torrent.torrent_id) + return torrent.torrent_id diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index dfd64fcff..ee1f34e4e 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -62,7 +62,7 @@ class GtkUI: self.main_window = MainWindow() # Start the signal receiver - self.signal_receiver = Signals() + self.signal_receiver = Signals(self) # Show the main window self.main_window.show() diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 90ba2a654..00827cdcc 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -57,10 +57,15 @@ from deluge.config import Config log = logging.getLogger("deluge") class Signals: - def __init__(self): - core = functions.get_core() - core.connect_to_signal("torrent_added", self.torrent_added_signal) + def __init__(self, ui): + self.ui = ui + self.core = functions.get_core() + self.core.connect_to_signal("torrent_added", self.torrent_added_signal) - def torrent_added_signal(self, torrentid): + def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") - log.debug("torrent id: %s", torrentid) + log.debug("torrent id: %s", torrent_id) + # Add the torrent to the treeview + self.ui.main_window.torrentview.add_torrent(torrent_id, + self.core.get_torrent_info(torrent_id), + self.core.get_torrent_status(torrent_id)) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index cab25c937..5f2341a4c 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -54,9 +54,8 @@ class TorrentView: ## TreeModel setup ## # UID, Q#, Status Icon, Name, Size, Progress, Message, Seeders, Peers, # DL, UL, ETA, Share - self.torrent_model = gtk.ListStore(int, gobject.TYPE_UINT, - gtk.gdk.Pixbuf, str, gobject.TYPE_UINT64, float, str, int, int, - int, int, int, int, gobject.TYPE_UINT, float) + self.torrent_model = gtk.ListStore(str, int, gtk.gdk.Pixbuf, str, + long, float, str, int, int, int, int, int, int, int, float) ## TreeView setup ## self.torrent_view.set_model(self.torrent_model) @@ -150,8 +149,30 @@ class TorrentView: self.torrent_view.get_selection().connect("changed", self.on_selection_changed) + def add_torrent(self, torrent_id, info, status): + """Adds a new torrent row to the treeview""" + # UID, Q#, Status Icon, Name, Size, Progress, Message, Seeders, Peers, + # DL, UL, ETA, Share + self.torrent_model.insert(status[12], [ + torrent_id, + status[12]+1, + None, ## Status Icon + info[0], + info[2], + status[2], + status[0], + status[9], + status[9], + status[8], + status[8], + status[6], + status[7], + status[11], + 0.0 + ]) + ### Callbacks ### - def on_button_press_event(self, widget, event): + def on_button_press_event(self, widget, event, data): log.debug("on_button_press_event") def on_selection_changed(self, treeselection, data): From ca5297e20daff6189f921715910331cf43e3052c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 24 Jul 2007 01:53:35 +0000 Subject: [PATCH 0020/1009] Can now remove torrents. Torrent view is now updating in timer. --- deluge/core/core.py | 38 +++++- deluge/core/torrent.py | 21 +++- deluge/core/torrentmanager.py | 34 ++++++ deluge/core/torrentqueue.py | 6 +- deluge/ui/gtkui/columns.py | 2 +- deluge/ui/gtkui/functions.py | 25 ++++ deluge/ui/gtkui/glade/torrent_menu.glade | 2 +- deluge/ui/gtkui/mainwindow.py | 7 ++ deluge/ui/gtkui/menubar.py | 2 + deluge/ui/gtkui/signals.py | 12 +- deluge/ui/gtkui/torrentview.py | 141 +++++++++++++++++------ 11 files changed, 238 insertions(+), 52 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index b8e51a3fe..f16cc91e1 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -143,7 +143,21 @@ class Core(dbus.service.Object): self.torrent_added(torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="(six)") + in_signature="s", out_signature="") + def remove_torrent(self, torrent_id): + log.debug("Removing torrent %s from the core.", torrent_id) + try: + # Remove from libtorrent session + self.session.remove_torrent(self.torrents[torrent_id].handle) + # Remove from TorrentManager + self.torrents.remove(torrent_id) + # Emit the torrent_removed signal + self.torrent_removed(torrent_id) + except RuntimeError, KeyError: + log.warning("Error removing torrent") + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="(sxi)") def get_torrent_info(self, torrent_id): # Get the info tuple from the torrent and return it return self.torrents[torrent_id].get_info() @@ -154,6 +168,20 @@ class Core(dbus.service.Object): def get_torrent_status(self, torrent_id): # Get the status tuple from the torrent and return it return self.torrents[torrent_id].get_status() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="", + out_signature="as") + def get_torrent_status_template(self): + # A list of strings the correspond to the status tuple + return self.torrents.get_status_template() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="", + out_signature="as") + def get_torrent_info_template(self): + # A list of strings the correspond to the info tuple + return self.torrents.get_info_template() ## Queueing functions ###### @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", @@ -162,8 +190,6 @@ class Core(dbus.service.Object): # If the queue method returns True, then we should emit a signal if self.queue.top(torrent_id): self.torrent_queue_top() - # Store the new torrent position in the torrent object -# self.torrents[torrent_id].set_position(self.queue[torrent_id]) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -199,6 +225,12 @@ class Core(dbus.service.Object): """Emitted when a new torrent fails addition to the session""" log.debug("torrent_add_failed signal emitted") + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") + def torrent_removed(self, torrent_id): + """Emitted when a torrent has been removed from the core""" + log.debug("torrent_remove signal emitted") + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") def torrent_queue_top(self, torrent_id): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 8b59dac59..54d695e88 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -31,8 +31,13 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import logging + import deluge.libtorrent as lt +# Get the logger +log = logging.getLogger("deluge") + class Torrent: def __init__(self, handle, queue): # Set the libtorrent handle @@ -42,17 +47,20 @@ class Torrent: # Set the torrent_id for this torrent self.torrent_id = str(handle.info_hash()) + def __del__(self): + self.queue.remove(self.torrent_id) + def get_eta(self): """Returns the ETA in seconds for this torrent""" left = self.handle.status().total_wanted \ - self.handle.status().total_done - # The torrent file is done - if left == 0: - return 0 - - # Calculate the ETA in seconds and return it - return (left / self.handle.status().download_payload_rate) + try: + eta = left / self.handle.status().download_payload_rate + except ZeroDivisionError: + eta = 0 + + return eta def get_info(self): """Returns the torrents info.. stuff that remains constant, such as @@ -83,3 +91,4 @@ class Torrent: self.get_eta(), self.queue[self.torrent_id] ) + diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index bb71fec54..af715935e 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -59,3 +59,37 @@ class TorrentManager: # Add the torrent to the queue self.queue.append(torrent.torrent_id) return torrent.torrent_id + + def remove(self, torrent_id): + """Remove a torrent from the manager""" + try: + del self.torrents[torrent_id] + except KeyError, ValueError: + return False + return True + + def get_info_template(self): + """Returns a list of strings that correspond to the info tuple""" + return [ + "name", + "total_size", + "num_pieces" + ] + + def get_status_template(self): + """Returns a list of strings that correspond to the status tuple""" + return [ + "state", + "paused", + "progress", + "next_announce", + "total_payload_download", + "total_payload_upload", + "download_payload_rate", + "upload_payload_rate", + "num_peers", + "num_seeds", + "total_wanted", + "eta", + "position" + ] diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py index 3e09cadcb..5694f592e 100644 --- a/deluge/core/torrentqueue.py +++ b/deluge/core/torrentqueue.py @@ -54,7 +54,11 @@ class TorrentQueue: """Prepend torrent_id to the top of the queue""" log.debug("Prepend torrent %s to queue..", torrent_id) self.queue.insert(0, torrent_id) - + + def remove(self, torrent_id): + """Removes torrent_id from the list""" + self.queue.remove(torrent_id) + def up(self, torrent_id): """Move torrent_id up one in the queue""" if torrent_id not in self.queue: diff --git a/deluge/ui/gtkui/columns.py b/deluge/ui/gtkui/columns.py index 23ceeda12..d47cd3c5a 100644 --- a/deluge/ui/gtkui/columns.py +++ b/deluge/ui/gtkui/columns.py @@ -62,7 +62,7 @@ def cell_data_time(column, cell, model, iter, data): time_str = _("Infinity") else: time_str = deluge.common.ftime(time) - cell.set_property('text', time_str) + cell.set_property('text', time_str) def cell_data_ratio(column, cell, model, iter, data): ratio = float(model.get_value(iter, data)) diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py index f06e8f332..5e6fb7226 100644 --- a/deluge/ui/gtkui/functions.py +++ b/deluge/ui/gtkui/functions.py @@ -85,3 +85,28 @@ def add_torrent_file(): (path, filename) = os.path.split(torrent_file) core.add_torrent_file(filename, f.read()) f.close() + +def remove_torrent(torrent_ids): + """Removes torrent_ids from the core.. Expects a list of torrent_ids""" + log.debug("Attempting to removing torrents: %s", torrent_ids) + core = get_core() + for torrent_id in torrent_ids: + core.remove_torrent(torrent_id) + +def get_torrent_status_dict(core, torrent_id): + """Builds and returns a status dictionary using the status template""" + status = core.get_torrent_status(torrent_id) + template = core.get_torrent_status_template() + status_dict = {} + for string in template: + status_dict[string] = status[template.index(string)] + return status_dict + +def get_torrent_info_dict(core, torrent_id): + """Builds and returns an info dictionary using the info template""" + info = core.get_torrent_info(torrent_id) + template = core.get_torrent_info_template() + info_dict = {} + for string in template: + info_dict[string] = info[template.index(string)] + return info_dict diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 21b1a7f12..dc1c89d74 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -58,7 +58,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Remove Torrent True - + True diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index f9641954c..ffa8b66c9 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -36,6 +36,7 @@ import logging import pygtk pygtk.require('2.0') import gtk, gtk.glade +import gobject import pkg_resources from menubar import MenuBar @@ -58,7 +59,13 @@ class MainWindow: self.menubar = MenuBar(self) self.toolbar = ToolBar(self) self.torrentview = TorrentView(self) + + gobject.timeout_add(1000, self.update) + def update(self): + self.torrentview.update() + return True + def show(self): self.window.show_all() diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 0b3e6a036..5e758a788 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -125,6 +125,8 @@ class MenuBar: log.debug("on_menuitem_edittrackers_activate") def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") + functions.remove_torrent( + self.window.torrentview.get_selected_torrents()) def on_menuitem_queuetop_activate(self, data=None): log.debug("on_menuitem_queuetop_activate") def on_menuitem_queueup_activate(self, data=None): diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 00827cdcc..c1d4431d8 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -61,11 +61,17 @@ class Signals: self.ui = ui self.core = functions.get_core() self.core.connect_to_signal("torrent_added", self.torrent_added_signal) + self.core.connect_to_signal("torrent_removed", + self.torrent_removed_signal) def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) # Add the torrent to the treeview - self.ui.main_window.torrentview.add_torrent(torrent_id, - self.core.get_torrent_info(torrent_id), - self.core.get_torrent_status(torrent_id)) + self.ui.main_window.torrentview.add_row(torrent_id) + + def torrent_removed_signal(self, torrent_id): + log.debug("torrent_remove signal received..") + log.debug("torrent id: %s", torrent_id) + # Remove the torrent from the treeview + self.ui.main_window.torrentview.remove_row(torrent_id) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 5f2341a4c..c09177da9 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,14 +40,33 @@ import gobject import gettext import columns +import functions # Get the logger log = logging.getLogger("deluge") +# Initializes the columns for the torrent_view +(TORRENT_VIEW_COL_UID, +TORRENT_VIEW_COL_QUEUE, +TORRENT_VIEW_COL_STATUSICON, +TORRENT_VIEW_COL_NAME, +TORRENT_VIEW_COL_SIZE, +TORRENT_VIEW_COL_PROGRESS, +TORRENT_VIEW_COL_STATUS, +TORRENT_VIEW_COL_CONNECTED_SEEDS, +TORRENT_VIEW_COL_SEEDS, +TORRENT_VIEW_COL_CONNECTED_PEERS, +TORRENT_VIEW_COL_PEERS, +TORRENT_VIEW_COL_DOWNLOAD, +TORRENT_VIEW_COL_UPLOAD, +TORRENT_VIEW_COL_ETA, +TORRENT_VIEW_COL_RATIO) = range(15) + class TorrentView: def __init__(self, window): log.debug("TorrentView Init..") self.window = window + self.core = functions.get_core() # Get the torrent_view widget self.torrent_view = self.window.main_glade.get_widget("torrent_view") @@ -63,23 +82,6 @@ class TorrentView: self.torrent_view.set_reorderable(True) self.torrent_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - # Initializes the columns for the torrent_view - (TORRENT_VIEW_COL_UID, - TORRENT_VIEW_COL_QUEUE, - TORRENT_VIEW_COL_STATUSICON, - TORRENT_VIEW_COL_NAME, - TORRENT_VIEW_COL_SIZE, - TORRENT_VIEW_COL_PROGRESS, - TORRENT_VIEW_COL_STATUS, - TORRENT_VIEW_COL_CONNECTED_SEEDS, - TORRENT_VIEW_COL_SEEDS, - TORRENT_VIEW_COL_CONNECTED_PEERS, - TORRENT_VIEW_COL_PEERS, - TORRENT_VIEW_COL_DOWNLOAD, - TORRENT_VIEW_COL_UPLOAD, - TORRENT_VIEW_COL_ETA, - TORRENT_VIEW_COL_RATIO) = range(15) - self.queue_column = columns.add_text_column( self.torrent_view, "#", TORRENT_VIEW_COL_QUEUE) @@ -149,32 +151,97 @@ class TorrentView: self.torrent_view.get_selection().connect("changed", self.on_selection_changed) - def add_torrent(self, torrent_id, info, status): + def update(self): + """Update the view, this is likely called by a timer""" + # Iterate through the torrent_model and update rows + row = self.torrent_model.get_iter_first() + while row is not None: + torrent_id = self.torrent_model.get_value(row, 0) + status = functions.get_torrent_status_dict(self.core, torrent_id) + # Set values for each column in the row + self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, + status["position"]+1) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_PROGRESS, + status["progress"]*100) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_STATUS, + status["state"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_CONNECTED_SEEDS, + status["num_seeds"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_SEEDS, + status["num_seeds"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_CONNECTED_PEERS, + status["num_peers"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_PEERS, + status["num_peers"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_DOWNLOAD, + status["download_payload_rate"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_UPLOAD, + status["upload_payload_rate"]) + self.torrent_model.set_value(row, TORRENT_VIEW_COL_ETA, + status["eta"]) + row = self.torrent_model.iter_next(row) + + def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" - # UID, Q#, Status Icon, Name, Size, Progress, Message, Seeders, Peers, - # DL, UL, ETA, Share - self.torrent_model.insert(status[12], [ + # Get the status and info dictionaries + status = functions.get_torrent_status_dict(self.core, torrent_id) + info = functions.get_torrent_info_dict(self.core, torrent_id) + + # Insert the row with info provided from core + self.torrent_model.insert(status["position"], [ torrent_id, - status[12]+1, - None, ## Status Icon - info[0], - info[2], - status[2], - status[0], - status[9], - status[9], - status[8], - status[8], - status[6], - status[7], - status[11], - 0.0 + status["position"]+1, + None, + info["name"], + info["total_size"], + status["progress"]*100, + status["state"], + status["num_seeds"], + status["num_seeds"], + status["num_peers"], + status["num_peers"], + status["download_payload_rate"], + status["upload_payload_rate"], + status["eta"], + 0.0 ]) + + def remove_row(self, torrent_id): + """Removes a row with torrent_id""" + row = self.torrent_model.get_iter_first() + while row is not None: + # Check if this row is the row we want to remove + if self.torrent_model.get_value(row, 0) == torrent_id: + self.torrent_model.remove(row) + # Force an update of the torrentview + self.update() + break + row = self.torrent_model.iter_next(row) + + def get_selected_torrents(self): + """Returns a list of selected torrents or None""" + torrent_ids = [] + paths = self.torrent_view.get_selection().get_selected_rows()[1] + + try: + for path in paths: + torrent_ids.append( + self.torrent_model.get_value( + self.torrent_model.get_iter(path), 0)) + return torrent_ids + except ValueError: + return None ### Callbacks ### - def on_button_press_event(self, widget, event, data): + def on_button_press_event(self, widget, event): log.debug("on_button_press_event") + # We only care about right-clicks + if event.button == 3: + # Show the Torrent menu from the MenuBar + torrentmenu = self.window.menubar.torrentmenu.get_widget( + "torrent_menu") + torrentmenu.popup(None, None, None, event.button, event.time) - def on_selection_changed(self, treeselection, data): + def on_selection_changed(self, treeselection): log.debug("on_selection_changed") From 7c36587f235f760f276caea536a5a55cf2757d26 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 24 Jul 2007 07:19:38 +0000 Subject: [PATCH 0021/1009] Can now change queue order in the UI. --- deluge/core/core.py | 44 +++++++----------------- deluge/core/torrent.py | 3 ++ deluge/ui/gtkui/addtorrentdialog.py | 1 - deluge/ui/gtkui/functions.py | 28 +++++++++++++++ deluge/ui/gtkui/glade/torrent_menu.glade | 4 +-- deluge/ui/gtkui/menubar.py | 19 ++++++++-- deluge/ui/gtkui/signals.py | 7 ++++ deluge/ui/gtkui/toolbar.py | 25 ++++++++++++-- deluge/ui/gtkui/torrentview.py | 10 +++--- 9 files changed, 99 insertions(+), 42 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index f16cc91e1..07c46827d 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -188,29 +188,29 @@ class Core(dbus.service.Object): in_signature="s", out_signature="") def queue_top(self, torrent_id): # If the queue method returns True, then we should emit a signal - if self.queue.top(torrent_id): - self.torrent_queue_top() + if self.torrents.queue.top(torrent_id): + self.torrent_queue_changed() @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def queue_up(self, torrent_id): # If the queue method returns True, then we should emit a signal - if self.queue.up(torrent_id): - self.torrent_queue_up() - + if self.torrents.queue.up(torrent_id): + self.torrent_queue_changed() + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def queue_down(self, torrent_id): # If the queue method returns True, then we should emit a signal - if self.queue.down(torrent_id): - self.torrent_queue_down() + if self.torrents.queue.down(torrent_id): + self.torrent_queue_changed() @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def queue_bottom(self, torrent_id): # If the queue method returns True, then we should emit a signal - if self.queue.bottom(torrent_id): - self.torrent_queue_bottom() + if self.torrents.queue.bottom(torrent_id): + self.torrent_queue_changed() # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", @@ -232,25 +232,7 @@ class Core(dbus.service.Object): log.debug("torrent_remove signal emitted") @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") - def torrent_queue_top(self, torrent_id): - """Emitted when a torrent is queued to the top""" - log.debug("torrent_queue_top signal emitted") - - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") - def torrent_queue_up(self, torrent_id): - """Emitted when a torrent is queued up""" - log.debug("torrent_queue_up signal emitted") - - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") - def torrent_queue_down(self, torrent_id): - """Emitted when a torrent is queued down""" - log.debug("torrent_queue_down signal emitted") - - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") - def torrent_queue_bottom(self, torrent_id): - """Emitted when a torrent is queued to the bottom""" - log.debug("torrent_queue_bottom signal emitted") + signature="") + def torrent_queue_changed(self): + """Emitted when a torrent queue position is changed""" + log.debug("torrent_queue_changed signal emitted") diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 54d695e88..7b7824667 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -55,6 +55,9 @@ class Torrent: left = self.handle.status().total_wanted \ - self.handle.status().total_done + if left == 0 or self.handle.status().download_payload_rate == 0: + return 0 + try: eta = left / self.handle.status().download_payload_rate except ZeroDivisionError: diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 7cde8358e..98c294e08 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -70,7 +70,6 @@ class AddTorrentDialog: self.chooser.set_current_folder( self.config.get("default_load_path")) - def run(self): """Returns a list of selected files or None if no files were selected. """ diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py index 5e6fb7226..7de2d515c 100644 --- a/deluge/ui/gtkui/functions.py +++ b/deluge/ui/gtkui/functions.py @@ -93,6 +93,34 @@ def remove_torrent(torrent_ids): for torrent_id in torrent_ids: core.remove_torrent(torrent_id) +def queue_top(torrent_ids): + """Attempts to queue all torrent_ids to the top""" + log.debug("Attempting to queue to top these torrents: %s", torrent_ids) + core = get_core() + for torrent_id in torrent_ids: + core.queue_top(torrent_id) + +def queue_up(torrent_ids): + """Attempts to queue all torrent_ids up""" + log.debug("Attempting to queue up these torrents: %s", torrent_ids) + core = get_core() + for torrent_id in torrent_ids: + core.queue_up(torrent_id) + +def queue_down(torrent_ids): + """Attempts to queue all torrent_ids down""" + log.debug("Attempting to queue down these torrents: %s", torrent_ids) + core = get_core() + for torrent_id in torrent_ids: + core.queue_down(torrent_id) + +def queue_bottom(torrent_ids): + """Attempts to queue all torrent_ids to the bottom""" + log.debug("Attempting to queue to bottom these torrents: %s", torrent_ids) + core = get_core() + for torrent_id in torrent_ids: + core.queue_bottom(torrent_id) + def get_torrent_status_dict(core, torrent_id): """Builds and returns a status dictionary using the status template""" status = core.get_torrent_status(torrent_id) diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index dc1c89d74..a14b823af 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -106,7 +106,7 @@ True _Up True - + True @@ -122,7 +122,7 @@ True _Down True - + True diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 5e758a788..61bb4a8fa 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -102,10 +102,13 @@ class MenuBar: def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") functions.add_torrent_file() + def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") + def on_menuitem_clear_activate(self, data=None): log.debug("on_menuitem_clear_activate") + def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") self.window.quit() @@ -113,36 +116,48 @@ class MenuBar: ## Edit Menu ## def on_menuitem_preferences_activate(self, data=None): log.debug("on_menuitem_preferences_activate") + def on_menuitem_plugins_activate(self, data=None): log.debug("on_menuitem_plugins_activate") ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") + def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") + def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") + def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") functions.remove_torrent( self.window.torrentview.get_selected_torrents()) + def on_menuitem_queuetop_activate(self, data=None): log.debug("on_menuitem_queuetop_activate") + functions.queue_top(self.window.torrentview.get_selected_torrents()) + def on_menuitem_queueup_activate(self, data=None): log.debug("on_menuitem_queueup_activate") + functions.queue_up(self.window.torrentview.get_selected_torrents()) + def on_menuitem_queuedown_activate(self, data=None): log.debug("on_menuitem_queuedown_activate") + functions.queue_down(self.window.torrentview.get_selected_torrents()) + def on_menuitem_queuebottom_activate(self, data=None): log.debug("on_menuitem_queuebottom_activate") - + functions.queue_bottom(self.window.torrentview.get_selected_torrents()) + ## View Menu ## def on_menuitem_toolbar_toggled(self, data=None): log.debug("on_menuitem_toolbar_toggled") + def on_menuitem_infopane_toggled(self, data=None): log.debug("on_menuitem_infopane_toggled") ## Help Menu ## def on_menuitem_about_activate(self, data=None): log.debug("on_menuitem_about_activate") - diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index c1d4431d8..10c87a105 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -63,6 +63,8 @@ class Signals: self.core.connect_to_signal("torrent_added", self.torrent_added_signal) self.core.connect_to_signal("torrent_removed", self.torrent_removed_signal) + self.core.connect_to_signal("torrent_queue_changed", + self.torrent_queue_changed_signal) def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") @@ -75,3 +77,8 @@ class Signals: log.debug("torrent id: %s", torrent_id) # Remove the torrent from the treeview self.ui.main_window.torrentview.remove_row(torrent_id) + + def torrent_queue_changed_signal(self): + log.debug("torrent_queue_changed signal received..") + # Force an update of the torrent view + self.ui.main_window.torrentview.update() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 305f2faa4..a565c38df 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -66,19 +66,40 @@ class ToolBar: ### Callbacks ### def on_toolbutton_add_clicked(self, data): log.debug("on_toolbutton_add_clicked") - functions.add_torrent_file() + # Use the menubar's callback + self.window.menubar.on_menuitem_addtorrent_activate(data) + def on_toolbutton_remove_clicked(self, data): log.debug("on_toolbutton_remove_clicked") + # Use the menubar's callbacks + self.window.menubar.on_menuitem_remove_activate(data) + def on_toolbutton_clear_clicked(self, data): log.debug("on_toolbutton_clear_clicked") + # Use the menubar's callbacks + self.window.menubar.on_menuitem_clear_activate(data) + def on_toolbutton_pause_clicked(self, data): log.debug("on_toolbutton_pause_clicked") + # Use the menubar's callbacks + self.window.menubar.on_menuitem_pause_activate(data) + def on_toolbutton_queueup_clicked(self, data): log.debug("on_toolbutton_queueup_clicked") + # Use the menubar's callbacks + self.window.menubar.on_menuitem_queueup_activate(data) + def on_toolbutton_queuedown_clicked(self, data): log.debug("on_toolbutton_queuedown_clicked") + # Use the menubar's callbacks + self.window.menubar.on_menuitem_queuedown_activate(data) + def on_toolbutton_preferences_clicked(self, data): log.debug("on_toolbutton_preferences_clicked") + # Use the menubar's callbacks + self.window.menubar.on_menuitem_preferences_activate(data) + def on_toolbutton_plugins_clicked(self, data): log.debug("on_toolbutton_plugins_clicked") - + # Use the menubar's callbacks + self.window.menubar.on_menuitem_preferences_activate(data) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index c09177da9..7c8d8b002 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -153,9 +153,9 @@ class TorrentView: def update(self): """Update the view, this is likely called by a timer""" - # Iterate through the torrent_model and update rows - row = self.torrent_model.get_iter_first() - while row is not None: + + # This function is used for the foreach method of the treemodel + def update_row(model, path, row, user_data): torrent_id = self.torrent_model.get_value(row, 0) status = functions.get_torrent_status_dict(self.core, torrent_id) # Set values for each column in the row @@ -179,7 +179,9 @@ class TorrentView: status["upload_payload_rate"]) self.torrent_model.set_value(row, TORRENT_VIEW_COL_ETA, status["eta"]) - row = self.torrent_model.iter_next(row) + + # Iterates through every row and updates them accordingly + self.torrent_model.foreach(update_row, None) def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" From 2eb455179b41e1b2325e60144c489aac5beab99c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 25 Jul 2007 00:43:37 +0000 Subject: [PATCH 0022/1009] Updates --- deluge/config.py | 6 ++- deluge/core/core.py | 67 ++++++++++------------------ deluge/core/torrent.py | 8 +++- deluge/core/torrentmanager.py | 83 +++++++++++++++++++++++++++++++++-- deluge/main.py | 1 - deluge/ui/gtkui/functions.py | 6 +++ deluge/ui/gtkui/menubar.py | 2 + deluge/ui/gtkui/signals.py | 5 +++ 8 files changed, 127 insertions(+), 51 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index 4981a1775..2fd367d98 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -51,6 +51,8 @@ class Config: # Load the config from file in the config_dir self.config_file = deluge.common.get_config_dir(filename) self.load(self.config_file) + # Save + self.save() def __del__(self): log.debug("Config object deconstructing..") @@ -90,6 +92,8 @@ class Config: # Sets the "key" with "value" in the config dict log.debug("Setting '%s' to %s", key, value) self.config[key] = value + # Whenever something is set, we should save + self.save() def get(self, key): # Attempts to get the "key" value and returns None if the key is @@ -106,4 +110,4 @@ class Config: return self.config[key] def __setitem__(self, key, value): - self.config[key] = value + self.set(key, value) diff --git a/deluge/core/core.py b/deluge/core/core.py index 07c46827d..d25825f02 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -67,9 +67,6 @@ DEFAULT_PREFS = { class Core(dbus.service.Object): def __init__(self, path="/org/deluge_torrent/Core"): log.debug("Core init..") - - # Start the TorrentManager - self.torrents = TorrentManager() # Setup DBUS bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", @@ -87,6 +84,9 @@ class Core(dbus.service.Object): self.session.listen_on(self.config.get("listen_ports")[0], self.config.get("listen_ports")[1]) + # Start the TorrentManager + self.torrents = TorrentManager(self.session) + log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() @@ -105,56 +105,27 @@ class Core(dbus.service.Object): This requires the torrents filename and a dump of it's content """ log.info("Adding torrent: %s", filename) - - # Convert the filedump data array into a string of bytes - filedump = "".join(chr(b) for b in filedump) - - # Bdecode the filedata sent from the UI - torrent_filedump = lt.bdecode(filedump) - handle = None - try: - handle = self.session.add_torrent(lt.torrent_info(torrent_filedump), - self.config["download_location"], - self.config["compact_allocation"]) - except RuntimeError: - log.warning("Error adding torrent") - - if not handle or not handle.is_valid(): - # The torrent was not added to the session - # Emit the torrent_add_failed signal + torrent_id = self.torrents.add(filename, filedump) + if torrent_id is not None: + # Emit the torrent_added signal + self.torrent_added(torrent_id) + else: self.torrent_add_failed() - return - - # Write the .torrent file to the torrent directory - log.debug("Attemping to save torrent file: %s", filename) - try: - f = open(os.path.join(self.config["torrentfiles_location"], - filename), - "wb") - f.write(filedump) - f.close() - except IOError: - log.warning("Unable to save torrent file: %s", filename) - - # Add the torrent to the torrentmanager - torrent_id = self.torrents.add(handle) - - # Emit the torrent_added signal - self.torrent_added(torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def remove_torrent(self, torrent_id): log.debug("Removing torrent %s from the core.", torrent_id) - try: - # Remove from libtorrent session - self.session.remove_torrent(self.torrents[torrent_id].handle) - # Remove from TorrentManager - self.torrents.remove(torrent_id) + if self.torrents.remove(torrent_id): # Emit the torrent_removed signal self.torrent_removed(torrent_id) - except RuntimeError, KeyError: - log.warning("Error removing torrent") + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def pause_torrent(self, torrent_id): + log.debug("Pausing torrent %s", torrent_id) + if self.torrents.pause(torrent_id): + self.torrent_paused(torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="(sxi)") @@ -236,3 +207,9 @@ class Core(dbus.service.Object): def torrent_queue_changed(self): """Emitted when a torrent queue position is changed""" log.debug("torrent_queue_changed signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") + def torrent_paused(self, torrent_id): + """Emitted when a torrent is paused""" + log.debug("torrent_paused signal emitted") diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 7b7824667..ca58efb67 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -39,7 +39,9 @@ import deluge.libtorrent as lt log = logging.getLogger("deluge") class Torrent: - def __init__(self, handle, queue): + def __init__(self, filename, handle, queue): + # Set the filename + self.filename = filename # Set the libtorrent handle self.handle = handle # Set the queue this torrent belongs too @@ -49,6 +51,10 @@ class Torrent: def __del__(self): self.queue.remove(self.torrent_id) + + def get_state(self): + """Returns the state of this torrent for saving to the session state""" + return (self.torrent_id, self.filename) def get_eta(self): """Returns the ETA in seconds for this torrent""" diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index af715935e..bd40a1340 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -32,16 +32,25 @@ # statement from all source files in the program, then also delete it here. import logging +import pickle +import os.path +import deluge.libtorrent as lt + +import deluge.common +from deluge.config import Config from deluge.core.torrent import Torrent from deluge.core.torrentqueue import TorrentQueue +from deluge.core.torrentmanagerstate import TorrentManagerState, TorrentState # Get the logger log = logging.getLogger("deluge") class TorrentManager: - def __init__(self): + def __init__(self, session): log.debug("TorrentManager init..") + # Set the libtorrent session + self.session = session # Create the torrents dict { torrent_id: Torrent } self.torrents = {} self.queue = TorrentQueue() @@ -50,10 +59,42 @@ class TorrentManager: """Return the Torrent with torrent_id""" return self.torrents[torrent_id] - def add(self, handle): + def add(self, filename, filedump): """Add a torrent to the manager and returns it's torrent_id""" + # Get the core config + config = Config("core.conf") + + # Convert the filedump data array into a string of bytes + filedump = "".join(chr(b) for b in filedump) + + # Bdecode the filedata sent from the UI + torrent_filedump = lt.bdecode(filedump) + handle = None + + try: + handle = self.session.add_torrent(lt.torrent_info(torrent_filedump), + config["download_location"], + config["compact_allocation"]) + except RuntimeError: + log.warning("Error adding torrent") + + if not handle or not handle.is_valid(): + # The torrent was not added to the session + return None + + # Write the .torrent file to the torrent directory + log.debug("Attemping to save torrent file: %s", filename) + try: + f = open(os.path.join(config["torrentfiles_location"], + filename), + "wb") + f.write(filedump) + f.close() + except IOError: + log.warning("Unable to save torrent file: %s", filename) + # Create a Torrent object - torrent = Torrent(handle, self.queue) + torrent = Torrent(filename, handle, self.queue) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent # Add the torrent to the queue @@ -62,12 +103,48 @@ class TorrentManager: def remove(self, torrent_id): """Remove a torrent from the manager""" + try: + # Remove from libtorrent session + self.session.remove_torrent(self.torrents[torrent_id].handle) + except RuntimeError, KeyError: + log.warning("Error removing torrent") + return False + try: del self.torrents[torrent_id] except KeyError, ValueError: return False return True + + def pause(self, torrent_id): + """Pause a torrent""" + try: + self.torrents[torrent_id].handle.pause() + except: + return False + + return True + + def save_state(self): + """Save the state of the TorrentManager to the torrents.state file""" + state = TorrentManagerState() + # Grab the queue from TorrentQueue + state.queue = self.queue.queue + # Create the state for each Torrent and append to the list + for (key, torrent) in self.torrents: + t = TorrentState(torrent.get_state()) + state.torrents.append(t) + # Pickle the TorrentManagerState object + try: + log.debug("Saving torrent state file.") + state_file = open(deluge.common.get_config_dir("torrents.state"), "wb") + pickle.dump(state, state_file) + state_file.close() + except IOError: + log.warning("Unable to save state file.") + + def get_info_template(self): """Returns a list of strings that correspond to the info tuple""" return [ diff --git a/deluge/main.py b/deluge/main.py index 393cd9f02..b46f06ffc 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -53,7 +53,6 @@ log = logging.getLogger("deluge") def main(): # Setup the argument parser - # FIXME: need to use deluge.common to fill in version parser = OptionParser(usage="%prog [options] [actions]", version=deluge.common.get_version()) parser.add_option("--daemon", dest="daemon", help="Start Deluge daemon", diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py index 7de2d515c..b11cb5c3e 100644 --- a/deluge/ui/gtkui/functions.py +++ b/deluge/ui/gtkui/functions.py @@ -92,6 +92,12 @@ def remove_torrent(torrent_ids): core = get_core() for torrent_id in torrent_ids: core.remove_torrent(torrent_id) + +def pause_torrent(torrent_ids): + """Pauses torrent_ids""" + core = get_core() + for torrent_id in torrent_ids: + core.pause_torrent(torrent_id) def queue_top(torrent_ids): """Attempts to queue all torrent_ids to the top""" diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 61bb4a8fa..bc071c9bf 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -123,6 +123,8 @@ class MenuBar: ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") + functions.pause_torrent( + self.window.torrentview.get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 10c87a105..6a8ba2d29 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -65,6 +65,7 @@ class Signals: self.torrent_removed_signal) self.core.connect_to_signal("torrent_queue_changed", self.torrent_queue_changed_signal) + self.core.connect_to_signal("torrent_paused", self.torrent_paused) def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") @@ -82,3 +83,7 @@ class Signals: log.debug("torrent_queue_changed signal received..") # Force an update of the torrent view self.ui.main_window.torrentview.update() + + def torrent_paused(self, torrent_id): + log.debug("torrent_paused signal received..") + self.ui.main_window.torrentview.update() From ac1bffb65f07ec57f01ae2573c7b51dbfe1b7739 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 25 Jul 2007 05:07:43 +0000 Subject: [PATCH 0023/1009] get_torrent_status() and get_torrent_info() now use pickled dictionaries thus removing the need for the templates. --- deluge/core/core.py | 31 +++++++++---------------- deluge/core/torrent.py | 41 +++++++++++++++++----------------- deluge/core/torrentmanager.py | 41 ++++++++++------------------------ deluge/ui/gtkui/functions.py | 33 ++++++++++++++------------- deluge/ui/gtkui/torrentview.py | 12 +++++----- 5 files changed, 66 insertions(+), 92 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index d25825f02..7e2d79654 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -33,6 +33,7 @@ import logging import os.path +import pickle try: import dbus, dbus.service @@ -128,31 +129,21 @@ class Core(dbus.service.Object): self.torrent_paused(torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="(sxi)") + in_signature="s", out_signature="ay") def get_torrent_info(self, torrent_id): - # Get the info tuple from the torrent and return it - return self.torrents[torrent_id].get_info() + # Pickle the info dictionary from the torrent and return it + info = self.torrents[torrent_id].get_info() + info = pickle.dumps(info) + return info @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", - out_signature="(ibdixxddiixii)") + out_signature="ay") def get_torrent_status(self, torrent_id): - # Get the status tuple from the torrent and return it - return self.torrents[torrent_id].get_status() - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", - out_signature="as") - def get_torrent_status_template(self): - # A list of strings the correspond to the status tuple - return self.torrents.get_status_template() - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", - out_signature="as") - def get_torrent_info_template(self): - # A list of strings the correspond to the info tuple - return self.torrents.get_info_template() + # Pickle the status dictionary from the torrent and return it + status = self.torrents[torrent_id].get_status() + status = pickle.dumps(status) + return status ## Queueing functions ###### @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index ca58efb67..27e7f675d 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -75,29 +75,28 @@ class Torrent: """Returns the torrents info.. stuff that remains constant, such as name.""" - return ( - self.handle.torrent_info().name(), - self.handle.torrent_info().total_size(), - self.handle.status().num_pieces - ) + return { + "name": self.handle.torrent_info().name(), + "total_size": self.handle.torrent_info().total_size(), + "num_pieces": self.handle.status().num_pieces + } def get_status(self): """Returns the torrent status""" status = self.handle.status() - return ( - status.state, - status.paused, - status.progress, - status.next_announce.seconds, - status.total_payload_download, - status.total_payload_upload, - status.download_payload_rate, - status.upload_payload_rate, - status.num_peers, - status.num_seeds, - status.total_wanted, - self.get_eta(), - self.queue[self.torrent_id] - ) - + return { + "state": int(status.state), + "paused": status.paused, + "progress": status.progress, + "next_announce": status.next_announce.seconds, + "total_payload_download": status.total_payload_download, + "total_payload_upload": status.total_payload_upload, + "download_payload_rate": status.download_payload_rate, + "upload_payload_rate": status.upload_payload_rate, + "num_peers": status.num_peers, + "num_seeds": status.num_seeds, + "total_wanted": status.total_wanted, + "eta": self.get_eta(), + "queue": self.queue[self.torrent_id] + } diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index bd40a1340..af125e287 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -59,15 +59,24 @@ class TorrentManager: """Return the Torrent with torrent_id""" return self.torrents[torrent_id] - def add(self, filename, filedump): + def add(self, filename, filedump=None): """Add a torrent to the manager and returns it's torrent_id""" # Get the core config config = Config("core.conf") # Convert the filedump data array into a string of bytes - filedump = "".join(chr(b) for b in filedump) + if filedump is not None: + filedump = "".join(chr(b) for b in filedump) + else: + # Get the data from the file + try: + filedump = open(os.path.join(config["torrentfiles_location"], + filename, "rb")).read() + except IOError: + log.warning("Unable to open %s", filename) + return None - # Bdecode the filedata sent from the UI + # Bdecode the filedata torrent_filedump = lt.bdecode(filedump) handle = None @@ -144,29 +153,3 @@ class TorrentManager: except IOError: log.warning("Unable to save state file.") - - def get_info_template(self): - """Returns a list of strings that correspond to the info tuple""" - return [ - "name", - "total_size", - "num_pieces" - ] - - def get_status_template(self): - """Returns a list of strings that correspond to the status tuple""" - return [ - "state", - "paused", - "progress", - "next_announce", - "total_payload_download", - "total_payload_upload", - "download_payload_rate", - "upload_payload_rate", - "num_peers", - "num_seeds", - "total_wanted", - "eta", - "position" - ] diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py index b11cb5c3e..91428e24d 100644 --- a/deluge/ui/gtkui/functions.py +++ b/deluge/ui/gtkui/functions.py @@ -33,6 +33,7 @@ import logging import os.path +import pickle try: import dbus, dbus.service @@ -127,20 +128,20 @@ def queue_bottom(torrent_ids): for torrent_id in torrent_ids: core.queue_bottom(torrent_id) -def get_torrent_status_dict(core, torrent_id): - """Builds and returns a status dictionary using the status template""" - status = core.get_torrent_status(torrent_id) - template = core.get_torrent_status_template() - status_dict = {} - for string in template: - status_dict[string] = status[template.index(string)] - return status_dict - -def get_torrent_info_dict(core, torrent_id): - """Builds and returns an info dictionary using the info template""" +def get_torrent_info(core, torrent_id): + """Builds the info dictionary and returns it""" info = core.get_torrent_info(torrent_id) - template = core.get_torrent_info_template() - info_dict = {} - for string in template: - info_dict[string] = info[template.index(string)] - return info_dict + # Join the array of bytes into a string for pickle to read + info = "".join(chr(b) for b in info) + # De-serialize the object + info = pickle.loads(info) + return info + +def get_torrent_status(core, torrent_id): + """Builds the status dictionary and returns it""" + status = core.get_torrent_status(torrent_id) + # Join the array of bytes into a string for pickle to read + status = "".join(chr(b) for b in status) + # De-serialize the object + status = pickle.loads(status) + return status diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 7c8d8b002..985e1b921 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -157,10 +157,10 @@ class TorrentView: # This function is used for the foreach method of the treemodel def update_row(model, path, row, user_data): torrent_id = self.torrent_model.get_value(row, 0) - status = functions.get_torrent_status_dict(self.core, torrent_id) + status = functions.get_torrent_status(self.core, torrent_id) # Set values for each column in the row self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, - status["position"]+1) + status["queue"]+1) self.torrent_model.set_value(row, TORRENT_VIEW_COL_PROGRESS, status["progress"]*100) self.torrent_model.set_value(row, TORRENT_VIEW_COL_STATUS, @@ -186,13 +186,13 @@ class TorrentView: def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" # Get the status and info dictionaries - status = functions.get_torrent_status_dict(self.core, torrent_id) - info = functions.get_torrent_info_dict(self.core, torrent_id) + status = functions.get_torrent_status(self.core, torrent_id) + info = functions.get_torrent_info(self.core, torrent_id) # Insert the row with info provided from core - self.torrent_model.insert(status["position"], [ + self.torrent_model.insert(status["queue"], [ torrent_id, - status["position"]+1, + status["queue"]+1, None, info["name"], info["total_size"], From 139b5d82c799b8373b740eaa8853b1ca81972521 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 27 Jul 2007 06:35:45 +0000 Subject: [PATCH 0024/1009] updating mainwindow to current trunk --- deluge/ui/gtkui/mainwindow.py | 1301 +++++++++++++++++++++++++++++++-- 1 file changed, 1224 insertions(+), 77 deletions(-) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index ffa8b66c9..726c1ac0f 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -1,77 +1,1224 @@ -# -# mainwindow.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -import logging - -import pygtk -pygtk.require('2.0') -import gtk, gtk.glade -import gobject -import pkg_resources - -from menubar import MenuBar -from toolbar import ToolBar -from torrentview import TorrentView - -# Get the logger -log = logging.getLogger("deluge") - -class MainWindow: - def __init__(self): - # Get the glade file for the main window - self.main_glade = gtk.glade.XML( - pkg_resources.resource_filename("deluge.ui.gtkui", - "glade/main_window.glade")) - - self.window = self.main_glade.get_widget("main_window") - - # Initialize various components of the gtkui - self.menubar = MenuBar(self) - self.toolbar = ToolBar(self) - self.torrentview = TorrentView(self) - - gobject.timeout_add(1000, self.update) - - def update(self): - self.torrentview.update() - return True - - def show(self): - self.window.show_all() - - def hide(self): - self.window.hide() - - def quit(self): - self.hide() - gtk.main_quit() + + + + + + Deluge + + + + + + True + 4 + 3 + + + True + False + + + True + Add Torrent + Add + True + gtk-add + + + + False + + + + + True + False + Remove Torrent + Remove + True + gtk-remove + + + + False + + + + + True + Clear Finished Torrents + Clear + True + gtk-clear + + + + False + + + + + True + + + False + False + + + + + True + False + Start or Pause torrent + Start + True + gtk-media-play + + + + False + + + + + True + False + Queue Torrent Up + Up + True + gtk-go-up + + + + False + + + + + True + False + Queue Torrent Down + Down + True + gtk-go-down + + + + False + + + + + True + + + False + False + + + + + True + Change Deluge preferences + Preferences + True + gtk-preferences + + + + False + + + + + True + Open the plugins dialog + Plugins + True + gtk-disconnect + + + + False + + + + + 1 + 2 + GTK_FILL + + + + + True + False + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + + + 2 + 3 + 1 + 2 + + GTK_FILL + + + + + True + + + True + _File + True + + + + + True + _Add Torrent + True + + + + True + gtk-add + 1 + + + + + + + True + Add _URL + True + + + + + + True + _Clear Completed + True + + + + True + gtk-clear + 1 + + + + + + + True + + + + + True + gtk-quit + True + True + + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-preferences + True + True + + + + + + True + Pl_ugins + True + + + + True + gtk-disconnect + 1 + + + + + + + + + + + True + _Torrent + True + + + + + True + _View + True + + + True + + + True + _Toolbar + True + True + + + + + + True + _Details + True + True + + + + + + True + Columns + True + + + True + + + True + Size + True + True + + + + + + True + Status + True + True + + + + + + True + Seeders + True + True + + + + + + True + Peers + True + True + + + + + + True + Down Speed + True + True + + + + + + True + Up Speed + True + True + + + + + + True + Time Remaining + True + True + + + + + + True + Availability + True + True + + + + + + True + Share Ratio + True + True + + + + + + + + + + + + + + True + _Help + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Help translate this application + _Translate This Application... + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-edit + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + True + gtk-about + True + True + + + + + + + + + + 3 + + + + + + True + + + 3 + 3 + 4 + + + + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + + + + True + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + 1 + 2 + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 5 + 2 + 2 + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 2 + 3 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + + + + False + False + + + + + 3 + 2 + 3 + + + + + + From 6f3a57f827bf1db9b41e5b1b7cbd9bb11a146196 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 27 Jul 2007 20:17:13 +0000 Subject: [PATCH 0025/1009] Moving functions.py to UI --- deluge/ui/gtkui/functions.py | 11 ++++------- deluge/ui/gtkui/menubar.py | 5 +++-- deluge/ui/gtkui/signals.py | 2 +- deluge/ui/gtkui/toolbar.py | 2 -- deluge/ui/gtkui/torrentview.py | 2 +- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/gtkui/functions.py index 91428e24d..b48cd573c 100644 --- a/deluge/ui/gtkui/functions.py +++ b/deluge/ui/gtkui/functions.py @@ -52,9 +52,6 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade -from addtorrentdialog import AddTorrentDialog -from deluge.ui.ui import UI - # Get the logger log = logging.getLogger("deluge") @@ -69,10 +66,10 @@ def get_core(): log.debug("Got core proxy object..") return core -def add_torrent_file(): - """Opens a file chooser dialog and adds any files selected to the core""" - at_dialog = AddTorrentDialog() - torrent_files = at_dialog.run() +def add_torrent_file(torrent_files): + """Adds torrent files to the core + Expects a list of torrent files + """ if torrent_files is None: log.debug("No torrent files selected..") return diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index bc071c9bf..1106cd757 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -38,7 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import functions +import ui.functions # Get the logger log = logging.getLogger("deluge") @@ -101,7 +101,8 @@ class MenuBar: ## File Menu ## def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") - functions.add_torrent_file() + from addtorrentdialog import AddTorrentDialog + functions.add_torrent_file(AddTorrentDialog().run()) def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 6a8ba2d29..bbce3264b 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -50,7 +50,7 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade -import functions +import ui.functions from deluge.config import Config # Get the logger diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index a565c38df..38bc23b20 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -37,8 +37,6 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade -import functions - # Get the logger log = logging.getLogger("deluge") diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 985e1b921..a61c681a6 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,7 +40,7 @@ import gobject import gettext import columns -import functions +import ui.functions # Get the logger log = logging.getLogger("deluge") From 510bc309712d3a40f8ea187922e03b52ee676a91 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 27 Jul 2007 20:17:57 +0000 Subject: [PATCH 0026/1009] Moving functions.py to UI (actual file move) --- deluge/ui/{gtkui => }/functions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename deluge/ui/{gtkui => }/functions.py (100%) diff --git a/deluge/ui/gtkui/functions.py b/deluge/ui/functions.py similarity index 100% rename from deluge/ui/gtkui/functions.py rename to deluge/ui/functions.py From 8c67927974066e1bdc4a97cdda1284a145fc654e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 27 Jul 2007 20:20:38 +0000 Subject: [PATCH 0027/1009] Fix up the importing of functions --- deluge/ui/gtkui/menubar.py | 2 +- deluge/ui/gtkui/signals.py | 2 +- deluge/ui/gtkui/torrentview.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 1106cd757..59eb36b31 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -38,7 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import ui.functions +import deluge.ui.functions as functions # Get the logger log = logging.getLogger("deluge") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index bbce3264b..6ffd3f671 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -50,7 +50,7 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade -import ui.functions +import deluge.ui.functions as functions from deluge.config import Config # Get the logger diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index a61c681a6..5264d9d53 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,7 +40,7 @@ import gobject import gettext import columns -import ui.functions +import deluge.ui.functions as functions # Get the logger log = logging.getLogger("deluge") From bb299f4e97c45bbf2a3eef9f0b8d0ea63a45ca0f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 27 Jul 2007 22:16:57 +0000 Subject: [PATCH 0028/1009] Add Resume button/menuitem to the toolbar and torrent menu. --- deluge/ui/gtkui/glade/main_window.glade | 1666 +++++++++++----------- deluge/ui/gtkui/glade/torrent_menu.glade | 24 +- 2 files changed, 859 insertions(+), 831 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index e29ef7cc4..1ec630877 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -13,167 +13,708 @@ 4 3 - + True - False - + True - Add Torrent - Add Torrent - True - gtk-add - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + - False + False - + True - False - Remove Torrent - Remove Torrent - True - gtk-remove - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + - False - - - - - True - Clear Finished Torrents - Clear Finished - True - gtk-clear - - - - False - - - - - True - - - False - False - - - - - True - False - Start / Pause - Start - True - gtk-media-play - - - - False - - - - - True - False - Queue Torrent Up - Move Up - True - gtk-go-up - - - - False - - - - - True - False - Queue Torrent Down - Move Down - True - gtk-go-down - - - - False - - - - - True - - - False - False - - - - - True - Preferences - Preferences - True - gtk-preferences - - - - False - - - - - True - Plugins - Plugins - True - gtk-disconnect - - - - False + False + False - 1 - 2 - GTK_FILL - - - - - True - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - False - - - 2 3 - 1 - 2 - - GTK_FILL + 2 + 3 + + + + + True + + + 3 + 3 + 4 + @@ -349,712 +890,179 @@ - + True + False + 2 3 - 3 - 4 - + 1 + 2 + + GTK_FILL - + True + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - + Add Torrent + Add Torrent + True + gtk-add + - True - False + False - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - 1 - 2 - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 4 - 4 - 5 - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - + False + Remove Torrent + Remove Torrent + True + gtk-remove + - False - False + False + + + + + True + Clear Finished Torrents + Clear Finished + True + gtk-clear + + + + False + + + + + True + + + False + False + + + + + True + False + Start / Pause + Pause + True + gtk-media-pause + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Resume + gtk-media-play + + + + False + + + + + True + False + Queue Torrent Up + Move Up + True + gtk-go-up + + + + False + + + + + True + False + Queue Torrent Down + Move Down + True + gtk-go-down + + + + False + + + + + True + + + False + False + + + + + True + Preferences + Preferences + True + gtk-preferences + + + + False + + + + + True + Plugins + Plugins + True + gtk-disconnect + + + + False - 3 - 2 - 3 + 1 + 2 + GTK_FILL diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index a14b823af..f6006fd3c 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -8,10 +8,30 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-media-pause + _Pause True - True + + + gtk-media-pause + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Resume selected torrents. + Resu_me + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-media-play + + From bcf70c3e0ff7d5a23065f8a6e05140ebf1e8efd7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 28 Jul 2007 18:48:32 +0000 Subject: [PATCH 0029/1009] get_torrent_status() now takes a list of keys and only returns the values for those keys. --- deluge/core/core.py | 18 +- deluge/core/torrent.py | 31 +- deluge/core/torrentmanager.py | 5 +- deluge/ui/functions.py | 4 +- deluge/ui/gtkui/glade/main_window.glade | 1668 +++++++++++----------- deluge/ui/gtkui/glade/torrent_menu.glade | 1 + deluge/ui/gtkui/torrentview.py | 20 +- 7 files changed, 882 insertions(+), 865 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 7e2d79654..e862df16d 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -129,19 +129,15 @@ class Core(dbus.service.Object): self.torrent_paused(torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="ay") - def get_torrent_info(self, torrent_id): - # Pickle the info dictionary from the torrent and return it - info = self.torrents[torrent_id].get_info() - info = pickle.dumps(info) - return info - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", + in_signature="sas", out_signature="ay") - def get_torrent_status(self, torrent_id): + def get_torrent_status(self, torrent_id, keys): + # Convert the array of strings to a python list of strings + nkeys = [] + for key in keys: + nkeys.append(str(key)) # Pickle the status dictionary from the torrent and return it - status = self.torrents[torrent_id].get_status() + status = self.torrents[torrent_id].get_status(nkeys) status = pickle.dumps(status) return status diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 27e7f675d..e0673d16e 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -71,21 +71,15 @@ class Torrent: return eta - def get_info(self): - """Returns the torrents info.. stuff that remains constant, such as - name.""" - - return { - "name": self.handle.torrent_info().name(), - "total_size": self.handle.torrent_info().total_size(), - "num_pieces": self.handle.status().num_pieces - } - - def get_status(self): - """Returns the torrent status""" + def get_status(self, keys): + """Returns the status of the torrent based on the keys provided""" + # Create the full dictionary status = self.handle.status() - return { + full_status = { + "name": self.handle.torrent_info().name(), + "total_size": self.handle.torrent_info().total_size(), + "num_pieces": self.handle.status().num_pieces, "state": int(status.state), "paused": status.paused, "progress": status.progress, @@ -98,5 +92,14 @@ class Torrent: "num_seeds": status.num_seeds, "total_wanted": status.total_wanted, "eta": self.get_eta(), - "queue": self.queue[self.torrent_id] + "queue": self.queue[self.torrent_id] } + + # Create the desired status dictionary and return it + status_dict = {} + + for key in keys: + if key in full_status: + status_dict[key] = full_status[key] + + return status_dict diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index af125e287..f28403a29 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -132,7 +132,10 @@ class TorrentManager: except: return False - return True + return True + + def resume(self, torrent_id): + pass def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index b48cd573c..0c594da9a 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -134,9 +134,9 @@ def get_torrent_info(core, torrent_id): info = pickle.loads(info) return info -def get_torrent_status(core, torrent_id): +def get_torrent_status(core, torrent_id, keys): """Builds the status dictionary and returns it""" - status = core.get_torrent_status(torrent_id) + status = core.get_torrent_status(torrent_id, keys) # Join the array of bytes into a string for pickle to read status = "".join(chr(b) for b in status) # De-serialize the object diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 1ec630877..5bf073b36 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -13,708 +13,186 @@ 4 3 - + True + False - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - + Add Torrent + Add Torrent + True + gtk-add + - False + False - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - 1 - 2 - 10 - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 4 - 4 - 5 - - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - + False + Remove Torrent + Remove Torrent + True + gtk-remove + - False - False + False + + + + + True + Clear Finished Torrents + Clear Finished + True + gtk-clear + + + + False + + + + + True + + + False + False + + + + + True + False + Start / Pause + Pause + True + gtk-media-pause + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Resume + gtk-media-play + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + + + + + True + Queue Torrent Up + Move Up + True + gtk-go-up + + + + False + + + + + True + Queue Torrent Down + Move Down + True + gtk-go-down + + + + False + + + + + True + + + False + False + + + + + True + Preferences + Preferences + True + gtk-preferences + + + + False + + + + + True + Plugins + Plugins + True + gtk-disconnect + + + + False - 3 - 2 - 3 + 1 + 2 + GTK_FILL - + True + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + + + 2 3 - 3 - 4 - + 1 + 2 + + GTK_FILL @@ -890,179 +368,709 @@ - + True - False - 2 3 - 1 - 2 - - GTK_FILL + 3 + 4 + - + True - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - False - - True - Add Torrent - Add Torrent - True - gtk-add - - - - False - - - - - True - False - Remove Torrent - Remove Torrent - True - gtk-remove - - - - False - - - - - True - Clear Finished Torrents - Clear Finished - True - gtk-clear - - - - False - - - - - True - - - False - False - - - - - True - False - Start / Pause - Pause - True - gtk-media-pause - - - - False - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Resume - gtk-media-play - + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + - False + False + False - + True - False - Queue Torrent Up - Move Up - True - gtk-go-up - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + - False - - - - - True - False - Queue Torrent Down - Move Down - True - gtk-go-down - - - - False - - - - - True - - - False - False - - - - - True - Preferences - Preferences - True - gtk-preferences - - - - False - - - - - True - Plugins - Plugins - True - gtk-disconnect - - - - False + False + False - 1 - 2 - GTK_FILL + 3 + 2 + 3 diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index f6006fd3c..774cc3716 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -25,6 +25,7 @@ Resume selected torrents. Resu_me True + True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 5264d9d53..98bf7b119 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -153,11 +153,15 @@ class TorrentView: def update(self): """Update the view, this is likely called by a timer""" - # This function is used for the foreach method of the treemodel def update_row(model, path, row, user_data): torrent_id = self.torrent_model.get_value(row, 0) - status = functions.get_torrent_status(self.core, torrent_id) + status_keys = ["queue", "progress", "state", "num_seeds", + "num_peers", "download_payload_rate", "upload_payload_rate", + "eta"] + status = functions.get_torrent_status(self.core, torrent_id, + status_keys) + # Set values for each column in the row self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, status["queue"]+1) @@ -186,16 +190,18 @@ class TorrentView: def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" # Get the status and info dictionaries - status = functions.get_torrent_status(self.core, torrent_id) - info = functions.get_torrent_info(self.core, torrent_id) - + status_keys = ["queue", "name", "total_size", "progress", "state", + "num_seeds", "num_peers", "download_payload_rate", + "upload_payload_rate", "eta"] + status = functions.get_torrent_status(self.core, torrent_id, + status_keys) # Insert the row with info provided from core self.torrent_model.insert(status["queue"], [ torrent_id, status["queue"]+1, None, - info["name"], - info["total_size"], + status["name"], + status["total_size"], status["progress"]*100, status["state"], status["num_seeds"], From 7cd0ba0a6c49cb724c6fbbe94231ebe682cc38e2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 2 Aug 2007 18:52:33 +0000 Subject: [PATCH 0030/1009] Updates and revert mainwindow.py from last change --- deluge/core/core.py | 11 + deluge/core/torrentmanager.py | 10 +- deluge/ui/functions.py | 6 + deluge/ui/gtkui/glade/main_window.glade | 1686 +++++++++++------------ deluge/ui/gtkui/mainwindow.py | 1301 ++--------------- deluge/ui/gtkui/menubar.py | 6 + deluge/ui/gtkui/toolbar.py | 6 + 7 files changed, 957 insertions(+), 2069 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index e862df16d..faf24f07a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -127,6 +127,13 @@ class Core(dbus.service.Object): log.debug("Pausing torrent %s", torrent_id) if self.torrents.pause(torrent_id): self.torrent_paused(torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def resume_torrent(self, torrent_id): + log.debug("Resuming torrent %s", torrent_id) + if self.torrents.resume(torrent_id): + self.torrent_resumed(torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="sas", @@ -200,3 +207,7 @@ class Core(dbus.service.Object): def torrent_paused(self, torrent_id): """Emitted when a torrent is paused""" log.debug("torrent_paused signal emitted") + + def torrent_resumed(self, torrent_id): + """Emitted when a torrent is resumed""" + log.debug("torrent_resumed signal emitted") diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index f28403a29..07115cdff 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -135,8 +135,14 @@ class TorrentManager: return True def resume(self, torrent_id): - pass - + """Resume a torrent""" + try: + self.torrents[torrent_id].handle.resume() + except: + return False + + return True + def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" state = TorrentManagerState() diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 0c594da9a..6f228be6d 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -97,6 +97,12 @@ def pause_torrent(torrent_ids): for torrent_id in torrent_ids: core.pause_torrent(torrent_id) +def resume_torrent(torrent_ids): + """Resume torrent_ids""" + core = get_core() + for torrent_id in torrent_ids: + core.resume_torrent(torrent_id) + def queue_top(torrent_ids): """Attempts to queue all torrent_ids to the top""" log.debug("Attempting to queue to top these torrents: %s", torrent_ids) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 5bf073b36..f5472e8a9 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -13,186 +13,709 @@ 4 3 - + True - False - - True - Add Torrent - Add Torrent - True - gtk-add - - - - False - - - - - True - False - Remove Torrent - Remove Torrent - True - gtk-remove - - - - False - - - - - True - Clear Finished Torrents - Clear Finished - True - gtk-clear - - - - False - - - - - True - - - False - False - - - - - True - False - Start / Pause - Pause - True - gtk-media-pause - - - - False - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Resume - gtk-media-play - + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + - False + False + False - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + - False - - - - - True - Queue Torrent Up - Move Up - True - gtk-go-up - - - - False - - - - - True - Queue Torrent Down - Move Down - True - gtk-go-down - - - - False - - - - - True - - - False - False - - - - - True - Preferences - Preferences - True - gtk-preferences - - - - False - - - - - True - Plugins - Plugins - True - gtk-disconnect - - - - False + False + False - 1 - 2 - GTK_FILL - - - - - True - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - False - - - 2 3 - 1 - 2 - - GTK_FILL + 2 + 3 + + + + + True + + + 3 + 3 + 4 + @@ -368,709 +891,186 @@ - + True + False + 2 3 - 3 - 4 - + 1 + 2 + + GTK_FILL - + True + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - + Add torrent + Add Torrent + True + gtk-add + - False - False + False - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - 1 - 2 - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 4 - 4 - 5 - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - + Remove the selected torrents + Remove Torrent + True + gtk-remove + - False - False + False + + + + + True + Remove the finished torrents + Clear Finished + True + gtk-clear + + + + False + + + + + True + + + False + False + + + + + True + Pause the selected torrents + Pause + True + gtk-media-pause + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Resume the selected torrents + Resume + gtk-media-play + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + False + + + + + True + Queue the selected torrents up + Move Up + True + gtk-go-up + + + + False + + + + + True + Queue the selected torrents down + Move Down + True + gtk-go-down + + + + False + + + + + True + + + False + False + + + + + True + Preferences + Preferences + True + gtk-preferences + + + + False + + + + + True + Plugins + Plugins + True + gtk-disconnect + + + + False - 3 - 2 - 3 + 1 + 2 + GTK_FILL diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 726c1ac0f..ffa8b66c9 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -1,1224 +1,77 @@ - - - - - - Deluge - - - - - - True - 4 - 3 - - - True - False - - - True - Add Torrent - Add - True - gtk-add - - - - False - - - - - True - False - Remove Torrent - Remove - True - gtk-remove - - - - False - - - - - True - Clear Finished Torrents - Clear - True - gtk-clear - - - - False - - - - - True - - - False - False - - - - - True - False - Start or Pause torrent - Start - True - gtk-media-play - - - - False - - - - - True - False - Queue Torrent Up - Up - True - gtk-go-up - - - - False - - - - - True - False - Queue Torrent Down - Down - True - gtk-go-down - - - - False - - - - - True - - - False - False - - - - - True - Change Deluge preferences - Preferences - True - gtk-preferences - - - - False - - - - - True - Open the plugins dialog - Plugins - True - gtk-disconnect - - - - False - - - - - 1 - 2 - GTK_FILL - - - - - True - False - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - False - - - 2 - 3 - 1 - 2 - - GTK_FILL - - - - - True - - - True - _File - True - - - - - True - _Add Torrent - True - - - - True - gtk-add - 1 - - - - - - - True - Add _URL - True - - - - - - True - _Clear Completed - True - - - - True - gtk-clear - 1 - - - - - - - True - - - - - True - gtk-quit - True - True - - - - - - - - - - True - _Edit - True - - - True - - - True - gtk-preferences - True - True - - - - - - True - Pl_ugins - True - - - - True - gtk-disconnect - 1 - - - - - - - - - - - True - _Torrent - True - - - - - True - _View - True - - - True - - - True - _Toolbar - True - True - - - - - - True - _Details - True - True - - - - - - True - Columns - True - - - True - - - True - Size - True - True - - - - - - True - Status - True - True - - - - - - True - Seeders - True - True - - - - - - True - Peers - True - True - - - - - - True - Down Speed - True - True - - - - - - True - Up Speed - True - True - - - - - - True - Time Remaining - True - True - - - - - - True - Availability - True - True - - - - - - True - Share Ratio - True - True - - - - - - - - - - - - - - True - _Help - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Help translate this application - _Translate This Application... - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-edit - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - True - gtk-about - True - True - - - - - - - - - - 3 - - - - - - True - - - 3 - 3 - 4 - - - - - - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - - - - True - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - 1 - 2 - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 5 - 2 - 2 - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 2 - 3 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - - - - False - False - - - - - 3 - 2 - 3 - - - - - - +# +# mainwindow.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import gobject +import pkg_resources + +from menubar import MenuBar +from toolbar import ToolBar +from torrentview import TorrentView + +# Get the logger +log = logging.getLogger("deluge") + +class MainWindow: + def __init__(self): + # Get the glade file for the main window + self.main_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/main_window.glade")) + + self.window = self.main_glade.get_widget("main_window") + + # Initialize various components of the gtkui + self.menubar = MenuBar(self) + self.toolbar = ToolBar(self) + self.torrentview = TorrentView(self) + + gobject.timeout_add(1000, self.update) + + def update(self): + self.torrentview.update() + return True + + def show(self): + self.window.show_all() + + def hide(self): + self.window.hide() + + def quit(self): + self.hide() + gtk.main_quit() diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 59eb36b31..f9812fb91 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -82,6 +82,7 @@ class MenuBar: self.torrentmenu.signal_autoconnect({ ## Torrent Menu "on_menuitem_pause_activate": self.on_menuitem_pause_activate, + "on_menuitem_resume_activate": self.on_menuitem_resume_activate, "on_menuitem_updatetracker_activate": \ self.on_menuitem_updatetracker_activate, "on_menuitem_edittrackers_activate": \ @@ -126,6 +127,11 @@ class MenuBar: log.debug("on_menuitem_pause_activate") functions.pause_torrent( self.window.torrentview.get_selected_torrents()) + + def on_menuitem_resume_activate(self, data=None): + log.debug("on_menuitem_resume_activate") + functions.resume_torrent( + self.window.torrentview.get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 38bc23b20..872046119 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -51,6 +51,7 @@ class ToolBar: "on_toolbutton_remove_clicked": self.on_toolbutton_remove_clicked, "on_toolbutton_clear_clicked": self.on_toolbutton_clear_clicked, "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked, + "on_toolbutton_resume_clicked": self.on_toolbutton_resume_clicked, "on_toolbutton_queueup_clicked": \ self.on_toolbutton_queueup_clicked, "on_toolbutton_queuedown_clicked": \ @@ -81,6 +82,11 @@ class ToolBar: log.debug("on_toolbutton_pause_clicked") # Use the menubar's callbacks self.window.menubar.on_menuitem_pause_activate(data) + + def on_toolbutton_resume_clicked(self, data): + log.debug("on_toolbutton_resume_clicked") + # Use the menubar's calbacks + self.window.menubar.on_menuitem_resume_activate(data) def on_toolbutton_queueup_clicked(self, data): log.debug("on_toolbutton_queueup_clicked") From 78e225526c82a81c101dd3dfc7ed2aa096a0a184 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 5 Aug 2007 20:03:00 +0000 Subject: [PATCH 0031/1009] 'Hack' in plugin support. This is totally wrong, but it sort of works. --- deluge/core/core.py | 7 ++- deluge/core/pluginmanager.py | 63 ++++++++++++++++++++++++++ deluge/plugins/queue/queue/__init__.py | 39 ++++++++++++++++ deluge/plugins/queue/setup.py | 49 ++++++++++++++++++++ setup.py | 11 ++++- 5 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 deluge/core/pluginmanager.py create mode 100644 deluge/plugins/queue/queue/__init__.py create mode 100644 deluge/plugins/queue/setup.py diff --git a/deluge/core/core.py b/deluge/core/core.py index faf24f07a..c9db26f69 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -50,10 +50,12 @@ else: dbus_imported = True import gobject import deluge.libtorrent as lt +import pkg_resources from deluge.config import Config import deluge.common from deluge.core.torrentmanager import TorrentManager +from deluge.core.pluginmanager import PluginManager # Get the logger log = logging.getLogger("deluge") @@ -87,7 +89,10 @@ class Core(dbus.service.Object): # Start the TorrentManager self.torrents = TorrentManager(self.session) - + + # Load plugins + self.plugins = PluginManager() + log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py new file mode 100644 index 000000000..3fb9eef4e --- /dev/null +++ b/deluge/core/pluginmanager.py @@ -0,0 +1,63 @@ +# +# pluginmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging +import os.path + +import pkg_resources + +# Get the logger +log = logging.getLogger("deluge") + +class PluginManager: + def __init__(self): + # This will load any .eggs in the plugins folder inside the main + # deluge egg.. Need to scan the local plugin folder too. + + plugin_dir = os.path.join(os.path.dirname(__file__), "..", "plugins") + + pkg_resources.working_set.add_entry(plugin_dir) + pkg_env = pkg_resources.Environment([plugin_dir]) + + self.plugins = {} + for name in pkg_env: + egg = pkg_env[name][0] + egg.activate() + modules = [] + for name in egg.get_entry_map("deluge.plugin"): + entry_point = egg.get_entry_info("deluge.plugin", name) + cls = entry_point.load() + instance = cls() + self.plugins[name] = instance + + log.info("Plugins loaded: %s", self.plugins) diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py new file mode 100644 index 000000000..dea9791ae --- /dev/null +++ b/deluge/plugins/queue/queue/__init__.py @@ -0,0 +1,39 @@ +# +# __init__.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +class QueuePlugin: + def __init__(self): + print "queue plugin init!" + + def test(self): + print "queue plugin test!" diff --git a/deluge/plugins/queue/setup.py b/deluge/plugins/queue/setup.py new file mode 100644 index 000000000..681dc02b4 --- /dev/null +++ b/deluge/plugins/queue/setup.py @@ -0,0 +1,49 @@ +# setup.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +""" +Allow torrents to be queued in a specific order +""" + +from setuptools import setup + +__author__ = "Andrew Resch" + +setup( + name="Queue", + version="1.0", + description=__doc__, + author=__author__, + packages=["queue"], + entry_points=""" + [deluge.plugin] + Queue = queue:QueuePlugin + """ +) diff --git a/setup.py b/setup.py index 4f14bfcba..bb19020dd 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ ez_setup.use_setuptools() from setuptools import setup, find_packages, Extension import platform import glob +import os python_version = platform.python_version()[0:3] @@ -83,6 +84,11 @@ libtorrent = Extension( sources = _sources ) +# Build the plugin eggs +for path in glob.glob('deluge/plugins/*'): + print path + "/setup.py" + os.system("cd " + path + "&& python setup.py bdist_egg -d ..") + # Main setup setup( @@ -100,11 +106,12 @@ setup( include_package_data = True, package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png", - "ui/gtkui/po/*.po?" + "ui/gtkui/po/*.po?", + "plugins/*.egg", ]}, ext_package = "deluge", ext_modules = [libtorrent], - packages = find_packages(), + packages = find_packages(exclude=["plugins"]), entry_points = """ [console_scripts] deluge = deluge.main:main From 13fc181fa2e8360b3aafd9f03cdf0768660fbf3b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 10 Aug 2007 08:15:02 +0000 Subject: [PATCH 0032/1009] More work on the queue torrent plugin and plugins in general. --- deluge/core/core.py | 41 +- deluge/core/pluginmanager.py | 36 +- deluge/core/torrent.py | 10 +- deluge/core/torrentmanager.py | 8 +- deluge/core/torrentqueue.py | 136 -- deluge/plugins/queue/queue/__init__.py | 117 +- deluge/plugins/queue/setup.py | 4 +- deluge/ui/functions.py | 11 +- deluge/ui/gtkui/glade/main_window.glade | 1507 +++++++++++------------ deluge/ui/gtkui/torrentview.py | 31 +- 10 files changed, 896 insertions(+), 1005 deletions(-) delete mode 100644 deluge/core/torrentqueue.py diff --git a/deluge/core/core.py b/deluge/core/core.py index c9db26f69..e8922fa79 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -112,6 +112,10 @@ class Core(dbus.service.Object): """ log.info("Adding torrent: %s", filename) torrent_id = self.torrents.add(filename, filedump) + + # Run the plugin hooks for 'post_torrent_add' + self.plugins.run_post_torrent_add(torrent_id) + if torrent_id is not None: # Emit the torrent_added signal self.torrent_added(torrent_id) @@ -123,6 +127,8 @@ class Core(dbus.service.Object): def remove_torrent(self, torrent_id): log.debug("Removing torrent %s from the core.", torrent_id) if self.torrents.remove(torrent_id): + # Run the plugin hooks for 'post_torrent_remove' + self.plugins.run_post_torrent_remove(torrent_id) # Emit the torrent_removed signal self.torrent_removed(torrent_id) @@ -153,35 +159,6 @@ class Core(dbus.service.Object): status = pickle.dumps(status) return status - ## Queueing functions ###### - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def queue_top(self, torrent_id): - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.top(torrent_id): - self.torrent_queue_changed() - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def queue_up(self, torrent_id): - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.up(torrent_id): - self.torrent_queue_changed() - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def queue_down(self, torrent_id): - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.down(torrent_id): - self.torrent_queue_changed() - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def queue_bottom(self, torrent_id): - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.bottom(torrent_id): - self.torrent_queue_changed() - # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") @@ -201,12 +178,6 @@ class Core(dbus.service.Object): """Emitted when a torrent has been removed from the core""" log.debug("torrent_remove signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="") - def torrent_queue_changed(self): - """Emitted when a torrent queue position is changed""" - log.debug("torrent_queue_changed signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") def torrent_paused(self, torrent_id): diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 3fb9eef4e..d3885b53d 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -41,6 +41,9 @@ log = logging.getLogger("deluge") class PluginManager: def __init__(self): + # Set up the hooks dictionary + self.hooks = {"post_torrent_add": []} + # This will load any .eggs in the plugins folder inside the main # deluge egg.. Need to scan the local plugin folder too. @@ -54,10 +57,35 @@ class PluginManager: egg = pkg_env[name][0] egg.activate() modules = [] - for name in egg.get_entry_map("deluge.plugin"): - entry_point = egg.get_entry_info("deluge.plugin", name) + for name in egg.get_entry_map("deluge.plugin.core"): + entry_point = egg.get_entry_info("deluge.plugin.core", name) cls = entry_point.load() - instance = cls() + instance = cls(self) self.plugins[name] = instance + log.info("Load plugin %s", name) + + def __getitem__(self, key): + return self.plugins[key] - log.info("Plugins loaded: %s", self.plugins) + def register_hook(self, hook, function): + """Register a hook function with the plugin manager""" + try: + self.hooks[hook].append(function) + except KeyError: + log.warning("Plugin attempting to register invalid hook.") + + def run_post_torrent_add(self, torrent_id): + log.debug("run_post_torrent_add") + try: + for function in self.hooks["post_torrent_add"]: + function(torrent_id) + except: + pass + + def run_post_torrent_remove(self, torrent_id): + log.debug("run_post_torrent_remove") + try: + for function in self.hooks["post_torrent_remove"]: + function(torrent_id) + except: + pass diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index e0673d16e..2197fc668 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -39,19 +39,14 @@ import deluge.libtorrent as lt log = logging.getLogger("deluge") class Torrent: - def __init__(self, filename, handle, queue): + def __init__(self, filename, handle): # Set the filename self.filename = filename # Set the libtorrent handle self.handle = handle - # Set the queue this torrent belongs too - self.queue = queue # Set the torrent_id for this torrent self.torrent_id = str(handle.info_hash()) - def __del__(self): - self.queue.remove(self.torrent_id) - def get_state(self): """Returns the state of this torrent for saving to the session state""" return (self.torrent_id, self.filename) @@ -91,8 +86,7 @@ class Torrent: "num_peers": status.num_peers, "num_seeds": status.num_seeds, "total_wanted": status.total_wanted, - "eta": self.get_eta(), - "queue": self.queue[self.torrent_id] + "eta": self.get_eta() } # Create the desired status dictionary and return it diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 07115cdff..da3ea17e4 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -40,7 +40,6 @@ import deluge.libtorrent as lt import deluge.common from deluge.config import Config from deluge.core.torrent import Torrent -from deluge.core.torrentqueue import TorrentQueue from deluge.core.torrentmanagerstate import TorrentManagerState, TorrentState # Get the logger @@ -53,7 +52,6 @@ class TorrentManager: self.session = session # Create the torrents dict { torrent_id: Torrent } self.torrents = {} - self.queue = TorrentQueue() def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" @@ -103,11 +101,9 @@ class TorrentManager: log.warning("Unable to save torrent file: %s", filename) # Create a Torrent object - torrent = Torrent(filename, handle, self.queue) + torrent = Torrent(filename, handle) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent - # Add the torrent to the queue - self.queue.append(torrent.torrent_id) return torrent.torrent_id def remove(self, torrent_id): @@ -146,8 +142,6 @@ class TorrentManager: def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" state = TorrentManagerState() - # Grab the queue from TorrentQueue - state.queue = self.queue.queue # Create the state for each Torrent and append to the list for (key, torrent) in self.torrents: t = TorrentState(torrent.get_state()) diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py deleted file mode 100644 index 5694f592e..000000000 --- a/deluge/core/torrentqueue.py +++ /dev/null @@ -1,136 +0,0 @@ -# -# torrentqueue.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -import logging - -# Get the logger -log = logging.getLogger("deluge") - -class TorrentQueue: - def __init__(self): - log.debug("TorrentQueue init..") - self.queue = [] - - def __getitem__(self, torrent_id): - """Return the queue position of the torrent_id""" - return self.queue.index(torrent_id) - - def append(self, torrent_id): - """Append torrent_id to the bottom of the queue""" - log.debug("Append torrent %s to queue..", torrent_id) - self.queue.append(torrent_id) - - def prepend(self, torrent_id): - """Prepend torrent_id to the top of the queue""" - log.debug("Prepend torrent %s to queue..", torrent_id) - self.queue.insert(0, torrent_id) - - def remove(self, torrent_id): - """Removes torrent_id from the list""" - self.queue.remove(torrent_id) - - def up(self, torrent_id): - """Move torrent_id up one in the queue""" - if torrent_id not in self.queue: - # Raise KeyError if the torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s up..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue up if torrent is already at top - if index is 0: - return False - - # Pop and insert the torrent_id at index - 1 - self.queue.insert(index - 1, self.queue.pop(index)) - - return True - - def top(self, torrent_id): - """Move torrent_id to top of the queue""" - if torrent_id not in self.queue: - # Raise KeyError if the torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s to top..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue up if torrent is already at top - if index is 0: - return False - - # Pop and prepend the torrent_id - self.prepend(self.queue.pop(index)) - - return True - - def down(self, torrent_id): - """Move torrent_id down one in the queue""" - if torrent_id not in self.queue: - # Raise KeyError if torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s down..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue down of torrent_id is at bottom - if index is len(self.queue) - 1: - return False - - # Pop and insert the torrent_id at index + 1 - self.queue.insert(index + 1, self.queue.pop(index)) - - return True - - def bottom(self, torrent_id): - """Move torrent_id to bottom of the queue""" - if torrent_id not in self.queue: - # Raise KeyError if torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s to bottom..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue down of torrent_id is at bottom - if index is len(self.queue) - 1: - return False - - # Pop and append the torrent_id - self.append(self.queue.pop(index)) - - return True diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py index dea9791ae..0c219b53f 100644 --- a/deluge/plugins/queue/queue/__init__.py +++ b/deluge/plugins/queue/queue/__init__.py @@ -31,9 +31,116 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -class QueuePlugin: - def __init__(self): - print "queue plugin init!" +import logging + +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True + +from torrentqueue import TorrentQueue + +# Get the logger +log = logging.getLogger("deluge") + +class QueueCorePlugin(dbus.service.Object): + def __init__(self, plugin, path="/org/deluge_torrent/Plugin/Queue"): + # Get the pluginmanager reference + self.plugin = plugin + + # Setup DBUS + bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", + bus=dbus.SessionBus()) + + dbus.service.Object.__init__(self, bus_name, path) + + # Instantiate the TorrentQueue object + self.queue = TorrentQueue() + + # Register core hooks + self.plugin.register_hook("post_torrent_add", self.post_torrent_add) + self.plugin.register_hook("post_torrent_remove", + self.post_torrent_remove) + + log.info("Queue plugin initialized..") - def test(self): - print "queue plugin test!" + ## Hooks for core ## + def post_torrent_add(self, torrent_id): + if torrent_id is not None: + self.queue.append(torrent_id) + + def post_torrent_remove(self, torrent_id): + if torrent_id is not None: + self.queue.remove(torrent_id) + + ## Queueing functions ## + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_top(self, torrent_id): + log.debug("Attempting to queue %s to top", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.top(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_up(self, torrent_id): + log.debug("Attempting to queue %s to up", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.up(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_down(self, torrent_id): + log.debug("Attempting to queue %s to down", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.down(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_bottom(self, torrent_id): + log.debug("Attempting to queue %s to bottom", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.bottom(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="", out_signature="as") + def get_queue(self): + """Returns the queue list. + """ + log.debug("Getting queue list") + return self.queue.queue + + ## Signals ## + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge.Queue", + signature="") + def torrent_queue_changed(self): + """Emitted when a torrent queue position is changed""" + log.debug("torrent_queue_changed signal emitted") diff --git a/deluge/plugins/queue/setup.py b/deluge/plugins/queue/setup.py index 681dc02b4..77f9669b9 100644 --- a/deluge/plugins/queue/setup.py +++ b/deluge/plugins/queue/setup.py @@ -43,7 +43,7 @@ setup( author=__author__, packages=["queue"], entry_points=""" - [deluge.plugin] - Queue = queue:QueuePlugin + [deluge.plugin.core] + Queue = queue:QueueCorePlugin """ ) diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 6f228be6d..089585ea5 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -66,6 +66,15 @@ def get_core(): log.debug("Got core proxy object..") return core +def get_core_plugin(plugin): + """Get the core plugin object and return it""" + log.debug("Getting core plugin %s from DBUS..", plugin) + bus = dbus.SessionBus() + proxy = bus.get_object("org.deluge_torrent.Deluge", + "/org/deluge_torrent/Plugin/" + plugin) + core = dbus.Interface(proxy, "org.deluge_torrent.Deluge." + plugin) + return core + def add_torrent_file(torrent_files): """Adds torrent files to the core Expects a list of torrent files @@ -106,7 +115,7 @@ def resume_torrent(torrent_ids): def queue_top(torrent_ids): """Attempts to queue all torrent_ids to the top""" log.debug("Attempting to queue to top these torrents: %s", torrent_ids) - core = get_core() + core = get_core_plugin("Queue") for torrent_id in torrent_ids: core.queue_top(torrent_id) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index f5472e8a9..fd79fb914 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -8,716 +8,9 @@ - + True - 4 - 3 - - - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - - - - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - 1 - 2 - 10 - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 4 - 4 - 5 - - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - - - - - False - - - - - True - Details - - - tab - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - - - - - - False - False - - - - - 3 - 2 - 3 - - - - - True - - - 3 - 3 - 4 - - - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True @@ -727,7 +20,7 @@ _File True - + True @@ -748,7 +41,6 @@ True Add _URL True - @@ -756,9 +48,8 @@ True _Clear Completed True - - + True gtk-clear 1 @@ -767,7 +58,7 @@ - + True @@ -790,7 +81,7 @@ _Edit True - + True @@ -798,7 +89,6 @@ gtk-preferences True True - @@ -806,9 +96,8 @@ True Pl_ugins True - - + True gtk-disconnect 1 @@ -833,7 +122,7 @@ _View True - + True @@ -841,7 +130,6 @@ _Toolbar True True - @@ -850,7 +138,6 @@ _Details True True - @@ -870,14 +157,13 @@ _Help True - + True gtk-about True True - @@ -886,39 +172,11 @@ - 3 - + False - - True - False - - - 2 - 3 - 1 - 2 - - GTK_FILL - - - - - True - - - 1 - 2 - 1 - 2 - GTK_FILL - GTK_FILL - - - - + True False @@ -995,42 +253,6 @@ False - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - False - - - - - True - Queue the selected torrents up - Move Up - True - gtk-go-up - - - - False - - - - - True - Queue the selected torrents down - Move Down - True - gtk-go-down - - - - False - - True @@ -1068,9 +290,710 @@ - 1 - 2 - GTK_FILL + False + 1 + + + + + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + True + False + + + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + False + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + 1 + 2 + 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 4 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + + + + + False + + + + + True + Details + + + tab + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + + + + + 1 + False + + + + + True + Peers + + + tab + 1 + False + False + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + + + + + 2 + False + + + + + True + Files + + + tab + 2 + False + False + + + + + + + False + False + + + + + 2 + + + + + True + + + False + 3 diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 98bf7b119..52beff1d8 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -47,7 +47,7 @@ log = logging.getLogger("deluge") # Initializes the columns for the torrent_view (TORRENT_VIEW_COL_UID, -TORRENT_VIEW_COL_QUEUE, +#TORRENT_VIEW_COL_QUEUE, TORRENT_VIEW_COL_STATUSICON, TORRENT_VIEW_COL_NAME, TORRENT_VIEW_COL_SIZE, @@ -60,7 +60,7 @@ TORRENT_VIEW_COL_PEERS, TORRENT_VIEW_COL_DOWNLOAD, TORRENT_VIEW_COL_UPLOAD, TORRENT_VIEW_COL_ETA, -TORRENT_VIEW_COL_RATIO) = range(15) +TORRENT_VIEW_COL_RATIO) = range(14) class TorrentView: def __init__(self, window): @@ -71,9 +71,9 @@ class TorrentView: self.torrent_view = self.window.main_glade.get_widget("torrent_view") ## TreeModel setup ## - # UID, Q#, Status Icon, Name, Size, Progress, Message, Seeders, Peers, + # UID, Status Icon, Name, Size, Progress, Message, Seeders, Peers, # DL, UL, ETA, Share - self.torrent_model = gtk.ListStore(str, int, gtk.gdk.Pixbuf, str, + self.torrent_model = gtk.ListStore(str, gtk.gdk.Pixbuf, str, long, float, str, int, int, int, int, int, int, int, float) ## TreeView setup ## @@ -82,9 +82,9 @@ class TorrentView: self.torrent_view.set_reorderable(True) self.torrent_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - self.queue_column = columns.add_text_column( - self.torrent_view, "#", - TORRENT_VIEW_COL_QUEUE) + # self.queue_column = columns.add_text_column( + # self.torrent_view, "#", + # TORRENT_VIEW_COL_QUEUE) self.name_column = columns.add_texticon_column( self.torrent_view, _("Name"), @@ -138,8 +138,8 @@ class TorrentView: self.peer_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_PEERS) # Set the default sort column to the queue column - self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, - gtk.SORT_ASCENDING) + # self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, + # gtk.SORT_ASCENDING) ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the @@ -156,15 +156,15 @@ class TorrentView: # This function is used for the foreach method of the treemodel def update_row(model, path, row, user_data): torrent_id = self.torrent_model.get_value(row, 0) - status_keys = ["queue", "progress", "state", "num_seeds", + status_keys = ["progress", "state", "num_seeds", "num_peers", "download_payload_rate", "upload_payload_rate", "eta"] status = functions.get_torrent_status(self.core, torrent_id, status_keys) # Set values for each column in the row - self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, - status["queue"]+1) + # self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, + # status["queue"]+1) self.torrent_model.set_value(row, TORRENT_VIEW_COL_PROGRESS, status["progress"]*100) self.torrent_model.set_value(row, TORRENT_VIEW_COL_STATUS, @@ -190,15 +190,16 @@ class TorrentView: def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" # Get the status and info dictionaries - status_keys = ["queue", "name", "total_size", "progress", "state", + status_keys = ["name", "total_size", "progress", "state", "num_seeds", "num_peers", "download_payload_rate", "upload_payload_rate", "eta"] status = functions.get_torrent_status(self.core, torrent_id, status_keys) # Insert the row with info provided from core - self.torrent_model.insert(status["queue"], [ + #self.torrent_model.insert(status["queue"], [ + self.torrent_model.append([ torrent_id, - status["queue"]+1, + # status["queue"]+1, None, status["name"], status["total_size"], From 895f7e2761ad87c1bc618033b1029c188e6365ec Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 11 Aug 2007 06:46:41 +0000 Subject: [PATCH 0033/1009] Add missing files. Start of plugin support in gtkui. --- deluge/common.py | 4 + deluge/core/core.py | 3 +- deluge/core/pluginmanager.py | 5 +- deluge/core/torrentmanagerstate.py | 46 +++++++ deluge/plugins/queue/queue/__init__.py | 116 ++-------------- deluge/plugins/queue/queue/core.py | 146 +++++++++++++++++++++ deluge/plugins/queue/queue/gtkui.py | 41 ++++++ deluge/plugins/queue/queue/torrentqueue.py | 136 +++++++++++++++++++ deluge/plugins/queue/setup.py | 4 +- deluge/ui/gtkui/gtkui.py | 4 + 10 files changed, 396 insertions(+), 109 deletions(-) create mode 100644 deluge/core/torrentmanagerstate.py create mode 100644 deluge/plugins/queue/queue/core.py create mode 100644 deluge/plugins/queue/queue/gtkui.py create mode 100644 deluge/plugins/queue/queue/torrentqueue.py diff --git a/deluge/common.py b/deluge/common.py index 4daa2aeef..2d51b48ad 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -62,6 +62,10 @@ def get_default_download_dir(): def get_default_torrent_dir(): """Returns the default torrent directory""" return os.path.join(get_config_dir(), "torrentfiles") + +def get_default_plugin_dir(): + """Returns the default plugin directory""" + return os.path.join(get_config_dir(), "plugins") ## Formatting text functions diff --git a/deluge/core/core.py b/deluge/core/core.py index e8922fa79..bb84e7081 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -64,7 +64,8 @@ DEFAULT_PREFS = { "compact_allocation": True, "download_location": deluge.common.get_default_download_dir(), "listen_ports": [6881, 6891], - "torrentfiles_location": deluge.common.get_default_torrent_dir() + "torrentfiles_location": deluge.common.get_default_torrent_dir(), + "plugins_location": deluge.common.get_default_plugin_dir() } class Core(dbus.service.Object): diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index d3885b53d..21bc5d114 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -42,7 +42,10 @@ log = logging.getLogger("deluge") class PluginManager: def __init__(self): # Set up the hooks dictionary - self.hooks = {"post_torrent_add": []} + self.hooks = { + "post_torrent_add": [], + "post_torrent_remove": [] + } # This will load any .eggs in the plugins folder inside the main # deluge egg.. Need to scan the local plugin folder too. diff --git a/deluge/core/torrentmanagerstate.py b/deluge/core/torrentmanagerstate.py new file mode 100644 index 000000000..55c3d96ce --- /dev/null +++ b/deluge/core/torrentmanagerstate.py @@ -0,0 +1,46 @@ +# +# torrentmanagerstate.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +# Get the logger +log = logging.getLogger("deluge") + +class TorrentState: + def __init__(self, torrent_id, filename): + self.torrent_id = torrent_id + self.filename = filename + +class TorrentManagerState: + def __init__(self): + self.torrents = [] diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py index 0c219b53f..d5e50baad 100644 --- a/deluge/plugins/queue/queue/__init__.py +++ b/deluge/plugins/queue/queue/__init__.py @@ -33,114 +33,18 @@ import logging -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True - -from torrentqueue import TorrentQueue +from core import Core +from gtkui import GtkUI # Get the logger log = logging.getLogger("deluge") -class QueueCorePlugin(dbus.service.Object): - def __init__(self, plugin, path="/org/deluge_torrent/Plugin/Queue"): - # Get the pluginmanager reference - self.plugin = plugin +class CorePlugin: + def __init__(self, plugin_manager): + # Load the Core portion of the plugin + self.core = Core(plugin_manager) - # Setup DBUS - bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", - bus=dbus.SessionBus()) - - dbus.service.Object.__init__(self, bus_name, path) - - # Instantiate the TorrentQueue object - self.queue = TorrentQueue() - - # Register core hooks - self.plugin.register_hook("post_torrent_add", self.post_torrent_add) - self.plugin.register_hook("post_torrent_remove", - self.post_torrent_remove) - - log.info("Queue plugin initialized..") - - ## Hooks for core ## - def post_torrent_add(self, torrent_id): - if torrent_id is not None: - self.queue.append(torrent_id) - - def post_torrent_remove(self, torrent_id): - if torrent_id is not None: - self.queue.remove(torrent_id) - - ## Queueing functions ## - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_top(self, torrent_id): - log.debug("Attempting to queue %s to top", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.queue.top(torrent_id): - self.torrent_queue_changed() - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_up(self, torrent_id): - log.debug("Attempting to queue %s to up", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.queue.up(torrent_id): - self.torrent_queue_changed() - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_down(self, torrent_id): - log.debug("Attempting to queue %s to down", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.queue.down(torrent_id): - self.torrent_queue_changed() - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_bottom(self, torrent_id): - log.debug("Attempting to queue %s to bottom", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.queue.bottom(torrent_id): - self.torrent_queue_changed() - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="", out_signature="as") - def get_queue(self): - """Returns the queue list. - """ - log.debug("Getting queue list") - return self.queue.queue - - ## Signals ## - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge.Queue", - signature="") - def torrent_queue_changed(self): - """Emitted when a torrent queue position is changed""" - log.debug("torrent_queue_changed signal emitted") +class GtkUIPlugin: + def __init__(self, plugin_manager): + # Load the GtkUI portion of the plugin + self.gtkui = GtkUI(plugin_manager) diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py new file mode 100644 index 000000000..09936e1f1 --- /dev/null +++ b/deluge/plugins/queue/queue/core.py @@ -0,0 +1,146 @@ +# +# core.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True + +from torrentqueue import TorrentQueue + +# Get the logger +log = logging.getLogger("deluge") + +class Core(dbus.service.Object): + def __init__(self, plugin, path="/org/deluge_torrent/Plugin/Queue"): + # Get the pluginmanager reference + self.plugin = plugin + + # Setup DBUS + bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", + bus=dbus.SessionBus()) + + dbus.service.Object.__init__(self, bus_name, path) + + # Instantiate the TorrentQueue object + self.queue = TorrentQueue() + + # Register core hooks + self.plugin.register_hook("post_torrent_add", self.post_torrent_add) + self.plugin.register_hook("post_torrent_remove", + self.post_torrent_remove) + + log.info("Queue Core plugin initialized..") + + ## Hooks for core ## + def post_torrent_add(self, torrent_id): + if torrent_id is not None: + self.queue.append(torrent_id) + + def post_torrent_remove(self, torrent_id): + if torrent_id is not None: + self.queue.remove(torrent_id) + + ## Queueing functions ## + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_top(self, torrent_id): + log.debug("Attempting to queue %s to top", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.top(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_up(self, torrent_id): + log.debug("Attempting to queue %s to up", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.up(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_down(self, torrent_id): + log.debug("Attempting to queue %s to down", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.down(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="") + def queue_bottom(self, torrent_id): + log.debug("Attempting to queue %s to bottom", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.queue.bottom(torrent_id): + self.torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="", out_signature="as") + def get_queue(self): + """Returns the queue list. + """ + log.debug("Getting queue list") + return self.queue.queue + + ## Signals ## + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge.Queue", + signature="") + def torrent_queue_changed(self): + """Emitted when a torrent queue position is changed""" + log.debug("torrent_queue_changed signal emitted") diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py new file mode 100644 index 000000000..be44a0ac4 --- /dev/null +++ b/deluge/plugins/queue/queue/gtkui.py @@ -0,0 +1,41 @@ +# +# core.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +# Get the logger +log = logging.getLogger("deluge") + +class GtkUI: + def __init__(self, plugin_manager): + log.debug("Queue GtkUI plugin initalized..") diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py new file mode 100644 index 000000000..5ce3a2ed8 --- /dev/null +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -0,0 +1,136 @@ +# +# torrentqueue.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +# Get the logger +log = logging.getLogger("deluge") + +class TorrentQueue: + def __init__(self): + self.queue = [] + + def __getitem__(self, torrent_id): + """Return the queue position of the torrent_id""" + return self.queue.index(torrent_id) + + def append(self, torrent_id): + """Append torrent_id to the bottom of the queue""" + log.debug("Append torrent %s to queue..", torrent_id) + self.queue.append(torrent_id) + + def prepend(self, torrent_id): + """Prepend torrent_id to the top of the queue""" + log.debug("Prepend torrent %s to queue..", torrent_id) + self.queue.insert(0, torrent_id) + + def remove(self, torrent_id): + """Removes torrent_id from the list""" + log.debug("Remove torrent %s from queue..", torrent_id) + self.queue.remove(torrent_id) + + def up(self, torrent_id): + """Move torrent_id up one in the queue""" + if torrent_id not in self.queue: + # Raise KeyError if the torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s up..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue up if torrent is already at top + if index is 0: + return False + + # Pop and insert the torrent_id at index - 1 + self.queue.insert(index - 1, self.queue.pop(index)) + + return True + + def top(self, torrent_id): + """Move torrent_id to top of the queue""" + if torrent_id not in self.queue: + # Raise KeyError if the torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s to top..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue up if torrent is already at top + if index is 0: + return False + + # Pop and prepend the torrent_id + self.prepend(self.queue.pop(index)) + + return True + + def down(self, torrent_id): + """Move torrent_id down one in the queue""" + if torrent_id not in self.queue: + # Raise KeyError if torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s down..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue down of torrent_id is at bottom + if index is len(self.queue) - 1: + return False + + # Pop and insert the torrent_id at index + 1 + self.queue.insert(index + 1, self.queue.pop(index)) + + return True + + def bottom(self, torrent_id): + """Move torrent_id to bottom of the queue""" + if torrent_id not in self.queue: + # Raise KeyError if torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s to bottom..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue down of torrent_id is at bottom + if index is len(self.queue) - 1: + return False + + # Pop and append the torrent_id + self.append(self.queue.pop(index)) + + return True diff --git a/deluge/plugins/queue/setup.py b/deluge/plugins/queue/setup.py index 77f9669b9..fe58e2380 100644 --- a/deluge/plugins/queue/setup.py +++ b/deluge/plugins/queue/setup.py @@ -44,6 +44,8 @@ setup( packages=["queue"], entry_points=""" [deluge.plugin.core] - Queue = queue:QueueCorePlugin + Queue = queue:CorePlugin + [deluge.plugin.ui.gtk] + Queue = queue:GtkUIPlugin """ ) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index ee1f34e4e..2a4e83635 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -41,6 +41,7 @@ import pkg_resources from mainwindow import MainWindow from signals import Signals +from pluginmanager import PluginManager # Get the logger log = logging.getLogger("deluge") @@ -64,6 +65,9 @@ class GtkUI: # Start the signal receiver self.signal_receiver = Signals(self) + # Initalize the plugins + self.plugins = PluginManager() + # Show the main window self.main_window.show() From 873b5397ebb352fe3bc5403f1ab9f311eb70faf0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 11 Aug 2007 06:49:18 +0000 Subject: [PATCH 0034/1009] Add missing file. --- deluge/plugins/queue/queue/gtkui.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 69 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 deluge/ui/gtkui/pluginmanager.py diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index be44a0ac4..f243fbbb5 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -1,5 +1,5 @@ # -# core.py +# gtkui.py # # Copyright (C) 2007 Andrew Resch ('andar') # diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py new file mode 100644 index 000000000..6ea4cf9a7 --- /dev/null +++ b/deluge/ui/gtkui/pluginmanager.py @@ -0,0 +1,69 @@ +# +# pluginmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging +import os.path + +import pkg_resources + +# Get the logger +log = logging.getLogger("deluge") + +class PluginManager: + def __init__(self): + # Set up the hooks dictionary + self.hooks = { + } + + # This will load any .eggs in the plugins folder inside the main + # deluge egg.. Need to scan the local plugin folder too. + + plugin_dir = os.path.join(os.path.dirname(__file__), "../..", "plugins") + + pkg_resources.working_set.add_entry(plugin_dir) + pkg_env = pkg_resources.Environment([plugin_dir]) + + self.plugins = {} + for name in pkg_env: + egg = pkg_env[name][0] + egg.activate() + modules = [] + for name in egg.get_entry_map("deluge.plugin.ui.gtk"): + entry_point = egg.get_entry_info("deluge.plugin.ui.gtk", name) + cls = entry_point.load() + instance = cls(self) + self.plugins[name] = instance + log.info("Loaded plugin %s", name) + + def __getitem__(self, key): + return self.plugins[key] From e13b42203bad6871946f03a26090fdeb8f1e8758 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 12 Aug 2007 07:01:27 +0000 Subject: [PATCH 0035/1009] Start of move to generic ListView. Not really functional yet. --- deluge/core/pluginmanager.py | 4 + deluge/plugins/queue/queue/gtkui.py | 4 + deluge/ui/gtkui/gtkui.py | 6 +- deluge/ui/gtkui/listview.py | 241 ++++++++++++++++++++++++++++ deluge/ui/gtkui/pluginmanager.py | 11 +- deluge/ui/gtkui/signals.py | 13 +- deluge/ui/gtkui/torrentview.py | 216 +++++-------------------- 7 files changed, 300 insertions(+), 195 deletions(-) create mode 100644 deluge/ui/gtkui/listview.py diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 21bc5d114..b40bc5522 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -76,7 +76,11 @@ class PluginManager: self.hooks[hook].append(function) except KeyError: log.warning("Plugin attempting to register invalid hook.") + + def run_hook(self, hook, data): + log.debug("Running hook %s", hook) + def run_post_torrent_add(self, torrent_id): log.debug("run_post_torrent_add") try: diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index f243fbbb5..e9f6839de 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -39,3 +39,7 @@ log = logging.getLogger("deluge") class GtkUI: def __init__(self, plugin_manager): log.debug("Queue GtkUI plugin initalized..") + self.plugin = plugin_manager + # Get the torrentview component from the plugin manager + self.torrentview = self.plugin.get_torrentview() + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 2a4e83635..3d405706e 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -60,16 +60,16 @@ class GtkUI: "po")) # Initialize the main window - self.main_window = MainWindow() + self.mainwindow = MainWindow() # Start the signal receiver self.signal_receiver = Signals(self) # Initalize the plugins - self.plugins = PluginManager() + self.plugins = PluginManager(self) # Show the main window - self.main_window.show() + self.mainwindow.show() # Start the gtk main loop gtk.main() diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py new file mode 100644 index 000000000..ca03fe61e --- /dev/null +++ b/deluge/ui/gtkui/listview.py @@ -0,0 +1,241 @@ +# +# listview.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import logging + +import pygtk +pygtk.require('2.0') +import gtk +import gettext + +# Get the logger +log = logging.getLogger("deluge") + +# Cell data functions to pass to add_func_column() + +def cell_data_speed(column, cell, model, iter, data): + speed = int(model.get_value(iter, data)) + speed_str = deluge.common.fspeed(speed) + cell.set_property('text', speed_str) + +def cell_data_size(column, cell, model, iter, data): + size = long(model.get_value(iter, data)) + size_str = deluge.common.fsize(size) + cell.set_property('text', size_str) + +def cell_data_peer(column, cell, model, iter, data): + c1, c2 = data + a = int(model.get_value(iter, c1)) + b = int(model.get_value(iter, c2)) + cell.set_property('text', '%d (%d)'%(a, b)) + +def cell_data_time(column, cell, model, iter, data): + time = int(model.get_value(iter, data)) + if time < 0 or time == 0: + time_str = _("Infinity") + else: + time_str = deluge.common.ftime(time) + cell.set_property('text', time_str) + +def cell_data_ratio(column, cell, model, iter, data): + ratio = float(model.get_value(iter, data)) + if ratio == -1: + ratio_str = _("Unknown") + else: + ratio_str = "%.3f"%ratio + cell.set_property('text', ratio_str) + +class ListView: + class ListViewColumn: + def __init__(self, name, column_indices): + self.name = name + # self.column_types = column_types + self.column_indices = column_indices + + def __init__(self, treeview_widget=None): + log.debug("ListView initialized..") + + if treeview_widget is not None: + # User supplied a treeview widget + self.treeview = treeview_widget + else: + self.treeview = gtk.TreeView() + + self.liststore = None + + self.treeview.set_model(self.liststore) + self.treeview.set_rules_hint(True) + self.treeview.set_reorderable(True) + self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + self.columns = {} + self.liststore_columns = [] + + def create_new_liststore(self): + # Create a new liststore with added column and move the data from the + # old one to the new one. + new_list = gtk.ListStore(*tuple(self.liststore_columns)) + + # This function is used in the liststore.foreach method with user_data + # being the new liststore and the columns list + def copy_row(model, path, row, user_data): + new_list, columns = user_data + # Iterate over the columns except the last one. This one would have + # been just added and no need to copy it from the old list. + for column in range(model.get_n_columns()): + # Get the current value of the column for this row + value = model.get_value(row, column) + # Set the value of this row and column in the new liststore + new_list.set_value(row, column, value) + + # Do the actual row copy + if self.liststore is not None: + self.liststore.foreach(copy_row, (new_list, self.columns)) + + self.liststore = new_list + self.treeview.set_model(self.liststore) + + return + + def add_text_column(self, header, visible=True): + # Create a new column object and add it to the list + self.liststore_columns.append(str) + self.columns[header] = self.ListViewColumn(header, + [len(self.liststore_columns) - 1]) + + # Create a new list with the added column + self.create_new_liststore() + + # Now add the column to the treeview so the user can see it + render = gtk.CellRendererText() + column = gtk.TreeViewColumn(header, render, + text=self.columns[header].column_indices[0]) + column.set_clickable(True) + column.set_sort_column_id(self.columns[header].column_indices[0]) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + column.set_visible(visible) + self.treeview.append_column(column) + + return True + + def add_func_column(self, header, function, column_types, sortid=0): + # Add the new column types to the list and keep track of the liststore + # columns that this column object uses. + # Set sortid to the column index relative the to column_types used. + # Default is 0. + + column_indices = [] + for column_type in column_types: + self.liststore_columns.append(column_type) + column_indices.append(len(self.liststore_columns) - 1) + + # Create a new column object and add it to the list + self.columns[header] = self.ListViewColumn(header, column_indices) + + # Create new list with the added columns + self.create_new_liststore() + + column = gtk.TreeViewColumn(header) + render = gtk.CellRendererText() + column.pack_start(render, True) + column.set_cell_data_func(render, function, + tuple(self.columns[header].column_indices)) + column.set_clickable(True) + column.set_sort_column_id(column_indices[sortid]) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + self.treeview.append_column(column) + + return True + + def add_progress_column(self, header): + # For the progress value + self.liststore_columns.append(float) + # For the text + self.liststore_columns.append(str) + column_indices = [len(self.liststore_columns) - 2, + len(self.liststore_columns) - 1] + # Create a new column object and add it to the list + self.columns[header] = self.ListViewColumn(header, column_indices) + + # Create new list with the added columns + self.create_new_liststore() + + render = gtk.CellRendererProgress() + column = gtk.TreeViewColumn(header, render, + value=self.columns[header].column_indices[0], + text=self.columns[header].column_indices[1]) + column.set_clickable(True) + column.set_sort_column_id(self.columns[header].column_indices[0]) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + self.treeview.append_column(column) + + return True + + def add_texticon_column(self, header): + # For icon + self.liststore_columns.append(gtk.gdk.Pixbuf) + # For text + self.liststore_columns.append(str) + column_indices = [len(self.liststore_columns) - 2, + len(self.liststore_columns) - 1] + self.columns[header] = self.ListViewColumn(header, column_indices) + + # Create new list with the added columns + self.create_new_liststore() + + column = gtk.TreeViewColumn(header) + column.set_clickable(True) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + render = gtk.CellRendererPixbuf() + column.pack_start(render, expand=False) + column.add_attribute(render, 'pixbuf', + self.columns[header].column_indices[0]) + render = gtk.CellRendererText() + column.pack_start(render, expand=True) + column.add_attribute(render, 'text', + self.columns[header].column_indices[1]) + self.treeview.append_column(column) + + return True diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 6ea4cf9a7..527d0cd12 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -40,10 +40,9 @@ import pkg_resources log = logging.getLogger("deluge") class PluginManager: - def __init__(self): - # Set up the hooks dictionary - self.hooks = { - } + def __init__(self, gtkui): + + self._gtkui = gtkui # This will load any .eggs in the plugins folder inside the main # deluge egg.. Need to scan the local plugin folder too. @@ -67,3 +66,7 @@ class PluginManager: def __getitem__(self, key): return self.plugins[key] + + def get_torrentview(self): + """Returns a reference to the torrentview component""" + return self._gtkui.mainwindow.torrentview diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 6ffd3f671..1d97ac4dd 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -63,27 +63,20 @@ class Signals: self.core.connect_to_signal("torrent_added", self.torrent_added_signal) self.core.connect_to_signal("torrent_removed", self.torrent_removed_signal) - self.core.connect_to_signal("torrent_queue_changed", - self.torrent_queue_changed_signal) self.core.connect_to_signal("torrent_paused", self.torrent_paused) def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) # Add the torrent to the treeview - self.ui.main_window.torrentview.add_row(torrent_id) + self.ui.mainwindow.torrentview.add_row(torrent_id) def torrent_removed_signal(self, torrent_id): log.debug("torrent_remove signal received..") log.debug("torrent id: %s", torrent_id) # Remove the torrent from the treeview - self.ui.main_window.torrentview.remove_row(torrent_id) - - def torrent_queue_changed_signal(self): - log.debug("torrent_queue_changed signal received..") - # Force an update of the torrent view - self.ui.main_window.torrentview.update() + self.ui.mainwindow.torrentview.remove_row(torrent_id) def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") - self.ui.main_window.torrentview.update() + self.ui.mainwindow.torrentview.update() diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 52beff1d8..a44b6645c 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -39,208 +39,68 @@ import gtk, gtk.glade import gobject import gettext -import columns import deluge.ui.functions as functions +import listview # Get the logger log = logging.getLogger("deluge") -# Initializes the columns for the torrent_view -(TORRENT_VIEW_COL_UID, -#TORRENT_VIEW_COL_QUEUE, -TORRENT_VIEW_COL_STATUSICON, -TORRENT_VIEW_COL_NAME, -TORRENT_VIEW_COL_SIZE, -TORRENT_VIEW_COL_PROGRESS, -TORRENT_VIEW_COL_STATUS, -TORRENT_VIEW_COL_CONNECTED_SEEDS, -TORRENT_VIEW_COL_SEEDS, -TORRENT_VIEW_COL_CONNECTED_PEERS, -TORRENT_VIEW_COL_PEERS, -TORRENT_VIEW_COL_DOWNLOAD, -TORRENT_VIEW_COL_UPLOAD, -TORRENT_VIEW_COL_ETA, -TORRENT_VIEW_COL_RATIO) = range(14) - class TorrentView: def __init__(self, window): log.debug("TorrentView Init..") self.window = window self.core = functions.get_core() - # Get the torrent_view widget - self.torrent_view = self.window.main_glade.get_widget("torrent_view") - - ## TreeModel setup ## - # UID, Status Icon, Name, Size, Progress, Message, Seeders, Peers, - # DL, UL, ETA, Share - self.torrent_model = gtk.ListStore(str, gtk.gdk.Pixbuf, str, - long, float, str, int, int, int, int, int, int, int, float) - - ## TreeView setup ## - self.torrent_view.set_model(self.torrent_model) - self.torrent_view.set_rules_hint(True) - self.torrent_view.set_reorderable(True) - self.torrent_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - # self.queue_column = columns.add_text_column( - # self.torrent_view, "#", - # TORRENT_VIEW_COL_QUEUE) - self.name_column = columns.add_texticon_column( - self.torrent_view, - _("Name"), - TORRENT_VIEW_COL_STATUSICON, - TORRENT_VIEW_COL_NAME) - self.size_column = columns.add_func_column( - self.torrent_view, - _("Size"), - columns.cell_data_size, - TORRENT_VIEW_COL_SIZE) - self.progress_column = columns.add_progress_column( - self.torrent_view, - _("Progress"), - TORRENT_VIEW_COL_PROGRESS, - TORRENT_VIEW_COL_STATUS) - self.seed_column = columns.add_func_column( - self.torrent_view, - _("Seeders"), - columns.cell_data_peer, - (TORRENT_VIEW_COL_CONNECTED_SEEDS, TORRENT_VIEW_COL_SEEDS)) - self.peer_column = columns.add_func_column( - self.torrent_view, - _("Peers"), - columns.cell_data_peer, - (TORRENT_VIEW_COL_CONNECTED_PEERS, TORRENT_VIEW_COL_PEERS)) - self.dl_column = columns.add_func_column( - self.torrent_view, - _("Down Speed"), - columns.cell_data_speed, - TORRENT_VIEW_COL_DOWNLOAD) - self.ul_column = columns.add_func_column( - self.torrent_view, - _("Up Speed"), - columns.cell_data_speed, - TORRENT_VIEW_COL_UPLOAD) - self.eta_column = columns.add_func_column( - self.torrent_view, - _("ETA"), - columns.cell_data_time, - TORRENT_VIEW_COL_ETA) - self.share_column = columns.add_func_column( - self.torrent_view, - _("Ratio"), - columns.cell_data_ratio, - TORRENT_VIEW_COL_RATIO) - - # Set some column settings - self.progress_column.set_expand(True) - self.name_column.set_sort_column_id(TORRENT_VIEW_COL_NAME) - self.seed_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_SEEDS) - self.peer_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_PEERS) - - # Set the default sort column to the queue column - # self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, - # gtk.SORT_ASCENDING) - + # Create a ListView object using the torrent_view from the mainwindow + self.torrent_view = listview.ListView( + self.window.main_glade.get_widget("torrent_view")) + + self.torrent_view.add_text_column("torrent_id", visible=False) + self.torrent_view.add_texticon_column("Name") + self.torrent_view.add_func_column("Size", + listview.cell_data_size, + [long]) + self.torrent_view.add_progress_column("Progress") + self.torrent_view.add_func_column("Seeders", + listview.cell_data_peer, + [int, int]) + self.torrent_view.add_func_column("Peers", + listview.cell_data_peer, + [int, int]) + self.torrent_view.add_func_column("Down Speed", + listview.cell_data_speed, + [int]) + self.torrent_view.add_func_column("Up Speed", + listview.cell_data_speed, + [int]) + self.torrent_view.add_func_column("ETA", + listview.cell_data_time, + [int]) + self.torrent_view.add_func_column("Ratio", + listview.cell_data_ratio, + [float]) + ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the # torrent menu popup. - self.torrent_view.connect("button-press-event", + self.torrent_view.treeview.connect("button-press-event", self.on_button_press_event) # Connect to the 'changed' event of TreeViewSelection to get selection # changes. - self.torrent_view.get_selection().connect("changed", + self.torrent_view.treeview.get_selection().connect("changed", self.on_selection_changed) def update(self): - """Update the view, this is likely called by a timer""" - # This function is used for the foreach method of the treemodel - def update_row(model, path, row, user_data): - torrent_id = self.torrent_model.get_value(row, 0) - status_keys = ["progress", "state", "num_seeds", - "num_peers", "download_payload_rate", "upload_payload_rate", - "eta"] - status = functions.get_torrent_status(self.core, torrent_id, - status_keys) - - # Set values for each column in the row - # self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, - # status["queue"]+1) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_PROGRESS, - status["progress"]*100) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_STATUS, - status["state"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_CONNECTED_SEEDS, - status["num_seeds"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_SEEDS, - status["num_seeds"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_CONNECTED_PEERS, - status["num_peers"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_PEERS, - status["num_peers"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_DOWNLOAD, - status["download_payload_rate"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_UPLOAD, - status["upload_payload_rate"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_ETA, - status["eta"]) - - # Iterates through every row and updates them accordingly - self.torrent_model.foreach(update_row, None) + pass def add_row(self, torrent_id): - """Adds a new torrent row to the treeview""" - # Get the status and info dictionaries - status_keys = ["name", "total_size", "progress", "state", - "num_seeds", "num_peers", "download_payload_rate", - "upload_payload_rate", "eta"] - status = functions.get_torrent_status(self.core, torrent_id, - status_keys) - # Insert the row with info provided from core - #self.torrent_model.insert(status["queue"], [ - self.torrent_model.append([ - torrent_id, - # status["queue"]+1, - None, - status["name"], - status["total_size"], - status["progress"]*100, - status["state"], - status["num_seeds"], - status["num_seeds"], - status["num_peers"], - status["num_peers"], - status["download_payload_rate"], - status["upload_payload_rate"], - status["eta"], - 0.0 - ]) + pass def remove_row(self, torrent_id): - """Removes a row with torrent_id""" - row = self.torrent_model.get_iter_first() - while row is not None: - # Check if this row is the row we want to remove - if self.torrent_model.get_value(row, 0) == torrent_id: - self.torrent_model.remove(row) - # Force an update of the torrentview - self.update() - break - row = self.torrent_model.iter_next(row) - + pass + def get_selected_torrents(self): - """Returns a list of selected torrents or None""" - torrent_ids = [] - paths = self.torrent_view.get_selection().get_selected_rows()[1] - - try: - for path in paths: - torrent_ids.append( - self.torrent_model.get_value( - self.torrent_model.get_iter(path), 0)) - return torrent_ids - except ValueError: - return None - + pass + ### Callbacks ### def on_button_press_event(self, widget, event): log.debug("on_button_press_event") From ac29983651d877ffbeb2b4e5496fc7a5f280a737 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 12 Aug 2007 09:48:34 +0000 Subject: [PATCH 0036/1009] ListView still needs some improvements, but it is working now. Removed columns.py as this has been merged with listview.py. --- deluge/ui/gtkui/columns.py | 153 --------------------------------- deluge/ui/gtkui/listview.py | 28 ++++-- deluge/ui/gtkui/torrentview.py | 134 +++++++++++++++++++++++------ 3 files changed, 131 insertions(+), 184 deletions(-) delete mode 100644 deluge/ui/gtkui/columns.py diff --git a/deluge/ui/gtkui/columns.py b/deluge/ui/gtkui/columns.py deleted file mode 100644 index d47cd3c5a..000000000 --- a/deluge/ui/gtkui/columns.py +++ /dev/null @@ -1,153 +0,0 @@ -# -# columns.py -# -# Copyright (C) 2006 Zach Tibbitts ('zachtib') -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -import pygtk -pygtk.require('2.0') -import gtk - -import deluge.common - -# Cell data functions to pass to add_func_column() - -def cell_data_speed(column, cell, model, iter, data): - speed = int(model.get_value(iter, data)) - speed_str = deluge.common.fspeed(speed) - cell.set_property('text', speed_str) - -def cell_data_size(column, cell, model, iter, data): - size = long(model.get_value(iter, data)) - size_str = deluge.common.fsize(size) - cell.set_property('text', size_str) - -def cell_data_peer(column, cell, model, iter, data): - c1, c2 = data - a = int(model.get_value(iter, c1)) - b = int(model.get_value(iter, c2)) - cell.set_property('text', '%d (%d)'%(a, b)) - -def cell_data_time(column, cell, model, iter, data): - time = int(model.get_value(iter, data)) - if time < 0 or time == 0: - time_str = _("Infinity") - else: - time_str = deluge.common.ftime(time) - cell.set_property('text', time_str) - -def cell_data_ratio(column, cell, model, iter, data): - ratio = float(model.get_value(iter, data)) - if ratio == -1: - ratio_str = _("Unknown") - else: - ratio_str = "%.3f"%ratio - cell.set_property('text', ratio_str) - -## Functions to create columns - -def add_func_column(view, header, func, data, sortid=None): - column = gtk.TreeViewColumn(header) - render = gtk.CellRendererText() - column.pack_start(render, True) - column.set_cell_data_func(render, func, data) - if sortid is not None: - column.set_clickable(True) - column.set_sort_column_id(sortid) - else: - try: - if len(data) == 1: - column.set_clickable(True) - column.set_sort_column_id(data[0]) - except TypeError: - column.set_clickable(True) - column.set_sort_column_id(data) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - view.append_column(column) - return column - - -def add_text_column(view, header, cid): - render = gtk.CellRendererText() - column = gtk.TreeViewColumn(header, render, text=cid) - column.set_clickable(True) - column.set_sort_column_id(cid) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - view.append_column(column) - return column - -def add_progress_column(view, header, pid, mid): - render = gtk.CellRendererProgress() - column = gtk.TreeViewColumn(header, render, value=pid, text=mid) - column.set_clickable(True) - column.set_sort_column_id(pid) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - view.append_column(column) - return column - -def add_toggle_column(view, header, cid, toggled_signal=None): - render = gtk.CellRendererToggle() - render.set_property('activatable', True) - column = gtk.TreeViewColumn(header, render, active=cid) - column.set_clickable(True) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - view.append_column(column) - if toggled_signal is not None: - render.connect("toggled", toggled_signal) - return column - -def add_texticon_column(view, header, icon_col, text_col): - column = gtk.TreeViewColumn(header) - column.set_clickable(True) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - render = gtk.CellRendererPixbuf() - column.pack_start(render, expand=False) - column.add_attribute(render, 'pixbuf', icon_col) - render = gtk.CellRendererText() - column.pack_start(render, expand=True) - column.add_attribute(render, 'text', text_col) - view.append_column(column) - return column diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index ca03fe61e..253d81bd3 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -38,6 +38,8 @@ pygtk.require('2.0') import gtk import gettext +import deluge.common + # Get the logger log = logging.getLogger("deluge") @@ -49,9 +51,9 @@ def cell_data_speed(column, cell, model, iter, data): cell.set_property('text', speed_str) def cell_data_size(column, cell, model, iter, data): - size = long(model.get_value(iter, data)) - size_str = deluge.common.fsize(size) - cell.set_property('text', size_str) + size = long(model.get_value(iter, data)) + size_str = deluge.common.fsize(size) + cell.set_property('text', size_str) def cell_data_peer(column, cell, model, iter, data): c1, c2 = data @@ -79,7 +81,6 @@ class ListView: class ListViewColumn: def __init__(self, name, column_indices): self.name = name - # self.column_types = column_types self.column_indices = column_indices def __init__(self, treeview_widget=None): @@ -101,6 +102,17 @@ class ListView: self.columns = {} self.liststore_columns = [] + def set_treeview(self, treeview_widget): + self.treeview = treeview_widget + return + + def get_column_index(self, name): + # Only return as list if needed + if len(self.columns[name].column_indices) > 1: + return self.columns[name].column_indices + else: + return self.columns[name].column_indices[0] + def create_new_liststore(self): # Create a new liststore with added column and move the data from the # old one to the new one. @@ -110,8 +122,6 @@ class ListView: # being the new liststore and the columns list def copy_row(model, path, row, user_data): new_list, columns = user_data - # Iterate over the columns except the last one. This one would have - # been just added and no need to copy it from the old list. for column in range(model.get_n_columns()): # Get the current value of the column for this row value = model.get_value(row, column) @@ -171,8 +181,12 @@ class ListView: column = gtk.TreeViewColumn(header) render = gtk.CellRendererText() column.pack_start(render, True) - column.set_cell_data_func(render, function, + if len(self.columns[header].column_indices) > 1: + column.set_cell_data_func(render, function, tuple(self.columns[header].column_indices)) + else: + column.set_cell_data_func(render, function, + self.columns[header].column_indices[0]) column.set_clickable(True) column.set_sort_column_id(column_indices[sortid]) column.set_resizable(True) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index a44b6645c..eb58085a2 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -45,61 +45,147 @@ import listview # Get the logger log = logging.getLogger("deluge") -class TorrentView: +class TorrentView(listview.ListView): def __init__(self, window): + # Call the ListView constructor + listview.ListView.__init__(self) log.debug("TorrentView Init..") self.window = window self.core = functions.get_core() - # Create a ListView object using the torrent_view from the mainwindow - self.torrent_view = listview.ListView( - self.window.main_glade.get_widget("torrent_view")) + # Set the treeview used in listview with the one from our glade file + self.set_treeview(self.window.main_glade.get_widget("torrent_view")) - self.torrent_view.add_text_column("torrent_id", visible=False) - self.torrent_view.add_texticon_column("Name") - self.torrent_view.add_func_column("Size", + self.add_text_column("torrent_id", visible=False) + self.add_texticon_column("Name") + self.add_func_column("Size", listview.cell_data_size, [long]) - self.torrent_view.add_progress_column("Progress") - self.torrent_view.add_func_column("Seeders", + self.add_progress_column("Progress") + self.add_func_column("Seeders", listview.cell_data_peer, [int, int]) - self.torrent_view.add_func_column("Peers", + self.add_func_column("Peers", listview.cell_data_peer, [int, int]) - self.torrent_view.add_func_column("Down Speed", + self.add_func_column("Down Speed", listview.cell_data_speed, - [int]) - self.torrent_view.add_func_column("Up Speed", + [float]) + self.add_func_column("Up Speed", listview.cell_data_speed, - [int]) - self.torrent_view.add_func_column("ETA", + [float]) + self.add_func_column("ETA", listview.cell_data_time, [int]) - self.torrent_view.add_func_column("Ratio", + self.add_func_column("Ratio", listview.cell_data_ratio, [float]) - + ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the # torrent menu popup. - self.torrent_view.treeview.connect("button-press-event", + self.treeview.connect("button-press-event", self.on_button_press_event) # Connect to the 'changed' event of TreeViewSelection to get selection # changes. - self.torrent_view.treeview.get_selection().connect("changed", + self.treeview.get_selection().connect("changed", self.on_selection_changed) def update(self): - pass + """Update the view, this is likely called by a timer""" + # This function is used for the foreach method of the treemodel + def update_row(model, path, row, user_data): + torrent_id = self.liststore.get_value(row, 0) + status_keys = ["progress", "state", "num_seeds", + "num_peers", "download_payload_rate", "upload_payload_rate", + "eta"] + status = functions.get_torrent_status(self.core, torrent_id, + status_keys) + + # Set values for each column in the row + + self.liststore.set_value(row, + self.get_column_index("Progress")[0], + status["progress"]*100) + self.liststore.set_value(row, + self.get_column_index("Progress")[1], + status["state"]) + self.liststore.set_value(row, + self.get_column_index("Seeders")[0], + status["num_seeds"]) + self.liststore.set_value(row, + self.get_column_index("Seeders")[1], + status["num_seeds"]) + self.liststore.set_value(row, + self.get_column_index("Peers")[0], + status["num_peers"]) + self.liststore.set_value(row, + self.get_column_index("Peers")[1], + status["num_peers"]) + self.liststore.set_value(row, + self.get_column_index("Down Speed"), + status["download_payload_rate"]) + self.liststore.set_value(row, + self.get_column_index("Up Speed"), + status["upload_payload_rate"]) + self.liststore.set_value(row, + self.get_column_index("ETA"), + status["eta"]) + + # Iterates through every row and updates them accordingly + if self.liststore is not None: + self.liststore.foreach(update_row, None) def add_row(self, torrent_id): - pass - + """Adds a new torrent row to the treeview""" + # Get the status and info dictionaries + status_keys = ["name", "total_size", "progress", "state", + "num_seeds", "num_peers", "download_payload_rate", + "upload_payload_rate", "eta"] + status = functions.get_torrent_status(self.core, torrent_id, + status_keys) + # Insert the row with info provided from core + self.liststore.append([ + torrent_id, + None, + status["name"], + status["total_size"], + status["progress"]*100, + status["state"], + status["num_seeds"], + status["num_seeds"], + status["num_peers"], + status["num_peers"], + status["download_payload_rate"], + status["upload_payload_rate"], + status["eta"], + 0.0 + ]) + def remove_row(self, torrent_id): - pass + """Removes a row with torrent_id""" + row = self.liststore.get_iter_first() + while row is not None: + # Check if this row is the row we want to remove + if self.liststore.get_value(row, 0) == torrent_id: + self.liststore.remove(row) + # Force an update of the torrentview + self.update() + break + row = self.liststore.iter_next(row) def get_selected_torrents(self): - pass + """Returns a list of selected torrents or None""" + torrent_ids = [] + paths = self.treeview.get_selection().get_selected_rows()[1] + + try: + for path in paths: + torrent_ids.append( + self.liststore.get_value( + self.liststore.get_iter(path), 0)) + return torrent_ids + except ValueError: + return None ### Callbacks ### def on_button_press_event(self, widget, event): From b7e3c6b3dfe7f4a9958cc79b31f6b1a5a8ff006d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 13 Aug 2007 09:28:29 +0000 Subject: [PATCH 0037/1009] Sync libtorrent to trunk. Start of columns menu. --- deluge/ui/gtkui/glade/torrent_menu.glade | 99 +----------- deluge/ui/gtkui/listview.py | 18 ++- deluge/ui/gtkui/torrentview.py | 5 +- libtorrent/include/libtorrent/alert_types.hpp | 57 +++++++ .../include/libtorrent/bandwidth_manager.hpp | 64 +++++--- libtorrent/include/libtorrent/config.hpp | 5 + .../include/libtorrent/disk_io_thread.hpp | 4 + libtorrent/include/libtorrent/entry.hpp | 2 +- .../include/libtorrent/peer_connection.hpp | 15 +- libtorrent/include/libtorrent/peer_info.hpp | 4 + .../include/libtorrent/piece_picker.hpp | 7 +- libtorrent/include/libtorrent/policy.hpp | 2 + libtorrent/include/libtorrent/session.hpp | 5 +- .../include/libtorrent/session_settings.hpp | 9 ++ libtorrent/include/libtorrent/torrent.hpp | 2 + .../include/libtorrent/torrent_handle.hpp | 16 +- .../include/libtorrent/torrent_info.hpp | 4 +- libtorrent/include/libtorrent/xml_parse.hpp | 146 ++++++++++++++++-- libtorrent/src/Makefile.am | 2 +- libtorrent/src/connection_queue.cpp | 2 +- libtorrent/src/disk_io_thread.cpp | 5 + libtorrent/src/file.cpp | 16 +- libtorrent/src/lsd.cpp | 1 - libtorrent/src/peer_connection.cpp | 80 ++++++++-- libtorrent/src/piece_picker.cpp | 70 +++++++-- libtorrent/src/policy.cpp | 53 ++++++- libtorrent/src/session_impl.cpp | 32 +--- libtorrent/src/storage.cpp | 21 ++- libtorrent/src/torrent.cpp | 37 +++-- libtorrent/src/torrent_handle.cpp | 3 + libtorrent/src/upnp.cpp | 48 ++++-- libtorrent/src/ut_pex.cpp | 50 +++--- libtorrent/src/web_peer_connection.cpp | 9 +- 33 files changed, 613 insertions(+), 280 deletions(-) diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 774cc3716..b371e68d3 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -35,6 +35,11 @@ + + + True + + True @@ -69,7 +74,7 @@ - + True @@ -90,97 +95,5 @@ - - - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Queue - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Top - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-goto-top - 1 - - - - - - - True - _Up - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-go-up - 1 - - - - - - - True - _Down - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-go-down - 1 - - - - - - - True - _Bottom - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-goto-bottom - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-sort-ascending - 1 - - - - diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 253d81bd3..0dec730cc 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -82,6 +82,7 @@ class ListView: def __init__(self, name, column_indices): self.name = name self.column_indices = column_indices + self.column = None def __init__(self, treeview_widget=None): log.debug("ListView initialized..") @@ -113,6 +114,13 @@ class ListView: else: return self.columns[name].column_indices[0] + def create_checklist_menu(self): + menu = gtk.Menu() + for column in self.columns.values(): + menuitem = gtk.CheckMenuItem(column.name) + menu.append(menuitem) + return menu + def create_new_liststore(self): # Create a new liststore with added column and move the data from the # old one to the new one. @@ -158,6 +166,7 @@ class ListView: column.set_reorderable(True) column.set_visible(visible) self.treeview.append_column(column) + self.columns[header].column = column return True @@ -194,7 +203,8 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) self.treeview.append_column(column) - + self.columns[header].column = column + return True def add_progress_column(self, header): @@ -221,7 +231,8 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) self.treeview.append_column(column) - + self.columns[header].column = column + return True def add_texticon_column(self, header): @@ -251,5 +262,6 @@ class ListView: column.add_attribute(render, 'text', self.columns[header].column_indices[1]) self.treeview.append_column(column) - + self.columns[header].column = column + return True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index eb58085a2..1d6fdc03c 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -80,6 +80,9 @@ class TorrentView(listview.ListView): listview.cell_data_ratio, [float]) + self.window.main_glade.get_widget("menu_columns").set_submenu( + self.create_checklist_menu()) + ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the # torrent menu popup. @@ -100,7 +103,7 @@ class TorrentView(listview.ListView): "eta"] status = functions.get_torrent_status(self.core, torrent_id, status_keys) - + # Set values for each column in the row self.liststore.set_value(row, diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 7b32d2502..48491bca4 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -184,6 +184,63 @@ namespace libtorrent { return std::auto_ptr(new torrent_finished_alert(*this)); } }; + struct TORRENT_EXPORT piece_finished_alert: torrent_alert + { + piece_finished_alert( + const torrent_handle& h + , int piece_num + , const std::string& msg) + : torrent_alert(h, alert::warning, msg) + , piece_index(piece_num) + { assert(piece_index >= 0);} + + int piece_index; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new piece_finished_alert(*this)); } + }; + + struct TORRENT_EXPORT block_finished_alert: torrent_alert + { + block_finished_alert( + const torrent_handle& h + , int block_num + , int piece_num + , const std::string& msg) + : torrent_alert(h, alert::warning, msg) + , block_index(block_num) + , piece_index(piece_num) + { assert(block_index >= 0 && piece_index >= 0);} + + int block_index; + int piece_index; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new block_finished_alert(*this)); } + }; + + struct TORRENT_EXPORT block_downloading_alert: torrent_alert + { + block_downloading_alert( + const torrent_handle& h + , std::string& speedmsg + , int block_num + , int piece_num + , const std::string& msg) + : torrent_alert(h, alert::warning, msg) + , peer_speedmsg(speedmsg) + , block_index(block_num) + , piece_index(piece_num) + { assert(block_index >= 0 && piece_index >= 0);} + + std::string peer_speedmsg; + int block_index; + int piece_index; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new block_downloading_alert(*this)); } + }; + struct TORRENT_EXPORT storage_moved_alert: torrent_alert { storage_moved_alert(torrent_handle const& h, std::string const& path) diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 4df9d4f2f..75e1f1d4e 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -90,47 +90,47 @@ struct bandwidth_limit { static const int inf = boost::integer_traits::const_max; - bandwidth_limit() + bandwidth_limit() throw() : m_quota_left(0) , m_local_limit(inf) , m_current_rate(0) {} - void throttle(int limit) + void throttle(int limit) throw() { m_local_limit = limit; } - int throttle() const + int throttle() const throw() { return m_local_limit; } - void assign(int amount) + void assign(int amount) throw() { assert(amount > 0); m_current_rate += amount; m_quota_left += amount; } - void use_quota(int amount) + void use_quota(int amount) throw() { assert(amount <= m_quota_left); m_quota_left -= amount; } - int quota_left() const + int quota_left() const throw() { return (std::max)(m_quota_left, 0); } - void expire(int amount) + void expire(int amount) throw() { assert(amount >= 0); m_current_rate -= amount; } - int max_assignable() const + int max_assignable() const throw() { if (m_local_limit == inf) return inf; if (m_local_limit <= m_current_rate) return 0; @@ -160,7 +160,7 @@ private: }; template -T clamp(T val, T ceiling, T floor) +T clamp(T val, T ceiling, T floor) throw() { assert(ceiling >= floor); if (val >= ceiling) return ceiling; @@ -171,7 +171,7 @@ T clamp(T val, T ceiling, T floor) template struct bandwidth_manager { - bandwidth_manager(io_service& ios, int channel) + bandwidth_manager(io_service& ios, int channel) throw() : m_ios(ios) , m_history_timer(m_ios) , m_limit(bandwidth_limit::inf) @@ -179,14 +179,14 @@ struct bandwidth_manager , m_channel(channel) {} - void throttle(int limit) + void throttle(int limit) throw() { mutex_t::scoped_lock l(m_mutex); assert(limit >= 0); m_limit = limit; } - int throttle() const + int throttle() const throw() { mutex_t::scoped_lock l(m_mutex); return m_limit; @@ -197,7 +197,7 @@ struct bandwidth_manager // this is used by web seeds void request_bandwidth(intrusive_ptr peer , int blk - , bool non_prioritized) + , bool non_prioritized) throw() { INVARIANT_CHECK; assert(blk > 0); @@ -257,8 +257,11 @@ struct bandwidth_manager private: - void add_history_entry(history_entry const& e) try + void add_history_entry(history_entry const& e) throw() { +#ifndef NDEBUG + try { +#endif INVARIANT_CHECK; m_history.push_front(e); m_current_quota += e.amount; @@ -268,11 +271,17 @@ private: m_history_timer.expires_at(e.expires_at); m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); +#ifndef NDEBUG + } + catch (std::exception&) { assert(false); } +#endif } - catch (std::exception&) { assert(false); } - void on_history_expire(asio::error_code const& e) try + void on_history_expire(asio::error_code const& e) throw() { +#ifndef NDEBUG + try { +#endif INVARIANT_CHECK; if (e) return; @@ -303,14 +312,20 @@ private: // means we can hand out more (in case there // are still consumers in line) if (!m_queue.empty()) hand_out_bandwidth(); +#ifndef NDEBUG + } + catch (std::exception&) + { + assert(false); + } +#endif } - catch (std::exception&) - { - assert(false); - }; - void hand_out_bandwidth() try + void hand_out_bandwidth() throw() { +#ifndef NDEBUG + try { +#endif INVARIANT_CHECK; ptime now(time_now()); @@ -404,9 +419,12 @@ private: add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); } +#ifndef NDEBUG + } + catch (std::exception& e) + { assert(false); }; +#endif } - catch (std::exception& e) - { assert(false); }; typedef boost::mutex mutex_t; diff --git a/libtorrent/include/libtorrent/config.hpp b/libtorrent/include/libtorrent/config.hpp index c8d86955e..b36d4da22 100755 --- a/libtorrent/include/libtorrent/config.hpp +++ b/libtorrent/include/libtorrent/config.hpp @@ -37,6 +37,8 @@ POSSIBILITY OF SUCH DAMAGE. #if defined(__GNUC__) && __GNUC__ >= 4 +#define TORRENT_DEPRECATED __attribute__ ((deprecated)) + # if defined(TORRENT_BUILDING_SHARED) || defined(TORRENT_LINKING_SHARED) # define TORRENT_EXPORT __attribute__ ((visibility("default"))) # else @@ -61,6 +63,9 @@ POSSIBILITY OF SUCH DAMAGE. # define TORRENT_EXPORT #endif +#ifndef TORRENT_DEPRECATED +#define TORRENT_DEPRECATED +#endif #endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index aff0930e4..16ee0bca4 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -111,6 +111,10 @@ namespace libtorrent // memory pool for read and write operations boost::pool<> m_pool; +#ifndef NDEBUG + int m_block_size; +#endif + // thread for performing blocking disk io operations boost::thread m_disk_io_thread; }; diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp index a1eba5324..59e29803d 100755 --- a/libtorrent/include/libtorrent/entry.hpp +++ b/libtorrent/include/libtorrent/entry.hpp @@ -59,7 +59,7 @@ POSSIBILITY OF SUCH DAMAGE. */ -#include +#include #include #include #include diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index d32d250fc..31bcde94a 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -74,10 +74,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type.hpp" #include "libtorrent/intrusive_ptr_base.hpp" -// TODO: each time a block is 'taken over' -// from another peer. That peer must be given -// a chance to become not-interested. - namespace libtorrent { class torrent; @@ -395,9 +391,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_ENCRYPTION buffer::interval wr_recv_buffer() { -#if defined _SECURE_SCL && _SECURE_SCL > 0 if (m_recv_buffer.empty()) return buffer::interval(0,0); -#endif return buffer::interval(&m_recv_buffer[0] , &m_recv_buffer[0] + m_recv_pos); } @@ -405,9 +399,7 @@ namespace libtorrent buffer::const_interval receive_buffer() const { -#if defined _SECURE_SCL && _SECURE_SCL > 0 if (m_recv_buffer.empty()) return buffer::const_interval(0,0); -#endif return buffer::const_interval(&m_recv_buffer[0] , &m_recv_buffer[0] + m_recv_pos); } @@ -646,7 +638,8 @@ namespace libtorrent bool m_queued; // these are true when there's a asynchronous write - // or read operation running. + // or read operation in progress. Or an asyncronous bandwidth + // request is in progress. bool m_writing; bool m_reading; @@ -701,6 +694,10 @@ namespace libtorrent // a timestamp when the remote download rate // was last updated ptime m_remote_dl_update; + + // the number of bytes send to the disk-io + // thread that hasn't yet been completely written. + int m_outstanding_writing_bytes; #ifndef NDEBUG public: diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index f8ee2feb6..15ad34a7a 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -144,6 +144,10 @@ namespace libtorrent // approximate peer download rate int remote_dl_rate; + + // number of bytes this peer has in + // the disk write queue + int pending_disk_bytes; }; } diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index 136e71c63..54df003ef 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -163,7 +163,9 @@ namespace libtorrent void we_have(int index); // sets the priority of a piece. - void set_piece_priority(int index, int prio); + // returns true if the priority was changed from 0 to non-0 + // or vice versa + bool set_piece_priority(int index, int prio); // returns the priority for the piece at 'index' int piece_priority(int index) const; @@ -215,6 +217,9 @@ namespace libtorrent void mark_as_finished(piece_block block, void* peer); int num_peers(piece_block block) const; + // returns information about the given piece + void piece_info(int index, piece_picker::downloading_piece& st) const; + // if a piece had a hash-failure, it must be restored and // made available for redownloading void restore_piece(int index); diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index e087ccb75..6c976d047 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -110,6 +110,8 @@ namespace libtorrent // the peer is not interested in our pieces void not_interested(peer_connection& c); + void ip_filter_updated(); + #ifndef NDEBUG bool has_connection(const peer_connection* p); diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 52ec62cdb..38206f32c 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -144,14 +144,15 @@ namespace libtorrent , int block_size = 16 * 1024 , storage_constructor_type sc = default_storage_constructor); - // TODO: deprecated, this is for backwards compatibility only + // ==== deprecated, this is for backwards compatibility only + // instead, use one of the other add_torrent overloads torrent_handle add_torrent( entry const& e , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true , int block_size = 16 * 1024 - , storage_constructor_type sc = default_storage_constructor) + , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED { return add_torrent(torrent_info(e), save_path, resume_data , compact_mode, block_size, sc); diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index 783492227..ebc30eae3 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -108,6 +108,7 @@ namespace libtorrent , unchoke_interval(20) , num_want(200) , initial_picker_threshold(4) + , max_outstanding_disk_bytes_per_connection(64 * 1024) #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) #endif @@ -251,6 +252,14 @@ namespace libtorrent // random pieces instead of rarest first. int initial_picker_threshold; + // the maximum number of bytes a connection may have + // pending in the disk write queue before its download + // rate is being throttled. This prevents fast downloads + // to slow medias to allocate more and more memory + // indefinitely. This should be set to at least 32 kB + // to not completely disrupt normal downloads. + int max_outstanding_disk_bytes_per_connection; + #ifndef TORRENT_DISABLE_DHT // while this is true, the dht will note be used unless the // tracker is online diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index bed076fe9..2eef2656b 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -175,6 +175,8 @@ namespace libtorrent boost::tuples::tuple bytes_done() const; size_type quantized_bytes_done() const; + void ip_filter_updated() { m_policy->ip_filter_updated(); } + void pause(); void resume(); bool is_paused() const { return m_paused; } diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index a4c8af923..3f7ae5bcc 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -241,6 +241,12 @@ namespace libtorrent enum { max_blocks_per_piece = 256 }; int piece_index; int blocks_in_piece; + // the number of blocks in the finished state + int finished; + // the number of blocks in the writing state + int writing; + // the number of blocks in the requested state + int requested; block_info blocks[max_blocks_per_piece]; enum state_t { none, slow, medium, fast }; state_t piece_state; @@ -290,13 +296,13 @@ namespace libtorrent // marks the piece with the given index as filtered // it will not be downloaded - void filter_piece(int index, bool filter) const; - void filter_pieces(std::vector const& pieces) const; - bool is_piece_filtered(int index) const; - std::vector filtered_pieces() const; + void filter_piece(int index, bool filter) const TORRENT_DEPRECATED; + void filter_pieces(std::vector const& pieces) const TORRENT_DEPRECATED; + bool is_piece_filtered(int index) const TORRENT_DEPRECATED; + std::vector filtered_pieces() const TORRENT_DEPRECATED; // marks the file with the given index as filtered // it will not be downloaded - void filter_files(std::vector const& files) const; + void filter_files(std::vector const& files) const TORRENT_DEPRECATED; // ================ end deprecation ============ diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index e52024255..a2d6c4ef9 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #ifdef _MSC_VER #pragma warning(push, 1) @@ -146,7 +147,8 @@ namespace libtorrent const std::string& name() const { assert(m_piece_length > 0); return m_name; } // ------- start deprecation ------- - void print(std::ostream& os) const; +// this functionaily will be removed in a future version + void print(std::ostream& os) const TORRENT_DEPRECATED; // ------- end deprecation ------- bool is_valid() const { return m_piece_length > 0; } diff --git a/libtorrent/include/libtorrent/xml_parse.hpp b/libtorrent/include/libtorrent/xml_parse.hpp index c3aeddad7..67ae406f0 100644 --- a/libtorrent/include/libtorrent/xml_parse.hpp +++ b/libtorrent/include/libtorrent/xml_parse.hpp @@ -33,11 +33,25 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_XML_PARSE_HPP #define TORRENT_XML_PARSE_HPP +#include + namespace libtorrent { - const int xml_start_tag = 0; - const int xml_end_tag = 1; - const int xml_string = 2; + enum + { + xml_start_tag, + xml_end_tag, + xml_empty_tag, + xml_declaration_tag, + xml_string, + xml_attribute, + xml_comment, + xml_parse_error + }; + + // callback(int type, char const* name, char const* val) + // str2 is only used for attributes. name is element or attribute + // name and val is attribute value template void xml_parse(char* p, char* end, CallbackType callback) @@ -45,6 +59,8 @@ namespace libtorrent for(;p != end; ++p) { char const* start = p; + char const* val_start = 0; + int token; // look for tag start for(; *p != '<' && p != end; ++p); @@ -55,39 +71,135 @@ namespace libtorrent assert(*p == '<'); *p = 0; } - callback(xml_string, start); + token = xml_string; + callback(token, start, val_start); if (p != end) *p = '<'; } - + if (p == end) break; // skip '<' ++p; - // parse the name of the tag. Ignore attributes - for (start = p; p != end && *p != '>'; ++p) - { - // terminate the string at the first space - // to ignore tag attributes - if (*p == ' ') *p = 0; - } + // parse the name of the tag. + for (start = p; p != end && *p != '>' && !std::isspace(*p); ++p); + + char* tag_name_end = p; + + // skip the attributes for now + for (; p != end && *p != '>'; ++p); // parse error - if (p == end) break; + if (p == end) + { + token = xml_parse_error; + start = "unexpected end of file"; + callback(token, start, val_start); + break; + } assert(*p == '>'); - *p = 0; + // save the character that terminated the tag name + // it could be both '>' and ' '. + char save = *tag_name_end; + *tag_name_end = 0; + char* tag_end = p; if (*start == '/') { ++start; - callback(xml_end_tag, start); + token = xml_end_tag; + callback(token, start, val_start); + } + else if (*(p-1) == '/') + { + *(p-1) = 0; + token = xml_empty_tag; + callback(token, start, val_start); + *(p-1) = '/'; + tag_end = p - 1; + } + else if (*start == '?' && *(p-1) == '?') + { + *(p-1) = 0; + ++start; + token = xml_declaration_tag; + callback(token, start, val_start); + *(p-1) = '?'; + tag_end = p - 1; + } + else if (start + 5 < p && memcmp(start, "!--", 3) == 0 && memcmp(p-2, "--", 2) == 0) + { + start += 3; + *(p-2) = 0; + token = xml_comment; + callback(token, start, val_start); + *(p-2) = '-'; + tag_end = p - 2; } else { - callback(xml_start_tag, start); + token = xml_start_tag; + callback(token, start, val_start); + } + + *tag_name_end = save; + + // parse attributes + for (char* i = tag_name_end; i < tag_end; ++i) + { + // find start of attribute name + for (; i != tag_end && std::isspace(*i); ++i); + if (i == tag_end) break; + start = i; + // find end of attribute name + for (; i != tag_end && *i != '=' && !std::isspace(*i); ++i); + char* name_end = i; + + // look for equality sign + for (; i != tag_end && *i != '='; ++i); + + if (i == tag_end) + { + token = xml_parse_error; + val_start = 0; + start = "garbage inside element brackets"; + callback(token, start, val_start); + break; + } + + ++i; + for (; i != tag_end && std::isspace(*i); ++i); + // check for parse error (values must be quoted) + if (i == tag_end || (*i != '\'' && *i != '\"')) + { + token = xml_parse_error; + val_start = 0; + start = "unquoted attribute value"; + callback(token, start, val_start); + break; + } + char quote = *i; + ++i; + val_start = i; + for (; i != tag_end && *i != quote; ++i); + // parse error (missing end quote) + if (i == tag_end) + { + token = xml_parse_error; + val_start = 0; + start = "missing end quote on attribute"; + callback(token, start, val_start); + break; + } + save = *i; + *i = 0; + *name_end = 0; + token = xml_attribute; + callback(token, start, val_start); + *name_end = '='; + *i = save; } - *p = '>'; } } diff --git a/libtorrent/src/Makefile.am b/libtorrent/src/Makefile.am index ef834018b..fabe68b65 100644 --- a/libtorrent/src/Makefile.am +++ b/libtorrent/src/Makefile.am @@ -95,6 +95,6 @@ $(top_srcdir)/include/libtorrent/version.hpp libtorrent_la_LDFLAGS = $(LDFLAGS) -release @VERSION@ libtorrent_la_LIBADD = @ZLIB@ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ -AM_CXXFLAGS= -ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @ZLIBINCL@ @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CXXFLAGS= -ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @ZLIBINCL@ @DEBUGFLAGS@ @PTHREAD_CFLAGS@ -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION AM_LDFLAGS= $(LDFLAGS) -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index 473a92920..859205ed0 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -159,7 +159,7 @@ namespace libtorrent ptime next_expire = max_time(); ptime now = time_now(); for (std::list::iterator i = m_queue.begin(); - i != m_queue.end();) + !m_queue.empty() && i != m_queue.end();) { if (i->connecting && i->expires < now) { diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index b02ab0012..6bc357506 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -41,6 +41,9 @@ namespace libtorrent : m_abort(false) , m_queue_buffer_size(0) , m_pool(block_size) +#ifndef NDEBUG + , m_block_size(block_size) +#endif , m_disk_io_thread(boost::ref(*this)) {} @@ -192,6 +195,7 @@ namespace libtorrent } else { + assert(j.buffer_size <= m_block_size); ret = j.storage->read_impl(j.buffer, j.piece, j.offset , j.buffer_size); @@ -201,6 +205,7 @@ namespace libtorrent break; case disk_io_job::write: assert(j.buffer); + assert(j.buffer_size <= m_block_size); j.storage->write_impl(j.buffer, j.piece, j.offset , j.buffer_size); diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 515406a46..3d568d1f7 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -248,11 +248,17 @@ namespace libtorrent void set_size(size_type s) { size_type pos = tell(); - seek(s - 1); - char dummy = 0; - read(&dummy, 1); - seek(s - 1); - write(&dummy, 1); + // Only set size if current file size not equals s. + // 2 as "m" argument is to be sure seek() sets SEEK_END on + // all compilers. + if(s != seek(0, 2)) + { + seek(s - 1); + char dummy = 0; + read(&dummy, 1); + seek(s - 1); + write(&dummy, 1); + } seek(pos); } diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index b73e407bc..76f25548d 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -35,7 +35,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/io.hpp" #include "libtorrent/http_tracker_connection.hpp" -#include "libtorrent/xml_parse.hpp" #include #include #include diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 7dbef4381..b73e32896 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -116,6 +116,7 @@ namespace libtorrent , m_remote_bytes_dled(0) , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) + , m_outstanding_writing_bytes(0) #ifndef NDEBUG , m_in_constructor(true) #endif @@ -189,6 +190,7 @@ namespace libtorrent , m_remote_bytes_dled(0) , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) + , m_outstanding_writing_bytes(0) #ifndef NDEBUG , m_in_constructor(true) #endif @@ -936,7 +938,8 @@ namespace libtorrent && r.start < t->torrent_file().piece_size(r.piece) && r.length > 0 && r.length + r.start <= t->torrent_file().piece_size(r.piece) - && m_peer_interested) + && m_peer_interested + && r.length <= t->block_size()) { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() @@ -961,7 +964,8 @@ namespace libtorrent "i: " << m_peer_interested << " | " "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " | " - "h: " << t->have_piece(r.piece) << " ]\n"; + "h: " << t->have_piece(r.piece) << " | " + "block_limit: " << t->block_size() << " ]\n"; #endif ++m_num_invalid_requests; @@ -1150,6 +1154,8 @@ namespace libtorrent fs.async_write(p, data, bind(&peer_connection::on_disk_write_complete , self(), _1, _2, p, t)); + m_outstanding_writing_bytes += p.length; + assert(!m_reading); picker.mark_as_writing(block_finished, peer_info_struct()); } @@ -1158,6 +1164,16 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_outstanding_writing_bytes -= p.length; + assert(m_outstanding_writing_bytes >= 0); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** on_disk_write_complete() " << p.length << "\n"; +#endif + // in case the outstanding bytes just dropped down + // to allow to receive more data + setup_receive(); + if (ret == -1 || !t) { if (!t) @@ -1183,6 +1199,11 @@ namespace libtorrent assert(p.start == j.offset); piece_block block_finished(p.piece, p.start / t->block_size()); picker.mark_as_finished(block_finished, peer_info_struct()); + if (t->alerts().should_post(alert::info)) + { + t->alerts().post_alert(block_finished_alert(t->get_handle(), + block_finished.block_index, block_finished.piece_index, "block finished")); + } if (!has_peer_choked() && !t->is_seed() && !m_torrent.expired()) { @@ -1290,11 +1311,29 @@ namespace libtorrent piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); - if (speed == fast) state = piece_picker::fast; - else if (speed == medium) state = piece_picker::medium; - else state = piece_picker::slow; + std::string speedmsg; + if (speed == fast) + { + speedmsg = "fast"; + state = piece_picker::fast; + } + else if (speed == medium) + { + speedmsg = "medium"; + state = piece_picker::medium; + } + else + { + speedmsg = "slow"; + state = piece_picker::slow; + } t->picker().mark_as_downloading(block, peer_info_struct(), state); + if (t->alerts().should_post(alert::info)) + { + t->alerts().post_alert(block_downloading_alert(t->get_handle(), + speedmsg, block.block_index, block.piece_index, "block downloading")); + } m_request_queue.push_back(block); } @@ -1642,6 +1681,7 @@ namespace libtorrent p.payload_up_speed = statistics().upload_payload_rate(); p.pid = pid(); p.ip = remote(); + p.pending_disk_bytes = m_outstanding_writing_bytes; #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES p.country[0] = m_country[0]; @@ -2015,11 +2055,13 @@ namespace libtorrent m_bandwidth_limit[channel].assign(amount); if (channel == upload_channel) { + assert(m_writing); m_writing = false; setup_send(); } else if (channel == download_channel) { + assert(m_reading); m_reading = false; setup_receive(); } @@ -2067,10 +2109,11 @@ namespace libtorrent (*m_logger) << "req bandwidth [ " << upload_channel << " ]\n"; #endif + assert(!m_writing); // peers that we are not interested in are non-prioritized + m_writing = true; t->request_bandwidth(upload_channel, self() , !(is_interesting() && !has_peer_choked())); - m_writing = true; } return; } @@ -2116,6 +2159,9 @@ namespace libtorrent INVARIANT_CHECK; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "setup_receive: reading = " << m_reading << "\n"; +#endif if (m_reading) return; shared_ptr t = m_torrent.lock(); @@ -2130,8 +2176,8 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "req bandwidth [ " << download_channel << " ]\n"; #endif - t->request_bandwidth(download_channel, self(), m_non_prioritized); m_reading = true; + t->request_bandwidth(download_channel, self(), m_non_prioritized); } return; } @@ -2144,7 +2190,7 @@ namespace libtorrent if (!m_ignore_bandwidth_limits && max_receive > quota_left) max_receive = quota_left; - assert(max_receive > 0); + if (max_receive == 0) return; assert(m_recv_pos >= 0); assert(m_packet_size > 0); @@ -2242,10 +2288,7 @@ namespace libtorrent m_recv_pos += bytes_transferred; assert(m_recv_pos <= int(m_recv_buffer.size())); - { - INVARIANT_CHECK; - on_receive(error, bytes_transferred); - } + on_receive(error, bytes_transferred); assert(m_packet_size > 0); @@ -2322,9 +2365,17 @@ namespace libtorrent { INVARIANT_CHECK; - return (m_bandwidth_limit[download_channel].quota_left() > 0 + bool ret = (m_bandwidth_limit[download_channel].quota_left() > 0 || m_ignore_bandwidth_limits) - && !m_connecting; + && !m_connecting + && m_outstanding_writing_bytes < + m_ses.settings().max_outstanding_disk_bytes_per_connection; + +#if defined(TORRENT_VERBOSE_LOGGING) + (*m_logger) << "*** can_read() " << ret << " reading: " << m_reading << "\n"; +#endif + + return ret; } void peer_connection::connect(int ticket) @@ -2649,6 +2700,7 @@ namespace libtorrent (*m_logger) << time_now_string() << " ==> KEEPALIVE\n"; #endif + m_last_sent = time_now(); write_keepalive(); } diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 61f3544b7..ddc2c2f5a 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -129,6 +129,35 @@ namespace libtorrent } } + void piece_picker::piece_info(int index, piece_picker::downloading_piece& st) const + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + assert(index >= 0); + assert(index < int(m_piece_map.size())); + + if (m_piece_map[index].downloading) + { + std::vector::const_iterator piece = std::find_if( + m_downloads.begin(), m_downloads.end() + , bind(&downloading_piece::index, _1) == index); + assert(piece != m_downloads.end()); + st = *piece; + st.info = 0; + return; + } + st.info = 0; + st.index = index; + st.writing = 0; + st.requested = 0; + if (m_piece_map[index].have()) + { + st.finished = blocks_in_piece(index); + return; + } + st.finished = 0; + } + void piece_picker::set_sequenced_download_threshold( int sequenced_download_threshold) { @@ -196,7 +225,8 @@ namespace libtorrent int block_index = num_downloads * m_blocks_per_piece; if (int(m_block_info.size()) < block_index + m_blocks_per_piece) { - block_info* base = &m_block_info[0]; + block_info* base = 0; + if (!m_block_info.empty()) base = &m_block_info[0]; m_block_info.resize(block_index + m_blocks_per_piece); if (!m_downloads.empty() && &m_block_info[0] != base) { @@ -605,9 +635,10 @@ namespace libtorrent void piece_picker::sort_piece(std::vector::iterator dp) { assert(m_piece_map[dp->index].downloading); + if (dp == m_downloads.begin()) return; int complete = dp->writing + dp->finished; for (std::vector::iterator i = dp, j(dp-1); - i != m_downloads.begin(); --i, --j) + i != m_downloads.begin() && j != m_downloads.begin(); --i, --j) { assert(j >= m_downloads.begin()); if (j->finished + j->writing >= complete) return; @@ -935,7 +966,7 @@ namespace libtorrent } - void piece_picker::set_piece_priority(int index, int new_piece_priority) + bool piece_picker::set_piece_priority(int index, int new_piece_priority) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(new_piece_priority >= 0); @@ -946,16 +977,18 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; // if the priority isn't changed, don't do anything - if (new_piece_priority == int(p.piece_priority)) return; + if (new_piece_priority == int(p.piece_priority)) return false; int prev_priority = p.priority(m_sequenced_download_threshold); + bool ret = false; if (new_piece_priority == piece_pos::filter_priority && p.piece_priority != piece_pos::filter_priority) { // the piece just got filtered if (p.have()) ++m_num_have_filtered; else ++m_num_filtered; + ret = true; } else if (new_piece_priority != piece_pos::filter_priority && p.piece_priority == piece_pos::filter_priority) @@ -963,6 +996,7 @@ namespace libtorrent // the piece just got unfiltered if (p.have()) --m_num_have_filtered; else --m_num_filtered; + ret = true; } assert(m_num_filtered >= 0); assert(m_num_have_filtered >= 0); @@ -970,7 +1004,7 @@ namespace libtorrent p.piece_priority = new_piece_priority; int new_priority = p.priority(m_sequenced_download_threshold); - if (new_priority == prev_priority) return; + if (new_priority == prev_priority) return false; if (prev_priority == 0) { @@ -980,6 +1014,7 @@ namespace libtorrent { move(prev_priority, p.index); } + return ret; } int piece_picker::piece_priority(int index) const @@ -1051,8 +1086,12 @@ namespace libtorrent // downloaded to std::vector backup_blocks; + // When prefer_whole_pieces is set (usually set when downloading from + // fast peers) the partial pieces will not be prioritized, but actually + // ignored as long as possible. All blocks found in downloading + // pieces are regarded as backup blocks bool ignore_downloading_pieces = false; - if (!prefer_whole_pieces) + if (prefer_whole_pieces) { std::vector downloading_pieces; downloading_pieces.reserve(m_downloads.size()); @@ -1061,8 +1100,8 @@ namespace libtorrent { downloading_pieces.push_back(i->index); } - num_blocks = add_interesting_blocks(downloading_pieces, pieces - , interesting_blocks, backup_blocks, num_blocks + add_interesting_blocks(downloading_pieces, pieces + , backup_blocks, backup_blocks, num_blocks , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); ignore_downloading_pieces = true; } @@ -1072,10 +1111,6 @@ namespace libtorrent // has filled the interesting_blocks with num_blocks // blocks. - // When prefer_whole_pieces is set (usually set when downloading from - // fast peers) the partial pieces will not be prioritized, but actually - // ignored as long as possible. - // +1 is to ignore pieces that no peer has. The bucket with index 0 contains // pieces that 0 other peers have. bucket will point to a bucket with // pieces with the same priority. It will be iterated in priority @@ -1128,8 +1163,7 @@ namespace libtorrent if (!backup_blocks.empty()) interesting_blocks.insert(interesting_blocks.end() - , backup_blocks.begin(), backup_blocks.begin() - + (std::min)(num_blocks, (int)backup_blocks.size())); + , backup_blocks.begin(), backup_blocks.end()); } void piece_picker::clear_peer(void* peer) @@ -1216,7 +1250,6 @@ namespace libtorrent // will be picked. if (prefer_whole_pieces && !exclusive) { - if (int(backup_blocks.size()) >= num_blocks) continue; for (int j = 0; j < num_blocks_in_piece; ++j) { block_info const& info = p->info[j]; @@ -1251,7 +1284,6 @@ namespace libtorrent && !exclusive_active && !ignore_speed_categories) { - if (int(backup_blocks.size()) >= num_blocks) continue; backup_blocks.push_back(piece_block(*i, j)); continue; } @@ -1264,9 +1296,9 @@ namespace libtorrent // to look for blocks until we have num_blocks // blocks that have not been requested from any // other peer. - interesting_blocks.push_back(piece_block(*i, j)); if (p->info[j].state == block_info::state_none) { + interesting_blocks.push_back(piece_block(*i, j)); // we have found a block that's free to download num_blocks--; // if we prefer whole pieces, continue picking from this @@ -1275,6 +1307,10 @@ namespace libtorrent assert(num_blocks >= 0); if (num_blocks == 0) return num_blocks; } + else + { + backup_blocks.push_back(piece_block(*i, j)); + } } assert(num_blocks >= 0 || prefer_whole_pieces); if (num_blocks < 0) num_blocks = 0; diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 2444deeb7..572f48d35 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -249,7 +249,7 @@ namespace libtorrent // with the other's, to see if we should abort another // peer_connection in favour of this one std::vector busy_pieces; - busy_pieces.reserve(10); + busy_pieces.reserve(num_requests); for (std::vector::iterator i = interesting_pieces.begin(); i != interesting_pieces.end(); ++i) @@ -272,6 +272,8 @@ namespace libtorrent // by somebody else. request it from this peer // and return c.add_request(*i); + assert(p.num_peers(*i) == 1); + assert(p.is_requested(*i)); num_requests--; } @@ -286,6 +288,8 @@ namespace libtorrent return; } + // if all blocks has the same number of peers on them + // we want to pick a random block std::random_shuffle(busy_pieces.begin(), busy_pieces.end()); // find the block with the fewest requests to it @@ -293,7 +297,11 @@ namespace libtorrent busy_pieces.begin(), busy_pieces.end() , bind(&piece_picker::num_peers, boost::cref(p), _1) < bind(&piece_picker::num_peers, boost::cref(p), _2)); - +#ifndef NDEBUG + piece_picker::downloading_piece st; + p.piece_info(i->piece_index, st); + assert(st.requested + st.finished + st.writing == p.blocks_in_piece(i->piece_index)); +#endif c.add_request(*i); c.send_block_requests(); } @@ -304,6 +312,47 @@ namespace libtorrent , m_available_free_upload(0) , m_last_optimistic_disconnect(min_time()) { assert(t); } + + // disconnects and removes all peers that are now filtered + void policy::ip_filter_updated() + { + aux::session_impl& ses = m_torrent->session(); + piece_picker* p = 0; + if (m_torrent->has_picker()) + p = &m_torrent->picker(); + for (std::list::iterator i = m_peers.begin() + , end(m_peers.end()); i != end;) + { + if ((ses.m_ip_filter.access(i->ip.address()) & ip_filter::blocked) == 0) + { + ++i; + continue; + } + + if (i->connection) + { + i->connection->disconnect(); + if (ses.m_alerts.should_post(alert::info)) + { + ses.m_alerts.post_alert(peer_blocked_alert(i->ip.address() + , "disconnected blocked peer")); + } + assert(i->connection == 0 + || i->connection->peer_info_struct() == 0); + } + else + { + if (ses.m_alerts.should_post(alert::info)) + { + ses.m_alerts.post_alert(peer_blocked_alert(i->ip.address() + , "blocked peer removed from peer list")); + } + } + if (p) p->clear_peer(&(*i)); + m_peers.erase(i++); + } + } + // finds the peer that has the worst download rate // and returns it. May return 0 if all peers are // choked. diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index f4323586c..81f48a3ce 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -613,29 +613,9 @@ namespace detail // Close connections whose endpoint is filtered // by the new ip-filter - for (session_impl::connection_map::iterator i - = m_connections.begin(); i != m_connections.end();) - { - tcp::endpoint sender; - try { sender = i->first->remote_endpoint(); } - catch (std::exception&) { sender = i->second->remote(); } - if (m_ip_filter.access(sender.address()) & ip_filter::blocked) - { -#if defined(TORRENT_VERBOSE_LOGGING) - (*i->second->m_logger) << "*** CONNECTION FILTERED\n"; -#endif - if (m_alerts.should_post(alert::info)) - { - m_alerts.post_alert(peer_blocked_alert(sender.address() - , "peer connection closed by IP filter")); - } - - session_impl::connection_map::iterator j = i; - ++i; - j->second->disconnect(); - } - else ++i; - } + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->ip_filter_updated(); } void session_impl::set_settings(session_settings const& s) @@ -1921,13 +1901,15 @@ namespace detail int session_impl::upload_rate_limit() const { mutex_t::scoped_lock l(m_mutex); - return m_bandwidth_manager[peer_connection::upload_channel]->throttle(); + int ret = m_bandwidth_manager[peer_connection::upload_channel]->throttle(); + return ret == std::numeric_limits::max() ? -1 : ret; } int session_impl::download_rate_limit() const { mutex_t::scoped_lock l(m_mutex); - return m_bandwidth_manager[peer_connection::download_channel]->throttle(); + int ret = m_bandwidth_manager[peer_connection::download_channel]->throttle(); + return ret == std::numeric_limits::max() ? -1 : ret; } void session_impl::start_lsd() diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 793f95365..b23a2e858 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -981,7 +981,7 @@ namespace libtorrent return true; #endif -#if defined(__APPLE__) || defined(__linux__) +#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) // find the last existing directory of the save path fs::path query_path = p; while (!query_path.empty() && !exists(query_path)) @@ -1019,6 +1019,7 @@ namespace libtorrent return true; } + // workaround for bugs in Mac OS X where zero run is not reported if (!strcmp(fsinfo.f_fstypename, "hfs") || !strcmp(fsinfo.f_fstypename, "ufs")) return true; @@ -1026,7 +1027,7 @@ namespace libtorrent return false; #endif -#if defined(__linux__) +#if defined(__linux__) || defined(__FreeBSD__) struct statfs buf; int err = statfs(query_path.native_directory_string().c_str(), &buf); if (err == 0) @@ -1044,6 +1045,7 @@ namespace libtorrent case 0x52345362: // Reiser4 case 0x58465342: // XFS case 0x65735546: // NTFS-3G + case 0x19540119: // UFS2 return true; } } @@ -1125,6 +1127,7 @@ namespace libtorrent j.piece = r.piece; j.offset = r.start; j.buffer_size = r.length; + assert(r.length <= 16 * 1024); m_io_thread.add_job(j, handler); } @@ -1569,16 +1572,12 @@ namespace libtorrent } if (m_unallocated_slots.empty()) - { m_state = state_create_files; - return false; - } - - if (m_compact_mode) - { + else if (m_compact_mode) m_state = state_create_files; - return false; - } + else + m_state = state_allocating; + return false; } m_state = state_full_check; @@ -1598,7 +1597,7 @@ namespace libtorrent | | | v | +------------+ - | | allocating | + |->| allocating | | +------------+ | | | v diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 39f29172f..13309c1e7 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -831,6 +831,11 @@ namespace libtorrent if (passed_hash_check) { + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(piece_finished_alert(get_handle() + , index, "piece finished")); + } // the following call may cause picker to become invalid // in case we just became a seed announce_piece(index); @@ -1104,7 +1109,7 @@ namespace libtorrent void torrent::set_piece_priority(int index, int priority) { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(valid_metadata()); if (is_seed()) return; @@ -1114,13 +1119,13 @@ namespace libtorrent assert(index >= 0); assert(index < m_torrent_file.num_pieces()); - m_picker->set_piece_priority(index, priority); - update_peer_interest(); + bool filter_updated = m_picker->set_piece_priority(index, priority); + if (filter_updated) update_peer_interest(); } int torrent::piece_priority(int index) const { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(valid_metadata()); if (is_seed()) return 1; @@ -1144,14 +1149,15 @@ namespace libtorrent assert(m_picker.get()); int index = 0; + bool filter_updated = false; for (std::vector::const_iterator i = pieces.begin() , end(pieces.end()); i != end; ++i, ++index) { assert(*i >= 0); assert(*i <= 7); - m_picker->set_piece_priority(index, *i); + filter_updated |= m_picker->set_piece_priority(index, *i); } - update_peer_interest(); + if (filter_updated) update_peer_interest(); } void torrent::piece_priorities(std::vector& pieces) const @@ -1653,17 +1659,26 @@ namespace libtorrent }; #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + namespace + { + unsigned long swap_bytes(unsigned long a) + { + return (a >> 24) | ((a & 0xff0000) >> 8) | ((a & 0xff00) << 8) | (a << 24); + } + } + void torrent::resolve_peer_country(boost::intrusive_ptr const& p) const { if (m_resolving_country || p->has_country() || p->is_connecting() || p->is_queued() - || p->in_handshake()) return; + || p->in_handshake() + || p->remote().address().is_v6()) return; m_resolving_country = true; - tcp::resolver::query q(boost::lexical_cast(p->remote().address()) - + ".zz.countries.nerd.dk", "0"); + asio::ip::address_v4 reversed(swap_bytes(p->remote().address().to_v4().to_ulong())); + tcp::resolver::query q(reversed.to_string() + ".zz.countries.nerd.dk", "0"); m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p))); } @@ -2325,15 +2340,11 @@ namespace libtorrent torrent_handle torrent::get_handle() const { - INVARIANT_CHECK; - return torrent_handle(&m_ses, &m_checker, m_torrent_file.info_hash()); } session_settings const& torrent::settings() const { -// INVARIANT_CHECK; - return m_ses.settings(); } diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 10e1172fa..4538e66e8 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -773,6 +773,9 @@ namespace libtorrent partial_piece_info pi; pi.piece_state = (partial_piece_info::state_t)i->state; pi.blocks_in_piece = p.blocks_in_piece(i->index); + pi.finished = (int)i->finished; + pi.writing = (int)i->writing; + pi.requested = (int)i->requested; int piece_size = t->torrent_file().piece_size(i->index); for (int j = 0; j < pi.blocks_in_piece; ++j) { diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index c60725e9d..aefff41b1 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -62,9 +62,10 @@ namespace libtorrent { if (a.is_v6()) return false; address_v4 a4 = a.to_v4(); - return ((a4.to_ulong() & 0xff000000) == 0x0a000000 - || (a4.to_ulong() & 0xfff00000) == 0xac100000 - || (a4.to_ulong() & 0xffff0000) == 0xc0a80000); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 + || (ip & 0xfff00000) == 0xac100000 + || (ip & 0xffff0000) == 0xc0a80000); } address_v4 guess_local_address(asio::io_service& ios) @@ -658,7 +659,7 @@ void upnp::on_upnp_xml(asio::error_code const& e d.upnp_connection.reset(); } - if (e) + if (e && e != asio::error::eof) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() @@ -671,7 +672,16 @@ void upnp::on_upnp_xml(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== incomplete http message" << std::endl; + << " <== error while fetching control url: incomplete http message" << std::endl; +#endif + return; + } + + if (p.status_code() != 200) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while fetching control url: " << p.message() << std::endl; #endif return; } @@ -790,7 +800,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e d.upnp_connection.reset(); } - if (e) + if (e && e != asio::error::eof) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() @@ -823,12 +833,22 @@ void upnp::on_upnp_map_response(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== incomplete http message" << std::endl; + << " <== error while adding portmap: incomplete http message" << std::endl; #endif m_devices.erase(d); return; } + if (p.status_code() != 200) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while adding portmap: " << p.message() << std::endl; +#endif + m_devices.erase(d); + return; + } + error_code_parse_state s; xml_parse((char*)p.get_body().begin, (char*)p.get_body().end , m_strand.wrap(bind(&find_error_code, _1, _2, boost::ref(s)))); @@ -932,7 +952,7 @@ void upnp::on_upnp_unmap_response(asio::error_code const& e d.upnp_connection.reset(); } - if (e) + if (e && e != asio::error::eof) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() @@ -944,11 +964,21 @@ void upnp::on_upnp_unmap_response(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== incomplete http message" << std::endl; + << " <== error while deleting portmap: incomplete http message" << std::endl; #endif return; } + if (p.status_code() != 200) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while deleting portmap: " << p.message() << std::endl; +#endif + m_devices.erase(d); + return; + } + #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end) diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 41df77475..18cbf6c2f 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -197,39 +197,41 @@ namespace libtorrent { namespace } virtual bool on_extended(int length, int msg, buffer::const_interval body) - try { if (msg != extension_index) return false; if (m_message_index == 0) return false; if (length > 500 * 1024) - throw protocol_error("ut peer exchange message larger than 500 kB"); + throw protocol_error("uT peer exchange message larger than 500 kB"); if (body.left() < length) return true; - entry pex_msg = bdecode(body.begin, body.end); - std::string const& peers = pex_msg["added"].string(); - std::string const& peer_flags = pex_msg["added.f"].string(); - - int num_peers = peers.length() / 6; - char const* in = peers.c_str(); - char const* fin = peer_flags.c_str(); - - if (int(peer_flags.size()) != num_peers) - return true; - - peer_id pid(0); - policy& p = m_torrent.get_policy(); - for (int i = 0; i < num_peers; ++i) + try { - tcp::endpoint adr = detail::read_v4_endpoint(in); - char flags = detail::read_uint8(fin); - p.peer_from_tracker(adr, pid, peer_info::pex, flags); - } - return true; - } - catch (std::exception&) - { + entry pex_msg = bdecode(body.begin, body.end); + std::string const& peers = pex_msg["added"].string(); + std::string const& peer_flags = pex_msg["added.f"].string(); + + int num_peers = peers.length() / 6; + char const* in = peers.c_str(); + char const* fin = peer_flags.c_str(); + + if (int(peer_flags.size()) != num_peers) + return true; + + peer_id pid(0); + policy& p = m_torrent.get_policy(); + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v4_endpoint(in); + char flags = detail::read_uint8(fin); + p.peer_from_tracker(adr, pid, peer_info::pex, flags); + } + } + catch (std::exception&) + { + throw protocol_error("invalid uT peer exchange message"); + } return true; } diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index 5a8acf512..6c6745f30 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -315,7 +315,14 @@ namespace libtorrent { INVARIANT_CHECK; - if (error) return; + if (error) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "*** web_peer_connection error: " + << error.message() << "\n"; +#endif + return; + } boost::shared_ptr t = associated_torrent().lock(); assert(t); From e208724bc3ebc012479d2b5a9ef91a39b7f50d81 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 15 Aug 2007 14:30:47 +0000 Subject: [PATCH 0038/1009] Updated listview menu creation. Added 'hidden' columns. --- deluge/ui/gtkui/listview.py | 79 +++++++++++++++++++++++++++++----- deluge/ui/gtkui/torrentview.py | 4 +- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 0dec730cc..6b3359614 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -80,9 +80,17 @@ def cell_data_ratio(column, cell, model, iter, data): class ListView: class ListViewColumn: def __init__(self, name, column_indices): + # Name is how a column is identified and is also the header self.name = name + # Column_indices holds the indexes to the liststore_columns that + # this column utilizes. It is stored as a list. self.column_indices = column_indices + # Column is a reference to the GtkTreeViewColumn object self.column = None + # If column is 'hidden' then it will not be visible and will not + # show up in any menu listing; it cannot be shown ever. + self.hidden = False + def __init__(self, treeview_widget=None): log.debug("ListView initialized..") @@ -100,8 +108,17 @@ class ListView: self.treeview.set_reorderable(True) self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + # Dictionary of 'header' or 'name' to ListViewColumn object self.columns = {} + # Column_index will keep track of the order that the visible columns + # are in. + self.column_index = [] + # The column types for the list store.. this may have more entries than + # visible columns due to some columns utilizing more than 1 liststore + # column and some columns being hidden. self.liststore_columns = [] + # The GtkMenu that is created after every addition, removal or reorder + self.menu = None def set_treeview(self, treeview_widget): self.treeview = treeview_widget @@ -115,11 +132,22 @@ class ListView: return self.columns[name].column_indices[0] def create_checklist_menu(self): - menu = gtk.Menu() - for column in self.columns.values(): + self.menu = gtk.Menu() + # Iterate through the column_index list to preserve order + for name in self.column_index: + column = self.columns[name] + # If the column is hidden, then we do not want to show it in the + # menu. + if column.hidden is True: + continue menuitem = gtk.CheckMenuItem(column.name) - menu.append(menuitem) - return menu + # If the column is currently visible, make sure it's set active + # (or checked) in the menu. + if column.column.get_visible() is True: + menuitem.set_active(True) + # Add the new checkmenuitem to the menu + self.menu.append(menuitem) + return self.menu def create_new_liststore(self): # Create a new liststore with added column and move the data from the @@ -145,9 +173,11 @@ class ListView: return - def add_text_column(self, header, visible=True): + def add_text_column(self, header, hidden=False): # Create a new column object and add it to the list self.liststore_columns.append(str) + # Add to the index list so we know the order of the visible columns. + self.column_index.append(header) self.columns[header] = self.ListViewColumn(header, [len(self.liststore_columns) - 1]) @@ -164,13 +194,18 @@ class ListView: column.set_expand(False) column.set_min_width(10) column.set_reorderable(True) - column.set_visible(visible) + column.set_visible(not hidden) self.treeview.append_column(column) + # Set hidden in the column + self.columns[header].hidden = hidden self.columns[header].column = column - + # Re-create the menu item because of the new column + self.create_checklist_menu() + return True - def add_func_column(self, header, function, column_types, sortid=0): + def add_func_column(self, header, function, column_types, sortid=0, + hidden=False): # Add the new column types to the list and keep track of the liststore # columns that this column object uses. # Set sortid to the column index relative the to column_types used. @@ -180,7 +215,9 @@ class ListView: for column_type in column_types: self.liststore_columns.append(column_type) column_indices.append(len(self.liststore_columns) - 1) - + + # Add to the index list so we know the order of the visible columns. + self.column_index.append(header) # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) @@ -202,18 +239,26 @@ class ListView: column.set_expand(False) column.set_min_width(10) column.set_reorderable(True) + column.set_visible(not hidden) self.treeview.append_column(column) + # Set hidden in the column + self.columns[header].hidden = hidden self.columns[header].column = column + # Re-create the menu item because of the new column + self.create_checklist_menu() return True - def add_progress_column(self, header): + def add_progress_column(self, header, hidden=False): # For the progress value self.liststore_columns.append(float) # For the text self.liststore_columns.append(str) column_indices = [len(self.liststore_columns) - 2, len(self.liststore_columns) - 1] + # Add to the index list so we know the order of the visible columns. + self.column_index.append(header) + # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) @@ -230,18 +275,25 @@ class ListView: column.set_expand(False) column.set_min_width(10) column.set_reorderable(True) + column.set_visible(not hidden) self.treeview.append_column(column) + # Set hidden in the column + self.columns[header].hidden = hidden self.columns[header].column = column + # Re-create the menu item because of the new column + self.create_checklist_menu() return True - def add_texticon_column(self, header): + def add_texticon_column(self, header, hidden=False): # For icon self.liststore_columns.append(gtk.gdk.Pixbuf) # For text self.liststore_columns.append(str) column_indices = [len(self.liststore_columns) - 2, len(self.liststore_columns) - 1] + # Add to the index list so we know the order of the visible columns. + self.column_index.append(header) self.columns[header] = self.ListViewColumn(header, column_indices) # Create new list with the added columns @@ -261,7 +313,12 @@ class ListView: column.pack_start(render, expand=True) column.add_attribute(render, 'text', self.columns[header].column_indices[1]) + column.set_visible(not hidden) self.treeview.append_column(column) + # Set hidden in the column + self.columns[header].hidden = hidden self.columns[header].column = column + # Re-create the menu item because of the new column + self.create_checklist_menu() return True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 1d6fdc03c..9c10cae0f 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -55,7 +55,7 @@ class TorrentView(listview.ListView): # Set the treeview used in listview with the one from our glade file self.set_treeview(self.window.main_glade.get_widget("torrent_view")) - self.add_text_column("torrent_id", visible=False) + self.add_text_column("torrent_id", hidden=True) self.add_texticon_column("Name") self.add_func_column("Size", listview.cell_data_size, @@ -81,7 +81,7 @@ class TorrentView(listview.ListView): [float]) self.window.main_glade.get_widget("menu_columns").set_submenu( - self.create_checklist_menu()) + self.menu) ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the From 8d23e8ee806def79744ae70a7f214123a45c2d74 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 17 Aug 2007 03:16:56 +0000 Subject: [PATCH 0039/1009] The column menu list now toggles visibility of the columns. --- deluge/ui/gtkui/listview.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 6b3359614..262b05dc9 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -110,8 +110,7 @@ class ListView: # Dictionary of 'header' or 'name' to ListViewColumn object self.columns = {} - # Column_index will keep track of the order that the visible columns - # are in. + # Column_index keeps track of the order of the visible columns. self.column_index = [] # The column types for the list store.. this may have more entries than # visible columns due to some columns utilizing more than 1 liststore @@ -130,7 +129,15 @@ class ListView: return self.columns[name].column_indices else: return self.columns[name].column_indices[0] - + + def on_menuitem_toggled(self, widget): + # Get the column name from the widget + name = widget.get_child().get_text() + + # Set the column's visibility based on the widgets active state + self.columns[name].column.set_visible(widget.get_active()) + return + def create_checklist_menu(self): self.menu = gtk.Menu() # Iterate through the column_index list to preserve order @@ -145,6 +152,8 @@ class ListView: # (or checked) in the menu. if column.column.get_visible() is True: menuitem.set_active(True) + # Connect to the 'toggled' event + menuitem.connect("toggled", self.on_menuitem_toggled) # Add the new checkmenuitem to the menu self.menu.append(menuitem) return self.menu From 98ecdd12a985ed2b43db04af06086c8fd2c64dd6 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 17 Aug 2007 09:55:33 +0000 Subject: [PATCH 0040/1009] Added remove_column() to ListView. --- deluge/ui/gtkui/listview.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 262b05dc9..0759b8214 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -179,7 +179,36 @@ class ListView: self.liststore = new_list self.treeview.set_model(self.liststore) + + return + + def remove_column(self, header): + """Removes the column with the name 'header' from the listview""" + # Start by removing this column from the treeview + self.treeview.remove_column(self.columns[header].column) + # Get the column indices + column_indices = self.columns[header].column_indices + # Delete the column + del self.columns[header] + self.column_index.remove(header) + # Shift the column_indices values of those columns effected by the + # removal. Any column_indices > the one removed. + for column in self.columns.values(): + if column.column_indices[0] > column_indices[0]: + # We need to shift this column_indices + for index in column.column_indices: + index = index - len(column_indices) + # Remove from the liststore columns list + for index in column_indices: + del self.liststore_columns[index] + + # Create a new liststore + self.create_new_liststore() + + # Re-create the menu + self.create_checklist_menu() + return def add_text_column(self, header, hidden=False): From 38dd216e16bbd037f8033a267c0dac8c855a68b1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 18 Aug 2007 11:16:31 +0000 Subject: [PATCH 0041/1009] Updates to ListView and the TorrentQueue plugin. --- deluge/plugins/queue/queue/core.py | 11 ++++- deluge/plugins/queue/queue/gtkui.py | 30 +++++++++++- deluge/ui/gtkui/listview.py | 73 ++++++++++++++++++++++++----- deluge/ui/gtkui/torrentview.py | 23 +++++++-- 4 files changed, 118 insertions(+), 19 deletions(-) diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 09936e1f1..1fdbc0d8a 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -132,12 +132,19 @@ class Core(dbus.service.Object): @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", in_signature="", out_signature="as") - def get_queue(self): + def get_queue_list(self): """Returns the queue list. """ log.debug("Getting queue list") return self.queue.queue - + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", + in_signature="s", out_signature="i") + def get_position(self, torrent_id): + """Returns the queue position of torrent_id""" + log.debug("Getting queue position for %s", torrent_id) + return self.queue[torrent_id] + ## Signals ## @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge.Queue", signature="") diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index e9f6839de..0fb08b0ee 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -33,6 +33,19 @@ import logging +try: + import dbus, dbus.service + dbus_version = getattr(dbus, "version", (0,0,0)) + if dbus_version >= (0,41,0) and dbus_version < (0,80,0): + import dbus.glib + elif dbus_version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + else: + pass +except: dbus_imported = False +else: dbus_imported = True + # Get the logger log = logging.getLogger("deluge") @@ -40,6 +53,21 @@ class GtkUI: def __init__(self, plugin_manager): log.debug("Queue GtkUI plugin initalized..") self.plugin = plugin_manager + # Get a reference to the core portion of the plugin + bus = dbus.SessionBus() + proxy = bus.get_object("org.deluge_torrent.Deluge", + "/org/deluge_torrent/Plugin/Queue") + self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge.Queue") # Get the torrentview component from the plugin manager self.torrentview = self.plugin.get_torrentview() - + # Add the '#' column at the first position + self.torrentview.add_text_column("#", + col_type=int, + position=0, + get_function=self.column_get_function) + + def column_get_function(self, torrent_id): + """Returns the queue position for torrent_id""" + # Return the value + 1 because we want the queue list to start at 1 + # for the user display. + return self.core.get_position(torrent_id) + 1 diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 0759b8214..1c9738f0c 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -87,6 +87,9 @@ class ListView: self.column_indices = column_indices # Column is a reference to the GtkTreeViewColumn object self.column = None + # The get_function is called when a column is in need of an update + # This is primarily used by plugins. + self.get_function = None # If column is 'hidden' then it will not be visible and will not # show up in any menu listing; it cannot be shown ever. self.hidden = False @@ -162,7 +165,7 @@ class ListView: # Create a new liststore with added column and move the data from the # old one to the new one. new_list = gtk.ListStore(*tuple(self.liststore_columns)) - + # This function is used in the liststore.foreach method with user_data # being the new liststore and the columns list def copy_row(model, path, row, user_data): @@ -211,14 +214,25 @@ class ListView: return - def add_text_column(self, header, hidden=False): + def add_text_column(self, header, col_type=str, hidden=False, + position=None, get_function=None): # Create a new column object and add it to the list - self.liststore_columns.append(str) + self.liststore_columns.append(col_type) # Add to the index list so we know the order of the visible columns. - self.column_index.append(header) + if position is not None: + self.column_index.insert(position, header) + else: + self.column_index.append(header) + self.columns[header] = self.ListViewColumn(header, [len(self.liststore_columns) - 1]) - + + # Set the get_function.. This function is used mainly for plugins. + # You can have your listview call this function to update the column + # value. + if get_function is not None: + self.columns[header].get_function = get_function + # Create a new list with the added column self.create_new_liststore() @@ -233,7 +247,10 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) column.set_visible(not hidden) - self.treeview.append_column(column) + if position is not None: + self.treeview.insert_column(column, position) + else: + self.treeview.append_column(column) # Set hidden in the column self.columns[header].hidden = hidden self.columns[header].column = column @@ -243,7 +260,7 @@ class ListView: return True def add_func_column(self, header, function, column_types, sortid=0, - hidden=False): + hidden=False, position=None, get_function=None): # Add the new column types to the list and keep track of the liststore # columns that this column object uses. # Set sortid to the column index relative the to column_types used. @@ -256,6 +273,13 @@ class ListView: # Add to the index list so we know the order of the visible columns. self.column_index.append(header) + + # Add to the index list so we know the order of the visible columns. + if position is not None: + self.column_index.insert(position, header) + else: + self.column_index.append(header) + # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) @@ -278,7 +302,10 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) column.set_visible(not hidden) - self.treeview.append_column(column) + if position is not None: + self.treeview.insert_column(column, position) + else: + self.treeview.append_column(column) # Set hidden in the column self.columns[header].hidden = hidden self.columns[header].column = column @@ -287,7 +314,8 @@ class ListView: return True - def add_progress_column(self, header, hidden=False): + def add_progress_column(self, header, hidden=False, position=None, + get_function=None): # For the progress value self.liststore_columns.append(float) # For the text @@ -296,7 +324,12 @@ class ListView: len(self.liststore_columns) - 1] # Add to the index list so we know the order of the visible columns. self.column_index.append(header) - + + # Add to the index list so we know the order of the visible columns. + if position is not None: + self.column_index.insert(position, header) + else: + self.column_index.append(header) # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) @@ -314,7 +347,10 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) column.set_visible(not hidden) - self.treeview.append_column(column) + if position is not None: + self.treeview.insert_column(column, position) + else: + self.treeview.append_column(column) # Set hidden in the column self.columns[header].hidden = hidden self.columns[header].column = column @@ -323,7 +359,8 @@ class ListView: return True - def add_texticon_column(self, header, hidden=False): + def add_texticon_column(self, header, hidden=False, position=None, + get_function=None): # For icon self.liststore_columns.append(gtk.gdk.Pixbuf) # For text @@ -332,6 +369,13 @@ class ListView: len(self.liststore_columns) - 1] # Add to the index list so we know the order of the visible columns. self.column_index.append(header) + + # Add to the index list so we know the order of the visible columns. + if position is not None: + self.column_index.insert(position, header) + else: + self.column_index.append(header) + self.columns[header] = self.ListViewColumn(header, column_indices) # Create new list with the added columns @@ -352,7 +396,10 @@ class ListView: column.add_attribute(render, 'text', self.columns[header].column_indices[1]) column.set_visible(not hidden) - self.treeview.append_column(column) + if position is not None: + self.treeview.insert_column(column, position) + else: + self.treeview.append_column(column) # Set hidden in the column self.columns[header].hidden = hidden self.columns[header].column = column diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 9c10cae0f..f2863bcdf 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -146,8 +146,8 @@ class TorrentView(listview.ListView): "upload_payload_rate", "eta"] status = functions.get_torrent_status(self.core, torrent_id, status_keys) - # Insert the row with info provided from core - self.liststore.append([ + + row_list = [ torrent_id, None, status["name"], @@ -162,7 +162,24 @@ class TorrentView(listview.ListView): status["upload_payload_rate"], status["eta"], 0.0 - ]) + ] + + # Insert any column info from get_functions.. this is usually from + # plugins + for column in self.columns.values(): + if column.get_function is not None: + if len(column.column_indices) == 1: + row_list.insert(column.column_indices[0], + column.get_function(torrent_id)) + else: + result = column.get_function(torrent_id) + r_index = 0 + for index in column.column_indices: + row_list.insert(index, result[r_index]) + r_index = r_index + 1 + + # Insert the row with info provided from core + self.liststore.append(row_list) def remove_row(self, torrent_id): """Removes a row with torrent_id""" From d12e333e3ae905cde9a76d43f0abf34365800a9b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 20 Aug 2007 12:47:54 +0000 Subject: [PATCH 0042/1009] Updates to the TorrentQueue plugin. Added 'add_toolbutton()' and 'add_separator()' to ToolBar. Added 'get_toolbar()' and 'get_selected_torrents()' to PluginManager. --- deluge/plugins/queue/queue/gtkui.py | 39 +++++++++++++++++++++++ deluge/ui/functions.py | 28 ----------------- deluge/ui/gtkui/listview.py | 1 + deluge/ui/gtkui/pluginmanager.py | 8 +++++ deluge/ui/gtkui/toolbar.py | 48 ++++++++++++++++++++--------- deluge/ui/gtkui/torrentview.py | 12 ++++---- 6 files changed, 87 insertions(+), 49 deletions(-) diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 0fb08b0ee..31cba2b50 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -58,6 +58,11 @@ class GtkUI: proxy = bus.get_object("org.deluge_torrent.Deluge", "/org/deluge_torrent/Plugin/Queue") self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge.Queue") + + # Connect to the 'torrent_queue_changed' signal + self.core.connect_to_signal("torrent_queue_changed", + self.torrent_queue_changed_signal) + # Get the torrentview component from the plugin manager self.torrentview = self.plugin.get_torrentview() # Add the '#' column at the first position @@ -65,7 +70,41 @@ class GtkUI: col_type=int, position=0, get_function=self.column_get_function) + # Add a toolbar buttons + self.plugin.get_toolbar().add_separator() + self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-up", + label="Queue Up", + tooltip="Queue selected torrents up", + callback=self.on_queueup_toolbutton_clicked) + + self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-down", + label="Queue Down", + tooltip="Queue selected torrents down", + callback=self.on_queuedown_toolbutton_clicked) + + def on_queuedown_toolbutton_clicked(self, widget): + log.debug("Queue down toolbutton clicked.") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_down(torrent_id) + return + + def on_queueup_toolbutton_clicked(self, widget): + log.debug("Queue Up toolbutton clicked.") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_up(torrent_id) + return + def torrent_queue_changed_signal(self): + """This function is called whenever we receive a 'torrent_queue_changed' + signal from the core plugin. + """ + log.debug("torrent_queue_changed signal received..") + return + def column_get_function(self, torrent_id): """Returns the queue position for torrent_id""" # Return the value + 1 because we want the queue list to start at 1 diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 089585ea5..b755fea60 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -112,34 +112,6 @@ def resume_torrent(torrent_ids): for torrent_id in torrent_ids: core.resume_torrent(torrent_id) -def queue_top(torrent_ids): - """Attempts to queue all torrent_ids to the top""" - log.debug("Attempting to queue to top these torrents: %s", torrent_ids) - core = get_core_plugin("Queue") - for torrent_id in torrent_ids: - core.queue_top(torrent_id) - -def queue_up(torrent_ids): - """Attempts to queue all torrent_ids up""" - log.debug("Attempting to queue up these torrents: %s", torrent_ids) - core = get_core() - for torrent_id in torrent_ids: - core.queue_up(torrent_id) - -def queue_down(torrent_ids): - """Attempts to queue all torrent_ids down""" - log.debug("Attempting to queue down these torrents: %s", torrent_ids) - core = get_core() - for torrent_id in torrent_ids: - core.queue_down(torrent_id) - -def queue_bottom(torrent_ids): - """Attempts to queue all torrent_ids to the bottom""" - log.debug("Attempting to queue to bottom these torrents: %s", torrent_ids) - core = get_core() - for torrent_id in torrent_ids: - core.queue_bottom(torrent_id) - def get_torrent_info(core, torrent_id): """Builds the info dictionary and returns it""" info = core.get_torrent_info(torrent_id) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 1c9738f0c..b8af8878a 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -124,6 +124,7 @@ class ListView: def set_treeview(self, treeview_widget): self.treeview = treeview_widget + self.treeview.set_model(self.liststore) return def get_column_index(self, name): diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 527d0cd12..7c19954e3 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -70,3 +70,11 @@ class PluginManager: def get_torrentview(self): """Returns a reference to the torrentview component""" return self._gtkui.mainwindow.torrentview + + def get_toolbar(self): + """Returns a reference to the toolbar component""" + return self._gtkui.mainwindow.toolbar + + def get_selected_torrents(self): + """Returns a list of the selected torrent_ids""" + return self._gtkui.mainwindow.torrentview.get_selected_torrents() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 872046119..65ae7683e 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -44,7 +44,7 @@ class ToolBar: def __init__(self, window): log.debug("ToolBar Init..") self.window = window - + self.toolbar = self.window.main_glade.get_widget("toolbar") ### Connect Signals ### self.window.main_glade.signal_autoconnect({ "on_toolbutton_add_clicked": self.on_toolbutton_add_clicked, @@ -52,15 +52,43 @@ class ToolBar: "on_toolbutton_clear_clicked": self.on_toolbutton_clear_clicked, "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked, "on_toolbutton_resume_clicked": self.on_toolbutton_resume_clicked, - "on_toolbutton_queueup_clicked": \ - self.on_toolbutton_queueup_clicked, - "on_toolbutton_queuedown_clicked": \ - self.on_toolbutton_queuedown_clicked, "on_toolbutton_preferences_clicked": \ self.on_toolbutton_preferences_clicked, "on_toolbutton_plugins_clicked": \ self.on_toolbutton_plugins_clicked, }) + + def add_toolbutton(self, callback, label=None, image=None, stock=None, + tooltip=None): + """Adds a toolbutton to the toolbar""" + # Create the button + toolbutton = gtk.ToolButton(stock) + if label is not None: + toolbutton.set_label(label) + if image is not None: + toolbutton.set_icon_widget(image) + # Set the tooltip + if tooltip is not None: + tip = gtk.Tooltips() + tip.set_tip(toolbutton, tooltip) + + # Connect the 'clicked' event callback + toolbutton.connect("clicked", callback) + + # Append the button to the toolbar + self.toolbar.insert(toolbutton, -1) + + return + + def add_separator(self, position=None): + """Adds a separator toolitem""" + sep = gtk.SeparatorToolItem() + if position is not None: + self.toolbar.insert(sep, position) + else: + # Append the separator + self.toolbar.insert(sep, -1) + return ### Callbacks ### def on_toolbutton_add_clicked(self, data): @@ -88,16 +116,6 @@ class ToolBar: # Use the menubar's calbacks self.window.menubar.on_menuitem_resume_activate(data) - def on_toolbutton_queueup_clicked(self, data): - log.debug("on_toolbutton_queueup_clicked") - # Use the menubar's callbacks - self.window.menubar.on_menuitem_queueup_activate(data) - - def on_toolbutton_queuedown_clicked(self, data): - log.debug("on_toolbutton_queuedown_clicked") - # Use the menubar's callbacks - self.window.menubar.on_menuitem_queuedown_activate(data) - def on_toolbutton_preferences_clicked(self, data): log.debug("on_toolbutton_preferences_clicked") # Use the menubar's callbacks diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index f2863bcdf..0c5cc189b 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -47,14 +47,14 @@ log = logging.getLogger("deluge") class TorrentView(listview.ListView): def __init__(self, window): - # Call the ListView constructor - listview.ListView.__init__(self) - log.debug("TorrentView Init..") self.window = window + # Call the ListView constructor + listview.ListView.__init__(self, + self.window.main_glade.get_widget("torrent_view")) + log.debug("TorrentView Init..") self.core = functions.get_core() - # Set the treeview used in listview with the one from our glade file - self.set_treeview(self.window.main_glade.get_widget("torrent_view")) - + + # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) self.add_texticon_column("Name") self.add_func_column("Size", From 810c0813ce4232f5540ce0162d379ad115ba9aad Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 21 Aug 2007 14:43:15 +0000 Subject: [PATCH 0043/1009] Start work on the addition of 'status_field' to ListViewColumn. --- deluge/ui/gtkui/listview.py | 34 +++++++++------- deluge/ui/gtkui/torrentview.py | 71 +++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 34 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index b8af8878a..a0d26377c 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -90,6 +90,9 @@ class ListView: # The get_function is called when a column is in need of an update # This is primarily used by plugins. self.get_function = None + # This is the name of the status field that the column will query + # the core for if an update is called. + self.status_field = None # If column is 'hidden' then it will not be visible and will not # show up in any menu listing; it cannot be shown ever. self.hidden = False @@ -216,7 +219,8 @@ class ListView: return def add_text_column(self, header, col_type=str, hidden=False, - position=None, get_function=None): + position=None, get_function=None, + status_field=None): # Create a new column object and add it to the list self.liststore_columns.append(col_type) # Add to the index list so we know the order of the visible columns. @@ -233,6 +237,8 @@ class ListView: # value. if get_function is not None: self.columns[header].get_function = get_function + + self.columns[header].status_field = status_field # Create a new list with the added column self.create_new_liststore() @@ -261,7 +267,8 @@ class ListView: return True def add_func_column(self, header, function, column_types, sortid=0, - hidden=False, position=None, get_function=None): + hidden=False, position=None, get_function=None, + status_field=None): # Add the new column types to the list and keep track of the liststore # columns that this column object uses. # Set sortid to the column index relative the to column_types used. @@ -272,9 +279,6 @@ class ListView: self.liststore_columns.append(column_type) column_indices.append(len(self.liststore_columns) - 1) - # Add to the index list so we know the order of the visible columns. - self.column_index.append(header) - # Add to the index list so we know the order of the visible columns. if position is not None: self.column_index.insert(position, header) @@ -283,7 +287,9 @@ class ListView: # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) - + + self.columns[header].status_field = status_field + # Create new list with the added columns self.create_new_liststore() @@ -316,16 +322,14 @@ class ListView: return True def add_progress_column(self, header, hidden=False, position=None, - get_function=None): + get_function=None, + status_field=None): # For the progress value self.liststore_columns.append(float) # For the text self.liststore_columns.append(str) column_indices = [len(self.liststore_columns) - 2, len(self.liststore_columns) - 1] - # Add to the index list so we know the order of the visible columns. - self.column_index.append(header) - # Add to the index list so we know the order of the visible columns. if position is not None: self.column_index.insert(position, header) @@ -333,7 +337,8 @@ class ListView: self.column_index.append(header) # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) - + + self.columns[header].status_field = status_field # Create new list with the added columns self.create_new_liststore() @@ -361,15 +366,14 @@ class ListView: return True def add_texticon_column(self, header, hidden=False, position=None, - get_function=None): + get_function=None, + status_field=None): # For icon self.liststore_columns.append(gtk.gdk.Pixbuf) # For text self.liststore_columns.append(str) column_indices = [len(self.liststore_columns) - 2, len(self.liststore_columns) - 1] - # Add to the index list so we know the order of the visible columns. - self.column_index.append(header) # Add to the index list so we know the order of the visible columns. if position is not None: @@ -378,7 +382,7 @@ class ListView: self.column_index.append(header) self.columns[header] = self.ListViewColumn(header, column_indices) - + self.columns[header].status_field = status_field # Create new list with the added columns self.create_new_liststore() diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 0c5cc189b..40b182bb7 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -56,29 +56,36 @@ class TorrentView(listview.ListView): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) - self.add_texticon_column("Name") + self.add_texticon_column("Name", status_field=["name"]) self.add_func_column("Size", listview.cell_data_size, - [long]) - self.add_progress_column("Progress") + [long], + status_field=["total_size"]) + self.add_progress_column("Progress", status_field=["progress", "state"]) self.add_func_column("Seeders", - listview.cell_data_peer, - [int, int]) + listview.cell_data_peer, + [int, int], + status_field=["num_seeds", "num_seeds"]) self.add_func_column("Peers", - listview.cell_data_peer, - [int, int]) + listview.cell_data_peer, + [int, int], + status_field=["num_peers", "num_peers"]) self.add_func_column("Down Speed", - listview.cell_data_speed, - [float]) + listview.cell_data_speed, + [float], + status_field=["download_payload_rate"]) self.add_func_column("Up Speed", - listview.cell_data_speed, - [float]) + listview.cell_data_speed, + [float], + status_field=["upload_payload_rate"]) self.add_func_column("ETA", listview.cell_data_time, - [int]) + [int], + status_field=["eta"]) self.add_func_column("Ratio", listview.cell_data_ratio, - [float]) + [float], + status_field=["ratio"]) self.window.main_glade.get_widget("menu_columns").set_submenu( self.menu) @@ -93,19 +100,45 @@ class TorrentView(listview.ListView): self.treeview.get_selection().connect("changed", self.on_selection_changed) - def update(self): - """Update the view, this is likely called by a timer""" + def update(self, columns=None): + """Update the view. If columns is not None, it will attempt to only + update those columns selected. + """ # This function is used for the foreach method of the treemodel def update_row(model, path, row, user_data): torrent_id = self.liststore.get_value(row, 0) - status_keys = ["progress", "state", "num_seeds", - "num_peers", "download_payload_rate", "upload_payload_rate", - "eta"] + status_keys = [] + if columns is None: + # Iterate through the list of columns and only add the + # 'status-fields' of the visible ones. + for column in self.columns.values(): + # Make sure column is visible and has 'status_field' set. + # If not, we can ignore it. + if column.column.get_visible() is True \ + and column.hidden is False \ + and column.status_field is not None: + for field in column.status_field: + status_keys.append(field) + else: + # Iterate through supplied list of columns to update + for column in columns: + if self.columns[column.name].column.get_visible() is True \ + and self.columns[column.name].hidden is False \ + and self.columns[column.name].status_field is not None: + for field in self.columns[column.name].status_field: + status_keys.append(field) + + # If there is nothing in status_keys then we must not continue + if status_keys is []: + return + + # Remove duplicates from status_key list + status_keys = list(set(status_keys)) status = functions.get_torrent_status(self.core, torrent_id, status_keys) # Set values for each column in the row - + # FIXME: Need to update based on 'status_keys' self.liststore.set_value(row, self.get_column_index("Progress")[0], status["progress"]*100) From 5611037db21acf550da5092cdf2db978d1e4815c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 22 Aug 2007 06:23:16 +0000 Subject: [PATCH 0044/1009] Modified update() in TorrentView. It is now able to update only the requested columns and only columns which are visible. --- deluge/core/torrent.py | 8 +++-- deluge/ui/gtkui/torrentview.py | 54 ++++++++++++++++------------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 2197fc668..71e020092 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -71,13 +71,16 @@ class Torrent: # Create the full dictionary status = self.handle.status() + # Adjust progress to be 0-100 value + progress = status.progress*100 + full_status = { "name": self.handle.torrent_info().name(), "total_size": self.handle.torrent_info().total_size(), "num_pieces": self.handle.status().num_pieces, "state": int(status.state), "paused": status.paused, - "progress": status.progress, + "progress": progress, "next_announce": status.next_announce.seconds, "total_payload_download": status.total_payload_download, "total_payload_upload": status.total_payload_upload, @@ -86,7 +89,8 @@ class Torrent: "num_peers": status.num_peers, "num_seeds": status.num_seeds, "total_wanted": status.total_wanted, - "eta": self.get_eta() + "eta": self.get_eta(), + "ratio": 0.0 } # Create the desired status dictionary and return it diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 40b182bb7..961efef5c 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -107,7 +107,10 @@ class TorrentView(listview.ListView): # This function is used for the foreach method of the treemodel def update_row(model, path, row, user_data): torrent_id = self.liststore.get_value(row, 0) + # Store the 'status_fields' we need to send to core status_keys = [] + # Store the actual columns we will be updating + columns_to_update = [] if columns is None: # Iterate through the list of columns and only add the # 'status-fields' of the visible ones. @@ -118,7 +121,8 @@ class TorrentView(listview.ListView): and column.hidden is False \ and column.status_field is not None: for field in column.status_field: - status_keys.append(field) + status_keys.append(field) + columns_to_update.append(column.name) else: # Iterate through supplied list of columns to update for column in columns: @@ -127,6 +131,7 @@ class TorrentView(listview.ListView): and self.columns[column.name].status_field is not None: for field in self.columns[column.name].status_field: status_keys.append(field) + columns_to_update.append(column) # If there is nothing in status_keys then we must not continue if status_keys is []: @@ -138,34 +143,25 @@ class TorrentView(listview.ListView): status_keys) # Set values for each column in the row - # FIXME: Need to update based on 'status_keys' - self.liststore.set_value(row, - self.get_column_index("Progress")[0], - status["progress"]*100) - self.liststore.set_value(row, - self.get_column_index("Progress")[1], - status["state"]) - self.liststore.set_value(row, - self.get_column_index("Seeders")[0], - status["num_seeds"]) - self.liststore.set_value(row, - self.get_column_index("Seeders")[1], - status["num_seeds"]) - self.liststore.set_value(row, - self.get_column_index("Peers")[0], - status["num_peers"]) - self.liststore.set_value(row, - self.get_column_index("Peers")[1], - status["num_peers"]) - self.liststore.set_value(row, - self.get_column_index("Down Speed"), - status["download_payload_rate"]) - self.liststore.set_value(row, - self.get_column_index("Up Speed"), - status["upload_payload_rate"]) - self.liststore.set_value(row, - self.get_column_index("ETA"), - status["eta"]) + for column in columns_to_update: + if type(self.get_column_index(column)) is not list: + # We only have a single list store column we need to update + self.liststore.set_value(row, + self.get_column_index(column), + status[self.columns[column].status_field[0]]) + else: + # We have more than 1 liststore column to update + i = 0 + for index in self.get_column_index(column): + # Only update the column if the status field exists + try: + self.liststore.set_value(row, + index, + status[self.columns[column].status_field[i]]) + except: + pass + i = i + 1 + # Iterates through every row and updates them accordingly if self.liststore is not None: From f185f19cfb298978b51376871150d9693fb82b96 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 22 Aug 2007 08:24:40 +0000 Subject: [PATCH 0045/1009] Plugins can now register 'status_fields' with core for use with get_status(). Updates to TorrentQueue plugin. --- deluge/core/core.py | 6 +++++- deluge/core/pluginmanager.py | 17 +++++++++++++++++ deluge/plugins/queue/queue/core.py | 7 ++++++- deluge/plugins/queue/queue/gtkui.py | 5 ++++- deluge/ui/gtkui/torrentview.py | 9 +++++---- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index bb84e7081..a039a2ca9 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -155,8 +155,12 @@ class Core(dbus.service.Object): nkeys = [] for key in keys: nkeys.append(str(key)) - # Pickle the status dictionary from the torrent and return it + # Pickle the status dictionary from the torrent status = self.torrents[torrent_id].get_status(nkeys) + # Get the leftover fields and ask the plugin manager to fill them + leftover_fields = list(set(nkeys) - set(status.keys())) + if len(leftover_fields) > 0: + status.update(self.plugins.get_status(torrent_id, leftover_fields)) status = pickle.dumps(status) return status diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index b40bc5522..27fcc48ed 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -47,6 +47,8 @@ class PluginManager: "post_torrent_remove": [] } + self.status_fields = {} + # This will load any .eggs in the plugins folder inside the main # deluge egg.. Need to scan the local plugin folder too. @@ -70,6 +72,21 @@ class PluginManager: def __getitem__(self, key): return self.plugins[key] + def register_status_field(self, field, function): + """Register a new status field. This can be used in the same way the + client requests other status information from core.""" + self.status_fields[field] = function + + def get_status(self, torrent_id, fields): + status = {} + for field in fields: + try: + status[field] = self.status_fields[field](torrent_id) + except KeyError: + log.warning("Status field %s is not registered with the\ + PluginManager.") + return status + def register_hook(self, hook, function): """Register a hook function with the plugin manager""" try: diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 1fdbc0d8a..87a9f3168 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -69,6 +69,7 @@ class Core(dbus.service.Object): self.plugin.register_hook("post_torrent_add", self.post_torrent_add) self.plugin.register_hook("post_torrent_remove", self.post_torrent_remove) + self.plugin.register_status_field("queue", self.status_field_queue) log.info("Queue Core plugin initialized..") @@ -80,7 +81,11 @@ class Core(dbus.service.Object): def post_torrent_remove(self, torrent_id): if torrent_id is not None: self.queue.remove(torrent_id) - + + ## Status field function ## + def status_field_queue(self, torrent_id): + return self.queue[torrent_id]+1 + ## Queueing functions ## @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", in_signature="s", out_signature="") diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 31cba2b50..ff1ea661c 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -69,7 +69,8 @@ class GtkUI: self.torrentview.add_text_column("#", col_type=int, position=0, - get_function=self.column_get_function) + get_function=self.column_get_function, + status_field=["queue"]) # Add a toolbar buttons self.plugin.get_toolbar().add_separator() self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-up", @@ -103,6 +104,8 @@ class GtkUI: signal from the core plugin. """ log.debug("torrent_queue_changed signal received..") + # We only need to update the queue column + self.torrentview.update(["#"]) return def column_get_function(self, torrent_id): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 961efef5c..103757681 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -126,10 +126,10 @@ class TorrentView(listview.ListView): else: # Iterate through supplied list of columns to update for column in columns: - if self.columns[column.name].column.get_visible() is True \ - and self.columns[column.name].hidden is False \ - and self.columns[column.name].status_field is not None: - for field in self.columns[column.name].status_field: + if self.columns[column].column.get_visible() is True \ + and self.columns[column].hidden is False \ + and self.columns[column].status_field is not None: + for field in self.columns[column].status_field: status_keys.append(field) columns_to_update.append(column) @@ -169,6 +169,7 @@ class TorrentView(listview.ListView): def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" + ## REWRITE TO USE STATUS FIELDS # Get the status and info dictionaries status_keys = ["name", "total_size", "progress", "state", "num_seeds", "num_peers", "download_payload_rate", From f3c80eb816b07041d68805166b1871a18cbc2e92 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 22 Aug 2007 08:53:12 +0000 Subject: [PATCH 0046/1009] Remove idea of a 'get_function'. Updated add_row() to use new status_fields. --- deluge/plugins/queue/queue/gtkui.py | 8 +- deluge/ui/gtkui/torrentview.py | 173 +++++++++++----------------- 2 files changed, 71 insertions(+), 110 deletions(-) diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index ff1ea661c..0a03d44eb 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -69,7 +69,6 @@ class GtkUI: self.torrentview.add_text_column("#", col_type=int, position=0, - get_function=self.column_get_function, status_field=["queue"]) # Add a toolbar buttons self.plugin.get_toolbar().add_separator() @@ -107,9 +106,4 @@ class GtkUI: # We only need to update the queue column self.torrentview.update(["#"]) return - - def column_get_function(self, torrent_id): - """Returns the queue position for torrent_id""" - # Return the value + 1 because we want the queue list to start at 1 - # for the user display. - return self.core.get_position(torrent_id) + 1 + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 103757681..60c9eff67 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -56,7 +56,7 @@ class TorrentView(listview.ListView): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) - self.add_texticon_column("Name", status_field=["name"]) + self.add_text_column("Name", status_field=["name"]) self.add_func_column("Size", listview.cell_data_size, [long], @@ -104,113 +104,80 @@ class TorrentView(listview.ListView): """Update the view. If columns is not None, it will attempt to only update those columns selected. """ - # This function is used for the foreach method of the treemodel - def update_row(model, path, row, user_data): - torrent_id = self.liststore.get_value(row, 0) - # Store the 'status_fields' we need to send to core - status_keys = [] - # Store the actual columns we will be updating - columns_to_update = [] - if columns is None: - # Iterate through the list of columns and only add the - # 'status-fields' of the visible ones. - for column in self.columns.values(): - # Make sure column is visible and has 'status_field' set. - # If not, we can ignore it. - if column.column.get_visible() is True \ - and column.hidden is False \ - and column.status_field is not None: - for field in column.status_field: - status_keys.append(field) - columns_to_update.append(column.name) - else: - # Iterate through supplied list of columns to update - for column in columns: - if self.columns[column].column.get_visible() is True \ - and self.columns[column].hidden is False \ - and self.columns[column].status_field is not None: - for field in self.columns[column].status_field: - status_keys.append(field) - columns_to_update.append(column) - - # If there is nothing in status_keys then we must not continue - if status_keys is []: - return - - # Remove duplicates from status_key list - status_keys = list(set(status_keys)) - status = functions.get_torrent_status(self.core, torrent_id, - status_keys) - - # Set values for each column in the row - for column in columns_to_update: - if type(self.get_column_index(column)) is not list: - # We only have a single list store column we need to update - self.liststore.set_value(row, - self.get_column_index(column), - status[self.columns[column].status_field[0]]) - else: - # We have more than 1 liststore column to update - i = 0 - for index in self.get_column_index(column): - # Only update the column if the status field exists - try: - self.liststore.set_value(row, - index, - status[self.columns[column].status_field[i]]) - except: - pass - i = i + 1 - - # Iterates through every row and updates them accordingly if self.liststore is not None: - self.liststore.foreach(update_row, None) - - def add_row(self, torrent_id): - """Adds a new torrent row to the treeview""" - ## REWRITE TO USE STATUS FIELDS - # Get the status and info dictionaries - status_keys = ["name", "total_size", "progress", "state", - "num_seeds", "num_peers", "download_payload_rate", - "upload_payload_rate", "eta"] + self.liststore.foreach(self.update_row, columns) + + def update_row(self, model, path, row, columns=None): + torrent_id = self.liststore.get_value(row, + self.columns["torrent_id"].column_indices[0]) + # Store the 'status_fields' we need to send to core + status_keys = [] + # Store the actual columns we will be updating + columns_to_update = [] + if columns is None: + # Iterate through the list of columns and only add the + # 'status-fields' of the visible ones. + for column in self.columns.values(): + # Make sure column is visible and has 'status_field' set. + # If not, we can ignore it. + if column.column.get_visible() is True \ + and column.hidden is False \ + and column.status_field is not None: + for field in column.status_field: + status_keys.append(field) + columns_to_update.append(column.name) + else: + # Iterate through supplied list of columns to update + for column in columns: + if self.columns[column].column.get_visible() is True \ + and self.columns[column].hidden is False \ + and self.columns[column].status_field is not None: + for field in self.columns[column].status_field: + status_keys.append(field) + columns_to_update.append(column) + + # If there is nothing in status_keys then we must not continue + if status_keys is []: + return + + # Remove duplicates from status_key list + status_keys = list(set(status_keys)) status = functions.get_torrent_status(self.core, torrent_id, status_keys) - - row_list = [ - torrent_id, - None, - status["name"], - status["total_size"], - status["progress"]*100, - status["state"], - status["num_seeds"], - status["num_seeds"], - status["num_peers"], - status["num_peers"], - status["download_payload_rate"], - status["upload_payload_rate"], - status["eta"], - 0.0 - ] - # Insert any column info from get_functions.. this is usually from - # plugins - for column in self.columns.values(): - if column.get_function is not None: - if len(column.column_indices) == 1: - row_list.insert(column.column_indices[0], - column.get_function(torrent_id)) - else: - result = column.get_function(torrent_id) - r_index = 0 - for index in column.column_indices: - row_list.insert(index, result[r_index]) - r_index = r_index + 1 - - # Insert the row with info provided from core - self.liststore.append(row_list) - + # Set values for each column in the row + for column in columns_to_update: + if type(self.get_column_index(column)) is not list: + # We only have a single list store column we need to update + self.liststore.set_value(row, + self.get_column_index(column), + status[self.columns[column].status_field[0]]) + else: + # We have more than 1 liststore column to update + i = 0 + for index in self.get_column_index(column): + # Only update the column if the status field exists + try: + self.liststore.set_value(row, + index, + status[self.columns[column].status_field[i]]) + except: + pass + i = i + 1 + + def add_row(self, torrent_id): + """Adds a new torrent row to the treeview""" + # Insert a new row to the liststore + row = self.liststore.append() + # Store the torrent id + self.liststore.set_value( + row, + self.columns["torrent_id"].column_indices[0], + torrent_id) + # Update the new row so + self.update_row(None, None, row) + def remove_row(self, torrent_id): """Removes a row with torrent_id""" row = self.liststore.get_iter_first() From 6a525530ee01c659f2b36d407fc8efb7a4732138 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 26 Aug 2007 14:34:50 +0000 Subject: [PATCH 0047/1009] Big code clean-up and some refactoring. Added docstrings but more are needed. --- deluge/__init__.py | 1 + deluge/common.py | 29 ++++----- deluge/config.py | 18 ++++-- deluge/core/core.py | 26 ++------ deluge/core/daemon.py | 19 +----- deluge/core/pluginmanager.py | 51 ++++++++-------- deluge/core/torrent.py | 9 +-- deluge/core/torrentmanager.py | 29 +++++---- deluge/core/torrentmanagerstate.py | 5 +- deluge/main.py | 20 ++---- deluge/ui/functions.py | 19 ++---- deluge/ui/gtkui/addtorrentdialog.py | 23 +++---- deluge/ui/gtkui/listview.py | 94 +++++++++++++++++------------ deluge/ui/gtkui/pluginmanager.py | 21 +++---- deluge/ui/gtkui/signals.py | 23 +------ deluge/ui/gtkui/torrentview.py | 71 +++++++++++----------- 16 files changed, 200 insertions(+), 258 deletions(-) diff --git a/deluge/__init__.py b/deluge/__init__.py index e69de29bb..1836ded86 100644 --- a/deluge/__init__.py +++ b/deluge/__init__.py @@ -0,0 +1 @@ +"""Deluge""" diff --git a/deluge/common.py b/deluge/common.py index 2d51b48ad..ddd619a85 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -31,15 +31,12 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging +"""Common functions for various parts of Deluge to use.""" + import os import pkg_resources import xdg, xdg.BaseDirectory -import gettext - -# Get the logger -log = logging.getLogger("deluge") def get_version(): """Returns the program version from the egg metadata""" @@ -76,7 +73,7 @@ def estimate_eta(total_size, total_done, download_rate): try: return ftime(get_eta(total_size, total_done, download_rate)) except ZeroDivisionError: - return _("Infinity") + return "Infinity" def get_eta(size, done, speed): """Returns the ETA in seconds @@ -93,20 +90,20 @@ def fsize(fsize_b): """ fsize_kb = float (fsize_b / 1024.0) if fsize_kb < 1000: - return _("%.1f KiB")%fsize_kb + return "%.1f KiB" % fsize_kb fsize_mb = float (fsize_kb / 1024.0) if fsize_mb < 1000: - return _("%.1f MiB")%fsize_mb + return "%.1f MiB" % fsize_mb fsize_gb = float (fsize_mb / 1024.0) - return _("%.1f GiB")%fsize_gb + return "%.1f GiB" % fsize_gb def fpcnt(dec): """Returns a formatted string representing a percentage""" - return '%.2f%%'%(dec * 100) + return '%.2f%%' % (dec * 100) def fspeed(bps): """Returns a formatted string representing transfer speed""" - return '%s/s'%(fsize(bps)) + return '%s/s' % (fsize(bps)) def fseed(num_seeds, total_seeds): """Returns a formatted string num_seeds (total_seeds)""" @@ -119,21 +116,21 @@ def fpeer(num_peers, total_peers): def ftime(seconds): """Returns a formatted time string""" if seconds < 60: - return '%ds'%(seconds) + return '%ds' % (seconds) minutes = int(seconds/60) seconds = seconds % 60 if minutes < 60: - return '%dm %ds'%(minutes, seconds) + return '%dm %ds' % (minutes, seconds) hours = int(minutes/60) minutes = minutes % 60 if hours < 24: - return '%dh %dm'%(hours, minutes) + return '%dh %dm' % (hours, minutes) days = int(hours/24) hours = hours % 24 if days < 7: - return '%dd %dh'%(days, hours) + return '%dd %dh' % (days, hours) weeks = int(days/7) days = days % 7 if weeks < 10: - return '%dw %dd'%(weeks, days) + return '%dw %dd' % (weeks, days) return 'unknown' diff --git a/deluge/config.py b/deluge/config.py index 2fd367d98..589abd4e3 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -31,15 +31,16 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging +"""Configuration class used to access/create/modify configuration files.""" + import pickle import deluge.common - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class Config: + """This class is used to access configuration files.""" + def __init__(self, filename, defaults=None): log.debug("Config created with filename: %s", filename) log.debug("Config defaults: %s", defaults) @@ -59,6 +60,8 @@ class Config: self.save() def load(self, filename=None): + """Load a config file either by 'filename' or the filename set during + construction of this object.""" # Use self.config_file if filename is None if filename is None: filename = self.config_file @@ -76,6 +79,8 @@ class Config: pkl_file.close() def save(self, filename=None): + """Save configuration to either 'filename' or the filename set during + construction of this object.""" # Saves the config dictionary if filename is None: filename = self.config_file @@ -89,6 +94,7 @@ class Config: log.warning("IOError: Unable to save file '%s'", filename) def set(self, key, value): + """Set the 'key' with 'value'.""" # Sets the "key" with "value" in the config dict log.debug("Setting '%s' to %s", key, value) self.config[key] = value @@ -96,6 +102,8 @@ class Config: self.save() def get(self, key): + """Get the value of 'key'. If it is an invalid key then get() will + return None.""" # Attempts to get the "key" value and returns None if the key is # invalid try: @@ -104,7 +112,7 @@ class Config: return value except KeyError: log.warning("Key does not exist, returning None") - return + return None def __getitem__(self, key): return self.config[key] diff --git a/deluge/core/core.py b/deluge/core/core.py index a039a2ca9..9661cb737 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -31,34 +31,20 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging -import os.path import pickle - -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) import gobject -import deluge.libtorrent as lt -import pkg_resources +import deluge.libtorrent as lt from deluge.config import Config import deluge.common from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log DEFAULT_PREFS = { "compact_allocation": True, diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index 710947fcb..e4bc29603 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -30,25 +30,12 @@ # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True -import logging +import dbus +from dbus.mainloop.glib import DBusGMainLoop from deluge.core.core import Core - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class Daemon: def __init__(self): diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 27fcc48ed..7b8b840cb 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -31,15 +31,18 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging +"""PluginManager for Core""" + import os.path import pkg_resources -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class PluginManager: + """PluginManager handles the loading of plugins and provides plugins with + functions to access parts of the core.""" + def __init__(self): # Set up the hooks dictionary self.hooks = { @@ -59,15 +62,14 @@ class PluginManager: self.plugins = {} for name in pkg_env: - egg = pkg_env[name][0] - egg.activate() - modules = [] - for name in egg.get_entry_map("deluge.plugin.core"): - entry_point = egg.get_entry_info("deluge.plugin.core", name) - cls = entry_point.load() - instance = cls(self) - self.plugins[name] = instance - log.info("Load plugin %s", name) + egg = pkg_env[name][0] + egg.activate() + for name in egg.get_entry_map("deluge.plugin.core"): + entry_point = egg.get_entry_info("deluge.plugin.core", name) + cls = entry_point.load() + instance = cls(self) + self.plugins[name] = instance + log.info("Load plugin %s", name) def __getitem__(self, key): return self.plugins[key] @@ -78,6 +80,7 @@ class PluginManager: self.status_fields[field] = function def get_status(self, torrent_id, fields): + """Return the value of status fields for the selected torrent_id.""" status = {} for field in fields: try: @@ -93,23 +96,17 @@ class PluginManager: self.hooks[hook].append(function) except KeyError: log.warning("Plugin attempting to register invalid hook.") - - def run_hook(self, hook, data): - log.debug("Running hook %s", hook) - - + def run_post_torrent_add(self, torrent_id): + """This hook is run after a torrent has been added to the session.""" log.debug("run_post_torrent_add") - try: - for function in self.hooks["post_torrent_add"]: - function(torrent_id) - except: - pass + for function in self.hooks["post_torrent_add"]: + function(torrent_id) def run_post_torrent_remove(self, torrent_id): + """This hook is run after a torrent has been removed from the session. + """ log.debug("run_post_torrent_remove") - try: - for function in self.hooks["post_torrent_remove"]: - function(torrent_id) - except: - pass + for function in self.hooks["post_torrent_remove"]: + function(torrent_id) + diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 71e020092..df90a9448 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -31,14 +31,11 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - -import deluge.libtorrent as lt - -# Get the logger -log = logging.getLogger("deluge") +"""Internal Torrent class""" class Torrent: + """Torrent holds information about torrents added to the libtorrent session. + """ def __init__(self, filename, handle): # Set the filename self.filename = filename diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index da3ea17e4..3cb01d978 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -31,7 +31,8 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging +"""TorrentManager handles Torrent objects""" + import pickle import os.path @@ -41,11 +42,13 @@ import deluge.common from deluge.config import Config from deluge.core.torrent import Torrent from deluge.core.torrentmanagerstate import TorrentManagerState, TorrentState - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class TorrentManager: + """TorrentManager contains a list of torrents in the current libtorrent + session. This object is also responsible for saving the state of the + session for use on restart.""" + def __init__(self, session): log.debug("TorrentManager init..") # Set the libtorrent session @@ -79,7 +82,8 @@ class TorrentManager: handle = None try: - handle = self.session.add_torrent(lt.torrent_info(torrent_filedump), + handle = self.session.add_torrent( + lt.torrent_info(torrent_filedump), config["download_location"], config["compact_allocation"]) except RuntimeError: @@ -92,11 +96,11 @@ class TorrentManager: # Write the .torrent file to the torrent directory log.debug("Attemping to save torrent file: %s", filename) try: - f = open(os.path.join(config["torrentfiles_location"], + save_file = open(os.path.join(config["torrentfiles_location"], filename), "wb") - f.write(filedump) - f.close() + save_file.write(filedump) + save_file.close() except IOError: log.warning("Unable to save torrent file: %s", filename) @@ -143,14 +147,15 @@ class TorrentManager: """Save the state of the TorrentManager to the torrents.state file""" state = TorrentManagerState() # Create the state for each Torrent and append to the list - for (key, torrent) in self.torrents: - t = TorrentState(torrent.get_state()) - state.torrents.append(t) + for torrent in self.torrents.values(): + torrent_state = TorrentState(torrent.get_state()) + state.torrents.append(torrent_state) # Pickle the TorrentManagerState object try: log.debug("Saving torrent state file.") - state_file = open(deluge.common.get_config_dir("torrents.state"), "wb") + state_file = open(deluge.common.get_config_dir("torrents.state"), + "wb") pickle.dump(state, state_file) state_file.close() except IOError: diff --git a/deluge/core/torrentmanagerstate.py b/deluge/core/torrentmanagerstate.py index 55c3d96ce..5a7d0b1b9 100644 --- a/deluge/core/torrentmanagerstate.py +++ b/deluge/core/torrentmanagerstate.py @@ -31,10 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class TorrentState: def __init__(self, torrent_id, filename): diff --git a/deluge/main.py b/deluge/main.py index b46f06ffc..e1db010d5 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -34,24 +34,18 @@ # The main starting point for the program. This function is called when the # user runs the command 'deluge'. -import logging +"""Main starting point for Deluge. Contains the main() entry point.""" + import os -import signal from optparse import OptionParser from deluge.core.daemon import Daemon from deluge.ui.ui import UI +from deluge.log import LOG as log import deluge.common -# Setup the logger -logging.basicConfig( - level=logging.DEBUG, - format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" -) -# Get the logger for deluge -log = logging.getLogger("deluge") - def main(): + """Entry point for Deluge""" # Setup the argument parser parser = OptionParser(usage="%prog [options] [actions]", version=deluge.common.get_version()) @@ -68,9 +62,7 @@ def main(): log.debug("options: %s", options) log.debug("args: %s", args) - daemon = None pid = None - uri = None # Start the daemon if options.daemon: @@ -82,9 +74,9 @@ def main(): # Since we are starting daemon this process will not start a UI options.ui = False # Create the daemon object - daemon = Daemon() + Daemon() # Start the UI if options.ui: log.info("Starting ui..") - ui = UI() + UI() diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index b755fea60..fde7f6a13 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -31,29 +31,18 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging import os.path import pickle -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) import pygtk pygtk.require('2.0') import gtk, gtk.glade -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log def get_core(): """Get the core object and return it""" diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 98c294e08..33966631c 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -31,16 +31,13 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - import pygtk pygtk.require('2.0') import gtk, gtk.glade +import gettext from deluge.config import Config - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class AddTorrentDialog: def __init__(self, parent=None): @@ -55,14 +52,14 @@ class AddTorrentDialog: self.chooser.set_property("skip-taskbar-hint", True) # Add .torrent and * file filters - f0 = gtk.FileFilter() - f0.set_name(_("Torrent files")) - f0.add_pattern("*." + "torrent") - self.chooser.add_filter(f0) - f1 = gtk.FileFilter() - f1.set_name(_("All files")) - f1.add_pattern("*") - self.chooser.add_filter(f1) + file_filter = gtk.FileFilter() + file_filter.set_name(_("Torrent files")) + file_filter.add_pattern("*." + "torrent") + self.chooser.add_filter(file_filter) + file_filter = gtk.FileFilter() + file_filter.set_name(_("All files")) + file_filter.add_pattern("*") + self.chooser.add_filter(file_filter) # Load the 'default_load_path' from the config self.config = Config("gtkui.conf") diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index a0d26377c..211fe2916 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -31,50 +31,51 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - import pygtk pygtk.require('2.0') import gtk import gettext import deluge.common - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log # Cell data functions to pass to add_func_column() -def cell_data_speed(column, cell, model, iter, data): - speed = int(model.get_value(iter, data)) +def cell_data_speed(column, cell, model, row, data): + """Display value as a speed, eg. 2 KiB/s""" + speed = int(model.get_value(row, data)) speed_str = deluge.common.fspeed(speed) cell.set_property('text', speed_str) -def cell_data_size(column, cell, model, iter, data): - size = long(model.get_value(iter, data)) +def cell_data_size(column, cell, model, row, data): + """Display value in terms of size, eg. 2 MB""" + size = long(model.get_value(row, data)) size_str = deluge.common.fsize(size) cell.set_property('text', size_str) -def cell_data_peer(column, cell, model, iter, data): - c1, c2 = data - a = int(model.get_value(iter, c1)) - b = int(model.get_value(iter, c2)) - cell.set_property('text', '%d (%d)'%(a, b)) +def cell_data_peer(column, cell, model, row, data): + """Display values as 'value1 (value2)'""" + column1, column2 = data + first = int(model.get_value(row, column1)) + second = int(model.get_value(row, column2)) + cell.set_property('text', '%d (%d)' % (first, second)) -def cell_data_time(column, cell, model, iter, data): - time = int(model.get_value(iter, data)) +def cell_data_time(column, cell, model, row, data): + """Display value as time, eg 1m10s""" + time = int(model.get_value(row, data)) if time < 0 or time == 0: time_str = _("Infinity") else: time_str = deluge.common.ftime(time) cell.set_property('text', time_str) -def cell_data_ratio(column, cell, model, iter, data): - ratio = float(model.get_value(iter, data)) +def cell_data_ratio(column, cell, model, row, data): + """Display value as a ratio with a precision of 3.""" + ratio = float(model.get_value(row, data)) if ratio == -1: ratio_str = _("Unknown") else: - ratio_str = "%.3f"%ratio + ratio_str = "%.3f" % ratio cell.set_property('text', ratio_str) class ListView: @@ -87,9 +88,6 @@ class ListView: self.column_indices = column_indices # Column is a reference to the GtkTreeViewColumn object self.column = None - # The get_function is called when a column is in need of an update - # This is primarily used by plugins. - self.get_function = None # This is the name of the status field that the column will query # the core for if an update is called. self.status_field = None @@ -126,11 +124,15 @@ class ListView: self.menu = None def set_treeview(self, treeview_widget): + """Set the treeview widget that this listview uses.""" self.treeview = treeview_widget self.treeview.set_model(self.liststore) return def get_column_index(self, name): + """Get the liststore column indices belonging to this column. + Will return a list if greater than 1 column. + """ # Only return as list if needed if len(self.columns[name].column_indices) > 1: return self.columns[name].column_indices @@ -218,37 +220,39 @@ class ListView: return - def add_text_column(self, header, col_type=str, hidden=False, - position=None, get_function=None, - status_field=None): - # Create a new column object and add it to the list - self.liststore_columns.append(col_type) + def add_column(self, header, render, col_types, hidden, position, + status_field, sortid, text=0, value=0, function=None): + # Add the column types to liststore_columns + if type(col_types) is list: + for col_type in col_types: + self.liststore_columns.append(col_type) + else: + self.liststore_columns.append(col_types) + # Add to the index list so we know the order of the visible columns. if position is not None: self.column_index.insert(position, header) else: self.column_index.append(header) + # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, [len(self.liststore_columns) - 1]) - # Set the get_function.. This function is used mainly for plugins. - # You can have your listview call this function to update the column - # value. - if get_function is not None: - self.columns[header].get_function = get_function - + self.columns[header].status_field = status_field # Create a new list with the added column self.create_new_liststore() - # Now add the column to the treeview so the user can see it - render = gtk.CellRendererText() - column = gtk.TreeViewColumn(header, render, - text=self.columns[header].column_indices[0]) + if type(render) is gtk.CellRendererText: + column = gtk.TreeViewColumn(header, render, + text=self.columns[header].column_indices[text]) + else: + column = gtk.TreeViewColumn(header, render) + + column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) - column.set_sort_column_id(self.columns[header].column_indices[0]) column.set_resizable(True) column.set_expand(False) column.set_min_width(10) @@ -263,7 +267,18 @@ class ListView: self.columns[header].column = column # Re-create the menu item because of the new column self.create_checklist_menu() + + return True + def add_text_column(self, header, col_type=str, hidden=False, + position=None, + status_field=None, + sortid=0): + # Add a text column to the treeview + render = gtk.CellRendererText() + self.add_column(header, render, col_type, hidden, position, + status_field, sortid, text=0) + return True def add_func_column(self, header, function, column_types, sortid=0, @@ -321,7 +336,8 @@ class ListView: return True - def add_progress_column(self, header, hidden=False, position=None, + def add_progress_column(self, header, col_type=[float,str], hidden=False, + position=None, get_function=None, status_field=None): # For the progress value diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 7c19954e3..31cb1c7e3 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -31,13 +31,11 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging import os.path import pkg_resources -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class PluginManager: def __init__(self, gtkui): @@ -54,15 +52,14 @@ class PluginManager: self.plugins = {} for name in pkg_env: - egg = pkg_env[name][0] - egg.activate() - modules = [] - for name in egg.get_entry_map("deluge.plugin.ui.gtk"): - entry_point = egg.get_entry_info("deluge.plugin.ui.gtk", name) - cls = entry_point.load() - instance = cls(self) - self.plugins[name] = instance - log.info("Loaded plugin %s", name) + egg = pkg_env[name][0] + egg.activate() + for name in egg.get_entry_map("deluge.plugin.ui.gtk"): + entry_point = egg.get_entry_info("deluge.plugin.ui.gtk", name) + cls = entry_point.load() + instance = cls(self) + self.plugins[name] = instance + log.info("Loaded plugin %s", name) def __getitem__(self, key): return self.plugins[key] diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 1d97ac4dd..10970775a 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -31,30 +31,9 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True - -import pygtk -pygtk.require('2.0') -import gtk, gtk.glade - import deluge.ui.functions as functions from deluge.config import Config - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class Signals: def __init__(self, ui): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 60c9eff67..d7723703e 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -31,21 +31,19 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging +"""The torrent view component that lists all torrents in the session.""" import pygtk pygtk.require('2.0') import gtk, gtk.glade -import gobject import gettext import deluge.ui.functions as functions -import listview - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log +import deluge.ui.gtkui.listview as listview class TorrentView(listview.ListView): + """TorrentView handles the listing of torrents.""" def __init__(self, window): self.window = window # Call the ListView constructor @@ -108,34 +106,31 @@ class TorrentView(listview.ListView): if self.liststore is not None: self.liststore.foreach(self.update_row, columns) - def update_row(self, model, path, row, columns=None): - torrent_id = self.liststore.get_value(row, + def update_row(self, model=None, path=None, row=None, columns=None): + """Updates the column values for 'row'. If columns is None it will + update all visible columns.""" + + torrent_id = model.get_value(row, self.columns["torrent_id"].column_indices[0]) # Store the 'status_fields' we need to send to core status_keys = [] # Store the actual columns we will be updating columns_to_update = [] + if columns is None: - # Iterate through the list of columns and only add the - # 'status-fields' of the visible ones. - for column in self.columns.values(): - # Make sure column is visible and has 'status_field' set. - # If not, we can ignore it. - if column.column.get_visible() is True \ - and column.hidden is False \ - and column.status_field is not None: - for field in column.status_field: - status_keys.append(field) - columns_to_update.append(column.name) - else: - # Iterate through supplied list of columns to update - for column in columns: - if self.columns[column].column.get_visible() is True \ - and self.columns[column].hidden is False \ - and self.columns[column].status_field is not None: - for field in self.columns[column].status_field: - status_keys.append(field) - columns_to_update.append(column) + # We need to iterate through all columns + columns = self.columns.keys() + + # Iterate through supplied list of columns to update + for column in columns: + # Make sure column is visible and has 'status_field' set. + # If not, we can ignore it. + if self.columns[column].column.get_visible() is True \ + and self.columns[column].hidden is False \ + and self.columns[column].status_field is not None: + for field in self.columns[column].status_field: + status_keys.append(field) + columns_to_update.append(column) # If there is nothing in status_keys then we must not continue if status_keys is []: @@ -148,23 +143,23 @@ class TorrentView(listview.ListView): # Set values for each column in the row for column in columns_to_update: - if type(self.get_column_index(column)) is not list: + column_index = self.get_column_index(column) + if type(column_index) is not list: # We only have a single list store column we need to update - self.liststore.set_value(row, - self.get_column_index(column), + model.set_value(row, + column_index, status[self.columns[column].status_field[0]]) else: # We have more than 1 liststore column to update - i = 0 - for index in self.get_column_index(column): + for index in column_index: # Only update the column if the status field exists try: - self.liststore.set_value(row, + model.set_value(row, index, - status[self.columns[column].status_field[i]]) + status[self.columns[column].status_field[ + column_index.index(index)]]) except: pass - i = i + 1 def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" @@ -176,7 +171,7 @@ class TorrentView(listview.ListView): self.columns["torrent_id"].column_indices[0], torrent_id) # Update the new row so - self.update_row(None, None, row) + self.update_row(model=self.liststore, row=row) def remove_row(self, torrent_id): """Removes a row with torrent_id""" @@ -206,6 +201,7 @@ class TorrentView(listview.ListView): ### Callbacks ### def on_button_press_event(self, widget, event): + """This is a callback for showing the right-click context menu.""" log.debug("on_button_press_event") # We only care about right-clicks if event.button == 3: @@ -215,5 +211,6 @@ class TorrentView(listview.ListView): torrentmenu.popup(None, None, None, event.button, event.time) def on_selection_changed(self, treeselection): + """This callback is know when the selection has changed.""" log.debug("on_selection_changed") From 729692073142443126cbf1bbe54721bedd728b95 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 27 Aug 2007 04:55:12 +0000 Subject: [PATCH 0048/1009] Updates to the MainWindow glade file. --- deluge/ui/gtkui/glade/main_window.glade | 972 +++++++++++------------- 1 file changed, 454 insertions(+), 518 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index fd79fb914..cdfa9f306 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -62,6 +62,25 @@ True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Quit & Shutdown Daemon + True + + + gtk-quit + 1 + + + + + + + True + + True @@ -91,20 +110,6 @@ True - - - True - Pl_ugins - True - - - True - gtk-disconnect - 1 - - - - @@ -275,19 +280,6 @@ False - - - True - Plugins - Plugins - True - gtk-disconnect - - - - False - - False @@ -304,6 +296,7 @@ True + False GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC @@ -350,6 +343,253 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -382,175 +622,15 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - + True 15 5 - + True 0 - <b>Rate:</b> + <b>ETA:</b> True @@ -558,27 +638,8 @@ 2 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Rate:</b> - True - - - - - 2 - 3 - 1 - 2 + 3 + 4 @@ -603,15 +664,15 @@ - + True 15 5 - + True 0 - <b>ETA:</b> + <b>Rate:</b> True @@ -619,10 +680,189 @@ 2 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Rate:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + 3 4 + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + False @@ -650,253 +890,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - @@ -917,63 +910,6 @@ False - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - - - - - 1 - False - - - - - True - Peers - - - tab - 1 - False - False - - - - - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - - - - - 2 - False - - - - - True - Files - - - tab - 2 - False - False - - From 623c34b349c60586cc544e81a27376e48b8b9b01 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 27 Aug 2007 05:18:32 +0000 Subject: [PATCH 0049/1009] TorrentQueue plugin now diplays menu in TorrentMenu. --- deluge/plugins/queue/queue/core.py | 2 ++ deluge/plugins/queue/queue/gtkui.py | 17 +++++++++++++++++ deluge/plugins/queue/setup.py | 1 + deluge/ui/gtkui/menubar.py | 8 +++++--- deluge/ui/gtkui/pluginmanager.py | 25 +++++++++++++++++-------- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 87a9f3168..0f6d5c32e 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -69,6 +69,8 @@ class Core(dbus.service.Object): self.plugin.register_hook("post_torrent_add", self.post_torrent_add) self.plugin.register_hook("post_torrent_remove", self.post_torrent_remove) + + # Register the 'queue' status field self.plugin.register_status_field("queue", self.status_field_queue) log.info("Queue Core plugin initialized..") diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 0a03d44eb..c9cff5f26 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -46,6 +46,9 @@ try: except: dbus_imported = False else: dbus_imported = True +import pkg_resources +import gtk.glade + # Get the logger log = logging.getLogger("deluge") @@ -59,6 +62,12 @@ class GtkUI: "/org/deluge_torrent/Plugin/Queue") self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge.Queue") + # Get the queue menu from the glade file + menu_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", + "glade/queuemenu.glade")) + + menu = menu_glade.get_widget("menu_queue") + # Connect to the 'torrent_queue_changed' signal self.core.connect_to_signal("torrent_queue_changed", self.torrent_queue_changed_signal) @@ -81,6 +90,14 @@ class GtkUI: label="Queue Down", tooltip="Queue selected torrents down", callback=self.on_queuedown_toolbutton_clicked) + + # Add the queue menu to the torrent menu + queue_menuitem = gtk.ImageMenuItem("Queue") + queue_image = gtk.Image() + queue_image.set_from_stock(gtk.STOCK_SORT_ASCENDING, gtk.ICON_SIZE_MENU) + queue_menuitem.set_image(queue_image) + queue_menuitem.set_submenu(menu) + self.plugin.get_torrentmenu().append(queue_menuitem) def on_queuedown_toolbutton_clicked(self, widget): log.debug("Queue down toolbutton clicked.") diff --git a/deluge/plugins/queue/setup.py b/deluge/plugins/queue/setup.py index fe58e2380..4aa3ceec9 100644 --- a/deluge/plugins/queue/setup.py +++ b/deluge/plugins/queue/setup.py @@ -42,6 +42,7 @@ setup( description=__doc__, author=__author__, packages=["queue"], + package_data = {"queue": ["glade/*.glade"]}, entry_points=""" [deluge.plugin.core] Queue = queue:CorePlugin diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index f9812fb91..e5509d671 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -48,13 +48,15 @@ class MenuBar: log.debug("MenuBar init..") self.window = window # Get the torrent menu from the glade file - self.torrentmenu = gtk.glade.XML( + torrentmenu_glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/torrent_menu.glade")) + self.torrentmenu = torrentmenu_glade.get_widget("torrent_menu") + # Attach the torrent_menu to the Torrent file menu self.window.main_glade.get_widget("menu_torrent").set_submenu( - self.torrentmenu.get_widget("torrent_menu")) + self.torrentmenu) ### Connect Signals ### self.window.main_glade.signal_autoconnect({ @@ -79,7 +81,7 @@ class MenuBar: "on_menuitem_about_activate": self.on_menuitem_about_activate }) - self.torrentmenu.signal_autoconnect({ + torrentmenu_glade.signal_autoconnect({ ## Torrent Menu "on_menuitem_pause_activate": self.on_menuitem_pause_activate, "on_menuitem_resume_activate": self.on_menuitem_resume_activate, diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 31cb1c7e3..64424c920 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -52,14 +52,15 @@ class PluginManager: self.plugins = {} for name in pkg_env: - egg = pkg_env[name][0] - egg.activate() - for name in egg.get_entry_map("deluge.plugin.ui.gtk"): - entry_point = egg.get_entry_info("deluge.plugin.ui.gtk", name) - cls = entry_point.load() - instance = cls(self) - self.plugins[name] = instance - log.info("Loaded plugin %s", name) + egg = pkg_env[name][0] + egg.activate() + modules = [] + for name in egg.get_entry_map("deluge.plugin.ui.gtk"): + entry_point = egg.get_entry_info("deluge.plugin.ui.gtk", name) + cls = entry_point.load() + instance = cls(self) + self.plugins[name] = instance + log.info("Loaded plugin %s", name) def __getitem__(self, key): return self.plugins[key] @@ -71,7 +72,15 @@ class PluginManager: def get_toolbar(self): """Returns a reference to the toolbar component""" return self._gtkui.mainwindow.toolbar + + def get_menubar(self): + """Returns a reference to the menubar component""" + return self._gtkui.mainwindow.menubar + def get_torrentmenu(self): + """Returns a reference to the torrentmenu component""" + return self._gtkui.mainwindow.menubar.torrentmenu + def get_selected_torrents(self): """Returns a list of the selected torrent_ids""" return self._gtkui.mainwindow.torrentview.get_selected_torrents() From 1bd62ba584a351d8de6297791b5d739b91acb100 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 27 Aug 2007 05:52:32 +0000 Subject: [PATCH 0050/1009] Add TorrentQueue's menu glade file. --- .../plugins/queue/queue/glade/queuemenu.glade | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 deluge/plugins/queue/queue/glade/queuemenu.glade diff --git a/deluge/plugins/queue/queue/glade/queuemenu.glade b/deluge/plugins/queue/queue/glade/queuemenu.glade new file mode 100644 index 000000000..7a5232256 --- /dev/null +++ b/deluge/plugins/queue/queue/glade/queuemenu.glade @@ -0,0 +1,49 @@ + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-top + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-up + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-down + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-bottom + True + True + + + + + From c85fda318807d1c161d0113792cf3d37887e106d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 27 Aug 2007 14:34:44 +0000 Subject: [PATCH 0051/1009] Code refactoring and added docstrings. --- deluge/ui/gtkui/listview.py | 94 +++++++++++++++---------------------- 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 211fe2916..2be56aa37 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -79,7 +79,13 @@ def cell_data_ratio(column, cell, model, row, data): cell.set_property('text', ratio_str) class ListView: + """ListView is used to make custom GtkTreeViews. It supports the adding + and removing of columns, creating a menu for a column toggle list and + support for 'status_field's which are used while updating the columns data. + """ + class ListViewColumn: + """Holds information regarding a column in the ListView""" def __init__(self, name, column_indices): # Name is how a column is identified and is also the header self.name = name @@ -140,6 +146,7 @@ class ListView: return self.columns[name].column_indices[0] def on_menuitem_toggled(self, widget): + """Callback for the generated column menuitems.""" # Get the column name from the widget name = widget.get_child().get_text() @@ -148,6 +155,7 @@ class ListView: return def create_checklist_menu(self): + """Creates a menu used for toggling the display of columns.""" self.menu = gtk.Menu() # Iterate through the column_index list to preserve order for name in self.column_index: @@ -166,8 +174,9 @@ class ListView: # Add the new checkmenuitem to the menu self.menu.append(menuitem) return self.menu - + def create_new_liststore(self): + """Creates a new GtkListStore based on the liststore_columns list""" # Create a new liststore with added column and move the data from the # old one to the new one. new_list = gtk.ListStore(*tuple(self.liststore_columns)) @@ -222,12 +231,16 @@ class ListView: def add_column(self, header, render, col_types, hidden, position, status_field, sortid, text=0, value=0, function=None): + """Adds a column to the ListView""" # Add the column types to liststore_columns + column_indices = [] if type(col_types) is list: for col_type in col_types: self.liststore_columns.append(col_type) + column_indices.append(len(self.liststore_columns) - 1) else: self.liststore_columns.append(col_types) + column_indices.append(len(self.liststore_columns) - 1) # Add to the index list so we know the order of the visible columns. if position is not None: @@ -236,8 +249,7 @@ class ListView: self.column_index.append(header) # Create a new column object and add it to the list - self.columns[header] = self.ListViewColumn(header, - [len(self.liststore_columns) - 1]) + self.columns[header] = self.ListViewColumn(header, column_indices) self.columns[header].status_field = status_field @@ -246,8 +258,21 @@ class ListView: self.create_new_liststore() if type(render) is gtk.CellRendererText: - column = gtk.TreeViewColumn(header, render, + # Check to see if this is a function column or not + if function is None: + # Not a function column, so lets treat it as a regular text col + column = gtk.TreeViewColumn(header, render, text=self.columns[header].column_indices[text]) + else: + # This is a function column + column = gtk.TreeViewColumn(header) + column.pack_start(render, True) + if len(self.columns[header].column_indices) > 1: + column.set_cell_data_func(render, function, + tuple(self.columns[header].column_indices)) + else: + column.set_cell_data_func(render, function, + self.columns[header].column_indices[0]) else: column = gtk.TreeViewColumn(header, render) @@ -274,66 +299,23 @@ class ListView: position=None, status_field=None, sortid=0): - # Add a text column to the treeview + """Add a text column to the listview. Only the header name is required. + """ render = gtk.CellRendererText() self.add_column(header, render, col_type, hidden, position, status_field, sortid, text=0) return True - def add_func_column(self, header, function, column_types, sortid=0, - hidden=False, position=None, get_function=None, - status_field=None): - # Add the new column types to the list and keep track of the liststore - # columns that this column object uses. - # Set sortid to the column index relative the to column_types used. - # Default is 0. + def add_func_column(self, header, function, col_types, sortid=0, + hidden=False, position=None, status_field=None): + """Add a function column to the listview. Need a header name, the + function and the column types.""" - column_indices = [] - for column_type in column_types: - self.liststore_columns.append(column_type) - column_indices.append(len(self.liststore_columns) - 1) - - # Add to the index list so we know the order of the visible columns. - if position is not None: - self.column_index.insert(position, header) - else: - self.column_index.append(header) - - # Create a new column object and add it to the list - self.columns[header] = self.ListViewColumn(header, column_indices) - - self.columns[header].status_field = status_field - - # Create new list with the added columns - self.create_new_liststore() - - column = gtk.TreeViewColumn(header) render = gtk.CellRendererText() - column.pack_start(render, True) - if len(self.columns[header].column_indices) > 1: - column.set_cell_data_func(render, function, - tuple(self.columns[header].column_indices)) - else: - column.set_cell_data_func(render, function, - self.columns[header].column_indices[0]) - column.set_clickable(True) - column.set_sort_column_id(column_indices[sortid]) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - column.set_visible(not hidden) - if position is not None: - self.treeview.insert_column(column, position) - else: - self.treeview.append_column(column) - # Set hidden in the column - self.columns[header].hidden = hidden - self.columns[header].column = column - # Re-create the menu item because of the new column - self.create_checklist_menu() - + self.add_column(header, render, col_types, hidden, position, + status_field, sortid, function=function) + return True def add_progress_column(self, header, col_type=[float,str], hidden=False, From 53934ec8d828e0a202c97662e7d824f765119a13 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 28 Aug 2007 07:33:07 +0000 Subject: [PATCH 0052/1009] Add file. --- deluge/log.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 deluge/log.py diff --git a/deluge/log.py b/deluge/log.py new file mode 100644 index 000000000..09324bda3 --- /dev/null +++ b/deluge/log.py @@ -0,0 +1,45 @@ +# +# log.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +"""Logging functions""" + +import logging + +# Setup the logger +logging.basicConfig( + level=logging.DEBUG, + format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" +) + +# Get the logger +LOG = logging.getLogger("deluge") From ca5306bd2d548c5c9468bcf56a2e33ecf3a3be68 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 28 Aug 2007 09:50:25 +0000 Subject: [PATCH 0053/1009] Code refactoring. Sync preferences_dialog.glade from trunk. --- .../ui/gtkui/glade/preferences_dialog.glade | 2373 +++++++++++++---- deluge/ui/gtkui/listview.py | 161 +- 2 files changed, 1893 insertions(+), 641 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 4e8eca460..454df49c5 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -3,13 +3,14 @@ + 500 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Deluge Preferences GTK_WIN_POS_CENTER_ON_PARENT 550 True - GDK_WINDOW_TYPE_HINT_DIALOG + GDK_WINDOW_TYPE_HINT_NORMAL True True False @@ -21,6 +22,7 @@ True True + True True @@ -60,29 +62,36 @@ - + True - 10 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 2 True - Save all downloads to: - True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Store all downloads in: + Store all downloads in: 0 + True True radio_ask_save + - - False - True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder - 1 + 1 + 2 @@ -123,38 +132,73 @@ 2 12 - + True - 10 - + True - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 0 - Maximum simultaneous active torrents: + 10 + + + True + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 0 + Maximum simultaneous active torrents: + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + True + GTK_UPDATE_IF_VALID + + + False + 2 + 1 + + - - False - - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - True - GTK_UPDATE_IF_VALID + Enable selecting files for torrents before loading + Enable selecting files for torrents before loading + 0 + True False - 2 1 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Prioritize first and last pieces of files in torrent + Prioritize first and last pieces of files in torrent + 0 + True + + + False + 2 + + @@ -189,13 +233,55 @@ 2 12 - + True - Compact allocation will only allocate as much storage as it needs to keep the pieces downloaded so far. - Use compact storage allocation - True - 0 - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Full allocation preallocates all of the space that is needed for the torrent and prevents disk fragmentation + Use Full Allocation + 0 + True + True + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 45 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Compact allocation only allocates space as needed + Use Compact Allocation + 0 + True + radio_full_allocation + + + + + False + False + 1 + + @@ -203,7 +289,7 @@ True - <b>Compact Allocation</b> + <b>Allocation</b> True @@ -218,272 +304,6 @@ 2 - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - True - Queue torrents to bottom when they begin seeding - True - 0 - True - - - - - True - 10 - - - True - Stop seeding torrents when their share ratio reaches: - True - 0 - True - - - False - - - - - True - True - 1 - 0 0 10 0.050000000745099998 10 9 - 1 - 2 - True - - - False - 1 - - - - - 1 - - - - - - - - - True - <b>Seeding</b> - True - - - label_item - - - - - False - False - 2 - 3 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 - 2 - - - True - True - The maximum upload rate for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The maximum download rate for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - True - The maximum number of upload slots. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - True - The maximum number of connections allowed. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum number of upload slots. Set -1 for unlimited. - 0 - Upload Slots: - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum download rate for all torrents. Set -1 for unlimited. - 0 - Maximum Download Rate (KiB/s): - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - The maximum upload rate for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Rate (KiB/s): - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - <b>Bandwidth Usage</b> - True - - - label_item - - - - - False - False - 2 - 4 - - @@ -629,11 +449,15 @@ - + True - 1 - Active Port: - GTK_JUSTIFY_RIGHT + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge will automatically choose a different port to use every time. + Random Ports + 0 + True + False @@ -641,19 +465,6 @@ 4 - - - True - 0 - 0000 - 5 - - - False - 5 - 5 - - True @@ -667,7 +478,7 @@ False False - 6 + 5 @@ -680,10 +491,46 @@ - + True - <b>TCP Port</b> - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + <b>TCP</b> + True + + + False + 5 + + + + + True + 1 + Active Port: + GTK_JUSTIFY_RIGHT + + + False + 5 + 1 + + + + + True + 0 + 0000 + 5 + + + False + 5 + 2 + + label_item @@ -721,9 +568,6 @@ True - - - @@ -964,6 +808,521 @@ Full Stream 4 + + + + + + + + + + 1 + False + + + + + True + Network + + + tab + 1 + False + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Queue torrents to bottom when they begin seeding + True + 0 + True + + + + + + True + False + True + Queue new torrents above completed ones + True + 0 + True + + + 1 + + + + + True + 10 + + + True + Stop seeding torrents when their share ratio reaches: + True + 0 + True + + + + False + + + + + True + False + True + 1 + 0 0 10 0.050000000745099998 10 9 + 1 + 2 + True + + + False + 1 + + + + + 2 + + + + + True + False + True + Automatically clear torrents that reach the max share ratio + True + 0 + True + + + 3 + + + + + + + + + True + <b>Seeding</b> + True + + + label_item + + + + + False + False + + + + + + + + + + + + 2 + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Seeding + + + tab + 2 + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + + + GTK_FILL + + + + + True + True + The maximum number of connections allowed. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download speed for all torrents. Set -1 for unlimited. + 1 + 0 -1 9000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + 0 -1 9000 1 10 10 + 1 + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + + + + + True + <b>Global Bandwidth Usage</b> + True + + + label_item + + + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + + + 1 + 2 + + + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + + + 1 + 2 + GTK_FILL + + + + + True + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Per Torrent Bandwidth Usage</b> + True + + + label_item + + + + + False + 1 + + + + + + + + + 3 + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Bandwidth + + + tab + 3 + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True @@ -986,35 +1345,14 @@ Full Stream Peer Proxy 0 True + - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Only affects HTTP tracker connections (UDP tracker connections are affected if the given proxy supports UDP, e.g. SOCKS5). - Tracker Proxy - 0 - True - - - 1 - + - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects the DHT messages. Since they are sent over UDP, it only has any effect if the proxy supports UDP. - DHT Proxy - 0 - True - - - 2 - + @@ -1030,6 +1368,18 @@ Full Stream + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + 4 + + True @@ -1060,8 +1410,9 @@ Full Stream - + True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK None Socksv4 @@ -1076,8 +1427,9 @@ HTTP W/ Auth - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1089,8 +1441,9 @@ HTTP W/ Auth - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False @@ -1127,19 +1480,9 @@ HTTP W/ Auth - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - 4 - - - - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 8080 0 10000 1 10 10 @@ -1161,7 +1504,7 @@ HTTP W/ Auth True - <b>Proxy</b> + <b>Peer Proxy</b> True @@ -1172,7 +1515,592 @@ HTTP W/ Auth False 2 - 5 + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects regular bittorrent peers + Tracker Proxy + 0 + True + + + + + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 4 + + + + + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + 4 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Proxy type + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Username + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Password + + + 2 + 3 + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + None +Socksv4 +Socksv5 +Socksv5 W/ Auth +HTTP +HTTP W/ Auth + + + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + 1 + 2 + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Server + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port + + + 2 + 3 + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 8080 0 10000 1 10 10 + + + 3 + 4 + 1 + 2 + + + + + 1 + + + + + + + True + <b>Tracker Proxy</b> + True + + + label_item + + + + + False + 2 + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects regular bittorrent peers + DHT Proxy + 0 + True + + + + + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 4 + + + + + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + 4 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Proxy type + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Username + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Password + + + 2 + 3 + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + None +Socksv4 +Socksv5 +Socksv5 W/ Auth +HTTP +HTTP W/ Auth + + + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + 1 + 2 + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Server + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port + + + 2 + 3 + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 8080 0 10000 1 10 10 + + + 3 + 4 + 1 + 2 + + + + + 1 + + + + + + + True + <b>DHT Proxy</b> + True + + + label_item + + + + + False + 2 + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Affects regular bittorrent peers + Web Seed Proxy + 0 + True + + + + + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 4 + + + + + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 3 + 4 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Proxy type + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Username + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Password + + + 2 + 3 + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + None +Socksv4 +Socksv5 +Socksv5 W/ Auth +HTTP +HTTP W/ Auth + + + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + 1 + 2 + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Server + + + 2 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port + + + 2 + 3 + 1 + 2 + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 8080 0 10000 1 10 10 + + + 3 + 4 + 1 + 2 + + + + + 1 + + + + + + + True + <b>Web Seed Proxy</b> + True + + + label_item + + + + + False + 2 + 4 @@ -1181,241 +2109,612 @@ HTTP W/ Auth - 1 + 4 False - + True - Network + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Proxies tab - 1 + 4 False False - + True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC + 2 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 GTK_SHADOW_NONE - + True - 2 + 2 + 2 + 12 - + True - 0 - GTK_SHADOW_NONE - + True - 2 - 2 - 12 + Enable system tray icon + True + 0 + True + True + + + + + + True + 10 - + + True + False + Minimize to tray on close + True + 0 + True + + + + + 1 + + + + + True + 10 + + + True + False + Start in tray + True + 0 + True + + + + + 2 + + + + + True + 3 + 10 + + True - + True - Enable system tray icon + False + True + Password protect system tray True 0 + True + + + + + + True + + + True + 5 + + + True + 0 + Password: + + + + + False + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + 16 + + + False + 1 + + + + + 1 + + + + + + + False + 3 + + + + + + + + + True + <b>System Tray</b> + True + + + label_item + + + + + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 40 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 40 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Auto-detect (xdg-open) +Konqueror +Nautilus +Thunar + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 True True - - - True - 10 - - - True - Minimize to tray on close - True - 0 - True - - - - - 1 - - - - - True - 3 - 10 - - - True - - - True - True - Password protect system tray - True - 0 - True - - - - - True - - - True - 5 - - - True - 0 - Password: - - - - - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - 16 - - - False - 1 - - - - - 1 - - - - - - - False - 2 - - - - - - True - <b>System Tray</b> - True - - label_item + False + False - - False - 2 - + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Desktop File Manager</b> + True + + + label_item + + + + + False + False + 2 + 1 + + + + + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 - - 0 - GTK_SHADOW_NONE + + True + 15 - + True - 2 - 2 - 12 - - - True - 15 - - - True - 0 - GUI update interval (seconds) - - - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0.5 0.5 5 0.5 0.5 1 - 1 - 1 - True - - - False - 1 - - - - - - - - - True - <b>Performance</b> - True + 0 + GUI update interval (seconds) - label_item + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.5 0.5 5 0.5 0.5 1 + 1 + 1 + True + + + False + 1 - - False - False - 2 - 1 - + + + + + + True + <b>Performance</b> + True + + + label_item + + + + + False + False + 2 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge will check our servers and will tell you if a newer version has been released + Be alerted about new releases + 0 + True + + + False + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Updates</b> + True + + + label_item + + + + + False + False + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Help us improve Deluge by sending us your Python and PyGTK +versions, OS and processor types. Absolutely no other +information is sent. + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>System Information</b> + True + + + label_item + + + + + False + False + 4 + + + + + 5 + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Other + + + tab + 5 + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 350 + 150 + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + 350 + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + GTK_WRAP_WORD + False + False + + + + + + + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 168 + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-preferences + True + 0 + + + + + + False + False + + + + + False + False + 3 + + - 2 + 6 False - + True - Other + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Plugins tab - 2 + 6 False False @@ -1431,7 +2730,7 @@ HTTP W/ Auth True GTK_BUTTONBOX_END - + True gtk-cancel True @@ -1439,7 +2738,7 @@ HTTP W/ Auth - + True gtk-ok True diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 2be56aa37..1b670a52d 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -229,7 +229,7 @@ class ListView: return - def add_column(self, header, render, col_types, hidden, position, + def add_column(self, header, renderer, col_types, hidden, position, status_field, sortid, text=0, value=0, function=None): """Adds a column to the ListView""" # Add the column types to liststore_columns @@ -257,24 +257,43 @@ class ListView: # Create a new list with the added column self.create_new_liststore() - if type(render) is gtk.CellRendererText: - # Check to see if this is a function column or not - if function is None: - # Not a function column, so lets treat it as a regular text col - column = gtk.TreeViewColumn(header, render, - text=self.columns[header].column_indices[text]) - else: - # This is a function column - column = gtk.TreeViewColumn(header) - column.pack_start(render, True) - if len(self.columns[header].column_indices) > 1: - column.set_cell_data_func(render, function, - tuple(self.columns[header].column_indices)) + if type(renderer) is not tuple and type(renderer) is not list: + renderer = [renderer] + + column = gtk.TreeViewColumn(header) + for render in renderer: + if type(render) is gtk.CellRendererText: + # Check to see if this is a function column or not + if function is None: + # Not a function column, so lets treat it as a regular + # text col + column.pack_start(render) + column.add_attribute(render, "text", + self.columns[header].column_indices[text]) else: - column.set_cell_data_func(render, function, + # This is a function column + column.pack_start(render, True) + if len(self.columns[header].column_indices) > 1: + column.set_cell_data_func(render, function, + tuple(self.columns[header].column_indices)) + else: + column.set_cell_data_func(render, function, self.columns[header].column_indices[0]) - else: - column = gtk.TreeViewColumn(header, render) + elif type(render) is gtk.CellRendererProgress: + # This is a progress column + column.pack_start(render) + column.add_attribute(render, "text", + self.columns[header].column_indices[text]) + column.add_attribute(render, "value", + self.columns[header].column_indices[value]) + + elif type(render) is gtk.CellRendererPixbuf: + # This is a pixbuf column + column.pack_start(render, expand=False) + column.add_attribute(render, "pixbuf", + self.columns[header].column_indices[pixbuf]) + else: + column.pack_start(render) column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) @@ -318,95 +337,29 @@ class ListView: return True - def add_progress_column(self, header, col_type=[float,str], hidden=False, + def add_progress_column(self, header, col_types=[float, str], + sortid=0, + hidden=False, position=None, - get_function=None, status_field=None): - # For the progress value - self.liststore_columns.append(float) - # For the text - self.liststore_columns.append(str) - column_indices = [len(self.liststore_columns) - 2, - len(self.liststore_columns) - 1] - # Add to the index list so we know the order of the visible columns. - if position is not None: - self.column_index.insert(position, header) - else: - self.column_index.append(header) - # Create a new column object and add it to the list - self.columns[header] = self.ListViewColumn(header, column_indices) - - self.columns[header].status_field = status_field - # Create new list with the added columns - self.create_new_liststore() - + """Add a progress column to the listview.""" + render = gtk.CellRendererProgress() - column = gtk.TreeViewColumn(header, render, - value=self.columns[header].column_indices[0], - text=self.columns[header].column_indices[1]) - column.set_clickable(True) - column.set_sort_column_id(self.columns[header].column_indices[0]) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - column.set_visible(not hidden) - if position is not None: - self.treeview.insert_column(column, position) - else: - self.treeview.append_column(column) - # Set hidden in the column - self.columns[header].hidden = hidden - self.columns[header].column = column - # Re-create the menu item because of the new column - self.create_checklist_menu() - - return True - - def add_texticon_column(self, header, hidden=False, position=None, - get_function=None, - status_field=None): - # For icon - self.liststore_columns.append(gtk.gdk.Pixbuf) - # For text - self.liststore_columns.append(str) - column_indices = [len(self.liststore_columns) - 2, - len(self.liststore_columns) - 1] + self.add_column(header, render, col_types, hidden, position, + status_field, sortid, value=0, text=1) + + return True + + def add_texticon_column(self, header, col_types=[gtk.gdk.Pixbuf, str], + sortid=0, + hidden=False, + position=None, + status_field=None): + """Adds a texticon column to the listview.""" + render1 = gtk.CellRendererPixbuf() + render2 = gtk.CellRendererText() + + self.add_column(header, (render1, render2), col_types, hidden, + position, status_field, sortid, text=1, pixbuf=0) - # Add to the index list so we know the order of the visible columns. - if position is not None: - self.column_index.insert(position, header) - else: - self.column_index.append(header) - - self.columns[header] = self.ListViewColumn(header, column_indices) - self.columns[header].status_field = status_field - # Create new list with the added columns - self.create_new_liststore() - - column = gtk.TreeViewColumn(header) - column.set_clickable(True) - column.set_resizable(True) - column.set_expand(False) - column.set_min_width(10) - column.set_reorderable(True) - render = gtk.CellRendererPixbuf() - column.pack_start(render, expand=False) - column.add_attribute(render, 'pixbuf', - self.columns[header].column_indices[0]) - render = gtk.CellRendererText() - column.pack_start(render, expand=True) - column.add_attribute(render, 'text', - self.columns[header].column_indices[1]) - column.set_visible(not hidden) - if position is not None: - self.treeview.insert_column(column, position) - else: - self.treeview.append_column(column) - # Set hidden in the column - self.columns[header].hidden = hidden - self.columns[header].column = column - # Re-create the menu item because of the new column - self.create_checklist_menu() - return True From c5d76cf8ee3b6b8b0164c4bfb63960769b238d25 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 29 Aug 2007 08:00:19 +0000 Subject: [PATCH 0054/1009] State saving and loading in core and TorrentQueue. GtkUI now requests a 'session_state' on start-up to populate it's listview. GtkUI can now shutdown core. --- deluge/core/core.py | 22 +- deluge/core/pluginmanager.py | 11 + deluge/core/torrentmanager.py | 56 +- deluge/core/torrentmanagerstate.py | 43 -- deluge/plugins/queue/queue/core.py | 28 +- deluge/plugins/queue/queue/gtkui.py | 64 +- deluge/plugins/queue/queue/torrentqueue.py | 32 +- deluge/ui/functions.py | 19 +- deluge/ui/gtkui/glade/main_window.glade | 823 +++++++++++---------- deluge/ui/gtkui/listview.py | 3 +- deluge/ui/gtkui/menubar.py | 32 +- deluge/ui/gtkui/torrentview.py | 11 +- 12 files changed, 628 insertions(+), 516 deletions(-) delete mode 100644 deluge/core/torrentmanagerstate.py diff --git a/deluge/core/core.py b/deluge/core/core.py index 9661cb737..73271f7bb 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -90,6 +90,10 @@ class Core(dbus.service.Object): """Shutdown the core""" log.info("Shutting down core..") self.loop.quit() + del self.torrents + self.plugins.shutdown() + del self.plugins + del self.session @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="say", out_signature="") @@ -97,7 +101,6 @@ class Core(dbus.service.Object): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content """ - log.info("Adding torrent: %s", filename) torrent_id = self.torrents.add(filename, filedump) # Run the plugin hooks for 'post_torrent_add' @@ -149,6 +152,23 @@ class Core(dbus.service.Object): status.update(self.plugins.get_status(torrent_id, leftover_fields)) status = pickle.dumps(status) return status + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="", + out_signature="ay") + def get_session_state(self): + """Returns a list of torrent_ids in the session.""" + # Get the torrent list from the TorrentManager + torrent_list = self.torrents.get_torrent_list() + # Pickle the list and send it + session_state = pickle.dumps(torrent_list) + return session_state + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") + def save_state(self): + """Save the current session state to file.""" + # Have the TorrentManager save it's state + self.torrents.save_state() # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 7b8b840cb..8c4079a7f 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -71,6 +71,17 @@ class PluginManager: self.plugins[name] = instance log.info("Load plugin %s", name) + def shutdown(self): + log.debug("PluginManager shutting down..") + # Del all plugins to allow them to deconstruct properly + for plugin in self.plugins.values(): + plugin.core.shutdown() + # del plugin + #while plugin is not None: + # del plugin + + del self.plugins + def __getitem__(self, key): return self.plugins[key] diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 3cb01d978..aeeeb7cf8 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -41,9 +41,17 @@ import deluge.libtorrent as lt import deluge.common from deluge.config import Config from deluge.core.torrent import Torrent -from deluge.core.torrentmanagerstate import TorrentManagerState, TorrentState from deluge.log import LOG as log +class TorrentState: + def __init__(self, torrent_id, filename): + self.torrent_id = torrent_id + self.filename = filename + +class TorrentManagerState: + def __init__(self): + self.torrents = [] + class TorrentManager: """TorrentManager contains a list of torrents in the current libtorrent session. This object is also responsible for saving the state of the @@ -55,24 +63,40 @@ class TorrentManager: self.session = session # Create the torrents dict { torrent_id: Torrent } self.torrents = {} - + # Try to load the state from file + self.load_state() + + def __del__(self): + log.debug("TorrentManager shutting down..") + # Save state on shutdown + self.save_state() + def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" return self.torrents[torrent_id] + def get_torrent_list(self): + """Returns a list of torrent_ids""" + return self.torrents.keys() + def add(self, filename, filedump=None): """Add a torrent to the manager and returns it's torrent_id""" + log.info("Adding torrent: %s", filename) # Get the core config config = Config("core.conf") - + + # Make sure 'filename' is a python string + filename = str(filename) + # Convert the filedump data array into a string of bytes if filedump is not None: filedump = "".join(chr(b) for b in filedump) else: # Get the data from the file try: + log.debug("Attempting to open %s for add.", filename) filedump = open(os.path.join(config["torrentfiles_location"], - filename, "rb")).read() + filename), "rb").read() except IOError: log.warning("Unable to open %s", filename) return None @@ -104,10 +128,13 @@ class TorrentManager: except IOError: log.warning("Unable to save torrent file: %s", filename) + log.debug("Torrent %s added.", handle.info_hash()) # Create a Torrent object torrent = Torrent(filename, handle) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent + # Save the session state + self.save_state() return torrent.torrent_id def remove(self, torrent_id): @@ -123,6 +150,8 @@ class TorrentManager: del self.torrents[torrent_id] except KeyError, ValueError: return False + # Save the session state + self.save_state() return True def pause(self, torrent_id): @@ -142,13 +171,30 @@ class TorrentManager: return False return True + + def load_state(self): + """Load the state of the TorrentManager from the torrents.state file""" + state = TorrentManagerState() + try: + log.debug("Opening torrent state file for load.") + state_file = open(deluge.common.get_config_dir("torrents.state"), + "rb") + state = pickle.load(state_file) + state_file.close() + except IOError: + log.warning("Unable to load state file.") + + # Try to add the torrents in the state to the session + for torrent_state in state.torrents: + self.add(torrent_state.filename) + def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" state = TorrentManagerState() # Create the state for each Torrent and append to the list for torrent in self.torrents.values(): - torrent_state = TorrentState(torrent.get_state()) + torrent_state = TorrentState(*torrent.get_state()) state.torrents.append(torrent_state) # Pickle the TorrentManagerState object diff --git a/deluge/core/torrentmanagerstate.py b/deluge/core/torrentmanagerstate.py deleted file mode 100644 index 5a7d0b1b9..000000000 --- a/deluge/core/torrentmanagerstate.py +++ /dev/null @@ -1,43 +0,0 @@ -# -# torrentmanagerstate.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -from deluge.log import LOG as log - -class TorrentState: - def __init__(self, torrent_id, filename): - self.torrent_id = torrent_id - self.filename = filename - -class TorrentManagerState: - def __init__(self): - self.torrents = [] diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 0f6d5c32e..568da2e62 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -31,25 +31,13 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) from torrentqueue import TorrentQueue - -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class Core(dbus.service.Object): def __init__(self, plugin, path="/org/deluge_torrent/Plugin/Queue"): @@ -62,7 +50,7 @@ class Core(dbus.service.Object): dbus.service.Object.__init__(self, bus_name, path) - # Instantiate the TorrentQueue object + # Instantiate the TorrentQueue object self.queue = TorrentQueue() # Register core hooks @@ -75,6 +63,10 @@ class Core(dbus.service.Object): log.info("Queue Core plugin initialized..") + def shutdown(self): + # Save the queue state + self.queue.save_state() + ## Hooks for core ## def post_torrent_add(self, torrent_id): if torrent_id is not None: diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index c9cff5f26..448d2d3c2 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -66,7 +66,17 @@ class GtkUI: menu_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", "glade/queuemenu.glade")) - menu = menu_glade.get_widget("menu_queue") + menu_glade.signal_autoconnect({ + "on_menuitem_queuetop_activate": \ + self.on_menuitem_queuetop_activate, + "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, + "on_menuitem_queuedown_activate": \ + self.on_menuitem_queuedown_activate, + "on_menuitem_queuebottom_activate": \ + self.on_menuitem_queuebottom_activate + }) + + menu = menu_glade.get_widget("menu_queue") # Connect to the 'torrent_queue_changed' signal self.core.connect_to_signal("torrent_queue_changed", @@ -79,17 +89,20 @@ class GtkUI: col_type=int, position=0, status_field=["queue"]) + # Update the new column right away + self.torrentview.update(["#"]) + # Add a toolbar buttons self.plugin.get_toolbar().add_separator() self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-up", label="Queue Up", tooltip="Queue selected torrents up", - callback=self.on_queueup_toolbutton_clicked) + callback=self.on_toolbutton_queueup_clicked) self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-down", label="Queue Down", tooltip="Queue selected torrents down", - callback=self.on_queuedown_toolbutton_clicked) + callback=self.on_toolbutton_queuedown_clicked) # Add the queue menu to the torrent menu queue_menuitem = gtk.ImageMenuItem("Queue") @@ -98,23 +111,58 @@ class GtkUI: queue_menuitem.set_image(queue_image) queue_menuitem.set_submenu(menu) self.plugin.get_torrentmenu().append(queue_menuitem) - - def on_queuedown_toolbutton_clicked(self, widget): - log.debug("Queue down toolbutton clicked.") + + ## Menu callbacks ## + def on_menuitem_queuetop_activate(self, data=None): + log.debug("on_menuitem_queuetop_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_top(torrent_id) + return + + def on_menuitem_queueup_activate(self, data=None): + log.debug("on_menuitem_queueup_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_up(torrent_id) + return + + def on_menuitem_queuedown_activate(self, data=None): + log.debug("on_menuitem_queuedown_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_down(torrent_id) + return + + def on_menuitem_queuebottom_activate(self, data=None): + log.debug("on_menuitem_queuebottom_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_bottom(torrent_id) + return + + ## Toolbutton callbacks ## + def on_toolbutton_queuedown_clicked(self, widget): + log.debug("on_toolbutton_queuedown_clicked") # Get the selected torrents torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: self.core.queue_down(torrent_id) return - def on_queueup_toolbutton_clicked(self, widget): - log.debug("Queue Up toolbutton clicked.") + def on_toolbutton_queueup_clicked(self, widget): + log.debug("on_toolbutton_queueup_clicked") # Get the selected torrents torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: self.core.queue_up(torrent_id) return + ## Signals ## def torrent_queue_changed_signal(self): """This function is called whenever we receive a 'torrent_queue_changed' signal from the core plugin. diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py index 5ce3a2ed8..9a5e6de52 100644 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -31,19 +31,45 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging +import pickle -# Get the logger -log = logging.getLogger("deluge") +import deluge.common +from deluge.log import LOG as log class TorrentQueue: def __init__(self): self.queue = [] + # Try to load the queue state from file + self.load_state() def __getitem__(self, torrent_id): """Return the queue position of the torrent_id""" return self.queue.index(torrent_id) + def load_state(self): + """Load the queue state""" + try: + log.debug("Opening queue state file for load.") + state_file = open(deluge.common.get_config_dir("queue.state"), + "rb") + state = pickle.load(state_file) + state_file.close() + self.queue = state + except IOError: + log.warning("Unable to load queue state file.") + + + def save_state(self): + """Save the queue state""" + try: + log.debug("Saving queue state file.") + state_file = open(deluge.common.get_config_dir("queue.state"), + "wb") + pickle.dump(self.queue, state_file) + state_file.close() + except IOError: + log.warning("Unable to save queue state file.") + def append(self, torrent_id): """Append torrent_id to the bottom of the queue""" log.debug("Append torrent %s to queue..", torrent_id) diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index fde7f6a13..c40e84a06 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -63,7 +63,13 @@ def get_core_plugin(plugin): "/org/deluge_torrent/Plugin/" + plugin) core = dbus.Interface(proxy, "org.deluge_torrent.Deluge." + plugin) return core - + +def shutdown(): + """Shutdown the core daemon""" + core = get_core() + core.shutdown() + return + def add_torrent_file(torrent_files): """Adds torrent files to the core Expects a list of torrent files @@ -118,3 +124,14 @@ def get_torrent_status(core, torrent_id, keys): # De-serialize the object status = pickle.loads(status) return status + +def get_session_state(core=None): + # Get the core if not supplied + if core is None: + core = get_core() + state = core.get_session_state() + # Join the array of bytes into a string for pickle to read + state = "".join(chr(b) for b in state) + # De-serialize the object + state = pickle.loads(state) + return state diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index cdfa9f306..0433e3af4 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -68,6 +68,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Quit & Shutdown Daemon True + gtk-quit @@ -343,253 +344,6 @@ 1 2 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - True @@ -622,36 +376,175 @@ 4 5 - + True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - + 0 - 2 - 3 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 3 4 - + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + True 15 5 - + True 0 - <b>Peers:</b> + <b>Rate:</b> True @@ -659,8 +552,6 @@ 2 3 - 2 - 3 @@ -685,15 +576,15 @@ - + True 15 5 - + True 0 - <b>Rate:</b> + <b>Peers:</b> True @@ -701,168 +592,31 @@ 2 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - 2 3 - + True + 15 5 - + True 0 - <b>Uploaded:</b> + <b>ETA:</b> True - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 + 2 + 3 3 4 - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - False @@ -890,6 +644,253 @@ GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 6 + 2 + 2 + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Tracker:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 1b670a52d..ea7a99d1a 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -185,11 +185,12 @@ class ListView: # being the new liststore and the columns list def copy_row(model, path, row, user_data): new_list, columns = user_data + new_row = new_list.append() for column in range(model.get_n_columns()): # Get the current value of the column for this row value = model.get_value(row, column) # Set the value of this row and column in the new liststore - new_list.set_value(row, column, value) + new_list.set_value(new_row, column, value) # Do the actual row copy if self.liststore is not None: diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index e5509d671..56682196b 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -66,6 +66,8 @@ class MenuBar: "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, "on_menuitem_clear_activate": \ self.on_menuitem_clear_activate, + "on_menuitem_quitdaemon_activate": \ + self.on_menuitem_quitdaemon_activate, "on_menuitem_quit_activate": self.on_menuitem_quit_activate, ## Edit Menu @@ -90,13 +92,7 @@ class MenuBar: "on_menuitem_edittrackers_activate": \ self.on_menuitem_edittrackers_activate, "on_menuitem_remove_activate": self.on_menuitem_remove_activate, - "on_menuitem_queuetop_activate": \ - self.on_menuitem_queuetop_activate, - "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, - "on_menuitem_queuedown_activate": \ - self.on_menuitem_queuedown_activate, - "on_menuitem_queuebottom_activate": \ - self.on_menuitem_queuebottom_activate + }) ### Callbacks ### @@ -112,6 +108,12 @@ class MenuBar: def on_menuitem_clear_activate(self, data=None): log.debug("on_menuitem_clear_activate") + + def on_menuitem_quitdaemon_activate(self, data=None): + log.debug("on_menuitem_quitdaemon_activate") + # Tell the core to shutdown + functions.shutdown() + self.window.quit() def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") @@ -145,22 +147,6 @@ class MenuBar: log.debug("on_menuitem_remove_activate") functions.remove_torrent( self.window.torrentview.get_selected_torrents()) - - def on_menuitem_queuetop_activate(self, data=None): - log.debug("on_menuitem_queuetop_activate") - functions.queue_top(self.window.torrentview.get_selected_torrents()) - - def on_menuitem_queueup_activate(self, data=None): - log.debug("on_menuitem_queueup_activate") - functions.queue_up(self.window.torrentview.get_selected_torrents()) - - def on_menuitem_queuedown_activate(self, data=None): - log.debug("on_menuitem_queuedown_activate") - functions.queue_down(self.window.torrentview.get_selected_torrents()) - - def on_menuitem_queuebottom_activate(self, data=None): - log.debug("on_menuitem_queuebottom_activate") - functions.queue_bottom(self.window.torrentview.get_selected_torrents()) ## View Menu ## def on_menuitem_toolbar_toggled(self, data=None): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index d7723703e..fe74236b9 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -97,6 +97,13 @@ class TorrentView(listview.ListView): # changes. self.treeview.get_selection().connect("changed", self.on_selection_changed) + + # We need to get the core session state to know which torrents are in + # the session so we can add them to our list. + session_state = functions.get_session_state(self.core) + print "session_state:", session_state + for torrent_id in session_state: + self.add_row(torrent_id) def update(self, columns=None): """Update the view. If columns is not None, it will attempt to only @@ -165,6 +172,7 @@ class TorrentView(listview.ListView): """Adds a new torrent row to the treeview""" # Insert a new row to the liststore row = self.liststore.append() + print "columnid:", self.columns["torrent_id"].column_indices[0] # Store the torrent id self.liststore.set_value( row, @@ -206,8 +214,7 @@ class TorrentView(listview.ListView): # We only care about right-clicks if event.button == 3: # Show the Torrent menu from the MenuBar - torrentmenu = self.window.menubar.torrentmenu.get_widget( - "torrent_menu") + torrentmenu = self.window.menubar.torrentmenu torrentmenu.popup(None, None, None, event.button, event.time) def on_selection_changed(self, treeselection): From cb0505405d27ec4ff9f721cb028780fa4c0cfaad Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 29 Aug 2007 08:02:06 +0000 Subject: [PATCH 0055/1009] Clean-up. --- deluge/core/pluginmanager.py | 5 ----- deluge/plugins/queue/queue/__init__.py | 5 +---- deluge/plugins/queue/queue/torrentqueue.py | 1 - deluge/ui/gtkui/torrentview.py | 2 -- 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 8c4079a7f..3196b8d84 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -73,13 +73,8 @@ class PluginManager: def shutdown(self): log.debug("PluginManager shutting down..") - # Del all plugins to allow them to deconstruct properly for plugin in self.plugins.values(): plugin.core.shutdown() - # del plugin - #while plugin is not None: - # del plugin - del self.plugins def __getitem__(self, key): diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py index d5e50baad..8f6655e0e 100644 --- a/deluge/plugins/queue/queue/__init__.py +++ b/deluge/plugins/queue/queue/__init__.py @@ -31,13 +31,10 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - from core import Core from gtkui import GtkUI -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class CorePlugin: def __init__(self, plugin_manager): diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py index 9a5e6de52..75033ef9c 100644 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -58,7 +58,6 @@ class TorrentQueue: except IOError: log.warning("Unable to load queue state file.") - def save_state(self): """Save the queue state""" try: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index fe74236b9..175e3cccd 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -101,7 +101,6 @@ class TorrentView(listview.ListView): # We need to get the core session state to know which torrents are in # the session so we can add them to our list. session_state = functions.get_session_state(self.core) - print "session_state:", session_state for torrent_id in session_state: self.add_row(torrent_id) @@ -172,7 +171,6 @@ class TorrentView(listview.ListView): """Adds a new torrent row to the treeview""" # Insert a new row to the liststore row = self.liststore.append() - print "columnid:", self.columns["torrent_id"].column_indices[0] # Store the torrent id self.liststore.set_value( row, From 55fc7f47258b08444562237b954e0950e2cf063f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 1 Sep 2007 09:57:02 +0000 Subject: [PATCH 0056/1009] Fix issue where core could not save .torrent files if the torrentfiles_location did not exist. Core will not create this directory before attempting to write the file. --- deluge/core/torrentmanager.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index aeeeb7cf8..0b0ca09bf 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -35,6 +35,7 @@ import pickle import os.path +import os import deluge.libtorrent as lt @@ -115,10 +116,19 @@ class TorrentManager: if not handle or not handle.is_valid(): # The torrent was not added to the session - return None + return None + + log.debug("Attemping to save torrent file: %s", filename) + # Test if the torrentfiles_location is accessible + if os.access(os.path.join(config["torrentfiles_location"]), os.F_OK) \ + is False: + # The directory probably doesn't exist, so lets create it + try: + os.makedirs(os.path.join(config["torrentfiles_location"])) + except IOError: + log.warning("Unable to create torrent files directory..") # Write the .torrent file to the torrent directory - log.debug("Attemping to save torrent file: %s", filename) try: save_file = open(os.path.join(config["torrentfiles_location"], filename), From e9917bfbb2a6099519dc9f29832db989dc3fab48 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 1 Sep 2007 11:52:01 +0000 Subject: [PATCH 0057/1009] Code clean-up. --- deluge/plugins/queue/queue/gtkui.py | 20 ++++---------------- deluge/ui/gtkui/gtkui.py | 5 +---- deluge/ui/gtkui/mainwindow.py | 5 +---- deluge/ui/gtkui/menubar.py | 5 +---- deluge/ui/gtkui/toolbar.py | 5 +---- deluge/ui/ui.py | 7 +------ 6 files changed, 9 insertions(+), 38 deletions(-) diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 448d2d3c2..f00d23331 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -31,26 +31,14 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - -try: - import dbus, dbus.service - dbus_version = getattr(dbus, "version", (0,0,0)) - if dbus_version >= (0,41,0) and dbus_version < (0,80,0): - import dbus.glib - elif dbus_version >= (0,80,0): - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - else: - pass -except: dbus_imported = False -else: dbus_imported = True +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) import pkg_resources import gtk.glade -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class GtkUI: def __init__(self, plugin_manager): diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 3d405706e..a14918f2d 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -31,8 +31,6 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - import pygtk pygtk.require('2.0') import gtk, gtk.glade @@ -43,8 +41,7 @@ from mainwindow import MainWindow from signals import Signals from pluginmanager import PluginManager -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class GtkUI: def __init__(self): diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index ffa8b66c9..c544469d5 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -31,8 +31,6 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - import pygtk pygtk.require('2.0') import gtk, gtk.glade @@ -43,8 +41,7 @@ from menubar import MenuBar from toolbar import ToolBar from torrentview import TorrentView -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class MainWindow: def __init__(self): diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 56682196b..205426b06 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -31,8 +31,6 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - import pygtk pygtk.require('2.0') import gtk, gtk.glade @@ -40,8 +38,7 @@ import pkg_resources import deluge.ui.functions as functions -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class MenuBar: def __init__(self, window): diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 65ae7683e..95058e125 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -31,14 +31,11 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - import pygtk pygtk.require('2.0') import gtk, gtk.glade -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log class ToolBar: def __init__(self, window): diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 24edba815..e13b15d1f 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -31,14 +31,9 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import logging - -import time - from deluge.config import Config -# Get the logger -log = logging.getLogger("deluge") +from deluge.log import LOG as log DEFAULT_PREFS = { "selected_ui": "gtk" From 0d12b1e0cfb840817269ad7b60c6b3ebff1fe521 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 2 Sep 2007 11:55:41 +0000 Subject: [PATCH 0058/1009] Fix names of widgets. --- deluge/ui/gtkui/glade/main_window.glade | 357 +++++++++++++++--------- 1 file changed, 220 insertions(+), 137 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 0433e3af4..42a17c0d3 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -297,7 +297,6 @@ True - False GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC @@ -327,40 +326,41 @@ False True - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_NEVER - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - + True 1 2 10 - + True 0 - + True 10 10 15 15 - + True 5 - + True 0.10000000149 @@ -370,13 +370,13 @@ - + True - 4 + 5 4 5 - + True 0 @@ -386,7 +386,7 @@ - + True 0 @@ -396,7 +396,7 @@ - + True 0 @@ -408,7 +408,7 @@ - + True 0 @@ -420,7 +420,7 @@ - + True 0 @@ -432,7 +432,7 @@ - + True 0 @@ -444,7 +444,7 @@ - + True 0 @@ -456,7 +456,7 @@ - + True 0 @@ -468,11 +468,11 @@ - + True 5 - + True 0 <b>Downloaded:</b> @@ -482,11 +482,11 @@ - + True 5 - + True 0 <b>Uploaded:</b> @@ -500,11 +500,11 @@ - + True 5 - + True 0 <b>Seeders:</b> @@ -518,11 +518,11 @@ - + True 5 - + True 0 <b>Share Ratio:</b> @@ -536,15 +536,15 @@ - + True 15 5 - + True 0 - <b>Rate:</b> + <b>Speed:</b> True @@ -555,15 +555,15 @@ - + True 15 5 - + True 0 - <b>Rate:</b> + <b>Speed:</b> True @@ -576,12 +576,12 @@ - + True 15 5 - + True 0 <b>Peers:</b> @@ -597,12 +597,12 @@ - + True 15 5 - + True 0 <b>ETA:</b> @@ -617,6 +617,76 @@ 4 + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + False @@ -628,7 +698,7 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Statistics</b> @@ -645,12 +715,12 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 @@ -658,54 +728,34 @@ 15 15 - + True 6 2 2 - + True - 0 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + - 1 - 2 - 5 - 6 - + 2 + 3 + GTK_FILL - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - + True 0 @@ -718,7 +768,7 @@ - + True 0 @@ -731,7 +781,7 @@ - + True 0 True @@ -744,32 +794,12 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - + True 0 1 @@ -785,39 +815,24 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True - 0 - 1 - <b>Pieces:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Tracker:</b> - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + @@ -828,17 +843,24 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True - 0 - 1 - <b>Tracker Status:</b> - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + @@ -849,12 +871,12 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True 0 1 @@ -869,12 +891,73 @@ GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Torrent Info</b> From be5855dcd6e1afcb110cbc107d8729f27e1019f8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 2 Sep 2007 14:15:41 +0000 Subject: [PATCH 0059/1009] Added TorrentDetails component to GtkUI, but it is currently not optimized. Added more status fields to Torrent. --- deluge/common.py | 23 +----- deluge/core/torrent.py | 47 +++++++++-- deluge/ui/gtkui/mainwindow.py | 3 + deluge/ui/gtkui/torrentdetails.py | 130 ++++++++++++++++++++++++++++++ deluge/ui/gtkui/torrentview.py | 11 ++- 5 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 deluge/ui/gtkui/torrentdetails.py diff --git a/deluge/common.py b/deluge/common.py index ddd619a85..1ccb241dd 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -66,23 +66,6 @@ def get_default_plugin_dir(): ## Formatting text functions -def estimate_eta(total_size, total_done, download_rate): - """Returns a string with the estimated ETA and will return 'Unlimited' - if the torrent is complete - """ - try: - return ftime(get_eta(total_size, total_done, download_rate)) - except ZeroDivisionError: - return "Infinity" - -def get_eta(size, done, speed): - """Returns the ETA in seconds - Will raise an exception if the torrent is completed - """ - if (size - done) == 0: - raise ZeroDivisionError - return (size - done) / speed - def fsize(fsize_b): """Returns formatted string describing filesize fsize_b should be in bytes @@ -105,16 +88,14 @@ def fspeed(bps): """Returns a formatted string representing transfer speed""" return '%s/s' % (fsize(bps)) -def fseed(num_seeds, total_seeds): - """Returns a formatted string num_seeds (total_seeds)""" - return str(str(num_seeds) + " (" + str(total_seeds) + ")") - def fpeer(num_peers, total_peers): """Returns a formatted string num_peers (total_peers)""" return str(str(num_peers) + " (" + str(total_peers) + ")") def ftime(seconds): """Returns a formatted time string""" + if seconds is 0: + return "Infinity" if seconds < 60: return '%ds' % (seconds) minutes = int(seconds/60) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index df90a9448..aa7fc497c 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -43,6 +43,8 @@ class Torrent: self.handle = handle # Set the torrent_id for this torrent self.torrent_id = str(handle.info_hash()) + # This is for saving the total uploaded between sessions + self.total_uploaded = 0 def get_state(self): """Returns the state of this torrent for saving to the session state""" @@ -62,7 +64,23 @@ class Torrent: eta = 0 return eta - + + def get_ratio(self): + """Returns the ratio for this torrent""" + up = self.total_uploaded + self.handle.status().total_payload_upload + down = self.handle.status().total_done + + # Convert 'up' and 'down' to floats for proper calculation + up = float(up) + down = float(down) + + try: + ratio = up / down + except ZeroDivisionError: + return 0.0 + + return ratio + def get_status(self, keys): """Returns the status of the torrent based on the keys provided""" # Create the full dictionary @@ -70,11 +88,27 @@ class Torrent: # Adjust progress to be 0-100 value progress = status.progress*100 - + + # Get the total number of seeds and peers + if status.num_complete is -1: + total_seeds = status.num_seeds + else: + total_seeds = status.num_complete + + if status.num_incomplete is -1: + total_peers = status.num_peers - status.num_seeds + else: + total_peers = status.num_incomplete + full_status = { "name": self.handle.torrent_info().name(), "total_size": self.handle.torrent_info().total_size(), - "num_pieces": self.handle.status().num_pieces, + "num_files": self.handle.torrent_info().num_files(), + "num_pieces": self.handle.torrent_info().num_pieces(), + "piece_length": self.handle.torrent_info().piece_length(), + "distributed_copies": status.distributed_copies, + "total_done": status.total_done, + "total_uploaded": self.total_uploaded + status.total_payload_upload, "state": int(status.state), "paused": status.paused, "progress": progress, @@ -83,11 +117,14 @@ class Torrent: "total_payload_upload": status.total_payload_upload, "download_payload_rate": status.download_payload_rate, "upload_payload_rate": status.upload_payload_rate, - "num_peers": status.num_peers, + "num_peers": status.num_peers - status.num_seeds, "num_seeds": status.num_seeds, + "total_peers": total_peers, + "total_seeds": total_seeds, "total_wanted": status.total_wanted, "eta": self.get_eta(), - "ratio": 0.0 + "ratio": self.get_ratio(), + "tracker": status.current_tracker } # Create the desired status dictionary and return it diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index c544469d5..19024a1f0 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -40,6 +40,7 @@ import pkg_resources from menubar import MenuBar from toolbar import ToolBar from torrentview import TorrentView +from torrentdetails import TorrentDetails from deluge.log import LOG as log @@ -56,11 +57,13 @@ class MainWindow: self.menubar = MenuBar(self) self.toolbar = ToolBar(self) self.torrentview = TorrentView(self) + self.torrentdetails = TorrentDetails(self) gobject.timeout_add(1000, self.update) def update(self): self.torrentview.update() + self.torrentdetails.update() return True def show(self): diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py new file mode 100644 index 000000000..5c31edab5 --- /dev/null +++ b/deluge/ui/gtkui/torrentdetails.py @@ -0,0 +1,130 @@ +# +# torrentdetails.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +"""The torrent details component shows info about the selected torrent.""" + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import gettext + +import deluge.ui.functions as functions +import deluge.common +from deluge.log import LOG as log + +class TorrentDetails: + def __init__(self, window): + self.window = window + glade = self.window.main_glade + + self.core = functions.get_core() + + self.notebook = glade.get_widget("torrent_info") + self.details_tab = glade.get_widget("torrentdetails_tab") + + # Get the labels we need to update. + self.progress_bar = glade.get_widget("progressbar") + self.name = glade.get_widget("summary_name") + self.total_size = glade.get_widget("summary_total_size") + self.num_files = glade.get_widget("summary_num_files") + self.pieces = glade.get_widget("summary_pieces") + self.availability = glade.get_widget("summary_availability") + self.total_downloaded = glade.get_widget("summary_total_downloaded") + self.total_uploaded = glade.get_widget("summary_total_uploaded") + self.download_speed = glade.get_widget("summary_download_speed") + self.upload_speed = glade.get_widget("summary_upload_speed") + self.seeders = glade.get_widget("summary_seeders") + self.peers = glade.get_widget("summary_peers") + self.percentage_done = glade.get_widget("summary_percentage_done") + self.share_ratio = glade.get_widget("summary_share_ratio") + self.tracker = glade.get_widget("summary_tracker") + self.tracker_status = glade.get_widget("summary_tracker_status") + self.next_announce = glade.get_widget("summary_next_announce") + self.eta = glade.get_widget("summary_eta") + + def update(self): + # Only update if this page is showing + if self.notebook.page_num(self.details_tab) is \ + self.notebook.get_current_page(): + # Get the first selected torrent + selected = self.window.torrentview.get_selected_torrents() + + # Only use the first torrent in the list or return if None selected + if selected is not None: + selected = selected[0] + else: + # No torrent is selected in the torrentview + return + + # Get the torrent status + status_keys = ["progress", "name", "total_size", "num_files", + "num_pieces", "piece_length", "distributed_copies", + "total_done", "total_payload_download", "total_uploaded", + "total_payload_upload", "download_payload_rate", + "upload_payload_rate", "num_peers", "num_seeds", "total_peers", + "total_seeds", "eta", "ratio", "tracker", "next_announce"] + status = functions.get_torrent_status(self.core, + selected, + status_keys) + + # We need to adjust the value core gives us for progress + progress = status["progress"]/100 + self.progress_bar.set_fraction(progress) + self.progress_bar.set_text(deluge.common.fpcnt(progress)) + + self.name.set_text(status["name"]) + self.total_size.set_text(deluge.common.fsize(status["total_size"])) + self.num_files.set_text(str(status["num_files"])) + self.pieces.set_text("%s (%s)" % (status["num_pieces"], + deluge.common.fsize(status["piece_length"]))) + self.availability.set_text("%.3f" % status["distributed_copies"]) + self.total_downloaded.set_text("%s (%s)" % \ + (deluge.common.fsize(status["total_done"]), + deluge.common.fsize(status["total_payload_download"]))) + self.total_uploaded.set_text("%s (%s)" % \ + (deluge.common.fsize(status["total_uploaded"]), + deluge.common.fsize(status["total_payload_upload"]))) + self.download_speed.set_text( + deluge.common.fspeed(status["download_payload_rate"])) + self.upload_speed.set_text( + deluge.common.fspeed(status["upload_payload_rate"])) + self.seeders.set_text(deluge.common.fpeer(status["num_seeds"], + status["total_seeds"])) + self.peers.set_text(deluge.common.fpeer(status["num_peers"], + status["total_peers"])) + self.eta.set_text(deluge.common.ftime(status["eta"])) + self.share_ratio.set_text("%.3f" % status["ratio"]) + self.tracker.set_text(status["tracker"]) + self.next_announce.set_text( + deluge.common.ftime(status["next_announce"])) + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 175e3cccd..ffb6ae7fa 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -63,11 +63,13 @@ class TorrentView(listview.ListView): self.add_func_column("Seeders", listview.cell_data_peer, [int, int], - status_field=["num_seeds", "num_seeds"]) + status_field=["num_seeds", + "total_seeds"]) self.add_func_column("Peers", listview.cell_data_peer, [int, int], - status_field=["num_peers", "num_peers"]) + status_field=["num_peers", + "total_peers"]) self.add_func_column("Down Speed", listview.cell_data_speed, [float], @@ -201,6 +203,11 @@ class TorrentView(listview.ListView): torrent_ids.append( self.liststore.get_value( self.liststore.get_iter(path), 0)) + + if len(torrent_ids) is 0: + # Only return a list if there is something in it. + return None + return torrent_ids except ValueError: return None From b187d02c9411266dfbeb49336279b53eab4cc420 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 3 Sep 2007 06:35:31 +0000 Subject: [PATCH 0060/1009] Change torrent_add_file() method in core to return a boolean value based on the success of the torrent add. Removed the torrent_add_failed() signal from core. --- deluge/core/core.py | 12 ++++-------- deluge/ui/functions.py | 14 ++++---------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 73271f7bb..755c3e58f 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -96,7 +96,7 @@ class Core(dbus.service.Object): del self.session @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="say", out_signature="") + in_signature="say", out_signature="b") def add_torrent_file(self, filename, filedump): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content @@ -109,8 +109,10 @@ class Core(dbus.service.Object): if torrent_id is not None: # Emit the torrent_added signal self.torrent_added(torrent_id) + return True else: - self.torrent_add_failed() + # Return False because the torrent was not added successfully + return False @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") @@ -177,12 +179,6 @@ class Core(dbus.service.Object): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="") - def torrent_add_failed(self): - """Emitted when a new torrent fails addition to the session""" - log.debug("torrent_add_failed signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") def torrent_removed(self, torrent_id): diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index c40e84a06..505000ece 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -85,8 +85,11 @@ def add_torrent_file(torrent_files): f = open(torrent_file, "rb") # Get the filename because the core doesn't want a path. (path, filename) = os.path.split(torrent_file) - core.add_torrent_file(filename, f.read()) + result = core.add_torrent_file(filename, f.read()) f.close() + if result is False: + # The torrent was not added successfully. + log.warning("Torrent %s was not added successfully.", filename) def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" @@ -107,15 +110,6 @@ def resume_torrent(torrent_ids): for torrent_id in torrent_ids: core.resume_torrent(torrent_id) -def get_torrent_info(core, torrent_id): - """Builds the info dictionary and returns it""" - info = core.get_torrent_info(torrent_id) - # Join the array of bytes into a string for pickle to read - info = "".join(chr(b) for b in info) - # De-serialize the object - info = pickle.loads(info) - return info - def get_torrent_status(core, torrent_id, keys): """Builds the status dictionary and returns it""" status = core.get_torrent_status(torrent_id, keys) From b4ad65ab9be03aa097299b848e5ac5c8d83e85c3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 4 Sep 2007 15:35:07 +0000 Subject: [PATCH 0061/1009] Add Deluge fingerprint. --- deluge/core/core.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 755c3e58f..d8896b836 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -66,9 +66,15 @@ class Core(dbus.service.Object): # Get config self.config = Config("core.conf", DEFAULT_PREFS) + # Create the client fingerprint + version = [] + for value in deluge.common.get_version().split("."): + version.append(int(value)) + fingerprint = lt.fingerprint("DE", *version) + # Setup the libtorrent session and listen on the configured ports log.debug("Starting libtorrent session..") - self.session = lt.session() + self.session = lt.session(fingerprint) log.debug("Listening on %i-%i", self.config.get("listen_ports")[0], self.config.get("listen_ports")[1]) self.session.listen_on(self.config.get("listen_ports")[0], From 02874799f2eb067330b971e7b3ec10de9dbb8ce5 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 5 Sep 2007 17:26:52 +0000 Subject: [PATCH 0062/1009] Update the columns menu when new columns are added. --- deluge/ui/gtkui/listview.py | 16 +++++++++++++++- deluge/ui/gtkui/torrentview.py | 7 +++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index ea7a99d1a..73cc6ce7a 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -128,6 +128,9 @@ class ListView: self.liststore_columns = [] # The GtkMenu that is created after every addition, removal or reorder self.menu = None + # A list of menus that self.menu will be a submenu of everytime it is + # created. + self.checklist_menus = [] def set_treeview(self, treeview_widget): """Set the treeview widget that this listview uses.""" @@ -153,7 +156,13 @@ class ListView: # Set the column's visibility based on the widgets active state self.columns[name].column.set_visible(widget.get_active()) return - + + def register_checklist_menu(self, menu): + """Register a checklist menu with the listview. It will automatically + attach any new checklist menu it makes to this menu. + """ + self.checklist_menus.append(menu) + def create_checklist_menu(self): """Creates a menu used for toggling the display of columns.""" self.menu = gtk.Menu() @@ -173,6 +182,11 @@ class ListView: menuitem.connect("toggled", self.on_menuitem_toggled) # Add the new checkmenuitem to the menu self.menu.append(menuitem) + + # Attach this new menu to all the checklist_menus + for menu in self.checklist_menus: + menu.set_submenu(self.menu) + return self.menu def create_new_liststore(self): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index ffb6ae7fa..b22353f05 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -51,6 +51,11 @@ class TorrentView(listview.ListView): self.window.main_glade.get_widget("torrent_view")) log.debug("TorrentView Init..") self.core = functions.get_core() + + # Register the columns menu with the listview so it gets updated + # accordingly. + self.register_checklist_menu( + self.window.main_glade.get_widget("menu_columns")) # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) @@ -87,8 +92,6 @@ class TorrentView(listview.ListView): [float], status_field=["ratio"]) - self.window.main_glade.get_widget("menu_columns").set_submenu( - self.menu) ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the From 05a0a8a6a427233821f008a1c7ac87a0d36b782b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 7 Sep 2007 08:20:20 +0000 Subject: [PATCH 0063/1009] Make myself the only author of this branch. --- setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index bb19020dd..ba5c4bdce 100644 --- a/setup.py +++ b/setup.py @@ -94,11 +94,9 @@ for path in glob.glob('deluge/plugins/*'): setup( name = "deluge", fullname = "Deluge Bittorent Client", - version = "0.6", - author = "Zach Tibbitts, Alon Zakai, Marcos Pinto, Andrew Resch", - author_email = "zach@collegegeek.org, kripkensteiner@gmail.com, \ - marcospinto@dipconsultants.com, \ - andrewresch@gmail.com", + version = "0.6.0.0", + author = "Andrew Resch", + author_email = "andrewresch@gmail.com", description = "GTK+ bittorrent client", url = "http://deluge-torrent.org", license = "GPLv2", From 38312177efdfb9c31def533f21f2f5ce91ad07ed Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 7 Sep 2007 08:21:19 +0000 Subject: [PATCH 0064/1009] Inital import of the AlertManager. --- deluge/core/alertmanager.py | 61 +++++++++++++++++++++++++++++++++++++ deluge/core/core.py | 4 +++ 2 files changed, 65 insertions(+) create mode 100644 deluge/core/alertmanager.py diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py new file mode 100644 index 000000000..cff748169 --- /dev/null +++ b/deluge/core/alertmanager.py @@ -0,0 +1,61 @@ +# +# alertmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +"""The AlertManager handles all the libtorrent alerts.""" + +import gobject + +import deluge.libtorrent as lt +from deluge.log import LOG as log + +class AlertManager: + def __init__(self, session): + log.debug("AlertManager initialized..") + self.session = session + self.session.set_severity_level(lt.alert.severity_levels.info) + # Handle the alerts every 50 milliseconds + gobject.timeout_add(50, self.handle_alerts) + + def handle_alerts(self): + """Pops all libtorrent alerts in the session queue and handles them + appropriately.""" + alert = self.session.pop_alert() + while alert is not None: + # Loop through all alerts in the queue + # Do some magic to the type string and display the alert message + log.debug("%s: %s", str(type(alert)).split("'")[1].split(".")[2], + alert.msg()) + alert = self.session.pop_alert() + + # Return True so that the timer continues + return True diff --git a/deluge/core/core.py b/deluge/core/core.py index d8896b836..43547e6e8 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -44,6 +44,7 @@ from deluge.config import Config import deluge.common from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager +from deluge.core.alertmanager import AlertManager from deluge.log import LOG as log DEFAULT_PREFS = { @@ -86,6 +87,9 @@ class Core(dbus.service.Object): # Load plugins self.plugins = PluginManager() + # Start the AlertManager + self.alerts = AlertManager(self.session) + log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() From f5a00e8d5804558c0d64398c85dd00465e0daabf Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Sep 2007 08:30:15 +0000 Subject: [PATCH 0065/1009] Add some more error checking. --- deluge/core/core.py | 9 ++++++++- deluge/plugins/queue/queue/core.py | 5 ++++- deluge/plugins/queue/queue/torrentqueue.py | 5 ++++- deluge/ui/gtkui/torrentview.py | 7 +++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 43547e6e8..75c4159ec 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -157,7 +157,14 @@ class Core(dbus.service.Object): for key in keys: nkeys.append(str(key)) # Pickle the status dictionary from the torrent - status = self.torrents[torrent_id].get_status(nkeys) + try: + status = self.torrents[torrent_id].get_status(nkeys) + except KeyError: + # The torrent_id is not found in the torrentmanager, so return None + status = None + status.pickle.dumps(status) + return status + # Get the leftover fields and ask the plugin manager to fill them leftover_fields = list(set(nkeys) - set(status.keys())) if len(leftover_fields) > 0: diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 568da2e62..a82660b17 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -78,7 +78,10 @@ class Core(dbus.service.Object): ## Status field function ## def status_field_queue(self, torrent_id): - return self.queue[torrent_id]+1 + try: + return self.queue[torrent_id]+1 + except TypeError: + return None ## Queueing functions ## @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py index 75033ef9c..983639d10 100644 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -44,7 +44,10 @@ class TorrentQueue: def __getitem__(self, torrent_id): """Return the queue position of the torrent_id""" - return self.queue.index(torrent_id) + try: + return self.queue.index(torrent_id) + except ValueError: + return None def load_state(self): """Load the queue state""" diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index b22353f05..1b482b6b8 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -199,8 +199,11 @@ class TorrentView(listview.ListView): def get_selected_torrents(self): """Returns a list of selected torrents or None""" torrent_ids = [] - paths = self.treeview.get_selection().get_selected_rows()[1] - + try: + paths = self.treeview.get_selection().get_selected_rows()[1] + except AttributeError: + # paths is likely None .. so lets return None + return None try: for path in paths: torrent_ids.append( From e737e3ef126a08a1c73e92507ab864087dcac602 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Sep 2007 14:01:52 +0000 Subject: [PATCH 0066/1009] Can now register handlers with the AlertManager to handle libtorrent alerts. --- deluge/core/alertmanager.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index cff748169..898648669 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -43,18 +43,49 @@ class AlertManager: log.debug("AlertManager initialized..") self.session = session self.session.set_severity_level(lt.alert.severity_levels.info) + # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} + self.handlers = {} # Handle the alerts every 50 milliseconds gobject.timeout_add(50, self.handle_alerts) + + def register_handler(self, alert_type, handler): + """Registers a function that will be called when 'alert_type' is pop'd + in handle_alerts. The handler function should look like: + handler(alert) + Where 'alert' is the actual alert object from libtorrent + """ + if alert_type not in self.handlers.keys(): + # There is no entry for this alert type yet, so lets make it with an + # empty list. + self.handlers[alert_type] = [] + # Append the handler to the list in the handlers dictionary + self.handlers[alert_type].append(handler) + log.debug("Registered handler %s for alert %s", handler, alert_type) + + def deregister_handler(self, handler): + """De-registers the 'handler' function from all alert types.""" + # Iterate through all handlers and remove 'handler' where found + for (key, value) in self.handlers: + if handler in value: + # Handler is in this alert type list + value.remove(handler) + def handle_alerts(self): """Pops all libtorrent alerts in the session queue and handles them appropriately.""" alert = self.session.pop_alert() while alert is not None: # Loop through all alerts in the queue - # Do some magic to the type string and display the alert message - log.debug("%s: %s", str(type(alert)).split("'")[1].split(".")[2], - alert.msg()) + # Do some magic to get the alert type as a string + alert_type = str(type(alert)).split("'")[1].split(".")[2] + # Display the alert message + log.debug("%s: %s", alert_type, alert.msg()) + # Call any handlers for this alert type + if alert_type in self.handlers.keys(): + for handler in self.handlers[alert_type]: + handler(alert) + alert = self.session.pop_alert() # Return True so that the timer continues From 2b120cdbe42376a2e3bf59b6daca1aeed31520d6 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 10 Sep 2007 13:21:33 +0000 Subject: [PATCH 0067/1009] Inital import of Preferences dialog. --- .../ui/gtkui/glade/preferences_dialog.glade | 3764 ++++++----------- deluge/ui/gtkui/mainwindow.py | 2 + deluge/ui/gtkui/menubar.py | 1 + deluge/ui/gtkui/preferences.py | 98 + 4 files changed, 1446 insertions(+), 2419 deletions(-) create mode 100644 deluge/ui/gtkui/preferences.py diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 454df49c5..c6b73c51a 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,375 +1,157 @@ - + - 500 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Deluge Preferences GTK_WIN_POS_CENTER_ON_PARENT - 550 + 500 + 500 True - GDK_WINDOW_TYPE_HINT_NORMAL - True - True + GDK_WINDOW_TYPE_HINT_DIALOG False + - + True - 1 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 - + True True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True - GTK_POLICY_NEVER - GTK_POLICY_AUTOMATIC + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + True - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_SHADOW_NONE + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC - + True - 2 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - + True - 0 - GTK_SHADOW_NONE + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True - 2 - 2 - 12 - - - True - - - True - Ask where to save each download - True - 0 - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 2 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Store all downloads in: - Store all downloads in: - 0 - True - True - radio_ask_save - - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - Select A Folder - - - 1 - 2 - - - - - 1 - - - - - - - - - True - <b>Download Location</b> + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <b><i><big>Downloads</big></i></b> True - label_item + False - - - False - False - 2 - - - - - True - 0 - GTK_SHADOW_NONE - + True - 2 - 2 - 12 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + 0 + GTK_SHADOW_NONE - + True + 2 + 2 + 12 - + True - 10 - + True - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 0 - Maximum simultaneous active torrents: + Ask where to save each download + True + True - - False - - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - True - GTK_UPDATE_IF_VALID + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Store all downloads in: + Store all downloads in: + True + True + + + False + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder + + + False + 1 + + - False - 2 1 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Enable selecting files for torrents before loading - Enable selecting files for torrents before loading - 0 - True - - - False - 1 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Prioritize first and last pieces of files in torrent - Prioritize first and last pieces of files in torrent - 0 - True - - - False - 2 - - - - - - - True - <b>Torrents</b> - True - - - label_item - - - - - False - False - 2 - 1 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Full allocation preallocates all of the space that is needed for the torrent and prevents disk fragmentation - Use Full Allocation - 0 - True - True - - - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 45 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Compact allocation only allocates space as needed - Use Compact Allocation - 0 - True - radio_full_allocation - - - - - False - False - 1 - - - - - - - - - True - <b>Allocation</b> - True - - - label_item - - - - - False - False - 2 - 2 - - - - - - - - - False - - - - - True - Downloads - - - tab - False - False - - - - - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_SHADOW_NONE - - - True - - - True - - - True - 2 - - - True - gtk-dialog-warning - 6 - - - False - False - 10 - - - - - True - 0.20000000298023224 - <b>Please Note - Changes to these settings will only be applied the next time Deluge is restarted.</b> + <b>Download Location</b> True - True - 0 - False - False - 1 + label_item @@ -377,278 +159,466 @@ False False 5 + 2 - - - False - - - - - True - 0 - GTK_SHADOW_NONE - - True - 2 - 2 - 12 - - - True - - - True - - - True - From: - - - False - - - - - True - True - 0 0 65535 1 10 10 - 1 - - - False - 5 - 1 - - - - - True - 5 - To: - - - False - False - 2 - - - - - True - True - 0 0 65535 1 10 10 - 1 - - - False - 5 - 3 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Deluge will automatically choose a different port to use every time. - Random Ports - 0 - True - - - - False - 5 - 4 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Test Active Port - 0 - - - - False - False - 5 - - - - - 5 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - <b>TCP</b> - True - - - False - 5 - - - - - True - 1 - Active Port: - GTK_JUSTIFY_RIGHT - - - False - 5 - 1 - - - - - True - 0 - 0000 - 5 - - - False - 5 - 2 - - - - - label_item - - - - - False - 2 - 1 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - True - Distributed hash table may improve the amount of active connections. - Enable Mainline DHT - True - 0 - True - - - - - - - - - True - <b>DHT</b> - True - - - label_item - - - - - False - 2 - 2 - - - - - True - - + True 0 GTK_SHADOW_NONE - + True 2 2 12 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Full allocation preallocates all of the space that is needed for the torrent and prevents disk fragmentation + Use Full Allocation + True + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Compact allocation only allocates space as needed + Use Compact Allocation + True + + + False + False + 1 + + + + + + + + + True + <b>Allocation</b> + True + + + label_item + + + + + False + False + 5 + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + True - + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Enable selecting files for torrents before loading + Enable selecting files for torrents before loading + True + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Prioritize first and last pieces of files in torrent + Prioritize first and last pieces of files in torrent + True + + + False + 1 + + + + + + + + + True + <b>Torrents</b> + True + + + label_item + + + + + False + False + 5 + 4 + + + + + + + + + False + + + + + + tab + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <b><i><big>Network</big></i></b> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + + + True + From: + + + False + + + + + True + True + 1 + 0 0 65535 1 10 10 + 1 + + + False + 5 + 1 + + + + + True + 5 + To: + + + False + False + 2 + + + + + True + True + 1 + 0 0 65535 1 10 10 + 1 + + + False + 5 + 3 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Test Active Port + + + False + False + 4 + + + + + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 20 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge will automatically choose a different port to use every time. + Use Random Ports + True + + + False + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + 1 + Active Port: + GTK_JUSTIFY_RIGHT + + + False + 5 + + + + + True + 0 + 0000 + 5 + + + False + 5 + 1 + + + + + False + 5 + 1 + + + + + 5 + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Ports</b> + True + + + label_item + + + + + False + 5 + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + True + Distributed hash table may improve the amount of active connections. + Enable Mainline DHT + True + True + + + + + + + + + True + <b>DHT</b> + True + + + label_item + + + + + False + 5 + 3 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + 10 + + True True Universal Plug and Play UPnP True - 0 True True + False 2 - + True True NAT Port Mapping Protocol NAT-PMP True - 0 True True + False 2 1 - + True True µTorrent Peer-Exchange µTorrent-PeX True - 0 True True + False 2 2 @@ -658,7 +628,7 @@ - + True <b>Network Extras</b> True @@ -669,1593 +639,451 @@ - 2 + False + 5 + 4 - - - False - False - 3 - - - - - True - 0 - GTK_SHADOW_NONE - + True - 2 - 2 - 12 + 0 + GTK_SHADOW_NONE - + True - 2 + 2 + 2 + 12 - + True + 2 - + True - 1 - Inbound: - - - False - - - - - True - Disabled + + + True + 1 + Inbound: + + + False + + + + + True + Disabled Enabled Forced + + + False + 5 + 1 + + + + + True + 1 + Outbound: + + + False + 2 + + + + + True + Disabled +Enabled +Forced + + + False + 5 + 3 + + + + + + + True + + + True + 0 + Level: + + + False + + + + + True + Handshake +Either +Full Stream + + + False + 6 + 1 + + - 5 1 - - True - 1 - Outbound: - - - 2 - - - - - True - Disabled -Enabled -Forced - - - 5 - 3 - - - - - - - True - - + True True Prefer to encrypt the entire stream True - 0 True False - - - - - True - 1 - Level: - - - 1 - - - - - True - Handshake -Either -Full Stream - - - 6 2 - - 1 - - - - - - True - <b>Encryption</b> - True + + + True + <b>Encryption</b> + True + + + label_item + + - label_item + False + False + 5 + 5 - - False - False - 2 - 4 - - - - + + 1 + False + - - - 1 - False - - - - - True - Network - - - tab - 1 - False - False - - - - - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - + + + tab + + + + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_SHADOW_NONE + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - + True - 0 - GTK_SHADOW_NONE + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True - 2 - 2 - 12 - - - True - - - True - True - Queue torrents to bottom when they begin seeding - True - 0 - True - - - - - - True - False - True - Queue new torrents above completed ones - True - 0 - True - - - 1 - - - - - True - 10 - - - True - Stop seeding torrents when their share ratio reaches: - True - 0 - True - - - - False - - - - - True - False - True - 1 - 0 0 10 0.050000000745099998 10 9 - 1 - 2 - True - - - False - 1 - - - - - 2 - - - - - True - False - True - Automatically clear torrents that reach the max share ratio - True - 0 - True - - - 3 - - - - - - - - - True - <b>Seeding</b> + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <b><i><big>Bandwidth</big></i></b> True - label_item + False - - - False - False - - - - - - - - - - - - 2 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Seeding - - - tab - 2 - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - 0 - GTK_SHADOW_NONE - + True - 2 - 2 - 12 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + 0 + GTK_SHADOW_NONE - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 - 2 + 2 + 2 + 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 + 4 + 2 + 15 - + + True + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + True + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + 0 -1 9000 1 10 10 + 1 + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download speed for all torrents. Set -1 for unlimited. + 1 + 0 -1 9000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections allowed. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + GTK_FILL + + + + True The maximum upload speed for all torrents. Set -1 for unlimited. 0 Maximum Upload Slots: + + 3 + 4 + GTK_FILL + - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - + True The maximum number of connections allowed. Set -1 for unlimited. 0 Maximum Connections: + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + - - GTK_FILL - - - - - True - True - The maximum number of connections allowed. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The maximum download speed for all torrents. Set -1 for unlimited. - 1 - 0 -1 9000 1 10 10 - 1 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - 0 -1 9000 1 10 10 - 1 - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - - True - <b>Global Bandwidth Usage</b> - True + + + True + <b>Global Bandwidth Usage</b> + True + + + label_item + + - label_item + False + False + 5 + 2 - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 2 - 12 + 0 + GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 2 + 2 + 2 + 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + 15 - + + True + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + + + 1 + 2 + GTK_FILL + + + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 Maximum Connections: + + GTK_FILL + - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 Maximum Upload Slots: - - - - 1 - 2 - - - - - True - True - The maximum number of connections per torrent. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - - - 1 - 2 - GTK_FILL - - - - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Per Torrent Bandwidth Usage</b> - True - - - label_item - - - - - False - 1 - - - - - - - - - 3 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Bandwidth - - - tab - 3 - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - True - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects regular bittorrent peers - Peer Proxy - 0 - True - - - - - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 4 - - - - - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - 4 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Proxy type - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Username - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Password - - - 2 - 3 - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - None -Socksv4 -Socksv5 -Socksv5 W/ Auth -HTTP -HTTP W/ Auth - - - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - 1 - 2 - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Server - - - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Port - - - 2 - 3 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 10000 1 10 10 - - - 3 - 4 - 1 - 2 - - - - - 1 - - - - - - - True - <b>Peer Proxy</b> - True - - - label_item - - - - - False - 2 - 1 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects regular bittorrent peers - Tracker Proxy - 0 - True - - - - - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 4 - - - - - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - 4 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Proxy type - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Username - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Password - - - 2 - 3 - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - None -Socksv4 -Socksv5 -Socksv5 W/ Auth -HTTP -HTTP W/ Auth - - - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - 1 - 2 - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Server - - - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Port - - - 2 - 3 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 10000 1 10 10 - - - 3 - 4 - 1 - 2 - - - - - 1 - - - - - - - True - <b>Tracker Proxy</b> - True - - - label_item - - - - - False - 2 - 2 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects regular bittorrent peers - DHT Proxy - 0 - True - - - - - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 4 - - - - - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - 4 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Proxy type - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Username - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Password - - - 2 - 3 - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - None -Socksv4 -Socksv5 -Socksv5 W/ Auth -HTTP -HTTP W/ Auth - - - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - 1 - 2 - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Server - - - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Port - - - 2 - 3 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 10000 1 10 10 - - - 3 - 4 - 1 - 2 - - - - - 1 - - - - - - - True - <b>DHT Proxy</b> - True - - - label_item - - - - - False - 2 - 3 - - - - - True - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Affects regular bittorrent peers - Web Seed Proxy - 0 - True - - - - - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 4 - - - - - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - 4 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Proxy type - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Username - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Password - - - 2 - 3 - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - None -Socksv4 -Socksv5 -Socksv5 W/ Auth -HTTP -HTTP W/ Auth - - - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 1 - 2 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - 1 - 2 - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Server - - - 2 - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Port - - - 2 - 3 - 1 - 2 - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 10000 1 10 10 - - - 3 - 4 - 1 - 2 - - - - - 1 - - - - - - - True - <b>Web Seed Proxy</b> - True - - - label_item - - - - - False - 2 - 4 - - - - - - - - - 4 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Proxies - - - tab - 4 - False - False - - - - - True - 2 - - - True - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - - - True - Enable system tray icon - True - 0 - True - True - - - - - - True - 10 - - - True - False - Minimize to tray on close - True - 0 - True - - - - - 1 - - - - - True - 10 - - - True - False - Start in tray - True - 0 - True - - - - - 2 - - - - - True - 3 - 10 - - - True - - - True - False - True - Password protect system tray - True - 0 - True - - - - - - True - - - True - 5 - - - True - 0 - Password: - - - - False - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - 16 - - - False - 1 + 1 + 2 + GTK_FILL - - 1 - + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Per Torrent Bandwidth Usage</b> + True + + + label_item + + False + 5 3 @@ -2263,376 +1091,16 @@ HTTP W/ Auth - - - True - <b>System Tray</b> - True - - - label_item - - - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 40 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 40 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Auto-detect (xdg-open) -Konqueror -Nautilus -Thunar - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - - - - False - False - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Desktop File Manager</b> - True - - - label_item - - - - - False - False - 2 - 1 - - - - - 0 - GTK_SHADOW_NONE - - - True - 2 - 2 - 12 - - - True - 15 - - - True - 0 - GUI update interval (seconds) - - - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0.5 0.5 5 0.5 0.5 1 - 1 - 1 - True - - - False - 1 - - - - - - - - - True - <b>Performance</b> - True - - - label_item - - - - - False - False - 2 2 + False - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Deluge will check our servers and will tell you if a newer version has been released - Be alerted about new releases - 0 - True - - - False - False - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Updates</b> - True - - - label_item - - - + - False - False - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 12 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Help us improve Deluge by sending us your Python and PyGTK -versions, OS and processor types. Absolutely no other -information is sent. - 0 - True - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>System Information</b> - True - - - label_item - - - - - False - False - 4 - - - - - 5 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Other - - - tab - 5 - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 350 - 150 - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - 1 + tab @@ -2643,111 +1111,569 @@ information is sent. GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - - 350 + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - GTK_WRAP_WORD - False - False + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Other</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + + + True + Enable system tray icon + True + True + True + + + + + True + 10 + + + True + False + Minimize to tray on close + True + True + + + + + 1 + + + + + True + 10 + + + True + False + Start in tray + True + True + + + + + 2 + + + + + True + 3 + 10 + + + True + False + True + Password protect system tray + True + True + + + + + False + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 20 + + + True + 5 + + + True + 0 + Password: + + + False + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + 16 + + + False + 1 + + + + + + + 4 + + + + + + + + + True + <b>System Tray</b> + True + + + label_item + + + + + False + 5 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + True + True + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + True + True + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Auto-detect (xdg-open) +Konqueror +Nautilus +Thunar + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + + + 1 + 2 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Desktop File Manager</b> + True + + + label_item + + + + + False + False + 5 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge will check our servers and will tell you if a newer version has been released + Be alerted about new releases + True + + + False + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Updates</b> + True + + + label_item + + + + + False + False + 5 + 4 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Help us improve Deluge by sending us your Python and PyGTK versions, OS and processor types. Absolutely no other information is sent. + True + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Yes, please send anonymous statistics + True + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>System Information</b> + True + + + label_item + + + + + False + False + 5 + 5 + + - 2 + 3 + False - + + + tab + + + + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 168 + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-preferences - True - 0 - + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Plugins</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + GTK_WRAP_WORD + False + False + + + False + + + + + 2 + + - - False - False - - False - False - 3 + 4 + False + + + + + + tab - - 6 - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Plugins - - - tab - 6 - False - False - - 2 - 2 + 1 - + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END - + True - gtk-cancel + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel True - 0 + - + True - gtk-ok + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-apply True - 1 + 1 + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + + + + 2 + + False diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 19024a1f0..a67847a92 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -41,6 +41,7 @@ from menubar import MenuBar from toolbar import ToolBar from torrentview import TorrentView from torrentdetails import TorrentDetails +from preferences import Preferences from deluge.log import LOG as log @@ -58,6 +59,7 @@ class MainWindow: self.toolbar = ToolBar(self) self.torrentview = TorrentView(self) self.torrentdetails = TorrentDetails(self) + self.preferences = Preferences(self) gobject.timeout_add(1000, self.update) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 205426b06..015e442f1 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -119,6 +119,7 @@ class MenuBar: ## Edit Menu ## def on_menuitem_preferences_activate(self, data=None): log.debug("on_menuitem_preferences_activate") + self.window.preferences.show() def on_menuitem_plugins_activate(self, data=None): log.debug("on_menuitem_plugins_activate") diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py new file mode 100644 index 000000000..3b64b7ec3 --- /dev/null +++ b/deluge/ui/gtkui/preferences.py @@ -0,0 +1,98 @@ +# +# preferences.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import pkg_resources + +from deluge.log import LOG as log + +class Preferences: + def __init__(self, window): + self.window = window + self.pref_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/preferences_dialog.glade")) + self.pref_dialog = self.pref_glade.get_widget("pref_dialog") + self.treeview = self.pref_glade.get_widget("treeview") + self.notebook = self.pref_glade.get_widget("notebook") + # Setup the liststore for the categories (tab pages) + self.liststore = gtk.ListStore(int, str) + self.treeview.set_model(self.liststore) + render = gtk.CellRendererText() + column = gtk.TreeViewColumn("Categories", render, text=1) + self.treeview.append_column(column) + # Add the default categories + self.liststore.append([0, "Downloads"]) + self.liststore.append([1, "Network"]) + self.liststore.append([2, "Bandwidth"]) + self.liststore.append([3, "Other"]) + self.liststore.append([4, "Plugins"]) + + # Connect to the 'changed' event of TreeViewSelection to get selection + # changes. + self.treeview.get_selection().connect("changed", + self.on_selection_changed) + + self.pref_glade.signal_autoconnect({ + "on_pref_dialog_delete_event": self.on_pref_dialog_delete_event, + "on_button_ok_clicked": self.on_button_ok_clicked, + "on_button_apply_clicked": self.on_button_apply_clicked, + "on_button_cancel_clicked": self.on_button_cancel_clicked + }) + + def show(self): + self.pref_dialog.show() + + def hide(self): + self.pref_dialog.hide() + + def on_pref_dialog_delete_event(self, widget, event): + self.hide() + return True + + def on_button_ok_clicked(self, data): + log.debug("on_button_ok_clicked") + + def on_button_apply_clicked(self, data): + log.debug("on_button_apply_clicked") + + def on_button_cancel_clicked(self, data): + log.debug("on_button_cancel_clicked") + + def on_selection_changed(self, treeselection): + # Show the correct notebook page based on what row is selected. + (model, row) = treeselection.get_selected() + self.notebook.set_current_page(model.get_value(row, 0)) + From 1be0705aab518eb4c1444d3e2c52c0a3286e7f57 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 12 Sep 2007 03:09:36 +0000 Subject: [PATCH 0068/1009] Add activate signal to Preferences menuitem. --- deluge/ui/gtkui/glade/main_window.glade | 906 ++++++++++++------------ 1 file changed, 454 insertions(+), 452 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 42a17c0d3..7e1190b9d 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -109,6 +109,7 @@ gtk-preferences True True + @@ -297,6 +298,7 @@ True + False GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC @@ -344,376 +346,6 @@ 1 2 10 - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - True @@ -734,54 +366,33 @@ 2 2 - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - + True 0 1 2 - 2 - 3 + 5 + 6 - + True 0 1 2 - 1 - 2 + 4 + 5 - + True 0 True @@ -790,55 +401,49 @@ 1 2 + 3 + 4 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True 0 + 0 1 - <b>Total Size:</b> + <b>Name:</b> True - 1 - 2 GTK_FILL - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - + 0 + 1 + <b>Next Announce:</b> + True - 3 - 4 + 5 + 6 GTK_FILL @@ -871,48 +476,56 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + True 0 1 - <b>Next Announce:</b> + <b>Total Size:</b> True - 5 - 6 + 1 + 2 GTK_FILL - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - + True 0 True @@ -921,37 +534,56 @@ 1 2 - 3 - 4 - + True 0 1 2 - 4 - 5 + 1 + 2 - + True 0 1 2 - 5 - 6 + 2 + 3 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 2 + 3 + GTK_FILL + + @@ -974,6 +606,376 @@ GTK_FILL + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + From a46610a04ac499636ccd201a8402effd0e8c0356 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 12 Sep 2007 12:11:10 +0000 Subject: [PATCH 0069/1009] Fix widget naming. --- .../ui/gtkui/glade/preferences_dialog.glade | 323 ++++++++++-------- 1 file changed, 177 insertions(+), 146 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index c6b73c51a..b42d77d0e 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -32,6 +32,7 @@ False + True @@ -96,10 +97,11 @@ True - + True Ask where to save each download True + 0 True @@ -109,12 +111,13 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Store all downloads in: Store all downloads in: + 0 True True @@ -123,7 +126,7 @@ - + True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -179,12 +182,13 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Full allocation preallocates all of the space that is needed for the torrent and prevents disk fragmentation Use Full Allocation + 0 True True @@ -194,12 +198,13 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Compact allocation only allocates space as needed Use Compact Allocation + 0 True @@ -245,12 +250,13 @@ True - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Enable selecting files for torrents before loading Enable selecting files for torrents before loading + 0 True @@ -258,12 +264,13 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Prioritize first and last pieces of files in torrent Prioritize first and last pieces of files in torrent + 0 True @@ -375,7 +382,7 @@ - + True True 1 @@ -401,7 +408,7 @@ - + True True 1 @@ -415,12 +422,13 @@ - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Test Active Port + 0 False @@ -439,12 +447,13 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 20 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Deluge will automatically choose a different port to use every time. Use Random Ports + 0 True @@ -469,7 +478,7 @@ - + True 0 0000 @@ -531,12 +540,13 @@ True - + True True Distributed hash table may improve the amount of active connections. Enable Mainline DHT True + 0 True @@ -577,12 +587,13 @@ True 10 - + True True Universal Plug and Play UPnP True + 0 True True @@ -592,12 +603,13 @@ - + True True NAT Port Mapping Protocol NAT-PMP True + 0 True True @@ -608,12 +620,13 @@ - + True True µTorrent Peer-Exchange µTorrent-PeX True + 0 True True @@ -673,7 +686,7 @@ - + True Disabled Enabled @@ -697,7 +710,7 @@ Forced - + True Disabled Enabled @@ -725,7 +738,7 @@ Forced - + True Handshake Either @@ -743,11 +756,12 @@ Full Stream - + True True Prefer to encrypt the entire stream True + 0 True @@ -852,82 +866,26 @@ Full Stream 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - True - True The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - 0 -1 9000 1 10 10 - 1 + 0 + Maximum Upload Speed (KiB/s): - 1 - 2 2 3 GTK_FILL - + True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The maximum download speed for all torrents. Set -1 for unlimited. - 1 - 0 -1 9000 1 10 10 - 1 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 - GTK_FILL - - - - - True - True The maximum number of connections allowed. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 + 0 + Maximum Connections: - 1 - 2 GTK_FILL @@ -945,29 +903,85 @@ Full Stream - + True + True The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: + 1 + -1 -1 1000 1 10 10 + 1 + 1 + 2 GTK_FILL - + True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum download speed for all torrents. Set -1 for unlimited. 0 - Maximum Upload Speed (KiB/s): + Maximum Download Speed (KiB/s): + 1 + 2 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download speed for all torrents. Set -1 for unlimited. + 1 + 0 -1 9000 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + 0 -1 9000 1 10 10 + 1 + + + 1 + 2 2 3 GTK_FILL + + + True + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + + + 1 + 2 + 3 + 4 + GTK_FILL + + @@ -1011,36 +1025,18 @@ Full Stream 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL - - - True - True - The maximum number of connections per torrent. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - - - 1 - 2 - GTK_FILL - - True @@ -1053,13 +1049,31 @@ Full Stream - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + + + 1 + 2 1 2 GTK_FILL @@ -1159,10 +1173,11 @@ Full Stream True - + True Enable system tray icon True + 0 True True @@ -1172,11 +1187,12 @@ Full Stream True 10 - + True False Minimize to tray on close True + 0 True @@ -1190,11 +1206,12 @@ Full Stream True 10 - + True False Start in tray True + 0 True @@ -1209,12 +1226,13 @@ Full Stream 3 10 - + True False True Password protect system tray True + 0 True @@ -1244,7 +1262,7 @@ Full Stream - + True False True @@ -1304,35 +1322,21 @@ Full Stream 2 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - True - True - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - True - True + 1 + 2 1 2 GTK_FILL - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Auto-detect (xdg-open) @@ -1340,7 +1344,7 @@ Konqueror Nautilus Thunar - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1355,19 +1359,35 @@ Thunar - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + GTK_FILL + + @@ -1407,12 +1427,13 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Deluge will check our servers and will tell you if a newer version has been released Be alerted about new releases + 0 True @@ -1479,11 +1500,12 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Yes, please send anonymous statistics + 0 True @@ -1578,7 +1600,7 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1586,10 +1608,11 @@ Thunar False + True - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1600,6 +1623,7 @@ Thunar False + True @@ -1624,6 +1648,10 @@ Thunar + + True + True + @@ -1643,6 +1671,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True + 0 @@ -1654,6 +1683,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply True + 0 @@ -1668,6 +1698,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-ok True + 0 From 4691dcbaa2c6c1f6622772e2aa572878d56b8afb Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 14 Sep 2007 11:45:42 +0000 Subject: [PATCH 0070/1009] Preferences dialog now sets core configuration options. --- deluge/config.py | 4 + deluge/core/core.py | 44 ++- deluge/log.py | 2 +- deluge/ui/functions.py | 22 ++ .../ui/gtkui/glade/preferences_dialog.glade | 274 ++++++++++-------- deluge/ui/gtkui/menubar.py | 4 - deluge/ui/gtkui/preferences.py | 178 +++++++++++- 7 files changed, 388 insertions(+), 140 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index 589abd4e3..8ec226f5c 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -114,6 +114,10 @@ class Config: log.warning("Key does not exist, returning None") return None + def get_config(self): + """Returns the entire configuration as a dictionary.""" + return self.config + def __getitem__(self, key): return self.config[key] diff --git a/deluge/core/core.py b/deluge/core/core.py index 75c4159ec..f5bda969d 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -52,7 +52,23 @@ DEFAULT_PREFS = { "download_location": deluge.common.get_default_download_dir(), "listen_ports": [6881, 6891], "torrentfiles_location": deluge.common.get_default_torrent_dir(), - "plugins_location": deluge.common.get_default_plugin_dir() + "plugins_location": deluge.common.get_default_plugin_dir(), + "prioritize_first_last_pieces": False, + "random_port": False, + "dht": False, + "upnp": False, + "natpmp": False, + "utpex": False, + "enc_in_policy": 1, + "enc_out_policy": 1, + "enc_level": 1, + "enc_prefer_rc4": True, + "max_connections_global": -1, + "max_upload_speed": -1.0, + "max_download_speed": -1.0, + "max_upload_slots_global": -1, + "max_connections_per_torrent": -1, + "max_upload_slots_per_torrent": -1 } class Core(dbus.service.Object): @@ -188,6 +204,32 @@ class Core(dbus.service.Object): """Save the current session state to file.""" # Have the TorrentManager save it's state self.torrents.save_state() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="", + out_signature="ay") + def get_config(self): + """Get all the preferences as a dictionary""" + config = self.config.get_config() + config = pickle.dumps(config) + return config + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="ay") + def set_config(self, config): + """Set the config with values from dictionary""" + # Convert the byte array into the dictionary + config = "".join(chr(b) for b in config) + config = pickle.loads(config) + # Load all the values into the configuration + for key in config.keys(): + self.config[key] = config[key] + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + out_signature="i") + def get_listen_port(self): + """Returns the active listen port""" + return self.session.listen_port() # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", diff --git a/deluge/log.py b/deluge/log.py index 09324bda3..a126aace3 100644 --- a/deluge/log.py +++ b/deluge/log.py @@ -38,7 +38,7 @@ import logging # Setup the logger logging.basicConfig( level=logging.DEBUG, - format="[%(levelname)-8s] %(name)s:%(module)s:%(lineno)d %(message)s" + format="[%(levelname)-8s] %(module)s:%(lineno)d %(message)s" ) # Get the logger diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 505000ece..6bcf4ac54 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -129,3 +129,25 @@ def get_session_state(core=None): # De-serialize the object state = pickle.loads(state) return state + +def get_config(core=None): + if core is None: + core = get_core() + config = core.get_config() + config = "".join(chr(b) for b in config) + config = pickle.loads(config) + return config + +def set_config(config, core=None): + if config == {}: + return + if core is None: + core = get_core() + config = pickle.dumps(config) + core.set_config(config) + +def get_listen_port(core=None): + if core is None: + core = get_core() + return int(core.get_listen_port()) + diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index b42d77d0e..7e436bcee 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -24,11 +24,17 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True + GTK_RESIZE_QUEUE + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + False @@ -120,6 +126,8 @@ 0 True True + radio_ask_save + False @@ -206,6 +214,7 @@ Use Compact Allocation 0 True + radio_full_allocation False @@ -385,9 +394,12 @@ True True + 5 1 0 0 65535 1 10 10 1 + True + True False @@ -411,9 +423,12 @@ True True + 5 1 0 0 65535 1 10 10 1 + True + True False @@ -756,7 +771,7 @@ Full Stream - + True True Prefer to encrypt the entire stream @@ -866,54 +881,60 @@ Full Stream 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. + The maximum upload speed for all torrents. Set -1 for unlimited. 1 - -1 -1 1000 1 10 10 + -1 -1 9000 1 10 10 1 + 1 + True 1 2 + 2 + 3 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True + + + 1 + 2 + 1 + 2 GTK_FILL @@ -931,57 +952,61 @@ Full Stream - + True True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The maximum download speed for all torrents. Set -1 for unlimited. - 1 - 0 -1 9000 1 10 10 - 1 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - 0 -1 9000 1 10 10 - 1 - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 + True + True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1025,18 +1050,40 @@ Full Stream 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + True + True + + + 1 + 2 + GTK_FILL + + True @@ -1049,31 +1096,13 @@ Full Stream - + True - True - The maximum number of connections per torrent. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 - GTK_FILL - - - - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - - - 1 - 2 1 2 GTK_FILL @@ -1322,14 +1351,31 @@ Full Stream 2 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock - 1 - 2 1 2 GTK_FILL @@ -1359,35 +1405,19 @@ Thunar - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - GTK_FILL - - @@ -1485,7 +1515,7 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 - Help us improve Deluge by sending us your Python and PyGTK versions, OS and processor types. Absolutely no other information is sent. + Help us improve Deluge by sending us your Python version, PyGTK version, OS and processor types. Absolutely no other information is sent. True diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 015e442f1..01c725c10 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -70,7 +70,6 @@ class MenuBar: ## Edit Menu "on_menuitem_preferences_activate": \ self.on_menuitem_preferences_activate, - "on_menuitem_plugins_activate": self.on_menuitem_plugins_activate, ## View Menu "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, @@ -120,9 +119,6 @@ class MenuBar: def on_menuitem_preferences_activate(self, data=None): log.debug("on_menuitem_preferences_activate") self.window.preferences.show() - - def on_menuitem_plugins_activate(self, data=None): - log.debug("on_menuitem_plugins_activate") ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 3b64b7ec3..355a68bfc 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -37,16 +37,18 @@ import gtk, gtk.glade import pkg_resources from deluge.log import LOG as log +import deluge.ui.functions as functions class Preferences: def __init__(self, window): self.window = window - self.pref_glade = gtk.glade.XML( + self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/preferences_dialog.glade")) - self.pref_dialog = self.pref_glade.get_widget("pref_dialog") - self.treeview = self.pref_glade.get_widget("treeview") - self.notebook = self.pref_glade.get_widget("notebook") + self.pref_dialog = self.glade.get_widget("pref_dialog") + self.treeview = self.glade.get_widget("treeview") + self.notebook = self.glade.get_widget("notebook") + self.core = functions.get_core() # Setup the liststore for the categories (tab pages) self.liststore = gtk.ListStore(int, str) self.treeview.set_model(self.liststore) @@ -54,26 +56,166 @@ class Preferences: column = gtk.TreeViewColumn("Categories", render, text=1) self.treeview.append_column(column) # Add the default categories - self.liststore.append([0, "Downloads"]) - self.liststore.append([1, "Network"]) - self.liststore.append([2, "Bandwidth"]) - self.liststore.append([3, "Other"]) - self.liststore.append([4, "Plugins"]) - + i = 0 + for category in ["Downloads", "Network", "Bandwidth", "Other", + "Plugins"]: + self.liststore.append([i, category]) + i += 1 + # Connect to the 'changed' event of TreeViewSelection to get selection # changes. self.treeview.get_selection().connect("changed", self.on_selection_changed) - self.pref_glade.signal_autoconnect({ + self.glade.signal_autoconnect({ "on_pref_dialog_delete_event": self.on_pref_dialog_delete_event, "on_button_ok_clicked": self.on_button_ok_clicked, "on_button_apply_clicked": self.on_button_apply_clicked, - "on_button_cancel_clicked": self.on_button_cancel_clicked + "on_button_cancel_clicked": self.on_button_cancel_clicked, + "on_radio_save_all_to_toggled": self.on_toggle }) + + def add_page(self, name, widget): + """Add a another page to the notebook""" + index = self.notebook.append_page(widget) + self.liststore.append([index, name]) + + def get_config(self): + """Get the configuration from the core.""" + # Get the config dictionary from the core + self.config = functions.get_config(self.core) def show(self): + self.get_config() + # Update the preferences dialog to reflect current config settings + + ## Downloads tab ## + # FIXME: Add GtkUI specific prefs here + # Core specific options for Downloads tab + + # This one will need to be re-evaluated if the core is running on a + # different machine.. We won't be able to use the local file browser to + # choose a download location.. It will be specific to the machine core + # is running on. + self.glade.get_widget("download_path_button").set_filename( + self.config["download_location"]) + self.glade.get_widget("radio_compact_allocation").set_active( + self.config["compact_allocation"]) + self.glade.get_widget("radio_full_allocation").set_active( + not self.config["compact_allocation"]) + self.glade.get_widget("chk_prioritize_first_last_pieces").set_active( + self.config["prioritize_first_last_pieces"]) + + ## Network tab ## + self.glade.get_widget("spin_port_min").set_value( + self.config["listen_ports"][0]) + self.glade.get_widget("spin_port_max").set_value( + self.config["listen_ports"][1]) + self.glade.get_widget("active_port_label").set_text( + str(functions.get_listen_port(self.core))) + self.glade.get_widget("chk_random_port").set_active( + self.config["random_port"]) + self.glade.get_widget("chk_dht").set_active( + self.config["dht"]) + self.glade.get_widget("chk_upnp").set_active( + self.config["upnp"]) + self.glade.get_widget("chk_natpmp").set_active( + self.config["natpmp"]) + self.glade.get_widget("chk_utpex").set_active( + self.config["utpex"]) + self.glade.get_widget("combo_encin").set_active( + self.config["enc_in_policy"]) + self.glade.get_widget("combo_encout").set_active( + self.config["enc_out_policy"]) + self.glade.get_widget("combo_enclevel").set_active( + self.config["enc_level"]) + self.glade.get_widget("chk_pref_rc4").set_active( + self.config["enc_prefer_rc4"]) + + ## Bandwidth tab ## + self.glade.get_widget("spin_max_connections_global").set_value( + self.config["max_connections_global"]) + self.glade.get_widget("spin_max_download").set_value( + self.config["max_download_speed"]) + self.glade.get_widget("spin_max_upload").set_value( + self.config["max_upload_speed"]) + self.glade.get_widget("spin_max_upload_slots_global").set_value( + self.config["max_upload_slots_global"]) + self.glade.get_widget("spin_max_connections_per_torrent").set_value( + self.config["max_connections_per_torrent"]) + self.glade.get_widget("spin_max_upload_slots_per_torrent").set_value( + self.config["max_upload_slots_per_torrent"]) + + ## Other tab ## + # All of it is UI only. + + # Now show the dialog self.pref_dialog.show() + + def set_config(self): + """Sets all altered config values in the core""" + # Get the values from the dialog + new_config = {} + ## Downloads tab ## + new_config["download_location"] = \ + self.glade.get_widget("download_path_button").get_filename() + new_config["compact_allocation"] = \ + self.glade.get_widget("radio_compact_allocation").get_active() + new_config["prioritize_first_last_pieces"] = \ + self.glade.get_widget( + "chk_prioritize_first_last_pieces").get_active() + + ## Network tab ## + listen_ports = [] + listen_ports.append( + self.glade.get_widget("spin_port_min").get_value_as_int()) + listen_ports.append( + self.glade.get_widget("spin_port_max").get_value_as_int()) + new_config["listen_ports"] = listen_ports + new_config["random_port"] = \ + self.glade.get_widget("chk_random_port").get_active() + new_config["dht"] = self.glade.get_widget("chk_dht").get_active() + new_config["upnp"] = self.glade.get_widget("chk_upnp").get_active() + new_config["natpmp"] = self.glade.get_widget("chk_natpmp").get_active() + new_config["utpex"] = self.glade.get_widget("chk_utpex").get_active() + new_config["enc_in_policy"] = \ + self.glade.get_widget("combo_encin").get_active() + new_config["enc_out_policy"] = \ + self.glade.get_widget("combo_encout").get_active() + new_config["enc_level"] = \ + self.glade.get_widget("combo_enclevel").get_active() + new_config["enc_prefer_rc4"] = \ + self.glade.get_widget("chk_pref_rc4").get_active() + + ## Bandwidth tab ## + new_config["max_connections_global"] = \ + self.glade.get_widget( + "spin_max_connections_global").get_value_as_int() + new_config["max_download_speed"] = \ + self.glade.get_widget("spin_max_download").get_value() + new_config["max_upload_speed"] = \ + self.glade.get_widget("spin_max_upload").get_value() + new_config["max_upload_slots_global"] = \ + self.glade.get_widget( + "spin_max_upload_slots_global").get_value_as_int() + new_config["max_connections_per_torrent"] = \ + self.glade.get_widget( + "spin_max_connections_per_torrent").get_value_as_int() + new_config["max_upload_slots_per_torrent"] = \ + self.glade.get_widget( + "spin_max_upload_slots_per_torrent").get_value_as_int() + + config_to_set = {} + for key in new_config.keys(): + # The values do not match so this needs to be updated + if self.config[key] != new_config[key]: + config_to_set[key] = new_config[key] + + # Set each changed config value in the core + functions.set_config(config_to_set, self.core) + + # Update the configuration + self.config.update(config_to_set) def hide(self): self.pref_dialog.hide() @@ -82,14 +224,26 @@ class Preferences: self.hide() return True + def on_toggle(self, widget): + """Handles widget sensitivity based on radio/check button values.""" + value = widget.get_active() + if widget == self.glade.get_widget('radio_save_all_to'): + self.glade.get_widget('download_path_button').set_sensitive(value) + def on_button_ok_clicked(self, data): log.debug("on_button_ok_clicked") + self.set_config() + self.hide() + return True def on_button_apply_clicked(self, data): log.debug("on_button_apply_clicked") + self.set_config() def on_button_cancel_clicked(self, data): log.debug("on_button_cancel_clicked") + self.hide() + return True def on_selection_changed(self, treeselection): # Show the correct notebook page based on what row is selected. From 9bb65d1ab4ea3a9e596e60a6a808ff48ec23786e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 15 Sep 2007 22:22:06 +0000 Subject: [PATCH 0071/1009] Add random port support patch from Marcos. --- deluge/core/core.py | 20 +- .../ui/gtkui/glade/preferences_dialog.glade | 221 +++++++++--------- deluge/ui/gtkui/preferences.py | 9 +- 3 files changed, 133 insertions(+), 117 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index f5bda969d..6048be343 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -89,14 +89,24 @@ class Core(dbus.service.Object): version.append(int(value)) fingerprint = lt.fingerprint("DE", *version) - # Setup the libtorrent session and listen on the configured ports + # Start the libtorrent session log.debug("Starting libtorrent session..") self.session = lt.session(fingerprint) - log.debug("Listening on %i-%i", self.config.get("listen_ports")[0], - self.config.get("listen_ports")[1]) - self.session.listen_on(self.config.get("listen_ports")[0], - self.config.get("listen_ports")[1]) + # Set the listening ports + if self.config.get("random_port"): + import random + randrange = lambda: random.randrange(49152, 65535) + ports = [randrange(), randrange()] + ports.sort() + log.debug("Listening on %i-%i", ports[0], ports[1]) + self.session.listen_on(ports[0], ports[1]) + else: + listen_ports = self.config.get("listen_ports") + log.debug("Listening on %i-%i", listen_ports[0], + listen_ports[1]) + self.session.listen_on(listen_ports[0], + listen_ports[1]) # Start the TorrentManager self.torrents = TorrentManager(self.session) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 7e436bcee..9c96608a6 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -127,7 +127,7 @@ True True radio_ask_save - + False @@ -470,6 +470,7 @@ Use Random Ports 0 True + False @@ -881,40 +882,71 @@ Full Stream 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -939,74 +971,43 @@ Full Stream - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1050,24 +1051,29 @@ Full Stream 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1085,24 +1091,19 @@ Full Stream - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1351,31 +1352,14 @@ Full Stream 2 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock + 1 + 2 1 2 GTK_FILL @@ -1405,19 +1389,36 @@ Thunar - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + GTK_FILL + + diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 355a68bfc..3f1d0af5d 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -72,7 +72,7 @@ class Preferences: "on_button_ok_clicked": self.on_button_ok_clicked, "on_button_apply_clicked": self.on_button_apply_clicked, "on_button_cancel_clicked": self.on_button_cancel_clicked, - "on_radio_save_all_to_toggled": self.on_toggle + "on_toggle": self.on_toggle }) def add_page(self, name, widget): @@ -229,7 +229,12 @@ class Preferences: value = widget.get_active() if widget == self.glade.get_widget('radio_save_all_to'): self.glade.get_widget('download_path_button').set_sensitive(value) - + + self.glade.get_widget('spin_port_min').set_sensitive( + not self.glade.get_widget('chk_random_port').get_active()) + self.glade.get_widget('spin_port_max').set_sensitive( + not self.glade.get_widget('chk_random_port').get_active()) + def on_button_ok_clicked(self, data): log.debug("on_button_ok_clicked") self.set_config() From 7f5a0d8ab70a965b7e06f1262d42050b066d6265 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Sep 2007 01:24:08 +0000 Subject: [PATCH 0072/1009] Applied i18n patch from Marcos. --- deluge/i18n/POTFILES.in | 3 + deluge/i18n/deluge.pot | 521 ++++++++++++++++++++++++ deluge/plugins/queue/queue/gtkui.py | 24 +- deluge/ui/gtkui/glade/main_window.glade | 12 +- deluge/ui/gtkui/gtkui.py | 12 +- gettextize.sh | 2 + msgfmt.py | 438 ++++++++++++++++++++ setup.py | 64 ++- 8 files changed, 1055 insertions(+), 21 deletions(-) create mode 100644 deluge/i18n/POTFILES.in create mode 100644 deluge/i18n/deluge.pot create mode 100644 gettextize.sh create mode 100644 msgfmt.py diff --git a/deluge/i18n/POTFILES.in b/deluge/i18n/POTFILES.in new file mode 100644 index 000000000..0eff8a995 --- /dev/null +++ b/deluge/i18n/POTFILES.in @@ -0,0 +1,3 @@ +deluge/ui/gtkui/glade/main_window.glade +deluge/ui/gtkui/glade/preferences_dialog.glade +deluge/plugins/queue/queue/gtkui.py diff --git a/deluge/i18n/deluge.pot b/deluge/i18n/deluge.pot new file mode 100644 index 000000000..16b9fc2d0 --- /dev/null +++ b/deluge/i18n/deluge.pot @@ -0,0 +1,521 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-09-15 18:22-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: deluge/ui/gtkui/glade/main_window.glade:20 +msgid "_File" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:27 +msgid "_Add Torrent" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:42 +msgid "Add _URL" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:49 +msgid "_Clear Completed" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:69 +msgid "Quit & Shutdown Daemon" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:101 +msgid "_Edit" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:122 +msgid "_Torrent" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:129 +msgid "_View" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:137 +msgid "_Toolbar" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:145 +msgid "_Details" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:153 +msgid "Columns" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:164 +msgid "_Help" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:192 +msgid "Add torrent" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:193 +msgid "Add Torrent" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:205 +msgid "Remove the selected torrents" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:206 +msgid "Remove Torrent" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:218 +msgid "Remove the finished torrents" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:219 +msgid "Clear Finished" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:240 +msgid "Pause the selected torrents" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:241 +msgid "Pause" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:254 +msgid "Resume the selected torrents" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:255 +msgid "Resume" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:275 +#: deluge/ui/gtkui/glade/main_window.glade:276 +msgid "Preferences" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:420 +msgid "Name:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:439 +msgid "Next Announce:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:465 +msgid "Tracker Status:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:493 +msgid "Tracker:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:516 +msgid "Total Size:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:576 +msgid "# of files:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:595 +msgid "Torrent Info" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:666 +msgid "Availability:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:702 +msgid "Pieces:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:719 +msgid "ETA:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:740 +msgid "Peers:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:761 +#: deluge/ui/gtkui/glade/main_window.glade:782 +msgid "Speed:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:800 +msgid "Share Ratio:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:818 +msgid "Seeders:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:836 +msgid "Uploaded:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:854 +msgid "Downloaded:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:966 +msgid "Statistics" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:991 +msgid "Details" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:8 +msgid "Deluge Preferences" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:74 +msgid "Downloads" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:108 +msgid "Ask where to save each download" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:124 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:125 +msgid "Store all downloads in:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:142 +msgid "Select A Folder" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:161 +msgid "Download Location" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:197 +msgid "" +"Full allocation preallocates all of the space that is needed for the torrent " +"and prevents disk fragmentation" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:198 +msgid "Use Full Allocation" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:213 +msgid "Compact allocation only allocates space as needed" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:214 +msgid "Use Compact Allocation" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:232 +msgid "Allocation" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:266 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:267 +msgid "Enable selecting files for torrents before loading" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:280 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:281 +msgid "Prioritize first and last pieces of files in torrent" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:297 +msgid "Torrents" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:350 +msgid "Network" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:387 +msgid "From:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:414 +msgid "To:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:445 +msgid "Test Active Port" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:469 +msgid "Deluge will automatically choose a different port to use every time." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:470 +msgid "Use Random Ports" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:488 +msgid "Active Port:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:500 +msgid "0000" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:530 +msgid "Ports" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:562 +msgid "Distributed hash table may improve the amount of active connections." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:563 +msgid "Enable Mainline DHT" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:576 +msgid "DHT" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:609 +msgid "Universal Plug and Play" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:610 +msgid "UPnP" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:625 +msgid "NAT Port Mapping Protocol" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:626 +msgid "NAT-PMP" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:642 +msgid "µTorrent Peer-Exchange" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:643 +msgid "µTorrent-PeX" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:662 +msgid "Network Extras" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:698 +msgid "Inbound:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:707 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:731 +msgid "" +"Disabled\n" +"Enabled\n" +"Forced" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:721 +msgid "Outbound:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:750 +msgid "Level:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:759 +msgid "" +"Handshake\n" +"Either\n" +"Full Stream" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:778 +msgid "Prefer to encrypt the entire stream" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:795 +msgid "Encryption" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:849 +msgid "Bandwidth" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:887 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:911 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:977 +msgid "The maximum upload speed for all torrents. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:889 +msgid "Maximum Upload Speed (KiB/s):" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:900 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:925 +msgid "The maximum number of connections allowed. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:902 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1071 +msgid "Maximum Connections:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:913 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1058 +msgid "Maximum Upload Slots:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:943 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:958 +msgid "The maximum download speed for all torrents. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:945 +msgid "Maximum Download Speed (KiB/s):" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:996 +msgid "The maximum upload slots for all torrents. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1018 +msgid "Global Bandwidth Usage" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1081 +msgid "The maximum number of connections per torrent. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1097 +msgid "The maximum upload slots per torrent. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1120 +msgid "Per Torrent Bandwidth Usage" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1174 +msgid "Other" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1208 +msgid "Enable system tray icon" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1223 +msgid "Minimize to tray on close" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1242 +msgid "Start in tray" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1263 +msgid "Password protect system tray" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1288 +msgid "Password:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1322 +msgid "System Tray" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1372 +msgid "" +"Auto-detect (xdg-open)\n" +"Konqueror\n" +"Nautilus\n" +"Thunar" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1396 +msgid "Custom:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1413 +msgid "Open folder with:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1430 +msgid "Desktop File Manager" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1465 +msgid "" +"Deluge will check our servers and will tell you if a newer version has been " +"released" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1466 +msgid "Be alerted about new releases" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1483 +msgid "Updates" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1519 +msgid "" +"Help us improve Deluge by sending us your Python version, PyGTK version, OS " +"and processor types. Absolutely no other information is sent." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1538 +msgid "Yes, please send anonymous statistics" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1557 +msgid "System Information" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1611 +msgid "Plugins" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1703 +msgid "gtk-cancel" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1715 +msgid "gtk-apply" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1730 +msgid "gtk-ok" +msgstr "" + +#: deluge/plugins/queue/queue/gtkui.py:100 +msgid "Queue Up" +msgstr "" + +#: deluge/plugins/queue/queue/gtkui.py:101 +msgid "Queue selected torrents up" +msgstr "" + +#: deluge/plugins/queue/queue/gtkui.py:105 +msgid "Queue Down" +msgstr "" + +#: deluge/plugins/queue/queue/gtkui.py:106 +msgid "Queue selected torrents down" +msgstr "" diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index f00d23331..f4db6a9f3 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -37,11 +37,25 @@ DBusGMainLoop(set_as_default=True) import pkg_resources import gtk.glade - +import gettext +import locale from deluge.log import LOG as log class GtkUI: def __init__(self, plugin_manager): + # Initialize gettext + locale.setlocale(locale.LC_MESSAGES, '') + locale.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + locale.textdomain("deluge") + gettext.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + gettext.textdomain("deluge") + gettext.install("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) log.debug("Queue GtkUI plugin initalized..") self.plugin = plugin_manager # Get a reference to the core portion of the plugin @@ -83,13 +97,13 @@ class GtkUI: # Add a toolbar buttons self.plugin.get_toolbar().add_separator() self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-up", - label="Queue Up", - tooltip="Queue selected torrents up", + label=_("Queue Up"), + tooltip=_("Queue selected torrents up"), callback=self.on_toolbutton_queueup_clicked) self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-down", - label="Queue Down", - tooltip="Queue selected torrents down", + label=_("Queue Down"), + tooltip=_("Queue selected torrents down"), callback=self.on_toolbutton_queuedown_clicked) # Add the queue menu to the torrent menu diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 7e1190b9d..c0461fe09 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -190,7 +190,7 @@ True Add torrent - Add Torrent + Add Torrent True gtk-add @@ -203,7 +203,7 @@ True Remove the selected torrents - Remove Torrent + Remove Torrent True gtk-remove @@ -216,7 +216,7 @@ True Remove the finished torrents - Clear Finished + Clear Finished True gtk-clear @@ -238,7 +238,7 @@ True Pause the selected torrents - Pause + Pause True gtk-media-pause @@ -252,7 +252,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Resume the selected torrents - Resume + Resume gtk-media-play @@ -273,7 +273,7 @@ True Preferences - Preferences + Preferences True gtk-preferences diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index a14918f2d..9a859b753 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -35,6 +35,7 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade import gettext +import locale import pkg_resources from mainwindow import MainWindow @@ -46,15 +47,18 @@ from deluge.log import LOG as log class GtkUI: def __init__(self): # Initialize gettext + locale.setlocale(locale.LC_MESSAGES, '') + locale.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + locale.textdomain("deluge") gettext.bindtextdomain("deluge", pkg_resources.resource_filename( - "deluge.ui.gtkui", - "po")) + "deluge", "i18n")) gettext.textdomain("deluge") gettext.install("deluge", pkg_resources.resource_filename( - "deluge.ui.gtkui", - "po")) + "deluge", "i18n")) # Initialize the main window self.mainwindow = MainWindow() diff --git a/gettextize.sh b/gettextize.sh new file mode 100644 index 000000000..281903741 --- /dev/null +++ b/gettextize.sh @@ -0,0 +1,2 @@ +#!/bin/sh +xgettext -f deluge/i18n/POTFILES.in -o deluge/i18n/deluge.pot diff --git a/msgfmt.py b/msgfmt.py new file mode 100644 index 000000000..31d117d9c --- /dev/null +++ b/msgfmt.py @@ -0,0 +1,438 @@ +# -*- coding: iso-8859-1 -*- +# Written by Martin v. Lwis +# Plural forms support added by alexander smishlajev +""" +Generate binary message catalog from textual translation description. + +This program converts a textual Uniforum-style message catalog (.po file) into +a binary GNU catalog (.mo file). This is essentially the same function as the +GNU msgfmt program, however, it is a simpler implementation. + +Usage: msgfmt.py [OPTIONS] filename.po + +Options: + -o file + --output-file=file + Specify the output file to write to. If omitted, output will go to a + file named filename.mo (based off the input file name). + + -h + --help + Print this message and exit. + + -V + --version + Display version information and exit. +""" + +import sys +import os +import getopt +import struct +import array + +__version__ = "1.1" + +MESSAGES = {} + + +def usage (ecode, msg=''): + """ + Print usage and msg and exit with given code. + """ + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(ecode) + + +def add (msgid, transtr, fuzzy): + """ + Add a non-fuzzy translation to the dictionary. + """ + global MESSAGES + if not fuzzy and transtr and not transtr.startswith('\0'): + MESSAGES[msgid] = transtr + + +def generate (): + """ + Return the generated output. + """ + global MESSAGES + keys = MESSAGES.keys() + # the keys are sorted in the .mo file + keys.sort() + offsets = [] + ids = strs = '' + for _id in keys: + # For each string, we need size and file offset. Each string is NUL + # terminated; the NUL does not count into the size. + offsets.append((len(ids), len(_id), len(strs), len(MESSAGES[_id]))) + ids += _id + '\0' + strs += MESSAGES[_id] + '\0' + output = '' + # The header is 7 32-bit unsigned integers. We don't use hash tables, so + # the keys start right after the index tables. + # translated string. + keystart = 7*4+16*len(keys) + # and the values start after the keys + valuestart = keystart + len(ids) + koffsets = [] + voffsets = [] + # The string table first has the list of keys, then the list of values. + # Each entry has first the size of the string, then the file offset. + for o1, l1, o2, l2 in offsets: + koffsets += [l1, o1+keystart] + voffsets += [l2, o2+valuestart] + offsets = koffsets + voffsets + output = struct.pack("Iiiiiii", + 0x950412deL, # Magic + 0, # Version + len(keys), # # of entries + 7*4, # start of key index + 7*4+len(keys)*8, # start of value index + 0, 0) # size and offset of hash table + output += array.array("i", offsets).tostring() + output += ids + output += strs + return output + + +def make (filename, outfile): + ID = 1 + STR = 2 + global MESSAGES + MESSAGES = {} + + # Compute .mo name from .po name and arguments + if filename.endswith('.po'): + infile = filename + else: + infile = filename + '.po' + if outfile is None: + outfile = os.path.splitext(infile)[0] + '.mo' + + try: + lines = open(infile).readlines() + except IOError, msg: + print >> sys.stderr, msg + sys.exit(1) + + section = None + fuzzy = 0 + + # Parse the catalog + msgid = msgstr = '' + lno = 0 + for l in lines: + lno += 1 + # If we get a comment line after a msgstr, this is a new entry + if l[0] == '#' and section == STR: + add(msgid, msgstr, fuzzy) + section = None + fuzzy = 0 + # Record a fuzzy mark + if l[:2] == '#,' and (l.find('fuzzy') >= 0): + fuzzy = 1 + # Skip comments + if l[0] == '#': + continue + # Start of msgid_plural section, separate from singular form with \0 + if l.startswith('msgid_plural'): + msgid += '\0' + l = l[12:] + # Now we are in a msgid section, output previous section + elif l.startswith('msgid'): + if section == STR: + add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = '' + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + l = l[6:] + # Check for plural forms + if l.startswith('['): + # Separate plural forms with \0 + if not l.startswith('[0]'): + msgstr += '\0' + # Ignore the index - must come in sequence + l = l[l.index(']') + 1:] + # Skip empty lines + l = l.strip() + if not l: + continue + # XXX: Does this always follow Python escape semantics? + l = eval(l) + if section == ID: + msgid += l + elif section == STR: + msgstr += l + else: + print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ + 'before:' + print >> sys.stderr, l + sys.exit(1) + # Add last entry + if section == STR: + add(msgid, msgstr, fuzzy) + + # Compute output + output = generate() + + try: + open(outfile,"wb").write(output) + except IOError,msg: + print >> sys.stderr, msg + + +def main (): + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print >> sys.stderr, "msgfmt.py", __version__ + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print >> sys.stderr, 'No input file given' + print >> sys.stderr, "Try `msgfmt --help' for more information." + return + + for filename in args: + make(filename, outfile) + + +if __name__ == '__main__': + main() +# -*- coding: iso-8859-1 -*- +# Written by Martin v. Lwis +# Plural forms support added by alexander smishlajev +""" +Generate binary message catalog from textual translation description. + +This program converts a textual Uniforum-style message catalog (.po file) into +a binary GNU catalog (.mo file). This is essentially the same function as the +GNU msgfmt program, however, it is a simpler implementation. + +Usage: msgfmt.py [OPTIONS] filename.po + +Options: + -o file + --output-file=file + Specify the output file to write to. If omitted, output will go to a + file named filename.mo (based off the input file name). + + -h + --help + Print this message and exit. + + -V + --version + Display version information and exit. +""" + +import sys +import os +import getopt +import struct +import array + +__version__ = "1.1" + +MESSAGES = {} + + +def usage (ecode, msg=''): + """ + Print usage and msg and exit with given code. + """ + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(ecode) + + +def add (msgid, transtr, fuzzy): + """ + Add a non-fuzzy translation to the dictionary. + """ + global MESSAGES + if not fuzzy and transtr and not transtr.startswith('\0'): + MESSAGES[msgid] = transtr + + +def generate (): + """ + Return the generated output. + """ + global MESSAGES + keys = MESSAGES.keys() + # the keys are sorted in the .mo file + keys.sort() + offsets = [] + ids = strs = '' + for _id in keys: + # For each string, we need size and file offset. Each string is NUL + # terminated; the NUL does not count into the size. + offsets.append((len(ids), len(_id), len(strs), len(MESSAGES[_id]))) + ids += _id + '\0' + strs += MESSAGES[_id] + '\0' + output = '' + # The header is 7 32-bit unsigned integers. We don't use hash tables, so + # the keys start right after the index tables. + # translated string. + keystart = 7*4+16*len(keys) + # and the values start after the keys + valuestart = keystart + len(ids) + koffsets = [] + voffsets = [] + # The string table first has the list of keys, then the list of values. + # Each entry has first the size of the string, then the file offset. + for o1, l1, o2, l2 in offsets: + koffsets += [l1, o1+keystart] + voffsets += [l2, o2+valuestart] + offsets = koffsets + voffsets + output = struct.pack("Iiiiiii", + 0x950412deL, # Magic + 0, # Version + len(keys), # # of entries + 7*4, # start of key index + 7*4+len(keys)*8, # start of value index + 0, 0) # size and offset of hash table + output += array.array("i", offsets).tostring() + output += ids + output += strs + return output + + +def make (filename, outfile): + ID = 1 + STR = 2 + global MESSAGES + MESSAGES = {} + + # Compute .mo name from .po name and arguments + if filename.endswith('.po'): + infile = filename + else: + infile = filename + '.po' + if outfile is None: + outfile = os.path.splitext(infile)[0] + '.mo' + + try: + lines = open(infile).readlines() + except IOError, msg: + print >> sys.stderr, msg + sys.exit(1) + + section = None + fuzzy = 0 + + # Parse the catalog + msgid = msgstr = '' + lno = 0 + for l in lines: + lno += 1 + # If we get a comment line after a msgstr, this is a new entry + if l[0] == '#' and section == STR: + add(msgid, msgstr, fuzzy) + section = None + fuzzy = 0 + # Record a fuzzy mark + if l[:2] == '#,' and (l.find('fuzzy') >= 0): + fuzzy = 1 + # Skip comments + if l[0] == '#': + continue + # Start of msgid_plural section, separate from singular form with \0 + if l.startswith('msgid_plural'): + msgid += '\0' + l = l[12:] + # Now we are in a msgid section, output previous section + elif l.startswith('msgid'): + if section == STR: + add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = '' + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + l = l[6:] + # Check for plural forms + if l.startswith('['): + # Separate plural forms with \0 + if not l.startswith('[0]'): + msgstr += '\0' + # Ignore the index - must come in sequence + l = l[l.index(']') + 1:] + # Skip empty lines + l = l.strip() + if not l: + continue + # XXX: Does this always follow Python escape semantics? + l = eval(l) + if section == ID: + msgid += l + elif section == STR: + msgstr += l + else: + print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ + 'before:' + print >> sys.stderr, l + sys.exit(1) + # Add last entry + if section == STR: + add(msgid, msgstr, fuzzy) + + # Compute output + output = generate() + + try: + open(outfile,"wb").write(output) + except IOError,msg: + print >> sys.stderr, msg + + +def main (): + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print >> sys.stderr, "msgfmt.py", __version__ + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print >> sys.stderr, 'No input file given' + print >> sys.stderr, "Try `msgfmt --help' for more information." + return + + for filename in args: + make(filename, outfile) + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index ba5c4bdce..950c8d4be 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,12 @@ import ez_setup ez_setup.use_setuptools() from setuptools import setup, find_packages, Extension +from distutils import cmd +from distutils.command.build import build as _build +from distutils.command.install import install as _install +from distutils.command.install_data import install_data as _install_data +import msgfmt + import platform import glob import os @@ -84,6 +90,52 @@ libtorrent = Extension( sources = _sources ) +class build_trans(cmd.Command): + description = 'Compile .po files into .mo files' + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + po_dir = os.path.join(os.path.dirname(__file__), 'deluge/i18n/') + for path, names, filenames in os.walk(po_dir): + for f in filenames: + if f.endswith('.po'): + lang = f[:len(f) - 3] + src = os.path.join(path, f) + dest_path = os.path.join('deluge', 'i18n', lang, \ + 'LC_MESSAGES') + dest = os.path.join(dest_path, 'deluge.mo') + if not os.path.exists(dest_path): + os.makedirs(dest_path) + if not os.path.exists(dest): + print 'Compiling %s' % src + msgfmt.make(src, dest) + else: + src_mtime = os.stat(src)[8] + dest_mtime = os.stat(dest)[8] + if src_mtime > dest_mtime: + print 'Compiling %s' % src + msgfmt.make(src, dest) + +class build(_build): + sub_commands = _build.sub_commands + [('build_trans', None)] + def run(self): + _build.run(self) + +class install_data(_install_data): + def run(self): + _install_data.run(self) + +cmdclass = { + 'build': build, + 'build_trans': build_trans, + 'install_data': install_data +} + # Build the plugin eggs for path in glob.glob('deluge/plugins/*'): print path + "/setup.py" @@ -95,23 +147,23 @@ setup( name = "deluge", fullname = "Deluge Bittorent Client", version = "0.6.0.0", - author = "Andrew Resch", - author_email = "andrewresch@gmail.com", + author = "Andrew Resch, Marcos Pinto", + author_email = "andrewresch@gmail.com, markybob@dipconsultants.com", description = "GTK+ bittorrent client", url = "http://deluge-torrent.org", license = "GPLv2", - include_package_data = True, package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png", - "ui/gtkui/po/*.po?", "plugins/*.egg", + "i18n/*.pot", + "i18n/*/LC_MESSAGES/*.mo" ]}, ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(exclude=["plugins"]), + cmdclass=cmdclass, entry_points = """ [console_scripts] deluge = deluge.main:main - """ -) + """) From ef5c499f170f676d594d6eaa7023168d5e1e4acc Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Sep 2007 01:25:26 +0000 Subject: [PATCH 0073/1009] Remove 'po' directory in gtkui due to new i18n patch. --- deluge/ui/gtkui/po/deluge.pot | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 deluge/ui/gtkui/po/deluge.pot diff --git a/deluge/ui/gtkui/po/deluge.pot b/deluge/ui/gtkui/po/deluge.pot deleted file mode 100644 index e69de29bb..000000000 From 70dde55ecf890a79a1f34a0da2bed50508d0ba1f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Sep 2007 01:41:18 +0000 Subject: [PATCH 0074/1009] Tweaked random port selection. --- deluge/core/core.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 6048be343..01cfbb94d 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -96,17 +96,17 @@ class Core(dbus.service.Object): # Set the listening ports if self.config.get("random_port"): import random - randrange = lambda: random.randrange(49152, 65535) - ports = [randrange(), randrange()] - ports.sort() - log.debug("Listening on %i-%i", ports[0], ports[1]) - self.session.listen_on(ports[0], ports[1]) + listen_ports = [] + randrange = lambda: random.randrange(49152, 65525) + listen_ports.append(randrange()) + listen_ports.append(listen_ports[0]+10) else: listen_ports = self.config.get("listen_ports") - log.debug("Listening on %i-%i", listen_ports[0], - listen_ports[1]) - self.session.listen_on(listen_ports[0], - listen_ports[1]) + + log.debug("Listening on ports %i-%i", listen_ports[0], + listen_ports[1]) + self.session.listen_on(listen_ports[0], + listen_ports[1]) # Start the TorrentManager self.torrents = TorrentManager(self.session) From 9d2f6ad3a0bdf6d3e8a408dd7f6245c0fd81479c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Sep 2007 02:02:27 +0000 Subject: [PATCH 0075/1009] Adding TODO file. --- TODO | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 000000000..fea02c0b4 --- /dev/null +++ b/TODO @@ -0,0 +1,25 @@ +* Hook gtkui preferences up in the preferences dialog +* Add 'set config functions' in the core so when config options change the + appropriate function will be called to 'apply' those changes. This type of + system may need to be added to the gtkui as well. +* Status icons for the torrentview +* Icons for the gtkui windows +* Have the ui better handle not being able to connect to the daemon. +* Mainwindow state saving.. Size, location, vpane position, etc.. +* System tray icon +* Add state saving to listview.. this includes saving column size and position. +* Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's + intended functionality. +* Add plugin list and the ability to load/unload them from the preferences + dialog. +* Have core keep track of which plugins are activated or not, so it only loads + those on start-up. +* Have the UI request a list of activated plugins on start-up so it nows which + plugins to load. +* Figure out easy way for user-made plugins to add i18n support. +* Change the menubar.py gtkui component to menus.py and add support for plugins + to add menuitems to the torrentmenu in an easy way. +* Create a new status icon.. a red alert icon to show there is an error with + the torrent.. ie, disk full alert and stuff like that.. +* Add the tracker responses to the torrent details + From bc88fb3d11ef1734080c9ef43ded33468754d1a0 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 16 Sep 2007 03:46:50 +0000 Subject: [PATCH 0076/1009] icons and internal pixmap --- createicons.sh | 6 + deluge.desktop | 12 + deluge/common.py | 22 + .../icons/hicolor/128x128/apps/deluge.png | Bin 0 -> 14218 bytes .../data/icons/hicolor/16x16/apps/deluge.png | Bin 0 -> 722 bytes .../icons/hicolor/192x192/apps/deluge.png | Bin 0 -> 24967 bytes .../data/icons/hicolor/22x22/apps/deluge.png | Bin 0 -> 1109 bytes .../data/icons/hicolor/24x24/apps/deluge.png | Bin 0 -> 1252 bytes .../icons/hicolor/256x256/apps/deluge.png | Bin 0 -> 37118 bytes .../data/icons/hicolor/32x32/apps/deluge.png | Bin 0 -> 1888 bytes .../data/icons/hicolor/36x36/apps/deluge.png | Bin 0 -> 2223 bytes .../data/icons/hicolor/48x48/apps/deluge.png | Bin 0 -> 3420 bytes .../data/icons/hicolor/64x64/apps/deluge.png | Bin 0 -> 5191 bytes .../data/icons/hicolor/72x72/apps/deluge.png | Bin 0 -> 6153 bytes .../data/icons/hicolor/96x96/apps/deluge.png | Bin 0 -> 9458 bytes deluge/data/icons/scalable/apps/deluge.svg | 402 ++++++++++++++++++ deluge/data/pixmaps/deluge.png | Bin 0 -> 3420 bytes deluge/data/pixmaps/deluge.svg | 402 ++++++++++++++++++ deluge/ui/gtkui/mainwindow.py | 2 + deluge/ui/gtkui/preferences.py | 2 + setup.py | 34 +- 21 files changed, 879 insertions(+), 3 deletions(-) create mode 100755 createicons.sh create mode 100644 deluge.desktop create mode 100644 deluge/data/icons/hicolor/128x128/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/16x16/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/192x192/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/22x22/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/24x24/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/256x256/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/32x32/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/36x36/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/48x48/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/64x64/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/72x72/apps/deluge.png create mode 100644 deluge/data/icons/hicolor/96x96/apps/deluge.png create mode 100644 deluge/data/icons/scalable/apps/deluge.svg create mode 100644 deluge/data/pixmaps/deluge.png create mode 100644 deluge/data/pixmaps/deluge.svg diff --git a/createicons.sh b/createicons.sh new file mode 100755 index 000000000..114a822c5 --- /dev/null +++ b/createicons.sh @@ -0,0 +1,6 @@ +#!/bin/bash +for size in 16 22 24 32 36 48 64 72 96 128 192 256; do mkdir -p deluge/data/\ +icons/hicolor/${size}x${size}/apps; rsvg-convert -w ${size} -h ${size} \ +-o deluge/data/icons/hicolor/${size}x${size}/apps/deluge.png deluge/data/pixmaps\ +/deluge.svg; mkdir -p deluge/data/icons/scalable/apps/; cp deluge/data/pixmaps/\ +deluge.svg deluge/data/icons/scalable/apps/deluge.svg; done diff --git a/deluge.desktop b/deluge.desktop new file mode 100644 index 000000000..85cd5ea1b --- /dev/null +++ b/deluge.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +Name=Deluge BitTorrent Client +Comment=Bittorrent client written in PyGTK +Exec=deluge +Icon=deluge.png +Terminal=false +Type=Application +Categories=Application;Network +StartupNotify=true +MimeType=application/x-bittorrent; diff --git a/deluge/common.py b/deluge/common.py index 1ccb241dd..ff7ca0937 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -115,3 +115,25 @@ def ftime(seconds): if weeks < 10: return '%dw %dd' % (weeks, days) return 'unknown' + +def windows_check(): + import platform + if platform.system() in ('Windows', 'Microsoft'): + return True + else: + return False + +def get_pixmap(fname): + import pkg_resources + from os import path + return pkg_resources.resource_filename("deluge", path.join("data", \ + "pixmaps", fname)) + +def get_logo(size): + import gtk + if windows_check(): + return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.png"), \ + size, size) + else: + return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.svg"), \ + size, size) diff --git a/deluge/data/icons/hicolor/128x128/apps/deluge.png b/deluge/data/icons/hicolor/128x128/apps/deluge.png new file mode 100644 index 0000000000000000000000000000000000000000..85e21fafdcd11a4b326678bab3bd9e9808b3b004 GIT binary patch literal 14218 zcmV;5H+9H~P)lZ!GU+W_49pWo36u=ktkt@gm~oi+9gC zzq8zPZooI`oAgckCVfNFKm4b^{G7Mm`+@3i-^Xr}-0d6KEqd#{AE?euPyMGkhQGDj z_p+Px4J!aMcKGkA)yf^sM*Yq2`nliu@!h_k-6FBuH?SM@uAh1D_s`Ew{f_m&U#BL% zvbyoA-}uFMe15m@?N-w_oB+K2Cw_JR!oIotqcE7tj#+DoH#6ylAefqJHa~Umz4r#YdEd8!p7sLp)_45U-=CgteV?@; zrC|T!T<+$rZ*;k`x`B`aAq0E(OjEB_ky6m8MbGW9?|r*__d8EdYaifEKl}>^5AL7+ z<0w#7DJ2K@@1fbK0fHpaoV{?Fet$^4S|JRbvNswv);79mW2n?B_dNGS-*(~ifAvRS z-rZlabM$mZ0I@hf@$pI+v@9^$YB1SqV6DYk%h?N;Nwq;6%h?N8NK%azf-np@ux}oe z0)&{Eo&2?b{Lg;l`MdpNc8Z?10`NmW_MV@gY&BkDfiO_)-#ZHyEO2>cgHA8T0>(nD z4QDPaV=aK7*{riLHw{7|RZyRtoBHHG_^J0z?CKBNA$nR0!1ui6e}CER^u$kFW3d(v z?q5JCg%!~2$1Gi5!wN7~U=SE9=ni5oTwcctun6YorkR+igHTi|;o+Id=y!MfckKi{ z?R1wq)jgTxZ%rHAMi9lj3oIH1#L|cTAAOs)~LShm02Qg6?QmsS) zOf>7PwR@!6qQc;M}D(262iN0x2bj_Rk}nKfpwz`G36q-uJ(5mw)0-rKgnu zeD_=b#ScwSPJW+7BBbKb!Mz|P)(Y0!1Fo!fu|j(2mk$2D39udoQf)bTZW*mD7D2UI zVc)_G2#HWZFwtm!_y>RHHy3yNx85vz+6chw-~4k&=I3VKiaa%r^#2&{$uduOQC zDlSxvqL=P@zWKG?{na;zo)!Wyn10dEH=2!?SRn|)klPL{U~vq+wAyC1J-{N-9{R1{ zj7I_M&4DiMTLc%bwCN0DtPlt(IecIa6$Bt8)oSG(Z~yt<`M%x$?KetKc>#FCTYljc zlg)|$HDm0NLkomqh!v84oO1f&nq#sM-u{zoec=x{##>t;fU!dYV4hz#3GPVwkj(6{h#>NkJfg3 zES@Mmr3GNV-h5xRT0LNdq*|?V_`rM)>E|x3)9J^K@j_xn;rVNy@iV2(dI2#=j1U-s zj|?R1y_oY?+PP3H?wO?7sAGjh2Ens?gYW~pJr+-tp0Wb)9Y6d_KirsT{5^w22*sU; z_adaiB4~GqoV~n(70NNwmKbauQ+00Z9UA2Rfe?jX;2lGdTwLj~)*1M37u>#ohA;?0 zNTMkCAAk54KK_o~9FHeTPe}oI%@6+Ko%Lq@-yobrc3^RiX1#{>+WqjED7Y zn_;cl_^=GZrWJyTW}O3jr*lW|(Q_+~NuJqy1m^wg$Z?+#5=CMt6A~k29!gpv&;}ko zzn(`A&3c6c3sVjyq^wqI!N-2&-jB}g_846cJ>>*oQuqF4wOV~KLZX6@qeu4UHvY=` zfJ;pV(gijU}mxjLL#L)M78!| zuuqxF;-^$L>(&3@S6)@9$sL2qJfZ-VQP8c)MboyIeer<2!RzUpZF0X zJMPedY(hx53}L?SGl1DK3s`^50==Oo2qg7dhy^B_RhHWWlGGrCxZ|!@A5&lZi;sMI z7l-Pa=_!K%FZ+>SovB2P-$E*7g<^hYl7&3mCwTbGGKt2+d#3Sj632M$*O@OMiZIWN zn1WJhSe_@yBLtCu_S8~`ZeIfeB_+=|IEzvaZGs^9nIHVcKX}7#kJ+`*Q$_$Ps`5S+ zssl(xRH<x7*rrp zQaLjr#fS+I!foY-A>sdOW9beP=2|rnf@&os)s}WY!CLB4iC2Voyy`z(`0}UBZj9Tt z(38IG`DJhW&1Xp|ehuNY`_SSvQxo+(ggbU>mBBD!*y}RrbV&L=q;jR+(pURR>E350 z;KDOHFAJA~YP~_DHA#J9f~evWdqUvL#2nMDun6x5xMP-$Uc$MR9{XnM2;lJE7VUn* zT6YLWe9O^82mj5N;UC@lk-Ii}QhTd+z4@cj%*^`df5575 z`(UgEEbc2UNB2!J*@!S$hOy==$FDGmQ#95N2#@x7YRmf5JKdPRw`g- z&mN}d=aEWb{R!(Uh^)03W3hmgaMz((!cc&LmClg+PhLT5FvfIa%}YP=&);x;H+&1| zNh<)aeEa*q11bJQ2q_SfXC0nr-%O3p>I$89mp}XL1N4Wp>~S>{NrNM z`q;g%?cBs+x?y@!I)N{L`)}=0O8jRb)dWg0Jz3*WOVM6gM(dO>-G7Re_3q>TA1MS% zNx!-MKuM&8VVp7;CNydlfp}AyK5907!vUB8vhYLP&=K#%MOy z*N6v08m*QO3%wBd8OC-$rV<9!D*+&wYE-zgJ|r~;YkAHyU-S1j9{kMjd~Sya@tWyL zCjhU0*U@)cseW8YMR$FT1NDTdW(9z5KjyE$@)*Y0O`lUzdZr7{U`Y^oh6jpJNtATL zp`^@_K}yLWPKknmY80S^MB>bX6fz42kpi$F1;oQX>&q+D>UDy!;!YrPI9TfpnVzT+ zC?`;p^@yd_9>)Ez*WUH&@BhpLpZW0VTQsCsr6-jS@Tzxw@NS`kFCayA`TRLrD(1z{ zbzR!l!hikZLoBb2Lcfv@x}_8->C(V%Upl4w@94YV;-L&bA$n?VVIv6k~IT^>BWiZ&K)?fF`eyzs+6 z`=+H^IJ{fZlSTku_7fkiMes!ncU?Mt0@K^z+h6t^g22J-u}3fPz@r!P>oceil_XFC zsT6^fDCOWf^X{b-2t2fdLl5r^3dG0=NGX|{Y7&JG7%k<{&${;|MyJ}MwWdBb$DRWR zUD2sOZwyQ~BaZH$%#S^MW{tDU9kj7v&8MG7Zgn0-g?$TCIhdafzfLP)xV# zxv;n%0t?nUA+XjJmuic)2Ca=lmZ;ADJMPT$1lhIEJ~YilHN;}+4pP4O@DjsVqm8i$ z-t?)T|L#BBrJ>(_0eIy<`NU!nimzQb{?J@|b%p6xo!7kNnchyreGi=G)VXECKoN$D zN~nmUkSI_D0Vt^mg20)A!1(}5N}uR)K7tiR+SmJXNa2_W9=46aT9?@~)=;lSRHLwj z5F&?iCjiEO&gO0AS{p=EWAS!ZD6EaUMoPgwM`j77^9L@k_xb8$SDXnkW>sqW;y-%N zcRjWnY-0wK7Hc3>sKxlD9I~dbd*ZPS*8n@*E#m+1)?yZS_z1vkXj|A z8U^lH6jF&osue{PDk@<}B~nx(MHB{vVd#V=a_8L^5QP$@0z#!wfeRDEASlAbKw>mR zVSvc|32#OunbYW#NBA(&@0&EHy|zwsW)^D&7K_%Fc0Xo*(k)I=t0=UwtaXQ2sKwg6 za^WT4``c$f_pw{<5A3QB@a;eSpWc1>%;|4iU2^vQo@XAQ*>EA>V3_dLht3j(LC(-> zC2&X(D54-B3I#zJ5J(^LNr?(vA&5f(O358blBD#93H?DroTj9yCP^I(NC|+#hXR1b`AJ^>f;;l_pa1F! zR@b{!qF`i;f`Bj-PQ!hi9|n>zbWUKP6w*fmN=mx@n2k=Kc6Z2N=sJU~u{2vPre>yS zPBaOt6{LztQy1b5rs9+2X;)KsXxI}-Ly8mnWkw1ISo8Esb`LHVj@Wyxkaiyo$9(?R;Ups7! zrdkR4_Ln`IO5_0PPLa^Q) zu)Nk~7&}z87AmzWhYlTJVQ~*qh4d4{ATgxc7IA?s_N{eVUQw2$yN>C%8T5K+>%u{8 z3fph2B~3NPK%zBTYvR<<+;fn|)U+1@7ePGl_BkqnLzva}kS{%Q+5ZnSNE7>tPyfpI z+_E)^cEtxc@ai|cV(3OCW$W`@>xQ%!V#NCnF1JeH>fnH{2Bw08dz@+*)vl^AgD$T!LDrd z0RpgZIPj{!`=QhS?PI-L!HZii055vmZ-(pVFZ{pVjg1K4_5<@AJ-i2S(ENq3pTJm4 zB?_ok!kqclD5P2~F*|Ys5rrzx<1bz5u-fi>A3zWUlEX(1@`4xM&E(_+oq=Jcqe-;Q zlSFy4C}Y0N^8fxlfKMt}z{-fAG08-uib;lOUxScO9F;)T`Y^D!u}-};ixC2A4RNX& zCWg6|dsf9nEutldpgFhxp8AHs`!=GTm|kn7i|J|90*2rI!IrG^)Jp z`M2jG-$N&sxV+M#>NUR_g;YXC#XDvd@BO>iFc*X*HJrb+jy5LG3|6ZZp8ta9a@(N; z2r0SJPUs9wQ3Eixjiie6#E}yS7dDEV%f?z#5z&~Mp(+i-K_5$b_Ryt|h4TxsCMD4c zQKMLAs?$#h14+}To@XYiEN=`LCI*YW{m^isd+M)$_pf)FAGcHh?s@&Y8|^{-5u>#N z`0rkLXP$dsUhQ)K@$*zG4)Uu}V1LOUrGfl72!+QE$}vmT$R- znQ7O=d0{m#Ah*zdMv$No1|R0-HKKX702WA$g-V0zi5mS*+b5NZ-)jvB;j)HF zzfVwWpu&jEMl5%ROf@5_p+W%D%?jsMy4f7Ws}H~A?|$m!=RUUm)aOm0TUr&IHfDZ3 ziQ^F9(Bcd;lXbui+I`1Pdne8%cB06IdzDZThBA-m!yt?01L8z;d9CA8!O96lvsvY( zFS&=w$tFP{xwxK?>e6hXpEMfXWIm~+r0}zZC1DW)VV$kdibk@yUmMtsR04?YRJ)j?lA^Jh{!7W!8gA1k1IRLlUpVL z^RKzL*=cY5066V>&K<=vJNG|wjyO&TlysUMC;};b_D>Rqf%EvY96%`1T61ZogA&;> zMZH$xMK8RYiDr#J!nu{$t?=cih%-j3^1k%NBH0Y$h)@&-C0XsDBx^V+{WuG2am3XA z+o;wm?)#*lh7Pj21ckIQgSE>!$_N2*VtDxMT8=b__B5GoRlO@A?&^k}ckd)$ZkYfy zRQyhz>ImS-fjJuWVvhgRxn;sI@VR}L>kq@Ql=M;g`6yIKAy``O`0FH5=+57L_cNH9 zY7hpBm9Al^af3g`%t8oCj8}3@`-{1RqQY3_KHi8Xd#7(F#wvnUWh_qum}di-k?N-nK*5CV6dukg9;&?0*m<_MK!m{?YO+D{LA=57K1 zGCSu5L`sPg*?fX19WT&fBx;bGoyo2Vm|QqOB?=26$f}a0LZTV0T`BwuulbLhTW6TK zPNRAy;P65#6Nsu-{Q6F@=9UP+e%t*XtFSEUmuXFH`J3{}qD z3r~N1fKxoG(wyoI#IB){wMU zb2E_DrCeDbu)HzwvzUTA7pF=+s`foQ(RIAz0&w7UKe69v{VIUzR-I|@`45JP)Bci8 z4ofwC%A10$fHMpvYa0W=_{v^)t~JqQad9s9@RvI#XLL~d-#OFEjQ94wFb~DdU*Qyv zRu`4~2}CXw6rrTI>lTP>Gt?T*;$FNkC@InXjXa{rl1%`|&#&j9pPpI8q(*Bd@mN7QV$Q4?mN_N>XQ2E|HLAw{b^P(8g+mGz`h#}b+ z*t`ZXx8dU+|0WY4$85YDVdOaci!mP{Pa(@Or%<>GA~7*f6h)})_j#lf$pB$eul*u7 z2fbm+#TAzY76K0Moh0u@6#UH1X3vfbfVKAfz&U>h_fGp`aN_J0-0?zl0_m75GbZMP z2Hk#wwYsqFN>Z&vEbg82S|wN?m=c1Nd;m3?1CUDOWn<$)Fk%kMwE!i%Kb|@C-ug(w zNcj1Im;hOy8ZiaRm#U?`4X^!B?7(9e*YjePeKQRzVde|)!dL#-f4Hf>z>W&Q!N2oU z`w-&!0P`~wd7s_IE9>+Iu}iOlkF#Bwy8B; z9STS(I50o*sDhiC7uZn&uqoe#7v07A$^7`ar8W2EelQ%=`hoHL(hGxY(h&3qNvTQ0 zW#twYW^>EUQEu^;F|1yS@R1)7%3;;#@QQXd}-bw^XM~!PQh5MYUrRo z@_oXNb~B5T47B?(s~v|B)hJ+QB43MAN-f@cvv{$vd=V35RM?GyRn4W481O;lO#(UT$`yZ6HECMJRM@G}Xg#%bPf#c4eC?BH` zgsd(gt05>mb)`rT8nZ~LN*&2EABWPY<#c|zXo6Xstrv9$!v4=UDRwJPSdP5nNB`{$ub(TM zY3C5Ynpb4IGn0+{@bYS5+j7AdMPmkJ16OEUl9(~*wKSWxTyqmM=KQHGHdzu3vDxF_ zI?G31ATn~S%=|GFE3{vfw7ED(VWk{mz&j_)V3xK1YE~ zo`3Ovv-WRx_C%hYD=|3>Iewed zyk%yW+sZ^q%k$JPCFtLRikv_}L35T$tw6;r^;hmg%xI-O$WdXsRV`JVn7d!{miNwX z`^DEtJLv>I2b=?#(dx$5b&l3a0KRCpsLCDd;VWMsRiXlVGx%3qL;xe{-!YG0$|CB> zy?=b+#~93);(Afm`jQ_Ya?KZ8tN&Ff`>l55h^0SB{VXB0Y7t7xY>-4n@rtef*Y(hj z2*9CN{PZ-i00sQRDEw!nJ=39R)HJ@B(&FGfE5^v4`U$vZP_MO6Fn%23)mZW=Cfsiw{DekYeB$dGs=4w4fciwU}rjmLc1^fEm_SU zxQ8~DDeJ%2*0^4~ocX>V-&m|QMXkQbnmGkV8${@`fJ(WifMnX}$S>z+i_f_39)~o- z3JeyETeiUni}adnh3g%(-X9VE6*9Ol)qRiA$oVlEXZncNk0?QY0pQZ$Ogev|Sq)h( zF1}~WFS-ue5dlbz-Iu{+B?|N7TP^Bjd}poE_!YNo@#_5pA@JS188T!mb2;q|q+8f| zQ`m(C&**Gmt$&<|#exKjKpKmc0%0;+Z}VtF3gP_|_kNtWMHsk!1$WteZE5fQzs?&UUNCd^UsOSo3SvYHeHxt<9ES z$(e1i*5&w(F?mm(wT6Cwm@_IUO|=$t_;OQ-SJ?YM1$V#`$VICO*~8Ar2vgBt(*wIFt3|k2rpr>=UE0${I}8Yy3wEzP1a#`vyU<=U;a_3QsyDiMT%%GU!9l;XnDN`U|h6fz$)<5%>^ zmvS*6rNtTt{T2|$=2|ZNBGBMHPVDwP&rST+9(>vMXNKQADI_Pnp&YkF!lsn+OJlca`5HOd`8Z8XcP8@V5#gz@3vXda+k zz&VoT1Gg3+WAXjiy=^DO(tVyha-zHq^afj$@5N|g&>q! zmu|VBLyj`O?B!?NAEWY-@p!z}#TGQj9yy)cbj9ZRg3W~; zo3nV=W~?$smHhfKht7^4FEe}WdKedmQTw0!1~wA_k$*T%(~%2}wb#C4_7=1w0$`w> z?I%8u?Ht0b2RnoIIC0t^dlX3gIzC!!lEg6Ctf7q|)y^!Ozp%pD3sy+Q2m)cQwWT65)Ec(NVs zhyYm2`E0+O{;gHE_E(BzkYSi;;?&K6C8;J(65=E!)hThRnQB!*NarM`hR04_;Kb>R zfFNX>Rn|(39+86*#|wL3^4=*qa(2wov$&^@j%)u_p?zzHZ-)Ou!>u*cD=M=!^!vm7 zLTe2M&Rio~wxpdg0aE7(;MVGK)U0pc>aIDwU0h!r-{D z)Z{lZQFn_`WMV z@4ZGK9st(?oYCaO_5uKumRxmCUz{;NNg2ioacoG_gg7zOsv%Pob+_1KV)*IbpyjD0SfrOX{TYjbCFQ`Znh1S+#cROw!&@PdI$ zRMo+^tiDsCl^keM>!_aC(Otl)$KZp%~@)uv=_{j@MG@&R7!B#f7blmUe%pS4Jj56I= zTPzqk4(*Ib9R;%mHlJf~W#z?;WR`@?wOgZWHQos{PhJ7Qav!*f zp4AOk-yTJZiN@6nA+r@VQ|*{POcDlh${A&Gwg;H}d0)bB{lLotc6pNg2l4E#2yc z!1)F#Ns=%aCJcs}T2--ku7wbmZa?PZpZZH4c=!~NNRa4JN6pyMLm$T97mtG*MT=3U zms3togA30u9W;7xIt(POW(}=1{b9oK z6X!@%&Eb8s7%NCkx%i~C)~^}WbdyIT*>yJmT=^LQ`EvtH0+Zdi%^XToIGV;wSB5^t z^tPY)G43Ju7=y)t(Hz>-VrHU>#c=-ODpyw8U^LchF#O4VAG`NMH_E4-LV%FJ{ac06 z{~x$WpzFiIx#=bo^~b4)hnxvXsu>Jp260M%kT8g2hOwqUj2R4Fo?sZeN3$iVW_q&0 z!b}ru;j>?QfZzRtzo6G%BN8PfC^O!UGQRB8l^w&}!ONpIJNhapY>dBVq;%XL7iD1) z?jz0wWX_{O7=x06LkpAsJ)FO^R@i=PF=_lUMvTc5qn$v25I4;nHuk-sA%o~U!OaEC z&$u>cW9*1o*pA#lWpn#k+CQ7SmmWHW_tHfk%7uYqs^vO|E-knD`mx8Tgp$QQlO)EW zL9W@h(D3p1#^4r)&IQ3o1*RM_QqpuwQFBP)U*x}~nC5lJVf_C!9@e!F?+p89o9vmc zW36Rvy~~NSm$1g2x5jk3=fD2rOJ^QTU~CfPI_YK$K!6i~5D0`77oyq2|44xB_G9im zv#xooIBQ)&>hwevA>jCl^BjNlH1kt6 zCMFuh>1M{4;XZ?Z;~8$OEftq$rl<5;77ECPz}=HAas(O5P?!*54aREMEo?1HSnj@k zk3$7451zP4uiM8OXBu=e{O!vR{^_5tudf$&=Lyoy6aYoR{16D@_Bq`=_>vb3sSYQp zW@fTMvr$1QiPn}YtJjvpFg_m3ULEs`Y+8m8#(T@i4>C)HIgLZ*h8l|bM_pK zYCvnENvsRTmzlqnDahh)Te_!WCeUfW$ljMwBfCFau6oYRk%x6-at7bFcaqtOD%Kj> z?LH5kyy$Gd(O7NlaP9cJRxcc1?Q}Y20eC`4aI*wJQb2#Ug+N%FJDRFW^E(0hgP6nn zT{qLzM4gl8mp8S2ZAY0o$TT(AdTU+th!DQ0wA@pWg_TYa!cfs_RuEWDoIKCzQ)j7! zf{A8>Sd+v5sFUXrU<>VccV1|JCJ>n^f>A*k$s73cHYX^?J9ma=CE(e&%{r*JhWj5m zPp?0~8iO?&o%TO{>Ha_d?bX#)og|5m3H^;c0VKFt0uXGs^_Cwy+uHxa??uRo-XLav zx=EwvP#_9}k#_ONqul1=p4e5Ceq~Sm5*VwoMz~V2QYfcORmC8vRRbagS1w)V)XCHI zx*dWbB8)1oLOIuVu}RxEYyW8IXuyY-cmXZt89X9rlTG+&tai3tTEktp&r=B$)*3D? zZ*cPLC9HPZZ?rbOi^u-S%7x>Xhr^*M2|ylfTrcBph5$&m7XXEhWqsk<4H?#73b4@` zaCrY5Lcmn3&gJE{pW?hWatv_4G}BgZ3H+c6?_3$S3iGtd9_ivfFQr?A(hLV&S-QyD z@@0~ENLZ;Nq^m=8Of*|Sdy!!z+s{qYXvmkHGuC2!o&#SCi8WZQIkGs#+*A#WoA>z2 zgQrQ86k{}2r=-Kqrw`-=GApnZ2AOH~P?%Ijk!n6LK5TY^| zCR8h7zFy$`%mk;-FJF^GSf=dyR2z=~StMZ4ezL6SC2*5W+FJLlzpUW2)QI99@8%Zx zPX=AqmM+p>U1m5KqJjV!1Wu^3NPuj#3+9)>xXknn1xAI@!@h_1xmJ}s4oo@c(P|!i z^dhV49c-FnO^QzS#_}V7^-nu%OWna>V1~n?VQc1J7Yf`g0l4ax4%T#S?x<3g=C^=b zCg$MYX+r-fw??hXxuwn{xk12Y&5w06$ZcH4C^??y;)4FUkDVf)a#ESEWMBix#sC0aD`-!FcFT)2#W#IA{fQYS7kQn`+Po z|0FOk9L8YMlw>$$&|YWl(nWfmHc1jAWPntv=p-md3}h(eV*TuUZ3g{DQw=0{ADKf* zfwh{AcAxtnIfqUX*BOO^PV_2AV>j%wEH|Y;z)=JaI*FzZsXzfwK+ganE1e*H6&mQ=kH<^@k z0sh+T3E|l_sdlC#D_u9nlEg9ncANIfW!kID40;2sHK-`^89qB=f)bq-vl{Qz8LFY= zo+ECVTWdAF{*e2Qogp5^F5XX5Od2nrKlWeV-CeufjpJCi+wGLAGyj$fz%?TPp!$~| zI@Mf!-ZMoI9Ryfj>oQTVX*KyX{QP6tyZ{s_;o`Ax zy6_dWMW=?;=#tZz%~Cp)NmG-5K8{lcy)Nr3D=aT9(Oz389wt}>NZ;X<38Z#TU8ACS z?vXhn1;%QI!-V^eong=)VswhuDWqw-eB#SLyL$HiQ~iEF>G%8E81s1Ae@g`b912|X zriW|ySLdJcMj>PbZq=~qmP-%`;TDwCsbP7&yXBW%D`n5|OSLWRzwal=v|M|JIyAO! z!f3=nw7I}&j~uB-k*sXpeMjnh6%0w7((eywudTCuX^FL!72;v+o?k2^#%N}n5zoGD zmOwZ`91Mnh`GGU^dVRD`T|OgC?Z(nWzk2DBzxwQ8Fi7G!*2Cd21@tv+PhJ5?dn-d3 zRgZ@gi?0TBX&j(Q3L$qJ_B@jcUZwa=Vx~d4goE&6$%LOPZ!dp5M6s0%I(x zu^6j;*|~87k!G6f>J)X(_@Z|q^+J&N_aolEwWM)EuhVAZ$`!gBYaCge;u(kLT*lH^ zI^6+ZK6ZwFukRebRHJoD=gO%Moc-#jJ{8At+VA(%e!rh^ZOq@gK7Je8DFG1M9G8F` zuAICSwf44y`piqgiR-ybYc4$WOUg{O>MYJrva;S|5Z_Sm1KD?ET5k=`In>%m0NInn z9n%x-9LLmH$M`h!AG9l3&mR%%t{tCA%rlB6%PtxkwF-A1-N$qzpx^CcwPCF@=d$cxv zM5*e;-Pu36aCx1NFO32~;vjj@9vflNXzZ<;Ktlr9!d+WxQ=l)F|F*v(# zN@3(!EWarslvNcKX=Nb@M9PJz+VZuB&hW*rpCC}Ku0f|M#-yfw>5=!H z`qC$VKS`1#j^nt~>0FQIUl;tt%@KesF`rjAF~;oew(iBR-yb#itg8CVi-lh|b#0@^ zrIj{&W+vR@+l63my2!k>>}x{g^L&{V~p7TqR9X+MOPiFaVSGS5Dsd%jdrOC!a1cKgKaN z9oxzbA3t|pbh8j(TnI|VPFudu_xqO~d{n6D;i$FuC7EN`A10hQyNptjnMt=&wo;P4 z^OGDoFo%?qc6YGl0+Lsw@>;)cUyi)rYNJ%nRsOj?+glPAr}a`w4ld4c&olS4FxzrZ z9`%{Z6KAjRrTb6O=?)7d(S|sQ`|U?Q{d1Qd{oL2%I8Krz*^2q&%-)LmyEYk=(YB1< zW*1a}^qchB!NmR>); zre7|vmGO`>e&j}Tq=4)mvu%2!#_b2^dG68u%uP4lIs`s5xV+ZoOZT7R)cIxigmGI; z2}jCa+&KQJcW*2mKilv3E^L6R+K+{xDLGG!g9IGsSc6+?PiRWr}0Xh0AMPT4{4>rJeU9j*}F^Ju0qb0yZrVE4{{# zuNmyk2V{kyf|h)^-L3QT#h1MOf{-46e?CU^`pwyn( z7Do^58CijRoHjZGR@b|%clxYt^w{VQ7{=~Va-ziO@n`)M@4d5F@)@d?kcmdsJ^0FR zD)1$h86x2<>mHBKS8ebk`vv>{_OwiTzTwbX77i?VVtIES@Xvkel;O@ z!n6|t09$MKmdw7&aaG^*>_g4NFaQ3qHvP)+_TG9m;=tZ%4(^>{s&(~_BXFK~e-JZ> z6BoXv+J|=TxxIoy7(yip0!6JFQmceC>ouZCZ97;N==WpJTwLMo(kh+aFt__z1rR`_ z#KqyIul&ZwsV_d@`JQS0c96dn^T!3?hG?e*0E%hitty4CI($N=7E*0$FKX5+?3rmXKQrN?hiGfpW@|VfTOt;6fyhKoAU2JZwb1SixOioqrR5FQ z+P#t4!J^M!5C&XGt~~gG^~e6|%f^^AWBhP9Owu&HnyueTFv`bnn08VC;OfO9kIVSd zz5T?QRA80eQC2^VQQkzbm^4X}WH=lqTI;l= z^;>!KkGDM`hi@y|X#vRST9`e~@J#|x-m0^Q=NpU9due5A|I1}qzsp+jxDiby3Tf0T zR4W0MFmR8g4+H1%=XT#QJ=Ml_9>od0{&3SW_b?g_U_k~!8c1_C>0bDw-oyX-(@Bz~ zUgOg^j*~P^Gsc%a_v;Dso=~WFHM&&-kbT!RYdhO8er%I~Qni`-#DN#w6E^ogyFRt> zd@Iy#+OXwn6nQd8j;;t=_V}~wM*4P(%Jbro5++c=suaUx$>m4>tb6A3Uo*yN&v>o1 z)?VZFSS~-^V*l|3;)d$~x1(Dr02yU91Xt7g?Sw#Xu`f66E2Wx?&p#5i77s`DsYBJ) z{2fAui`p`ywT(uYEs6!CV9X5Q1}F@b7AhDjCDw&a&e~z;;r``^?;9?C{e-nP)mj^4 zj21$e;c%F0N9kZN&`FX!-VENjFpLYtO@sTbXg36){N2~9`ErZb@qMwyex8w3suHT! zo@P{^X;tgf6G5fg%%0ztFDHux-QHl(UQPR#)&n85c0KM^2;qu;-MqZjS{t2lJWX|) zrY23(G;ynAZ8cewz28bO%FN#pjUT7o6o4{aGu(3n%pcnb0vE^!K|l}$LMbJJAjqq` z02>4WLI|6tsS!ft#h0Gp1~6%wnj}fIXL?qYp|^P5ZuYLs{F`dyACI0a0+7*m6-GD8 z{A{1nF-ot5VVDVp0BjhB;F%$Wuvrbh5W*x$VgMV*amKLiDs#pKVmkr2CEC6fJ=p}H zOl4shXZF>YzZK)Rnv^QPj+5Qub*u90@z-k=f?KZX+tHI*0LCd3h^>U-dZ7RDn4i(r znEynD;7LbMB>@}ncbZHx z(_~CE3L3E%3zlkKR5$9vQm6~b&euv%i*DR>*M*26Sg=KRA|i?uH(iJeMMX+|K&ez( z=)<(u+D_86X_A>tW+oXo7HpIBEbsZw$2sTT3v3hJJ^k(1dj^he$GB=GF5J2&v>n_( z&AHuVY+}Cg(cNon)i|b>pus0S(QPKIl}RYFJ33U0t19T~ei-Nd%DE+tK-F0=_5R~G z`d;6z#*(cEC$98`WjXZr+jxRk)m6CQz$A5$B(Xixa(4K~hZl+3g5F*xR*=W%rV}AQ zbwd;gY|{XYEOG`IWry5B_nv;5`0`5IC}IDvKbnvR%2?lXxG{o|>;o520G7fs@>pG5 zfSM%1=&sOn0H><%z|Xrb^DIHQK}6G`ChUwxp@aem1Qi6LF_iOZu!K2z=H|2W6@qZ{J374e9nid1{IxsQ-09)nWI@ef zYEH#+at+H%Nvx-nU`s{JOniScGW4JyKnke*OhGZ!*wWf@=??D^nsb`LQI8uQo^uLT zDfe@7?B(pI$9({109e%o8^Hna0igf7GyvoQK#aCsqEa1FtN2Y2@%!QC}z z(93tLZq=_HVgmpGJULlO_2;_%e=h{%`K!KYhxuG# zm?+3d0-pZ&WVaM0KL5e|B&Q^WIg3T`;w_$febOHQ01l9o6npQPaoFO8GS|^zy15(G zUw$-+?T_kj(fnSVn=M}njv#`jNq3hWN{dk)g73ARTvyMJe4EoG@@;il)lx-7e1cD;?QUb&`o84Dkds@OMg_Bn36UfTNTx8gDi zbhiro|4TV#%VBhz#@xPb!}1sVOLVz{qivRd_zO71_pdnzCKGFQW*0*acpU< znx1uUnTSDe=)6eFdKwu#9dF0%LP!~6QWoZHnvFKW}2i4H;w0Ej~%0mmhO zwoW{+9q%`b>i-H>F#WGJNzq5*wpxFir@2`i=l4?t!t9d$lejv)wC_I_O?~_JExl>u z+V5bb`lDOfEb$RCn^i1~pnC1>RtEQ+H~<~@uCTYt!$z?aP6tJa(^At=zPx32|8I&k zhYHvLy13n5eTRYheQa`A1*{l_pQFc;ZBT0T`4d#}A4oFTyWqY|)Z^yb&!@EF&O6(v zlw(!5i01x_$D)Uk&3YRK&@}B|X%bPPd#pH%mw2tZWa+e6f*BkHMw!q)CNE_EJ0f@* z5`8$5`1l!Tmgh(tH6*)osqa{FwDk6GkyyVt01gU-w`VSy68(k#4y*LAp>(DUBn;pL znMOs&;ytuEdLEoCH%wnUznO{w=t?rihg)BRc1TEL0>0>(ZmF8=%InXiC;zl#4NQ(!d7 zexUpiX43T{Vc+J9rdZP3&*iv|nC@vt|z zd!4MF*d^6=r9N+yX;wDR0|sKCAS>Q?Fjz4vyo3^wqZ%G(PC4RE1`q>;Z|ts8UANYp z{!@9lWU9N0slc1B0(=d3XuAx({=izG4^ofE0W|f7sWEp$;}*mrJPTCA>^}g2hGSNg zCSS+JFj26pGRS!L2EnpLha83s_ah(PRQNA68T$1&{(YJMJXG*Si|8Y31qB7wz4FK= z6f`Z5^*n9b$CwRJeoo<7MTirvzJY@~aq?`Mwyq9ljk&5{N9F)$aD1>h90Q8pB~dHt z)%A20=2L%M{wy9LDl-4B^<;$a=p}_W6FrDzlpqlB3?)2M9CX7%%jEIn{P9;%^@T)B zEafrwyovoZ5x6)>$XvA#$zQj^N$fy0&wXU_9#ecP8??6t3pRSiGqVbhu9;Hg4 zU-7DmyE03hgVmU8`2k2Hx0aBQEw|cNshK$H{F++4D$vL-KSsAK*BFY#P1616@71*?dOPvP3or|K#tizT zg283-$ul}`Uo)2xm@t!=9R3k56rg#zosnXI`r9X+)*(yw& zjS_>t9#KyDsMj^R~;?lv?Nqo^cI zOj{bSRlK&3OW!SBYA2t~0zm1Q!gmE(DgUDU5xe#s`n8t>r0>Let>d9@S5&;C+LCD=F7l({?J*k-T2&e_5bJ#-Bn z9!{>%E*4V@r@#t4fHwI7jr|(G^dvFDYo_A}3(++4Ae~5Ta!#nJT7lk8wN}f~HB;TT zoWB15M3?npop^_d@x5Noo;}sxDEnElfI~IleDc034IF%pcjDRH&8njnF}J(CVwqBv z<8ss)pvUTg^FYG{E3SqV4a`iJSmF ztD;0SNFFroD$2)vSR9~|X&}iolAp}?$6(v)(_h*+mt6D|G@lfKnM*=0b|*V;Lh|}^ z2o`j|mX1*x!_JVdJ7jmkfNbe%l=Z2moc&qps>fYh5A2D%f-bKmonqDftv#+mcq3w$PzA^3zYXjV@gErdCwLU*$Y#Jf3tRPhu zNyqyt!+-79`e$M8q!Td!J7pd&-W#SZ7iUEob1)&JG$yBZ1#AutD!7EJlBtU_^Ze>u zAWa5_&_1~UMd{ht7$#p4VSs>)0)=_9L$>zAY`jKnQAC458ua1o(3fS);rthDo2=X>C({RE75IQW10(%*{tV4xbDkG9EV*=@4%vc?bVfoeZ_3dWp z!SbDj6l-K`z1Ga5$zzl_=;b~%QVn!A%9o%@&crzWdx^1j1f`!9ioRldqyLOH6h%C`0am$ng`;>>#B;kcd*qljZaWsM7uHyQ2 zF?aj%bXbks-n~UC2ab?*2DFDD^OB4_vvG-RE}DnYvzO#1_RX-pfnx2)4M$!pqSr|( ze!DmyFSP4lJ$o4<@5>9HJZkkCd#nuTe%q2BQq%m*Q8eiXJ6&`)2XN}e8LMs_Ca?z? z@vhU`NrrcQLz}fgjd%%-j0xd%h-uqH+OcJ;|BG9N{9kT_f(QR{Ud0- ze0ayLz&uX~2+MrhH(by!)p|rgB(P-ugHq13R=1$2E`HX`^j_Sj{Vo?bae-DVzY)N% z_zA)2OFXO~HWt5J3zRb+?~9{gbnD8S0g|y;I^7Rr&&x4Zs68l`K-bzg-@cVcfCr%QW9VKl-Y6%ae>@!AemhK#H`#kD6@3Xv)_aS>-+Q_6#s|Q zs|&z&ro5kgh7O0J_g2=$RW20}aJo3#e4Z&^a_ep~+`S|g{EbIoIqlwi zwJZ5GdWAc0MCo;)yXT09g|ldeJJX`|s8}l^rBfj9-#6e}T~t5zgof*ZRgF6OdBIF< z9)M2~A2^KD74sr!m&4%hm_z#G<$ZlxxepNe=5EZHa+x8IN&QZ%)SNPfl2Z{kL3O=f z14p)TWYoqg@2tTBi4#p3zQNz&kl&w-)w?sRfJ_zb?V#J_?fcXNcY(qKT|nlYY1;#uG3}$8hOZnh31ab=e z?xdw7-KAS!&-_XY#1y4nTqY>^`B%!A-{vS%QB7*L2fQmsHBQissWJBbExuAtTdSI4 z#W1lheJh~bWmKErSpaB<8#;?bm~g==G8GtU23F z0Hz4ZF#fPV7mMVWWmEtRjlOeEmDFRYc_Eku3ydGYL6s`TGysq`Vz9KqKshu8{g{%T zKZ48sT9pIQa&`54JL3xLsaF7@(uC_Kglbjc;yK|B+LAfi>N#3>^1Ez~GGjd795=*~ z$Wci=;9|2S;8MN=znu(pPzE*>>*4&X3@c-n;={d9VG^0TA_3DeG;V<^0>2zvs z&ySQ+W3%x@AleA}#h?C^ATW{oI~+Pn;i@PLJ~YnIAL+TZt-;FG0m`LV$)+wAFe)SY zw?qFZ0Ys`h-~?;AcEv2INrVtwoYFU+AI`{mG_@-0nxovS1K5cB`7yI;BZM4b2ub5` zer(lni6)W+Q@lm<_!=pI(SlK}pB`I|h*0qDDk89$Pd4vun@*TSy=zW}`M{o@^|d@_ zxBXpck{)D3Ta>pYDz%4^tHlpUwp%zb=6mzi&{;oca1`(|oiLtFqWC|P-z=}Xo#AC@yky|^RwyfF z@II2NHeT)hzMHjHhJuWXF;E>jCaDy9Vr>Up~_j=*RgC!u^l|Ix#Uh^s}QMeXdn&^377B5 zFch1i4W<6f5h6p!(P-L|lncdRCOfjVl!O&`r+l<|y#hobk;WI36s?y5*EIizF)OV7 zSAP+rc3O@J{8C_d=7@paC>ZF)YAWA}bPkkONrP>S!^GSfD3h7eYpOD0@N-3(JEm5e zg7v^2+^xziX`zazv8G8Es0WhxM^d?g$y05q6ER(2{^QLXS|cEKK|+UOVbn{2tZWwA zskjuVUA{Up#|G$}pQjC?HDxA~7_O*Gav}n#S+X_UT}^1Up5%myd_LYjVaYoX2ecm5 z`hP~l*{f*R4hRpBW#@IavVm=Mec+kVtl|=l<=Hc`Z;2lBy`1wB%k<0tX3`4-NgD-x zicCLZe_4i)9RhVGkq_m2>#Cb*i+3z_FBbRKOK8LDM|pPD&p>{5I6FxOb8(2))e9bt zE=Y%VOFmuI-*yw);MH~;jWQ)T`kE0hAu09a=H1g;E_E+&q3yFB_d9CJ6DN7ZBYele z`u5d1!Hj%^xeb8Twc3i00@dAmv%?`Aqgmu5*0#kdWNM^Xe#1VClL#V2U?7cJCIVKG z1(c)we&Gguj! zJr^Fbh0?TOTB<2sXN^|qdgjjHVJl4Z@{ykGA4}aU{qrxCHt%mq>8|Wxi(}AGT73K- z?M#6no~q%;k-oyJ<{6=ibK64vE`V4&6dVjUf{@eVrbOVQhX(?TVa0xbPrOT`r(B2n z`Rftk*2cyd_~^sB#J!vD?J#Y4Nbd7wnG*P-MV`ANa8BYmAsEWQ`37k6OM68Q9uhQ2 z^~p>LZMPa64FryZn*?}{4H5Vsju&{+EJT;@F^}W;?F+B4gYWpm(N*UWazu*BM2a#0 zwY0iM+TIKY#eO5csH*bE`RF~L-3PoQH~3>nV5{Z_Fr9m=E5jMX7B$AyT@%DAGv0p! zRVCDc(7w?w(e3JP4T9vU3e<3cz`}AB_=(s8jWcCFVXCbA9U@#`>LzzrZB^I;aqvgC zyu5nbCiSu%9~PeOE8gu~zo_HWuZHd}^Z8n(IGbu9uU|qK-@TF$k%?MvEng&tGQ*%!Z{^{5rH?``55~g3s3LoERU?!m>Sy#=X**m#%sr*nNIFIq3caP}#iPvx`(?A1n(NjS&w98atP5 zDDO3}G8>=JXL&yv8YGa_DD9?&APH6cXxQc?6l2D9)4p{~4s0|W6bcLbw*BqLs(s<; zKN;l+j|qeux3lGAfzUl5Img8PvWcNWYB0}A*~g?T6k)Pvx|N4IPS9_radGoim2B|% zE$o+&gkT-HMV4R;H!`0^AGmnwN@X8&N-)%1E2K^y2q`9zPrr6_LtH| z`QP-Sr&FEnQez6^2)y+AJ~*Ov_H8r<(twOV72B*r$#)Z*_-WrK&yL*AC+#*GRQ*)# z{f);Lhz@KINiF`0;~Vt@eg3UpZ);UrfMOs{zTWCk?1*tok}M1i9Q7)NP5RQ@_k8s~ zGg+B(&$d+t96t>ZEV6X2ib0baq5S9FYCKw6P8+SxLK~WfcYGi9@4GB%j&Xcp@gaf? zRJw(osYI>)Jc)Q7@(ymaI8*3I=p^S>a?TPL_FxMJ-SdD*)yxRLGXVsx8NtCU$Aslb zNF#OXsydWEBt^&*65`Uc$BG_|wz}V*UWmb|$C|>%MFxs?kKhpI4PzLAe=5{IHO;#` z%@n=)<6rCP>}Y$imqvtIAn<3V*6-L6`=piZoFQ@xnICccUHYo|RG^*?LP^Pn8vA^v zHvFc{X9rFv8PNHcRw6s)qpA0oHRJE%Scv3y!Ip%Hkj@o%MuEF_w-1FDesqRCn83tZ zxk9^__!|rBd=TT&o{(X3Gd(R&7W**)BK$)^Pc)X=v}zT>hWS)Rj74W7a1V3b&W<|7 zBKf1|?ts)(77mc^v8w6xTZPc)6n3@`toZ$yUUDn%@DkW3XWiadQY1z zm%`O2IGBAdZg69=bP)@Z!Th*{Qov4C({q4YKU4;|&MZlZ!MOSQZlF!AKLc7M#0~+u zeGW&we+1E=>t}}P42e%Rx>IdL;rtg_Qs8di_<$BH*&%6D7^Wqwl6w0ahMe z{U6owZ$D-heEJ*jn7iNr1ROVe5jY4UjxY5M?AH>r{3WfqxQ4B$v_cW^V752}v#KV_ z>`+)0_a!2yrs*%vsmkG0XToTU$>7Ws9;R%g`B#aCHl$4sru%6?ZRnPR69SsHqlvLwFq-)&I(8WzYJwa~3<^SO*CUI+;(@&Z=h}Ae|bQQZ_;K`g~ zR?$9Kyn~$mn}%F)hc1>FR95>{ji?cofmhVw*au9VWyRGD1yN+rIWRzZkducqBCK_w8FL>Y;(MdRKkk>o46 z$sM5ssg=9dFpl@aqqY{~_c{>wkI=*^Rs9*tq=qUg`pnH^Dj)Bw(|{RIMZVsHJ7!hfwft7&1~ z5_A@jfHzH~8nq=P7kOZh%=%#D*hKN0`e}#YacRqE>T6iJ4dfy>>uL8JiCI>b-+k5k z;C+VmO1%$k#ib8e;aHTuo|q*X=#Vha@E!_};?a0_BZ^BII*rBDlbwIyY?Sc<$KU8J z&myeN0{*erNM|y7?Yj5Ot5*q2qE-);O3y5r2?iA{ZxuNix11!VH4<;jObqa7u zFtOSP*mQMaV4I7>V7%I2oDY_bz1(+Rw*BoE+Sb(V=bBwZU`;}q*r?24P?@E@IsS$Gs zMql8^V?aSkEu%#tMmRa5R0S_Ri_oN zg66!BQVkb3(}Ux}u$_%yA58p>!JUGWFJ^7Gf71>};n0(iYsEM-BK%fgkPN`~|ooEt+h_^Yp^sIz49i-^8xmohEGrN9L8ft9~xizENR4 zKd&Fl5;UI=hg=IiAPLSz1zJ#lLf>=e7rM4kJEiFMtv}GXxYXGR*WNkiFQl0hOI_ zdhsQOo6@FXaJGxR@K1S8dAIH5(XXoP;4gOqCc z2s4+u`ERD}8MQZ3u;Sjo0odXs(BnDLU=GS}K~PEtfON65iNEvH8iDYgMwoxQN~N8~ zFBbIGcaO8Nep*F;?g^%1MX^85qc6HRf$}6iR5(^o_wdCkv&zhJ`IBHkY6-G=+Hgbm z+gJpKL63k{#w_6wImL{w5;oT+#`1MSYJ1R60*^W|lLI5&| zQ*^80WQRUe^u+Jc#kA5xsLs>-Y_1pd^uGipzhiC z#&M>kb4d}!M(cJqN>Eql|70RB;|R&ocr+>lDQf?RW8_>A5RxRla@HjdmF|8EK-{P2mFN%S-#KSEY5$om&=wNC;7xJPPGe;*bK;WZm)=l)u z(iOLYo|{@W#qk|q+x>5P0)LrO<2QCNgwWG*x+=4FkTif=m^Wyzk+fUQD0MlQXDMPB zs+Lb1KYEN|@zK&_OJ)ln)-|b%Q)H1mVZ-$Es_*p|izs80YyM;J87h7!BaZJ$TwqkK zd+3&QghL?;V@srwxBcw(duJ*LB-JIfi!OH`{{8Z$N$dxT-BEALvVYVN6>G{gZv+8BKJtOtY;W;^Cumy3 zwulc)$|U?TE=JdwZ0(&c#MP0uES!>zbotc-VGeV#?=qcPzfW_)5-)|SOZ9o0nza@j zPbaFycU`QA$7U}@y%|#XnCb;E=XFqntI$vzshTuAt1s0qJJ@Xh9zzlZY|F~Bg|an! zC)7)C*~>f-dWqZ$yNOc39L)$PixTUx*Q&JZE4+U%$Xa$Tf@l_!rChb50YXof%T0H` zC;A?lht$t+v7N(rmVAjj+ogRW?OL%J-IF#N=3Zzd>&RD4&!>~bxh$O=M2|g-+^e%T zdG``tc1V$h)`Xo~M!;+DY}_1emQ@{(l`kbF`jDi==H;~%9XYdt0*m8CD~|1G^2#r) z!qjsNziN?69U zt$&H_lj*u6hCB06)ly`s<-B=A9<;H+ED7z8P}bNY5AG#^EahwW<97AM5X9w!rm9){ ze-1kbA7!TmcvFr2x+5>1X8GwC4vCB=i9GmHHYZfN9ai+PlnNI*I)Mab%JlPN#Q7Jf z0;0jixcDZOl%_2ch^<+W19{z}O#SPbSK^b_LAfxsoXs{|AOm@y?z{L)ar{DxVv@@F z!`we|O64HX*wS^`$`gT#-&NNFKDIuo1RcTsL06OJi?0Ujbw>-rQA?(125s(4J?cXqnis$Z>G*d+jNwBRpD^aiuqNH!@ybARr{p+=nV90H3F!oc)UKXG+MV@6cgpVv z(RIWL0j>x7YtL^j<8vU|Y8fwYJBnba`?2w@Za~gi9dqRUG*TVd~kLW8zL( zP9rOF0YvRBFXg-v(J)&%x%7;07YZ60Ts2`VbgrYqH_n0!+n(Y2QQJt~cOdo+@3i$X z#SWoU$h6Oai@}u|QVWdy8%+X$f6|C)bPpbo;LFuxr-SPZ8gu;tmT)=#Da3pP18Ero zdKYU1imEjC5Bn@9mxhN$8{J9d{LW2wklYZH@rp*g zOqUbaf$#{NDr|x#!OPd25Z>z}&oe--nJnQo;(Z1_QJFK`)A!iot z*7;g2E?F!F9Y1_+xRWIqMP6=2F2OjG={!57$|5ao%cy5$@j=D1^KRH-AKk^7sT_Hy6iS=*VP)(zV_AXZ#iUD=ncp^degv%@Vl zGaV%%p=2g_XrUjka0O-{fHlS+$t&L00$ZKdA8`Q0htYvJiOX+Z&{1amB{r}Pn_`8mx)5!X$OM=3^`*bzaQNcqAt3H=*3oXQV2wz9 z=zFhVi{omm$L6?;VCmGSSuWxGR^Rz^+TWjxlfn@z{i+lztiGUaoB+(Qe%)#_dxN15 z1h^o=O#YS#O{VHIW>7Dt!ZW+4WTvo9y<0YR_lIElvw4G+>wF!ka%Y(@iP=c>sP1y= zkwDb>CCX*_!c!mdHf_+x{|F6QeJZPrcr}#M6_+zDS2eTiIqX=8pjT9kGCIjz`iYEz zfk4|cL=2RS^NC?^DSU-6cojzvubV@5yH1y3;FF-}5J26Nc%rG(wFpUsS_PDOsIP|9 z3|FTpI=?E)4-pQpo`}uV(>#%AfrA|WykoG;tbT7qxzI_wTn9q&d%oRS>=wvFw0D#c zRt)WXQ!rU3NB^-H*N9JKv9(aYdsqFP9P`hYjiHhutZIb=>T_;xNai!4zvw-L2O&?) zk;M4KZB!Lcj@KA(YMc7|kGn+nrvJ&rBeDX%nX-~q`iYqKv^=PCoB{V_xhT{{*Mey> z(CGidT>m53HDw`03@q@Ui+kf%OOZNlcfgmlujTO7g6oH2Oao7^l$}NE1#FbjA>Q#- zM~NCZz`rq;`G|%&x<`Cl8^{V7o_H)wNc>NNaxh_MYnm5aUoF`%OoM^TM>!v+UILI- z&Fg_w+git3xJ#sE_Ii9Xjs4<^`>pHgw+a-}J9)t{;UW&uRsc>u1`xlZGb*7l`Q#EC z80bLK-znrPtW%E{U&9+L25`El(aT>5H4i}BaO-Y7`vbkfgS{Q)c`)z){1T5azfUAh6qu_-{cS zAUWvQy_5+nDeWnJBmDTAG8l?)@u7jnJ@`e3hxDZaUWRkauOF%L)~J^zGDSFeR_XUJON4Z~wbs!-JSHdKC4v0P-l>?qn9?hYJK&R$X1Qp^6&M8)$?Bk;*=f4E-K<`u%x15s^YsuDG6> zD2VnR;(eG59?5UbM1cS_I*?<9mmdINihf~jnVhce_^Ny(a$tf5TW$3+qP5j8(YCQ zf-O2-oq8wM_q$wZIQ~e1O?tEt$&?`a1p;l;8SJ zkcGVRA`0+Gb27xyalfj0+$hp&_UH?*^k^<(JTBjP+Vn9@aBL>3X#uaE8e@oYxTaqz z;(F<8&Ms){#aFk z(hDiIH!k}^|2gV%?C(pQRU0oA=nv!#AuSF5R#&>PI6wvpm>LtOWW`17z$tn-)x~5R zk(%Tmh&|=D*0%UlOwt{?1UpYdkpABct@Hl4e+B}4`o2S4{`*d$THUS@2ME4cn7ro;uI@Cl&*h4I#b;0#nu z%Z~ZkWCN6ANs2OJsDTNa)&-obr`zjAFL(CFnwl~&0JO5_pd3wbDKw_R-6ExBQDuFu zkK)X^`ty}hG~o;xEIs7x_ty0F2#FM}vCRe1_o+SLOYJOtA zO)(u?mp~^MuV6R=qNtt3Db5;TZWTLVpU?0#(D&G~8)s~D0SR>Wb_LBFLiCy)-?kWk z*6Mmk;KL0A1FSL3q-@duJ3@>xk54_`+%HRrLo!m%cLu&|nY?cquLB@OFMjep#_+9m zY!%wm12B*M&)ESUyY|w-XN%qjOjJh%b6N%D!2Nfo>}dUMf-iH|WShS6<8DY`_CoUL zC4}ShCrB}V?RG=yXDHUrYMy!n*Ltsn#gH_;e2V+rg5I!|iS-y#Cwg(qATh)kiEI#8 zV1wCep3jopy6N4Bm3K~j)_pDw?n?ue!4=n0rWSCGJs1uY9$TU&|8eo3NJeHgAv%sg z*}1JDPxI^Ut=ut9Q?7nTZrwMsGX zf1kMEjU6-Q2sEJ;!*f43^-VluYs)ecn&L@|Y6UfbYWB|L2It0Pv zio{a1`?Wu@{&7n0TJ$HOYl|AuzgZ!^z6N(;1tpk!@`2}7AQ#u4KVk0Hf~32$aBv5O zRXjchqnn9MRI7sF!eiqP7vfzDF}Z*sQkt_kD97h3m3uPLb1!mh3M|wq7*61C@j|o3 zzH4(7=kf(?aw;yd-}=?S=JH^iv}b-{#Vo7eMG(qi`zzRrxhDSVdNq1-KU2bi4fEcQ zI_U0y5MDiU^#$E2e#UD9;{GJ~-6_j2Uf_N{i8%bJvOlk|!`JKUP!Pf}t*|g8nuqqv zkpii@+grh3L2L-N#{s5%?pfmkaIbxURc7ufZ-4b>qHy2osec)hz0>UcCIDZb7$}#V zTHAQ56K_*shcos*N1sn)K5J1oH@en;C8Z6``BvIZkAF%F!WK1kX!8TKc*j?0q5;c) zx(&BPE2Hk-RqfC&x9B9)+JCrnKxW_!v-7poozZ#_68~G~KV8~!TaV1Prv;eQI`d_u zeh=HNKKf z(Vc5nEoKGJbrZVR{A#9M0c{$qw%v_>9PleE#tp*BPIL+mO6hVZ>j3@qkt4rC6fOX_% zXnLHA*fllsl3^R0?$puX{yjO<%h1~C#+RC=1*`p@y0c3&)JSt?;}bNf??}sJ==3#Q zN8fRZCsSH53BaY@<-Puf`~E?PTXRNtSLR`6wANk|_V;Ct zz}r3OQX%v;K}l?qXwTXPJzjqu0>X89PQTh|5Nz9j!$ZJgY0&uYZ-yrHlj!7_xG$62 zSIJFbOfF1gZod{Mmkq*BUrf+*9w+VxJ0iPr-<4Z^r?dMZrw_t4Z`e?97!DKzcPoMB z%+5iG#+L> zEgZl-^`T7t@XT6kcHv)F#_NV}m-SK7JIgE)fZ@MlD71L+ZJivWMd(>@W$MaEXXZm^ zmS0U@#mOIS$Pa?-9B7>Bp~&-M_nu&$;Qin-R@vWkHQ2-o*j3}FhLN`a_3jgpeWd!H zo%Nh|wHAN}i&l$23;<@Tjwb|%xB#~0v+FcrX3d#$mDhF64;pumUNw44zZfy;Er7@-O|&KyMIb;v3%Ypw?qJF?v3o$euvLtlkc@< zw~0-R$#o$0YS*4Q7bW&$QevPE;rBGk&)>rXGO9))rqte%e+pee6Q>1 zrdCXmVAY@QZog1=!zSq-VfF<_jEWW#5+LF0-(q2Ydfhr6v2kZ4*t0 zKXe!s&pDm2cBzmslwx7MZ{$1|$Lyq;0bk{B$qxpC@r^0fFu25xdIkj>qh#L%V842f zeN4nilu_v`ZDN0HJ!}4^U$v%bP%PQTJ9aj0pWK*!1g5VIY+SW@{j)7Mr}VUI<7-eI zB`Cf~OUD+knE*%SPuau9+fI#h6l5SBMqD)8?HM_a=gSx_4m5ZH zM&Rzw63r&lvw9c8B>{h4Rxc-F0|$nZtf>QOkH57_X(eUQehj{irW~pLoqrBj*tY<( z>vWnuLTPJH`~K?YKL!6K_%Fw3$-jX@p&@K56`y{6`sG0zt|0r79RT9lbr=enWwY0G zEvFIOiL3r4<{7Ev+EFm0RZ9C(X$-v+^`Dz+a?nCZVY+v5e#8Uja16u2$&KJdPDZtY zq|58P4CrRwyP3uhntfGBr_Q`kA*O4g-)%*ee7%w_w2Bi8bphpk$;eGVDeiixjeyn~ z728V7=`B{WH)KtHD)|l|dCKxVcf2kCUR6+u`gedqvIbYPs)9d8#M)LrM+>~h?4BQc z;+VlgP6a$q(~EL7yVZTv>-b zjuyA8THNC90v^xvGhFw@Umi7IMIOU;W|oYX!;Us+m~j0)h?&Sse)WjO*%Bie*-HU- zXgF|11N|}8Lv;^soNQ{!9ZE_fkoo?remAG-@4MnCOQM(7+V9!AXZWDHsLj^r%S02M zg1=!QbBOyrTmRBMABDkSOb^ZER@^ zORPr9nk^7O%)G4ouWJurP6!ha{g%|!l2Qm>a>-r(h;26fL$6#@k}TZR+QIGSKUZMR zZCP3ExSgBoTODh05{Oyz_3a`g?-Uv5mx}%Jb+OqM3nVZ0+Vnno;kNhh*sY=~*6*s4eT$q`ornuM$G_*CECDwjw38zZxv*hl+L^EY90E)Z}aX258e9Tpf4<{SRz zj1LLm*}%qk&esEoV~4y5wQ019IWvlbrhL>6RSqFc1dVf3fVbc=6t@={26Kny6b9(e zY{LW9GESu16<4uoj*FAjyK*ldc#X#XMOEiU#)oWw8sFwe+9J^mS6C$4KV?Y6kApAv zEdbQ0uT6l+W+;aMnJlS9QR>W%-ZmQ7Ybg0nKi8ikT^8BxFl6$c9*QJEqi9E18vpV> z=a`|vcaY{e#!R;Vd)Pi#7;%5tbJHT@ihteq#SQ9vm6i}oN%~LYFDZfZ38%!8Wx+~;s)ZF2}$%eUSM*?y!ZVC;n6UjFcD)< z>)^&`Z-^k0OSzb<(%2_sAma+z0^YpL+A@9ocQT{o7Sb;GOF-osRLsZC>P`8fUVHq& z@xf+utbkUuvV?N~s7N@M*dZBySlqIrsDJX7s&X^Jg{sC-Q5+Vh^~c;+1z*RPrW{ z+x(b;*B!(Gw8sP@Uci-n$fGWuqs|#JoGiX~ek1>?E6P1wcD45)55cfOcM{7a9ruk9 z@|#Q^e?E&vuSgLr&)OZEHU34&`Yx>&E}rDBXiZfZeqsu9X>Jwv9bE@xkv_eY#-Ybrfe$K#R6k;huT~JVaUC7lm>7&dTn6hzy4{2T*Q1(~nxcUTG6>+e8 zT$lPwl~-(wey@1t=oOQsn@Yry;}{iuA^tZdt7r1O39d?3 zAGK_ud9~>-#PImJizOf6BmeDgj+&0{-WX?|f71&-TdBPcE++2;FzTIY~ z-Bg51|J;=Ov{iA!V@W}P*$jThdONsq>BxPS0Q(P1$H>5o8Zjv!hWyDvn z2f%Nfi5m^>wmOg?IIWPf1pAA8GY7eEr7l2&Rr=RNwsdtgW}Ex_R-6RulIRR$35VBR z+tsPs1nH4QPyS8=`Ez4>0}V z;K82$Akh)``mlfKfZn~5k*xKeiDVoAmdJ)fy}`Wz=-R-?x~>!X%!k%e+%?3=Xdt18{ut37iE z9JNhqAHR{xtFIia_k`Y7-w!{sOWx8Muc;VVfrHz>v=ST>{jXn;`{0t^m=fyaYf`(i zd!2Bc^noKwbe?mjFWrw{hyodsqNrtn(v-~kZ>0F-PGg_$-$Hhwt;q26k(Dj-{uRp= zpO~|6^ot_L0u7bFmc@@;yPmwnPT5pOBX(C9cV3>mRwQIbhFpkT*Rz?<4p#jG0^gp? z(Bxoo|EfcWD+QV!U)d-F0#T~3zonBx#i!A%-(<=km7f*dvo3IFri!Hg#J5h)F<{Nr z4sZV9oaRg^>`oL*={ty!SA6Y#d972L4}tV3*U#Rwu~Y>%_c&TO7P1u9oYx!3s*xPKl++ z`GG57`{K$bO8|xOy}(r@tK^h~i|#)&Jt;r_1Vv8auYu2`4XrW{$sj+Y^!M;wS*Z{% zdN9pXA7)i$4P(`7pp^RyL*#_?+pCt-qOXm@BhA1^QNtV4kdA-mZrMLHNtI=_ZiT%E zWd^1@x4C6e@s%3C07mexi`$6)`1k;uj&>R#(CPVl=^xC;(~6DLHCtgq48Tq3V73DasO0nLo<}Ql1$G=R#SA zltTcR92*2<44hjbm|0lD$Nv7aGFb+oK-~?KW7a`*;H_p}Saz`k@@=1U#s7K8ayIy5 zgv6YIoPUL3Z10Xp{-#&NNB&$y-uec%i?tSxP>c+g?QapVu)JoRW7iWWfiIuyJHJrc z76KpyJ>+gLkeO)>oDwO_jqeeFGwasfhsufiDC{ zkDbEbee#P2c9dA+p<+)LI#%6iYI)Nh{@3A4TSCw$#Hg9S4|99JyMcv(a1gpF-O!!n zh&A~wm&9`{fSHp>;d$!;SOH@S7C|vQDF{RmsFh+A&4Z5r*u%`~U!+L#&)avmiB7|AjIP5?!@&0 zfp)DeKM26!p5?U`($vS+yT=CdhnjLj-fFV1#pQ-7 zgVyj%-k>0S;&MB{z%~(p2Os=@5&-{0Zwn!Mtia^>VE(XkA}s{6#)qSuq)Llxe;`FS z&7@zDTJ7%%K_YVr!vkfE4c7n&1BbqO+5IJD>VLZNTQteaHJ3#mN?@TNLYb!`V+a{!p6?8Db0M7><^n3qn;AzpNmF#X znJJ3X`GRhep_63FfzC{{C`~hz3K6Ep2T_PZlR$g?_-VZF!+(#tg;fN|P^851A|*TX z9wEqU@m}WcD>Tl1S>HmP%T?4?VwAG?c0yYKav0b+tIn7P04AEFwLi$7!Lt-XTL878 z7+O#5xObusEg*mZ&oay|t~t1$>#@>;c=rFF_shNjx~$XAog)PR!t&(Z#KP!@bz`fO z;HLc(UOi2xn_^+Pcd@*)k#PXY(NboNE+AJw6oAl^;OdEGZR}eztYnIS5eBLygaM)1 z=|TuXtKGpvN1jBzR>9bC9UMw!^uH45*anP0(~ zxfR)_#&H82ob%6r_Ah?*o#*|cFMzgM00R99(3~#}-}n;%#eiJJWqi2i%@$oL7x4Ir zxeZRWImwr2rAY`O7kN`Qo|r+w%D*EuOM^`6=tCvL$c{=;_z-H92wgMg*kNeEglf-K`@4*;8Q*AX@)_7Lt(I~) z8eI#B5OR?btr*O2IB-hcIyd{}oUDM5^Rk*_B-0Be2qZWTO$}kFs;5GieU2PIgGRG$ zwZD+sA8GgRW*_?N_cfbMG#Vx_c-e$tD+Hh~^8*Od^~LG(uG{{-hC!SQRLTW!(d=5K zh?6r*n@*W{HWES@ZExe`WFj7n6ht5=CL-igNm7Yw?JB2%O) zd39lzB_w*K1%Sb4`q14@+smT1=bqb%g}3MfI^7fp_l%ko5J@O>DiU*dg)05(fx zp#*GJ)bN|DkF-`MZ4uPei&2PyY7ym9gd`IxDxbX@C8@ zKJM2XYOlfU6Bex5Lwvu%wG!GES_=d94D2yR<7-84zeo{M&_@ViAkEGDM&#lb5+?xy zICAU^)*5Xi3k7m?P;2dZnuk0r<2$Gm)sIuNdX{i!1!)^VfauQ7VF=o z4{CK1j1T$FaJ^c>$uk#c1po~vEJjvJ9}*(DFWSbuEi+2%ptZ2bF{&=X2*LohQjA)) z1TKIRr)Keuho1n61mi;$upmNWX0_TcuV5NW?_Y670DyN%yK_v-(>r)8G7B znx?V%^o zX{}?hQbeIxL^rh-V8hHW{1B+&L5=nG^%^#D;Eu~XTA(zb5yk;f=d^-genVq_{rgVT zM}E)Dbp!<5apMG{K*K{R-QRfp6q>CLeC#j4vt)Yh=s&!z*<8!gG!@Neb0h74RSG~J z`fU8kGFF;+S{y{>=jwx-t!__9v{or%@ggEZE*b4e3IaITmyCwg!XWXGAeTWo3<7X0 zg5Kt4pA?iZV5pR0)GIMo*BW^2@h5TS)M*5ip;j$}BY;p)!ZM)M(4NLt*Jf7Agl`#) zMkkfq$m>!i+3v8lFhXKB@=?J5CDc=f$+9@vi4+Y?ABKUWadO^wmR59 zHR7FgWUzu`Cl@S+zX-C3fQ)t%I$jDg(G?C@M^G`p;G`H(GMtU-XlGe6Os3j0Jqa%t zh{FKYN&x{ASeReL@h6UTB!he*3moca1k zekDy)o@JTnbUGqU(~UH~=+kz=bR`5pGk2cxHxz=jv6xhLKVyo8@zeB4n(Z!XcYY7G~!#Gkpr3b^{>`5En`o8mWu55g7|zw8>EH?lHMxVsngZEtHjV zpp*-L%x|lNon~&XZ&X==I3V0{!?>OJN4Skm_F9D* zX=lTnLfx$V%?!667(=xvmps+w4GW9wc=X9xkLk4_WZmXxroZ;l|H?TRNs@?GtCcD3 z?^=NT_e*94F1G*>&Rqb!@7?87YvqZj?_yE$R(&!(RmsGtT~>?;7#ptR$?1hnSCctk z(%6?Ep^I))iqEX`s|)R{gQ&BkY6(^|RSS2tzgo;s%|Rr%Kqg?|1Xnm)GczLw#snx! z&{|!_^6U&+jdeMbMO=`u&8l-B9+89l&GIzrTIDS`wN45S2@T)KXn*0rHmMP`k0Mjt zLU0_~J%EX!ih)8x0PRjjsjFlMVz{b4Dm)}R-A;@fSH01 zb~bN4=0L$6&BPfz*rF%g(&G~{vkm1NKsuUSfOF+bWH3`TDqkCnfUMg^(p<&L+zc8k zOXzgk05JqnY*<4>B4YuB-u|EMT}g~2$8~*?nU%FycUSM-!`Vm=hvZNuZJ0J?LNH(u z5`2&$>!5=VzWQumB|rxOKKWq4_Q4n3LocehGG{YM^i)?Qq{}0>(L>+hLEdVa1}L)CdmW@qX>FkbX#?_ zs=MfRI$(^!NxL9=bTD0EBNqw?2LwgZ$bZq#rNPgQi7xPn1m!{oS1*(e|3O~k=ACWq zRa-JjqzjdB8T;o!?|-&GeDg1T-)EFk=6N3HoF}#aB<&-~nH~WE*mS>gGrRo!Hwm#q zl}{N*tL@?ZYB_eZg}DMcU0*J;bOI9E!(0N&jMqn*othLHyd*5jA)srS=~`t7%^ji4 z2x)ios9#&{LS9o>z7#^PV$GFJF7E{yQxAOj?G{?q3R=xN0#AmD9XA#Jj-;50dO{dP z&?B3G0Y1bOfiW;n8@o5({_{>_cV7s>`~5!m zeV-ZH|1cytlAP8EF!dNck#SlPmDgmoj+n!U8N! zQ#CqlS6aAndk37U&?slh*WnPHi~R?meZR5s;msfjIHi>NzRx|+Q~A(`(*8%z4?0Z( zOuhAXKf9A(d+AxrcAqjYUTw6IaZ{L^iIU|AAy{9YL$lpOyL)mZ01WNO0Zs@6*T9TG ztD`2Aa~TB^fYkd$&J8FVS{M2b=Mc0BWB^bDQaja57!f2z5DjL=d2|gS9eGX}`d$z1 zW)ro&UF_GT@4y9MJE>^0PUsp5%30T%oijDkajeYcaYe=Y<@cmEs5U$J=+*-WPSvw9 z^&AY~9DZx>N1Gr0?5B(|5d;Baj4|K$Ip_SbB!C$OI#Qc?^^Y=(&;9`+B=%n9Znbs5 zDYh(0f=<^*s~ulz@g#%`xelq04n-PM`U6bc`eB(SVnmA~q7WVZ0a5c1k?n8JEbFQ_ zLKXYdAZ4Myp%(U4TAYRClFyjX?dCpedlgi+x6y64RsNC-j%Wms<_iPoGRf`Y z>I^QZ*Birh2*FWlv~m6BCOD%?F(~j08TSu*&A0FU>Yx5bO?dGj2xza@<3SKi3;r{2 z0)_8K<8Z(y$u{lQda2UNekxaf)x3cKu({K~!fYXyDxpUL0vy{!**M`MD$T03C|9<< ztXdOAg`mwMVcPTBG6^#Xu7(ZmFYU@DIXk=df{coAxK ztUv>F&-!31eg&< zG*b$6geMG>aw8mSM4Xb}*CtO0ay{zss1#H{qfvl`rV`ir-jUO*pCgO_8Brh=&p)?V zz~$v4Sl|N$a#T>J&BksWpMJ5W2+w0tA1=Xvf9vCa`)<3sS>v2DrTrPDlm$Vcwf`ib zAG94=PJ;jhQviL~u?RdCboYLloqy(4V%f0}kWeHjByxrTz;zv5I=2AJBB<8eM^_z( z5It6l@E?Sc8%CLmwMQXNO^_%8Bti#TJGz_;83EFZ_j2WjC>uW_4-dhu|3=~Kmj>`* zi~u_1TF7Pa@^k00x;O(ur1{nNeE2;NmPPRSMiqB9t7?+YjPt9d<^A5>tq*_ty;kLJ zg>%k)-)Ee2stEuI;!z}EL)woN0f5Q1{Y1yKTki$E#xJuAPya3YOO9*1SU$z({lm{ zN0{)17ZN98^g>HS1g-TgLhx(nmvHUMI?}E*d4&4o3L((*`*`R58@TtNf^<3qs2AHg zXEIX6Iat4Qd+WouzTd3et#Qs-zu%{fG3I%m27eOJqmBoCb}TyVvn5!s< zc{lezx%`i20bW=qxp}!XfYhs`m;WcI7%0dY*T|JM5a>2MC zRGc5!tF`go^)Jxe@1oxi(CKuM&8F4cU@}tVckAzO{_3B9uwUP5D)2ML7&X8@3FyZi zAB&vk8(|ZJ+t@Iwk9Mmae}DU(%>3oASavEtEexR3^Rcm0$80edix2@4ShJ-ZE}vV3 zn{rTVc23MmCK~isLhr+z3*W3B@?achsD^@CdF?rK~GThs$p&u|T&J_++p0_N5 z`PqC(gm%|I_7OpwKPKqSQ}eL}_q+9)MUINcJl&t%en?@c~2o3&Z?TS6P}X z;)Q3{aOtUexG6`SAJ_WTMjP*4zk}_)=E2ktA(xw^oN-$J;_o&-`0*S4z8`ST=>W4w z19(#KpP22hg^WMtSnoCTUEo80Ht_s~ul&&;rsppI2?!ZjTQ#3`@tZGPPz&4+79r%k z!sqvP4ixH`>PHVTe>5p9ORdUD5VqwAp$K4+*fpX`^agog?&J54epjS4ZOg*i;w&z% z&m)^n8AOTO`a!_($roF=yIDQpC&+NAB#BK(z*{%|s`>eU{ zX%(i{pOgS3(T=`nj2?(cpzmYeGbus{%+KVpwls^C#hKVhm;^p!0(UkmxOHzA{XUJd zPDAHef}abHw1chI=Rf;izp-7{;P3bQ0b`6#0RBlquRl9h@B^n$0N@}Ajtu(bX1j$< z`Pw(XlPa!#i;(DD0#gVfxUg2nbC*{S_y>ce*6d<)yN<0&Q?6+GLI}6}l{Xuo{L9}5 zK|nd@j4{SE_&v`%4DcrGL8UoCpdX7Mv_2Z-~3O^WnIjb@|d5^W3gO7 z+D%1s0Qw|t@+jal#&LJ6hTHe|&@q6YwDqAz0N3XC{5${g7wzi(JccKB%JKr->P1Gc`iC9mneO*6km*ZvFfhYF1aKb1KcBFnJ~c{v_udZS}J;@-PVi zj0g6i!9L(vwrw{xfAt%$r^}bWP3+XtsE2Y>4%U`taDKIng}GwPyf}<-F5vY8cwT^> z7r^TWsx*@<%NpkAFgb}7Rcq0v5s*rykZ~R4GA=S{xzA->6!TfQt{vTM*fWZFlQE9% zN)z|D>!{S)GH(YM34EInilFvZ^W!&v#5re5>r)-yS6Y7(U{5m|oTxl(0sx~)Fx0#m zaXhFU?QPecd-m(Umn~oV7I8+82!PbG=a%QNJYT|Mxo}`oZJcP61qsa#=!i|irVsaO zB1>P#JSK_%xM2*{X&0Phr?QWoY71MHmP}e3pzV`s5w>MfTeN@C`t+?I_IoXlF~*W6 z52ch3(fUcSPeY0mm$N_sU~q_Z60na4{P4XsbLp!urWc?8dMZ2nJHtY!rdWiaJd?-L zd=ZP~BFZ!Qv8GZ8Y7Jl#k-{VZFn|Q=0{STC=puG-K)PNZyVZT{R9dJuI$&JlDV`TJ z82Fa$^hm$@@B262`UgS#eZ#?JoO7z);-ZO^hQqWcA;qc4Ss?&`aCUeU0fvEp90^9X ztyF1cu5|IW-%ro1zfNp-b1TuV#E-?$xmgdf5R%fRsbFSnJ-8JOB6dx0R{GwW-57 zXOvRLq}tcEzBy4fXg|%Ua5B^JL*y(GK+8CDW*V)30Q3WnEdch+rPr>x<)>dw%Vb1yLjc5RDR*NtyK0Bru=&y2wxI{lw-qnY@|{)+>{N+ zaR!n?mv-%WK@8;M9P_01Hv&MgQ+C@H-ben{$3O9RuiuiUy|i-(A$$z*M`dbGIvNa0 ziFhb-Rtcab3EWA|oJq9(B%3Cpl__6d%&uO0+089HXQvBSiS4cpoN^r^F%npjRv$O9 z6^#a?ZT^@fw7>170^1VX@R}cZoA3T>zx7}bfH2G)u1p-B0RCYU*hUdx82BHmoOJ?d zIc{xleny6zLk6_Ny<1q!=T@#gpDL_ewX?HVZ8!Hch?O7q0}j~!14v+=2P8*>c;7G) zLWoq_?bw2EFu(qhzx&BMLG{)>D8Lg!ga*9s(=Vx;z@Ol4t>ovUh%gBJXF?to0%(~Q z_@l@$hyWwOKcGzj5Vy2kO3z(hPvy#Mc4lVHcJu32I=gO>^s*qx4RnA9I>iY;0P#K$ zA{@u*I~M5zq5b_4j(U^hVzKuiGqaby_RK2;}o zoIGj-&~jAJk0QWS;2*LfY}>Zw=9i1<{Cv^M6iTUdv0%CRyzRP0+j4V&bf5vr2>~z* ztS5wEg!*m5{kCA;wxFFB@9xyVvkejMPB1N?CUL~Q_o<2aUW+qz9`+a>^@_l^^#H0bk8up7rA z5oBaiwl&}>rCfc+0dPvG2!cR^UW3{M{vZ;JBE~R6oCzC$oIL6T&@#-9f0*D;f_~uM zaU9|}P7L(=*z8jh5+FeaO#lG}dfl(EXIh;A009L_L_t(EJcl%IOh6~We-sj&X)|Y( zJT3$N9~P2JQ7IVARwSK@f04h#0ciZ1Q+g5==sXM@}9` z0+=#Mgl8D&j{^RY<_`}3-IK?{B!un*4w^v#OrY!jLEA}@;E|TcodBjBM&nNce%+5D zpJRgkq%v$I7}Y)r66p40BVQ&0BxPFg59-5+V+`0Q+oNQx8H6~flgGzFg2z_A%mhfv zae@D+=8q|Zz;CvvA;2Ug_|nJ|0*sb%;2%eTLHlGo**7eLk_5eOAJz;?0{=@bPY5ub zoR;~cE!D)mw;^WgN%x?|Yng4(~e$ zXLg2}S##ZGbw$xG3Oit3hz1dn7j{t~SvMI%bW=eWbJI;1cD|4(q_T@3DWsyX2or-v zkqpUH(p78Evoo`EeBbAJpDx-3&0Sr3t{!;)zYoum2UghjzxvLGeXqPfw9W^EWtR!RHso=?dZ|oRZH}KZX zystHSl47xM!JD;s&)~+2o7HD>&BXxvk(Gif#q__8P-0}69<6lfH zm%Z&)#L{tw^?9{PL;FH{NntbGd;Bo9EG8z1QiG0>w9reUv;IXChOK!!zdq3QqH09BY7p~}T!ls;0kxDpdnnv8# zXjH4Tq)Dle8eX+|SL^&whfn_3Sn9fITlc&@lQ3k)wsiUnNi^NSj3x;U3zHDPRcEf& z#LAY*rjxL6s#NQ!*L=Eu3wLn&fzALMB*;Cn}nc4 zZFUALkunmF{ZMJ-o)0dKek&K9TMjQBFF15bZ&xk4_6WqL(kWmU1BjC1r&<~GG2_78^wz|#2g#Pwy-P% zGgUxI!F0VtHW}NyENs)>sA)=*LV{9gXrxjuX)m^bT10FU%hWM+9XsY?d#k9mdc+-T z_`atP3@r&eQKJxsia#U){1 zsJ5nokRjtU4WtzOdHEXAh=pnDXc`NcLdcLnNCF`dLVy$~zh^C4H$4#9^M7CLUmKqz zmx^<8^epj&ODwetAr`PeBBTTqx(1#d(rLF9TJ6kY{-*0TKlY11aQDvrLWEAM<1<@p z<2WWWQ`2-?EtFx=@k6>jpCAwfzM#_!=u0U6x^nr{@y}j=4^X$jK+Z_7iI+>ot<_qK zKuE@?8g%`Ddc8q)a)NfFMz`mq6!@XSRso%QHN1HG*vr$`|F{NpmxANf>u0Zer7icD zi+z25An5u5)q0!j6Lsp%F0*sFb+&ELd$(kE|ABv zvLoB}?DBG}x7l{IWLZYE=ZkKu*}U4EzViL06CZyCRDp@bncfM$u|)s}uohM%0Uzi9 bV!6;ivEHN$|IEN0)C;d1TJzG&@2t8LjacczVzH>1 zt?Pakh{!IDZY~y!!Af+>N&(fd<6w73>-xSz#|AIne{`ifbtQ}T?SErv-P*pxm4+wk zPC%j1v8i|P;mc=FpE&ow0gvo`Ez`HA=ag$g*Or@n@R_&wbr-sx`0CsQuItc} zHpyhur1N>MR(-njnbm1Kco%qbDUT&1dT9TVfo#J3Vr=xHHa;aOaO%&W4b1wZlk&B1j*Ji79wxvj01_B5|Niq>92qYdF z9sPG<=$bb&Hoa~5;GU5c?I2sY@4fD{`arebVC_IVg^mmf%OcxZprxmuL?(x!t3lEz1L9%Hy@h0*0Xz*HrSk6sdHUY}zDk#j#AC zq@iQz8Zk{Jo@qso8~9;}B7}%V(R0fJEK?gORT@N5gda+>nG~iGCuC_)g+_do>YnnTWt?OYZhWMq0?K-2c{eTn0EWGsVp(jZ~Rh{sekRYi#F4Je}s zi6E2_uIHgCqHzD-H_ashsw$#TMwDw!>P?4<$r)53NtimirlBa;B~cU+N=YasK@bvz zA(8JPWPr!Cz9b+BsHT$!%>|E%Yx8(sz}II+P;cBADJ4M|5`+>z2=V=pFqB9ckuntO z^;&J@qt~l9{cdYh+jGw6v&o@SrNPA2GM1^M>l&^%%=(R+as3d_3-N*gq6keDXsRHl zgk(*H%k$+g|B=YQ{1ZW}J?QH0**#yg2|`J!Y7>SM-}9K7oTOf@;71V=%HPpAUO-X} zXjIBfUO4ya^!QI>K(rVj)tEY8Pxo!z(bL(IpRYCvrKD78Qm!&Q(4G)NU^^ZQu1}@Dz@0u$cHzv~PY)KQlx3iNa|eq(b{5cuW^`@b@$8HF*1|4R*L0x> zR7D{k)6jJdRS`H|$i&q-w^6!ueDu_j!@x8!1-LiOmiqJskONwPOz)P*wzl>S4hhX% zEff(KiV_6A`)74(^yH;)KmG=&041Qln9)iq4(LGahDLHdWB10mWc>w5^6zY{z|0*0 O00004q)c@UGV(CRdmR>*srIzkSkWf-Yq+7ZK$z2+iQb4*SB%~COkdQ9vZt3o> z{q6Vn&imIpcW3UMow?7QbDs10oOsT8ulZb=gpigH000t|r;6GD0Kz_k04N^zX6RO6 zjlJQSt0^l2nE$S5p=ct1vHzoD4jr)ckZdtg)CX18i0V|@Rb zZo(NNwWSr~Gn&JATA=)YAA92h?u?i&nqx3a`;JYM>q9vom3PcDWn*|qZ~G+X&+}FD zPS*aZ?(y!|t}Fa&m(FA~qTd2FQ$2MK=-l6R{hH{OAvs zs%x%9$@r>>UY%e1)uczrDsoV|4NQCWevAn?&6K&W`snW?RaZVb0+b@JYWpXh6MI8b z1&@o#qKIG2Uyn2$G^X;LU7l{V+%wvt*cb}U5t+L-*_-AQIP{9timhsx+L9D#s&lyw z#~hdG@}AV5ZY*hPKx*VSNSr5SkV4Wo(7lMPQElwR#Vx{k?+Fo^)>I=<=IAy<^aJv+(C`IrTv?e(TfA#2g%Zf zxUt|sKWVSd#`WJ9CQVlZnf^!SnZ7aoyr;k-|2L7lKHt)5VS&SLB(a-84+Q`-Gxvi= z@>NP0m8J7_9MvAYm?>*IW?i^QY}&nq@K|6~Xr=D8;4hLnRk8o^)<5sn^6o=0pM0%u z^Uf&8)wQaA**N(}I2sq)?_CJ{LH#Uh1+$nP&UlY{m&M1!xNsd(6ZyvWdbdeHa1zzf zd4uE9&nF^<^!A!VW2z`o#z@>Bo!SPBIR&YIcQB7@~UBwlP;4sM{vqld9AI5uc*&a?Q78 zJELLyV{pPzyqVwWucQGBCu>6$zsyT1W%O2NZzxPYYu@(q;}aaszIEfu^?Yjo!#m6= zM#??AtU`G`!-uWeN`7Ll`noAPTCWrrfYMd7D_ba1Py#&!Mx%=g^L<55~7!= zaCipbSL>kYhkg$ zLQu9ag*m8l+K4{g40iu0%_y;AZIhoDD0 z1R;(i7N7aDZ$0&hL_Pg*OFSp|sGkPVi~Kpv6B^y-uKjh8{-M^8R6vJSTSBw1j1>=Z zn>T^ksh5fWpm?$|oX-e2@cCSl-5h!SeHL?@3wu+Ka;OZ_E<`-6Kbo{>Gg+`T^>&1S zWgX5QN`&okq7{zSHaso5G4Dem34zj)#OKBSRbtHmEbOs=)A5>@-{KmP3!RTgbOAo_ zIwIqII5RUTHREO(x$>E{YKs@=L%Sqf)h|Z0m1fpRl*`8CS2Lel&g6xP)*E>s{dov> z^$ukOy{{~gM9&~!5kJulK^q7(2WKHg4KGKfnvSx2E*q((#6?CS=n{oW|A|RueEgnP zfO1)_!z#_GaPI?5nj9pUuS_ab3h14&$Vo$81frFEn?F^o$Iam9N&lL>FF z-sHoPM0Sp($iu_jZ*SX;t^RS#Hz;Ot%a>hcd)*$L)xP(AIy3)){5^VV)0OxnQ8$Uy z#U9`)b25EXTeNgOp5dj3JH;gvN5Fv^r*ouqV?L_Yw4<>6OVR-+45imAehUHQ*$Hs} zl-kUlJ-$oe4Y(qrURgWY_+E%r4chpi$TzdWcjBbe?UG((%vS|0mp2?fRjAe8miyI< zxDBmW92~5CpP4xB_1ih3^<>9`WSVBBhN!zcFu4B(YFnghRUQqcJN0Do0=m{)X3poX z7)%;4nbOB?-c52+;`Up~K%(f}&D?qXnsw(sXTpc<7yAT3P`R!Oe{1@V`Ik$tfe4Gm z#i1u27G}@D-0iqxMW_}_gs8AgHeKhtwGDn2D(g{(7ieIam5Nw&wSC15sz^yp8jm^o zhB}S0;c0f<8hWLTbpWm@^l3hr$;rhRDVr)&mq59{HCS>bj3%%3`CXEU{*#6wP!@c; z?Y882VDb(R)&;`ZUUjaj7v*na>E!ow`WsQZNgiUh_yE@JCML?wxfEIl7$vOCeJ2lMu?QwZ(k=Q1usTVCLI; zekZ2dkcZN8iG+sW_2ab)`|AhrV>K^wax9s|4W!A6+()oAoE34!((6xQ`d*7cYc?BH z%`{goAa>7A5{RIX3^zhNIW%%nf5w)UDQIxB#q1!>X6{Z(<8Fpp`tmQ4>%Nc26*j!# zc{1IUNfJYRGZ3iitqJc;y@8Qe4@3nEtj4?3yMvpvEqZO=)&Wx&jdUHsThx$8FBX!( z{fCAqSz_!~S$X74M%pN`wYcPwu_j38y@x@E=}i$^oNF={aZP8+6Y1x^6FCB383Dtf zlh5Jx`)P2xy4I*($mh9yws0+;`|&Gp8T^`$3`o@Mg<5NVG&Z>aEoPoD^Y+owBHm+& zB8zhK-o&Pos9@8M7>?sp@}y*X`HI+|LttFC$YVDXlT973eavkmkt><6hYuElYLQOY z^Ck^%?aWs&D`|9VpAWH$&-eVE|<5>@g`M%p!Y4=I+@k`(17cvch zkJo$`(kqt?=sFXN?r}xLlgZybG@ld`!0V_JLk0CO5~kX9|nM0s%!aHS_k<#9=&B~K5T#c-B~co17GFT*rBCM_}(JV zMnyn~egPR8E}u-$-56rC_Ht-TQP!L;0y${X*472!{9$`k*yQiuzHm`ta{E()XQg3R zOBfq0-pi)GYrbQmXQK1Vv0|~7cW({8g)?lOKPc3zwT}{x=KAjAeIx&L(}^&&`Q_mp zRiL5NQs`P2FUS;=vYv_dNm26PJYnl1_{25!E8v#NP9}} zzlnMN42vY34cxNJv0bcCOg*b^Fw=}>-b|tj_Zd;3%TahRS|2L;){kfQ>7cE&$o{;m# zHyGvrnBpFGpIC++%jPgaL;rfp-15f)URxLI_gOf(wH3{^l%DP@@@0tsSP?rMHj~^Y zJ8<;dHXMzX?w(}%cZ=_5eae6?^#%c-V4^M#R9akVT@c-Telv#}G6jg0vPA4OX+*`H z+a+0m1BU&-uYv#j+K|ygJ<7$l9Fr3&A03>;?*9ksTdfhK>nH=Ye?%nr?yBqFbh>`~ z0fS1$!3xn2ySh_cTUhMwVCMtv&?j$$U{gZilz&lVf%oK%tA%FBN&CxE>_?+j=Uit- zvkJL(tVQn)nPxZNEhRW#reYl*kvi5so+{q9lj-4onEAaUnRkCbiBV_DL(gE_GL?c; zh{ohwrQ#Vs{+y?CL(?)&h$@%`A8`_du<#U}3S&!b9%H##E_D**Ycl*PtAL#7fVb{V zk?TC0M8o(F&s{_VsvZ@1UHxAz@7N5~d7NQ>h=JWs+$m@O2xvn^3w6CI^Q2LyUp znXNK#t;joM(tcitSj&u%u?&MS1;$<|`W2&_yql2ZlYv-PkE0MjOkK zL{CK)j*!*rd(HAnvuf58w}*#+Qs9d)9UqqF`?Ssa^tnuOPGTz z1BrPd&}bu~*ztiNPO*_F)7;v3_Hv6}>2&?Y!W>Se;z5h;2j8aN%bg?~^?P^mz1L!X zxtMt|f161wSw@zl3}6#mxx!h)yVLBuBW^dolI?ib$QWYNcLf?MZgidy5FM6Uc)nj} z1bF?4`TSeudj93Yts%!6=GQSow4kOaxhycIgda{l{?~QUkdEAJ;=2!u znJ)B!*aj_8$$(lxJ>xTmS7Zoxdre zcUOWRnUp_x&WSjGzOVRWaPH$9+I8AT#>2$KBNIj2Xiwt0?k5?oWO_@l-J&lKKkYZ*4Kf0{fez0@m69|B;f~`9qpUtMZ|Hb`N)^LcqT0x11 z$9z>Lz@}ZeS>=&r*kZ+-lC{sR9`13h8=TbiRPc^!V*}^K3%kD-=D(^lGgmv+Q(c~c z6PP((p4BqTQA^HJf0!&c-Qn|_mOM~EBiEhiL$@UBE1D^kCt$^r5TjO2dvcweTlfyI z>R;UFv;rI)_k(17TkkabGS-zn+Oa6=7nnO^#jJko$-61GVnDH%lF6IYMmXveHHVus zXK!Ju>iIH6e}Dhr6RUEJ`zqXsOAvv?M|hZY9{{5leKg7ff^p4q2Q4JaBNPpE7lNw3urmtNb9|e*Tzh!~N7BNWF$QkW;*z(Ol1g{Ywsg zZ1zclongmsI4HoA%^;e;>A;+9ilk+Jd%OCd@+lm3mlqyWx{IiWna^7? zmY$fIJIL~vvHHuoz1ba6o{)S`+^*PN`C6s#(@yt-k71{40o+Jp3pg4^gG|yz#sWix zOm++cE2sCb@0k)saF8(H|8MVgKgUihC%wbz!0(0AalJBSb$4nL!z|JT9P^$Tp*y|W zvGtaI$lPK6c_Yiqu3JfqStQG&weC>n(y45db*nKK*~b3uPYP-gu_vW7FM`;JZIT96 zMnP)a>u*~_N>|PU|2#r0{kCkgvU6{w*=)*}VRrSSS`e9zm>u6ye77}OH#IzcB)77C!SDHxmSm2QtJ#Xne%43IYPxlmwr$f^%1Fm}mvejtD??!IPVAwa z_a6*0lZd^lj+Z{c&&L7AO7-Y>uFD89_`5@;t8Ind?>oJ%t-KZj=X!!`<{_s3vlVmZ zUAn<$U}29M?cW>A9{+C?b~ZhWitOTeWKEI{v!)jlj>L1ftc@Ovx?jmD+w~d>l9%qR zu(y7i4C|rPjP6ArPBh+4V6Gh35B?Zc#0L~%6Rf&rWz>GjrTm3)KTSjucT+pmzg;rJ zca963aEt$!3!zxiJi(Z%Tujk@3uRM_1%yZ;@CR|A4bS?BdZRWiYFA3;4|T8#;FGmD zo{>S?F(A3JC>Mc*lCQWgniWjWb2H`N#iqjx3FK4NX>QL~qq=1LPv&z@p9w{Uq)6YC+ z7-C_nBYMNM26l0{r0v})e@JxHd}+-Z(T0s^zuFML>=483#Rvan^B6P5Hbsao9gm#3 zc0L5^AMy8u0@u|fbg3o}I=l}S?~Mx^z*zL0#lM(RbPj#eOK_^&mnt(5EvDGj!WuU2 zoZmpW>cfb#FwjD01fXQvwn8LI)0$CB?vP0zKg^(6dv_1|)O%#UzJi{*F9E5N;A=b_1*fDxRZQpFuac@x<9H zuk-i)oSQF;O-G$>g-yb;1Mi*f!SMs9PEb_$?BB9T9*ly`VPBm>wzeMYJ@2zPy>Jy2 zDY6wxjab$npZb{B!-X@N>QAM^xjeNlG4bx@!W^m7k7&=vM`1ga2DYQZ<&BjQQB^M# z=SYK);RvXXFlt<;^V^0MWzRyX4n=f{T!D~8S2#!&_YGmqokj=N*68n#)AnBzX19h* zATxSV+eA#4*Uro2#ueB+v+9br}ZZM4e=|)W?hFj+iRMA_=Cmg-znMr zZo8M0GEG{xmRO4H?IXUz=Qyg>n^d|PN?P#Km@|oD2SPbkz?4V-9#w&E>H3AerSF_C z=NNzL@3l4`K(8JLn@a9VNEfc0R|2vHiA0@GO_m#X6*$%vlCxF{uH~WhuZxzuFNGrX z8%mxgNZqT5kZ?V}-n*{azfxZ#*&fQr(ztW56Q)8Y$=dAZnUbKqy4da0seQz3xi2`5 z)xU!V`m{h><{S(It9hpdhOqa$m$C5#2%Pru24}@Q#f?(hndgJybyH-&m3us$~ zELWTAFN(RN_qPkDqsa@koQYVpRR4EY@vkCzAaElu_kncb+q+0U^{BJuD+ zIe3*FRX7<_`+Cz&xy^cpy}2UR?IQ&>L_Js!qoO{% zo3~lGDjI*YP{5hyBaQxO@K6ue>ICK@Wc~sJ#gN>_)Vx%bQ{qXLK_EPq(U%fjo6vJ zeM1m6jpueL*<#4E8Hr4ZL2fn*zo%KdSi=XhhW3IevYX=FtJTbLbgz!|H4V@Lv=O%a zwjYwi8xKBdWR5+n2V+ZGHgdl(qfE0c5x0-v%PE&pQ3C`byUB_bh}}u+lIXEe3R@vN zH0+{-HQd$j&23QHH|sTy4;5O@+4HunnPjm<^2i_WC59cC<3vnLpR7WW9l9pBJcwPt zc>lUx;qfnDG#3h3Ot=tTj@t7k*KF}hijQ~jt@3L==8e-aoKU-Stek8Qf?`LZ^8K*` zu3RO*95ysb0hU>hQwC?zt1g*<75nwmHDflc-3jE@2dtf6H@js*DqKsB>fe+}u;K9u z&BxS?v`;UwY>f#)S5L*O%fptR)<-)(qV9*CV40F%%$J(D%&M;8+TDf#kqS!u1brFA}JlLu^N-D0puY~_$vG4sm zjsq-6JXivYcw|Zk716SWza93|WiF<|n=Xbd)-QxjJV(`Vr4>FD+(@%bTfj4?st#^f z!KEn-O8$-vYRAe$PE%F;C7#qKz8lHzK0GO#wbt+Yo5djOpEqgbK%nlXBIlBMY>xeK zbjuMy0?h8zX^C-<&1n^ro{?Lthf0ohitYg4d3l!#!%RPjun z3}p>QnM(#(KD#71sl&C`+ezE$lzn8QPSlJx{Zz5oh)ouCbg*QN-n*%lxdVZ3IU>9x z$vT?FIvZ=5xtL_aqN)<&Z7_HESj2Am&8%l7OC$UC1SBnZIG&mg`a}Ay=n{L~G`Lr48;@fp2wPNPyF96ro(o)0;MnuQB+ zwSRb34Rr{x3lnp|9Tfv-=4+Wl0(HpbpbVFj?cR49gT-8a+StP9Hx=H?sThYCl^d!E zD#MNm>*gImnWu<3dYIsi29t5yGQLLIX@$^;Ku5Fa&wEp5?@G?x69W34o{xMYuwPe0 z$6i-S7zxfWV7bQSMS_0uUEizF??N~t#hNeoT`>loUvDUsjlP$0F>iHnoSrM#Tx{er z6M*zWMcyF>a1=wLl<@Nm(dHR+X~4=>E1IGw6h>rELM7yKVYIj<#q0C^c7qzTqh{lO z%~ zI=ZJv&F@b87OM>F|^5?AW-Ws4;_?pbub3$E4B3 z?*n|y1S{rZ=-a>r$Pk1*CVCzmtx-AuEK7oTnX`MrL9pCq(RF7=IKI||p2%-C@Y_(- zl7M)I-an%^`^9G~1(;bsvYIilC_cFKVMFzCGNB>TqE*1E5eQlS_yVil!dX!ud)Kqr zyWswdJ!NH_2Lw4%N$s6Ikr5KylE;&*`r-ldrTQK^!MEjTGba)}SFVG}PgGSQub5Pm zpi8swWWuhGJ7i4%;HSCY4NdG|{;OmVZ%rwG%aT~QV&?@WWdbAODs%|8)ztJw{eWL{ zHAFfV34Q)deoGvT-gbAD(K!s3>J29fMB>|X6aMrz^t0`9E$RW>jScOx)l+II{bU3Ldu_yt(Tu z?6lul5FbNy>5OJfzv6>e%_HV?sH$Y>p@_Bz0cY2k5gXJ7SAL6?niPd8Mcwhb%o(fV zNvDss@*K1?!;kXsG+qSMYtDxxjB_fw33WX#M=x1dxjCs+VxzaazVcuX=C|+P^1$U8 z?uhlf2ad`RuWyIC;jpaLg*`!SIw<{iRq!n+q^hS`bD>M z97+pa@O!y+2iTdB3bpsru-fx| z7eIx%`umqUmyacHT%up9+N8xOXFyW(f(67;FLY9`DTvrM!R0d@VpC&z_AH{d6JxvPgQg-2}k$qOdZoB{~B z)uS#H&Obd^_K3q$mjzk&%%|vO#NxX>5($xnFDAF+ph}6m$1*JP&AANEd+^5J)D!pM zy=)@T^xps6ZPa1YVTk6vO)pR6d;hk0oS6O|@DvK&cp|K-9k*ZLUN{#u=+JG+ED>=_ zs81oA!tgl1Oq_5vH{__#SK@OYOT4yZ*oN@oNh_JfwUF(2)*?vsy`36 ztl^aHe%Z(1&%nz*vg|bbX!qx8o(p2@aq+qGFILD&kLw!2pOPyT6|r@!!phh6nG&B9 znj5#AssQ%y_{Bv;%$BNhTri?H177A~wf1K$nFFs+O{hUb*TtI5HX$~<^m5Xph@6;5 zzhwL!81Q3WlBX`yxaS%XUa0NQV0uvfeg!XqQ$Tje(fRtqCHl~r|7)^Sai|UB?fp7O zI{u7wc+0WVY;x_sK~*5{&2~=2(xm8SuBDCXEJ1^-HGob!U8^59hCs6N8j%lcD%&Qe zgB;>Ayo#xsSMZ;*jfgibLtNmq+;diNJ!s5(L02VsY5c9nYsWGo6xTjnoLv^$=1P1V zX6`uCSuvq}jU{JCyG`2n}2Zbnu1xd}7QhfA2q4t%&?6lOhktNU)hK3(J_qnD-Z zWb0?d;rF1%AqKIA7>?r;L!uFjP=#o}E^<6#Iaw&=4NI3l_(0Z)2ojAXh9RSo&{!hy ziabvzk*FJUTvMxTC{zs0IC;QK2E!w+MYB>PSLDO|x<2V-YVKwdiON6!VbY~rFWx9F z(x3Nb$hL~=DJU|8;M$v$G<^ftG{I|F8b%d1ivC7aD9)pTRO)hV`Hz8$hfdrG9}rM5 z;8|eVSUfAHvjVo5;+o;ecHxc>R~Y!7EInB^c;^ zR^&Vk0-hAEFVqkv+?lU6l1T?pcHW;*my}u5-olT3q?ABR6Gk8SVHJP22($ON0V}X5 z^XO)Sy5q>qU`aoVGlp{szHocqbi36g{gbA$YV!jg`eoy)^U<#Pwh174 zJ;+LyUER3l$h5(^^l)INnBGIWYKB}I!%LPkwyCPo^eUEh0Vg+4=q0J_Pt}2^hSs`P z{Ry=!B5a+_UiBxf0?68%T2tski}=P1;`h>dh=HJj0pH2QhrEn~;V z)Uk%lC-Z=QxKo3fGTSkvS%gt6MDH6>N7wXOpl)7p?5pefSB31mo{p_*P3RNQ(_@70 zqlUE2g&!u*lgU8qQ2A1IT(Q#Xk+;5Z4uN1-mnEpasro163gX^|l98Z+<0u`2V2UI5 z=P>%(wpwCCiQk<^vk@X?4ae!>MNBMqYH=x~&>N@ljW9o}Qk+uci;R(77lq=e+baf? zfSkpCp7Ft$J8?;+?jlIU9-o@<(v820jq(h^1_vBJApildhWUOqb39R?D_+<6qk9py z9BV_F3so4R&|{Hp9_~681}5NO*vs#V3TL(+8#3xq54Ve#*4z2$)MJu!dPVDM@bZri z^L`E#od$ls>)>0Px^kHYeQmB7$%91off;_Vq3<2?BV=WVFl0`%Yy2wVk(QSF`jdV<`?+ z_TDjU71+ttC-?hpXav1bA+fT%u(U-E#9%q@xc{UGC=zDvb#(apjtJtsthx`Ew22+{rEF!bZ zn#kI<3EJHV+RZ_RV%(=&E!Y;F=k-yZzk;oQMtL@X6diFWbDH2^`8DG^MXZCE%q!8R z?wGd)TBwO?6y@s#2FVWgE?g#}#!4qlPFEn9XwfsZci47G z)Vn{@ltgA7jUlK;4Bz+f6M4C0wPsG-mqN$u;8iml@yPtdvzAvx&6Ki}R<+fivG7E| z;1L8iQs`%RMdLI2#wBJA<1Y8y`>G6VYG8k}#*750Y;`Dd20-zfxxW&Z=ckHQj0IM^JIfD_f;DBVApMa&Yv@ z$>ZoW3UHxk)fs8j(Sj;od2zv9!fZ^0SSXUrw|7P@q&?q}gx@dHBl=+>BUtwV2(<(M!okb7H+&MQ=>-D)}`#(t=UJY0wSO>QvUQYlKy@CP1XBC z$DA=J_~d~>^XU>(Gy+}mQEbBgh#RpXXsQ!2l0+b^4ZyG+Jxe`!dd1H?>ZQ@OzaNKq zz@j6g&E4$iEtYc@K_Uin#jJxniW`dHISvLMSn7?7)GeUd=js9$|4XZw^WnLLLcSvV z?;E8fPKp+!ROR|YjCfLlUqyMebe9)^!ZaKv&sOnABnTHVQRJmTV%i_uzP@3t-)ZjfRR_^_lR$&H4d; zOIv=357QMd$03kyT(o#o61G|uX0{~Q?a3IDl$!cy_KYTE>j{I8iem#+_I$KPy>7DD z=j~0eMkL+}I92;I8DQs_^~$Jy+F-@ePMv0j&VfsSIlUr~4 z4nb`d%}c5zoMUxA=0H8rMJP1HjI`c!zj?GTzH-$p*|dQsXFM`%0_3A2;)d?a^dU|i z&8dF#z6k!I(B0M(DCckaC#_BG1O6^RpQzF8a*0>r7^ zu7-o;yI5iEJvg6@TS9|1%-zm~+)5xI7@s=1&gy%~-}=fXd~HwjOnIVpg-xYbYFy9Z zjaM74n@}9NreayB&*f47!35|xr@OYJYn{0udf%_pWY=IpKVWCmS*khiM_h!q*igN za>l$G`f}<$kaX1{Yg=W(H40a?_&HhOOZ_45w@#scRIjP( z*QqvkiN@z%<5Y1T$$aVNv!$h9>wa6xYVnG);A(5fK^Xn4r$V?^1*&E1{C_ho2R|8G zsHIj^43hSn;Jv$?cNcxgOHjumh)!&{j+51f6cGLVEsZiM(-X>eAOFU6oXS@|=!^G$Q7pU~$JwQNM&{|SQ0+xpHc z2~{j*ccNPcU6n^f%msW$Do!Pv^^{{YIW$srj!SPq^^5QX5Zy z+N*sO=srUW{61Kd>RKNug(-z#iy{IAblMLYK5eSWQs&G#Wa-j;)&jWO|I&0fRaxURuW#^%iqi}_SVSU6O`31q z=0Z5Rhx7qym&IPp@sC0-zbQ!bsm#swL@9&J8Qm-6W4iNxGjk8b*mD4Dza2W^8c*vp zISWgF-g)58%Z6S?u1pZf^jYISj%hM(@6>G?^w!Aa!E;-=c)ayDdKB7~0y`^_)!rhD zA^w-2>LgpCdjkJGMI~B2EH84FJiS+c*~!D9THT6|*Lw2?2`rXDPlP9%aOBxPR17hz zt4Fg8;B1}Z30hDNQ?S3KP3XKKN>bM>zmK)lemGg$nZ!`uN z1`t8Q%%z(T6T*JhzdG{RuAVWuUJb@vH?nI8?^hEVok^o>>dut4IvsL{?8y!5q`E{ z9;)YT?6N_9v$K>=0sW^O_vMq-1}vomeo|NjROJjC%?0Irvkv%$(thpY=sI!si&nmc z8ev%rW1FxC<@G4+-?HNDQ5|dolrpEk#=rz%u{}Q_FegNTHrCWR) zQjkdaOM*O_-b8_(C}%#dA7NQNj%p^5@ai{RXq4hl(7&b-ZB-KJ7#b7C3F++mwtV8A zJYh%F*y9|?T&u)Pf|vX7A;IaVg_0jxxxbx)PhN6W$s4}?YfMQf=Ibm*Goo}iM)c9J z^MSy+`R8Jo#qK0?4zn#D+rEOW7ptU)W>fdn1Q;+K=tHR14nF2ZBKXEOBgp)He;yBK z_0j_3S_k2Ul6t_){iJeS9*UELkz@Um@hG2ubz0tJ4q&}HEA08KD>bHOe{ZCZ_uB2} znv7|PCwof;g*_0GaMlNoFnq&Ko5!!c4$+)fRY2U9hO0H%MQRy2BKC78;);*tOsa})76~56e zZ?x1ECKbymh94wBxUjooHZUgfw?ZgbSR8z8pH2yJK2vD~bN!6@v__yZU?}@EIB(93#)n79#n z1iewwzc#zMDf2hwyfM4Wx1OovmJtZV{PpKw{*tSP^Qu@W0HUdDokB;TbU^^pKa$AM z2{!RxL?A+5T)wr9{;uow0<~ugN#lcg<^Ad*6wH<>5cmH|D^93iOt^eXg03HXey&l z>?4otKS0>jt~lz=MDOnax2@Z$6{^={vi!qCp+ar)_PBioS1E^8dRm3Vru*|!(3*^! zI&-9p!;3~b4K>H(BEDM{FgTB9&X0cG4J(Xjke79pNxgkfK><(Iw9zH4^HH*d>G&bFOD|XCWl@vm zoO)g58Q~HW0(y){B-?X8jPiS#zpRx zl_=(OzjWr5t2P2{&G?s#1-qPs>rucqUvx=jEK^Fy#zm5)hXw7wc*LTeR7rw&4sO>^ zHyJ%v{W;rzgYw)$G7)GuzUJyfX^r4=hPM5=v+5X|moc?%n?E4v&BWUtVZ`0MrEgwS z#8mg6K7U4^C3v0I_axnCJVvDke2I@P9a}uMNMv$#QJu`T52EN+l)bzYU@fp&YW~rh_HUXqH0UcR+$XPK(a*HnySU)gG{u@;xhd z`H=+BTx!TQ@Bc;T?CY=@A=&{STe_TBFTGYj(TV35k!R~s_70l1WptZNM zq(I1ad9mVk=EUom^UWwK>`#XajM{6-}}TMVU3iO+?d&j9NZ)1a`G5aPg%XS zVZVHU4uZayL=h3rSKPC^%a;Xucc#@{X997cHP_9m)(wsURiS~0kBEtNh4De|WQz-A zxaPYrjLx{3|4^JY{{Qxk)9Zrce~~VyJF#;yUAqTrUh~%#_8RUf#{3?yK+Vzhdki@5 zb-91txq$>IriL;9zO9WN=|0+BmAJ`Wj;4RDZx587OcLYNLO*jyG+o~Y{~%CPoxFQtiY812-Gky1L7MVJvHW~LHxz`*;n z5InRXw0UZ6PlJ?*@s*Q>iyPjhJTop>IK)oq@xTM~khKolf)V}+V*Nk7O*1(!wjJq> zz8AX(?mlV0FeSI3`et2l%Dw_Ph=ng&;R22!GN3@WAN~VphkbWdK12ibul4RNh}YC4 z>pypteh&C;Iwz%>rs;~4KC|ZM|E`BUC1cgt73p(U2VUu5F**ko6&Ti)z;+rjlg7^D^7XX);tl!3J|aXky?O_ zoy5Q)D_?m(SqYkcc7xL?;rLfv0vm-R;rpA?{m@uox^yc%_&l__b9Y+Rp z>hf{x`^ct@+@C+=u=92as>Ka=A^HH(7)Sd`car~?;Qid^a@bMZPgmPlz;g)HFKj@V z;D>DUi*qAKL@g80=iHVw-`F~Nh4(IQV*kQI5azM`9Qh1_?!tCRhQbfAZ@(-O!16v#leT|gq}N1*d> zDah;SdNTyi-NTPKy4=zU?)^{oeT~I%PYGo#t|g=ay6eQ{DsdwRL4*66f+KGh z2MzG~c4qlF6Db8y!=C1*Rg5-8w~z2_*fExTcKq+VAUGGqzWj&-`0U#os~~if(Yb~= z?pSny1Ss7plJ?Zh_FbFKVX{Hx6PMD|#Y>2I)mxlsYskAR^m2GCibEB{PtW2?aMas0Iac_jr1r$<-eM@W(<$TFX(p`> z;=-=A8-rGoQrFKjDWO0P38H(J24UJLVJEXFfvb}*8}v*h#i%?`i}L6p=IYfK8jCXK zEziNi5*D))n+uTT>yoMViq*Q^<-xP}0WXM!%7ox$g93>IHpQ7pB>K&~T7S)3w!ZWG(-;!`9O*!LP`X+nUM zsMcU5t|=RStnzD~ve8514XDfV?5{2gLR)yJ@ z7Zmf<-yK z|JbbJMbl)WpEH{48#xzvC+Oae;k+PD8(2k32<-7yTmMK<+6h$c8CvCk`<1%{xA}zi zV!0+O78lR+wJ@ya^Q=VxpaL8GJU&CF*AtfmN;CX_G+kv|RPWb4GYl|vrw9WA14s!< z#}G=Vh;&GafON+Up@2V-?o>cTQjl&?>6Y&9jsXUkhyRP`1DxyHXPk>ZoCh}rS&AzkdM@_I9`$TO5iO(sdHfG0zjfzZQ3-w92l-GDrl*0dS{)*B1Y_D8 z=ewuw{2ZYt7h_Rk!onY6OU!vs;t|!l-hF~2nlp*XjXac65Ix2bxb z(H`D<>YV@irE{00E@)N$beOcWq>!FgC3LgJJ}Xt>f*8WVQKC}L zpy4Nv?dpG#q!j`f+G^hEleOO|iE^)Z?!&=A&@E9i`ua zX(Z<9?+2o{MR|AcAEYcL$YIPbH)MleZ74P=zU{*SU@gMaLc^~Wk>=MVL-(}WM_v04 zZPgZNdcx5VcLlFrt+#Hg&&c0$a+1@BJz*8u;nOPhDrimWZrj>|ss8!;Od!+U(CK&0 ziAG1jQ4_`^J%@NRM_@z?4^QQlnWk4OOQr}5B=)9soA(!hgkS=z{|u+l&`c>3Aqwpi zd^4@C^R7L7Tyb>%KK|E%vh-s&)n^7G#sr*&<~Zbx83EhYRi{qiK)q%t%C7L1Io({2 zx^ddnOTzw{0UV@aZ?nOVxLcbL;aC!&e^=Xh&o<7Pj=~{)aZN>G`wh<28iM=L3-Yv) z%?A4*cXTuRGP9rJd;1^Zhf*}MIK-61*%uV~<9?pSy!XkUF{n&YmWU7G4cn}hF=vHp z$e`qZW=fRv^5xEM!^q*tS-LoE<9)+hnP|ob1!m!R2*Ww{dq86GCcLO%JB8569zbNY zf*F#ctIQ4vE_Yhvy!!nL5-6bnOwWtusbJmCPaMimBgLxM#>%ewEqQVfP}kH}EQGWT zfIjK)d!+fqU3IZ#4#h)h7Cql8!Y^fAM{O+9`hz%Y&+ZR;>08n1PyCB=IuO=Ym zWC+yV>m$gsKDruJZwVsFlL+*v?&#Gve{!Lu%HgqVHyyp3-M`$L~oi2r$HiCOa*I zWV406n#i0;kW8&s0a^QC{t)P&v1sR|eQ~qt{%(|jAs|T!f;{VEVE|Too@_NgF zKHSosQx>9|7GjmBpqcToYT|QNiAYXlDcRB)6S5(v?`FZLU)oEA+{a0!C>;dmZEq@D z{4JQJ#M6TzITfegAOb1Fp-o)2sZKh8z;MMoVV^pDg@ z3r|MYVxQbz_9!)r{jvc87Tz4j;6XtXm14^QeS9^7bJ>AQzpvjE+}4z>>dX6}9eb~x zy)q+}Q58|v)g=ET1If$SaWq-FxJ*+rl#%Z2Q^~q;{DYmQlzV`MY zXSSU{2?|!cY3uw%>LIVo0vX8TV`)JZ9C=!D;NTOYZFRf@HN zW5?cLm*-vmPD|g`#9TLCNWT6X_QKA(zaxcc4du6FXCfouN@POU+;t0Fa0Z*Pv$}XG z#>@J(iEK`B>jk)8x8|>p1bbLp?+dDEFVR>07P*;9GKnP|)^`3fPyEjfS_K}CtA#Q? ztF?hg?uy2wfZiR}u`n7bK^roBp*O1^&0OPjCoIWAzi&<*kqvsg@rn8`T& zv6B&CG0DmP64^5=O9JVhDm#q9gVRBn{<)!6sf~b9#^5J7OTtd^U8YCFn@KIL@WNc} z7ms8=uKeR)BeXXkv1J#6wI@ArWx5}B9_)WH7c}PHj0>SQ3_p?7#6Sjxox}l{;v~l` z9=>YYF*fOC5tcxRY~mRaOvoJ?|1j^ZJK)mJ!GfoM>&foS8<271(S%KIcZvTalVN>b zj{s_0$-t;{g1X7o=z{WNcB3|1Tf~M)?QBnfaFj-dC!D-P7ik;*?^NT|(T`;lv*IUw zn%dd(M{Yx-`+vDlCNbH7F1SWTEiGxM<3+<(y!UAs561W>`)G1=SK(CWhb)%zXi zVmt!q`48u3$uikNID&g3c3PNur~U?gcc(Dkq5vL=j6h8*Z9v<;z2_JgPeV`j9d1o{ zchc&`DJbA>F6Up`$ix_+oPV#9)8YsiNa6x-2?;e{f_pU?Kf0COo>Q4W!>BLpC496Y;@sl z(L_OvBtYtsYL)W~mblv+hk^SA60z!43-g6WFK??%ovxKP!^t=RN>>J@`r?KK_)-1x zEwB@L9rZYtazphR(@gCswyYq&u>oU+#S(QAf8_Wb;YX8!myDZSc-(0z zU%GHT?KeFtV~kVrUtnb$tbhVw9&3bI@Mq~B)Dj<6AS|^z>jNVN7-lQC9TkG%d1FFg zAxRrdFfSgo#JYqu4ET6m`7jvev*77{*t%uYSxXIg82T$!^3maSZ;qBlog39yajIO` zHw?Xo1{?hTrUT_W)QZ`j?_r~S*r?+;Zm*;t-!(iXwWcJaod5VLza2nLlHTv7fL$&= z5D{m*1-*0eDg8X-{D^=-%Gnc%-MpecFWg@0dq1R2@Nei{a}xR4$s|Jj(EC@GYLXjA z!GPJ@8C?L8sR5t8R=p}I`z~6!xQ%xiBlx*AOQ6-E3gMl+X!Y8d+>g3%&W#k#AWOje zIrga$%C_H{UQox4SLdd4{(!b)j+!|0LL(5Uh@W%7iM7`1`}aO4-w>g~td)VkX)szy zkAC{QrF+ixbiM#_RQoSID{293(<~$y%mW5+8khgsc?a36JqLxgS-UkP5>6ZG-BRzg zngREF0me^ChPD`?iJatJCj(ie2!k|)?Nq+kk_2^hbmnj>fuJ~O&_$-cKR!K;F{1nU zH5Zu-0Ydpj%!eQ5xy?7(WSv(ey8|h9do0=uGUA_Go-4j%`GP(|$NAEAq)OfWJ$q(z zAxSjT|0U%h#ddGty4MSHma)(ORT;(*%}*!K&ISy}%K_3POT^qspgIO1v-fT?fBf8F ziU4N8p#SmmX7Sqc@8@2Yk8_Gt#29X5@DZTE_3VNEXs@4@$Zm{vy!N+ivc>_mt$SS@ zxZz|>7|G!!glV%)ObG{ETfX@EUHo7D$}U$&4(T^g*0tiV z4{cMT4veGaV~;^?f(38X#fq;e#qSonrfo9=yG1}z(ucorWS~yf8Rd& zQhKF?&E#ChJ9aN!xr1;rxD+F69KUJ4@S$wZYY3osD*D5e|vE|U;0BR25Ft$U()ajJ&&mga{e~dzi(X2Q_NFr z7Pxpd*i%sQl_(U(fgNKh45as94=ucx$N;q*) z9@#*cZg=xSlXklnB)zb3cHgFn`&S{UP;P%a{w8_9z!4Ph0{*_ZUU2!(71L^l8`FQM z5-XpyufL1XS*U%h1#PEhuilJRJfiYb@?HP3eunp8bY19!x$TtexxB2%BY9~7GDNHV zT-oX1J=vY76|yw&l`*^8lm{1v3PuO+qN~V^n8;YNcV+W+xhsUjwPV-b_>@9_^}puU zPDsH4I%3^{63$V@Ha_D0_p(`+eZO~ovjQ($VXGta9+sLH#xYL+5F&rIion*V_*Xoz zSBBW~;5=lueS%pW$?=gwy?q3F6RbaAgCNzu6Sg~1nD%sJQyR}Myte?5=fFsj%$aNj zi@$>i)#BZJfg&jAXCa-$#A{Fln5*CwitNaZd-$@#7XUpE(|)U^F-({@f8dKa zFMH&|u5Dnxve)1Z+FNgLnK?ayLD+NqO~P)LC=5az3SzUmP`CZ`+`j1EY^aXpdj`XAp8W5b&D#e4hD}sh2ECZ$XUFSt2U9BRplj}I^Dew z274Y22#G^mH-O8pv!pS=e4x-;Qi|)$`9-Bax4 zZ`zy*ne-2o`;s=5P!fmaYrVwp;cE03uXO@n;)l+|KPB{gnSL|kFX+9(5Ehtmp`P$Q z3#-&4beFdE2)n_T8gfn+KNTQ=(s+c#1Ut8v=O&_b3FXL9{=__l2-v?LG-qFb%Sc`1 zK~({2pb_IVW#)Cgha!iz?Vl=5%mrYEpSA|>3D+O%Cs0{@i2ZabeR%Ns&lU?wz3={A zo{?9%&g{x*fzNf@^LUwz;nOEsuWJNBTl=&eJZNj0Lnpb<5Bd#p@g!=q3Qd170Vut{ zhY&5*Jn!{JW# zAQEkylAafnG$8Tb@wn34ic30C)b`SZ!k$^Gr|WVexBg@82735 z_roO%7UY9iC9n(ybdua6<5H@^zcny~eBt7(qYmwNv_O{FGk-`=ARRLm9vQDkgm}S6 z%`)@);daMe@g_d>t3_6ug@OP7EXf@s(+pfoBnx6Wi??Zge;FSCH}vtNt2BDCPUne8 z;QJ)co`7R92r0}^;&cG!j@{7`*W(^8GzZd;Z*UabrUQJ~+nR5~o_jXf<#^vky+)@D z@x+*OofEAEsRk9+JaOsqyIv7Gkg}%z$U%VLM@Mram)#PfOYTFr(HlovWk%k{MXUGf zu7pKAnZ0v!)9O@d#~#Jg=I3pbj_;`k_tl>{`!@Pk1#+5+{Z#7X= zW`P^j>X(el{9Vzy-(IB)_xz|HB?pUF8qf&(_CIgn?b%FzSnes#8Ng9i5y6yqhk-_8 zgF*EqWD&Vb*Eq`0Q{U=4HIZ$(LPU547c-3gBfd`kKzfaG$Z?nFWIcd2lh(6a2wy9Q zgIfrJrp4zXzi(rxaEW6PpoP=>CXq-^Scz2udJ z@+ou2F=GUGS5q+c$At^J!oop8D_Y#^O?99qYoXZfDKkbFeDHUD)uN-ekD!mdl-$Q# zJ%nd{+*yQB908GEJV*65c)@+k$Wo8gc60$(@7$1d|8HDjL<`d%1jC?*fR;$!)Lj~x?R3y2_YtoV4ur%b4G2T(t_te*Z!<;MNAv{ zWJ_ZRK~<$zkPxUj`j{)#1XL#_NlQxq?I8wRqz{AYujL&nS`XJ>5oY;7E7Etc^ zcRA=X2MGUFTY{a#)&<*Ejh}&lUdIuqIueqj1r8cBX(3#SE?U7cyjy*`O#^O3?976F zyH1+&WFy`kWlo>Qo}Lx0ueeX2wg;bdUoPSV&0Oss!a`@hD}!A;F0MJA>FClcgjE z{`Np#L*QGR!LKT^t>abfo2$bdSaUFxE1b=k90p4ks)|9nJe!$2x3f-(2E3{spQPWM z{KY4(I6&i}KW9`^4+6C<^7DX~;nf zSgmKa*EH1PQRCMX;e%E^LUnmt538lw?Z$|30-^`I@c7m&LYL!KR;Y@;c)$8HfYp&G zSc~TqwK8b&Zb6!y{(Cw^HMV=O{k3&R%i!a;ZlVh)WY(cy*s|qwYzezJAO=diKUhM zYgwvn^>RpF5rExTd$zq!(II#xYbGCq<`zpvx7h_h&0G*7Xp^cu0;K8r1xdBE?h4bf zLX}|(34YJ=q(~^dFe7a;0U*#@yRA|&t46(h0o`|ArnMG3<$j(wr25m=uwuZT8l`^n z3Wceo1#zE~ulCrIB)M%p1MxIQOFq1pYFI=!Q52){!k<>T(pw;N?rRCPt%uL?LYY13 z<)~A%SOf&_yj6cwff%0aK(@x>vGz3D<;l&bjpy$_?wrYd`#pBk=q}^c14Zdq?l#~O z=6Vr>hvx&h3&6V4AR;}4*?*{~im}Qc{-h;uzv_-e9O!&k2*$+dAKi^T(}HZZSFcmN zLdizJodYL6{yj_yhMTJ=g=^_RY9{IdggFD}CJ9QCKpeG9^J8*YC)S3s<%{ zc1nWBp<%J?d+Vl6=0*35=8isJLbe?c{2rI{vNvV+@{qdDAO@u&hqq4Z<+3~k`RHT7 z07mK9u71Uv8%XXJ=r)Tz?bZFpyx{vVcgX1O!RQ-qfm{U?@rcLu&!Bx1)s-n#`4J*q z!OEgc@}=B_9ECJ4AIq({&2Ak~CQ*e3YHzt7iDKj8i1Q(kkxW(I=I;Fl-~FDp+!4gp z#{RRpF~&I%T~-zr2L?fDp7?PI*61*kwBeZkZKP#}@4gz-l@_=gLbv&%WK6FFnr?Bo zi{|_FlyM%h`Yt5~{Un@ZEnf-KZ{FkP@6yKHJ^0Zac)7sCnnS%fRaL9@9Dr3PnpWSB za-t+gq?kcKC=ak^1QBcRI+ldrnrOC>yDzGTmQ?N$3er0 zO2L2kCZDb~=ze`xefM3c3|7!f?Z4E%zsZfoWdJf7T;SZ4*H%hz(q8DbDin}!pAwyz zSAMN}DxWWX?NPj;x$!FCu!4v*2JaO_*-P+9J?Fm221DQY(Mf3){tQZleZsR^FUl0f zi+Pv#1I3;J(R&AD{oaXVV+IAAWH~dijl}#|d`jQ-BYbcyluvD=j+O|CCXp18AST5zhs`mv7{YV`bhNLZs;#|;dUqi8$dWY z!wr*iF7sDm+n;vYKYKE7Kp+ao5g_VhN*3(*{ouWPAq7#t5-2)f8!>C#eV^W#pq{?S zu=^q__}*jyh`kK*8ecH{0AhCWfU{iOl^F^qktYg?QoCqG&Wwrf()4T9Iuha3G9IBH z97?s#qCMVrxBb99?#>GR$hjprS1f@b>vbR*j$=X_93v09%5%6=4h|KWFEzL)Zo@!` zMp4dsn}zY-hcp=F|KA1hl`FD|jl;muWq6jHCZK7<#Ns6KQrmGZ67O)b8R>3C@uYQg z7!>r|Td6L|?p;a@fU2#0T!Wse$F(ce6qy}G5dy3te4xlg90Iuql-BYfT}q)iBe~T5 zgi=P^!t@DDV}7p8MoQ~7UrEWX3>ze?>Bk2J$`IT(_BV18(5*yu{Lv-m=h=3>v#aaM z9*cQ}GQJBei4DwhWrfzg#t+C2YZ`w)WJv&NF#E@vPyCxt!J<&GL81x^I31SC@B;-j z_(Rc0+?K3>52(`Sn&!jlH5MqW;Jy;A;{^LjHokX23)b#+IPNNLzFETL0dmSD)T;T4 zyahdEmkA%0*$G&m{Lbwz3Sl2%7ZmPcUy7C#%nG~Ki9%v_&Hrp9GXpAsd^{J+2#+>o zEAjQY-oUU`yG$*!p|zC1<;@|^{7c<#CV_C?FmLAn{Hx^b)}-4QX)O->ZEzl7I(-7^ zD5sbZUvBVE|2w`hD+bQUOf}7?<~+t?Loql|kmCVU!-IoGCNuKm|Hv&IiSTF;jSto@ z@KdwiF*^h_I@f1~(sxg5!*m+TQIQxbj(vLYdx{9E+SbH$2(2AlV-uF7OEz!D(t46% z-dUz22FYckOcc68fwXq`jL*&97LsE#Jb8$&)o<%8bS`qnMbkhU@)2^*`+qJ?2$n(p z2b;yXF#)<^Z(jOZYaJAGSVH0Cd*?kx;FsHi7 zDv%^$piF(M=i(VO(=VUM8Qgp;60|NGK!Wcyx%h>L4Ps^$K{mmr=4)1d^Zgj9Us>GW zaGy8-y}zp;_{obhoK8@v6Tsf;#IOsQF-QryRiq6LEl8#Z zrPq&-ZsJdS5P2#PONT@9_(Z3yigVZBH|xvDpQk_7lmK+=rPj6LN=((RXw7LS>alwQ zT*gj>I@V0G^~4xAKa6dEor>F2IM=Y;pJ3;9MKL{)*pe&KuR3g(=;e%mfil9<-Jb<} zy%>mVEQx<{Rf_ur+`8X4@ocqJI>yWT=CIH6Fz@Zrlp$g^FDC9L6#u=}FiyuKyfMy0 z?VFdP^1c)2u%Hh(eiMdoE5sJi_NXcP3FSAAxUH<-7_@qMkS zuQ}&5raVaU&`{F(-roQqKRj8vE8=xxr{B;b7Ra;71jXGWw-ngT;rjh^n}#bZJVo{X zFhW$mB0fS)d~!3$9 zobt_*|Q7gp{;_iQ?7Xol4tD#qR&*R20= z6X=$#v1Bnf6zdo+|7M)}Zf8+Z{|yu{_7C?$&W;U#AZ34kloZF@BwVAW0zj_cdCki; zZbmn_iAWnNRRu{p! z4BFfC9^UQZw_BQrj`bat%3j?Fka{>EnY|bY5N8dK7a^9w{*cHC{IxhAOsrL3b?Fg2_8CO}x6sy; z2j-oV*fpmtVc-k@8;G|3_WRw;j&zXyM_%6C=E|o34#xvs{Ab5{WbXy8T0gH#g(mti zFix%c`Xl*qvW8c4NUa%9?T@Zi+Y3N1#btMIal@Qu@jcKM%i~?yNeX1{?)3jcP!EWx zDsnI6|4WLN_V4^9L=GelQf&Dj8^e8wwZ>oM$e`aC0I)s&R&KcY_0Lvf*7sKrlJx4_ zt=?M{3KAdPRh5pFMou>58fKA4wDo8|el{-@7ZgYj9VQ}VBhMBci+KXiRV|?WX7q}S z?dpAE7|p=p67H*SD*T^?Z7ISHQzXEg55 zxq?oG`P8#591#=LY0LqT^7+N;WB4dBRWjvh7_RHr*=winSmoaYO)}t75sJShFl+Gh zwi^$sNxE;^JRfSR;&chgn58>guSLx}s;c<_@H2`$Mr9lz=!bigJAbZlJ}R%d6<6RS z0V#9o#Nbt;Z_>C5&J;X7tQm(wr~bIT8BjZjBtE`&);=LU%y**Rf>GpqeG8JITc*bG`WC?x54*VVmc6Qb&18+>wOz7svJ&rBN~S>1w8Q zuUF=kifzH<>kmz+z!brWH=D)iyDf*=*;(#%e={!zD5^b27YN53ma3a!_K+m3y0?Ke zpxW{QQl|V@<;(2}BuwL&eX(z=dTuH_oVc3c2b3r!TH3vkTa9mK#3CujA%J#qlHmLg zg?nZ5KO4cEQpWQgw@PXTsTqI&SwBod#d0Ak8R&Ao zZt!#QoRi&KxwnkO2OP1;=D-PIWy@_nJ)PBBoS9jamA3PsY}|sx;s-S|J%6)uv1vvt zw~c@MaAqFrT{iT^MAO`e9#8Bonh>8f>t8AaIIT$At8lem@<<3tD3IT>`*UtGmb}iT zsq&MKT0Zwx`8@!_suLEJUzl}pe(i%^z`U*Xuk*5@js6tG=gIrw^bCG{XKT0~`>|A% zU~ND1V>7~8kWN}sjq*2#!iZQA6?8V-A`R6X$y4-f|V)rJ9?UA z{ndvs#@KW)cgY$pduEp??%a90e^-W~Uq+IM@5cV1qr6$&+TtV2S4pMGN<0kJ@dR{D zUVf*Wto|~1#}2id!zHgX(W3C}3$~`yw||(@RXVu=wvbPfSQqc@Xwk1+Uo|)DHE}HV zzfvMl6wur%cW0ZEzhu~yX~Eq2?bUI~*EfpNfb^@&K{NdDtl;e5a1qQA&H*bYs_^uf z>P<&?kfdfiXS|R3GRwH?>z7kf3D$Cy$zb)T4sV~G{ipoZfbBC!Hh=l@U8jwD%}$-r ztUM5Gz?$%Y>*UV&i+uW1VDtQ{R-kDAd}_6mUpNAn;Y(KZZI5rjLN*7vKeJefa@QTR zM%cVg0B$0K67>w@y~g-rZh=daSj6wEU7Z3jX4Oq{WsSJ8}93&@-5 zoI_?uxDK^Wd5UC`Y4X9)96uWfN(>5oC$6HnyJ088)^SpJgTt>0A}o z1$~B?h~>&Hkyt$0b|5ABwOu2pkE2c(=-`B?A z$0Q%!jWTkkKQl>lk`E?_ulZg}9p>QGpSI!s3uz)+n#viN6x{X~ta#^=JdX~00*R}Xc*X9kfV^@Iht3JWQ_SBPGuD`Qn*=cX|4>AIi z4&)fUv4{KN=Gb>kT@kCP*SGR;k1}?bk^9LB`JO^gt_a#q?}x^%$pzvzD03E`Yxda6 zzc$(nefA1iy7SAVg1s49Nkkp&mZ?cP+p7AqTg2EfTO2q8mrIjQmi+va%@d60gx`#w zg~>%sMX5JpT?n*NUO$=tVHNZZ#P*OeYW!g6{G>_7O72N)VA}po^Xd1F)|bjQHw;rE z=${`ue3ygY zj=<1vf+&?B#UD(u^XocAI>e3}A!ABLVp;pZKVrA4ZE2(w~1Nvze#Cmu`4s>1d)`VBmk z*sOD;^`Ud@hQ*13&vX=;_3?r$swV&5;;Z>o{tEHqXj?HMf&?2htP*qKh&{7#2R4@V znW0(X8&0UCBg2@i1~N@KmtuT#S(cI8Uxu$9?OOV8v>yz)$z~axCk30E&#RGf#~ju! zrl^5)g02WVVk18>#JEfytRAe=C@KGDKuR}dP|N+DH=k`mCke2tfVnyTxDqP>`p%cN zc}KnyIE~7XB{l4b3PN?H?!=EiaTm$z-=?Wm3$-rQDPCHt2*16UZb{Y%omSKPnbMO; zN$hh;R_`jjWcy@;9R1faRXTD1R*9*_=vi)mj3DvwJH7wCWRW!ZrH}7?8*lw@Y1{jO?@?g96M)h9=-A3d@_HR43R#}GOBp@ri zI_og{kIg5@5)Sj{tA5j3(Ua=g(^NS$)qqZ1seBYo>+tCuOjA)hit$fvKU4>Ufot#~| zj9ElRyvL9y4Dc{UGg8`LRMT~8M1H1j`p3+xVCd`wjc(ry9-_>Qd52@PQdFn@-Qpms zo{ryj<*njnE!XO@sTyIqJI>dpYo4-jTcS!zWy_$-&ne|Y4ZA1SBYy{j2jL9UirJf+ zi$Xg$Clm7mSpNcQ6>e7-7etn(($%Yrmj5QQ(oCtrP&b)J!7K=JZ}0exXf1Q1j$U{b z{=tRST)oWHlkX;k6nLPCn!y@{kN#WL+$;2>4p?_?7qTW>B)FUSOyy7GMX<-x8!e4| zrMoLt6fov#NG8BJmQ-QJZTZzup-QK{y`7a=Dj=j#D@BKdhSS(HLJ-BAuUzvmM|a87 z(EoAkz%%2dH#@7(j%JNREE z+xskjkr%-VkVm|`HS|2$Kz>x;{Nz;|Gu^{!G{93SB0Sx8GE)|W#4x0zr;G^7XLFdA zdLHuu%In$Il`^sSgt7o9DO&LgI+S6y#RLU6EtolRZZ#AFyBZ! zAfDa5)kI9O2AIrHLFwj9%2uKx{l=Mx|K3670-HW&-PTAKodi4o(;uD1zV?r~+j>0z zKIJPHTT$0X?RfUYhRn0Pcyyqx7X`F!=q3ho(Ihl-kub<(MXYD+lt~i+@qzj>K(#VkJ=7skEEIm#7E=Qio)=CQJk}w=K;xQ1r z9Ew#~z@&@g@FMPMjEHKJ7gahRvp#Wa`2h5_DLy+Q6N=94PnX$h;CB3s-XAA1>GzlCp<9}ZhE z)H+PYV0M}0(JJXU-7S`qJAp+C+y7^pjE#8Zz=OxS+`qP=$eyd6EP<-K}v z@_S(D_B&MdbtQaedUt7JoI$U*AXv43NZrB;17s~MMakP)8s6Gb`J-@i1<6FvnvWD9 zB67+XSE2Do5ejNSz%@tyMn)c~uI94}-WsynqS&07RVz2$pVWBY_2nV45e?x}2Bt6U zoXK=#wfUPiDg|7Cr4J9E zWsrl`wU=I&yCvk5KHY^k1tM`JF%Nh5+tIU44?jiZem$t2ShyKSQa_LTv}$lam$sVJxT zcV&NbJn12U!jh=)(~Xea#9eobG5ocpXH|5DTHnGH zm2$|QXMKZ7n-D4wil-Dh_~Ks?+C1RtF&lm_xW2AR1C{swVwPf>z0g-u)2C18OQ5jq z2>~<3)CCYStXsA1lAuEH-<64V9fHFq5BseeUWIBf`|+&i&UYFgN((!C>0i*7m1G}r zymP*hHAW+`=*Wn#T=A|jUW1R zn{A>`NWM_-wjpJG*u#hSsjib^+I?n~r}CTv@?A`BPV#sA1l~;J>=wF_9I9RB#%p~B1y>{+j=~eIifoDvMV1i!`HmXIe z{`NyA-{sFOu`-}rTTeDxvG}g70tJb^zp+0icXgB3R#}4#!J_=CZtX$?Thrwqq;g zJ$pPby3^kKroa)}VQtwsuKVphvkrOJEVJP;Z`&Lw5{T7PzLxg5sQp>{^Yew}105z- zsC?qhTofI%*w7Gr?17#&O+;zVHcs4+1b)Y=^MyoHSclr{LKk;SHcx>wtl#I>!v$0M z#(A99W`clQm*%_t#BfP6?2$A2Vgenh{9GqLo?XY4NrAgSeGh4JgtVY(OCtwRh!^S0m3V3c*aw=Oz=#j_rmyCg@UFu!7(t6GI*Eyhct$N}0y; zMUqP6K*)^VNhxNiqRRWbRiGPTh_Ol?%f<5F(AL)5ORa@uT^?=@intv#CKK`$8A%m5 z_?!R!>aqw({DpqGgnOg$eTz`vM0Ln=kxOmq`o6+m8@hDe@l>RSY!$>S27) zC6Jx38v0U}YC2Mye#&0Rg<0x3n@i6V&WBm_9x5*$W7^l2sl!eZA&!^1=#)=X$=B19 zDoSAPdA_v5*WRV%gs%S-?&IX_?ylI>?DKzza$&3;x(rdlj)R>aSSvg^g42s>Bx|7Yu(}Mnp`@s7)Gc{6ao?IUzEam99;9c2fNRSJ-}s}C z=F;*1ivloaz`^%U=9~nNc~~}WOrbqG~U%FJ5?a&#N5|2&qF!_ z%6=|Gtc}*hRpPgXDoyWii0MSVFN~faAaQgq$(ubusC=lsKh@z6oblJg6>n+_Mb&_Q5u=qS%ETI@cr-nsqyvHozkISzJJ4 zNzCA9*Gf$gk=J{CwEy*!Ezp!fx*hk0RB<6?RD=lZbf%&GVPYoxhDj)yHHPt-8u&$f zs)+A)F@Rf03MffL%Gb{Rxw{Y%dHuSgwK2SoMJ^1aXiy^#w8+@FI<^gbQsGYQ0CEEC zNvo)AE9t*_7_vzP)r5wtJz!qSLZhjW>;siBLl-~BYV&jhcieqxU_jrlf1QZz2P4l48yFPw-Xu+~aklGjd76}kc{~EDE8aF8 zzNl<)8~e`9qNofl?2F1gl8dJ!6@0=4m^DiHmH_Q~Hz^{Sg1_^C9&1aRR)Bld zSw)|TIW^@APoaF>fi6X&-V0N}mHJDd4=U^2bI-|{Es_lerAU@ncxqiB8!n}NAA2LT znwwCz17Xzq*NyApea1d@P1_xetH&VHM~U}Lq=ld(CG33rbtny+F7IvhO<_W<@rKw=^IG*UsZyFN-i~IO01B-GcWbfl3^;H2^u8S zKlE|8Pz9ApszKAWvlg0|^zkK_N&YS3-S)j`smF4-TW(TsubYAPPD6b@>*VWctLc&! zHZ+fChS*cT-*Eo4T=%KJ3ln&xH8m9IM+j(Gf#CYM9p{hBU`Zmb6!6ppvA*EnW0`=4 zNTs0~$J5-hys$o27a2KDNv*|8mf8jEg`_Gs!}GX!-ClMTYNXUQ(rD4{&3zd-NO^M; z?C5-B@3dUq%C0DQIYGXa;>=&Z|Hjpetu_V#`Uig9-yQdRXQRaz)A#2IzV4$8lI|&o z>z}oF*)Duw!7OK~QJ01EV5iS~VaEs7C>7c{>osZkwBw!tNq5q2BZ9qJ;%KK-8I@nT zeru});4E#1vEUf0Mw)Gs<4ewp&CP>UKIj%iy)GMCTHhUmBDi&d%roR+4=1z#(k4Ai z@A;+hvFv_%3mWM-huN8|_l=>XZ|(@E~W|w2Ddc@b;HaTqsqazad~% zjD1Z}M4er>ZxPgxBpC?>elfoK-~XN zO`tB?2kj(hgF4R3L2z;73B6g$t?ZrfGv{jqp@}JH!j<{WSC={EP3M0ExSfPlC~2PS z#IqA{sIaYy>C+2LV6`6u7R2N%$AN2wtr+WKDPNn$xzt;C+j$iCa|5&4_Bjt>(I-$K z$t^;1a`Q_>MYH#l4ez1!&z<6b$9Kgn-v;-DeiT#JzUFJrl(abPWAN9ktS^y0B+9r+ zsL8At%^HFyg~VqbPiA>0d7Pv-yX{B_wN37RY9At8{hMSTuyHQ={gqm*N{CruH2WuV z2q*-G3JI#lJV3v$_!($HS=_s;z5e2YTNf4oZz14lD7gjmz+6vNW%V{GQP(vC8P9Gw zg#6Mh>*5MsdfIf$#o{r!7-*2z@GxcPrL3MF>+`UJanpPJFV&GmlaF+(sV%8q#Uw4K zW8z>rO^e6PgvxA4X29p#eAlvmJF4-5$7a2CS9Iwbn%}_Tk5MUd^u2U#c6;}G@14|R z$H$>~HAQpBbMZ1Q>f?PNhKoChX3Rfnca5^y28@>zv=_6YW)=ElA$*}Zg$ z#$A2Ykfz1hPoxuv*s+4c+l>_Z8^)wum|;wnkBWX`fx!X*xr>UToLpAAA9X%CZ9~q)V-=nQZI^80cX`i>~+)xLiv3e_fpcIdG zlheh?k}*6>Mq++*)-^&SHO*(wq4FMcJlp~}_hh!=gROze(x3gzW%Vb0aX|()OT{0~ zbjHv^yH`%fV45ZW+v#oEpd+f<`;v83ashT(z&027>!au)-y;=vi7fuFTymxQBX+UG zK?>GF+xz^1Xym-lrZ*P$>8sSyX3w2Yn+z#MW|65i7}1V)tywSIEonNo^*r&b)yuj1 zqCTDY(CgaA)(H9|sh!~e0v!+H@I}~a)iUl}pTpc#6Z?lf^rGk2jTp!fK#(Q_-WCMH zF59qxV{JZ=L3%m?2T!AW+rTXWshSKf+pqlFo{IP3RDcisiAEit`q)jpa(e-la+oFv zQ*U1GbR&H8%|}>X+f9WJQKaYSuh%L_^@>dT+gtDb{{OW8-Z$P#l0+7G{?i8khnfI@ z5xsZ8y1Zj#*9~m%tbPA(xiPy|Zp?qKsJ9>hL~(-mpKYOB3Nbs?EGh;-w-kKj<~*A9 z3U&{=h!Z)q{8^dy_RjnJ1mJJe1RwyBA&BgYOaKhw$@CwF&ucae0zH$9$b5h_=yh2a z=fD2SHGKS)Yx;#JdBFE1|FiX7eDlpmXm_F%_&G@$#R;M)HY7kuqV}WJH-Gbg*j|2T z$pBxm`@Egu5RvQ zbNc}EGp*d|#q7ZaFg@A8%QqL$Y*f+iM(5gWc$EAfR0Em-fb%nCIGdzXf|qi?amdv{>pWm4lkJG}_sedj6Od%TwJOq5bGx@?>z z2m=8`dtZP2&CmbC&d$o7hk1*CPxfa4p9TJ?#4wC3&fJRW{P`FU{u%YnK;vv5wj2Nm zYty&qW?ui9zZX;*pE#~@j{%w!b{iZFhA4MB`IAl>-7}@kCrxY_t7enM5Vx< z2Y&9YSP`AC?!58K|EFo=PkiwA`~BE-^pOCV0MrP}$Ju^vlJTMu0B{aKAGR$FE9Kcw z|MmZ(GI8zaAduT($hX<4CO-Y~TbP|H6b=J+*zV*0lXW~@-8r-7LqGeUmH($f04O+~ zUY`T>6e$$fo55WQ11!!>;?DIsOtz}2ZGO+dtlZz(@8GR_%lcK_(A#({gg+}1ED;dG zAc$%2-akHg^atO_fJFZ*?;^2N#+}UaQ3)Uw0sy0h0YD)M z@Ul5n>bGw}5MX|~iN(1IEY3}!T5+KFuXDiL+hv{Ld-QY@??2npy%6DY21kt>`l?>@r%^!Bh)o_qage!4ny z=dXz%9Gpii$Al2LzA%MX?<|@G*6}P!l zHl=`HDVH!aS;y>D3)7PgOtoqQovNtdhwtq<#((s51J5@0tj7mN0X`uN!id_B|K-7Z zU-=ydz3261j@Qov?P(Cfg#bTr@d*GpJMVrJ@N>&on)4IWAOFd}R&HJSN#sm8T4Xbm z4ZM1L5jU>P=(oA-r^8vau)MT@U z$yOb$MitYO4YW8flD+I4QR91h{!)^#y0wqTOB>kUYg>>5Lx68~CJ-P%W$OO%{=MJ$ zzhw8YSLF3`z+VL3Ve5~h8qO{0m$l140Kn1U9b489**4OrHvh_%sh9u4e;zjG{-Qu} z2coQ%!T{G7r*ZS@46e*g7IrT_t`Pyfz9xcRKSH;cpxcWOClaxgkWwK@6cV#6m2w0C ztwR*ZUSl(R^L-GeTcR(G(xzKh88 zp(ySWJP3RuLM2Gv=|1|(KkDtRZ0E%KM*)8n5;!RlJXgCc1OSW&Ouq$e&51z$)but}jmE_O;oe1CGWu0a&kZd*KPE3e08z0Fi){60ub2 z{k(m-Y@=35DFJt~%!8C|0?eu7fXg%B?et=-Z0zCb>JAPLyQXZ~+uP+Lk57bhNKblC zzxB_eop&F4z4;jUrwRU(sD$Tkmx%y?{3gQVf_v0*Vw5tq**gmpFa1Y9U2a|ZiBWR| zzE#R4EX=g9I5&yK*$K3oqc?SeTu_mAMHl%ueLaRaj&L8Tcbqz%UYU%eG|tTq)q^ zJWe@re=*E0^UL%*o#7#UO&P1UK`uHZEWwianSA! z0^D!@D+0YxX}|GI9OKf?Dg77^4MY9IMwVXx7VU)~cx2E2x#rD3?Q2 z$|1_75=x~2c6!-_0!!Ve*{+W+f=U(}ta%1`jf^zfY5W(@A z0x$^dR0kg9gFTx-EF=PZl4DdXO9UX8ThI|m-jn<9e3WdDs~Tj7p`_K`7LUiVyC^TTi~7?7#P( z?C$pfkO8{#0neVDw4HE1cj`N=G|J_PYtvzKX1dy#oeskKWKb&C z!*Zn|f^uC1rJ4{y)q*?>YC;4hA%q|$+k%LMK%Xc{1W`;XJ`gHCgzO)Z>>g0kK2)8} zO+@z9$qYt#fCn7tvn0Uaw7qI1U+npzOm;0Bolj=RZ&I7Xd$anE`KrEV4uZ3a;jXZR7!y% z8{6kBr@TJ{o!6TtI|4va6deWjQAyx9VL+bu|4`Wr0yx^nk^0XQ{KEjB{W~A|ch8c5 zw{9r-g%A`)5#_0ZD2h@^K<|_NeI6u`UH(wo3j!F=ib&unA~-AXA6xnt{4TOQfE0TE zBfg6OpSSL;NZ>;adf#3U!0~NduYX+d7Xkjb(*LA?^DJ<(AkSNu2XcS;yhz}MW$Xn3 zoSvNo{G)pRqn7>eVas{HkFp}*7cE}|5_kdnBkTnMoSmIE_)o+8=lw3)HViz+0e|+p z2=Lk8<9=S)81{ky&d<&p{71?D1YXz)?F9ime>*AohXH<6?_adO2xOxW z!D)vAFYFBL1p!=KJ4pg?*mBymngI|D&FPaaF)5gz&;H th`k_y51*ZOGvEt*VK3~3y|Bw@|3BiF{5c+w%me@c002ovPDHLkV1l4QjEn#P literal 0 HcmV?d00001 diff --git a/deluge/data/icons/hicolor/32x32/apps/deluge.png b/deluge/data/icons/hicolor/32x32/apps/deluge.png new file mode 100644 index 0000000000000000000000000000000000000000..2d272f2eaafba5b5c13a80220040ac7b476aa00c GIT binary patch literal 1888 zcmV-m2cP(fP)*}h4J7~MU_(=J| zFMl(8CpoM;Sv*kg+po3i06T9thk3gTANu;E58wTkHM=SUrM0U!Z&QDH;lQ8%ZvdZq z=v!MxM=MX}GAT<41JC#FUAyIdfBx&C1Ltowm$=mhJ9mG_P3Lnz9~>yVW7BoUXBw1B zC98k1@{3(x{qfNM9H1oB-c_qsZW_DPpe+S~5}ciGFfu$?&XiKWV$Ysi%6Utl;DJx? z-7&hV`fcI3#(Xmd%SH>ESQ|tMRQij z7i~IdVWA9*Rt&CV&}slFH>Nt*ByJ7OMub9D9B4#LMm4JO8og~F?#r-F43n0JY zJx^70;-2ZbI>lUy;Yywr{do)_aQqax{wjULqxi)#zVG1(jiD8-dYvV0(_hG%Sf?yMZunLhVQSg`QYQ{k3ExIt#jSKAkone)~+lYW0MQ?<qQQw`QvOB>1iU$~ae^#BY4-&^RHkra8k%~A`NTpC(;n)_wXJc3kD--D^#r9<#8MJV zg_IJ|-Hj-~5(ci2xB@ysbn4~D9*b`Z&}>|MX?kvvq5eDoQ77U2dj^$LC(+ z*wI&rIx%r9iDF47mT09h3@{Ba4UH)bOhYk{bEz#hF_PAAuhzQOQEUej$F0oBwhylx zt@K@-sR0ltlBFO*DvdA&&1RG7i{mUdmJqgwRGK(Rke5p@6;Yg^FS% z_@(*D*XICvBY-rJOYCgT^2;9@87gsUp$R}Ik<=OiVI&Eo4()b_V5vo8ewL;B0#O*D zwZ;%8QJfHk5qFh6#>OU@xp4Z4{&w`)?|malk`@r$ z3_$fD?E3i8znjGk8-?W#Z{4sGDHTgW)CEzRL@L5gLOV(b!wzATAeExOn4*$%Idc3I zljCDYjvw6jwP2~a05pNco(6FJCz%WM0VSYVz563O`c}W~^L_bDexQ^^DowK$k|YYt z7FebrpYh3MJhX!G=>;Zc8*!&O`Si&@?E5ZI2Nr;NAiPrOMsGp^6o5RC1F}PJ-SMu< z+IM`ywA1U*I_FrzbZiUD6vPVMiaL>!$wb(=_`7q5e)LorhIOFPb6vaA`EmfRoRb2w zz1{~Lzy?e&UrD>3iwbHjDPULH6H<7>Cx z;oZ3PzWlk!rOri?!BX%2TBJVk<#(IQdbbH~x$S4)eBb)TTy-Rdt2|T{Lg_3waacF9j#r`@Kc&W#!{r2n)f+xtTrO^?zXeddsdcx z;pRI(Hat@M#fqT;d1$5qmW`1X?ZlAJG7)GpEs-< z>wR^qjutjp3M~{Gi)N5g?JJWZu2}Qo>vli)__o*1B>x#(Cd%rUt5%PUPA&wrQj1vF zMA*bQB*G>#iUZRvYQqD{^}GkZa{G=I?-|hMZ~grG!J(lq8RhWO>+?)a&oMnc%k0c7 z^+uB@HE5}bwB+DXpEYBHgHpQNw`|$AY$ule;m2>jca>8p{$X^Wn0x4vzcV>;nCY20 zW{%Di1_8tYjfHuVD8ebYq(-2mpttOf%{WQvrN@8s*f|5*ymhNegMr6Jdd#YS?%7AZ z>EqZIMK_0=caTB=P%ahFaYS=|4x}PRG0>9-Aug+ZXv_02?|k6pa{^Rd_tl@eas9d^ zjyW>hV6d;mimJy@uSd1FM7dPJwk<3na0?EWgkY&jl%xdOVfC;l7MkH_MlS#S_5*+Y z-QwwLiFbne=y&eF+Mk|y`2A~#+4J%Q1HDE1%S9?)0i`6qA0wPRmXpI6OG3a&|e}%VWiErJ4kw|(;oYz9o#y;a)6on1{E()Z;y)*;Mg{sK6oMR%)%`=oygl1 zayI##>g*?Fk?$X+Snj2#U=ugzd2PDE_>hN_Q+Hqe-FtFp1T?bwM{D~$r#dy;z_Be# zZk}R3M-(TlJAVvRoViDgMFrJ&SD-UcBA z(~E7Yl>$S zR*0?*emi1nw#n?`5_!kw_m4iw;{4I$t535xBS|yTOk*$zz>=V3XSStgsFG)Xp+S~r zPrtqHDW49ze?zb5m97Esg9u{`QJkU;C`;nzZ4Mrq#AGQ&uhQN42|<*QBq?c@l4TvR z>=6raKlNx9mjKqLA}zC0;#rn8w`Hul}{;-d(_3UjCB-Nx;j3g}gho zafMs-F06Pi3k@IW9N(rN5rzp-k`l!!K@{UJwOE>;!LK)nq7Wk_2uY?jagq^+5yNFg z8U;)onw&oP^p4w_^;sW?0R2utMZonJCid%|b)UOnd?=@sq~2^F|EL`%EHncAAf_G0 z#BoYHh-vu&ON|Ee(=#m2&C&9G;uLDVc?y>1g}n!&nf<$deBj@^_IK3@faA8E49EZu z;ON+wnKAntS^1M|Mysk;t+3eeJG~H{Qx${>t#(X1OlSo$eh|@W$M`{v-wsK_fLdA6 zXf)V&U^1HCxBDkA?0WDCz&|0(>44}GD?mo|Hx`2C+&^MxXhW(+|HbP^8R#t`g&^*p zCRVZ%r3T=o~_di^6($f6~Mtt^F+Cc#kG zN&B^6>cxj%-}A_GVHmdPSkvkPwN6!RIY7sT)dg~aJdguyy4C{1wrv4qLWnGm!~k5BEM@UEWP7K}FQ|McMRe0l70`0_5tONYb8-)tHko{XyzQcAXO z-87Nq`U&73z+FmT>QcT8;8VZ#XTP>(y!nMIc202e%p%L39upH|q`BF-@1~E%|M}g2 ze&$m8yQBp6f9j8RkBtsLwP$Ku*iSTz?LN727zbN6jk1#Dw_f+*dtZL>dr!S_seE3t z9N52qzZx2D9Guv?c}RHyC+6B{C&`^;cB#)wqS>)+T*l4%Hy--q$9G;Tzn6@F+H%_; z4mX=OkB@I+`c#X|7T|Lv8pW~M4$2GIIzHC)gUXW+KKK{@rSN-62>il>U%E4>);_;` z=OicQR_G)ajUvZGJ4NmknUze>v>6$x(;TVaAa;#Cb}4*+;2ik52fk8|8ntJyx?)?? z4+4&!X@l|+(g*2brNRk?MUq+=q=uanBeZ(ijn~}uOUGXR_Q4-~5dR;z2kemgT63(q zV|291YbV>}B0%Gli?Rps(4sJd$3oXI+ew+)zDam~^yd$J>B*n^KO=C@ryqMDti_+a za@SVgJl$cbo3h;Q(rUG7Ew))&YSSNNXeY2j;iSS!#fgOh2F0$)v6>=!;^UwD*V;xg zY~ihS5klX4 z`J2yG279hpBlkm*g&9Jqh(Lh)Rbgzo&^Z#6roj~|+d zi5hW*A00W#STioI%1EO^Bd*XGsxVXw@I8g^Nun@7DM=LgC@GOrBAlhWw1gK`Sxy|2 zqcI5T3$5g*KQwXw-yc5wj~6uQyC?!>aXroF! zj&Kgzy#&89L}T+78e6tAG&+u7YY>JZp%UN>LJGjK(r%%mA(s1^JzE>Jd->F2WZYXX z9Q^KuV7g!l-S8`4{l)g&%r}4bXRhYhsYSZI0b?UIn)NEfag{JoEG%^iD;4(c+e=S- zB)SNqb8aocm4vjjM87plZ!nhpY1RV)qlfZn^ib-ahnH@_x83s0v+m-+^27ptoyk$0lB# zK22N=sn;shYZ0|-K)ahzuT{A1*6Zn{0?+e^LZ2w~2?LMNSA>B_;45UA1Ja8K$953c zV`>#2m30u#GCk9#8u?6)##@}Ie(`+RE{K3s-WPXGj&Ww8Lzd;#qJV1Er&b9_GlTDW z?7QX)gbJ`i5rrO6aMpsrBk&Y~r|><6=Se(IqWplMIZ3q|QVA5g*TOlNo@sOC46|`uMG93$ z-y;gWwWM7g%vQlmDbB4(LLdZql_C7Gooibb)>>NKgpqm$aQD6!zVpsTjmh`aYZ24O z=Wu{F25k)1Iy@<`&f!Uk@B2h0GiM#fTC@=e1P+HX4k_&#fmPt^2O{T+Kdz*)ypqtU z`8a29e=mIJ?Ez=0SE7I<)i~$KwISE8Bw;bel50a4$aAXB`h-14GOLiwro@bMt~9U~ ztaS|1jL26wXLsz|e<0Wp0b@+n^CY=30Hm2FO>+jB##53k*K}5TguYr!-oR7%N}<+C zW>qd{BlAw+#b|BUo#&Y*@B~<+MwK_dAp$}a{V7Z5xi+lyQ~HApW5F29TgT22dZN(0 zUua$_i4td{E(B}RSOsq10lt{MT8y4HB@2=2|Ok8Lgjly`N+(EcaMk?y=Mv z5Z6L}czBv;zw-iCXJ}JO!W3Y&v1nVg))cuQMFCogqBhBrfWlA;BvomLypPR>7hX{hA2XvwJLPid^DzDYv(FUt?B4wDG zXz=6LkCFA-FaG%79{XyRWd?XxBEKJj5U2vxpt0SqAIK;SG#(_s9*z*qA6tG6+`xI&hn+oQR*bo(j&Bx8_jk~AaF zENN~?Q^O$5=?^lxy&g;R3(TILp|#jzkYp%Nv3FY?qf=fve2m`m!Vlki@!+4%&(3s# zw2XdwF$82u!Uz1Ub8021jkT83@OIzx#D}ljMV=Rl{LVvbTl7e(8Dx1uC@aAC)13Yw zC&@KguIcv&R6WD)v55J_We&eJLw{-R$n=W`A3pW=n+rfv+MsODIo^+eDgUoZGHNh? z#R((BF<0#xXKXm; zM{k_smDi6mSXqAK=<{Fy!pw>3IiLsh*V((0_|kZj>I*jdpZ+gA_>}USwGuK;} z>rTL1f9^N$nw@TZaP~mcoovEqE7{+V0{_I3xhb+-v2PBrHPNdZQ&0}*HXW0O2Bvi*n3_Ql}hiPnrhN$)L6W#I+TuYBsyKeJyN?<;YZJMVP$L2J+ABExemVPU@i2MjKFs(l{}=7NFm6RKCO)ngcJm& zG6IM*1r`oYmgyx*_LA&7PQ2nx_n-dv`(N-T%D&`)*T3V>%8u{cWBDv>|s`2K_Uz3=aX7pnWd5Rj_ieH+WmCyzHAq!fJN%jdAx68awXN=UO_rdcmD zSufG7mzbzV)TNV6Pm44x zh?k$=FzCoWPg2UJQv{@H@Eu&o!F2@! zL7FS_T$3q{6avQ)xUPfmc^G3c)}Zo&fK27&M$$ZT6V~yWs)ZaoP~oxj+dR3_>MEta z@sscQzQ?XB0SgNY@^2SszPkF7B$7dftLlk<{tKlHV14@xg;CTeTOOh(K z+CBPlPIGFKYNLT0mXNMTmMc`2;hO}J#bjAbo@;Vt$y837X)+Cs`CBkZZl0@AFL^xl z#2Oo|^f94=d;aM+zP)#~IG%SENN)MTpIte3?gwu>F~{NA1`j>Dh~r2msv)&>)qlxbaNUOmpz#;%+ z4E=70pxR`kl`vHcnQfF<+3HOvIz9B%e?IWB>qUSFXYc-Gz2ti@z5OWXE^M&X>K4hZ z98s%=1Mnf*!uoa>*LAq_&f7S4(=m4X24w_}lsJyWaf)jx1VRV|65IfiR$m09Ws<_Lo#jC|k4bT+^!lH71+xy^xk#Ie~Ljh)OF8n2m=6QWk&ovHTHSai-&1x$jHA3D%eAgLN2sw}V*lx!Jp378of|DoaX(tBZ6yO8TCGgx)Y4nC(;5fLB9LXp| zq{KuGqFR%{a|r_%nf1UH73A!tHVC-oP#r6H^Q-Us;Jw$3fGF_qYt%|K>Lt#eU&Hq# zzV8))2H>*XV5}wZ9bRbsA4#wJnyB0C)x)LcM$#OhLGB-Pk?@1Cx zk-!EX;0-MAdu{>T9dmm};>w}rcfk)uxFOcB;YtTz=%SMm0%Krlv&Yr2*44$$_nA2lr9u^Bs0@ZT*twR0_-sB58d8%qtU`4Uw6NCO< zX^Rjbg`ifA(1tx0_s5f;dwJ5^jrToPADacmoOv~D)BuY86R;$irqj=vszrc}N?L!* zz7n8}zH_2lq8Df6N+SeVV=>lZv0y)<$F5BzS-7giU0;(gcZgD@Xow%CGfJD3?f&jLH$1kaU}0uKw%S`MG{TUb|iCNYvI z@Wu$RC~eVNqqIgFgOn~>+hRLQn^D{s?oxP*F&1qE0)xe3t;85x0G47D>!WdnC7T^N zK9~U+)>`5;2Wts^hmO@ZKO3&+>;XdACP3hOBxw#5mR4%GW;bY~aim3QL#{MRDLhYJ zG46*JAMf~vR(~2`y9*cw*0w`WD`e<(aZ(Tv`3_iHyZ48Gb$WjYaAgC)af;pA;ZRDG z=6SKU&ooj>1O*8jn{8al(=xy7IHOW`xx2$!9r60io9 z$=Q7&z*>=B>2qx?X{JfioH$V=slpws?-!R=@mxtT?D0o&e_4_%P>%t&Bf!?O$HJp) zGqAp~7&FWVrdZw@gU&Tra|Ml`Cjl;Ur%-Bdd@5~-lbkqJBw0?9WmL-^nbMqHTp>%xV)^_d zr`gM? z*mke&u4RoGS=`!!JSixu$DrGkqWH`e7Drf`wUBO{kYyPd{rI!tx~fjlT0XbB*(UH^ zCaQbe>@3&x`YG*hOuLt|(@E&|Go~74+MPc4f9%sFy>0Bkio--^2JWnN5ywW46Hp`t zDL@E2XoE-VAEYdL2T9=po`l&(iM7oZ(i$-O)B8$*G1(&<+g-Fa%uLmu_I;WuTHS=5 zR-aZUq0>$1^it~8kY2aXhaUVV)|QvR8q5Iju8fgk%qS-yEW!@g8+K&z(YgRjak{`V zU5{`jtgh{V)flZl{%n}8W>YjGoY)Mi2i_j~o;)(!;OvEsJvUg2I97x8fDmHTqK3Xl z6gWKg#CbZc7L8^dWhJ@N0~s_=gN{<`f&??1$4DA_kTwRTi=AVub8bJ@WT(~P!txqc zr5B(0-v<`@o16K55D)-Swt2=>j@g$D#c(BC?@T0MN%8#i|1+AT*9P>Ot3)>}{eBMao( zkYsAqs3=tskmrU>7p=238m%(YUYFMTCA#fxvMk4OJggK23G9-fjX@bpt~5sF2&1^| zXakk!eDSeGRF=|Tee&IlU;WR=vMf^r@S3mV1<#d$T`{@mS7%xxY`nG~XT11Dhrq(d z_R|kSK#>#TRFUO+0A7B))t=K(5I$MXC&0rbseH5+`)p9k$ljSzlhI z+wPDlg|-fku$($vqvSh0^3_GQwp(Oz`^obUe|%wW6x1#OX|739MVc$pOw*5ZlC1Ea zG|TAqdbBpTaPuCw9h<^)C0{;$k@d|MdEC8t;o-k}*QN7Mtfy(30@-EqyhC7kpF`0% zIDn(Gp3dXeBT@7CJ!?DtT0hP>F+a_*Lz5_NXm?`lGyI?sjLMkG4Ow0!wJbMRxduj)bICWARS0HcKy7Qpk>g7 z4rF*ORDYwFZY}G|QmQ`y9A75= zX99J#2PDFA(CqdHArLx#wbs1tJHDqnb<_8!%2cDkQYi(vPVo{T&2l!jyDYD@xU}9P*BTJUBl(~8FMi}VTU(d5dcEEd_)uc@ z67h`V`J51OuK+$6n|&N7Y~K8umrozN^Sd(Z-;w3obEKqP3aFL>D&^w*K;ReuFco+X zQc9FADn&oe*y+TyyYXn{=z8vsjki9v^XOlHxYOBe_jE$|B=U^x5% z5y6-YnAaZw4P)dpGdAZ}tjxc&T z>#lu1Ui^%`FMgG&f_FJ=dm9rMLi1Zk2g%0_YN+*mBaz06O@9e zdaxc;>Y?X(LP{xw?sQFWvn7NuT5DsB$+gxhNs_$Z?`Q1R^K5J$&-hn8_VcK$VAt|K z1?Ve*AC^-wCU~9)V1*DCFjb;6_qyBmNa`3}3PQY~;{|9T683ujp^7H@z002ovPDHLkV1ltu BN$LOq literal 0 HcmV?d00001 diff --git a/deluge/data/icons/hicolor/72x72/apps/deluge.png b/deluge/data/icons/hicolor/72x72/apps/deluge.png new file mode 100644 index 0000000000000000000000000000000000000000..22e2a018509616284518a966fe285b10914fd428 GIT binary patch literal 6153 zcmV+k820ChP)%r-#RM> zGXMqzK~dr+lcGdXlqn{PnX*1u<+53EB0G`gm?du?q&QMks-%#`cASboSh1s$D=J6v zB8gHYOEguE?MSq2iL@k&q%49P0Ad*cv%h)!-MTv;?tO3G03gL>03=mu)Tw)M@i5i> zoBug|&bd7U|9||yGk*C)|K;}gfAr%ES9>1gLtgC(49ETV9|`LdmCsF-ygxZ|% z=QAAt*8pnF&;NcY@D7&C!R>u9|6i~6e1=0^?Fj(<(no&hXJ%&_ADx|Uh;kTUEVtc! z=RZ90=)(_w?`qHOssp`b=%?VyrK)>+ekNvxH$$#^n znFhY+vAW*m+{y-?>#%Qrh9K|{O)l>L^wpf>Rdca_`b)ol=k#R#leJn|?w_BfKZrTM z+9Xaj%5kVw%UA?)s;|H0&L8dn?ZaRG=GB6m2#p}K5?w%w62s}0ErfD7ux}Q}_4bxa&KJOnD>>gQ>SFKux&N>@*=Rf%mdnBQ zhxVZj9A8?;AP~|4DM3gK5+ia*7D1y{MmY|hezJJW-A{(!`ReEY;!4f=iYe&6`|eZK za_uwH_3DQf_kvO!Kf6Jq!3c#$V31@;G!lbCBRRj>qt#28Y*d(NR0)FMU%c-^j_m2$}W^)Ag`iWUx9D0BftBhj{iBqW)Yys+FR&S2l(DN3b4 zhUMVDzxOx3c<@Th`-%Yi2mk!H-s+WtkCrQC7UrhuMw(MA?Ls-*Kr|}97sWH1D=9H> z{QMRO$)SC-IIcSpNcDvu{gtoymwWD)9nf9x`^{>}4?nHkz`5?4ISi6#&TNngg%M5x zNs*z7Aie!}T>vylI+5YjY8TIUIXFLsaNJuL7P8;H-1EQefXZIwkF>J#zqJS|})bQE(6~#&`X1$%zsD!dNP(jiVWmpBUZYZPpj6&FGuGy2EUqKD;lLzmW_bMg8vQ6a)@jDKefdAVyR+Nt zxGZ_lefK?}I$`#ZQ10QI56`2p{MpyO!_lM9v(;?T?G5M+Vp`n+%~qd5l%kZtQ4(Ve zaU9WYw`s1elBEe=DMUGn;rBDsmO+v+-3TZLE~~ALLdt$y?#ul4b`%bQz%LTNgT1Yz6uC}as?0|Ed3-y z2+4FkKnh8-8^8Yb@AxOZqhI@@T`g*N9nd>|2!NM^2BkBwWH5C zj>2;kp6B3u4!-B&y9x_LaZ0Tc;HUzU6i6u;bi1^hP5e?B<#}MC)k~;^y*Q&Y z$nH7(j{Cm;?8BcqwOi}i)fn{lU-+X5<;%yL%PWg-T&&Wlg*^PNXW4A^0B{|Jqa>c| z;5q^+B~nV16bJ!Q3Ir0R1be1xI7(sxYc0lrHilFiCifnoIx#_JEK&$=Tx=kKZ$Gz2 zs~4ZwaeC{QfBoGnyRoKS-NjVN{lx0h(&FAKOw>c3K7O9fRu9i{sFi&tY9*#8LZ%ua zQ;jN9jWW~qGL!X?iCRdlQleaPDfuq_D8Y3Vt|N1>j>L5&z9VU^EU|I^ECNfW4bPu# zBcbKiXr?232nnv322!Oy>MW9jrq+_cASH!iZ->hbu|)09gd)i9t| z38;iVl`x=O@+pTNmCz?Fd6ayQFz^V29FFHXC<#J9$@j)oCJ<5}5XAj1Q5+N28bpaE z)s~rB3D*&gA>A% zMiE)2@yiujy_8a*n68zGQ_WT{zU$E2{?T`jf8#UHy_U7T<{0#*`#<5zjAzfCICjmO zZ#u|<`AMF5`V7rhuTYFnIdmy`9;F};$gbm{l%U;>XtoDLY0Bi(6pg7#9Iu2_9;r3N zg8>d1PBy?K5t-JcnITIxsm_Q~Lz-#a`YhF{8MLtz=9G+0`0(&`Q<`7R~TAuKtRf_#Sxe24XBA1Ni(O28d&zJ*d)V!fN;IV#`IhvrCJ zrErzRQF4?!Ng)c?5@@TCjlEPSr|?`w;ODVT#%*+#Acf%A`4;_H^SXU?DuF93;&<=- zzyqZ#2I#s_{$div;SJaB#Td&oCzeOWXAZ_C3>->-U6@;#GqeJf8k)?nAfPf!SZm{3dad;kydoQ}}*fLU^tVSfV7w_Z;SCCt2J-M?W=K@-5tT#%*U( zI8@ONKuVybD5`&nJF!4mF5|flzNe6BA54Z2g6<&Y{N{k!Mv19f2@wD4?qB@${>u(% z{_YR%kD~bCHT(0|RL9S(;kq(cL9uAhb?{wDs}tin4!*0n?%Mr$j$&(I^UpXt)#Iok zoZ@~5m4FC6G|XWHD}hk)GI!<&_f9EvvS`6t0pCQjy0USK+$O&X}WQuApInR_Y?1!8^)V4*BUBEDuGh~Q6nf~1Z4!L$Q-kvF>1Pvmk3d!S!)g0 zJ5@$W0hXV?Y=8*VE!HAuCaVmhluj?ANL9A$Bne1?)^@ajdaaxW?(7KA&X{YB zAdTS29j-O}hU9<7786v_(#5)!i>AG@*+)siL@fa9&2PW|69+B}AZx^JLI@^mAhpLLd$@378=V*+ z-_5K===9#p14xQnD`D>ITVsHRO?7K5)_}!gi~$Rdr)^%wK4B`_VQalea63 zwODdcAg~y)Ht!A!@LaXsL$GybM-i5 zxsRiUJJhy<#F#rtF^qB-j~!qND7LuC`%L+L6sJf5zN5g}LmWBs1K0m5wt3Mg`Cbkv zPIExkl4(tnYSJw4?q!)pNl`pciIcS0vDA*(<2XeyR^vGiMZ96OLtToE9jevt1d~6r zc@bNv8fzKE31IPE1=e_PKN0NPbwCzV&ubyj+KirSV@b3oPBlrAk)#=}qw)q;ep@-i+DeH$8I;4eY60ewni+!0C$tUgl1A?C*5p@XkmP^Yn)5}Qn_0!s+7KrhQIrxTDN&TWTsibfQq9q4PLpQ&u_&O3 zNwP>J$H0tt3mJk?(*c(6F6ieb&QlV%w>j9#)2$`D~+pS2P9+1cF?hR z_ys6axEIz?@)TG@e-MK;m`H4I!@CB^ip^d>Pr8QN{cvb)iK3KVKc*ig^ad$|STj)z ziIR+Oe)}j%lH!Q%_VBnX4MB_tSz~s($@s#@Rie!pEHjKz7Gn+87_2o^0+%>RNz)W- zOqxxdIl1eAtguh_2DvmVr3(O=Jl^!9G=~!<4B{9mB-Jorv(@Fnzjy?f6v56IFjR#d zCY$4KV|K=takIu?tSvy>?apKIM+x%NMZ!`oIkY?d+`WuG@!0SEUb^dmq!8cE)sZkY zadFL)W`=&0(CtTbdNJL8LNAIb`3}|4=b7iv^3}h29A6|w(|utMTTjnOP zMXqG?d}&yQP&`7Qgh8V56wFUmXtsMKX#!U3H0eF`a@PAwU;Fq#P}z6;_5E1CadCc% zTBXFvv+Eaq(3r6uhg|m7VZb{nHgDs{u!!U!vFRqtocGzV*fb_$eUUO+X$H06{RdsFh#; z2`K~z7p7R-=+KWY&MEXz1>>c_Sga9JP%e42TP=>CI7Jdglq(f7!>Fzs#T~mHe?~cw z*$(PM1rC8?GRQJ~Ww~K-idLt`u~X+UX-e8U`=PVP9vl8(%u7Ogxqu|OdVC=0FF&_(oHT`;yMJ+>WNOQ!S1W;$1dl7gt;e+Tpjnx@er`)(WP2dW?|J)h+y*@hbe)ssJfBdmtuV;X9drg4c z0**Jl`jgif;Z^QQQ_bSS6jBIQH!ppLGA~2&g9pO}>Ov)XajVHvL#j2_8nP&+yR}JY zV~t*?jn)?BdfTyTm_UwY&RXY+OEZJbQcRYzZzg1JBIMNBb(YSpW762Pm!JN~>glJ? z#&N92;Jm1cUI`#?-1-~GPE{9fy?3h{)n_Iu?B6>{yE~xMyL57Dfi%-ZX&zTnt@9IO znITOzS*A&{EC;D`g{5gm630ZHChd(idfg6LrV&yhgxrxQW!e-imkg7o%+`Gl&DCkO zy8PXhvpkB&Q|I5A|Ctx3-tSavO)LU(i0DU zXmjQCX0O*vfbYky|WDtE=*AlJXSY5J5IM= zYG{+^Olhh~vI0z&$E!HgBY<(75hZDUpX8s@I&aivnMUZ8L$eiXzGSmK;K}FC((Cof zqTa^RlVADp+S0S<+U<5sUcwJ;9MC1l%LQaE05k+9jZPVCo_Mr0b@;l}$oW&}HYk?@ z4$M!oe{N#*YUriL=m%(cnyO_5j)lS+i(+I=(JaCjcyb9v^(IUf1$ zb0kTM$a=??o_Ofrte$>qsnu!?fVi;a0(;H7*l-lbDn?nn0EA5kS@-kcQV69k29^qEC@5Uc!7J zs4m`dOYOiNzl`!1m6RM@nBuy{S<0c0)|QQ}E~}dzn(aQ_UPM1iw%e}7&uYlxy_gz1 zY34eL$$FXTi3(GV3e~cYF|fAT=Gd7vR@U2jo&;J%>;H50@dy5}+wJzED2jj=vjHy> zdtNOd7|W=~!LXf3`Y07lT=ye?ue$ffpVn5+2_cxCsBmEKBztG-I~r|T8+wC;L6jhb zM2bA=Q!;JI9?7Yhd%X6 zDL4jI$28l0);8Pp2Fa)fa~x+t+WE%jvw!|S`WvU$2ZO<&P|dg- z4J{hGe_7+T9s|A*$~c^%xqz$uuvA}s+ig<^ZhEKmDsSiwk|}Gqf5dzI{k!Knqn3rf zAZ7`ntmDb1H1W}B^|?Q5o%rh~k|as`{eE14xiA2agNk^GfPAU38$YWdxEPE(b{$VJ zho+QYEtO_pcc4CV&EZmG@3meK&PwIgQ6f~*aRgRKB{LyRTc^od7I#h#){j3Poqgg3 zYi**n)=82i-EKE762GA(W7na*Vld;2+s)yS!@4;09)rWh>B8u6h~qdi2m;|ajsUFV zI0zw()>;EbYn}D`{VYw>3p3%2oq0{nOC*3VcU;cjVlV_VgtH^TWGt6r$8o38yvE+L zb7AE@UPFxoN_dH~;nj>Q`D-2|+bX&c&N!s;VaM!3NEtt%(A(O(*yWdX7;z<{&VmD7dw3)H`2sT-@x^@pZJ;geb?0F_9g$%%Xj_yufKD3r|<1r z+t;KC8lSBHVHEh|je3=OtwI#~`>MN|AKU5sx}K(X`UbAG{lvR}<2_U3tsnG#kHZIM z8EZ9Yce*IgJN%+od~U7X==3y&r?KE zK&R6q48wcwc=>C;_`s+C{P<4a>(#ce858v8H@~woHqpFKDH*hyHCoLYLI|V~G#fQ` zO^uHEFf_BBJm^eu<~Yb6Q}2foMNnMnZJ?Jmo!9i$M^M=#`*bW<`!1y_WFQe z-`;70AfQsKys#tp{pX#&@2hFgc^~j?Kltvw`)8*=;d!buvulj0i55b@)8`h*G7Un~ ziTkt~H9XIwQi)hy>k@|H3-7w;AD{aBPyOkacX|w0YR`2I5WAkzDq-c{{MdUx{PjCMw$1H1N5Hqf z^_RZ8RG+0CYm5+ z5LUyF{J^o_o!;p&j<)9%0r!6Yv3;#p7>2i2s}Z*! z*^88l`PFsimbxQ0Um~Q$3IRf4g(GEnEX|Ri)6J+?Lww(-Q4d*c_c2x+xa&0!*T48T z_x;6Aj~Sjr8qgj;_p?zHy$Yo`dT=*FdBmya)P*&)P($-03W-MzfVRSEgc0t#MPh|S zBRF|sjZ7PS-{v9 z3+wcAi&TnR_fH|DLQ46T@BMdw{=++k!H$Jv_rB#f56P?U38j~rzu#2k^(6uQVN6+M3o9*6cN?xR2xl#FhGoq$QZBy)?%#1T8IM0 z?fb_;2re#ndE)E}#@blty;pwd-QV%xvyH=!5b)+V->2p#S3l!<{wpg{z^m>)ND%m( zUtHrefA=t*l{K;~#aa*od`}Vh9@QwIUJFrDB85N{L zI$(?=Vxkdocy|+PEXU8Ta&Eaxp6e6+tpCzKKK32Y>dJ~8nKimN)qW3nue3sO`;l3+ zF`Rz-EFb@?FOX*0C7(~y97tGR>!PHj)u=EvRwoElF)b;vSmJJvey_*k`Lnbpr2&*eN>QtXNC{H9`C%!YTkH3_tSv2K zflA#~qXhyiSS)M3lvXt$@D*dVkh#?!#@gGy?%w0^qo4e}XRT=WtP*hVJAQl2TK_ME z^2P%Nhgy&&F~?8M^VHeJOFl1z!1ENIlK4vE`wGuf?x+-LmXqZ<&3c4V0;wDcN=iiG z4C3_;o#kc1Y7H+42ET0#taVd%wJLZ@QVl)k*J7;U-mm-m@BQpoKmDOo&ju#XY7}I2 z@B!)h2Rkb(?5$*2qgm_3Jo4oEjW;C(zNZNNq6a=v=o1AVVc-$^9#QBKg+9hu7FN~) zfs&3GC7re?De#rV=#=v(kFzv)0dT|$0ZFEL^1>QYz(g}*|E>l?3J*_w_}h>D$)0Be zlV^>9*S!7re@J-V_bi-0$4r#bYD8#ldElW_Xk*;X6u?KJPc;gtRYGc&fLi2UM`1uE z^b7DE5nSuW1qa~KJr;HLErH9KfF~a@yBydKNW-~=0$fLKnQr`_<7DRxS+#Hxkokf zsa66ip+_b3sf6yl1U(9T0^dbNz9;cLh3Cm48nRR(e-Ne)y8~QG&}l+vb&XnU3@H=_ zXNnqCpU_u~*F)x3VzSI0I8Rc4{L>%4sYSb+9){j|?7m>Cqwb$O`NT{5oi$!}&+Swz z0Sn6=KJ$N%J5v)V!oVZ&9SFYf;VX%!1X4LERzeiOg0*h;O8~|gtOadcxLK=&R3aa1 zhasr37Gn%rTXJL3Mw1)G-lMmHQfO_F67D)Yi6wP}|!~%J)EyBF{KcqD*i;I7-bpDK}RRUgg`vCyXTv%ptxkI%QQjL5n zQ4!&Uioo*-1AhR1DZSRf+Iq~|dP1+CV2!0Q)?#wk3{ka$P(IQRh~tDLPC#cMQ+%C* z)fj8h8bBbVf!v*23%Twycj^RF2W~+M!MWv_YUDFfkGO6BIBUIxb~n2#?&}W#Ke*jE z-83h>?}gv?p)*fB_9B3LUT_QbT1c;-@r6fE6NNstD4IFaO45(TZHH%h{;hkk7XJ3Z<0Pqep=Bj-8W09Vp-)(bmA*$1D7w9r`Q`P( z*;@AO+s)Bi50hKP@_I&^Z>$zrm*Qe6oiF&F7#0G@yRq<$u%bbf(9f zRbF=2A%qZ|I6cq8N|##X?0zNmsfHnyieH4AKGiTF3=|71>#o$`Nh*=gUC+OrTaF&0 zp9_{c85S22jrOuYCr6?sidf$OTKPnc7LgJp{g^`jF}8?AEcCl=g4!5DN>Xj;r#Vy2 z3iZgNm*#ZhR9Wmd&AjA|zkTk3k0slI)wV}LcYf!uysERZawoux?>LB*g4K49C!d}# z5+|3@R>HuUo-)e~1A+qlG&2OgBkJWZe<24B%+iiEE8QIBxyWYVbUo>^-q9|dGZunj zE`T645?$ZL)Si8Wfx`DZf-*|-1fDQ-7S0X`Tv+Whx7tGp!R`Aes8<4n5Qn0m@*CTQ z*R~Uort$l&wd|iAXRKMlT6plWvjl;Q_oL7+k}gHyE0@s*9#N=RSm~1GmM~CMBA=JM zg1LageH?LJ5tdU(p!mVD0GMnU;soFK z2t1d)MuA7*56cOa(4)PckmuSZWxmgycih74^fQm^{#o2og-DeQ0D z^UnJ!H;UP|5)f3pU$nr1JrmTbAzE7=J8_{XtVzPag_^!E34Ncy_Xq-yG}m;xeFEP} z6-wK?AryvQ|A^* zbM5T4^6)%G5V%r9;7b?xdyAc%DENzb*1U(rPG7k+qf+ z7uo>q+f}C)`2Y<+5bwGX+_sGXCFPF*9NF)re}1V$r`N|*Zm^!`9G>zdfmhV-J;mC3 zTmb1x(MJyL#`7fWi9tzK*#3=VE3*T04L8Z zAd1;3uc&5vN(^c>o+rtTCD*2yNRdoVj?rq=@uZ;Lw?iqvB=WPYyvJ%mZ%@H#gLU3KAGfeYt94 zxUg$DCQ+Mki^8fC8^&7J^|IWs+I6L%$wtMgh2@RcV}@>+02^d?0b;!2lEOSUtjEJT zRXO;<>*AkWn~{rxY9%VdL;FFmg<`z&{lWP_hwZTJz1)tm zxaBOOsCgU2H(V=zH$EdKxfKgzje>ynjjP*?84_RyELgAxTl|N$D5a1>0=6ikZA7Q6 z@mp&V)?$SPV{tNMz<`x*mdlPxJi{ZxZj|=RwGqiEg=th+ES9xyiZvD`1&vC;S~~;7 zJ=Y4~jT2z)eh^e#W=I^TBR5+50E0HBAj8ekA|$3*$zY5{YZo;koWu*ZBEVv;7_5MB z3qEo40|oF#*bP!Ih1*d-ur`dAsJ9x==)D)m;z}t5ShpIZpXOA2K{fDLb8p;zt?+F} z6f_G^3Ei^4emb(ab_85)48|I_JS2BZ%`FyfT+ee|gpgw3eEltI{Zef#w5cM|8myEq zq`kEJjF_IQZMOKwGGt(=0d}zDy`N-YEm7zd-?B&E@DuO5rX4riK!6wn2m_}~aWW*p z6q#X({e9Ph-Zq#Zno*!}4lp0Hp5=taVGyhYPGI?70CgK^Uxblj^33bWFj55cY~vUz)RlbW?I* z2P6!DF2d;Y9!z=7+QC=@Pl9!hPFk}YIB~V`ZHEc+%m$}*=|HpGxRuP>kQ_Zm-fve50`e=pX)I)UPOde1o|9#owCL4}Uo07P8gk*{5<&=k$%a_JOt_rs zVUy`8ZT$!nBh8K+OWQqi*c$ZmJJwm02OQQ*53ZaU|&6KAo2Cx<^-CTC@FO>C0I8^?s=uE@-wGqYJGtSYhaJ=)BS4!Z#xKB0O%Iwhe{M&@&TThc=FGLgcf$k-A)W$Q?_Hh%a9WUM zIsGgrPIBTTrJv>`Sw@-}c8%B2#*y@izkYz+XhN~Ie;+v0ktk(@RCB2$Z`Ono++lX> zD0SN9<|kZ5l|k8B35yVJOL#R@Na22~BmiSpzwqH>mu_~uLc8&5C_D_%E^0uH+SW^D zv~lr&zn>8&DM_m7CmC^~Ns<(cVApt!EZ3Z$Tju|K@(U=D4DTlgW1vW|qwMuY&?*JH9gcf0^*7TE{Zk!PJGx|wJFV5-pGo(<=OxDrH@RdhT@TvR1f=7ZBn;hauUFH&L zH(Q&KQPTo;=tK&j-CTj#=wphKbuokC9)u%YpP8s5FwC#C5k($gt@-x@yEwkc&k`*MRbo(j2BqdHV*5d?=U}my`Kyd%(ALSE&`S*A_ z9vS4NfR1iM+92s8#XoDDla?c?f-C_pE(|1|k*Uan4JB-G-Qr8ZWGeyz^NVeYXE=h* z|LR)t+m0wmSZnSFm|J#Qa5(~iqBdmc^;3FDMjU7K;*4IL5GNVkUP7K5c1_f2R71Y} z=qY~xkNyVT?;tUSrjLMI6!V5$Rh0-WSyk=C+%vW7y? z>-LMsCR>ED?Wo~h^Ro3DB|rh8&HJAOICpUcfZ3@QN?!h_vs{zqnr<&)Jx-h!#2LLf zrPt5s#wl?>qZ0Y--c@IAVTIrR@c-h}i4*uD-yqq84S2?4tQnGF9WkZ97+9S`qDQqr zijo9dL|S%3q9-v3>j+R-4$Y2%u$-G)aiOO%Sd)GHQ@?ZUidBKj0eHPl0Ra#YHW#Ai z?zi>&soXa+PPG!yjT2Tomw)ttHg1q^&u-B~F=@ik$x10{)k^&#`%i?k)e^*(V-2UEI3ademEkcs)%Hz=tqhekyP5fBCC~RQsh6gQrrRJhN~~ zygu5raqk*q9ry#QU~p5$qOj|rq@qy|$n%`9KK?Yz%Zt>j5nd3I72xOEkudwG#MY zPWTM%dI|809+cPyGtI4*&=z*W_Ru7?1%3H$)?drK$JHuk;vJ1wGa zEzC|enHsNi`oc07u7#@A{Je^L@Nq^RV=-h)J z{r9V@t2)nf!_Wj>l_|O&0=xqFjYpk%m8qjrh4ojiw0rEC9-|Tkj5n*CzOej^KU&({ zT$6mW(V-=&dfZGCxPJ(;+t+-;aulddq+KJ?S`rysx2>2&ffz<)+9 zxE=yV!QXJ6tUh(Jw&z9PiV*dcc8`O5CaG0JJWnyV)PBYvcIKv>LoSavQgkuBnHNM9 za|@X+w4`vpxv^Md$kT*mz0K;v9KCjjT<0j|BZMq!{O&J0lV^%>w%Ev>YmC+yofCS3 zyN^uaNy~b-&sQFIrpM@Vr)jDS&`aQN3IU?{j<;1ec`E$I%$i@F zytkiZXlL@FVbmuNRkwk zhX(hIYVDSrJpbSXp%Nr%$`>C!O=rD})f%1l+jCER`d!NxPA+u2-P{38}*2psV2Usm@hKst8LmC(#$>IAKWSYb&Sr?S(+>!|J$FRJNbntdc9tjBuUCf@bgXQS8UfqfMf*hk@LZ|JYG$# z(T_yq2VQ3p-ud}es!_mLv&!UHjj8cE3(M=z7#(eH##krOxfyZF!(*udxpAa8ZOKV9 zM_`(lJLGjyQ!3Ug=_2vUGWJeZ*)#5Aji#66eCg4%v^!n2&dBpDSw8u|zqxq){x9|W z{j}TdrVIh!s^1hRTz*eprVBu(?enWDsGbTMGxvb-&M&Ny=bGusCbeqF;k{GjxxQky ze7Pot*ie9TS+H}S&QTZsLu-sNgSVBPtWt&&p$?P z-2aszPvg$w@%#Vn`Nu!|#W;@B_4V}x2Xc0W<1;2>*F%71q=GwgyyU#U{L}@j(+9)W z{(mH-tSql}xjhk{2LJ#Bh)G02RJgdt`?F%beckXlH*#1M(3wQUEjHk~n zk!6~RR*gy}VBhRG2X;>&r6lfWS2Rs->G{T$(B-7PENaOcw;>HC&s;uWEka86OpkHr z(cK)}J4q0DXl*!gdVvQXI>qu@*Ck{c0;$hkeE3g(cKOU>=epf)-0SrcE(Jf`;{2*g z5!bS0@)9h7kYP>eO5F#7@jJf#4V7KD{*Vx|25$fF(F4;Q**`@T4wr*3ul1N;YBRso zW~JTRu$MrJ;xT3$He?i=@{|}7RFZ?!nno>RYP`;5tH#7w4Oy(D*4l7(eud+w=IM0% zgM2_JX;i%U(fKd`!H4?&e!ti2C3&7F1;Cf<<@;Cd+-`&b7_Ep7%MzCWKYAWCXC_)l zU-w;MbM{*;G6YakvUg^TLwhHg8gFb^abhiWd#2Usxv1^5Lp&z!Ujb+fV-0uXPtrUX0^7>2|xN z$w^0!m+RLQS==ZAFszJT0`{ikhI4=Hz|`2`SHCH2?RmXL47Q>~q0ipwF{US*>>95R z)|?J*SZv($wR!JvX?DupFXaHObx++|Tv=y+smPSijmFCfJyr(w(yl=se?zCp;mrBzy z)~L{`SEyBs=ZIE9!q6Y=s2N@FFN8pAWIk#Dw8{aFQa-!V-&`k<4NZMfIGWFrMDPjGZ4;f>!G)=QY z;gyFRxHSRsS~tT9H9v|MYg<2cTgDEw+GZ`dE*pO}z&zWnB;#tp zR}4YloakL?KgQ-hI!Us*UcVIynwtpvXxj+_Hn&kiHiJJZ^;;2ex%Ny1Y(+xOHLPaX zuJ&9YV5?SwKB@tuB@cLJ4JgkI8;Q88iIRV4|DUw~2UxFgg~g1v9RL6T07*qoM6N<$ Ef?oBbI{*Lx literal 0 HcmV?d00001 diff --git a/deluge/data/icons/scalable/apps/deluge.svg b/deluge/data/icons/scalable/apps/deluge.svg new file mode 100644 index 000000000..69e59776d --- /dev/null +++ b/deluge/data/icons/scalable/apps/deluge.svg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deluge/data/pixmaps/deluge.png b/deluge/data/pixmaps/deluge.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc443361f1d9dfb9f228deaea0ddeb402006434 GIT binary patch literal 3420 zcmV-i4WsgjP)!~k5BEM@UEWP7K}FQ|McMRe0l70`0_5tONYb8-)tHko{XyzQcAXO z-87Nq`U&73z+FmT>QcT8;8VZ#XTP>(y!nMIc202e%p%L39upH|q`BF-@1~E%|M}g2 ze&$m8yQBp6f9j8RkBtsLwP$Ku*iSTz?LN727zbN6jk1#Dw_f+*dtZL>dr!S_seE3t z9N52qzZx2D9Guv?c}RHyC+6B{C&`^;cB#)wqS>)+T*l4%Hy--q$9G;Tzn6@F+H%_; z4mX=OkB@I+`c#X|7T|Lv8pW~M4$2GIIzHC)gUXW+KKK{@rSN-62>il>U%E4>);_;` z=OicQR_G)ajUvZGJ4NmknUze>v>6$x(;TVaAa;#Cb}4*+;2ik52fk8|8ntJyx?)?? z4+4&!X@l|+(g*2brNRk?MUq+=q=uanBeZ(ijn~}uOUGXR_Q4-~5dR;z2kemgT63(q zV|291YbV>}B0%Gli?Rps(4sJd$3oXI+ew+)zDam~^yd$J>B*n^KO=C@ryqMDti_+a za@SVgJl$cbo3h;Q(rUG7Ew))&YSSNNXeY2j;iSS!#fgOh2F0$)v6>=!;^UwD*V;xg zY~ihS5klX4 z`J2yG279hpBlkm*g&9Jqh(Lh)Rbgzo&^Z#6roj~|+d zi5hW*A00W#STioI%1EO^Bd*XGsxVXw@I8g^Nun@7DM=LgC@GOrBAlhWw1gK`Sxy|2 zqcI5T3$5g*KQwXw-yc5wj~6uQyC?!>aXroF! zj&Kgzy#&89L}T+78e6tAG&+u7YY>JZp%UN>LJGjK(r%%mA(s1^JzE>Jd->F2WZYXX z9Q^KuV7g!l-S8`4{l)g&%r}4bXRhYhsYSZI0b?UIn)NEfag{JoEG%^iD;4(c+e=S- zB)SNqb8aocm4vjjM87plZ!nhpY1RV)qlfZn^ib-ahnH@_x83s0v+m-+^27ptoyk$0lB# zK22N=sn;shYZ0|-K)ahzuT{A1*6Zn{0?+e^LZ2w~2?LMNSA>B_;45UA1Ja8K$953c zV`>#2m30u#GCk9#8u?6)##@}Ie(`+RE{K3s-WPXGj&Ww8Lzd;#qJV1Er&b9_GlTDW z?7QX)gbJ`i5rrO6aMpsrBk&Y~r|><6=Se(IqWplMIZ3q|QVA5g*TOlNo@sOC46|`uMG93$ z-y;gWwWM7g%vQlmDbB4(LLdZql_C7Gooibb)>>NKgpqm$aQD6!zVpsTjmh`aYZ24O z=Wu{F25k)1Iy@<`&f!Uk@B2h0GiM#fTC@=e1P+HX4k_&#fmPt^2O{T+Kdz*)ypqtU z`8a29e=mIJ?Ez=0SE7I<)i~$KwISE8Bw;bel50a4$aAXB`h-14GOLiwro@bMt~9U~ ztaS|1jL26wXLsz|e<0Wp0b@+n^CY=30Hm2FO>+jB##53k*K}5TguYr!-oR7%N}<+C zW>qd{BlAw+#b|BUo#&Y*@B~<+MwK_dAp$}a{V7Z5xi+lyQ~HApW5F29TgT22dZN(0 zUua$_i4td{E(B}RSOsq10lt{MT8y4HB@2=2|Ok8Lgjly`N+(EcaMk?y=Mv z5Z6L}czBv;zw-iCXJ}JO!W3Y&v1nVg))cuQMFCogqBhBrfWlA;BvomLypPR>7hX{hA2XvwJLPid^DzDYv(FUt?B4wDG zXz=6LkCFA-FaG%79{XyRWd?XxBEKJj5U2vxpt0SqAIK;SG#(_s9*z*qA6tG6+`xI&hn+oQR*bo(j&Bx8_jk~AaF zENN~?Q^O$5=?^lxy&g;R3(TILp|#jzkYp%Nv3FY?qf=fve2m`m!Vlki@!+4%&(3s# zw2XdwF$82u!Uz1Ub8021jkT83@OIzx#D}ljMV=Rl{LVvbTl7e(8Dx1uC@aAC)13Yw zC&@KguIcv&R6WD)v55J_We&eJLw{-R$n=W`A3pW=n+rfv+MsODIo^+eDgUoZGHNh? z#R((BF<0#xXKXm; zM{k_smDi6mSXqAK=<{Fy!pw>3IiLsh*V((0_ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index a67847a92..6150c7764 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -42,6 +42,7 @@ from toolbar import ToolBar from torrentview import TorrentView from torrentdetails import TorrentDetails from preferences import Preferences +from deluge.common import get_logo from deluge.log import LOG as log @@ -53,6 +54,7 @@ class MainWindow: "glade/main_window.glade")) self.window = self.main_glade.get_widget("main_window") + self.window.set_icon(get_logo(32)) # Initialize various components of the gtkui self.menubar = MenuBar(self) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 3f1d0af5d..8252456e2 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -38,6 +38,7 @@ import pkg_resources from deluge.log import LOG as log import deluge.ui.functions as functions +from deluge.common import get_logo class Preferences: def __init__(self, window): @@ -46,6 +47,7 @@ class Preferences: pkg_resources.resource_filename("deluge.ui.gtkui", "glade/preferences_dialog.glade")) self.pref_dialog = self.glade.get_widget("pref_dialog") + self.pref_dialog.set_icon(get_logo(32)) self.treeview = self.glade.get_widget("treeview") self.notebook = self.glade.get_widget("notebook") self.core = functions.get_core() diff --git a/setup.py b/setup.py index 950c8d4be..a6fe39b5c 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ from distutils.command.install import install as _install from distutils.command.install_data import install_data as _install_data import msgfmt + import platform import glob import os @@ -142,7 +143,7 @@ for path in glob.glob('deluge/plugins/*'): os.system("cd " + path + "&& python setup.py bdist_egg -d ..") # Main setup - + setup( name = "deluge", fullname = "Deluge Bittorent Client", @@ -155,10 +156,37 @@ setup( include_package_data = True, package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png", + "data/pixmaps/logo.svg", "plugins/*.egg", "i18n/*.pot", - "i18n/*/LC_MESSAGES/*.mo" - ]}, + "i18n/*/LC_MESSAGES/*.mo"]}, + data_files = [('/usr/share/deluge/icons/scalable/apps', [ + 'deluge/data/icons/scalable/apps/deluge.svg']), + ('/usr/share/deluge/icons/hicolor/128x128/apps', [ + 'deluge/data/icons/hicolor/128x128/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/16x16/apps', [ + 'deluge/data/icons/hicolor/16x16/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/192x192/apps', [ + 'deluge/data/icons/hicolor/192x192/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/22x22/apps', [ + 'deluge/data/icons/hicolor/22x22/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/24x24/apps', [ + 'deluge/data/icons/hicolor/24x24/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/256x256/apps', [ + 'deluge/data/icons/hicolor/256x256/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/32x32/apps', [ + 'deluge/data/icons/hicolor/32x32/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/36x36/apps', [ + 'deluge/data/icons/hicolor/36x36/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/48x48/apps', [ + 'deluge/data/icons/hicolor/48x48/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/64x64/apps', [ + 'deluge/data/icons/hicolor/64x64/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/72x72/apps', [ + 'deluge/data/icons/hicolor/72x72/apps/deluge.png']), + ('/usr/share/deluge/icons/hicolor/96x96/apps', [ + 'deluge/data/icons/hicolor/96x96/apps/deluge.png']), + ('/usr/share/applications', ['deluge.desktop'])], ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(exclude=["plugins"]), From b9ffab249929698c0fba9c0879cf917ad94174ed Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 16 Sep 2007 03:55:32 +0000 Subject: [PATCH 0077/1009] touchup last and remove old files --- deluge.desktop | 12 - deluge/data/pixmaps/deluge128.png | Bin 13952 -> 0 bytes deluge/data/pixmaps/deluge192.png | Bin 24460 -> 0 bytes deluge/data/pixmaps/deluge22.png | Bin 1103 -> 0 bytes deluge/data/pixmaps/deluge256.png | Bin 36758 -> 0 bytes deluge/data/pixmaps/deluge32.png | Bin 1909 -> 0 bytes deluge/data/share/pixmaps/deluge.xpm | 415 --------------------------- setup.py | 3 +- 8 files changed, 2 insertions(+), 428 deletions(-) delete mode 100644 deluge.desktop delete mode 100644 deluge/data/pixmaps/deluge128.png delete mode 100644 deluge/data/pixmaps/deluge192.png delete mode 100644 deluge/data/pixmaps/deluge22.png delete mode 100644 deluge/data/pixmaps/deluge256.png delete mode 100644 deluge/data/pixmaps/deluge32.png delete mode 100644 deluge/data/share/pixmaps/deluge.xpm diff --git a/deluge.desktop b/deluge.desktop deleted file mode 100644 index 85cd5ea1b..000000000 --- a/deluge.desktop +++ /dev/null @@ -1,12 +0,0 @@ -[Desktop Entry] -Version=1.0 -Encoding=UTF-8 -Name=Deluge BitTorrent Client -Comment=Bittorrent client written in PyGTK -Exec=deluge -Icon=deluge.png -Terminal=false -Type=Application -Categories=Application;Network -StartupNotify=true -MimeType=application/x-bittorrent; diff --git a/deluge/data/pixmaps/deluge128.png b/deluge/data/pixmaps/deluge128.png deleted file mode 100644 index 840b96704c148b092bd0ed6aac602e740fe4e397..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13952 zcmV-`Hh;;9P)SE{r-OE_xqjSIYpFG`2X^)D|DxCU?=1aZ+qXZFMQ>FWT)@rT99u!0=)4p|6yos zaOm@Q-*xCe?DV}{3-T>TfPq^1qhaWe*J_nF-}kfcf6Y$c&rZv?6ajwXU;oy>s#VLc zV3fdPlnxG7KKSN${>H(bzOS8>Zy5r-{%!BOd1R>i?F-q_&a17YA7D5V%59Ym#E1fvw?Qt{CK+ot~ePT%8B$+rdp?mYBc z)pGF=B?OgH0TW|`*tdHeLEwWBf`MA)AH3!M|NJj^`aX9;p7R8F{ab(a`-TUr|CA5{ z$|$C%MnMRH=P~S=8imIwD5DsvSKs%qe&*M|eW&mDYRhw)06*{}KQlZ&H2AxWQ33!{ z6C((X)>A@IDHky@J_Je#eBTQPh6X?NlRx<%2X^|tuckbw2{1K1_3>gM7y*EKt%QMk z83fQ9As~ccbf}Jctqe*CiiKdhG+O=S&ffLamFFx0UU%QGymfe>`f>p9eGj|FN8Edh z%WGI&Z-G)xf~m<7grT+!)k^8*Z+Z9c{KAgi_*ItYBmrLYhF`pOY;54Y3V>1y(~~1$ zi~l-&o3yYFfv?6xl{m@f>08tXXYWLgj5Qr&(4DjJs4If1elr}1)&Vo^Vndm`r$YK z^arMQ_~&f9Jm&~-)AhT5t6DDHsFXq>3@|>TeL4l;{OmG1aRQ|Pr3gBSz}eYFC_ zP^*Tc_uqfNx3j-!d*wMrfFJy^pMTZRK<$l60fYd1rpG`Cg;Ky; zqm6~-29zR@ppXjs{CY>92LTAd#P|@3g#r`^isjNBYu>)!-s#`9o${O^z}>HT+t}zx z{dXxPL@I@e@nICg5Q+eakT`vA5fXp`fmDD5AQXWl1m_o4(P~GA1XSk-d>;TorCfT$ zeee0;kL>Wz+ctU55a8ghk&hJ$;jjWhwOYpL@SryBis1CQCB$56@Qt~bia-IH42t07 zxg{iA0wC~wAG>#r0EB^1Myi#{`+xlBK5)xU|Hkc*=L7+M;C27@?e%*7UI_xmJnY>) zVfuAoVWolP^%f*j;3WVFqAh?q=0f4rxkV@dAV8&D#Mnq3pcIU<@<6Tjhi|(7cS<|@ zL$^Vm^#pjuKl!=ahern9t3aR##h%^c&U|+wfivfqp+KQPAPfoYv9VB(6)->_ zKf3^~;6Mi`r11fS0wgFD(Z>oDN)arsw6M}>JEw5(t`P)*4-krCq44T|{+^G%eW!o- zR?4%Q0OJ$Gzga34Z%~9F2m|cdH3mhr_Rh?$pwW&25DhvA1Yk_QCWOo!r`-Z@YIX%t zBA@_JMzL>dR7Vqppj;}x_x1OG^u;^+!?#eLwFG#@KY7ph*K5@`DncPaaoxUMV2l9( z8tn*Y=GU}o2hEs#3PorNy0R7_^#v56kdolU%rc||KnRM(5K|LFI!VM>P%DQYfBnyX zVsu9vV2k8gNr3PC!FNv72de*#Fb|3lOid1>Qr5A%kigN?ivWmA@JIlXSfJsogbfER zF%(jAv?7jEbE}3p1jB-S9pM1!`p+Q!^cOFMQ% z11_wWZ@hj5of}X#3&o5Pq?*U;@AlScq9AP1VA_TDzIC6GLn|jmdTTQk(0AZs5P)MRH z1Jb0B{X6Q8K{R=e&8+A_BSH`cKK4xx0fYjSkz&#N(2u|OGy8YCZ7x@yH3Yc(hkvnL ztJFTmnC~l44A!f9pob7Z5j=Bh39(SA!)N<+M*@~11%Xl&!jgs9@d4uU0;y#nh`7SB zbE}RR1GN&yhpSMapp*?zpZwt~-~7MBoo=Jck!KA7N~Pj&28HlCMJc?%$94NAoM%6? z*uY9N(m)dmNto7jLNX*s&Al+-E0wVbnQ$?^o;ZhbwUuCIu>oCA1Wb+9Q7sps2m$rj z^Cl_4_8^dki2H%}#>jGpo*gyNxgnKFOMO z`<}4?S-{y|VgYVAg9X=4|A_HqNr1J$Bq|$$H zeF3Qmj-6XYlt_ROc%FyrriUDXnD77k55MPuJ9fIwE=8_s0=)P~e~kuV@L}q+0YxZE z#Uc(&k7^Aj6erHDqS@ibN`;9JSswwNP=f( z)*#HNQMDLi@5BH=CZt56`A7_MNom0)4D1wz^Wke5FK;QMx{)hr!WeGcGlG3nLm)&~^E~v_d32%}QM-dAY9nd4Aw&X7h^zM1;On`71cnf% z9Gnm!2nz_yB@{~)l&TeYe(w}P+dj%v5J~7AA%Xo9HH_3kNCj{q@yOA6v?2~E1Y*wr z&BxyPeZP2>o9&Y2nk2wW-uV71q4Z(qc?TF{7^;=<{F|r1C<6h(-#m2&k3Vq~&GmI8 zT!0G+0zGiUC;_7sj8POqAA{8r7^991gb?Tu-yZAlB!r?;ucJOPib5$r>@T(A15l=v zOqp{MH}4%rF<_7Yn(YJ+A6o$D5>g1kx%losc-QxS@v1i7CCar)fH(cl?-N4)F=Y(C z=ix=S??o~65hpR8ID8Uc{_5kK{wzWWs^tO(szp>vp#vV!7QtRaGS?SMWekjsqFNt- zP8KOZ0a7V#31D8Og6~n>x^D=C5-6!Kx6;DlGpmqNf^#_=w{XX&e&KJQ+u|nLGP&jm zaL+&cz&`?^9|oZyjN*9*Coozops}`w<)vl(>4Q&z3wc?e9rzwbhbkDTmRuK+1zzuH zQx7P3et_|*X;cOV4e)?cQg;PPIZIG42e@I^fIbI>BWG7Jv)F`?3Y?1v=T_VIeCd6! z5nIqaTQ1jB6mZW!|8M(2z4wE9Ak4$qKm}!*VDbDpbeau({jn351iqg1LEte8&!Yeo z7M2@WSY8LE(Eb6@U50+u#~vkMghKKJGbfJY?6ITZNen{Z{0IH2eT&sbjG3hdK(#Y@ zV6uj4DbU>xqj!%C6yCROjdq1{%~*hYe&T~32>mi4^gBQ)7$LZ2A^=rdah^Ici^rZh zd(qFy_Vrf73DrJ+R^v;>0F$G2FiKND09h-bV7hq|4G;`XPGMkVRJ#LGK{$UvK}m%h zcMqak^dX_pi8#J-WC3x)!37XW{@Oo!*Y|#6i<|9=<(jbognI8L)cX!F51n=gyNZzm ze`URe$B)ch@b4KV@H`6NW4SHx83Oa_dsH6>9=MR0U08#V(ybUmw8kR4iU?ywPb-7M z;>;PGdipSuD2B2QB+=wjgy5N(RU}-3fFKM!+&HCu2toj#u>bn%_x#C?+uo3uDA$Aq zcj$442zWF1AVBUv88C^U-gLwHoZaFyV-v8#)XZwQBC~;aI z1`J`~p%5?>0#9$DhcNIE2C3%fEP}@n_#QluVRgL&0;T)@Oc?3*DFr3wm{AZVasJpd zSY6VUM=+KEgkZg$;N)CG-y|W}Gg`q=CDh3#N^bE8{lHae*iFig6M#_mJ4%t==T98R z+WaiO{kHusOgq20hGVDabJtT!5Cjax&_}7@qg)D5E`}%<1C&eVwHTmW2v92cC>8W^ zv7oOn1Ret4ZkiCxBWSiFXZEQj0;8ZzTM}kosX6a4f~7Mjv2<=mTMcJj2+l3Hu(%%C z6eDifJ&1zu!PFo8!&klgv;X31xAR7_qg}wue(K|IkidUE`^;fP%?572aT?d{83O=B z3CHIjd;*=A>l-CN;4yecdp)0N@18NOxr~@Z5Y1%)b>4?K$IgPHY)qf|`L#*`#n1=f z@;;E}0x5OaC!`4jrGgL=TuON58g}hFpv%gnf|L@35ZtnF2nC-)N`sHerg+N6%&*Ptp$sXhIHs$MXm+Lt-A(85!vmm#Et5aLuI#GRl1r5{+l zT5aA>c7y;gd+P%Mq3kP5XHVa`cDFRPt z1_PhM^O)9t6XQFRZ5_gl!$(Z{7qM0V*>tXgP+DuH&?Kl;i_ZCz(pUf!>PZ8zkkSAt zA%%pH3WfSGhIZ{X1W}MmVyIHU^}A~NoKkr5)H2R4G$Dk5kn&HsP=Dw1zx@3>W`=iU zG>}sE3+qb@cP^eg0|0pb&3hgAjaGz54xa&|#P!vMfWbH6TVQ~medNx(n} zbYE@=Af-aH6C;W_xDeo6f(vcReWuCedlbIUz$k^3629j_DFs5b#UM&S5d}gOAfy>R z)QKQL00@Ql>H?NMANA335USr;SnHr%@Gw4D0)fH}Qv+CSL|AD`D3HHR2!0O0&+h~d zzz$e|zx9qk{H{)O{r{YL@-d>6!oFR@c>XPW0cfrK;@6(Sdb5MTV+cb{fY1OR2A($k zK85djPWyevoTFyDfKJ4*+UTI!j?j)0m(VE`ix?gmMx|N-VIG+0fifTMC`PN<24swo zDwLQ2q|kjoOsHqO0I5<7V0-}~6u8vnDvwQ}IyjPA0x5CpzG0LL9;B3Lb~wKJ)OkdS zgp^83sb2Z{Uw-xfxvD@IxvB*CuAlzHK_174k3as%m`Dx@#Yw<0XBww&{*ltNf2V9(xZjEs%I4~vKr ziFT~P1B}CGd;{AWJ|o~!8>F)am9)`mcU<4^NFWrrk|_a%fDjU$gkxy;btsgpkW#8HXb>)sNa`rUP|?CU-*^pzi_r|8|12nfl~0doqqa>F_9zyfEV1h z&w;^YkfBUD|)x-n1iax-- zKlKOI^CynIJ!VK`7@_Jvc9tH1HfRdgq!wHxc448d>ZXIc6s_R^by!>p6??J zS!xLa-(~o?5I8ftf+*oG(^o2nc>eQm!!5U5k1+7C*yLF2NKmJ#nT^_PA92StV-a-D zKs%C@076kD!5~KW97MHNbykD2bj%?8IjXacwZ(ZCff7P+X1UdG*b--TV1dOhIts|lh+YrSVdqM40lQoS@y{o0J5 zN%5IcLaO%(!Fobb7}<@X(GmUG<}*BIUkQBkIqk(6#GQ5~Bn6&2y^5IYDyCBCXyP+MeHNF6U2jB(Se`Sy?j` zzzrm|IlxP!7@eBdSxGM)FCb=Q(T1qa`5Dm6V3d#wPo7@Z^9=!v4HPjwTmb+OkO_eN z{?)(*xC#WAzV{u)_0^Rhu*WxMO#fp?&O!)@(B$NO^P1g!*Z*rP5GN8#t1S}_TC-ok zU3WbX)k+Z_qnKTdAr*1p_L%-I^KUbVHk;^%gKg=U9Z|IZOuAW0f)=e{e0s0eeB&V6 zOr;G|DeoX|HgZ;At(oBD?7ACEx?y?%m7;I!6~FssZ++mk+l>uy6$lU(i$5bc4*+0l zd;rx_0RYhI#5i?s8I+o=p2rY))FotcqjpRm6QUP=a`C?j3GcY$X4GqC_>5wHog)%3 zCf{ydrr(->mIB|c`B`!R*#SS|JV#}^h*eR*=+v~11kAm8T_lKC&pRt%{fblb4XiX` zfDlkdaO3nqT9g9(`;dx$9 ziq|uQ@EF6wa#NRVdFgn-zI{^|8LGo)6zxQ!5vdHQY5&}<{Z8v$zn`fka9bJ{5}Inh zt2ZF}JP5F^$`~Ay zm=K~00OP|o4AykHb(C3##ueUl#xG)6K4O=M}al`fdoVL%c zi<~po?U}PI8LWn9_OsgGLx>FE)R6#Y(|vn~)lnZA%X~jgYZ4hnw6^Gc3Nt!^C=ob4 z-*BPg_0t2X&jI}OOMmiXySEddZ6yHnyq~c9H||dz+oPxEAf(je>ZXBQ?Y=qoGg{6l z#d@>juDAL7n-3k-Swcp!(h{cMPm?+x?FKj71<*cV(;m&wRu`qCj7E#IgHGDMBPk4` zQmZ+CAh)RmGHzw+2Q(Q@%{S0V1VDgt!NZ=>3QVU{0YCsMjkP9~%Z^y73pA!fBcVzZ(EYv=rd( ze!pAeQ*EbRnx82?&5S$>38=~#3SpS~AsOofG1Y-o#}@&_atK>Y}HZL z!Hc~Lf^sdBb1=yzrI57rLK7yn0Lz18Gi$E-b`4idGKv7eYhL;<|NA7iLAHee)Azn( zFO<3qfCC=@&d#o&6X}5ytp-vsLeXv~spoA{tb3-%byq+MBB68jdFL$l@ zpTMIS8+3CNeE@IT4y?Az0yy)ZBf#8pdIfZ z_!u0hIn8b-Nc-AMMz6Qe&t&|%$d>l?+554xj5gBz3+=-JD*YO05T@Tr2$DFqt&npD z0pQ$n3*6Kx3|2x^3J&uL06)0ZKy5hzrtf_R18^q*jE~e@I(2So-OcNB8Uv(d_2%!> zv@6hwMXpgPAs87N&|niiY|FDL-+rTf8S~%U*VA5aKNI4)>1A1G4;KL6nSYXV@{(}S zB$7;Kk$U;xoM}HL70xX+)Ak(8S%H7Hbx3L14hwJyKnVaQMpE-{D|`Xu8dT5OL#BaJ zFa#Gl@F^u28Lm5^Vqv`NY;vbx=+~v~&LU*vd)h^Wt}w6P>>`qJ9c>)C9udhtu%^nopXu;q3T;Q4mPX8r(Reret9=e*y{kZX6F zBtpQwYb;5rQ~)#PITl&%&Sc(bR}#q93cAwq4KjUM67*<)ud`=Oy#qe?+w#9hmWDwH z(25mkA*a#QL`v0}sW}gBgHefZEb}fo&rIr3AII9{Axx_D=6I11f1VDRZ0- zm~bv#!&)g73r;H)LEik+H16JLtiK!ltR=`gdD&2JqkcY}XP7tnPKf&tEGVGOY6!;n z!3!?-w0ec5^^U7i9Ih4IJVgMn*c=R-lWikFy;5}Gt*rN%3rUDuZDS+J`3%qvTq=gn zqyuT@uRG`8Z>-;W_dOneAJdP1fajgOoF(Y`ZN0x2@M|MrthWl!tw58Jg|$>+hHJ&_ z1=lVCj05;>05E9k{QzKPt(O4GtfrwmY+a06zOKWY{B15$z(lKbY=Ou6aPd9Di zqc}*!GWfowo2{2@L!XQmrb>bvm|^?W-8m78@Vp6J%-?gpe*T?|BrE#N-l4 z=JSm;QHDT@fEcnVyTlOHB(OkI0zLK10dAeTJm{(D_bw#8@2V`noyE&tVVR(mo7@5g z6hoFJ$iWMK@g>N%5x`Ef27pe)doI)_UFuEd382g|A;itXm1b6M!nqrd_o!aJwO9Lz zYe=PtTiy*4P*&>!fatO=qEx28SDF4?b@lZZ2R-}ydiET+nRha?l%0iFw%ro<8O6X) z$)Z#`jrs~@t9<}RfWX(LD@r)Jr=KYcwn(RDOJQf!Dn|;+togR&0Fp$w^sLV=T`y>fY(<@2#I>FHxC4$U24Xh{M{5V(1}V&_L~b@sIx6 zmdqmB76OF6=R&}g#i`ieVZrA@TLLbnn@cC9X1$%3truoch~n4*mapvTb^2H~}dRR|bt*8rt_K<&8%TsD-g zCcq#7cwS0QvBA22R*cM3Yl&~2osfACN^O#z7nZFiKpRNc)dA8RdAG=n1>iz} z3$x~q(1hSzcLk-;Hx@v@x3s*DMzaG#DSWDODc+t!kM8WAP3~mk`>gis8{XtE&4TMr zsjLF0TJM0(o=;w?*|dgDdpocd@|$TCm1;Oxnhu4s;9DO-$;(?Xxl6W%09gw_FVM2e zth~jAK*9wQV*z4r2_V4;LAe+>KY&Y#qbFuegA*{4Z-;)PdRb@B+IwgA`;0i@R>B54iS!ms>zD z*zO&b`Kyd}r<&ho1(4z?GujWOm!h{KM2`<}?OFgKC89(pf5N~+_o56|TsI)4BS4Y} zBwQjfU?)O=0H~Hj-Bn24Rs8u^AJspAD0t{?i}Ya-nS6dfRaeMqb=vD@-|MHveZb5p zXJ4>$fcIW!8%1{^Qco6D3V=kT*~xVUN~vXRfozWt0HE25+(|nfyq`$p-6vckPBaOk zgd>hQ;)Ekkv{k583Q!8#5=ey;r)F{D^c)m`fb`}XfDN3yj963bjx&9+eLt(kS%)sK z`57>=5#Y*c`NqVN*#lH90TgsSg?mqOD0%E6SZqqRngEWK>#cMEsC*Fu*cG?(RzL%9 zErC=DzzXf^Z<$+*m*DBK31mp{S$bheSG}EkG-=$>n~BZngCD0v~8;$yOS5P06^XfL>BzS zY_YZoNh07eg5iO(a~jXhF5%Dq;$Z*;K1q;=bz}Ukr=8Ve^Z}{IRA0!8?_<(s<~n<8 zx;s*7Nb1HMlm>&C2i6lE0s}fn(r(A5E0R!39ljJ$E+kt@fHPnGl@$O-0OU%*vbBNz z#ICz5gmh-#f*vOvaV*^VLg=AdE|`!};-N>L#>0;v1tma8`j0v~lb$u(JzCtSf4)FJ zFLNO0uiXf2doF#?jxCtXBqXTPL>Q?SK#VzBZ$-{B7%TDArDC&@Y{x2LUjuOc!b&>9 z!Oji1kO(P3B?0FIojB3c#28O&_gFe_V4zw6B?OID1Q6g)zw{7>2ddaTH3q3T68p2d z`eB7rVED*&BqDVV~Q6dn<939ggM6p1_a1s30F}U)W2x=34f-#y$*6_PN%X8I{#)mD6xyYYGhd)P|*c z-6>Oul3EK2xTR$QU~X|8dMw-CnEc#Ufwdh3a7&!;_+*j}y zUwsTAVleQ&fLjn{mKd@NY}p06v>LCifi>YkS}8=7)qI0mZ3JX|m5>I63urQnuf??fD( zSX+W9<|z>(a~$jUIhPn7C}VuM0;Pa2fAvv(@_&9Aacd18(Il{Shq=n5ZVm5Cf|MjM zU5VCWmGB}Lm1p%W))$1xbAm>R30 zRxaSk@w52Q|M(L;@%UjBs16Zr>Dk3ekg*WbYP?3xdgd1R^u^9zQb>CP)kOl8sVXKJ z%OIWKvVWqky9A=OztxG1=~qxnoLqbAvriL3e6rCf+a=0YS^&>%e(U^W57=(th*=N< z0QOCdTySh4E9R-2;6kEpNYH9q0wid65_BStb`%?8aI_=sAXdsD_Dl?-S_<)(UwaG> z{LvTCT$)3`1eBCHBG?>+J66g)9mJYm^U&}ONMQT>T&zpQ-$k5TNO z9@+HgS`s8YB|*ExHTW?{+gOH9#}K0vrxqhl5CjanMyl|Lz~{d36@2bfM^6`!B}!@`tUsZ&_7er76y}!J(QKLgzm!mtFSlm? z`XlXj+qJW4_bZYs{s2M>6Z7ZTNF0HAd2Z(_;eT8PQv3X~L(O5*tG`AqUhLdA{W zYcHRU<2bf$tNy-AXtW|!N+F72 zfLpJd!Z)8jv+3^YmE%2Y}LC?S}g znZb$UCowcUgo&vs6sm(745jBKXb$8p0Nno>G*uefrR}0$DYPGoh; zv!+DU_+zh7`w;+m`0#1>RIuyzjN;7fGL}}G8+>f9XopQe#R^hL-IpsJYJ5065<|3qvAG>9)1#G7@{&Xipt;!DD|L}G(g+5VV(pq z`!-vbjsJ5~2MBWW5DcmyW&?nm_6#BLD3lavwmW#{)cKtD^Um5Q*5^+&I-L&95I|kE?1O`iL{UULosMmH3%n)5WyzIT0O#*ywj2SPXC69Vp1AEper5Dc z0PxMH&R}AshGH0?S}NdqH%#N2F#i@C^ZzxDwHmk6W;_dFy*BJd=*N8xh`j|sD? zm~hV)%B~o~?P_4mJh*`;kYWW(>&uXYf`vsCDs>bpHT^R!3yke2HaSF;d0JT44QIki z-M2_UN{rSEIIwF#&ry>K$4<{tai>nQsoLPia63TRONps<2 zOUM4|@gzw|9LHpReH|BR@2g6HO^N8N#_h!;zfl^y>6ZZD{K6WJotejhX+4qnmg{zb z3yCAA<}Ua-8-BjjlPI|`HH5y0M0g0eFprkwX0>q&#uykOU`&{&jp^qGQ=+GqyGmkE zP@SZQo=lh~eBxvkt@TyN*|VUY53f*0p$K10BS20szGl|+1x zfN>8V6JUw80wljGD6s=McAcWMiibc!C;%~sY__mGKL??Jpin}gTtTr~17*H>BB{+d zN+=;PSPgLfuDb3$gur^UgKr!@17Rk^=qH#ajpd_%_3z?NlXp5D6-ALs^pn;$YU9h6 ztssEf%(i~w!QZG~|6L!n$&0T)b^;!wZXwA#Z{7n!367sxxcnPYdSQi_$M6%1*kkZ{ z=J^9mKae6}48ErqoAAsEo-L_J2;0mSC>8xOBVF^+x1f>$n z^+6OWH3&tZq{N=F3Z{ljI^iS)*4r^2K70m(b4Y3WeZku+$G`sWwewG&isM*v&gI(L znk{|aXhY`plH{@<9GS`f`J3H_Z+p$(Ees!gi~S5r3GV*3gBZ^Y6`!43!9$OqM8f;j z_+5rDO3X6{m?MDaGx#)pCV^eG+;#!B@Ptx5&D@4}b`c4sbZ4tF14vS){k$D#unS;R z2M2N2Z3i(tSb-8c9lhFU;gP4#pwo#UIfvj0B#+~j6A!(2?#P4R;G9RTRx4^W8WHC_ z29R8AQ%YTOqg@FBZ1QK*1OVWV-uaK;5Y#4rI0cB}?mG@*EK@bq>csf^V<)h%oK_WG zsaU5^KbFQmc2+-m-1E|B58D+>VWI?^!6SsYwFF%KK-YJ;S^}B&{X_LK4jr6C=zE}y zp-?JesS)Auu~{TMflLxe!NGYfR!=_ss|!cI{9vckiIOCVS65d%n*|?0d^yRWDu*ZYr5l3WtxM$Km7WFDX}WA+e!f`owWFr_j^R zVRmw9Zh=Zh0F}-yly*44%{@RmegNGq6apVN>>I<#VAT;rNQp!HIKXS)E zd|gIR~i^TBg{Pge* zd;xE>`{iA?AFS@Z`=7F~a$ELuCr9fzbYQZlq&(&VXU{KVW`0Fi9Bs0DaMQ*!8t%+} z_f+zprQ=cyVt%vx+|j`brYDCmGEf1K9ZCjf7uWFEGiT9kN9mBWc?}ppyz=M+zsTcO zE720Kt*u2u2;1jp0pDmpQA$O)0@;$s$M@+9`t5h0%bg#%;d@?E96$6r%7W4CXHSgO zaOgn#py{k|Au+eOhI8|)I5)rQ<|FpGzx?truAhe^fHm!QHPP&hV(SY4fbV%29xP*I zpn}n%3PL~423p{k*PD3i_$=lY*PKsatBnXH32UDEz11Us_Gg^)IIH=b^NqmXhya&u z`d!;>1pxp@f{j4$_u9ShQNL6gy7^T<7z|8*KOr{tq4gXT#<)K+aotk3_!UUpi&G`DupN) z1GjE)-+Y?_n(Y`T&n@88xn(2?&&(MxwFOea_i?&&>MwsYT0DA&a~>O$pBR(h5BQDt z?dz6+4{R|3fS&H)VqkZl&s7ywrti9;IDY8metG0YgnB)7<$=#IF;d5_u>p(?RX40R zOhYyeWG>YU_#itrVhKDI)NgGwI#V9Lak1W-!MZ6{!@O?HH|gg!N_z^D7N3ueKcEKpu3Z z0OqrZBRO2IvG)IFEb%M1&fJU|q4G}{qcoj6^p zwVz~2KpBe}m8bd2vCp|v0ib?)v|ieE`>lRycz<0E3kS#guSBL0|B3PkbERNo|FQ`rIFV!b*^a|Aj zLeX)l5Ol}&+3?O1ftU#%e&9>r_aer~Duq~3ygeh@E5|zvPd^?n9-otX;XY|VcUv6C zTnND%jRxnOXTjcx0R60ijq$e~oU29v_l0@?V6(t)3VfucQr)iv0HJ=-4@>pX3rhtr zDEiC`1LhY3<_7`w!hkZ*N77iLo%uN#HzK5^*9w3ngeW0|eHBtlA%u`Z3-5F~f^#l1 zz)SQJ;6g;WYQS5$>I86Kth2ySrB!Y8p!L)^%+2f5!kTvz&(#Ky+c8mb-2GZ){ED?J8du$E(wreK< z;CUV~8|*R6C~qI|73W-X&SjD$GD#9!q#?VuZW448z#hlYqj)X6jD;Y9myMs#s zKMQ;}3DC8Vu6+dnzVBNSs3b|cD{o}izQpE9;2gZIO5$vVTvG(-7Sm1ivj!Iee^c7u zXJ7T%->3wu8~A=Cuz*Vda;G(YBe|vt&@EjS;zC`(W(crBa<}JoGbHHNcy+A;-Yw56 z0&FB1Vq~pBmJGm!&A*jyE085Zw}$KCglBmJJ|_sUS?RN=f<5kOgl9Xjo0V@V0{nmJ amH!8JLiw(f=*ETs0000UYaMD9{L?&?lf?p~&@mVlR+7mMu|dpC1aXG<0*SL>`ZL1F-a0CJLI8s6C_9bVa% zx|*!7hJJZ&=HdGMp4kPoY!ZoVY}oLDC6lH931Ynn;Yvw~N#Y^}O6tSHFq~gX%#{9k zknE<2LKL|TzG4G7HU_)Hax-#sbI;1rQ?@(qSKRIbgHHU{^_kpT6xPG;c3$yE3owoo)-VsW`Wl@dTU~EgXKH zhlcZ!QBbn=+x)26zc+sAe|zn%>a4LheE*zir*!S%Ebs8JI*`#E_IewuX3nl7=z2(- ztkU83#c;SD2Kt#v)yF1(za239&OH+RUZ^DOOcLs37VX1@Fc*uWm*%ys>9e(SFN#Uhq|m-hF9 ztcZLu*kAN!-goV(-1y(izSHfC(bxZV`;j$h_1u5p!sJ{T6AKZ*DkyCWDR?zH$srNL zbx9A%Zt(t5b6lM#@PP0~KVW@6^MB0`-v67(LZFF2SR3G{1putAD05eTM9DBSivH?o zQ#?$!T~hgQ@1Hd~|FV|P{LvgQPmR_8*B7Mi{^VDM0RyVQF@{1s8f6UM419iDMeJrz zW(-_9a!ecJuC>tpVoRre#rd1wH&Vc%{@;Op(EdKcv#*hC5gI=h{FCp*s$reR;6Lez zCzYyH5780P*opP&+i8*K&t3Nc`cIhii6?*w&aR*F<#9`F0(v{C-XoG|8KJrRPh&hb zvYuzB`3KA`aac+;^nRQCn)8mDz-zzi^?HQX2nz%t>toC6W9IL(%gzLRWS7Y=AAj#W zBo00NjMH13WsT?aOu$%X!ZHt|+Y<`tm{soFp+-Q_zhs*K_TJ_9oz$$m_R_g!kM4CI z4h@Oc1NU@*OP|?7T!$DINO8q7r-E6Mpo3}*_kFlq6S#aGem~dz|DEd&bN`h)Xs7sVa%#L=LL=isBQg`0Duh*3xjpGv%(rJ3U6wnvD!-&WGJe9T{r-c z!*z0cq4{$!%QZgRZFxSedB=K5q<}%uO1t$*b+vpeIZ&h#BP=Wc<%`$Uak0e-aIyIUUD4$jVF<;(4PoHLz{Mt@g%H9- z1xFRA&?$J{CyV?FPw<@`dT&mp^Y(fDYUgsYrv+5oF0w^}V;kQ7 zLM?Wbt&)jE@`)XVLc>(Ov!xUTCP0XRjt1Tg)o}L;#proX4 zNm3I~gee1u^B@oS(M0wT*&Zt=0(M?S04 zrvXz?Q`DyFqYp+esrP};bAq?C^q+vAz+#K_&Hb})TlIa}_=G5H`D{IdsC;XdT+E@m ze5)qg$#3v2l`PT-Yn7>~kK#0B!R`GHg7*c;fMB;V`;OVJhfChq%bnf8y$a3uppkp! z$okTEPD=LUB}&c8@^QGBvR@h@CB%L=4V#Zg2!*1k`3#oguU;ThNa8O+ETwEC(Zb0o zZPndEy5H5&6qxJ*M&h_qF989wT>*Dr*MGO{p&1yy!|*!s9h9R7I}6=@{M+(n9%Mn| zXR7Ja=zCtSo>&#PlKMf#56}`3Y)3LN=*2@cf04L2v5aV#U2$wufsuz1DPfv?c&Xs^ zUxZhi&qlvBX+8mbGOed$c?BJ91HuMnT2GZJ2&VoHh{a};x`BZGv0`?%uG-M~B=I(q zs6uFWj*NW%;*j&d9YNq~2>b_z`^MeJFf#fb)=!h#5PCVpQkIC9D@U~Mu-Xv9vlBssX6RybP49b^Ye2dhS4jpo5%k(ED_oya#H1S z&gm`{!nQCUx0YKi%Ku#}qHx-chc9*R=o7L9LyrQ4DZfERUl@XBraWk{BH>@`6yobMKh1kS%YFtu1`$jeoU#xiKvKO4f0J;=pl?;m8<1($arBZn7AbL#pIeC;8O6aT%y>i>Kqe+S{WQfptxn@D-tw z0K!Vf*A(UZt=#^|jbMw=uOH#WLkuqee$|m#qG+M ztA}rhv#v-uQ~@z}-YI&K38OkgFl@uKikJ?9!Eyml`4^164*P(5|5pMvB$A9=nUX`C z{4a&26=9pfUyeOQQNzXEpGW5t-p+Odt+n5CGK6;g@ilb(sOGk_uZS4LZT9EZZ=jLZ zx##Fo{i}{sk*U?d-mT$)am58bBFi=vn0P1ibNVk3Or|uY^G)x66_8LndI4b_39n*@ z7EqX7*~L@2R|Gp@cgonhc@rDu_J!kUg(nVFwa<0m zjMQssz@dV4lueQi7OriOC5^K3n;o#2q7n<`AiuZlU`h$J98c>nNuFt*u=O%=qz26Az)fXAo3|WL^;!woKJ|O`UmSedUoe zNV_-Qf$kG9;W~iPacbGIy2_ejq-akfwa{fx7{~;PzF3%PKidGeDcdkIN0p^_H#`CA zspPezbOzUmr7J97dku=y5n%tkkqgy9waV41`#)$f!krLTFY1W$<^^u;$ZjjM-nX@X zdj~)8CFpSZ&O@_p{SQ@&7T1u^%bKcWoqYlZrWL_FdqZQjy^mr4Kq(6ZpRU0c#v&y* zRvZSO4rGJ+!=R0VK$)#kZDTN6YG0R2lbHBG+E|(T=eimDYY(~4^MH{@hVdaD)x03^ zMa|~+!-pe@s#37XPlsj>5@F#|yxc{?p62`UQl3)*l%Yk`1f=ZQ z^yun9(YIJywE@n3YD66)CsR95v_Jze(h2FYL>Z>fp{jT*=Gz$~`s?U)%NetiaqAvP zC1%_Bv|KBfUJNrcDD5-cNe&?t&B{Y-uVBDB=_uC#5F8tYaGg5K6Yt%Ica3j9^T1ei zgcj%tryJ}IdLu!dT?5fjD$tfQbiQRenY`^lPc>@jRijf53t0soE|+!#yOs^BZ2|x7 zpy#T6t@2&oMOC8rrV;BS&swB) zMY4)SJdlxTI_;h*r~+4}`8x^cvrQ3^fLYyae=^Z43qiz6@VqBDsA)cPbW(1$ z0$s$tosu@m7vF%4qKL3sg1672K^lz3(A3H*TJMvaZ~rkRZ@Nj{K6S^W-Z?ZZ}?>L?RDLRI=wx#7>#*#^di88aQk?GZqCRlr$Y6tLkeShLQ zR4sVyDrSL|{d?jQqQv&N(360nAU((mvuF3cBZ)dbSFg{GD*O@^B2E$GyeipsYw!)R z*S5BMD?#|Ji9D+LfYaeaYYA|XP41&jA{CAyovkfaE`t|k?wybD=Vg@#JrUB#5iK#W z*YumQ7PBrf!Cv01sy%I0OdoRuf|v@gD|`<6eeUEyp9mu1ug$Q3zJ(XZ<^V91nF~_n zYQSg*RjIZ1KV+;E04T-QKO!6nq4Twv^V*!9%z8lb->D156FeLIU%s%9$=iqso)~R0 z^lB10Bmz8j3fkKBT;{$xe__tcb)nP^7<&)zVJ^0K^SWyzS>>4ZN_%<_j*zbD!hgYU_5 z#R^l(PQ}He9I8@ES^_SZ*FDzvI!+m7s*wP}`++siLB6)M2AdJO&Ld53=rQf{Y#45@ zC@j}^RAZlmk73C+)$^*x#!}{vc!M0k#CQGgr6&}KBdDyg>=h4emLmctJ))9PC~(OY zs|V$?DrZLm+YPWV=knVGMZ_iq|Ks-`(NB&foQm@qCuayt>~HbUggCw`$fDs#GbxhG z>mMt(9dF!Mec7__qTE@^7wvsZj!GzdZHW2grTKY*cMU;qga!{3;fJAY?t27_Z*4s( zuQwPLh6%Q%SyK8SMG_uG70(|NPH4DGi42D>8^SgX0kJVjeJkYqh(`GFn-z{+L-cMq ze{UbQU1;dR(J9RKDFrc>`xtrcjx57OnHV9`N01zLWNDi7@LFK6fl%Smf_iu7q>&&; zhs7hV`6MW&A_#pW^H{=p)(^V>+WY2@$L@<^jvDKNN#)#sS*hG5>-Z~5zD^P=!MuOGvl2#`myKgx1dr^=74pVyN!B{F*CvcmWT3X6Y#M4 zq^QzY1)|EvEvElUjifj6eSsVFBGxW46sI@%Hk;KQlZ;z?ct)bsueE zA5}2Qat4q4Z-Odkv(k2q7>dCW2&kBY{aAp2FVHXu=+xzU!W8XJND|Jxq(tnz;hh&0a9QG62LO85fpKn*BIzOn78 zLV;W2c$Fsg{){BZ)dWjXj249y7JNoYgV3fRfrN>ORr<-!Fns;z zk2KftK~0(9-20>ZxR3)QFj`M@mgxbb+)0&G;uPG^I4hLA*WWL8|NXkg#oxK_aHngZ zq5XXdoIIHr^V+Riwv9%Kl(rX&(<~nHb-t3X2D}hvX2b3$p(J`R%#4WI9)rUqowTtK zXzW0a*y3Aj1ADcNnWFLqU`q;%%&D)!o)^o)E^Dm&$lofEfpNdvJe>C`T~ucuU01Sb zz$RC&j|rhFOVvZo+a;pXD0ANR<1#~AyT`+`yY2%Ns_xz(JfSaD)nXaF$qGs6rDol8 zf72~cPCMN|6E}*`kCgBkpADTYqA|tnC@Io;T2eXdO62O{&`m2)!iCTJj=)9AW5m_r z?$9;$HT^1;xb^gNg$Gkrb%nY6ZYC(HD?0&egoF23ZDVHg58u;;1m@Mo7?O&W-rYF(sFOS35{TrC0ir3Vuw75P`5(QW#38 zHJ_!Uqw6(FBM4zb6O<-~#JU^iz77kqu|>=+A(Q!S1QyAGf!uovCqtnuJFe2m^{Goy zr()=_v>%tkM?%oIgTIG%w-`LteEVT)lBB^SM|dq&3U_X zGQTnr+bifast|*;SI6I~lHdG82&CC$BiLF6fBmftvIzX~jgBf@voZDPz+w=vZ+XgT z4?P#+vldO=7!18H=xna8>{|alhQgy~8xdK8PvM3kEUeO_Qssa#l`<>xPpIq8OQeoy z-LCDgCJ11A6^W|*&_kF?u;+3$Dbr1~nbC5Fc6|(-`B@NuWxS0qy)0A0WyhtAHzO&~ z*`ULtOh(3v=|hxyY0-#PnF#s{7ylkrDfXhMA{M;C7`S$Ah2nFcog^>g-^le-o%E9I2HTivZQpBmeM!GYKN# z+is4{199}!*xo@rm}{%h?if155(mRX9o@kJf2f+Z2&YJ`hlEV)BVMaR9OW3OYxR^b z$Rn+jO2pxM%RL`uu_mX_1s4|aH?-7H!e)M+3-Lt#{9(z z?K#E}jcvqx_zj#nHE5S7grV&3ZcMR?#dNbaZEfzfaJ>|GXdKOMvb?)K=Sex--SD`= zhAoZC`540oZ#SEdA-ZrUvvemy6IzJ7Uc@Ce(MgK~?#m5~JzKL+AP`0jEBE{9aSq6{ z&yO<9L@STZyR2_t|001vARLcnrI&A4h6os2zdtBAQni|aoCn5N`$SL3#oPz}-Qeyi zlOW{V@pc{FWvRygiKu=HJjWstX)>92+)Y7OLnAd+xb*1HzNIbUWC^CdI0oFA%k$1Y zg3BhWNXBW3E1`8Ool;(E43Y3SKoCI;}$@jl~j4W=^UQR14<7A^I{*p_4(fAH-aRH?ZWO-}+9 zS7%ReG>WyuGgEtzeEJt99^(^k;aVixk+C^>C?!NjpC3SQmvEckhTZeT4OXMT2)yr6 z4w$C?_bW}YS{B23;8^5If%49Y@w#F1uMBBvSKvKcuKynk70-^Ojp@8DWBTM0MjFi9 z=_EllqFg#P&}GkbFQXc(+@n4U76 z+LA^<_Y0aL7DwZbntqV^CTD}{*&M3yO-fn%UqU~czO1H34|^5@;DdLsGd2EebnE-TI1?iicUjjsrtCJz9XZG zIaaG$e266uJmd3!9~KsT2PQzB3^5)Ks(^@sXd0qhpzmV^7FRD)KuoriASBLftP`av zE~mynqHH-K6)h$#)3XTZXx)W>y}qa{(TuQ)9{-B!<-ga``3beM8q&Ju8^+!rSkv0< zx;pa*LvPnQ_99mQ9&QK&z#tWhYwgoiL?A@QlE9Uce=NMq7Mibv+AdhZn4bOYl9Ty% z2M_(Mzn3M1iCD!njZBqTLc~EoX@_JP$M1t1OCOFyg569Hj`~PQzgF#HaZxM;5<^BB zBMM7|AUPLMVwj$Vvt!B$g(66pyTD(c^r68!icuL?yVGwxQe{xD&pO;*&^78O9X z>V30Qe?+6ALu-D!UOi)mS zH7)EtaH$*748!^k+~-%!IhCnfmElaNN4s3-3X^iY)0wBEP2FsxYJG;HnwP2Ze|uiW zN!#&8?7%|_Z=ioo@>PY*d0R_qU8hwG#;wPukFp46@@QDM$jfu6P7SR|=3Vcv5QeR! zr{kvCIz0B`1m1IJ^SQIIp>DMX8=oLFxPU^VjUj3q z%mGrQo)NOngqNdGb(%@yA~w;5fm;NpF^e*m9mC=GgIKPqJZe}siJNt+Yv^Oq8|`tP zRZdTvxJCdOS(AAa0#7te(w~r|k3_F86ZBl)Mi8#cldm%J9;*n1oOM&oaCTyV?)S*% z#E12AeX|+qWSq@!*)o7Nz`GuZGt?)p@+yKs`e(9?U-^-$##0Lv%&8dKe)r6RZ(_1? z4gQr8y!DDq0@T*e^{#9Y6GvZ5^BB?xcfJxwL`44YI@ENVWbB&5UNU&ksrgRpI@!Qx z-+r*NhI|#6>KCYILSgvLv6`?5-(`f!#Dob|ut@wJTsSGu?!P)pmnvU<)(t<_J=$jf zL-T7nx~O&|a}D*w(4W#ItFdQ9`Yx7m|2>h;ZK<4!#g;MyEOx;BrapYS6ugT7b^7V! zm35Vut>14xVBN#tFhsta{I0WvL%}Xta9c@3k1__D@ns5$6{z3U%uLJT!*@NF$JU%| z8K@^jRQ|jk-xs#b!2q&wwQ9`?r0LO?G14OzyI>z2MZ2^r>kww-RNCGu2;}` z+WuTs6LDO<{8cYVQ60ymHP@^-a1WiAqh%)u`+B4#VyQ$8q(K=ycLYw|vr24KV}(y_ zo*MFjHI4Mf`mMrZIoqHB^}01T@oyQ$C6Z5;i>{^|_Y=nJI{Q}V?xUic)3RJqM+NWb1eoI z6_*}%X(~=7Pk43q(Z7c0e?!b&@nQqSCFtGUUs84KL2`8%O@{O4Cf+&&Y$GoUPjg(G z7sQj{^CmV`Bc=hCSkU>f%w!K^+ahlZ-$kBMo_pu*YT*aJIqx9jk(i+Sar$pUp5 z^`}DJajd@2Q%6&3302>2lSc zHu(AV_Sw;YM8ryW_JSiI8>PNj!$mxZNQaL)F?N6aL)J4}<33Y!r zdKzolOL>dvpmH4Q`nA?OPo7!NEP(O|nb!##bBx3*{I{YPh~JB-+RNVZGeZ5kqCC1T zjh$oo`R=@*Gxri#gOOU&Afp@SD-yAxYJbI2-n4T@Gx!s(p+UTOxztVh9YxeZc&d%+c0}|-j5#cR`{q3 z4WP^KFRnR`r9O;FfX}1Ht8Z0lNLCFw#*i$%%l&+UsAxGvtK}rtytLR&R7>jqU))*9lr3O-9{U{43z<4h$f`K}N z1dBVzQ?VHQl51qqhzpq3u}s81*B<{0h(bv^McCjycPtiUOcpuWlqlhGo4o(%~hz8v_Y&k+Sm8}N4CS*B`?q41*qCB2cE zB~I|-0Eww;Mssa#ilmFY`VKyuB3^SkQhb7UUOTWD_gz4$YtH>S#Et@%D$Tu0rZsc7 zP?;yhAuj8k5eG-dF$tgqb(-yGYQT;%`YY#W83PwLMs{D1U*Hmpz;2_I()UJ@XJ_S( z{iE%fU%Ubq_1ne_H!}lsTB++BH3?{UW)PWxy|lTDjim1o>A9FJtI@i3NA@e?PWEl0b}LMpAJh@* zZj3*8^(l6vN%FsR+)AUO-BmDQ(H0RoyOzyb-7w)Up`u~f70WoIiO})f+yDAfJ+9yvb3VxoF4>=FYnz#vS9|Nt-b@XEF%1>X zCkX3m-`*GYPnESfw$KNgyybNLdl~iZ7+=5ZrEOHtV_o;++Nj5xui|U1*)b~rp0xWM z9Jvt|<(8dkEXFBU)wfTUPbvNLu!Ppc!9liEHs!Y>YcmjrJVKx5XshnMm&v|=s1#rN zEJ2^SH`&b61N^@7@My<~*!?A{@u6?xBC&%uEhPjlr0kgOT$M{JiDwS{83Q9MGM@Fn z$tlH(Cg1sTDFcy7`<6qUtwo6M;-1~$-rr_eDDD>T%k8T9%fYAzrjLU!dc`psW+5bq z+QX?mV;>ZTn^&hWBJ(XXgi6BN0`$jswMa#L&-$57tQj z%TsxzsS?OL#ZsG1T+~E_L9ZPWcYg|oa*DfcT8IXXP)BLt z#6KsDRUG@It#hu#<;tJ@!liqR`xJOq&8h5PD^-6Ausw$G`uj1;-DP@E{>E_&cA|kj z6Y&RIX~6EatZw$hIIw+5FtYZ{pt3F2(JB8VWkN$;o@{Ah0r=f3(D`;FW`j!8)VcIH ztXJc}IWj=@@C|HTFdB3cK8$B+`>jc_jx=1?U>tM4e_~!6g)ZUr`b3rT8dbLeUDde0 z%s?W18fY$gp@ zPuMg&{NWmYK|^%-5XgGa0Adby>C>g^2V}EzjnUL1Qjysr%-H)Cd5xTGF|5OGCCyqX zz|>#1pR5NT9zaf|Gu8_@BMq-sS-V$=)kNr%vd!V;1+rg{iDCk94o**9&ig{3g6DgJ z66TwY!va3+QJ|H}MQ)~Q8qn+0uNcCzl+a{mXkObpRc$&2Z#lw2{}QxsE{9<%a~w;~ z@Kh^*v*L5#DSDdtWi|-!mQ=`GSImLS<2mdDokpp^REtO+cwTij5gFPIv4fPR?98ZT z9+Pjt(3UQI2o9NXDVUY9dC^70&q}>Xi~EKn5Yo*LZgQ$iDp(MSe>be4!;W1&uhL5~ ziUqjLeT6Y#bHM$#Stj*4Jk~&Dt%8=E9pOLNjTYAEErT!zMgZ_G?BZZIkSHTWfI}zc zkJid88=@b#l$lHUGwk4<95(3b#KmP(H7Qw`0nhK62ZMouVdC$a}>iVy< zi555>#ht#k@9`>6raU1#YEH4s#O`=_6nQTA#A9gBBHoM!&=_AtxB%n#EvM_;gvqwW zMCc;IFrppa({fU1sFrqI8RfGx$ma<4sqlxWBUAE*p}#+vD*8pgJFiB04NLjEo-DD` z4M!_E+=f7<-)GkcQ4+670<-cwi@h3Ubc4KiCqSqLTMdFLH*NY3;Fm%irO6Jz?c5e~ zNz5u(4qMo|$Xiz5F_BKd*IUy)IcX9mkI~FTlFPORLHv4*<=~AN8HgwL_8! zSQQH%P##(KJa5kW&$!iGBzyc`(Jzp*F}mLFI1bj9Ce-{N;Cx@rp8W3aa;LBkQy(r% z%lOO9jwmQzYD_zpcMU`=u{DAUs;uVp$3we~$M-K4r4c04?Ij5biI7B1?85)#r;Amx zhRx8*Tuf_h)TuN2Dh5-Up}tp-e~BM0(1IT76lb%)CZ57V(;gU~z0jKQyl>K*U!Oub zq)nVxGTe$+SQ<8e@n%BO>?=NXX<#I?pMyM4$=n8#?2c-elH)0T@TVs;Id@+>PU4%M zm{b;;p{W$LH)C5)uo38u2t4HL&5E+oRt6HJqRfX)6*U*g~0~-_%h^jK-bQvQT3VjNLCCsX~vJvOAAlt26%lnvGoW!^{Kx z46U|^N%gAL%e8?B`M*87BsgW-zHyl1Rg@VV(40^hUwmZ`+rWc{Yz%6BC&@>n*#YOT zYG4dy*2Pg8Klc}Jr`I18fJiOYsSL|90K_=6N(t zc2gf8Ts_={K-Tjw?H(1jvYC&eiNepR-+AY}tgih4eMlAJHulcP@wJG1It%oO`qKZv z{Q(d~0xs>`F6#{e%!$1ATPGU&5+&9p4|s2K=~9W-Of2b1N-M)(F2~X?`cRoZSLy?!%bQ{UTyw zJ`4SmWiA@VJs0Py-Qvi(vt9!NPr)iZb{y{t@0pishj@hiC%%dA|K~dBDQGp&^r+sa zeiumh@A*IjED)$h@x%L8h-DPg?rnIjn8wde`S+{gUI_fR+-l-5z9f8_+iv`rqi&9~ghy8mJ&0>h-(H1e2=p!5NCF zM#+o;Vd({-U3UOQ0FLd35V)e+(zH1G){596i{xkldy9L&lsSu4A05~^7(XzndGVGm z5ju7fE@?6uaq%veRjL~u@ER(OkpGxyy{e#4(2RyY1GaQP%1?SqrXAQH4}N-(-?@If zYZkV7{;+d<4RK~zO*)PQT%KpX-WdHdvXMjAI`m?fj>i5AiszXARde_s5sR&EXuRD95CpP3^ZyP1o^|?*MYnrUDNCd#Q)1E z`eNf&k!aUi)AFHD{KN+{KX)*@e%~(OlkTgMQFqJhTHsJsi)v|{=!Q^@rtejh94+ex z^K(zFPb5&Fc_3}T;B^Cr1zRe5{TCMoJ#6LX7EYr5F0uOfjUAdN_}$gOZczW|6 zZ$#j`ZF5Q-Y})<#&~cs#L-8iuxf2-g-Rr<3<)y2lw9m~;FoGy+ejh3|m+M7nt;B%` z)*K%!LaYYbE1$Yg5w&YJ;WuJ=BXkA-iLgBENZJ&gUP_w0eA<2eq#J~xA&f}kd>u62 zeP8v>51%Bb18VwuC-lfF*fQEa|L)l7qedeMf|x+r*YF=AhR2Rch9@E2-of3Rv7(b3 z%t=-7-P8Y4E;sdR)n%jA8&1_Th^YY*>WNS8k(dkl>6P@bdbY&QrYK-Q6}O;f4rk(9 zDgY}$-(h_#*Md!Le7Ss#EI{um8Y-ak#u?hJYt_&(in0*EZ{N|w(}p3cRSuUs;T>mI z7B`Tky-rhci;qA5JKfYjzkDd-#Aetoq6H7Q`WIvs*cabq+gp6wPB+#PGZBP_i5dV* z*je7e>@y#V2C?$-CQIpdL==lW@`x17woGU3aSG#xb+4qWC4MhAUvT7CZL;CyTaIlUg+s z1D}`&VzI~oM;tS>s(1rnifV@*BHz>=`rkgQ80?;T@ zE$l(N=>STop8l`k(&5H;A>! zbCx71hVJ#>AF52^c}MZEZisf%)jZ^;U~mZQT?&!%28~% zq>JqkeGVS061v`g+?mc3lS(TP2VrL4T06$9@o$?auik?{!2en^K5Fa=b@*vdWyE^z z#KC0J6{2nPrR)sv`;Sz!RY`$#)J?c?pT2X$IlaxQD}eWbv14^JU)vfLx_J6VluS2Tgps!P4DQ ze!U9bal)O<)^dQGGdgE{u3ffjQ;ko(kFZtlc;ZZKic1O?&jdc5n_jut-Lc%NZ@e*m zmu-3566)@=Y3XNTPwT!WBQLXh>-oKdeUYr`ga|fU5fOAzIa~w0&jt@UIC`>i={;Jr=rOd!Snh7=aU`hM3y`U8b{1E&n89s z=&b6Q=R@-;F1W%FG}+cO43m(*aj2y)a~4hm#eAmF4STBK9w-29x>YL`oW+NON7E4p zzzbB%Ev-U#XJbT&*4qB8UaT)wq)b6Nuu#dulmc8%KZRDpmOeD#F7HK&SGgG(5KF63 zV{ebM!uBi~{0iItSYuKOPRYcUJ0*(!jZ#LFpiVBo#3B7yoNUuH1-Z3htQ2IJ@+6;{ z#LoJPu;lt;`YLD3?{i#ah+vCJ#0rVq^4niQL?!DSTRv(bSU33`*f8SA0S{MD$ANPD zQHtJktY1~Gxwhh+?ybA09PEyo`sR+y)M99$wK*B!C%V(B4V#{j{p-{ zYB$h^$nB#QNixbALXhim5f}JE&C=tFYuLtCc?XmRONE{wA2#$LNew4ElaNs4^>Sk6 zZAX!H!j_z=u;-!x6({{MQo`=%aPf&BoZOkZ&IYZ7J}z0&e_3h}7Rs3t;h>qrj3+S8 z(ir(~nHl4=BS&DX2#*g7$h&YRCFtSA+~RN(!cuC_--}Wz+J`iqA4!b_c5aui{`Ck; zRJd}t(;RQ_K7e2sNkpoUoF>Qj_dW9(8vD?BzFghQ={J8*tBpB>`^~}EO{%G^m)n#` z*Gt0k$#FPNXg_k@XR#l3$rZpwIaNA1Ap?#`kqd%>szE!NUIj#{xRc6#C?X_03-o>-m%Cin>F@7x zc>X%(6CDq^oQq2fubG$MJRaq0k53kO{T|5w+Y&Xbg#-vk2W&oC$Ac8CWat|*PIQ&g z9dSRYxTfs05^S5a;hypGcE#p5ryx$ko4*oc7Ekh3nkM zJp|>Jw&1O+l$}m)E;>9duBUz~=(L!U4|RoVd=K*5z2Rs505nUXCZ?fv<}6uPisUB? ze;X`PF))@DB4A-cl%#?1h2moGnw=Djub%9Q%PZtez&K?f(}2y;i|P$u96l?%4_#N} zYcDh=R`-RY`xh&@c@ADAc8vz1>|;%Q(Wt~x<_)^?U7NA`1JC+2jLG- z_-N2=#Bol1-O4~8e4>%p$8+JZ!4yuCQ;xq{pl)H4(z{)nXV;R@I9j%tY$D>1a2z>! zLya;Q*0S100E-QNJPT=-a8*i5Fk)1OL!m&RQ;q*Q3pwlsXmghOA!bcFz@J}c?fd@L zV>c)H$ddWU81PXEE0m+B%u?xcHs&0(Y?CM)DR=+0oQ!~2vpUIOM7hWSEF@J62p#HX zt^2x&mNR#OLu%JVz2#ZC3BxdX&<6!K5J~ySvoh<3YRmv4E-N=t#7J)5-A}jcn&aP5 z-0NH-_D3+tSe$I=ic7nX!WaL%h;lFN8U99y(@pcG2wLlg(eXW8k8138S=U_3drT#J zbK18^Q)y;^uLTZvKx0b5`S14pVO^R;%W@?Mj<`>Vg3hiIC(_tQH5OGSXEy2PI~_JS zRC>f|+WiT;VjfPZ#3)(H>yxl5Wr#Nl;4UfZyQnZDyOYTsHjrtKnf)A+6QpE&eVhZ< zB&zhZluwWdK^ZlO?fAM?m18QQnA*n(I1WcWn*jj13+S)t9pP53Z; z$TOR&i@J1)0%Mbq@6Rnm)wW5O;Jq|tsia>FaFSRk2MM4*1%C!+A3*?YJm>AlVQzDt zqmyC*-jRK(m7spw)Sf&Qk;-qwCLM~Zqb?jw<_VWKW0Pe2VAQzFaqG7x0mqumQZ4`$ z?rIbTjIoi{VNiby9N@xUyMdqrb5=fFpwwdS_{#`1O^$}wYz z^hN}smUmfrP(&lfvZ>*GMI=I%r%^N zL|91ZYG#Jfvr zc`<}*Hin@<{SxrXiluiOcl8#$f63h1vZ8sHDe{?Ht;LT}+eke3G2H1t2Om`62Rd7) z2ID?FG{0F}JzJ|hSy+*i6vxIXQGT3?E{02`0)B427b`MB!Te~avO`H>QMUsSWX4WX~JqciHf32M; z1brkExo`1usoy07fVnvdz&(fReKYJRnZq6J`!B#^5nOm6x|s0y?N2+pK{AoAw_lA1 z9({wy7(aX-!(Wbj6rFwErY|bJdgHX_ z4He%YdRMrsD{qG6T@F^(Hu{E}f$pO|zyyXh(TcQPG}ZPvPd6VG!#z0aCU6@GHeNBW zzfeZG8wwHlN&q;6v_xjk+W!L!7eh6Fv3Yxh|6z)S5g)u?9)8O0X*oH2I@1&88;^X& zrHUBTa6ZC$H!BKE=4Ffr9i)L@ekRvz0_peKR{-7dqtCdefM2izW7BhS=2oMiiYRzn zVihW>zjt8V`M^zDROZM3oq$h&H-~kO?A^5sB4~Ud={E8;5o){AO+5DwdaoI^n=~d z!h+F_Aj6H{K35!6aiIu>7=5t>$Fj$wdLo$o??{w&TTx{>|$F$Mb2-5G2 zRpmo~wK@LDeu`MnmfHMaDX{@-)Tiz*3oVk&U*R$Se8;cpN861`iOF?8VL8JmQ$DS% zUADoJe{NkhiP|0p|9ch#x}6s=fCYH2P0#v1x2r!U{Wnh2Nl<%3tC&<0fsNFtMvBrH zc8g78M2AO(I#bTWXknM!b=Qrx5-=XhM&2dKyfc!YqNKkJqnS7Kqi`%$ZDmlct)8D4 zoY1vj>Dzt!^0gbJQ|wt>x^jlfQbyfTILIK_pnp>f6xsD4_`L+z-VBmA!>Ego$@f}GkieWs53Jb47@Mnoeol2MCS=6qX7<;gHN?J{=$lpvo{-xbWVGEOX`gfoH8jBRfPuhpqf+el@3=o!sWkxz zlX3{NP(l#%ppElz%J?1+e^A$aobw@-c#IKt$Z?FEF3|c3X4o!!xA`>HM6H#SY>=*H zbzha@3beKKY0*tw^CSAbHZv)QH6M4&f8B*n1_BTRPT=RV@n7X|jpOQk!aQSrA^J^^ z7#EOBW{HjMu9Ibkz7nV6SNH5hdS}#IFVp;%a)$AnSLufEgn%RCcUVJV3%b?V!cN2d zhfiu+;HvpQJ8n#TB?UclgcUGy3O^O?;Nq|R#&xGPJuK;c2P zE>K%!$7CT}q>oi2n(@WreqpH5Y1ACMU&9e`s5&4ax3CS@!`^Q)!f}NIN+8%|I#3yU z#@N>0LjY}OE{4YV;4Uw${IjrVMX45B_auR<-f?{z#6G-A@AJ6&j!ohvu7#!n>2Q9% z%*YpK<<2hvKo0-E{{_(8h4*9J5WolMYjZO7;forpBt|jW6R)-jZhBb7n|)-9FWc;&&&hZTVKj?f(nI9z5ZC zf{%RSbGYYgkANUiq6AELyziuxfx_l3cL0A+N-(@)s=t&*4p zh}6gEbParZ;|0S(44|*C6`IVAcmOe+f26-^;{r&DEX}a6xMsP(xd)PRO-lKh3;N&} zAzKCjA|ffWFZa!Tzie%M6W*?q(L?K!>Nk7@g4*CpIkQjj(qRE!t~`>M*yYnL;-xC$YK9ss}q zRRB_c`U@XE*gk~xC6+A#01+_&T%^sf_}fp-x5okdCI>J0Izdif2yOL+jsa3@{gakJ zVqyXf4&x>Kc%fV@VE1SZ-Bq0>5)xne>O=U*CqIX!m35R^ikQ+cXw6w_KOhD=DEHYw z;q6nWFluK`Dm`_Tu9d1siQp-|Wu0Y*Ij_gxiAk;G;XIss#(KfYqeK!o zJ-cF_0(RU=DM1jQ)OvAQ<+5MUC_jK`;ek(nMM`Na#OjS!+srU~#s^S{IWAO8EMNlJ zR+{S6Xoe)wAZR&ilq8-v??Zo&FaPHu z#7I$MsTzw2&Qb^TW+TP7!R(nqp~U;E`88l{xKnfkG%`tk4>YZOSaX^|ocS1I8`q6y7Ma<@+v}b$NOR z1Z%a)fyCu~)Om>j>2>3j+7sCGl7E~5h~e-8!~h9JZ}yJ$BJzU{2|Rjy)_OrQA4f_^ zA-;(DdzY7&mpK4_IYDsQE|gb(^GlYeKj@b`c5(srX4p39`rX6l)z~aA1_TBOi5C=G z5Tq%RM01zc0)L+IN z77G!>_)u>U=&se9I@N7xpy>%FfOPdw>h(HrHk(#2E*A(c%SA|OFVnvO0CV?#_#r7W z+c-{2iANrv363unV@!_JF6thngzX69gTV0pM8^fKmI{2ECR5ZBNHVjW*;v73kP;m{ zmrF4wM{5`v=tjgCzIy+o_{)#{10J~lVN`g60uw)h{&FR+aO`M)vR^G@a22^=!th^`7GM1((6GF5Z$x97_%i#eSIw(CnFsvN?!Y8DZ zwuM=m2|PA68=R!Cr-H%0u8Y1IONTTNnu${CW-CQANfijZHblxR=_GLn~6M=$BgHIK_Hf7`b8lV4wvX)~wP@@PtE0?D;s?f= z5H97JW(ovJ3rVYmW~+r}qU#D$2b83xL7~CZ(%`WBbY>|@G6*42Efp~_(u?7~DvlnV z!ry-KpK$Lz_aIrGLy`NVS-UvFaC>7dmFnauk(Z;FmcNJZ#}9)83S_BM7(D0Mjx-I{ zEY382lD-E~cd&b?8?~yM0iJZMv$kHx>DguYDNkelTlK|1ZZ@09vW%^*tmy^&z*^dcrl+&H>D)e>&LZX92KcPfF!! zxTG-XjP*`qfc1A1LLiGcgy0a7Qdlk|gis?(aK;b`2Ej_NKX-+R8 zpsl~@2q#p{vKqfkYQ+|M*)#zWsFow_AMdl?$OQWj9671ZnzR0?Oi!#n{<%*IA(#+? zNhysk7&Q9Jl8B_tTf|L}OF1;634?q^-w+h@_y6tZ$8LYs3^8se(Ei#(Qy3lWMZ}!} zIIw3JQ>PZrc_#U@NhTD}kRZsGw!jgGfKu`(l4>H3NDYytyaq!SaYRyikVtTFE|3W| znMNc8I57|>a3a-8QqL>E83Pd^jv~~0A}}H>&Ys4J<5TGF=|OF12z^7t$Wp*qdf^3` zPE7$%?{lIno?zPQ=pz}a8FM^O0PP8ae+{n}`f45!4c)N;tyCbpY4@NDw)MUv$7iv; z+OP&iIO+qjw(w{5<>gEW!5WPQWcsSgIf{_n>cZ z5X55`z49%rQ%Zl@RX@x%8f4sqg9W@LSPSiR%%xvFt<#oYO6ir8*gw{bYB@49mqS8L zbpF`%9E8YV)1D^tleLa4KK$8FNGT~v5=bdoy10j?^&J=bA?MR7<#N{}}FjWO~!?SqKS95>itlGxd@X zOwTXCl}F$lGLqmzDL&^68E5Km#=w~X=M0=nFcNxZd4ZG|7y}px#B~#5=;775+Rm5& zh*01Jw3OXa1&WG@N>$Vb22re3Y*4Ij+*o_3=r$>1*PYie{e6W25CNp)3Ce+s z0Aw0;(iQjkyue^@36n!TPQd|WS%&)_IbjuEb$t|o5D+3u*QOshm?UehBuPjJ!J5rx z)@U@K^}r}S<=08`piTSexO5(X&hX1CzulKCO|2(OQ|~VJ?fw=2WM8@OaeULWZwjc; z4SPp0b$U^Ct=lA#5^7E{2*{QmCn!?ZKg%QpXUHOB`8hH!5OIc#b3|M#K4ajS1n~?J zGBp2q8Tgx!z{2zf=BNR}nYw8FchaTmM6kokpXXIO^tAdNtSP z-nV?>o>GdjPgqpE z#XskDyN2)-j|u^bBK@Dc524=Jcuuhke+Jk|@Cp$}6mi6yA&NL6&JafooO48+z`0U% z&KV-E>5id2-ZFu}xckdUgMu;jb(tG&8k!#2Mr$T7DYbS;iDoJhmntY!tLW+-0OQ=& zCqM`GW|~+j!e$jJ6Q&e8oG(H3gEkqmEYlMXW&%=VDjv!*bXQ_Laqobpj*8byDRF9k z8DD#73L;G*rGU&*WLX9&GNg^Q`)BU`$WN`WuO~^8Bw3cF>+9?3>gs9&rP$_|ZnP|= z+*AN;x(A?eFwCPVO@TvgR7r8f#dYISCdO5ml-vRk~5Gc3Ce#fXkX{Q5hn? zsfA@MHiM(6Aao$%GCp197*E_c2u^^^gw67-tT*uBqbC8O<^xuMQI=n(S!3zg-M`Un zHbtr?C8s1wkR(Z7A8?lCO_fWX1-hW+i03@* z3HawPKZK;Usm1|-M~hN5^t#|28CPJ)1Vbhi^+|Ocz=@D@3C;k{SvajKV~8RGF#!$+ z&PmTTrl=5+@Mf7|f%SJ4Qv}z#S*J9GqA4P2pw(E%+Wags5DKL-O66{pdwLNCvvIoo zkR?ohC7h-*rYVM$nSK!hl_JM2`-Z?x{eVtItu+$-=L5&lN;5kv52Tr)Q=+~+{YR^( z9-eBoT1eAWW?6<-tA!*<{Foi*xV&ky=@iIo`p}8?oC__A0PtdMcmKeX{_6*b@t|Yy zU|$!W^9|R7=O)ouSYF3JfBB)dCKQ`3jFF-_hVF2N*rPz4Gh2p{-li=)=Q=yYxSMW? zySaz$1bliMpn_vp1#9|e`3)eHnAW6bhizS|DGiGYD0g+ETVhDN4OAsVLki5pv0)ikgg2YH1 zAO{2+hG)vo1@yZR9XoVYor&1DeQ8R(END$lqh!&tS(Jdh*vc)orl`4wW3Ye3H5Gqt`pwj25roI{GR0#*nP?sjgjUi$YxX>BcS zNGZAJdEE0nztidPAP6`{1U+IO;81Pdqa?u4Hldl!+%Ny$_o$OQoxIM%Yz@Ek`BOb* z(kmOgc;|y_V;>2OnBC+#VGO{KG$OZQNN2f*|lbj|(9J*L8WP z(-}qkKhOj?5co~I^2D#4ubh1KZwMio#HH}~{3KpF|3uF{v|S$;e!hs!#9~|HlrYO1 zI>5H=A4Z9^>BCq!GLUc|wD5EBh+u1erafS4B0^C$rv|v0j1B-q6987~G|IllM+1HU zvELwN7eO+)JPOqsiW47#@I_ucLF zU%dU@-Md$A`@SD&^C#e(^G2g#M}780g8QBTKokZ&lu-ZxtDpPzFBfLdzL{1Ud+M=i zeDU1lJ@=rL!lkRr_;~T|q5r-?Vl+2p1k8ZjVbLg&aYAj+qLBdlNFy@{CQ+t=PmSiM z_M~-P02jFj1X9=Qgd;+jwUWxz@x=cZWsa)11Y4;vDOf6G@yZKlQJl;VW>74Z!5G8FP7{}|uE6*GaP$ydJKFll19$!EPyT%O*87(OEkX$4d7kgOt^xn3 zpieV_4%N0D7Xb)H2mQ$J&A#-zf1D{z{aV^J=cgy|!ugY_%PD#R$9o^$!1DS|%3qF8 zjM)-~J2l{0sL`22K@s{4#F#`RCIY31cB3~Srqm?Jq6m@(IN}YK2~{ zuarW$n8TS2QV4o`14WOL?R&uK<}e$Wrwx;H&Y>_s8I?5 z;^kCgyWepro__oo7G@`+FN+BR1YF?#E4Q$`wgXBTCZ}r1W}OiHf`=wh;59d|zx%ga zi$A@n!5^7EtyU{YF?;qxgt26Sf$e2`UdP>oi3o!57pNq>Q!tak(XEsi-m{8f1pR29+|3@ z@bYt~P|Rmj?@cO&8%rCwvbfT-OXXomQv1iCM!Asg0(_`2m-5l4OYtr)h@(TcEW zLx(-n7wMuPl6LOA)wuef|272tqk?`I_{Wyj96JGkfu_*DK%e%S{MN5h6XobHLQ1S{HL<+5gXOjDJ^Ko__8w&a434;nFeFwQ{uAa`N%oz~@5Z_R1zcSzLh| za9t6auJs8ZGh+J(JD30EAGF3d&aTkTZs2*|FhK8#2;;K*ZMi1|03v^2P-JLipbvY$ zcIJ&=EzCUgJJiVzTup0kx{9-><}g(oeZ3*q3$U@%!fvyJX4}JV%SE&0cGVswft(~j z(g#SsmmCGsG(Q1QtrRd*D`R@HjC!@yQyd6bfCr+4SF`Qm#?l6EEpH+aG8Oo?)~6)6 z<=y!2zw)-1)|FCRN-1=FUl^?~gh+x|;s8Xr7vKl(Apw9?5*%3T_v<4plc}G5#3pA;YlguYUMF zclqK6TI&lbr7?Mg5Q4j|%PsKl2lPjx`HkFD0s#9WLBG$YtumKvQ<^E{YiHkhBVRxH z8-zL&!|rdoUdH^)1Qup$kr}b)-w3U#HISu}dV+m8N|;SMlvy-aJDkSxrj(+rf$p%- z8HT0#yBmE(Kh}=#wlz_2J})py3#UvlHlz} zfRTxDFToES0Re!%KEWVT3<7&!@Y_e0uNEhs`tnzEwZ~qkPCmVGC?SAs8Aw~R54ZwjI^me~eN;y>B_P4M7Gr#lBJ3x<4a<=JZW z)N5bLRu{fRGx^?{>LHTPI;fQLD3|i66!WN*a;TK@DCM(J9N#Wq=#Ji`lR*DTDiM62 zqt)@y>iGKTVPmTWpLbggHsHI?yR<#)FtK}~bK^(C9614i9+POlfFDGFG!k^Z3NvRGigV{*$&_bbrrFXNa)1R{DJ3Bx%6Sy>*=UAX zIPrx=v&0-4&l00%ITaGlCFr;T+8tkm-9@YIkCdFVA7|3!fr89r$SuD4i|>1PF8oY} zB4TCV1$@Cd=bq;Y&bbZTegxPnA&$i4Ny>de03(Uf;6P?hpY}+vO!?SE`NWGaW~&RY zuw3Q*pyb+s-_mXO4~Xe^hc{!pr#s_2+I5rKpE9<~ROjPhYw>OG?)yKdN(6&|ZyK)a z8sOW&?$_Q6Ar5_F`$6QsB7hOAd}6<5PCwA6^^<{pgwo=%XCKc`o;vH4rp`F|>RG}v z^P@m=FC+**pLBXHCtws&0Aa*+h`b&&mwzJeesDp0TRTDsMF=sVmqG|h2r=jvdYO_W z@NJ;muR#PD1pXs8chcm(B!CfJLUB-SKWqd@5`d(%iQRTe)8*3Kb7!3L^l6qYFHom2 zPcylBLY=OvyU4Z&;+b3n5?khP^vAl+d;T|oWDebRs9I6Ny}{e-m;I%SA1c4mkm0Jq zQigF?C8ZPyJ0h*Gon1i)k*@1X&bh7e2Z4SN5&99}$ei3^a$gew=<*MSI<*7N4-Nb& z?datc>V@3Y>EoHw%mT|-k5QJbFqW-ki-kI9rc6LfgwlcpD^jW$Koyiylu#Ac1?jEq z2$hV{z;T#IiE2Ue4eqyAz1G%}Y;CO)Z}YBb-d-hy=tVcf_46&wZq9`vfdM^$Fn!Cp z_bva-*`$`4>Ff#NzzpsIBkpvMA0{UTq-xv5{De;tZFvcij42&_tSh$6p zIoIzKLWrVzs9=nN#QU5o{XbBMr~bn3)8LOtz&VddVBXhSUIjs5w7m`bQ3)^#`0sn* z8+l*|V2kA}?-%&v0DqDINFEtu)N#T&%ZxGNI8Fq9eFOmL{xtBL^DaUNAtGXEEgu2j zXnM}M41ypH><1-5mz{sV+4#fcfh2&DL^AED!9SD$rtLV683kYnVaQ;tBru?VAhdm&JkSKNB{3?D7U3B`_`86gY#Y$q;Ll_-_5)G6 zk3fUl^czA1L7)IA19S`UNw6Q31a=<>ARjbspC%6t0c^3zFo*z$3I0*Ee_A-l?u#CT z2E5(Zb+kZk`i8ysN`ii)zyoLE43meF0JbE|q<)}3DELPyy_AnU+esk=K`aA;p-sBMh#wZq9qOLyYX%8Xldj#t7$s+002ovPDHLkV1fx?1kV5f diff --git a/deluge/data/pixmaps/deluge22.png b/deluge/data/pixmaps/deluge22.png deleted file mode 100644 index 29319b3f38e02265d74a2bfe065ec1aec9ce5320..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103 zcmV-V1hD&wP)T z(@Th3RUF6h@44@rG zl)7=DBDDBeNO57e;$tCg!4ccB_F>IrrcFD|qe&){C+{g132U$r;6+&{3C_rO2HPdvLbJF-0UUaP$zZd^6| z*sD9GyU$>;h zDY4@zs>RckOGOI9Bl$$k*#~UuTSfkXS4NUCYpiskMj;!=HciHFT8ZaHNyQ`ho=YrZ z6OBfR7sl}PF0+++3InmH@7;XxmcEJoc=9=27f7XuIu5yPiu6F5b^s{og_3rs2S6_p zr5Q`3Nr4bxn)*|~D-6_1ecTw<<;Ynxc6j-hKfi43}~ zQEPVbqxOS+6E~WzNXl~vLP;+SsMcC!5)NIZ)9D5Pbb0~0rU3#4NU7-b0yGt-`gYt4 zpx5n$ePK{P3LyX}U93@WcKGJo zBNU581Q1F|5Qg-EkZuqXh9OESQW2eMrT$&tDmu;ikF}xXUenM;ryFqMOc~oW2*abK z(@7FT!z}bPfd`Fu z!8q}Vi|M!ox!-ej>iIRIV~t zZ32++910m9FX|w4K|JE(CwgCf!?-JL^x z^ZwTQ=AS!vt$Xh|JI=HBe)fK+`9hV1kbw{Y0FtLql(Yc=g#8Nwpm^AeiEE(^_JZT4 z@bo1V`}2odMPl#i-IWd8b)0P6z06&$0WU8vf!B`qZkFcG)&fqhwrP9P3;+NJo+`<| z^!~Eb^A$9_`&D{7CgSkf0pA+pS-$-onQ8fB zyI(WG@+R{}cK?{v{w;+<7MILsUmnkn0urI&I~q^KB?D|c)etZz5In&>S-I{dsa@#H zxcokFcfHc_z8o{VzW4go#`b34SsV*s!hRH@(Q>310tslLFd<(7uK$HKXJj~#ZPo+H zsKzyM*^7>l)lo(|a^9P~NOY zyZh?%)&qAsoNmvFeBR2`ABz`5VL5u%mL!(dVE2C9lKt5v}ap>uFZM)0PV-d z#XK*Pp%V?d1F0<1IItD()|OKhCWNPb_bpL`cU4x%f(0hT&QN+1aN^-~*JLj^iyVzV z=8*yFpetO+%M5!PuAccGi|?#JcR0Ql|B4xN&}Ax9IeX?n;UFODD9=er6EV<%Y+8=- zi=y2a+3fR4U;%cV|I>7}pUpR9|9bV~+H@#4%`qK)8Gs{rpsEA~ho=^4RiK{c10rxu z{*|*NE-pDg^V_c(Uo5O}5Aazm*@$x%MOZPW%bbM=Q#MKkZdO^2kh;6Nca z%EV-eGO%C5=#NWBetX_CtPB3P|E=lt-U9m2~iOT1aLiz#~kX2jc@q7bU zcX4g*+qBPgDzIGIlXMeQ}%icYM`DLrhv9|duCTFmEZJtZ*lUS@)U;{2NPQ8v8gg= z8akSN%NvO6v+fU3ya_rVO`eaJp$$T647Z#BW)ZzaL3_J`sTp)Wi71f+1;og@gqTRA z!i&w^xHsjezSeIki|rXHh(l4DYAruZJK9Ho&MIdrQA2U$QLl7z-t-DVBGGgZ1UL1O z{tKkkss8#d*?!;SM6Bq*f1>wz?K5ENVn*$&9{>qakE37m<(F&Ou>E@D;ki8TQ40vLrzu#An-LHciJw4NPY0=>lqlbwG)cnN#jVqlw z9d|PDYt0B|ZvL7itp+a?Zg~hoDT;vRS}0wZ9yb^M3zw|Uict=ISIJ9w*=S^&FVh=Z zpS^UH9CZ1-#c5Mj!}AMP%Zvlh1p^088}cI--hMr7H@a#=%MvGH7M%k=n=(i7*|Tj+)27_Uatr|eDm4dnkvHd?X`DtIuI+%pJyQ!g%^-Vf=CIEjT0@}rcf@$=} zD}QQGbNdg>ljiz!u zCREM{szsO0BQw5AknyO zi-=8~7T=Lfjm%7`vH0ryGQgzGf3Pqdtr2r=C>9@9d;t$vV2geCo2@9ts50@eQ&J1r;|V}QT-?2JeOBRDZe4e$JIV8Z z(hR-vvElE>$Jv9Nmf`$vRXJmp4L%}kbPc#bI_=sD7F z&p#3qJ8>wU726e)c^qvbyNwDa1yQUePhia#A+@5fMvQ}F0W5`tNtyQ^hEY3=8*6g8 z4C7NBKUR+>R?}-uStlhvp*6jk_LIGQwti)Ly0Y^GyYY9k7x7*)_HX;*SSJrP5qtlNQg@xsQ&X!l<*sjH z{EflZSPq-Yr?rJ!w9+T4SNZ{CsOUjawOb-GC zB%a*&LLh3~+(GYBR=q}SO~C9-pN&&i4B{zlj z)HF5h3USig%1%VZiMJz30|5bLHGeak8oFB};gOd#hCoEDVD?nZmqjKDnn}{DLs3Xs z&3Xk*X0Zp0{X_tVz(HXQzMVemPs_R8AtdL8Zk;Z?)!AW-s_)W^%vh4LiJj^GH~OLcS}2=qMh%;nf>x#k zeK5~0|Mt%qz!3VEue-MSsD*_kaUNaGXa7M|4;@ZcDho2NYx_ApDc7?g5y;rjq=3Ly zlJPnScqL!A=hZOK{2gp>=im$#f>!!{+v);EKZ_rdKL30Z&nlgHUe8s4HTf4?pR;H4 zTJ$Uo7ikK@Bf#3_Er_VX%kw2qbNRIbj76pj^}@;?Bu%98`6`}%pOtKHGnjez7{j9= zm$~1K^AA@^^+n0o@ePUfS^;?^TWnEflrs5-tz2vJ^L8--OmQ+(xIERH78-evz=jFEDhMvG>x6N_d+ohYgj@2trF*cl1;CB zZ9#i&@qQHcYVgQ<`qML)*-)^1!-XU#W7UFwXXsCmQ0BM>EeRqoheu9qXy*U761Vu> zYy(opgxULDOwzW#hbxGfzKS6(XF5s?HFh`V5bD{))vf5Pn{B3uv zx0C2zU)V>iCb@jhM&{iv&GPOzj>6KLYlf^uCq-olnIDSwYv2syvdBIZEDdbb>~(`M z!4M*Ri^lE|qkcSy%=_+^xW1Sz6-uroF?Bt_IVv<5@XP=9M$N?rr3hn7BPma%PrJIP zZ`qc4oQTcJrF-2>ulCe~c+OLswX?Ys7RU_lH#&H#5AUjq)#U&2idak1viB_6e<8Bv z=yoe+Bv>jDZIM=GYMoa)vuJQqSJe{|sw6}mveE;VfU44tSDM;0;MO{TxL2wt9|^Bi z<9n2b`Jb(}9PN#??3pZJG8`Vr01{-MhQjw5OA<2I40Wujxq9%lcxdx1px8iTW}OpL zA4XCa5~4n~P}6T!)KWRhsme+;CQo}&h%z@>`mjLy=;#+S*J5x?%~dRmv3&8+gI=g# z^PBp}k8QH+lY5ADNC-d|JqTIjd&H` zq33gI_2nMdh4;!Yi$R8g%=ZoBUwXAviVqwQhAY55Y7MUyw`nFBK$OoSduSSCqpjW9 z(IGv_>Gr|(Eb^hLm4s?x(dV^9ut+UgWPjkH^11(4EW+YqqtF*cpW%@vue$P=*W3QG z8Una9_ZI5hE;C%>mM5N_#`Sw1Y)}}Hi@$DvnK`f3ANt7athxW--++mW{2{l3@O$TXb2a+KP_ ze@@*Kuwbm*R}lVRfi&wY&nf2YY7Kc`{K$!QfQ9Ls#wOu@HH4FT=G_B8=ze$it6$Xb zu9oX@_thm6!=(fH>r#e7Cq)etC%R|dC|##^VjGWSkiO^j*AIL@31H2q z>bg9zaCha3-0aCt{B(1|F}3K-%2 zzJ)JmwqcFu&eUE?crqH+hsq`*j2$lhIv>TKRfu7W)D&11ek5Yn7QN#ooE*?2IDZ_* z`k1xlxyFB$H&d@0^7hsqi_k5!;n{?~hHA{=Y-jr{_>1E!@jTlB8^6Hpq;{-x)hpjx zUJ!H-yp_rN1<&ShdPm^B7eYxj*1uDMzEG72%1JjE`yk(Y(aE8j-1gQN+>J!FD%Fs! zYRifl3xlGA71+)B9%?H^%bJ1xm- zm-fp^hA?x7y687~3-mAS6N8$@+n3^twS8XYohQ(svM==dZcNG&34Q%8Cq@6A8iF z%#h5S%Q$4LujIKXND-43*-L5!9T4U9M#)G5tByrWxw{NO6Sv`@x@RU>bjy@* z!!N9|;~2%N_ui~ph0amxp3oABIjf9MKPVX&uKoTRY?J>@19OCjLzPvRwWxPjf09WB z^EL#za+1|V?OYwqN+n52X_nv^!0`yT_9il<4_}OMy%^JL2F(T<#OsJN-A>49Pye&A)x#ivC=EiCLI(bz(#M6rl zPEkaqBw&B};j1N)G8b)D$`ElO#sVYa4D`^5S5{4(EK{4I;44+2v*)^0rhS+KLj}5U z!OO6P+nI*p%~)>&Y+9EQI(zX=j=Ktek*3Z$==;Z)k1%j*ko)K|y3)T4S5B-YChs)= zrKtf5&aOjkcXNG@&-st-^nJk0t|engA3{3Onr#?fvG_^PXr~KemV+sSmj7OuC})n_ z>+a~$|83`1@G6CB#S96G{~JiQ@x38#2xmWNQwG_5`1H<9=IIl`g)6Jj$q2Wp!Nh!c z#MGy$k8Kp3;h3SHdYbI*HaQ`xF{KJ9xTv{rW8;_aA;R?BRMD-i2po4w@~@tZFs3hV z&{NPlEI0&=*A1?KBVd#J@OXL7WQZk=|3lA0-3@LTp04ktAv3?DcFV`DKQC17<*PxJ z#Kd4O?dTl7I$uv{G*C-Na8F2dH1^b&h>!6BDs;$BD5=ldEi+(qM(%EG0880eQ7w=L*PmxU9{bE( zWE$NX$)hMxYE$r6Va)OCU%Rh!4qyMgQ8;*#uF4X=>GnxiM_cR6n^9PSGhUe!i4e$C zOc~n zb>HP*$tyxbVzL$ab`gBGmF*tEOqR}@SixY`Vd)4HRM<|^VfH@^(0Kav1|=hmrG{;} z#hcc0h?Rme2||qJiPc+ZV?oh$Hr>2+{+#eW$f zRjW-!)g0*|=*oLg7!zOl_^xNVU3Je>Yn&LGnE>deHbK`BaK%dZnBoR*dyX> zTafL`gZ3&g3Dv|P>3`(rGJ%^Ji2k9!yW^|f4z@b!Pm3lGr?0z^-V?C_r_tDBn~N<} zhBY0p{>t4vps85+9W_EH+HcBh<XF=UeL|+IwXt2@ zv%x;A3a&QmVYB;Oj#9iNo)T)UX($$Px47D}Mn{mAn+4X8f_mGO388h1d_y!Xx9`ud zC)*wfmT(nd>DVvm*%PGfSqS{fw%fqY-8c-`3T6Qy6^@;sz&|5jDA ze8T0&o)25HJW@EKS(~Na!7kXd01R&0!`!X#&zuRxEBn|%A%A(>)oETQ@7>D$*rBa- zGH>>98b+%jX)p!&gZ%et?%}Wt9HfU36+W85QE>X3cuZ>Ev#ICXItsKBo+ciGD>ubW9hx!-={@utkJuZhTS$jN883U>8Q z4|uucCXuaWhw`s?aj}n9G}eX2tHzq$u7H~Z7A)7Vucp(X@A=ZaR}Q4tX!L&XDRqBx z9Ugj*_zor%kkjnH7wtL~IQ#m&M9=88H0it-YYeL|Q>o3LjRqb=?Y$Tj9$A$&C|{+u zTlgd+VqLsa&t9G*RBFgunAT#U69sv3W-=w@zxsaR#;e8Gaet@lfefBAgX^*V-M@AD zlh?M18ms|4z(y8VA-9qUzeQe++tiSYNT_E{-2iPGXsI%R7 z(1~5|)}Md?elFwI`X>$(BdoLtq->{d177+csN3KZ=6gubhA}J&Z`?anj$s8w-V!K{xKJajQHO6>GphXP7`KZH%S(MQ zWhD~_r%tlojtbRP}K_yx^&kt5Zk*RxBC_$r^O-*kwi&xcV`%p=_kun_hg>%r&8fW^sJIvvsMt zT)sP4K+gJQjUar5QKg}kUg$};NV>=(o>JW@M{FCzn)1fri&T>4ZKT`vwim0^cK-++ zn-D8K$|{--nljxD6S4Wxu=_zTw4*0kv%FS)HM?7+n+o!)+EZ$=Z4AG1SMn8iE#&mn zL;9=A7?N08!SQ~R~P@40!2CSh`;PnTj&(7WK!IKkjdj$T1{ckZ9f4J z%x|@zGiKYEF6)LdKzk;~uJ@)#DHt@7b?yQE( zxmfV&CI}HVdmXN!2R&hi?Yi>FMYc+j_fK|O$3u!R!w*XSW$p*8JWXH7JEtng>nv(* zWwGM(MfO??%bMU38BE9?qENJpZVa=FEbv5f08d3lSX!7sTa=?i%f0No;eK~w__l<+ zEaT$z)Hf@p7r5ECxAT_I3{1+OO~`NXKYQgXfQW)s#-b+$ zI(&48TPh7ZwhntLyuN<NI|+@pDQ)t`F-PVWvw0}zM)3(f`u%IGFJ9uJ38OVL zi4ymU>mxNXtC?Bp_7!b!tJkIXtH5?iz{wh9g%h#c?~{x};B=rRqLCI1Uz3YK0h9=MA}-UDtG(~Oq0`9V<=Zc9!Qd_gU}p5rqpw;s=k^`?wM{#q^g#M{ z5h4+IA+)1Zpm#a65u`&4Z;@|pH-fkgJlYOKgpmQEY#rDWyXsLW&ib0BgU*p|W5oX= zIwEY*R*OcAoo78c2wzG53y4Vh=B%_P@t2J?<;`&~fq)4L+KF4epGH@iP<+X2hJRs3%~l_!_EwyvMEnkR&R zDGU1lSpY-)qkky)Kp-w`t-|k)8Y*J0o;vsa>w)Xdlsc+lhee4_h$|eRMEGy?^2P|h z{g#y_WCq6(JMuXzY4O=6+LtS3U86|IAi}oW&S90daYvU@>7+4C7qVN7+r8Eb(#xOc ziW&&m4unz7`>Z?Zmb(>}K zOfO=&_4gyG5ZL3z`%nz$05^3NuPN{(IT*+)|GkLxURG^RW%<5|Z9xEwN#X%(K1w@| zyT`T0Z<%Bc0Had|VDZY{=4~qv=Yrpggx>fYV-_S|^YD;yo=vZ{qUf0T%LVOv#ri$} zs#MLuh-{p9*!{z2s^1hk(_!1X6BD(l#c|>kcn*Z3)HC#hQHPaG-qDO!tfUQBn|usv zZ1>Ycayp1X7KX6nWlHL0WGigxAY`GVd+VFKlZCwO9Qf6*d#JxgMA$g&9<5nLDv zDns|5@_8(7w)f-RjpUp-t6A*(>K!Hc4)U~~?@J``z%;cT;1pe{G%B90(JuMw2j z6yID`hG15d`?-bQlBW|ruGWPuktPD$r{@vZTex0?LiNf8tM0?5qdp7wg?OZfg&)d> z^lucpaMZb(=t!2}^AIfdZxZqb82UM^OTV`#!3M}rn|u<^Ndw)-NqJYX3##<1Cih?X}pRSq+R zQ_E=XTD2m0T@gngBtX?_f?SNhD?!iW(|KC68z7E)s-U;Adj<)el1_2BUQOyst7gP(-IpHpo%Xc)}^sR%5u@pG7e>nK82Ew-8$e%=X72RiWlet{E;{@R0n>CnW2<2tH2VQUlpG#rte zPyOf8<5{}L;)oUGAtOrLv-DR?nSB<_V;17j zm)-%-KhrCT@Y;bxv^wSMWe4|Km^yofaAXXgY{`nJhA7B()-;5V)zlL!(oo&3=MWhf zIT@{Gwc%ZFoS3bpvicnp#d`VN6L?&21zH?mxM=?i1Q02OVaqC2mPX`@>e7|10Lr>I z`$}8QsxSDL)9x+{C-MU&>ac{E2!y!uWcrddep*c7Gefn$+i@F~=^q~Qc?5;JbB6@z zBS|U{EU|8<3?kdwcD{1jeU|&)Q*L|@q2H&O79@otRGwvs{yQ^3U0fMwEZhYfJ2jmS z5{Y34XYT!P_X?y+cWRww_K&tCj&3k8kDX)9(gf!LQ2~v-zEc|_B&Hi;qr|XCdxWEI z=>MvhvJlSHH(_DF&1$DiMg_~aDB$$SoS9Oy475We?w&?o(~Fx`@b9mB z+r=O%`^cz20N1Jm!Q3vR$}eC>B{vLqmqi-Dg?12$2jBKdX&22&{mL7sO{!ubPgGiE zk{7GqiG<@5uEj17UrxMvFpKGx{@VLPHHrX6xsO@E&V=kAIQ`1wNw%RuP2xvRdZc?& zs{$k;vgr}L?ARB_r{zUWE6habCRh|v`anlo- z=zT>DTH&Wq?rWm*iBkFFUHT)iFkj}n^YCyI4H5DI;#sKj|7l|G$1))mOaJN(cDvX> zxoNBG94vqrD!&2F*v^^2f3Jct%eI)x>36Pu!l;?RbtDNo73#uqtqBFGn$$1@-_t;tLldIYNyBC`FM>7mBK7s!yg{9JkM%(4a68^rbWk=>W`!^-Qwy$c%Jr@3ckZisk z+8_Aolb4mP<3K6$sr;XI)KCh^t#GX;691Nt4c<#1=5Y|4Tb#Ao?6Z!dgyAd#=ofxm zI{sqKswysMgT-MjrTRAkUZQeY+Kaaqrs73$ zq^zh8d>NoEf~ph>X12O=hV&OQ1ZDd)17>BYitqE-{QJ2&S#oQbsuv6=)ZUZqpD|gw zQmzgqA?3S`*RX%FK;znAX5o890eS$g((=3 z^!d6;FHlFjn7;uByb0Mxz;#klnufmYe_4e%E={^7)L42a2#T_Q?s)_Dhi?H*wbJ=^ zmC|<~e2Iqi7}Fy;wwzy|&H}I0`#R#-Xv2HgtaL5J7};s@sv5=M2q7ht(RFoL3RU_u zp-@8pPyG_m-CCle!FyvVK;mzh34jK(0P0T&M-`ig#-+6c9l!$;JMD!v*$HWe% z5Uo%Ct~a;<+cY@gN1`L=WP=Z1IhHn+;>Vp{ScS8@3254XuQYVBsQ%q)^Xhrh>y&z3 z&zqrOAWQ*dE*9b-7NX9{*Ya0{PBG`VQmLNe+v*sVrkFTT7_oU)f}#N9*SLYn67;sL zM3LmO4fp+;M6~a;i#Z|sdsw)tK`W7F8Gxts|bybZ#u74#^}IneD}9aXBZE4;0J?d zsneLTfysQQ@AGCDm$0(FI?uB_ifFV>pwx(&HawznkZG6THczp;M&S+ z{((0>rS$8yS~xonyK4R1&A-i7ZqxCB-TGH8^B3n#YXNxD6m}l$#QXK&yyj1YEI-lm zi~}2dR?c%7Y($z@M(|dS#Tl~9&VCg>noJlBj9(AaxWkY&+MSk2drK(^_|l@t1M2>~ zG4)t!4e5IueGoFS`&2g&s5?h1^7ISh&)}$&cAKkT-I>$T-LEA^(dUU*AJMfem;;}; zM2k<*Ps^XaZ~1B3^G|I^U4kM-OlDnJ|6NdP@4!MMh5)r&ENqQ5xJMJD9 zSr#~8PCP{LKnjYKP6*B@!X$SQu=DdsGBh<>iSt#RGEn5*;amDab$z_TwuG28IK#*Q zilRG6nA7z?ZlkoTd2CjNhlAL;|EmXdJ;=jS^j=Ni4cTzNRkyDN z#v@t7@@~g&t2vex+1a#!B^U!k5$kJ^PaJy+Ydk?&)AYxx|6Z%KMxt>MS(dbd;I}bhg=fl+Xnp|kLHyIzq`0^=8NuD}A(AFdF z(cFP2f1Dzvr^0m|18t90NVK4AaQrj6w~wm>5-Y&I+Z65lnZ*g_{^VEeg0X6Z5|%_`>js^zZry{vCPqsPZyWfDNp zzh~k0rZR*~&gr#);gyW!SZrF^Lgvo}o2GX`7i;HjxauD@IEl%HdiN3e*CJcSd_gdY zMAWwv@dFc($ZPp>_FuWUeF+yC1|}U_DjlRpzrX6&bdk~J+vW>3*y2q&3~Nc6<@|2* zV z8U7EPiY9-_RiJ+Li)nyKpCYtiuk>pi35n*<&HkRX@q^yOnz|!J=)*AB$Q5QG;P-uU zvN}7^;+fa=ZnfT;M&PnD;pvDC`1+u_Tx>tzOBss3!|Xx~Ov|5sF5mFywCX;JFgH88 z4V)fH0@;hV0RXu{Z^D9L;;UGgl`oTwaLEKiit%h5#7aF|ql)*VaClMb)4{z|d4V*y zGS*24&kF7Bi}KxhHKpmV`$j1=f8z-9(%3C-px_&y@Utt5HH{3Vu_aErbv&}iwXIn; z`;}_t)Sm~aN{EQbX*b*@@Vs7y`xhZ`fqFNS0g0n2Kx0OHhg!KFxMYnTv~+1QKl4B> zqG127%J25x$A3R$kAE+Qr)iAzyZfk0n(OE{t=piT$QoCEY-C#8mY$*$f~5@=RrLCF z2`Wf&e(K3N2Lvr2&k)Ggj8evvtyG5TIyn3oj8xE{wa8prVl(kMU7b$}019hylEi21 zl))$lnYu8#IZuNy=I02Tc%iw_*Rc|?(7+M^wisM74a-u;rS#*qD-$p?50oGN2MLBh zmf(WkV}v{Y2@YP7COA@%km&1Es|V=tCFDO8!AqF@m^l2(aV*iIz3&Dk@Da_17p=c_ zO@wvOFq~0K_{%TLpQqTagBIk>)f%2}QB0Ek>H)6X$Gx1_2bBAO|JZ{Jw~JQAJ0`(n zT+~!wcG(}l2j!0cu(WI|&P}ioLhL1ZATvN*nn$L{X^0v=-Ht}3QNz7Lvz7@Z;?e&Z z4>^tK410G5*)wCUc@f%_yxYCA6U~D^9=LPu%UH9vIrMb$`FD@++w(y-TIHK98lsSf zIU9BnkdO%ujmDSOf7iiJi5R?EvlqU&J-htC2Os2XW%rVzHR}=yFF5a&osLR#wHXi- zNaMD&D6|R)n{RC*8}XwD6{!5vZ@Fy{ynWB*p;KFLk`RJ^!Q^}GG@8(oGV%|< zEamB30i}!o_HB=KrzG5X(2_6^Eb1|?*YET`2IU~?tapK%nNwJnF%J+;SqkHO0 ztEBfy!Ql$Rv9{93_!VCJ0y@@(&)nAaj0A5jw$K0PVW=31ugA)&VcIq9-z%-r1PUUPLso7+peN8M4GSenq-A7;fiAQ@^ zt%Og`J9@?|=I#0(RpF|`v!*jUG7hUC-+kqPtk~JJG-UHxiA>6jrhp`HfzG@;&~Pj% z%N;+tUDWa9pu~HfhE1Gfvc~QDqGUx4IVtw1tD{PmftBx5Y^|wqO^^a38I;)zO@!v) z(*I@W`DIzk?N&LkX;!oVKC#g!d7LD1+dkc>+L$Y`fX* z^JFZ+u_AoI$Ha8{8>9$!wy60anOJeRHK5hIuD-_MoG{{nO2)Rm)T)Qk_rL&sydq}Vli#A(5*YkDx{ zb^~wwOWhz`Zj8W8NP*l95BEwH^xa^=_&Xj`TG3OrO(UdPqE zw+f}g*B+v(AJ+}0>$}3TP!j5RzL&i0SIb<7PaN&*3xUO-$pHru;uGVZJu^g?#AUNm z89z00rn;NRm_F592yd}Ue4iKGI$UU8#>Jd8%R3AL@kw$~xkqkn(YPmkIB$W&D84iE z{rOjKw8`@(LFRcZOPz0?b%U6?G-}QYE-o;J&Ih*lgA20uBoi3TzN^_My>Tnxh2Th% z_bern^2S%EqwWo*Vk}_!E&mX6n3khj6~Jz0Uh{7GS=#)5 zx!{9j66c{EvuH*-pVd;Q%TdAfLr6VjyES3SYE0fIGVbyS9*+XbrQSa3)%|#8_MOq7zd;MlhC9=bc*?sfqXyE*X~ZQn zf>>++6&%0ABIWiCS>st95kh4tz;8d9vVPzVKIn^yeNH-K=ia~>TYMj#ew%^M#*rQM zwprP~8T3nEcbo5((JzXrbjqWP2LgZqJh7hOyT)VjXj0m`EHdP^xLuH!^cSpM`=|{0)f{&4_Yn_h<5%2 zdIL+Nfj1&rb4XHx@QDcGzn(Kp+r(zoU6qsl|El=P5tS+W%PbFJldW0J85bBL5_`A; zC)`1RMW>{r;81`F2Cln1ysw)?Ox7BS3W$erCs)lk^Ok9X4<>5oPkmUoJ?pS?w9?&} z8)0fN1%r@vwd%RH0Q?w+?W_mQ#a{$Y&%Yj%-b}bJxZ$gZWzDwCy>s4K`Nn%yNwXk3 z`11!!NLV;(KyZO?M0Nc9tq0DU-b_r_i>sj`@;huvl+4?p7&Dh;>~AqTX5IO3&6IoS z((|EqVc4yVNh)E7L@sXzGM)E}N@qbWF5@U+h_Z`&R# zeF=GDf)8&XmXP930Hx!QO_t&eP4h@sYu zR)8+{Y7bO*myK>G(_z#;=N@ZUn&-2g^EXqP9fR5qJ1hrVWipxGE*tw7o@6l~3-=n| zaHPuvlH|_bMIU4^f{FrZmE)NwG8OQ};Cv!*^wZj`W_R{(umgXqhw+OtF=vbVHSYQ_ zv_EGnwGQ>v&UJZn(E50T?82Q==HFdTtT;m5^C5MNgo`}M8hK|6CE|y@OFsMAV&ll< z6Om#wkYe;z&m^Fu==Z!UGl~^v9_R!4;Ts%=+BF8w$See8jnvh z26qLeSb%(|20yv=>w@Lo)WBl{OQ zgE)}Cm2~C-)$fdQRt*Ffwq{V~sN(W^6B5DkTLBo%+%fNi&Am@u!Y*N+rI$1_v%BvM z{pfwv<{lhr2(QLMKv~TnSfA@PeDOx@a-45EFHU}rL$?V%JefDZw6?G$lhb9Hg{~bv zr7ZZhUMbLYlSzceup*N}BF3thcrmLw)PkgoC*FTboo-F8iobfL?05sf0cq|v^TeJ; z?JeK72a{y;z0%^Na!A$v22c@G3uD&twFg%OOHQuMo?Q2@O(g@9sfycTEh7U>t;{Ar zuk4oc!lOAFIfp;+!yBoKh9$-)o;hwDwcx?c@xRZ-z)&9_OzEhkqMK zlY0#E%ox=9-*FSQ<7bZPG(5Lbg|wc$_1echcz<~Iic*nogD-c_?XJVs$O+7J-+fdA zR9(I{`tnPPZQF;h@fm-6x`C547Y+V0h|8+&=Wj;#x&p}+e&-3gFK^%>&K9NV6_Z6T zWtD#$I>;@&I!K&8-zT>}Hbi3wKpJm7FZu&`VW^fME((dMUb8oza2y$cnlt`Ob7N93g#rUHFLB)n1z%Ez}=Z!wvFJ82wTYWq_23g0`^!3`Q+8)4?k+ zUhL>DPZ~;5Q5I;OFd^p6qf1lHL4!U~FezupYw@c{oGJ2`gh!N#< z`fBz0`zxLPh*XW)9*xm;_s^{RlnXS~TPj62Vjh3uuZ>PC-)xw+%#QCke|MuMeu-Bi zmH74G^&_&zzrZxfe!^N%PLEX*tL)EDlJC^$U;=6C8e9Q9add2On-=0w^v_@q{=fwc`_3Y{GO~Vl6 zK_W7^)#jO56PtysjP%_?&N&ZR;H*eKPjm6)Tn^?|^4a&E8=rOmND#!LXPinp(6qDn zF*&v(;wFh5IDtP-Shz8^G|PJhYOep1b(29-TOi*=y|mGu|7T`T^s$LTecZdqxoIqnHVRxDz?@>lrE0Pt%vbIf!hgYALSX%AktQ-t)o8cZwUB zg*3?u0^w<;q+3Ht=Zk)7#Z=N}#MRZ5vLEz&%4%?o_9g>0w9Iw<54WFPdLztG-LdqY zrOmYZ&Je%KztMu| zZ72QMEBK2y@WonuUvL|;b2a*A&WL@@SVf#MZopbtSnOZo@*8A89u2*9to~(}4yqvP z789_@`u<^DYju`miv)~rVBhdMcU2=8W$(QkXmr=h-GoQfAQKp4qYCFG&dxG@xg}D1 zQPZ%<_;;dh@Y0OtA@H#U_rD>~DZ7p^f}19dCRb@?Q}==Npmf=}F|j0OeBe**oDAM# zk~e04(|tSWWQCBSG{!KQ3}3X~voveafPAeJO!W-T%oRz!RM<0KbNl+?*iA+%OY%OI zGgT&etNiGBEKh^R@`I4WNyS0hvw%~plLc!*g6&ycsK2IjfTu0%$hR~p#}a8CzVbT_ z2!)ehG%gJF7f;pPj(v&d?WvbTSvflu^S(g_?EViXiJzapZ3Ki7<6&G$?S(0Tlb>S| z&Wr;w5TIAQ)_Wp-AT))cIM>rNBnVZt6{Bp4lXKmD<@=rPld#YL)%Q6?7yOQ|)%A|$9K!44?)OSz;QYkCk zrQ$F*C#c-lrwPi$MLZC!1t%192;))Xd#4} zj>X>oMR9H8Qn~A-d=(`KPJWZg6u;m2e!#aBeQAC7Xz|J%|Ic{xW3v0X6rb^Xi(pD# z_lsI^1swl>7C?NOH0(aE$ck1Fd;3Z!x9e0izQE^zlCiq-9UcMTagW4}aeGv=XQ-$IO6S7tJ4V=ls$a=@G7sF+gatN>QcL ziN0ff#1orops_2W$HUI}Q#Qwt0q@}W6##HJh|>b?R*MplxlRg=qtF3!uE=@Zvh0!EgfG(LjL*c ztfD$kNxk5EGqX<--BJ@(Ix(eeQ)rN%dUMT!jP}AA?XHa^Gu4m1?`a<%dbK({ns{PD zJXLRcQ~b)myme{yDgPZ07dhrEivI#J zT+D~H*936wEH_uc*Ve(2I*ePPvYN9AGh1~`mozORJTMBY=_}$BH|F!_oqrB|PlLRa z=}HLC8FEMccp{`K2i;#D2&YBe7kHS19q&$xGJNWqo`>5%$$`&7Ho)P2y?M((90;*9 zf0zS`hfg^QJ~oC+8|=&B^k;Z0{3-Y)@a8+Ia&U0*4SnrP>g=dHGNM1fL|0^?IJy{< z-QR;1ctl=SY@LoCx zlCQHOIh!g=F7=>!#DwzI-;mijAC>Ok#1Pa`ioT0(L@sD@lX6)rwERNkHr-4=x~|2j zdx9+c0f6~I{T_$7y96IF_$^_1EAy45GvhLZnj9J6ZoFxZRQS1LsQ&Ysr|P?fb|m_b z_nn;j1uRdoCqG*3bI;TD+~=h$QuIB7G!=E{DT|NBr7D%v#41idt3$!G?AINU82G-O zj&38V#ZkDNjG4UNF2r|tv+Kfb4X+Xc$cTQnySqUm2GK}CMREx7@23}z+9RL;Lk9eyynkattdYRjA?*O&I z&2-t(HcB7#nE=ja%8#rtuJyL7pR_Nc=5L~0u6^rd;Zc*)9MCL|-MGFr9Uw99u;BN}(q@)zW3sOr zt)Ah^|6TIv%^~-x2#xs9(XRv^dwT2H1G*BFTFc@RlTD_Z(;62N2m|4K+*!X8Z?e9c z9OnMR^qX{CW(HxgLe0C$wj_%;T2g~3U6q=-32oEs6lMOnIQ5GOfX6=M#x~?8pqyy= zPUqIXq`$Si7<%aI?SLT?YcH^FMNTvQ` zV(d^p`qKV4-^(;%R2ZPSRX*eR^d$gtd>yyfU)W==GRiH>$Y}Z0YzyQ$<-yn~QLCf+ zLpw~nmi?0iHk3I9074MvN^KatCRAg#{gDsNDn~lhngVh%1D>gWbCv(_YR|f05jZ!r z+!f9oK(WUHVqBY1O!cq%=jfL0L{8}=+1an@4^}izOti z@qfx@e?wjLLp|WT&R^4+mmRfnQ6;$B$wWcFjn;{5pgc-FS00g1t|BD^PTA5lMFMo% z3!5_2f}bgbCMmfmo9d(&)1Rm_cb|_y_3R}A^KTCy+?-Md z$=+W{O#XdG#AOuyNp4AKce#X{;xzm`{hONZ+7ao5CXF*jzK@i$`(*;M?RTibW$&kfjAFrG=|h5!33-=u3I3x7LO>z^gpZa_?Ov+1 zB-ZZx8oLKH4t<(y$UdbnRL+k-<7nhhI7O5QUfVq9i_(2;3XsTMu6xppzgp;$yYf0p zDVq4&$z~iZip^KM{0(9xl9$=$HA;*U`fZ~2{wej_&dIp_ih1tAPf89*rVv&G@rkuV!_gh73N$o_V7OMzi7tg zj(x@__mBnjme|Ez`%+%a=}&*d;nzQG?*flWC8rJpo{r=9^Xnwbs5|2_Csav~uy>xb z-Q2R3A-TCs)XV2^K<@(@d+gE6VaW!f8>1(AZYzcoudfU6kf+>qS&TX@NwQf5k3{c> zam40E%s82HZp32}6NAkiYCt){S84t;sct8l z-;(9uVmvw$do%mKq$&tHY>oL4$^v6hyD^HIX=oU38dcpJPHscwySCqfn6a-1 zxp`N>1NAn5Qa&n$=k2uj$>>g$21-+S24l*n>*9S0v2vbhILWD)(8_OoPnXrdo{^aL zDd^`vT0H4i>KsUKyo^IDzIMzv-^>gBx#?Ffc*GPzHl=?MUN*QTaSh^>kD=j#fi1C& z755#uyY5a4XX7*#VG%Lrc@&j*{1|ifj>tGDqSMtEeZ?Js!x*Hib9L(ntx^-Qm}7Ca zJ)LmDi&alZoSO;#>QJSg`-kLC=?|~qj<4tmi9!PsUrB!byGJ3PwdGWLzJYO19dKz} zIHP$QRM#;+ru`o=B2(*e+yeu+n=icH9=PCXc6tS`+p9Dz&*>|3@v>TDB??%k zeE_?ZA-wyN8i(9i=U<}p158MWNMVW5;_QlkE)%sE_ai^0%IcI*vof6?g8#83uh?9+o3(=Qw2ACR(WxI=JnoBP14Dr)ITg0#uY3s$44%7CC?KEw0gKZ!#V&{gJfP+zBADkq$md|E}?_HyteZem50sXFEIWfZyTESGYlV3KT`-JO$0zS|1fV47wOT$fg99y}i z3fjaU4JJO@urzD|;wz0$3**)4%$wR>e3K-5K7tyZ?*kR@n&ehr5O|Zx+Wz955J>mT zw{2eO;>3c{nZnH*-E$s)CmASu=(6`}s*`BQKMjF%luEtX8cqn8#2?bx z!)uH~n^?Be{0<)ZkJ`o4_pf{VXLrdhGB#Bx)?)VBVKl+a_XdkHUY1XbW=5Nfkm#ga7Ll} zkwchz?#STP)gbYzqicY#O~Kw7RrvJ5q70I~q3VxA7zxSDdDQ!tWUT()&Ys$Lec0uH zefteZ@c4R+#DhNH#{K8Nm;*pca&PTkWNYQs%=$?YZF&iYLSP$5TDlMVzSRo4cFg5P*0GdPBJf zLHL#@HGA=szr+uh3Ki4;_U%G;(9;G8J&EwIzy@A4paa~u+XP?$BaD-MW1$&T@h$T_ z_XcLw$BL_+xJV0zhnP8Mg)kKp;@UsbA7zrFz8x_58v6pY?kL*B3r`!)%e5 zNd4)6@QxC{4r)j93bsQ&=I-mV)Gne@CBu2oQ%t$Cp3>{mUOQheKIb7hEWRjj2$Yih zP%DYm7$ML3HmBgpfXa@E9xseW;e7kVl{0dN5rml$gvdFAktCDTj2}8yH0(8hHzID0 zyAfg?&$-NOb>Wi0ur+qN<`QQXUac4pFg8qo`z2MIZk4_LkWgG!rHr>4FE^1`A0-vL zp^7hid&7T?n6pe?i`KY)x9hfhd7hm}7!X9B(7az5@ampofcS+W%|=b|boZzR%jI0A zZ#B7wXAbsQEGUZ+oj%z*BkI$s1(0XQc00U z#;)WZ)|xJ9nQzebe_rXgl`?Bc*Ml1E(tbWI_ajxmdS=Uj5>Gi7-&U#3EspBxntEu~ zckC#@9xQ7bJXxGUI6XWhH98_RFqlKo(57X7(>__BB3un{R$hY7$*0%_V%y=jPNzaqpkkKLHCq&EU8sv*ZosRY4~QujLsp|!&%+U;mw&g zQk{M9=q1LShx1L?Ab5cv56|ZK261T>c6{5rxeq=6%XLg<*zoOyv51DBFX}&tI*P_i zhBDiJ?fmI9^zBw-+~tdLcw&8h0$Fy^U$D@ZU;$tqhD8{!$fGz80U(UVt`!&W7991l zg&v8fs1vL3f*-iDNCT?$KVAn5Sp`BQ<4fk~rZm*gDT1!jX9OSwEyW5o*-%CX4ZdJI^Es`K z-6z(F#;yEUa;184z*`2%Mg>fKi;T=jrqlhyTj+|-@LD_HByyKctBNlZbJ%U85A@>W zm|1nT{+A{|GXY$5Z>V-r#K$DoCtY#8^ER->QM!UMsiZ?JP5GbEYn)WD_f&tZV^1K&j5g?#YBpP=z>;=_x|F3lACB1=77UPZ! zv+Fs0D2iDIDD=GkbwpJOKJV+jE2B4%%D~EJkLpf{!)H}Go0!B z)3tcyemxm|UaAn!ZC8t~$&U8w26Mz^Kfv{!uL(6G9*C)7D7_TY7Wt_et(UZ1K`}$t zKYhR&$0Wa|XI!anoE0Y?J%g5M06-`YE9M7rXGZuD@>qPakB|~+$<+IV&Cuu1A6e?~ zS2;U0E%9KiorrSxd?<*5@3)np?F;c0BZLxyzn=vW!4{uE?pESvg2%Pr>C+NiC~RuS0UK)vfoo> zRl?&KH(8yr!8i7{Tj*mIVJ;9YznaCq-MQk6>J)7}%rYcf@AlP?K#?-KntSxuFT|Y- zZlor!^jnVV*q#NQF6Hbzd=-&w%CUA)us65*FyQO9x z1o*^qD2(4F5c%BciKsTc`{boL^1K!0zo87lGyh6+MZcNREa_=Zh!UnOYCEU7i?UkX)TC%grN&`UEMP`RG1k|h$?cQHV7gYkiX@#%Mx?LRbjtv|5wKeOnTr>D1qXa!0I?M_2 zCZ$QEuBW#R-4!;d`w!V0CF}k8(3qc~2yc{Im&@mS;%vH<+Cu0Zh8ak8#sgOyK?VtR zLgWk4L&-~zYL0)S1E7%U9u4mE%GmqN8-uk~oDtf3>Y?G5jER(N+d z*{cB}j@fEzOttqAPjC<1Of(`C>I`N<2SY)>L|xuL8{nN+HtUxmvI-9F`O2-2WtI7r zOTYN_+d|vuHO7(ntbn8R!<_&h)yXzC=|h8yGLw$!@%i`w#I&}@$I6N$@ntbD4wc|` zKg-;qJ}EI~fEi6vaJ27i>670+cFS+in-vtv zB8XrQeJEg4g7=iD+j<=O3`f|umuFsv6|w0q69^kaIb5I8_041N2*DBwG-czpHgj6p z{?22*!jljCRFP5pr*jLs5I8vaQP0TWg1H&&_?ts$qG9{O$7?HBpGY|o0N8dU6P?TN zeS_SU@3M#gmB3YZF1C2B9_3m()yVQZ|C^^j%63Mm`32#Bv2NenSlo6OjKj$~e$5F{ ze#Z8zg^m*u_%jo5B?0K@p;i4MrVcj9=Ra-O@~L3TuVBnY@1XQ`KRQh+$s{ZB=PQsA z`0}DbH^ms5DV^K#X2;U}U$r30PPyPVmm_0LX+^BZR{eSwu`l-GH*JTJ^9M~eSoh7> zt;{B_QbB%y(nMUB?~`yD(o}ciZd$B@bRIg=2LcZ!S`vx{jzlA|^3)Ag$Tt#fI+WoS z5&5wi#FT}yMDH22C+PqVC0)p_Ll~&87E_voMInL{?_Pl$iA^5md>FEees~BscKdx= z&ezg%UA!T!$shEI1Si8y@Rm!NY`*?=Xw3Hmz$ReF9Bo^ac2 z$A<&a8Vr|jM-(!VC5H8G3N7d>6$`=OjjxnuQm#W4!CVKcE#-HAIqq9y0!>GUr8?}@ z7urIv>TMF`Pa6Z2AE0@D{f9fMgHlUmCmk66K|$KMws>D#8AQ@zf@D=+Mnd@zEM16J z#fEI(4`;kWIflEQfTn5y#MKa2siFfhwi-fDOr{hgTxLSsSqjv&#fOP3x8op>`UVO3 zf)90t?r3TLEj|iU{*M0FF!T@q_MfkjUA_D0z+cF*H?yv1k|N{csg$G>Jc|ZZ@+Bca z;)r60V(A?oT<0K((`&%a3xCN4PYwa%B1He4b<6=n?})X$T1lQr&47i;a}^mMo*w34 zv6IRrV+EIxW%=?6ft}ht1}^c=P0PWz9|!(`<{iiiQ*`9(9B%CP63~88l?0>cS7sXR!Blju#8QtifOXA1VxPz705jx@{{(*HW zm+<_yp?ltYXE-c9{x*vY7KtYVXazTWQJ4E06`iz9 z9_{W7EMF%dFWXcP!!CwB`}p@go>m8lixfy+z#|x9WL$C3t4Mowwm(Ie?Nyo~IEEggVr}EPL)#e~$sT zsi9pAoRwiiRFolOt%X4o4Ke^+6OFy}OW3x^Sv9C5J}MkR0G3thkqtKb$%Fu{Gc97u zNC#j4)LrVjRUnqqlKW~;Tvg{WeRSnkxpVV#cP)wH4fiwujs1DtKAb9_vkzvTbWE6< z;san2mWG3}$H91%=MwL}fzI8u+XiaR`gyO`lGtQ84G|KUDguO0N#GutQ259l)z+vc zNPz=ZPGZyoX#1@s2F3b5t8?WSXJBVBIKayZ+QEIuX*3Ad%;iMJEsWQ_AInsK{Y$qb zctdp~>tMK$Y7Ea`zp&>6-(d5e6ZBgUDHSVOCufQ*6xs&K1Zi)@C%Rf@E;loC28`bu!`B*xj26OTr;2) zzb>OvbS@`tjLyIUu-L(#76LweWA2)ZxQc%1ME4+pk7)+?6E}`02iuEa0NzWWXHCGk z{%=))vZ{nQM3&VJElY6dHO{;Xy1hpMYm)fqa_>v^jv}hK?;^>9(FBkRB{sFyh zKLxL&-)ZXK1UO;?xHJy=ajs*@ht>PiAGnkY|{n@ zS0j?rT zT}~TZ{pPZXQzl=zQyrFJnMa33(gh@~%Pq#noZsJk7CDJc?I>Bszv%kvEny~0@Gm3) z{h7v^x-iAr7N`)BibI}ghn%le1u+--3Ot)Oe9SEM`1hJdo%=a~o5IQ#`=!MXYeVJ< z#KAY*uTTj;#I{|5RXovZ9)N`G_5*LgH}N7Zg+#-wZe!>ml_?3AX{8dzCAF!#=9u zEJ79gs?`yYx+_yC^RKVO^uuu|4oKApINPKwXQ@cuy$BWmSC_@tewG36$4N4ZV4Z<5 zeJY#7V(#xA6lNQ3+PDh4?=o;Ew*M>c-@EKO@C63TscvaCHGfML%7JI%JP45sU3Oa$ zieDBUM9BS*oF3B|-%_mvOH2yGH_>0K%C05^8%PTYhxyiR5PdT7upq&fI4dHr{`JdT znckAIBUl5SBIzuLEj;NP=4j3_Qh()gB6z#GhR8YE!ZBaex9^PdJVs3Y=*q%%ktXKY zYlX#HsWUzdA`YT&!4G|~uKB4sIUpTEm7bp9rS=M|7u^3(qc;(H?)DyVip8!*^fUHh>MwOm=qin`DU4tC#?qO`rUU_&Bx!Ny4x#T z)*8$#U!96ip!r3Di(gEqf5urTL6z@}YM8A3xPKYmLZeT_{_A^r`mq{L%zx^J{({Oc zYm+ywS_8h`d#$J^hUz;3Som&3s+)o}Nf=sZse%vS=VZqu2vEb>3Jwr?c{mm=Ek}L% z0g~1207<$YCs#slDB&7KexNTQ(-ASStz-OY^rQa@g}FTJyL>r+hz9nSG#;~mrFT(o zSWDUktTWZMNSW)(@{wZo$ijR2TFN(nKRLHC$qVI~p<(p$5zsJ7_{P742nYDa7ZJXW zsu1b>KcAy4%uNT0KCgH&qT^HzVeb#9iN+VPc%*`>?mYY~e(f#tw~&T-ajE+-xB9ZK ziXsW)aT?q91TEMF`m}fuvn>}Qn#2O|H?)7YAg|R`T%qP$N!6{D0%ql;Z(=(q^xlMH zUfvPyjy|zm-;iaDbxPnr*k!)J{(mh1^&@~Wc1Q~Tw+nZ({HqjcK;t^A;YtOwUl zB8Ub1 z5hFa2yOaFZ&z-VF9aSFL)_uG$M#mnUYRKXr*RR|Mjk5 zs+p-N^I^z(+`sUFmqTz}GcD+IVKB52zNzobm5R&z+*DtiBh5$4(X&x<)Nt`*sPjXU zDIw6F&eM^dgpnroOAs25hxWQsis>w6TUQ9wr3zEwAXF_Pc+WxDE31pW%lS&3<%xc@ zK3>2cCLsfsRa!`E3q{>ERlau&d?sJkpT`hvhgzC}{19=Pw&H}TpOz>w>NCYd zJ3z6wt_i|h+1*axY~cS`z?eMkUZCKYIVZh0wV{geeh{E3;Zw~h#YT7 z$mdiE&%RkprvuOUBcA@IPnt}Y7(7cEEb2KK&Li`k6p;tTEL=QaJL#UYsvNv?h zr6FvejhDB}dGq|z{S@(hv9sy;ELh6~lIq+}mjcw@EJH0gD@2y))RbSxO(wPRUc^2T zPJW8nN!cZvxU zx$K#N9`)| znL8dW*%*MC97vjuK8*5`Lm+1{0$P#57l(@(C7({mGvhD&BP!p7W4-6Uw-cPNsMOK| zF@hfdQeFR@Hc_BfMSw_Y877!lBB6^*GhsLzr4jdKqS(P!hAUOy4Ig!>nM%f@`EE5@0l`fSCNuz=cs z7C%JhXY4(w`QzU*`3fo@wVpSr(=?JH`tV+W`1(YFcGp!g%(20p3m+du`T!wG7ron} z=g=0UH6if54ClzSi8Rs6JExD(+U;pgQ=h;$IMybO4{vNck$hspVRx)lFj4hdk?SSH z)MGzWf4t`(KVc6_$OiW+J8yfhL(Rtbh!K~vpj--~o>gM(A0fhDPxHb<=X4m2hp~uoWvV@SAC2@*VjEm9zhr zuDCA9y)DgsStTl7Mczk&4W!Yr5lW{nSvwRI&4^f;BTtakOya(hnzskwLRJtaA@=i4 zCUxAjUFjARrE-e*Kk1Ds5L#;*NpLQxN-sE0{fpU;x7DWXpIBCJ_i65@Y}&()S|9Pk zlG$B?iOAKqAWC5%eV2d`E}Xms=!;)+kUDPtUwr*Kl%ehgRKJuZtT`{0`wwof>q{L& zL|^#K`0(O`=*ArV+?Exd&HQLvj{jYGtD3wMKs3Dq(u>4kD~=#^-3n+nmL%a|saQ0b zS`Fb*-ynez{IpTem7@7~b%UG@W&(=25Y578*x%}AhkPf>Yd)v<`yBW57>i)380uHGe+lR+ z;bUdNP2z*s`+}pZqwOc0PaMUIZ*Ud@`oyNC++RR89`;`qxT@VufRO}VOEwcW1^kx3 z0}rG$2+W6!LTcLpCSpCJc)LC(!mXQtF9hciOCguH(E@)z2i7lrv@e_-jNUO11G(YL zs~09W`dff>F1wk8{#r8jrtTdGFE1>-ZXL&nhT@Tc=rQiCP5ORT^pX5j%PI%cN{Q7y zC;yNX>^Vj-t}+Jb|Le*5jB==;CyQcC5_C3~>91!eA*deYkgqP+;Ube;5D|+hAjVDp zoPX$}i+U52+d~HRE{IwmV^bYKpQ^sEy{EcSY9wEf&pc@wdSJ+|6^K6Q83e?I#O;4As`nkuNWE@y7&N94#o)zQ(S zctTGKm6w?X&1r6IAgjP_Qe8Q3R_wPZ#kW0dtgQY-X14*$Ug8Ag=D!{cAHFX6UScW# zHz{W8QIKNDRAtCX{{@Pp^waCh8`1cEDQf*!c_|1TthYbpFp|iRwL%Ue>9vkJ`Bu`X z%h!9(VY?}eVabiY7I?V3$M#ax08)jfrC zasS92snTsPq%;lPqSB+ywFY~K=0aM87~vsE7J7Qn2w}fK0{y!{KBfI6wW?s4rIeEb z-~vVi!N7?W;I()o<-hbWI5Uz3;)16D9id=V`1o|!cyHAfei19oW1QWa^09$WP%;i* z;so}jdaqhyt(7;}wIP&qH8E~DbH}kXptmK>HU%;MuD<9?25mw_dZUfpL#s zaB!g?oUF2(t;B7r9V8~2h!_`RhVE=T84#3p`cK@b)6mFJ@LeeTx?XXF_iSeJTsG{r zS`m8Xn6_Nse_();%f=Ig#JksD7C*C)4^*#^tPfxT@b9HC&zZwwGb;lE|4sBia#(P= z95j&YRR3D)ac)bL!gyMP(bLOK?a!~^P#7argBf1xxwR3x{P^2YM>SE4TaE${(13&} zzhB9cLx<;qc$E=?5%eqZiAXWXHjZZ5j%`86e+r~O>v#Em*0*RD8tV{D7Tw;b(UC6h z)>wNEdCyn|*ab0G`bTK<_Sq;Mu%&-l6%N`nzHzc{D*zQHTtP2}EQzYI)`t8vt@Y~~ z*bW{7j>SR{Ea<9ImT}*S3UNNQK2stl+Nnn&m41d8h?1dKH?C!8oRblL&dS=YIbpksq?|RDLDQKYJ?(m>zwKq z)I_a67a>ak0yrsf$Az%A z(oDW-5JHq3n~Kz9ZhaT?k@gWG&i|g{jHMMAiE@T$S57OUnVd55Fk*Q=jb3 zsLqdq()a31?`2k9Pzd=j?CGtO_DJhyHDLj*UW=0AlglE*7H`$t`C}vF@9{wIUr=<2 z{7^Y1nM*v>63Go%>px4NWbwg)uqFhCX z_5~vZV{pqe5_H7-_J{SeI-E)Cd`a2V7b2r~KEO=i%z2U>U-uQ3=A{t>rtaH73IU6gX_HcHIn5HT+5Tc|FbJC5`Q|>UUk)clp`ssTVieY1uUa z48Yfa`A_qJS61oAXW^}&l9{v{uN{lr7J%~2g2hf%k4E!r|M(NM_MD@XXDR09Xh<{s~)gG zzQIy^(?#fCmmdG7Mp;gmbcZEhkL(Q2vRUD%OS+)&mR0HreT<`(!=VX|E5tjEZ3R}o z{bA^Vf%Iw*@&Y0RnC6C53c^2*Wg(eC&fG-e3CFG&JT}N@-5PZ<1Jy6Vvz;l}+b45_ zycU;h;(DeA2+AP(pBit3M@JdrbA3WDrVJEXS^>9yXKJ%6m&FicU1PTwhUnhIfy3wq zgpD!5eI@d)SyCcS@Q0a!rJJh3Bgc=-ES3vWHSQ;)9Uqvb39EI= zdozZMoALset|a@{bDkrTua2%d-5=J5d(~Q-uEoRW5}q0bS(y7kwkt?J7DFSDts3JkwFmV`YTJ3d$JOz7YA0&hs59@b0*q2gJmg4&l>21jCA zpAy(tSJvG2eF;_CL}Mnp)36JAKqQW85aZVLdkH~?q!SxDuvPbEB-FDRK|nt%B}Hkf zOri9yc_+7vjTMcEBd_1mGLuNDdnWU4?bgV>0AaOSNvLQXVL$&kv)N%qDpplTn-1uX z5bXUVzS*neIWhFzn_bSi9vgjpvEHe7%j}fX61~IJeMgB!p&AL*RMo|lRl?dwlY?dv z#P8Vm5I|X(>7Qn=P`(xdw@|Y)YsUy-X_ghdcMl}_e*ZXN?mI*&YlZ`JXRyJsj0V|B z0(}%%42ldmNWGY;zuZPyZE<-TvLC(&C!DyD3gBb8U^Ah;Ke9?y_XPe7P2Fz6ru%>8v7~*l+>4;%J_JAC8Es~jY`S5va+u*;s1C%~>&dv5t5{%>I zPx|xg>vvz=jS-k=kNcYy>iCBlMU(JIW-#O4rnbpbC$Gu|hV6LGI_wpri`Pj+uv#!d z7J+S*YB7?|*%6~>WdSi4@dm6oqQ9_(#kmy6X%63C0=J0gRY)dR@c4 zJ_xt46b78wxIC#BU~a|WRva>Id1>q7I<8}?tOeMzje{-b|btb zF@3RFZ7d>=6^s7iX_Uki88o8X_+SKl^e`}#0b)aKj&vx z5c{KLg~|{{%;5uR*w{F4ZC?ofJW2`q&x!pVrqnyX$!KU8w9A!r(*Pn@P_QDCm&g3Z zf(;@SDwGl@$Usnw^-D#TGezE{>G+hBdGZ%#X%s@Q%pg@y1s)Wcpk((xo}NL5S%W+L z8W~FL8Lu9a_zrzvWc%yMRM9*=!vudJpfu-0M5Ni5IjGxyb2a6Z-N70bf8Z(pXQH%c0tcD!l^TFuL&nVTMI$%* z&fJ`fwICG8u(>b%+LG*O!5BF4)C{@|Xf`;qp^HR-V7K%0Jk>>DrkQ;{&U9LZ5Bw@~ z#%K=tL@5m`68JR9@U-sVvX^bEM@yTynZ4^964X4aSNZP?`}qrohq|kVyJ8}bcCShj zxmHv#CKQCBTp9PybkMpyv?g_1D<)Uz@&nO-R#d7Yfo>ODv-%8BVp-(X<69DtVMoV5 z{zc;Xk7z3*do(@cS4$Bm$N%sj)|c1JPgZ}N(LVcW69Mis@w=nsG0I$yHeC4n%xM_1 zxd<82j-hjY>fOgzx1OUsAz5^z zE$-*&d(Kbut%WazA8zp9=+h%aj`RcAYwbXA!IvIRfzLFtr)>W&!6)l5#A zNDba$yU$Va4!57Spgu*diN;x#j7P7DlDSa#za<%njm2HQ+vY$RS-~x-jE>3$K}{GV0JI%6ob>0wYHHIE^xo z5Em$eBK$QACM1Q*j2adOoet|PvLoq%LhxZm>{We6bB~Z1Y^ZD!>_g=!MWaS+cp3vS z+{diXqpjIa(dq9=v1z+O(fs&)^?iEcRr@*_|F>9D_bJ^f=k+p8jFgvUa=2P{LaF%9 zVzQF|nJgCH#iU05eBrE?;hu{%&kxFwfzy+KS03=yzL7YmGCAXfv41+5GZY!K_o%%} z)mT2~D=biw$Et32Ke<8WiQmG#E1SxG_yNn9J+f=J2Sqf^`<+8y+|n(D&*+rm5X4aBuq-*( z2t9%>ILK50u}2AZ+>}NZxi%n4CeLI`A{ZpRkKrK&PQ@#62!n*`E&F< zST}hBS#7aP{F3@f@Y(6xmT(76I8fi;UO3v*NtAI1CT1z$=5K; zh9)#whNlq^#!UVkj9&ki4*D-4!&Kaln2K9_nYYa!K?jDb*mGr})Q5Yi23@EP!347O zaTb50rO(*!cY&bN|8|p8)x=RFjUV z*_)K?rqvcc;?T&f;7DrFy2>L)^CsQ8FdH(q7JuUUT0)~>!;Smljca@j+=0q@5iqv8 zgG`!GWbOQS@dgGYVUJn8WcSM5{@ZBjTyXzytF5?swB2+lczWL!$RW z12P%$7Gntfm+sT>Wt$S0>RwxlWh%RK#8oz>VjZ=C5L_0NsZ@9FcX6<%XR@e;NaZ|X z3r~22GNxSb{owV#=qv9)mCM2yielyu>Ir0f3?4rZ4-Zp>syK%)Zd>Q2xXF+9J)w^@wWRicp(xVs_4h6+v2XYV-X`DXL3{|$crea zg|%zUr9(V|Q4U_mwt{0vHF}0&VhA8NK@=8b3quiDIaAX6L&-^vpS>6)eote@rX+B??E|6Q}L;m8=?TI_jm#J72iOh#o;#as8Ay)K8f4J0FwVzlo)kPTzCl&;{OfsGYV*b z3zoW0vGKHg%cZN~5R)e{0pMfn+zJxVW<2XlSp777N;O7B^c2LLW*X~FIzS|LoVs8f z49fJ54n`RP>$9e|m7|p^9D+qbHM0v9=c8GN+Tw@@TUhOZf^_KXJvOOZ+x4f!1(St@ z7jwKy6GC0$fxT?={h`qRE zpn3B+@JPbB0;=fIMn$j*Eix@zFn2D2vQ;?MOrk{%Hg^x0QD=vh_0@dE!ep1@NRt2) z=4<`TmwFkI@E@T>;Cqru%}(o+fu4tAOkw4$rVpVMwPcEGT$+;JQ#S2D+j1a?fskCJ0SdovI8Y5y9Vf~EN* zyiD4_fe+b(q~EQS9kUZrL&20);Lc5QiQ#x~#|PnToM#iKJxVlm-uKLQavsT?+Vefr zwNNPG%eOItFyOS8w`FnPTC!O-;PW~ z`3dtNbBF+-u>H2i*!imM;5sR2k8RMRxp1ST;X0`j~C5CA?{b;+`F(z?zY=tO4W$(o&L1hPEYhmTGx{0gf)`Ku@?rR_#EMJR+|ghk zss2>w&7QpRdfYdy9DNGDib%56*e)^iChem2^mP`3bg@RZ7AIo_$y9PCvPxYkdLiPzK{Hw6wK3jB7_0zuvX(u;><&@U>GBxnOOmOtt+n zxvMv~zmh$87Eie$%RZZXd2eT2+vI*ph4S3l$|NsLX8iO`PxmfbG%j2nm=Rk!^0~*~ z#|NeHtvDtl&E|j6XWjl=Mv0g=orJbk{3JlX{+(39*Ven;D`H6Fe>K#p1;MPMR3Ay# z6-0V`mR_nG0VGy$%rCf)t)%8?#>$9tsC^i@yy)f0=d;W6*` zE+xN?{CIrZigmAX3yt(t zQLWFue!RiIv-&X-^K_G#k3$iw!Ry4LKJE)GdzRv0$fKutm)D3<=h~m*pT71uB785j zw4-H=t!!I6Z0oiTjl5TSk_xH&^pMWrMP)XDM?SV+n!^IYL%}8^Y~hO73_dP5Y80;qSTVR;A0!ceR4y?87+2v$ulCv)LFNj z)zdBHEzaa0vg^nCt-Uc;AMc%8@Q6{?BvFUqD<#I)YB5jc3HtT4j6so{rjt3r+#=Dq zqGT76SM@1h;_lWxTeuJZp3#XO;qUW;a1ya+$j1DJ@ukn1)F-XmQKoz&Nx4LSs^0a- ztqdr}Ih3LUc{II^-VU_nmBR&-v*NO&r;qxl(w~9E*~bGyK;Vx*Bj4WH(C94$t2SvJ zH!F>{cgA@boA|WtvFr5ZNLFs|Q-i%7vWUO6X!NOHLg!~Vxw>z(TNMEF>W_03aHrwD$~^1#p$ z{{IDy4RZ2knIyv1*+rbcGKZD*W?%cdNT~wyf(25| zwa;Ij!})7-5Q4S_zkc?Gkd`(sf9+Gz()HU?$~X)B?RGmhu|N6)^|py&zq~(iFbJSm zD6lJ_&srXNC7A;9Ag9K{32CzAExeOM&+NAq z_`?D=vGmS=S-bM`s|NgD%rCbI{+%FzeFflwAON6?1a<=GTP^374?pxHPuGq<`SUC& zbbW6i%c|uf9=U%CCuhd{N))Dzh_JA-f%(NX%q^~AVR>U9bucIa0LZKb=oJR2eR5)d zB!Ta90OuTIjVi`R>o_t}N26ZW_niMM(%v2bR@R%ibo~yl-C7z5_yAErYcH&w{(t|S zh+?PqrAXKD$8juE;O_NFC!NHH;0{^xz}i=D&NCnjxsse`e=baQXTImezWEe6vp?(Z|GM=f=yw z7B8K@ES1Hckx9GWpnjL-+sC zPluJ!kFl)+UC)MUWgMR##qsH}Ejt=*$ApwXtmTdiX1X`=toHK(Z>0X*!Wu5$T)^Donl8`r z?#KatfS9)y{&?f!pZvaV)RRc`rNQ57sgb?VAFy|V2o4nZfdfhaK+l}Oc7cCa1fYMc zdi2TrhfaL-Z-&n;uS7+_cs*dxm+-Ho*PN!pIeeLySj@KUq{DX!I2NL|i z0Ve>($VeQV;FEuj16IGyn%_aq1?er z^Q;$u(<;Cx0Knx365iZDMq)Xe2r{q7vz7HG=9ktnzqpFU)y>q`e)|KxDc3Q6i+5IE z+`RnUXJmVI!+3lE(jx&O)Oh{I>!&mPJN4^H82iz|XAnE^1OQkP*h>P)J72ooUuHx1 zf9%QX@;(i-L$*Rim?j>zjJnbq+}%~MYm00MC?uE#5vo{6r1`(-I3VZhE9lR^mcfG>^Y z*V%o)MIT{r2w-12_Y7+H1OXV!2?_3o1hyiUOs~JiJS#S4Mu+bIp}$gWOn-uh z?_t0(JXFE(P!+@VGOFbwD&-=os;+gRIdW3$yxTzggnpY*yM(1QoO$)eSl+Sk7EdD&cC@?AV*jEvm(LC+W? zuMdrO9?O{o;7=Q274VSr*7pjZqM1ObX6S7n6&T-Ru(NdRzaf`}BbTODjR+iJeq z!FscWW-A)FDRY+W_sdR^K zX0-=^IXO!v|J3&b4Eo8ItL;;sP03z>^G-poVPiPF3I)V-)h}@gFxDp#>;~v}4E){?{xSF{Uqs z2a*wyPe$Iu13tQez0)MnivaHFtp1?(o+1F>bd}p6VDFa%_8a_}BmgKP z@I1KzII_sV1G6djpIsODfdsIv_c!47mL&j0QACU}ilWE|eJ{Z0m2DUN>FK*x=J&Gq zECCqvRKZp%;2?m%FX``1k+qx!{48S7&nNwYWP(3U3P!$bkehNJ@V0D=-`$cxFYhk_ z(0dX1#@_P;;9E|Ju%F=H%Hwb4^?MQl;J{ue6w>RcP)PK0-ZB)3q9{QE#PC9ZpZzD#Q*(}LDoyLWgei%^ltM3hmwAm?2rKV$UIW$R}o~*r_0ji>9V}# zL2QsbfSKPe2_&iq4*|dXc1QsGY~Bb#+?)0j_wSbXH?~tMAYGP-;SWK+zjjCf2iQDv z=#>!kr|J2uQ51Qg15;2I8X7wnJ#-bI!(-e5nq0%^?-H#@ttv%B+tXYRen4?8rm*WHdzIx!!!iK_k+L2(X853YoZO1Iu zJ8axI@~LnC=y&_xGk`r`dMxW^bHC0H49K(dH9}z#Xp?rVn6Gxx%3^3J|KocP{Qj2r z1VH=AUv1ekS~xXZB@hV$ECS58$Nw(0=+9q@j=qiUS^RoGHD0W_t0CqfgzlZgOM21-E|f>21kO z_NivQ$H+SUhvN7u`L)h)Q~zC)Ha z5-;XGnRxa1H~>mYeAg!JCy1h$PB$cBDH@dp&QFZ9RBh3XG-HFQ{h$5*k-Og&Kqpp* z@`-5MTN5(`-H?2r&z7-%wvG)jQpixqr?G7t*Rk;(8^_R;rzSXeVVP{wkxAeA&F$ZP zv3NC&E7rh{y-)7(MDXW)+Tq{hGYk);8Oos678ij^=5-w zpvjKjz{bHO$KEQvTyM7T_{S4pSX1bV4s@IKCpQmgdE?X!g*e@L@Z2_u8r^7_>P5}FtH2+!${x_Z6e*5!U}6FH#%5`BA0Rwe&V4Q z*4&CU0lHy(e{nFwxeFE2Nr$wbpc{tl+_9NhT9=61xQ>NuTO@1)+frDjh1EAi#EoTAqH)^!2I?jIVKA)U%{1Ar)sQBwA*9a25(J;%baO>EP^G8BfA zC@GObAOImHXwVvJ%?_@uAdYvuQ}42qaD&m>V5p=BqX>Xj5Yp*J2%!nuZPEjIVyy|I z7%2sDtm%XiQVK-RU=3(Av0jZ%7^8%OILfT+KqR9@DFt!we5f@7s`WOeGMJuOAn6#m zj)iR*m`e48SCzE}tu=AH8c`e*MKNI*A&Jp(m|hoP?k_)Sv^!xdyo$~CfShjh}#cTALNpyLYNFeZ#OTQ`rUa3>1cMFXU2`%2lRj7io7w04!6HuuMG1 z#YW$9V`G{I?M|1ug%!#x%^<9u`{TLS4j(R;%k#hzPy*((*3r5E zJqWW0kpWUbN+~P7>#nbVl0@z!jX)O?wyn}BS0QEG4cgUit2WoJOdKtpJo c #8794A9", -", c #8592AA", -"' c #919FB5", -") c #A5B0C3", -"! c #7688A4", -"~ c #7687A1", -"{ c #8A99B1", -"] c #768AA7", -"^ c #7E92AE", -"/ c #8397B3", -"( c #92A4BD", -"_ c #96A8C0", -": c #5B7295", -"< c #8B9DB7", -"[ c #6A83A4", -"} c #728BAD", -"| c #7791B3", -"1 c #7B95B6", -"2 c #7C96B8", -"3 c #95ABC6", -"4 c #829CBC", -"5 c #45618C", -"6 c #7F96B3", -"7 c #6483A9", -"8 c #6384AC", -"9 c #6B8BB3", -"0 c #7091B8", -"a c #7495BC", -"b c #7596BD", -"c c #7496BD", -"d c #95AECC", -"e c #6F91B9", -"f c #6081A9", -"g c #3C6292", -"h c #365989", -"i c #4B6B97", -"j c #5676A1", -"k c #5F81AB", -"l c #6E93BF", -"m c #7097C2", -"n c #7297C3", -"o c #8FADCE", -"p c #618AB7", -"q c #476D9D", -"r c #5A779F", -"s c #1B4075", -"t c #1F4377", -"u c #365785", -"v c #4A6791", -"w c #4D6B94", -"x c #5D80AA", -"y c #6C93BF", -"z c #779DC6", -"A c #84A6CB", -"B c #5382B6", -"C c #3A659A", -"D c #5C789F", -"E c #224579", -"F c #1E4277", -"G c #274A7C", -"H c #2E4F7F", -"I c #335483", -"J c #44628D", -"K c #557197", -"L c #526E95", -"M c #506F99", -"N c #668FBD", -"O c #7FA3CA", -"P c #7299C4", -"Q c #36639A", -"R c #4F6D97", -"S c #345484", -"T c #305181", -"U c #42618A", -"V c #4E6992", -"W c #4A678F", -"X c #4B6992", -"Y c #4B6994", -"Z c #5F7A9E", -"` c #5E779C", -" . c #597398", -".. c #527099", -"+. c #618ABA", -"@. c #84A6CD", -"#. c #5D89BB", -"$. c #375A89", -"%. c #4C6992", -"&. c #3A5986", -"*. c #536E94", -"=. c #577197", -"-. c #6F92BB", -";. c #759ECA", -">. c #739CCA", -",. c #759DC9", -"'. c #6880A4", -"). c #5C7699", -"!. c #4F6F98", -"~. c #618DBD", -"{. c #81A4CA", -"]. c #4B7CB3", -"^. c #274F84", -"/. c #5A759B", -"(. c #375784", -"_. c #577196", -":. c #5A7498", -"<. c #6F89AB", -"[. c #8BADD1", -"}. c #7EA5D0", -"|. c #7FA6D0", -"1. c #7CA4CF", -"2. c #7CA4CE", -"3. c #789FC7", -"4. c #6984A7", -"5. c #5A7398", -"6. c #5076A5", -"7. c #6791C0", -"8. c #7198C3", -"9. c #2D5081", -"0. c #25477A", -"a. c #4C6890", -"b. c #627A9D", -"c. c #657DA1", -"d. c #A0BBD7", -"e. c #89AED6", -"f. c #8BAFD7", -"g. c #8BB0D7", -"h. c #8AAED6", -"i. c #86ACD4", -"j. c #82A8D2", -"k. c #7CA3CF", -"l. c #749DC8", -"m. c #6984A6", -"n. c #567298", -"o. c #5786BA", -"p. c #779CC6", -"q. c #5583B7", -"r. c #295086", -"s. c #2C4E7F", -"t. c #345483", -"u. c #5B7498", -"v. c #5D7699", -"w. c #93AAC4", -"x. c #9EBDDD", -"y. c #96B8DD", -"z. c #97B9DE", -"A. c #94B7DC", -"B. c #90B3DA", -"C. c #8AAFD6", -"D. c #83A9D2", -"E. c #7AA2CE", -"F. c #6F95BF", -"G. c #667E9F", -"H. c #577FAF", -"I. c #5585B8", -"J. c #759AC5", -"K. c #3F6FA7", -"L. c #254A80", -"M. c #5C769C", -"N. c #3C5B87", -"O. c #60789C", -"P. c #B0C3D8", -"Q. c #A1C0E3", -"R. c #A3C2E4", -"S. c #A3C3E5", -"T. c #A2C2E4", -"U. c #9EBFE2", -"V. c #98BADE", -"W. c #91B4DA", -"X. c #88AED6", -"Y. c #759ECB", -"Z. c #6885AD", -"`. c #5780B0", -" + c #5383B8", -".+ c #7299C3", -"++ c #4675AB", -"@+ c #395A89", -"#+ c #46648F", -"$+ c #3E5D88", -"%+ c #617A9C", -"&+ c #637B9D", -"*+ c #BFD1E4", -"=+ c #ADCBEA", -"-+ c #B0CDEB", -";+ c #AFCCEB", -">+ c #ACCAE9", -",+ c #A7C6E6", -"'+ c #9FC0E2", -")+ c #8DB1D8", -"!+ c #82A9D2", -"~+ c #77A0CC", -"{+ c #6A94C4", -"]+ c #5F8DBF", -"^+ c #5C89BA", -"/+ c #5C85B4", -"(+ c #3F5E8B", -"_+ c #3B5A86", -":+ c #647C9E", -"<+ c #61799D", -"[+ c #C1D2E4", -"}+ c #BBD5F1", -"|+ c #BCD6F2", -"1+ c #BAD5F1", -"2+ c #B5D1EE", -"3+ c #AECBEA", -"4+ c #A5C4E5", -"5+ c #9BBCE0", -"6+ c #84AAD3", -"7+ c #78A0CC", -"8+ c #6B96C6", -"9+ c #5F8CBF", -"0+ c #5282B8", -"a+ c #4F7FB4", -"b+ c #668CB7", -"c+ c #43618D", -"d+ c #325382", -"e+ c #5E779B", -"f+ c #5F789A", -"g+ c #BACADC", -"h+ c #C9E0F9", -"i+ c #C7DFF8", -"j+ c #C3DCF6", -"k+ c #BCD7F2", -"l+ c #B3CFED", -"m+ c #A8C7E7", -"n+ c #9DBEE1", -"o+ c #6A96C5", -"p+ c #5D8BBE", -"q+ c #5081B7", -"r+ c #497BB2", -"s+ c #698DB7", -"t+ c #45638E", -"u+ c #2E5080", -"v+ c #25487B", -"w+ c #506B93", -"x+ c #647B9E", -"y+ c #9DAFC7", -"z+ c #D6E9FD", -"A+ c #D1E8FE", -"B+ c #CAE2FA", -"C+ c #C1DAF5", -"D+ c #A9C8E8", -"E+ c #6894C4", -"F+ c #5A89BC", -"G+ c #4D7EB5", -"H+ c #4577AF", -"I+ c #6B8DB5", -"J+ c #3B6090", -"K+ c #3D5D8A", -"L+ c #3C5B89", -"M+ c #1D4276", -"N+ c #647C9C", -"O+ c #657D9E", -"P+ c #C2D4E6", -"Q+ c #D3E9FF", -"R+ c #CEE5FC", -"S+ c #C2DBF5", -"T+ c #80A7D1", -"U+ c #729CC9", -"V+ c #6491C2", -"W+ c #5686BA", -"X+ c #497BB3", -"Y+ c #5580B3", -"Z+ c #5B7EAA", -"`+ c #335686", -" @ c #4D6993", -".@ c #26497B", -"+@ c #4C6990", -"@@ c #6B82A2", -"#@ c #7D92B0", -"$@ c #C1D4E9", -"%@ c #C0DAF4", -"&@ c #B2CFED", -"*@ c #7BA3CE", -"=@ c #6F99C7", -"-@ c #6390C0", -";@ c #5886B9", -">@ c #4A7BB2", -",@ c #698CB7", -"'@ c #4C6F9C", -")@ c #284C7F", -"!@ c #577299", -"~@ c #2F517F", -"{@ c #536E93", -"]@ c #7086A5", -"^@ c #7B8FAC", -"/@ c #9FB3CD", -"(@ c #B8D2EE", -"_@ c #91B5DB", -":@ c #85ABD4", -"<@ c #7CA3CD", -"[@ c #729AC8", -"}@ c #6791C1", -"|@ c #5A87BA", -"1@ c #5280B4", -"2@ c #7A97BB", -"3@ c #416290", -"4@ c #2F5080", -"5@ c #4F6B91", -"6@ c #687F9F", -"7@ c #7B8FAB", -"8@ c #8396B0", -"9@ c #8AA1BF", -"0@ c #8CACCE", -"a@ c #8FB2D8", -"b@ c #86AAD2", -"c@ c #7DA3CC", -"d@ c #749BC7", -"e@ c #6993C1", -"f@ c #5B88B9", -"g@ c #7999C0", -"h@ c #587AA5", -"i@ c #21416E", -"j@ c #516D96", -"k@ c #415F8A", -"l@ c #567196", -"m@ c #637B9E", -"n@ c #6F84A3", -"o@ c #778BA7", -"p@ c #758BAA", -"q@ c #7490B3", -"r@ c #749BC5", -"s@ c #6992C0", -"t@ c #799DC4", -"u@ c #7291B6", -"v@ c #46658C", -"w@ c #264878", -"x@ c #4F6C94", -"y@ c #42608C", -"z@ c #294B7D", -"A@ c #4A6790", -"B@ c #567095", -"C@ c #597599", -"D@ c #587398", -"E@ c #536F96", -"F@ c #4F6D95", -"G@ c #7690B4", -"H@ c #7090B7", -"I@ c #4A6991", -"J@ c #2B4871", -"K@ c #41608C", -"L@ c #59749A", -"M@ c #234679", -"N@ c #214578", -"O@ c #3A5A87", -"P@ c #47648F", -"Q@ c #667EA2", -"R@ c #7188A9", -"S@ c #4B6892", -"T@ c #395985", -"U@ c #1C314F", -"V@ c #2E4F7D", -"W@ c #395986", -"X@ c #526E96", -"Y@ c #58739A", -"Z@ c #5B759B", -"`@ c #6A82A5", -" # c #627CA0", -".# c #365887", -"+# c #324B69", -"@# c #344B69", -"## c #34537D", -"$# c #294C7C", -"%# c #315383", -"&# c #40618D", -"*# c #40638F", -"=# c #3B5C87", -"-# c #36537A", -";# c #2E4156", -" ", -" . ", -" + @ # ", -" $ % & * = ", -" - ; > , ' ) ! ", -" ~ { ] ^ / ( _ ", -" : < [ } | 1 2 3 4 ", -" 5 6 7 8 9 0 a b c d e ", -" 7 f g h i j k l m n o p ", -" q r s s s t u v w x y z A B ", -" C D E s F G H I J K L M N O P ", -" Q R S s T U V W X Y Z ` ...+.@.#. ", -" $.%.t &.*.=.r -.;.>.,.0 '.).!.~.{.]. ", -" ^./.F (._.:.<.[.}.|.}.1.2.3.4.5.6.7.8. ", -" 9.v 0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q. ", -" r.v s.t.u.v.w.x.y.z.z.A.B.C.D.E.F.G.H.I.J.K. ", -" L.M.s N.b.O.P.Q.R.S.T.U.V.W.X.|.Y.Z.`. +.+++ ", -" @+#+s $+%+&+*+=+-+;+>+,+'+z.)+!+~+{+]+ +^+/+ ", -" (+S s _+:+<+[+}+|+1+2+3+4+5+B.6+7+8+9+0+a+b+ ", -" c+T s d+e+f+g+h+i+j+k+l+m+n+W.6+~+o+p+q+r+s+ ", -" t+u+s v+w+x+y+z+A+B+C+2+D+n+B.D.Y.E+F+G+H+I+J+ ", -" K+L+s M+N.N+O+P+Q+R+S+2+m+5+)+T+U+V+W+X+Y+Z+ ", -" `+ @s s .@+@@@#@$@R+%@&@4+z.e.*@=@-@;@>@,@'@ ", -" )@!@t s s ~@{@]@^@/@(@=+'+_@:@<@[@}@|@1@2@3@ ", -" S #+s s s 4@5@6@7@8@9@0@a@b@c@d@e@f@g@h@ ", -" i@j@u s s s G k@l@m@n@o@p@q@a r@s@t@u@v@ ", -" w@x@y@s s s s z@_+A@B@C@D@E@F@G@H@I@ ", -" J@K@L@y@M@s s N@z@S O@P@Q@R@S@T@ ", -" U@V@W@X@L@Y@Z@L@c.`@ #v .#+# ", -" @###$#%#&#*#=#-#;# ", -" ", -" "}; diff --git a/setup.py b/setup.py index a6fe39b5c..1083eb582 100644 --- a/setup.py +++ b/setup.py @@ -186,7 +186,8 @@ setup( 'deluge/data/icons/hicolor/72x72/apps/deluge.png']), ('/usr/share/deluge/icons/hicolor/96x96/apps', [ 'deluge/data/icons/hicolor/96x96/apps/deluge.png']), - ('/usr/share/applications', ['deluge.desktop'])], + ('/usr/share/applications', [ + 'deluge/data/share/applications/deluge.desktop'])], ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(exclude=["plugins"]), From 379eff9767e2bafd869242de5a73a62698c4416a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Sep 2007 04:26:32 +0000 Subject: [PATCH 0078/1009] Updates to last patch and updates to the preferences dialog. --- deluge/common.py | 43 +-- .../ui/gtkui/glade/preferences_dialog.glade | 272 ++++++++---------- deluge/ui/gtkui/mainwindow.py | 4 +- deluge/ui/gtkui/preferences.py | 137 +++++---- 4 files changed, 233 insertions(+), 223 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index ff7ca0937..b29f352af 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -64,6 +64,29 @@ def get_default_plugin_dir(): """Returns the default plugin directory""" return os.path.join(get_config_dir(), "plugins") +def windows_check(): + """Checks if the current platform is Windows. Returns True if it is Windows + and False if not.""" + import platform + if platform.system() in ('Windows', 'Microsoft'): + return True + else: + return False + +def get_pixmap(fname): + """Returns a pixmap file included with deluge""" + return pkg_resources.resource_filename("deluge", os.path.join("data", \ + "pixmaps", fname)) +def get_logo(size): + """Returns a deluge logo pixbuf based on the size parameter.""" + import gtk + if windows_check(): + return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.png"), \ + size, size) + else: + return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.svg"), \ + size, size) + ## Formatting text functions def fsize(fsize_b): @@ -116,24 +139,4 @@ def ftime(seconds): return '%dw %dd' % (weeks, days) return 'unknown' -def windows_check(): - import platform - if platform.system() in ('Windows', 'Microsoft'): - return True - else: - return False -def get_pixmap(fname): - import pkg_resources - from os import path - return pkg_resources.resource_filename("deluge", path.join("data", \ - "pixmaps", fname)) - -def get_logo(size): - import gtk - if windows_check(): - return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.png"), \ - size, size) - else: - return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.svg"), \ - size, size) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 9c96608a6..e09d0a440 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -38,7 +38,6 @@ False - True @@ -107,7 +106,6 @@ True Ask where to save each download True - 0 True @@ -123,7 +121,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Store all downloads in: Store all downloads in: - 0 True True radio_ask_save @@ -196,7 +193,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Full allocation preallocates all of the space that is needed for the torrent and prevents disk fragmentation Use Full Allocation - 0 True True @@ -212,7 +208,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Compact allocation only allocates space as needed Use Compact Allocation - 0 True radio_full_allocation @@ -265,7 +260,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Enable selecting files for torrents before loading Enable selecting files for torrents before loading - 0 True @@ -279,7 +273,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Prioritize first and last pieces of files in torrent Prioritize first and last pieces of files in torrent - 0 True @@ -443,7 +436,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Test Active Port - 0 False @@ -468,7 +460,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Deluge will automatically choose a different port to use every time. Use Random Ports - 0 True @@ -562,7 +553,6 @@ Distributed hash table may improve the amount of active connections. Enable Mainline DHT True - 0 True @@ -609,7 +599,6 @@ Universal Plug and Play UPnP True - 0 True True @@ -625,7 +614,6 @@ NAT Port Mapping Protocol NAT-PMP True - 0 True True @@ -642,7 +630,6 @@ µTorrent Peer-Exchange µTorrent-PeX True - 0 True True @@ -777,7 +764,6 @@ Full Stream True Prefer to encrypt the entire stream True - 0 True @@ -882,71 +868,40 @@ Full Stream 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -971,43 +926,74 @@ Full Stream - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1051,29 +1037,24 @@ Full Stream 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1091,19 +1072,24 @@ Full Stream - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1207,9 +1193,8 @@ Full Stream True Enable system tray icon True - 0 - True True + @@ -1222,7 +1207,6 @@ Full Stream False Minimize to tray on close True - 0 True @@ -1241,7 +1225,6 @@ Full Stream False Start in tray True - 0 True @@ -1262,8 +1245,8 @@ Full Stream True Password protect system tray True - 0 True + @@ -1282,8 +1265,9 @@ Full Stream True 5 - + True + False 0 Password: @@ -1343,6 +1327,7 @@ Full Stream True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 12 @@ -1352,14 +1337,31 @@ Full Stream 2 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1389,36 +1391,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - GTK_FILL - - @@ -1452,20 +1438,26 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 12 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Deluge will check our servers and will tell you if a newer version has been released - Be alerted about new releases - 0 - True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge will check our servers and will tell you if a newer version has been released + Be alerted about new releases + True + + False @@ -1536,7 +1528,6 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Yes, please send anonymous statistics - 0 True @@ -1639,7 +1630,6 @@ Thunar False - True @@ -1654,7 +1644,6 @@ Thunar False - True @@ -1679,10 +1668,6 @@ Thunar - - True - True - @@ -1702,7 +1687,6 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True - 0 @@ -1714,7 +1698,6 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply True - 0 @@ -1729,7 +1712,6 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-ok True - 0 diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 6150c7764..9945ae8c1 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -42,7 +42,7 @@ from toolbar import ToolBar from torrentview import TorrentView from torrentdetails import TorrentDetails from preferences import Preferences -from deluge.common import get_logo +import deluge.common from deluge.log import LOG as log @@ -54,7 +54,7 @@ class MainWindow: "glade/main_window.glade")) self.window = self.main_glade.get_widget("main_window") - self.window.set_icon(get_logo(32)) + self.window.set_icon(deluge.common.get_logo(32)) # Initialize various components of the gtkui self.menubar = MenuBar(self) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 8252456e2..d6f49c6f9 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -38,7 +38,7 @@ import pkg_resources from deluge.log import LOG as log import deluge.ui.functions as functions -from deluge.common import get_logo +import deluge.common class Preferences: def __init__(self, window): @@ -47,7 +47,7 @@ class Preferences: pkg_resources.resource_filename("deluge.ui.gtkui", "glade/preferences_dialog.glade")) self.pref_dialog = self.glade.get_widget("pref_dialog") - self.pref_dialog.set_icon(get_logo(32)) + self.pref_dialog.set_icon(deluge.common.get_logo(32)) self.treeview = self.glade.get_widget("treeview") self.notebook = self.glade.get_widget("notebook") self.core = functions.get_core() @@ -82,13 +82,13 @@ class Preferences: index = self.notebook.append_page(widget) self.liststore.append([index, name]) - def get_config(self): + def get_core_config(self): """Get the configuration from the core.""" # Get the config dictionary from the core - self.config = functions.get_config(self.core) + self.core_config = functions.get_config(self.core) def show(self): - self.get_config() + self.get_core_config() # Update the preferences dialog to reflect current config settings ## Downloads tab ## @@ -100,53 +100,53 @@ class Preferences: # choose a download location.. It will be specific to the machine core # is running on. self.glade.get_widget("download_path_button").set_filename( - self.config["download_location"]) + self.core_config["download_location"]) self.glade.get_widget("radio_compact_allocation").set_active( - self.config["compact_allocation"]) + self.core_config["compact_allocation"]) self.glade.get_widget("radio_full_allocation").set_active( - not self.config["compact_allocation"]) + not self.core_config["compact_allocation"]) self.glade.get_widget("chk_prioritize_first_last_pieces").set_active( - self.config["prioritize_first_last_pieces"]) + self.core_config["prioritize_first_last_pieces"]) ## Network tab ## self.glade.get_widget("spin_port_min").set_value( - self.config["listen_ports"][0]) + self.core_config["listen_ports"][0]) self.glade.get_widget("spin_port_max").set_value( - self.config["listen_ports"][1]) + self.core_config["listen_ports"][1]) self.glade.get_widget("active_port_label").set_text( str(functions.get_listen_port(self.core))) self.glade.get_widget("chk_random_port").set_active( - self.config["random_port"]) + self.core_config["random_port"]) self.glade.get_widget("chk_dht").set_active( - self.config["dht"]) + self.core_config["dht"]) self.glade.get_widget("chk_upnp").set_active( - self.config["upnp"]) + self.core_config["upnp"]) self.glade.get_widget("chk_natpmp").set_active( - self.config["natpmp"]) + self.core_config["natpmp"]) self.glade.get_widget("chk_utpex").set_active( - self.config["utpex"]) + self.core_config["utpex"]) self.glade.get_widget("combo_encin").set_active( - self.config["enc_in_policy"]) + self.core_config["enc_in_policy"]) self.glade.get_widget("combo_encout").set_active( - self.config["enc_out_policy"]) + self.core_config["enc_out_policy"]) self.glade.get_widget("combo_enclevel").set_active( - self.config["enc_level"]) + self.core_config["enc_level"]) self.glade.get_widget("chk_pref_rc4").set_active( - self.config["enc_prefer_rc4"]) + self.core_config["enc_prefer_rc4"]) ## Bandwidth tab ## self.glade.get_widget("spin_max_connections_global").set_value( - self.config["max_connections_global"]) + self.core_config["max_connections_global"]) self.glade.get_widget("spin_max_download").set_value( - self.config["max_download_speed"]) + self.core_config["max_download_speed"]) self.glade.get_widget("spin_max_upload").set_value( - self.config["max_upload_speed"]) + self.core_config["max_upload_speed"]) self.glade.get_widget("spin_max_upload_slots_global").set_value( - self.config["max_upload_slots_global"]) + self.core_config["max_upload_slots_global"]) self.glade.get_widget("spin_max_connections_per_torrent").set_value( - self.config["max_connections_per_torrent"]) + self.core_config["max_connections_per_torrent"]) self.glade.get_widget("spin_max_upload_slots_per_torrent").set_value( - self.config["max_upload_slots_per_torrent"]) + self.core_config["max_upload_slots_per_torrent"]) ## Other tab ## # All of it is UI only. @@ -157,13 +157,13 @@ class Preferences: def set_config(self): """Sets all altered config values in the core""" # Get the values from the dialog - new_config = {} + new_core_config = {} ## Downloads tab ## - new_config["download_location"] = \ + new_core_config["download_location"] = \ self.glade.get_widget("download_path_button").get_filename() - new_config["compact_allocation"] = \ + new_core_config["compact_allocation"] = \ self.glade.get_widget("radio_compact_allocation").get_active() - new_config["prioritize_first_last_pieces"] = \ + new_core_config["prioritize_first_last_pieces"] = \ self.glade.get_widget( "chk_prioritize_first_last_pieces").get_active() @@ -173,51 +173,51 @@ class Preferences: self.glade.get_widget("spin_port_min").get_value_as_int()) listen_ports.append( self.glade.get_widget("spin_port_max").get_value_as_int()) - new_config["listen_ports"] = listen_ports - new_config["random_port"] = \ + new_core_config["listen_ports"] = listen_ports + new_core_config["random_port"] = \ self.glade.get_widget("chk_random_port").get_active() - new_config["dht"] = self.glade.get_widget("chk_dht").get_active() - new_config["upnp"] = self.glade.get_widget("chk_upnp").get_active() - new_config["natpmp"] = self.glade.get_widget("chk_natpmp").get_active() - new_config["utpex"] = self.glade.get_widget("chk_utpex").get_active() - new_config["enc_in_policy"] = \ + new_core_config["dht"] = self.glade.get_widget("chk_dht").get_active() + new_core_config["upnp"] = self.glade.get_widget("chk_upnp").get_active() + new_core_config["natpmp"] = self.glade.get_widget("chk_natpmp").get_active() + new_core_config["utpex"] = self.glade.get_widget("chk_utpex").get_active() + new_core_config["enc_in_policy"] = \ self.glade.get_widget("combo_encin").get_active() - new_config["enc_out_policy"] = \ + new_core_config["enc_out_policy"] = \ self.glade.get_widget("combo_encout").get_active() - new_config["enc_level"] = \ + new_core_config["enc_level"] = \ self.glade.get_widget("combo_enclevel").get_active() - new_config["enc_prefer_rc4"] = \ + new_core_config["enc_prefer_rc4"] = \ self.glade.get_widget("chk_pref_rc4").get_active() ## Bandwidth tab ## - new_config["max_connections_global"] = \ + new_core_config["max_connections_global"] = \ self.glade.get_widget( "spin_max_connections_global").get_value_as_int() - new_config["max_download_speed"] = \ + new_core_config["max_download_speed"] = \ self.glade.get_widget("spin_max_download").get_value() - new_config["max_upload_speed"] = \ + new_core_config["max_upload_speed"] = \ self.glade.get_widget("spin_max_upload").get_value() - new_config["max_upload_slots_global"] = \ + new_core_config["max_upload_slots_global"] = \ self.glade.get_widget( "spin_max_upload_slots_global").get_value_as_int() - new_config["max_connections_per_torrent"] = \ + new_core_config["max_connections_per_torrent"] = \ self.glade.get_widget( "spin_max_connections_per_torrent").get_value_as_int() - new_config["max_upload_slots_per_torrent"] = \ + new_core_config["max_upload_slots_per_torrent"] = \ self.glade.get_widget( "spin_max_upload_slots_per_torrent").get_value_as_int() config_to_set = {} - for key in new_config.keys(): + for key in new_core_config.keys(): # The values do not match so this needs to be updated - if self.config[key] != new_config[key]: - config_to_set[key] = new_config[key] + if self.core_config[key] != new_core_config[key]: + config_to_set[key] = new_core_config[key] # Set each changed config value in the core functions.set_config(config_to_set, self.core) # Update the configuration - self.config.update(config_to_set) + self.core_config.update(config_to_set) def hide(self): self.pref_dialog.hide() @@ -229,14 +229,39 @@ class Preferences: def on_toggle(self, widget): """Handles widget sensitivity based on radio/check button values.""" value = widget.get_active() - if widget == self.glade.get_widget('radio_save_all_to'): - self.glade.get_widget('download_path_button').set_sensitive(value) + # Disable the download path button if user wants to pick where each + # new torrent is saved. + if widget == self.glade.get_widget("radio_save_all_to"): + self.glade.get_widget("download_path_button").set_sensitive(value) - self.glade.get_widget('spin_port_min').set_sensitive( - not self.glade.get_widget('chk_random_port').get_active()) - self.glade.get_widget('spin_port_max').set_sensitive( - not self.glade.get_widget('chk_random_port').get_active()) + # Disable the port spinners if random ports is selected. + if widget == self.glade.get_widget("chk_random_port"): + self.glade.get_widget("spin_port_min").set_sensitive(not value) + self.glade.get_widget("spin_port_max").set_sensitive(not value) + # Disable all the tray options if tray is not used. + if widget == self.glade.get_widget("chk_use_tray"): + self.glade.get_widget("chk_min_on_close").set_sensitive(value) + self.glade.get_widget("chk_start_in_tray").set_sensitive(value) + self.glade.get_widget("chk_lock_tray").set_sensitive(value) + if value == True: + lock = self.glade.get_widget("chk_lock_tray").get_active() + self.glade.get_widget("txt_tray_passwd").set_sensitive(lock) + self.glade.get_widget("password_label").set_sensitive(lock) + else: + self.glade.get_widget("txt_tray_passwd").set_sensitive(value) + self.glade.get_widget("password_label").set_sensitive(value) + + if widget == self.glade.get_widget("chk_lock_tray"): + self.glade.get_widget("txt_tray_passwd").set_sensitive(value) + self.glade.get_widget("password_label").set_sensitive(value) + + # Disable the file manager combo box if custom is selected. + if widget == self.glade.get_widget("radio_open_folder_custom"): + self.glade.get_widget("combo_file_manager").set_sensitive(not value) + self.glade.get_widget("txt_open_folder_location").set_sensitive( + value) + def on_button_ok_clicked(self, data): log.debug("on_button_ok_clicked") self.set_config() From e6341e35d99df1d0b3748a26b10bc1c20da25f7d Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 16 Sep 2007 07:40:44 +0000 Subject: [PATCH 0079/1009] finish off window icons for now --- TODO | 1 - deluge/ui/gtkui/addtorrentdialog.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index fea02c0b4..3089f43af 100644 --- a/TODO +++ b/TODO @@ -3,7 +3,6 @@ appropriate function will be called to 'apply' those changes. This type of system may need to be added to the gtkui as well. * Status icons for the torrentview -* Icons for the gtkui windows * Have the ui better handle not being able to connect to the daemon. * Mainwindow state saving.. Size, location, vpane position, etc.. * System tray icon diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 33966631c..8d48a69e8 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -38,6 +38,7 @@ import gettext from deluge.config import Config from deluge.log import LOG as log +import deluge.common class AddTorrentDialog: def __init__(self, parent=None): @@ -48,6 +49,7 @@ class AddTorrentDialog: buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) + self.chooser.set_icon(deluge.common.get_logo(32)) self.chooser.set_select_multiple(True) self.chooser.set_property("skip-taskbar-hint", True) From 70b71ad6ce4bf233dda634781fcb144572171f53 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 01:05:27 +0000 Subject: [PATCH 0080/1009] Added user-agent. Now start the metadata and utpex extensions on start-up. --- deluge/core/core.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 01cfbb94d..38de95e49 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -92,21 +92,34 @@ class Core(dbus.service.Object): # Start the libtorrent session log.debug("Starting libtorrent session..") self.session = lt.session(fingerprint) - + + # Set the user agent + self.settings = lt.session_settings() + self.settings.user_agent = "Deluge %s" % deluge.common.get_version() + self.session.set_settings(self.settings) + # Set the listening ports - if self.config.get("random_port"): + if self.config["random_port"]: import random listen_ports = [] randrange = lambda: random.randrange(49152, 65525) listen_ports.append(randrange()) listen_ports.append(listen_ports[0]+10) else: - listen_ports = self.config.get("listen_ports") + listen_ports = self.config["listen_ports"] log.debug("Listening on ports %i-%i", listen_ports[0], listen_ports[1]) self.session.listen_on(listen_ports[0], listen_ports[1]) + + # Load metadata extension + self.session.add_extension(lt.create_metadata_plugin) + + # Load utorrent peer-exchange + if self.config["utpex"]: + self.session.add_extension(lt.create_ut_pex_plugin) + # Start the TorrentManager self.torrents = TorrentManager(self.session) From 4a2bb465f0d2f8eb1c733c30f9b83203436181d0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 04:08:47 +0000 Subject: [PATCH 0081/1009] Fix libtorrent extensions from crashing on add_torrent. --- libtorrent/bindings/python/src/extensions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp index f8fb30bdf..10d18ff94 100755 --- a/libtorrent/bindings/python/src/extensions.cpp +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -142,6 +142,7 @@ void bind_extensions() // TODO move to it's own file class_("peer_connection", no_init); + class_ >("torrent_plugin", no_init); def("create_ut_pex_plugin", create_ut_pex_plugin); def("create_metadata_plugin", create_metadata_plugin); } From f764448cad0ecd47c9fbabe13a8cd1502ce6f87d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 04:19:59 +0000 Subject: [PATCH 0082/1009] Store allocation type in the torrent state file and add the ability to add a torrent with a different allocation type then what is set in the config. --- deluge/core/core.py | 2 +- deluge/core/torrent.py | 6 ++++-- deluge/core/torrentmanager.py | 16 +++++++++++----- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 38de95e49..1630d46ac 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -114,7 +114,7 @@ class Core(dbus.service.Object): listen_ports[1]) # Load metadata extension - self.session.add_extension(lt.create_metadata_plugin) + #self.session.add_extension(lt.create_metadata_plugin) # Load utorrent peer-exchange if self.config["utpex"]: diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index aa7fc497c..0058c1285 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -36,7 +36,7 @@ class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ - def __init__(self, filename, handle): + def __init__(self, filename, handle, compact): # Set the filename self.filename = filename # Set the libtorrent handle @@ -45,10 +45,12 @@ class Torrent: self.torrent_id = str(handle.info_hash()) # This is for saving the total uploaded between sessions self.total_uploaded = 0 + # Set the allocation mode + self.compact = compact def get_state(self): """Returns the state of this torrent for saving to the session state""" - return (self.torrent_id, self.filename) + return (self.torrent_id, self.filename, self.compact) def get_eta(self): """Returns the ETA in seconds for this torrent""" diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 0b0ca09bf..f9e4166d0 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -45,9 +45,10 @@ from deluge.core.torrent import Torrent from deluge.log import LOG as log class TorrentState: - def __init__(self, torrent_id, filename): + def __init__(self, torrent_id, filename, compact): self.torrent_id = torrent_id self.filename = filename + self.compact = compact class TorrentManagerState: def __init__(self): @@ -80,7 +81,7 @@ class TorrentManager: """Returns a list of torrent_ids""" return self.torrents.keys() - def add(self, filename, filedump=None): + def add(self, filename, filedump=None, compact=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) # Get the core config @@ -106,11 +107,16 @@ class TorrentManager: torrent_filedump = lt.bdecode(filedump) handle = None + # Make sure we are adding it with the correct allocation method. + if compact is None: + compact = config["compact_allocation"] + print "compact: ", compact + try: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), config["download_location"], - config["compact_allocation"]) + compact) except RuntimeError: log.warning("Error adding torrent") @@ -140,7 +146,7 @@ class TorrentManager: log.debug("Torrent %s added.", handle.info_hash()) # Create a Torrent object - torrent = Torrent(filename, handle) + torrent = Torrent(filename, handle, compact) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent # Save the session state @@ -197,7 +203,7 @@ class TorrentManager: # Try to add the torrents in the state to the session for torrent_state in state.torrents: - self.add(torrent_state.filename) + self.add(torrent_state.filename, compact=torrent_state.compact) def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" From 978a2ae80b33d155420cc5ff1464baf3a4e996af Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 04:20:40 +0000 Subject: [PATCH 0083/1009] Remove debug print from that last commit and re-add libtorrent metadata plugin. --- deluge/core/core.py | 2 +- deluge/core/torrentmanager.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 1630d46ac..38de95e49 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -114,7 +114,7 @@ class Core(dbus.service.Object): listen_ports[1]) # Load metadata extension - #self.session.add_extension(lt.create_metadata_plugin) + self.session.add_extension(lt.create_metadata_plugin) # Load utorrent peer-exchange if self.config["utpex"]: diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index f9e4166d0..af635ff16 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -110,7 +110,6 @@ class TorrentManager: # Make sure we are adding it with the correct allocation method. if compact is None: compact = config["compact_allocation"] - print "compact: ", compact try: handle = self.session.add_torrent( From cc8466988cae07e4031c3a474b6f37c4237265c8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 04:36:45 +0000 Subject: [PATCH 0084/1009] Add DEFAULT_PREFS. --- deluge/ui/gtkui/gtkui.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 9a859b753..6ff7badb6 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -41,9 +41,24 @@ import pkg_resources from mainwindow import MainWindow from signals import Signals from pluginmanager import PluginManager - +from deluge.config import Config from deluge.log import LOG as log +DEFAULT_PREFS = { + "interactive_add": False, + "enable_files_dialog": False, + "enable_system_tray": True, + "close_to_tray": True, + "start_in_tray": False, + "lock_tray": False, + "tray_password": "", + "open_folder_stock": True, + "stock_file_manager": 0, + "open_folder_location": "", + "check_new_releases": False, + "send_info": False +} + class GtkUI: def __init__(self): # Initialize gettext @@ -60,6 +75,10 @@ class GtkUI: pkg_resources.resource_filename( "deluge", "i18n")) + # Make sure gtkui.conf has at least the defaults set + config = Config("gtkui.conf", DEFAULT_PREFS) + del config + # Initialize the main window self.mainwindow = MainWindow() From c072e4d605de6878c9e28fa31f8b1831d48e66e2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 05:15:31 +0000 Subject: [PATCH 0085/1009] Preferences dialog now saves and displays gtkui specific config options. --- .../ui/gtkui/glade/preferences_dialog.glade | 8 +- deluge/ui/gtkui/preferences.py | 89 ++++++++++++++++--- 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index e09d0a440..4c7aa0c8c 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -8,7 +8,7 @@ Deluge Preferences GTK_WIN_POS_CENTER_ON_PARENT 500 - 500 + 520 True GDK_WINDOW_TYPE_HINT_DIALOG False @@ -1276,7 +1276,7 @@ Full Stream - + True False True @@ -1449,7 +1449,7 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index d6f49c6f9..d12586248 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -39,6 +39,7 @@ import pkg_resources from deluge.log import LOG as log import deluge.ui.functions as functions import deluge.common +from deluge.config import Config class Preferences: def __init__(self, window): @@ -81,19 +82,17 @@ class Preferences: """Add a another page to the notebook""" index = self.notebook.append_page(widget) self.liststore.append([index, name]) - - def get_core_config(self): - """Get the configuration from the core.""" - # Get the config dictionary from the core - self.core_config = functions.get_config(self.core) - + def show(self): - self.get_core_config() + self.core_config = functions.get_config(self.core) + self.gtkui_config = Config("gtkui.conf") # Update the preferences dialog to reflect current config settings ## Downloads tab ## - # FIXME: Add GtkUI specific prefs here - # Core specific options for Downloads tab + self.glade.get_widget("radio_ask_save").set_active( + self.gtkui_config["interactive_add"]) + self.glade.get_widget("radio_save_all_to").set_active( + not self.gtkui_config["interactive_add"]) # This one will need to be re-evaluated if the core is running on a # different machine.. We won't be able to use the local file browser to @@ -105,6 +104,8 @@ class Preferences: self.core_config["compact_allocation"]) self.glade.get_widget("radio_full_allocation").set_active( not self.core_config["compact_allocation"]) + self.glade.get_widget("chk_enable_files_dialog").set_active( + self.gtkui_config["enable_files_dialog"]) self.glade.get_widget("chk_prioritize_first_last_pieces").set_active( self.core_config["prioritize_first_last_pieces"]) @@ -149,7 +150,31 @@ class Preferences: self.core_config["max_upload_slots_per_torrent"]) ## Other tab ## - # All of it is UI only. + self.glade.get_widget("chk_use_tray").set_active( + self.gtkui_config["enable_system_tray"]) + self.glade.get_widget("chk_min_on_close").set_active( + self.gtkui_config["close_to_tray"]) + self.glade.get_widget("chk_start_in_tray").set_active( + self.gtkui_config["start_in_tray"]) + self.glade.get_widget("chk_lock_tray").set_active( + self.gtkui_config["lock_tray"]) + self.glade.get_widget("txt_tray_password").set_text( + self.gtkui_config["tray_password"]) + + self.glade.get_widget("combo_file_manager").set_active( + self.gtkui_config["stock_file_manager"]) + self.glade.get_widget("txt_open_folder_location").set_text( + self.gtkui_config["open_folder_location"]) + self.glade.get_widget("radio_open_folder_stock").set_active( + self.gtkui_config["open_folder_stock"]) + self.glade.get_widget("radio_open_folder_custom").set_active( + not self.gtkui_config["open_folder_stock"]) + + self.glade.get_widget("chk_new_releases").set_active( + self.gtkui_config["check_new_releases"]) + + self.glade.get_widget("chk_send_info").set_active( + self.gtkui_config["send_info"]) # Now show the dialog self.pref_dialog.show() @@ -158,7 +183,11 @@ class Preferences: """Sets all altered config values in the core""" # Get the values from the dialog new_core_config = {} + new_gtkui_config = {} + ## Downloads tab ## + new_gtkui_config["interactive_add"] = \ + self.glade.get_widget("radio_ask_save").get_active() new_core_config["download_location"] = \ self.glade.get_widget("download_path_button").get_filename() new_core_config["compact_allocation"] = \ @@ -166,6 +195,8 @@ class Preferences: new_core_config["prioritize_first_last_pieces"] = \ self.glade.get_widget( "chk_prioritize_first_last_pieces").get_active() + new_gtkui_config["enable_files_dialog"] = \ + self.glade.get_widget("chk_enable_files_dialog").get_active() ## Network tab ## listen_ports = [] @@ -207,6 +238,38 @@ class Preferences: self.glade.get_widget( "spin_max_upload_slots_per_torrent").get_value_as_int() + ## Other tab ## + new_gtkui_config["enable_system_tray"] = \ + self.glade.get_widget("chk_use_tray").get_active() + new_gtkui_config["close_to_tray"] = \ + self.glade.get_widget("chk_min_on_close").get_active() + new_gtkui_config["start_in_tray"] = \ + self.glade.get_widget("chk_start_in_tray").get_active() + new_gtkui_config["lock_tray"] = \ + self.glade.get_widget("chk_lock_tray").get_active() + new_gtkui_config["tray_password"] = \ + self.glade.get_widget("txt_tray_password").get_text() + + new_gtkui_config["stock_file_manager"] = \ + self.glade.get_widget("combo_file_manager").get_active() + new_gtkui_config["open_folder_location"] = \ + self.glade.get_widget("txt_open_folder_location").get_text() + new_gtkui_config["open_folder_stock"] = \ + self.glade.get_widget("radio_open_folder_stock").get_active() + + new_gtkui_config["check_new_releases"] = \ + self.glade.get_widget("chk_new_releases").get_active() + + new_gtkui_config["send_info"] = \ + self.glade.get_widget("chk_send_info").get_active() + + # GtkUI + for key in new_gtkui_config.keys(): + # The values do not match so this needs to be updated + if self.gtkui_config[key] != new_gtkui_config[key]: + self.gtkui_config[key] = new_gtkui_config[key] + + # Core config_to_set = {} for key in new_core_config.keys(): # The values do not match so this needs to be updated @@ -246,14 +309,14 @@ class Preferences: self.glade.get_widget("chk_lock_tray").set_sensitive(value) if value == True: lock = self.glade.get_widget("chk_lock_tray").get_active() - self.glade.get_widget("txt_tray_passwd").set_sensitive(lock) + self.glade.get_widget("txt_tray_password").set_sensitive(lock) self.glade.get_widget("password_label").set_sensitive(lock) else: - self.glade.get_widget("txt_tray_passwd").set_sensitive(value) + self.glade.get_widget("txt_tray_password").set_sensitive(value) self.glade.get_widget("password_label").set_sensitive(value) if widget == self.glade.get_widget("chk_lock_tray"): - self.glade.get_widget("txt_tray_passwd").set_sensitive(value) + self.glade.get_widget("txt_tray_password").set_sensitive(value) self.glade.get_widget("password_label").set_sensitive(value) # Disable the file manager combo box if custom is selected. From 7f8743df173293862c14246ca59bafad97c15157 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 05:23:59 +0000 Subject: [PATCH 0086/1009] Update TODO list. --- TODO | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index 3089f43af..b782985d1 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ -* Hook gtkui preferences up in the preferences dialog -* Add 'set config functions' in the core so when config options change the - appropriate function will be called to 'apply' those changes. This type of - system may need to be added to the gtkui as well. +* Add 'set config functions' in config.py so when config options change the + appropriate function will be called to 'apply' those changes. * Status icons for the torrentview * Have the ui better handle not being able to connect to the daemon. * Mainwindow state saving.. Size, location, vpane position, etc.. @@ -21,4 +19,6 @@ * Create a new status icon.. a red alert icon to show there is an error with the torrent.. ie, disk full alert and stuff like that.. * Add the tracker responses to the torrent details - +* About dialog +* Fast resume saving +* Restart daemon function From 6898d8dd17816da6e8433c5626cfe50832b8a410 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 06:10:39 +0000 Subject: [PATCH 0087/1009] Add alert status icon and the source svgs of the other status icons. --- deluge/data/pixmaps/alert.svg | 363 +++++++++++++++++++++++++++ deluge/data/pixmaps/alert16.png | Bin 0 -> 594 bytes deluge/data/pixmaps/downloading.svg | 364 ++++++++++++++++++++++++++++ deluge/data/pixmaps/inactive.svg | 303 +++++++++++++++++++++++ deluge/data/pixmaps/seeding.svg | 364 ++++++++++++++++++++++++++++ 5 files changed, 1394 insertions(+) create mode 100644 deluge/data/pixmaps/alert.svg create mode 100644 deluge/data/pixmaps/alert16.png create mode 100644 deluge/data/pixmaps/downloading.svg create mode 100644 deluge/data/pixmaps/inactive.svg create mode 100644 deluge/data/pixmaps/seeding.svg diff --git a/deluge/data/pixmaps/alert.svg b/deluge/data/pixmaps/alert.svg new file mode 100644 index 000000000..5442fa65e --- /dev/null +++ b/deluge/data/pixmaps/alert.svg @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + ! + + diff --git a/deluge/data/pixmaps/alert16.png b/deluge/data/pixmaps/alert16.png new file mode 100644 index 0000000000000000000000000000000000000000..1711bfb67730441a67aa2713909cf73124c45e26 GIT binary patch literal 594 zcmV-Y0?21 zcURhNq05#oy6D1n{|sps?z%0du7wCDt}Llf&?I6o(aD6|EMA`fDl z+1WBzS4*_ps#h%bsnymgmw%2rq*7m~*O#bP;p7CGO{mp$rl(7UVP?>wSbV|w_&4_U zU~>~@W)xd0!TdZVlRr2++n`brokYUIws)vjl_-$_*HsuBgM$MwjOTdXTeVU$`HCpA zxw`|)0>}CH!+ah@V1FO7*^h*wrIX9O;qp=mJZ~grrl#Qj9-`k#HL>k=78bs9cnJMIgdzAom?j(@sc7?gu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deluge/data/pixmaps/inactive.svg b/deluge/data/pixmaps/inactive.svg new file mode 100644 index 000000000..884f1e1d3 --- /dev/null +++ b/deluge/data/pixmaps/inactive.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deluge/data/pixmaps/seeding.svg b/deluge/data/pixmaps/seeding.svg new file mode 100644 index 000000000..b891cc1ff --- /dev/null +++ b/deluge/data/pixmaps/seeding.svg @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + + + + + From c5156df280cd78c2b1e822de4bafba141f635769 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 11:07:58 +0000 Subject: [PATCH 0088/1009] Add torrent from URL implemented. This is Marcos patch plus some more error checking and some modifications to make it work properly with TorrentManager. --- deluge/common.py | 20 +++++++ deluge/config.py | 1 + deluge/core/core.py | 23 ++++++++ deluge/core/torrentmanager.py | 5 +- deluge/ui/functions.py | 8 +++ deluge/ui/gtkui/addtorrenturl.py | 70 +++++++++++++++++++++++++ deluge/ui/gtkui/glade/main_window.glade | 1 + deluge/ui/gtkui/menubar.py | 2 + 8 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 deluge/ui/gtkui/addtorrenturl.py diff --git a/deluge/common.py b/deluge/common.py index b29f352af..54ccf5975 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -139,4 +139,24 @@ def ftime(seconds): return '%dw %dd' % (weeks, days) return 'unknown' +def is_url(url): + """A simple regex test to check if the URL is valid.""" + import re + return bool(re.search('^(https?|ftp)://', url)) +def fetch_url(url): + """Downloads a torrent file from a given + URL and checks the file's validity.""" + import urllib + from deluge.log import LOG as log + try: + filename, headers = urllib.urlretrieve(url) + except IOError: + log.debug("Network error while trying to fetch torrent from %s", url) + else: + if filename.endswith(".torrent") or headers["content-type"] ==\ + "application/x-bittorrent": + return filename + else: + log.debug("URL doesn't appear to be a valid torrent file: %s", url) + return None diff --git a/deluge/config.py b/deluge/config.py index 8ec226f5c..7cb0e6487 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -123,3 +123,4 @@ class Config: def __setitem__(self, key, value): self.set(key, value) + diff --git a/deluge/core/core.py b/deluge/core/core.py index 38de95e49..4f89cceb2 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -163,6 +163,29 @@ class Core(dbus.service.Object): # Return False because the torrent was not added successfully return False + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="b") + def add_torrent_url(self, url): + log.info("Attempting to add url %s", url) + + # Get the actual filename of the torrent from the url provided. + filename = url.split("/")[-1] + + # Get the .torrent file from the url + torrent_file = deluge.common.fetch_url(url) + if torrent_file is None: + return False + + # Dump the torrents file contents to a string + try: + filedump = open(torrent_file, "rb").read() + except IOError: + log.warning("Unable to open %s for reading.", torrent_file) + return False + + # Add the torrent to session + return self.add_torrent_file(filename, filedump) + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def remove_torrent(self, torrent_id): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index af635ff16..017959589 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -92,7 +92,10 @@ class TorrentManager: # Convert the filedump data array into a string of bytes if filedump is not None: - filedump = "".join(chr(b) for b in filedump) + # If the filedump is already of type str, then it's already been + # joined. + if type(filedump) is not str: + filedump = "".join(chr(b) for b in filedump) else: # Get the data from the file try: diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 6bcf4ac54..531f1423e 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -91,6 +91,14 @@ def add_torrent_file(torrent_files): # The torrent was not added successfully. log.warning("Torrent %s was not added successfully.", filename) +def add_torrent_url(torrent_url): + """Adds torrents to the core via url""" + core = get_core() + result = core.add_torrent_url(torrent_url) + if result is False: + # The torrent url was not added successfully. + log.warning("Torrent %s url was not added successfully.", torrent_url) + def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) diff --git a/deluge/ui/gtkui/addtorrenturl.py b/deluge/ui/gtkui/addtorrenturl.py new file mode 100644 index 000000000..9a0e69012 --- /dev/null +++ b/deluge/ui/gtkui/addtorrenturl.py @@ -0,0 +1,70 @@ +# +# addtorrenturl.py +# +# Copyright (C) 2007 Marcos Pinto ('markybob') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import pygtk +pygtk.require('2.0') +import gtk, gtk.glade +import gettext + +from deluge.config import Config +import deluge.common +import pkg_resources + +class AddTorrentUrl: + def __init__(self, parent=None): + """Set up url dialog""" + self.dlg = gtk.Dialog(title=_("Add torrent from URL"), parent=None, + buttons=(gtk.STOCK_CANCEL, 0, gtk.STOCK_OK, 1)) + self.dlg.set_icon(deluge.common.get_logo(32)) + self.dlg.set_default_response(1) + label = gtk.Label(_("Enter the URL of the .torrent to download")) + self.entry = gtk.Entry() + self.dlg.vbox.pack_start(label) + self.dlg.vbox.pack_start(self.entry) + clip = gtk.clipboard_get(selection='PRIMARY') + text = clip.wait_for_text() + if text: + text = text.strip() + if deluge.common.is_url(text): + self.entry.set_text(text) + + def run(self): + """Show url dialog and add torrent""" + self.dlg.show_all() + self.response = self.dlg.run() + url = self.entry.get_text() + self.dlg.destroy() + if self.response == 1: + return url + else: + return None diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index c0461fe09..dd79347db 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -41,6 +41,7 @@ True Add _URL True + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 01c725c10..98def96fd 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -101,6 +101,8 @@ class MenuBar: def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") + from addtorrenturl import AddTorrentUrl + functions.add_torrent_url(AddTorrentUrl().run()) def on_menuitem_clear_activate(self, data=None): log.debug("on_menuitem_clear_activate") From 8f796738d5a378dfe3af301b9f62ce09585714b2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 12:44:31 +0000 Subject: [PATCH 0089/1009] Initial import of ConfigManager. --- deluge/configmanager.py | 57 +++++++++++++++++++++++++++++ deluge/core/core.py | 6 +-- deluge/core/torrentmanager.py | 4 +- deluge/ui/gtkui/addtorrentdialog.py | 4 +- deluge/ui/gtkui/gtkui.py | 4 +- deluge/ui/gtkui/preferences.py | 4 +- deluge/ui/ui.py | 4 +- 7 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 deluge/configmanager.py diff --git a/deluge/configmanager.py b/deluge/configmanager.py new file mode 100644 index 000000000..aed3ad10e --- /dev/null +++ b/deluge/configmanager.py @@ -0,0 +1,57 @@ +# +# configmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log +from deluge.config import Config + +class _ConfigManager: + def __init__(self): + log.debug("ConfigManager started..") + self.config_files = {} + + def __del__(self): + del self.config_files + + def get_config(self, config_file, defaults=None): + """Get a reference to the Config object for this filename""" + # Create the config object if not already created + if config_file not in self.config_files.keys(): + self.config_files[config_file] = Config(config_file, defaults) + + return self.config_files[config_file] + +# Singleton functions +_configmanager = _ConfigManager() + +def ConfigManager(config, defaults=None): + return _configmanager.get_config(config, defaults) diff --git a/deluge/core/core.py b/deluge/core/core.py index 4f89cceb2..d5d4fae48 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -40,7 +40,7 @@ DBusGMainLoop(set_as_default=True) import gobject import deluge.libtorrent as lt -from deluge.config import Config +from deluge.configmanager import ConfigManager import deluge.common from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager @@ -81,8 +81,8 @@ class Core(dbus.service.Object): dbus.service.Object.__init__(self, bus_name, path) # Get config - self.config = Config("core.conf", DEFAULT_PREFS) - + self.config = ConfigManager("core.conf", DEFAULT_PREFS) + # Create the client fingerprint version = [] for value in deluge.common.get_version().split("."): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 017959589..464a941be 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -40,7 +40,7 @@ import os import deluge.libtorrent as lt import deluge.common -from deluge.config import Config +from deluge.configmanager import ConfigManager from deluge.core.torrent import Torrent from deluge.log import LOG as log @@ -85,7 +85,7 @@ class TorrentManager: """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) # Get the core config - config = Config("core.conf") + config = ConfigManager("core.conf") # Make sure 'filename' is a python string filename = str(filename) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 8d48a69e8..9966e58da 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -36,7 +36,7 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext -from deluge.config import Config +from deluge.configmanager import ConfigManager from deluge.log import LOG as log import deluge.common @@ -64,7 +64,7 @@ class AddTorrentDialog: self.chooser.add_filter(file_filter) # Load the 'default_load_path' from the config - self.config = Config("gtkui.conf") + self.config = ConfigManager("gtkui.conf") if self.config.get("default_load_path") is not None: self.chooser.set_current_folder( self.config.get("default_load_path")) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 6ff7badb6..fb29503aa 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -41,7 +41,7 @@ import pkg_resources from mainwindow import MainWindow from signals import Signals from pluginmanager import PluginManager -from deluge.config import Config +from deluge.configmanager import ConfigManager from deluge.log import LOG as log DEFAULT_PREFS = { @@ -76,7 +76,7 @@ class GtkUI: "deluge", "i18n")) # Make sure gtkui.conf has at least the defaults set - config = Config("gtkui.conf", DEFAULT_PREFS) + config = ConfigManager("gtkui.conf", DEFAULT_PREFS) del config # Initialize the main window diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index d12586248..1625b8b49 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -39,7 +39,7 @@ import pkg_resources from deluge.log import LOG as log import deluge.ui.functions as functions import deluge.common -from deluge.config import Config +from deluge.configmanager import ConfigManager class Preferences: def __init__(self, window): @@ -85,7 +85,7 @@ class Preferences: def show(self): self.core_config = functions.get_config(self.core) - self.gtkui_config = Config("gtkui.conf") + self.gtkui_config = ConfigManager("gtkui.conf") # Update the preferences dialog to reflect current config settings ## Downloads tab ## diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index e13b15d1f..6706c6a68 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -from deluge.config import Config +from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -42,7 +42,7 @@ DEFAULT_PREFS = { class UI: def __init__(self): log.debug("UI init..") - self.config = Config("ui.conf", DEFAULT_PREFS) + self.config = ConfigManager("ui.conf", DEFAULT_PREFS) if self.config["selected_ui"] == "gtk": log.info("Starting GtkUI..") From b6faa81c2683578eaf2c1d933a2a4f9434f1acaf Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Sep 2007 13:55:47 +0000 Subject: [PATCH 0090/1009] Upload actual status icon source svgs and update the alert status icon to be consistent with the rest. --- deluge/data/pixmaps/alert.svg | 29 +++++++++---------- deluge/data/pixmaps/alert16.png | Bin 594 -> 586 bytes deluge/data/pixmaps/downloading.svg | 26 ++++------------- deluge/data/pixmaps/inactive.svg | 43 ++++++++++++---------------- deluge/data/pixmaps/seeding.svg | 32 ++++++--------------- 5 files changed, 47 insertions(+), 83 deletions(-) diff --git a/deluge/data/pixmaps/alert.svg b/deluge/data/pixmaps/alert.svg index 5442fa65e..864a0e10c 100644 --- a/deluge/data/pixmaps/alert.svg +++ b/deluge/data/pixmaps/alert.svg @@ -20,7 +20,7 @@ inkscape:export-xdpi="32.603073" inkscape:export-ydpi="32.603073" inkscape:output_extension="org.inkscape.output.svg.inkscape" - sodipodi:modified="true"> + sodipodi:modified="TRUE"> - ! + x="5.0072942" + y="59.560848" + style="font-size:55.97062302px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;stroke-width:0.70000001;font-family:Bitstream Vera Sans;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:round">! diff --git a/deluge/data/pixmaps/alert16.png b/deluge/data/pixmaps/alert16.png index 1711bfb67730441a67aa2713909cf73124c45e26..19a27ddb4f347d028060a4c58abe02f5d52fbc49 100644 GIT binary patch delta 486 zcmVE=fNBPT=ynOoVfWl*=!q)e^DoxBmxHsoS!;s(%DT8V!-f#mD~!U`bY1 zen=)Wv2|Uf*^C5k%mbQ;0Dx`3S0dZ53I(p^^Nf_Dc6dmmQu)r&Q4^5>KnEb3eMBy| zE!{4z>$0`E$<0=aVH80SRH;_qa3&p1TcTcnPP@&8>h-{~?vTqpoB*ZLU53LfNpdj& z(CI*}_7qJsb$`m`dvv=1XiSy>phVE8^CbzyagBwA2RdQ6#(2zhpyc!v0Dmx`G8)aQ zcpg-%PjocxWWM_EAYc%Nw0)ne^MO*JlxC7j_2>QT`}=qY2e{LArm`%EqMtgM%r^`J z00BPRHUr1OcN~1voB_om^!s0Rj*p+QygUX_KyiJYtzX?;Hg07*qoM6N<$fUl_(~XR!}Q=l7_Kle@qd|01vG7gbo$fqfF>dj zVw>67GFMkiwA-pzEcU6@)+v{Njyj}LU#QoYs8-?R1e#5#)pVw(ON3!&(4km-!T9($ z_V!?N6J}-*QW2d*!os$9s8*FIkpS0K7#o9w12Bx|c-~vJQZo69 zD6+Y`1Iq%(`G5Drd>%w#e;=~hkA$J6lgqu~@=^&rZzN@=rr`b_qUaHh^IAvO16nOb zI*uYADvH1`l+*b+ @@ -337,28 +337,14 @@ d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z " id="path2069" sodipodi:nodetypes="ccccc" /> - - - - - + diff --git a/deluge/data/pixmaps/inactive.svg b/deluge/data/pixmaps/inactive.svg index 884f1e1d3..5edd9bece 100644 --- a/deluge/data/pixmaps/inactive.svg +++ b/deluge/data/pixmaps/inactive.svg @@ -15,7 +15,7 @@ sodipodi:version="0.32" inkscape:version="0.45.1" sodipodi:docbase="/home/andrew/Projects/deluge-icons" - sodipodi:docname="inactive.svg" + sodipodi:docname="inactive-border.svg" inkscape:export-filename="/home/andrew/Projects/deluge-icons/inactive16.png" inkscape:export-xdpi="32.603073" inkscape:export-ydpi="32.603073" @@ -204,15 +204,15 @@ inkscape:pageshadow="2" inkscape:zoom="8" inkscape:cx="36.250498" - inkscape:cy="37.884169" + inkscape:cy="23.072085" inkscape:current-layer="layer1" showgrid="false" inkscape:grid-bbox="true" inkscape:document-units="px" - inkscape:window-width="1266" - inkscape:window-height="944" - inkscape:window-x="12" - inkscape:window-y="34" + inkscape:window-width="1278" + inkscape:window-height="978" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:showpageshadow="false" /> @@ -268,36 +268,31 @@ inkscape:groupmode="layer"> - + id="g3590" + transform="matrix(0.9616363,0,0,0.855461,0.9207282,3.9296533)"> + style="fill:#cfcfcf;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:#cfcfcf;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/deluge/data/pixmaps/seeding.svg b/deluge/data/pixmaps/seeding.svg index b891cc1ff..0e32ddac2 100644 --- a/deluge/data/pixmaps/seeding.svg +++ b/deluge/data/pixmaps/seeding.svg @@ -15,7 +15,7 @@ sodipodi:version="0.32" inkscape:version="0.45.1" sodipodi:docbase="/home/andrew/Projects/deluge-icons" - sodipodi:docname="seeding.svg" + sodipodi:docname="seeding-border.png.svg" inkscape:export-filename="/home/andrew/Projects/deluge-icons/seeding16.png" inkscape:export-xdpi="32.603073" inkscape:export-ydpi="32.603073" @@ -275,10 +275,10 @@ showgrid="false" inkscape:grid-bbox="true" inkscape:document-units="px" - inkscape:window-width="794" - inkscape:window-height="622" - inkscape:window-x="395" - inkscape:window-y="67" + inkscape:window-width="1278" + inkscape:window-height="978" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:showpageshadow="false" /> @@ -337,28 +337,14 @@ d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z " id="path2069" sodipodi:nodetypes="ccccc" /> - - - - - + From d953c8d8b46ff5468b42edb1b5e5ca04756618e0 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 17 Sep 2007 20:38:32 +0000 Subject: [PATCH 0091/1009] url checks for add torrent --- deluge/ui/gtkui/addtorrenturl.py | 2 +- deluge/ui/gtkui/menubar.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/addtorrenturl.py b/deluge/ui/gtkui/addtorrenturl.py index 9a0e69012..5145fa66b 100644 --- a/deluge/ui/gtkui/addtorrenturl.py +++ b/deluge/ui/gtkui/addtorrenturl.py @@ -64,7 +64,7 @@ class AddTorrentUrl: self.response = self.dlg.run() url = self.entry.get_text() self.dlg.destroy() - if self.response == 1: + if self.response == 1 and deluge.common.is_url(url): return url else: return None diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 98def96fd..7252806b6 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -102,7 +102,9 @@ class MenuBar: def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") from addtorrenturl import AddTorrentUrl - functions.add_torrent_url(AddTorrentUrl().run()) + result = AddTorrentUrl().run() + if result is not None: + functions.add_torrent_url(result) def on_menuitem_clear_activate(self, data=None): log.debug("on_menuitem_clear_activate") From 25271af9f35fbf5ad0587a3c0042ba7202058d6f Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 17 Sep 2007 20:42:37 +0000 Subject: [PATCH 0092/1009] remove unused import --- deluge/ui/gtkui/addtorrenturl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/addtorrenturl.py b/deluge/ui/gtkui/addtorrenturl.py index 5145fa66b..d97fb0152 100644 --- a/deluge/ui/gtkui/addtorrenturl.py +++ b/deluge/ui/gtkui/addtorrenturl.py @@ -33,10 +33,9 @@ import pygtk pygtk.require('2.0') -import gtk, gtk.glade +import gtk import gettext -from deluge.config import Config import deluge.common import pkg_resources From 0a7789f6491af71d7e572c338ab0a32253fa8c15 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 18 Sep 2007 01:52:32 +0000 Subject: [PATCH 0093/1009] add about dialog and open_url_in_browser --- TODO | 1 - deluge/ui/functions.py | 11 + deluge/ui/gtkui/aboutdialog.py | 76 ++ deluge/ui/gtkui/glade/main_window.glade | 931 ++++++++++++------------ deluge/ui/gtkui/menubar.py | 3 + 5 files changed, 556 insertions(+), 466 deletions(-) create mode 100644 deluge/ui/gtkui/aboutdialog.py diff --git a/TODO b/TODO index b782985d1..e93528a79 100644 --- a/TODO +++ b/TODO @@ -19,6 +19,5 @@ * Create a new status icon.. a red alert icon to show there is an error with the torrent.. ie, disk full alert and stuff like that.. * Add the tracker responses to the torrent details -* About dialog * Fast resume saving * Restart daemon function diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 531f1423e..5de6bc472 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -159,3 +159,14 @@ def get_listen_port(core=None): core = get_core() return int(core.get_listen_port()) +def open_url_in_browser(link): + """Opens link in the desktop's default browser""" + import threading + import webbrowser + class BrowserThread(threading.Thread): + def __init__(self, link): + threading.Thread.__init__(self) + self.url = link + def run(self): + webbrowser.open(self.url) + BrowserThread(link).start() diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py new file mode 100644 index 000000000..4980c3eb9 --- /dev/null +++ b/deluge/ui/gtkui/aboutdialog.py @@ -0,0 +1,76 @@ +# +# self.aboutdialog.py +# +# Copyright (C) 2007 Marcos Pinto ('markybob') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import pygtk +pygtk.require('2.0') +import gtk +import pkg_resources + +import deluge.common +import deluge.ui.functions as functions + +class AboutDialog: + def __init__(self): + # Get the glade file for the about dialog + def url_hook(dialog, url): + functions.open_url_in_browser(url) + gtk.about_dialog_set_url_hook(url_hook) + self.about = gtk.glade.XML(pkg_resources.resource_filename(\ + "deluge.ui.gtkui", "glade/aboutdialog.glade")).get_widget(\ + "aboutdialog") + self.about.set_position(gtk.WIN_POS_CENTER) + self.about.set_name("Deluge") + self.about.set_version(deluge.common.get_version()) + self.about.set_authors(["Andrew Resch", "Marcos Pinto"]) + self.about.set_artists(["Andrew Wedderburn"]) + self.about.set_translator_credits(_("translator-credits")) + self.about.set_license(_("Deluge is free software, you can redistribute \ +it and/or\nmodify it under the terms of the GNU General Public\n License as \ +published by the Free Software Foundation,\neither version 2 of the License, \ +or (at your option) any\nlater version. Deluge is distributed in the hope \ +that it\nwill be useful, but WITHOUT ANY WARRANTY, without even \nthe implied \ +warranty of MERCHANTABILITY or FITNESS\nFOR A PARTICULAR PURPOSE. See the GNU \ +General\nPublic License for more details. You should have received\na copy of \ +the GNU General Public License along with\nDeluge, but if not, write to the \ +Free Software Foundation,\n Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110\ +-\n1301 USA")) + self.about.set_website("http://deluge-torrent.org") + self.about.set_website_label("http://deluge-torrent.org") + self.about.set_icon(deluge.common.get_logo(32)) + self.about.set_logo(gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("deluge-about.png"))) + + def run(self): + self.about.show_all() + self.about.run() + self.about.destroy() diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index dd79347db..04a171142 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -172,6 +172,7 @@ gtk-about True True + @@ -347,6 +348,376 @@ 1 2 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + True @@ -367,33 +738,54 @@ 2 2 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + True 0 1 2 - 5 - 6 + 2 + 3 - + True 0 1 2 - 4 - 5 + 1 + 2 - + True 0 True @@ -402,77 +794,27 @@ 1 2 - 3 - 4 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True 0 - 0 1 - <b>Name:</b> + <b>Total Size:</b> True - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - - - 4 - 5 + 1 + 2 GTK_FILL @@ -505,28 +847,76 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + True 0 1 - <b>Total Size:</b> + <b>Next Announce:</b> True - 1 - 2 + 5 + 6 GTK_FILL - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + True 0 True @@ -535,56 +925,37 @@ 1 2 + 3 + 4 - + True 0 1 2 - 1 - 2 + 4 + 5 - + True 0 1 2 - 2 - 3 + 5 + 6 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 2 - 3 - GTK_FILL - - @@ -607,376 +978,6 @@ GTK_FILL - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 7252806b6..85e17d09c 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -156,3 +156,6 @@ class MenuBar: ## Help Menu ## def on_menuitem_about_activate(self, data=None): log.debug("on_menuitem_about_activate") + from aboutdialog import AboutDialog + AboutDialog().run() + From 7338806613d14d73fee7cb915f9020b6ccd4532b Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 18 Sep 2007 02:12:30 +0000 Subject: [PATCH 0094/1009] fix header --- deluge/ui/gtkui/aboutdialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 4980c3eb9..2c596580d 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -1,5 +1,5 @@ # -# self.aboutdialog.py +# aboutdialog.py # # Copyright (C) 2007 Marcos Pinto ('markybob') # From e3188695c6d48f4e90e2428a0d43aad9d73960c6 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 18 Sep 2007 02:25:32 +0000 Subject: [PATCH 0095/1009] check url in functions instead --- deluge/ui/functions.py | 10 ++++++---- deluge/ui/gtkui/addtorrenturl.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 5de6bc472..c1cbbcd4a 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -94,10 +94,12 @@ def add_torrent_file(torrent_files): def add_torrent_url(torrent_url): """Adds torrents to the core via url""" core = get_core() - result = core.add_torrent_url(torrent_url) - if result is False: - # The torrent url was not added successfully. - log.warning("Torrent %s url was not added successfully.", torrent_url) + from deluge.common import is_url + if is_url(torrent_url): + result = core.add_torrent_url(torrent_url) + if result is False: + # The torrent url was not added successfully. + log.warning("Torrent %s url was not added successfully.", torrent_url) def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" diff --git a/deluge/ui/gtkui/addtorrenturl.py b/deluge/ui/gtkui/addtorrenturl.py index d97fb0152..5f11c1a4d 100644 --- a/deluge/ui/gtkui/addtorrenturl.py +++ b/deluge/ui/gtkui/addtorrenturl.py @@ -63,7 +63,7 @@ class AddTorrentUrl: self.response = self.dlg.run() url = self.entry.get_text() self.dlg.destroy() - if self.response == 1 and deluge.common.is_url(url): + if self.response == 1: return url else: return None From 9370d814441934cffb68bc5d605e21c4885b2b85 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Sep 2007 03:18:43 +0000 Subject: [PATCH 0096/1009] Add extra output in add_torrent_url() and update TODO list. --- TODO | 2 -- deluge/ui/functions.py | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index e93528a79..da5a7caf2 100644 --- a/TODO +++ b/TODO @@ -16,8 +16,6 @@ * Figure out easy way for user-made plugins to add i18n support. * Change the menubar.py gtkui component to menus.py and add support for plugins to add menuitems to the torrentmenu in an easy way. -* Create a new status icon.. a red alert icon to show there is an error with - the torrent.. ie, disk full alert and stuff like that.. * Add the tracker responses to the torrent details * Fast resume saving * Restart daemon function diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index c1cbbcd4a..304f729d0 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -99,7 +99,9 @@ def add_torrent_url(torrent_url): result = core.add_torrent_url(torrent_url) if result is False: # The torrent url was not added successfully. - log.warning("Torrent %s url was not added successfully.", torrent_url) + log.warning("Torrent %s was not added successfully.", torrent_url) + else: + log.warning("Invalid URL %s", torrent_url) def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" From e4ebef6ae38899c5362f4f733f51b784041508f8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Sep 2007 04:47:28 +0000 Subject: [PATCH 0097/1009] Properly shutdown ConfigManager. --- deluge/configmanager.py | 5 +++-- deluge/core/core.py | 2 ++ deluge/ui/gtkui/gtkui.py | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/deluge/configmanager.py b/deluge/configmanager.py index aed3ad10e..543492f36 100644 --- a/deluge/configmanager.py +++ b/deluge/configmanager.py @@ -38,10 +38,11 @@ class _ConfigManager: def __init__(self): log.debug("ConfigManager started..") self.config_files = {} - + def __del__(self): + log.debug("ConfigManager stopping..") del self.config_files - + def get_config(self, config_file, defaults=None): """Get a reference to the Config object for this filename""" # Create the config object if not already created diff --git a/deluge/core/core.py b/deluge/core/core.py index d5d4fae48..30e4aeb3b 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -142,6 +142,8 @@ class Core(dbus.service.Object): del self.torrents self.plugins.shutdown() del self.plugins + del self.config + del deluge.configmanager del self.session @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index fb29503aa..8d6acd372 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -43,6 +43,7 @@ from signals import Signals from pluginmanager import PluginManager from deluge.configmanager import ConfigManager from deluge.log import LOG as log +import deluge.configmanager DEFAULT_PREFS = { "interactive_add": False, @@ -93,3 +94,9 @@ class GtkUI: # Start the gtk main loop gtk.main() + + # Clean-up + del self.mainwindow + del self.signal_receiver + del self.plugins + del deluge.configmanager From 685e20fbf10a93fd688f8b9187088bf692e8c171 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Sep 2007 06:26:22 +0000 Subject: [PATCH 0098/1009] Only write a config file to disk if there is a change in the configuration. --- deluge/config.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/deluge/config.py b/deluge/config.py index 7cb0e6487..69845d9bc 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -84,6 +84,20 @@ class Config: # Saves the config dictionary if filename is None: filename = self.config_file + # Check to see if the current config differs from the one on disk + # We will only write a new config file if there is a difference + try: + log.debug("Opening pickled file for comparison..") + pkl_file = open(filename, "rb") + filedump = pickle.load(pkl_file) + pkl_file.close() + if filedump == self.config: + # The config has not changed so lets just return + log.debug("Not writing config file due to no changes..") + return + except IOError: + log.warning("IOError: Unable to open file: '%s'", filename) + try: log.debug("Opening pickled file for save..") pkl_file = open(filename, "wb") From 66192d0f8dd179b8ccbfbff3fe6f9abae8e288c3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Sep 2007 14:40:19 +0000 Subject: [PATCH 0099/1009] Add 'set functions' to Config.. These functions get called when the specified key's value changes. --- deluge/config.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index 69845d9bc..6f551c2ea 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -45,6 +45,8 @@ class Config: log.debug("Config created with filename: %s", filename) log.debug("Config defaults: %s", defaults) self.config = {} + self.set_functions = {} + # If defaults is not None then we need to use "defaults". if defaults != None: self.config = defaults @@ -111,9 +113,18 @@ class Config: """Set the 'key' with 'value'.""" # Sets the "key" with "value" in the config dict log.debug("Setting '%s' to %s", key, value) - self.config[key] = value - # Whenever something is set, we should save - self.save() + if self.config[key] != value: + self.config[key] = value + # Whenever something is set, we should save + self.save() + # Run the set_function for this key if any + try: + self.set_functions[key](value) + except KeyError: + pass + else: + log.debug("Not set as value is same.") + def get(self, key): """Get the value of 'key'. If it is an invalid key then get() will @@ -131,7 +142,12 @@ class Config: def get_config(self): """Returns the entire configuration as a dictionary.""" return self.config - + + def register_set_function(self, key, function): + """Register a function to be run when a config value changes.""" + self.set_functions[key] = function + return + def __getitem__(self, key): return self.config[key] From ec5c970499f8f0b8d78548a5c080b771c4e027d3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 20 Sep 2007 19:56:05 +0000 Subject: [PATCH 0100/1009] Add 'deluged' script to start the deluge daemon. --- deluge/main.py | 8 ++++++++ setup.py | 1 + 2 files changed, 9 insertions(+) diff --git a/deluge/main.py b/deluge/main.py index e1db010d5..6a5ee878b 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -80,3 +80,11 @@ def main(): if options.ui: log.info("Starting ui..") UI() + +def start_daemon(): + """Entry point for daemon script""" + log.info("Deluge daemon %s", deluge.common.get_version()) + log.info("Starting daemon..") + pid = os.fork() + if not pid: + Daemon() diff --git a/setup.py b/setup.py index 1083eb582..1f098d887 100644 --- a/setup.py +++ b/setup.py @@ -195,4 +195,5 @@ setup( entry_points = """ [console_scripts] deluge = deluge.main:main + deluged = deluge.main:start_daemon """) From d3c50643539f70a8145f0e14511acbd5b4ba3acb Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 20 Sep 2007 22:09:23 +0000 Subject: [PATCH 0101/1009] Add upnp, natpmp and pe_settings support to libtorrent bindings. --- libtorrent/bindings/python/src/docstrings.cpp | 13 ++++++++++++- libtorrent/bindings/python/src/session.cpp | 12 ++++++++++++ libtorrent/bindings/python/src/session_settings.cpp | 5 +++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index f51487a23..fbd0a157c 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -156,11 +156,22 @@ char const* session_set_max_half_open_connections_doc = char const* session_set_settings_doc = ""; +char const* session_set_pe_settings_doc = + ""; +char const* session_get_pe_settings_doc = + ""; char const* session_set_severity_level_doc = ""; char const* session_pop_alert_doc = ""; - +char const* session_start_upnp_doc = + ""; +char const* session_stop_upnp_doc = + ""; + char const* session_start_natpmp_doc = + ""; +char const* session_stop_natpmp_doc = + ""; // -- alert ----------------------------------------------------------------- char const* alert_doc = diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 9f7d530af..4ea7a1711 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -45,8 +45,14 @@ extern char const* session_set_max_uploads_doc; extern char const* session_set_max_connections_doc; extern char const* session_set_max_half_open_connections_doc; extern char const* session_set_settings_doc; +extern char const* session_set_pe_settings_doc; +extern char const* session_get_pe_settings_doc; extern char const* session_set_severity_level_doc; extern char const* session_pop_alert_doc; +extern char const* session_start_upnp_doc; +extern char const* session_stop_upnp_doc; +extern char const* session_start_natpmp_doc; +extern char const* session_stop_natpmp_doc; namespace { @@ -205,6 +211,8 @@ void bind_session() , session_set_max_half_open_connections_doc ) .def("set_settings", allow_threads(&session::set_settings), session_set_settings_doc) + .def("set_pe_settings", allow_threads(&session::set_pe_settings), session_set_pe_settings_doc) + .def_readonly("get_pe_settings", allow_threads(&session::get_pe_settings), session_get_pe_settings_doc) .def( "set_severity_level", allow_threads(&session::set_severity_level) , session_set_severity_level_doc @@ -217,6 +225,10 @@ void bind_session() #ifndef TORRENT_DISABLE_DHT .def("set_dht_proxy", allow_threads(&session::set_dht_proxy)) #endif + .def("start_upnp", allow_threads(&session::start_upnp), session_start_upnp_doc) + .def("stop_upnp", allow_threads(&session::stop_upnp), session_stop_upnp_doc) + .def("start_natpmp", allow_threads(&session::start_natpmp), session_start_natpmp_doc) + .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) ; def("supports_sparse_files", &supports_sparse_files); diff --git a/libtorrent/bindings/python/src/session_settings.cpp b/libtorrent/bindings/python/src/session_settings.cpp index 2c7474c21..f584956b2 100755 --- a/libtorrent/bindings/python/src/session_settings.cpp +++ b/libtorrent/bindings/python/src/session_settings.cpp @@ -47,7 +47,7 @@ void bind_session_settings() .value("http", proxy_settings::http) .value("http_pw", proxy_settings::http_pw) ; - scope ps = class_("proxy_settings") + class_("proxy_settings") .def_readwrite("hostname", &proxy_settings::hostname) .def_readwrite("port", &proxy_settings::port) .def_readwrite("password", &proxy_settings::password) @@ -64,9 +64,10 @@ void bind_session_settings() enum_("enc_level") .value("rc4", pe_settings::rc4) .value("plaintext", pe_settings::plaintext) + .value("both", pe_settings::both) ; - scope pes = class_("pe_settings") + class_("pe_settings") .def_readwrite("out_enc_policy", &pe_settings::out_enc_policy) .def_readwrite("in_enc_policy", &pe_settings::in_enc_policy) .def_readwrite("allowed_enc_level", &pe_settings::allowed_enc_level) From 943a62f62ce9faadda4b8c889348a899de418294 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 20 Sep 2007 22:10:03 +0000 Subject: [PATCH 0102/1009] Have all core config use set functions to apply the configuration to the session. --- deluge/common.py | 12 +++ deluge/config.py | 11 ++- deluge/core/core.py | 139 +++++++++++++++++++++++++++++----- deluge/core/torrentmanager.py | 20 ++++- 4 files changed, 160 insertions(+), 22 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 54ccf5975..7490de859 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -38,6 +38,18 @@ import os import pkg_resources import xdg, xdg.BaseDirectory + +TORRENT_STATE = [ + "Queued", + "Checking", + "Connecting", + "Downloading Metadata", + "Downloading", + "Finished", + "Seeding", + "Allocating" +] + def get_version(): """Returns the program version from the egg metadata""" return pkg_resources.require("Deluge")[0].version diff --git a/deluge/config.py b/deluge/config.py index 6f551c2ea..8db92a86c 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -119,7 +119,7 @@ class Config: self.save() # Run the set_function for this key if any try: - self.set_functions[key](value) + self.set_functions[key](key, value) except KeyError: pass else: @@ -145,9 +145,16 @@ class Config: def register_set_function(self, key, function): """Register a function to be run when a config value changes.""" + log.debug("Registering function for %s key..", key) self.set_functions[key] = function return - + + def apply_all(self): + """Runs all set functions""" + log.debug("Running all set functions..") + for key in self.set_functions.keys(): + self.set_functions[key](key, self.config[key]) + def __getitem__(self, key): return self.config[key] diff --git a/deluge/core/core.py b/deluge/core/core.py index 30e4aeb3b..3f0d9d862 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -98,28 +98,9 @@ class Core(dbus.service.Object): self.settings.user_agent = "Deluge %s" % deluge.common.get_version() self.session.set_settings(self.settings) - # Set the listening ports - if self.config["random_port"]: - import random - listen_ports = [] - randrange = lambda: random.randrange(49152, 65525) - listen_ports.append(randrange()) - listen_ports.append(listen_ports[0]+10) - else: - listen_ports = self.config["listen_ports"] - - log.debug("Listening on ports %i-%i", listen_ports[0], - listen_ports[1]) - self.session.listen_on(listen_ports[0], - listen_ports[1]) - # Load metadata extension self.session.add_extension(lt.create_metadata_plugin) - # Load utorrent peer-exchange - if self.config["utpex"]: - self.session.add_extension(lt.create_ut_pex_plugin) - # Start the TorrentManager self.torrents = TorrentManager(self.session) @@ -129,6 +110,39 @@ class Core(dbus.service.Object): # Start the AlertManager self.alerts = AlertManager(self.session) + # Register set functions in the Config + self.config.register_set_function("listen_ports", + self.on_set_listen_ports) + self.config.register_set_function("random_port", + self.on_set_random_port) + self.config.register_set_function("dht", self.on_set_dht) + self.config.register_set_function("upnp", self.on_set_upnp) + self.config.register_set_function("natpmp", self.on_set_natpmp) + self.config.register_set_function("utpex", self.on_set_utpex) + self.config.register_set_function("enc_in_policy", + self.on_set_encryption) + self.config.register_set_function("enc_out_policy", + self.on_set_encryption) + self.config.register_set_function("enc_level", + self.on_set_encryption) + self.config.register_set_function("enc_prefer_rc4", + self.on_set_encryption) + self.config.register_set_function("max_connections_global", + self.on_set_max_connections_global) + self.config.register_set_function("max_upload_speed", + self.on_set_max_upload_speed) + self.config.register_set_function("max_download_speed", + self.on_set_max_download_speed) + self.config.register_set_function("max_upload_slots_global", + self.on_set_max_upload_slots_global) + self.config.register_set_function("max_connections_per_torrent", + self.on_set_max_connections_per_torrent) + self.config.register_set_function("max_upload_slots_per_torrent", + self.on_set_max_upload_slots_per_torrent) + + # Run all the set functions now to set the config for the session + self.config.apply_all() + log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() @@ -301,3 +315,90 @@ class Core(dbus.service.Object): def torrent_resumed(self, torrent_id): """Emitted when a torrent is resumed""" log.debug("torrent_resumed signal emitted") + + # Config set functions + def on_set_listen_ports(self, key, value): + # Only set the listen ports if random_port is not true + if self.config["random_port"] is not True: + log.debug("listen port range set to %s-%s", value[0], value[1]) + self.session.listen_on(value[0], value[1]) + + def on_set_random_port(self, key, value): + log.debug("random port value set to %s", value) + # We need to check if the value has been changed to true and false + # and then handle accordingly. + if value: + import random + listen_ports = [] + randrange = lambda: random.randrange(49152, 65525) + listen_ports.append(randrange()) + listen_ports.append(listen_ports[0]+10) + else: + listen_ports = self.config["listen_ports"] + + # Set the listen ports + log.debug("listen port range set to %s-%s", listen_ports[0], + listen_ports[1]) + self.session.listen_on(listen_ports[0], listen_ports[1]) + + def on_set_dht(self, key, value): + log.debug("dht value set to %s", value) + if value: + self.session.start_dht(None) + else: + self.session.stop_dht() + + def on_set_upnp(self, key, value): + log.debug("upnp value set to %s", value) + if value: + self.session.start_upnp() + else: + self.session.stop_upnp() + + def on_set_natpmp(self, key, value): + log.debug("natpmp value set to %s", value) + if value: + self.session.start_natpmp() + else: + self.session.stop_natpmp() + + def on_set_utpex(self, key, value): + log.debug("utpex value set to %s", value) + if value: + self.session.add_extension(lt.create_ut_pex_plugin) + + def on_set_encryption(self, key, value): + log.debug("encryption value %s set to %s..", key, value) + pe_settings = lt.pe_settings() + pe_settings.out_enc_policy = \ + lt.enc_policy(self.config["enc_out_policy"]) + pe_settings.in_enc_policy = lt.enc_policy(self.config["enc_in_policy"]) + pe_settings.allow_enc_level = lt.enc_level(self.config["enc_level"]) + pe_settings.prefer_rc4 = self.config["enc_prefer_rc4"] + self.session.set_pe_settings(pe_settings) + + def on_set_max_connections_global(self, key, value): + log.debug("max_connections_global set to %s..", value) + self.session.set_max_connections(value) + + def on_set_max_upload_speed(self, key, value): + log.debug("max_upload_speed set to %s..", value) + # We need to convert Kb/s to B/s + self.session.set_upload_rate_limit(int(value * 1024)) + + def on_set_max_download_speed(self, key, value): + log.debug("max_download_speed set to %s..", value) + # We need to convert Kb/s to B/s + self.session.set_download_rate_limit(int(value * 1024)) + + def on_set_max_upload_slots_global(self, key, value): + log.debug("max_upload_slots_global set to %s..", value) + self.session.set_max_uploads(value) + + def on_set_max_connections_per_torrent(self, key, value): + log.debug("max_connections_per_torrent set to %s..", value) + self.torrents.set_max_connections(value) + + def on_set_max_upload_slots_per_torrent(self, key, value): + log.debug("max_upload_slots_per_torrent set to %s..", value) + self.torrents.set_max_uploads(value) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 464a941be..baf6e7c55 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -63,6 +63,9 @@ class TorrentManager: log.debug("TorrentManager init..") # Set the libtorrent session self.session = session + # Per torrent connection limit and upload slot limit + self.max_connections = -1 + self.max_uploads = -1 # Create the torrents dict { torrent_id: Torrent } self.torrents = {} # Try to load the state from file @@ -126,6 +129,10 @@ class TorrentManager: # The torrent was not added to the session return None + # Set per-torrent limits + handle.set_max_connections(self.max_connections) + handle.set_max_uploads(self.max_uploads) + log.debug("Attemping to save torrent file: %s", filename) # Test if the torrentfiles_location is accessible if os.access(os.path.join(config["torrentfiles_location"]), os.F_OK) \ @@ -224,4 +231,15 @@ class TorrentManager: state_file.close() except IOError: log.warning("Unable to save state file.") - + + def set_max_connections(self, value): + """Sets the per-torrent connection limit""" + self.max_connections = value + for key in self.torrents.keys(): + self.torrents[key].handle.set_max_connections(value) + + def set_max_uploads(self, value): + """Sets the per-torrent upload slot limit""" + self.max_uploads = value + for key in self.torrents.keys(): + self.torrents[key].handle.set_max_uploads(value) From ff13e8c2f7b191480de097c0bac31dbab87e4775 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 20 Sep 2007 22:21:43 +0000 Subject: [PATCH 0103/1009] Update TODO. --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index da5a7caf2..c94243c77 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -* Add 'set config functions' in config.py so when config options change the - appropriate function will be called to 'apply' those changes. * Status icons for the torrentview * Have the ui better handle not being able to connect to the daemon. * Mainwindow state saving.. Size, location, vpane position, etc.. From d44632f0a9cf0a47532f6226b1ca84ec3e5531d4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 20 Sep 2007 22:57:16 +0000 Subject: [PATCH 0104/1009] Add 'default_load_path' to default prefs. --- deluge/ui/gtkui/gtkui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 8d6acd372..0b37a1348 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -57,7 +57,8 @@ DEFAULT_PREFS = { "stock_file_manager": 0, "open_folder_location": "", "check_new_releases": False, - "send_info": False + "send_info": False, + "default_load_path": None } class GtkUI: From b2fe562dd4fa667fa27d9026c33c2da8552af1a7 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 20 Sep 2007 23:43:07 +0000 Subject: [PATCH 0105/1009] libtorrent sync 1592 --- libtorrent/bindings/python/src/alert.cpp | 15 +- libtorrent/bindings/python/src/docstrings.cpp | 21 +- libtorrent/bindings/python/src/extensions.cpp | 1 - libtorrent/bindings/python/src/session.cpp | 9 +- .../bindings/python/src/session_settings.cpp | 4 +- .../bindings/python/src/torrent_info.cpp | 31 +- libtorrent/include/asio/CVS/Entries | 45 - libtorrent/include/asio/CVS/Repository | 1 - libtorrent/include/asio/CVS/Root | 1 - libtorrent/include/asio/basic_socket.hpp | 6 + libtorrent/include/asio/buffer.hpp | 7 +- .../include/asio/completion_condition.hpp | 44 + libtorrent/include/asio/detail/CVS/Entries | 74 - libtorrent/include/asio/detail/CVS/Repository | 1 - libtorrent/include/asio/detail/CVS/Root | 1 - .../include/asio/detail/epoll_reactor.hpp | 155 +- .../include/asio/detail/kqueue_reactor.hpp | 82 +- libtorrent/include/asio/detail/null_event.hpp | 9 +- .../include/asio/detail/posix_event.hpp | 40 +- .../include/asio/detail/posix_mutex.hpp | 10 +- .../include/asio/detail/posix_thread.hpp | 3 +- .../include/asio/detail/posix_tss_ptr.hpp | 3 +- .../asio/detail/reactive_socket_service.hpp | 10 +- .../include/asio/detail/scoped_lock.hpp | 12 + .../include/asio/detail/select_reactor.hpp | 51 +- .../include/asio/detail/service_registry.hpp | 6 +- libtorrent/include/asio/detail/socket_ops.hpp | 38 +- .../include/asio/detail/socket_option.hpp | 13 +- .../include/asio/detail/socket_types.hpp | 7 + .../include/asio/detail/strand_service.hpp | 9 +- .../include/asio/detail/task_io_service.hpp | 99 +- .../include/asio/detail/timer_queue.hpp | 52 +- .../include/asio/detail/timer_queue_base.hpp | 6 + libtorrent/include/asio/detail/win_event.hpp | 21 +- .../asio/detail/win_iocp_io_service.hpp | 14 +- .../asio/detail/win_iocp_io_service_fwd.hpp | 2 + .../asio/detail/win_iocp_socket_service.hpp | 166 +- libtorrent/include/asio/detail/win_mutex.hpp | 5 +- libtorrent/include/asio/detail/win_thread.hpp | 4 +- .../include/asio/detail/win_tss_ptr.hpp | 4 +- .../include/asio/detail/winsock_init.hpp | 3 +- .../include/asio/detail/wrapped_handler.hpp | 14 +- libtorrent/include/asio/error.hpp | 346 ++-- libtorrent/include/asio/error_code.hpp | 44 +- libtorrent/include/asio/impl/CVS/Entries | 6 - libtorrent/include/asio/impl/CVS/Repository | 1 - libtorrent/include/asio/impl/CVS/Root | 1 - libtorrent/include/asio/impl/error_code.ipp | 6 +- libtorrent/include/asio/impl/io_service.ipp | 4 +- libtorrent/include/asio/impl/read_until.ipp | 18 +- libtorrent/include/asio/io_service.hpp | 2 +- libtorrent/include/asio/ip/CVS/Entries | 17 - libtorrent/include/asio/ip/CVS/Repository | 1 - libtorrent/include/asio/ip/CVS/Root | 1 - libtorrent/include/asio/ip/basic_endpoint.hpp | 34 +- libtorrent/include/asio/ssl/CVS/Entries | 8 - libtorrent/include/asio/ssl/CVS/Repository | 1 - libtorrent/include/asio/ssl/CVS/Root | 1 - .../asio/ssl/detail/openssl_operation.hpp | 4 +- libtorrent/include/libtorrent/alert.hpp | 2 +- libtorrent/include/libtorrent/alert_types.hpp | 14 +- libtorrent/include/libtorrent/assert.hpp | 52 + .../include/libtorrent/aux_/session_impl.hpp | 52 +- .../include/libtorrent/bandwidth_manager.hpp | 34 +- libtorrent/include/libtorrent/bencode.hpp | 2 + .../include/libtorrent/broadcast_socket.hpp | 80 + .../include/libtorrent/bt_peer_connection.hpp | 30 +- libtorrent/include/libtorrent/buffer.hpp | 3 +- libtorrent/include/libtorrent/config.hpp | 1 + .../include/libtorrent/connection_queue.hpp | 5 + libtorrent/include/libtorrent/debug.hpp | 1 + libtorrent/include/libtorrent/entry.hpp | 2 +- libtorrent/include/libtorrent/enum_net.hpp | 44 + libtorrent/include/libtorrent/extensions.hpp | 9 + libtorrent/include/libtorrent/fingerprint.hpp | 2 + libtorrent/include/libtorrent/hasher.hpp | 2 +- .../include/libtorrent/http_connection.hpp | 12 +- .../libtorrent/http_tracker_connection.hpp | 5 + .../include/libtorrent/intrusive_ptr_base.hpp | 5 +- .../include/libtorrent/invariant_check.hpp | 2 +- libtorrent/include/libtorrent/ip_filter.hpp | 6 +- .../include/libtorrent/kademlia/node.hpp | 2 +- .../include/libtorrent/kademlia/node_id.hpp | 2 +- .../libtorrent/kademlia/routing_table.hpp | 1 + libtorrent/include/libtorrent/lsd.hpp | 18 +- libtorrent/include/libtorrent/pe_crypto.hpp | 5 +- .../include/libtorrent/peer_connection.hpp | 72 +- libtorrent/include/libtorrent/peer_id.hpp | 2 +- libtorrent/include/libtorrent/peer_info.hpp | 12 +- .../include/libtorrent/piece_picker.hpp | 48 +- libtorrent/include/libtorrent/policy.hpp | 31 +- libtorrent/include/libtorrent/session.hpp | 27 +- .../include/libtorrent/session_settings.hpp | 12 +- libtorrent/include/libtorrent/stat.hpp | 1 + libtorrent/include/libtorrent/storage.hpp | 28 +- libtorrent/include/libtorrent/time.hpp | 7 +- libtorrent/include/libtorrent/torrent.hpp | 57 +- .../include/libtorrent/torrent_handle.hpp | 5 +- .../include/libtorrent/torrent_info.hpp | 84 +- libtorrent/include/libtorrent/upnp.hpp | 28 +- .../libtorrent/web_peer_connection.hpp | 3 +- libtorrent/src/Makefile.am | 9 +- libtorrent/src/assert.cpp | 65 + libtorrent/src/broadcast_socket.cpp | 144 ++ libtorrent/src/bt_peer_connection.cpp | 229 ++- libtorrent/src/connection_queue.cpp | 26 +- libtorrent/src/deluge_core.cpp | 1439 ----------------- libtorrent/src/disk_io_thread.cpp | 30 +- libtorrent/src/enum_net.cpp | 133 ++ libtorrent/src/escape_string.cpp | 3 +- libtorrent/src/http_connection.cpp | 7 +- libtorrent/src/http_tracker_connection.cpp | 102 +- libtorrent/src/identify_client.cpp | 77 +- libtorrent/src/kademlia/closest_nodes.cpp | 1 + libtorrent/src/kademlia/dht_tracker.cpp | 8 +- libtorrent/src/kademlia/node_id.cpp | 2 +- libtorrent/src/lsd.cpp | 88 +- libtorrent/src/metadata_transfer.cpp | 2 +- libtorrent/src/natpmp.cpp | 6 +- libtorrent/src/pe_crypto.cpp | 2 +- libtorrent/src/peer_connection.cpp | 583 +++++-- libtorrent/src/piece_picker.cpp | 484 ++++-- libtorrent/src/policy.cpp | 262 +-- libtorrent/src/session.cpp | 29 +- libtorrent/src/session_impl.cpp | 510 ++++-- libtorrent/src/socks5_stream.cpp | 1 + libtorrent/src/storage.cpp | 232 ++- libtorrent/src/torrent.cpp | 366 +++-- libtorrent/src/torrent_handle.cpp | 227 ++- libtorrent/src/torrent_info.cpp | 43 +- libtorrent/src/tracker_manager.cpp | 6 +- libtorrent/src/upnp.cpp | 270 ++-- libtorrent/src/web_peer_connection.cpp | 31 +- 133 files changed, 4077 insertions(+), 3744 deletions(-) delete mode 100644 libtorrent/include/asio/CVS/Entries delete mode 100644 libtorrent/include/asio/CVS/Repository delete mode 100644 libtorrent/include/asio/CVS/Root delete mode 100644 libtorrent/include/asio/detail/CVS/Entries delete mode 100644 libtorrent/include/asio/detail/CVS/Repository delete mode 100644 libtorrent/include/asio/detail/CVS/Root delete mode 100644 libtorrent/include/asio/impl/CVS/Entries delete mode 100644 libtorrent/include/asio/impl/CVS/Repository delete mode 100644 libtorrent/include/asio/impl/CVS/Root delete mode 100644 libtorrent/include/asio/ip/CVS/Entries delete mode 100644 libtorrent/include/asio/ip/CVS/Repository delete mode 100644 libtorrent/include/asio/ip/CVS/Root delete mode 100644 libtorrent/include/asio/ssl/CVS/Entries delete mode 100644 libtorrent/include/asio/ssl/CVS/Repository delete mode 100644 libtorrent/include/asio/ssl/CVS/Root create mode 100644 libtorrent/include/libtorrent/assert.hpp create mode 100644 libtorrent/include/libtorrent/broadcast_socket.hpp create mode 100644 libtorrent/include/libtorrent/enum_net.hpp create mode 100644 libtorrent/src/assert.cpp create mode 100644 libtorrent/src/broadcast_socket.cpp delete mode 100644 libtorrent/src/deluge_core.cpp create mode 100644 libtorrent/src/enum_net.cpp diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index f34cf4b5d..3e188a3ce 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -25,6 +25,8 @@ extern char const* peer_error_alert_doc; extern char const* invalid_request_alert_doc; extern char const* peer_request_doc; extern char const* torrent_finished_alert_doc; +extern char const* torrent_paused_alert_doc; +extern char const* storage_moved_alert_doc; extern char const* metadata_failed_alert_doc; extern char const* metadata_received_alert_doc; extern char const* fastresume_rejected_alert_doc; @@ -140,7 +142,18 @@ void bind_alert() ) .def_readonly("handle", &torrent_finished_alert::handle) ; - + + class_, noncopyable>( + "torrent_paused_alert", torrent_paused_alert_doc, no_init + ) + .def_readonly("handle", &torrent_paused_alert::handle) + ; + + class_, noncopyable>( + "storage_moved_alert", storage_moved_alert_doc, no_init + ) + .def_readonly("handle", &storage_moved_alert::handle) + ; class_, noncopyable>( "metadata_failed_alert", metadata_failed_alert_doc, no_init ) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index fbd0a157c..e4a99ba31 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -164,14 +164,14 @@ char const* session_set_severity_level_doc = ""; char const* session_pop_alert_doc = ""; -char const* session_start_upnp_doc = +char const* session_start_upnp_doc = ""; -char const* session_stop_upnp_doc = +char const* session_stop_upnp_doc = ""; - char const* session_start_natpmp_doc = +char const* session_start_natpmp_doc = + ""; +char const* session_stop_natpmp_doc = ""; -char const* session_stop_natpmp_doc = - ""; // -- alert ----------------------------------------------------------------- char const* alert_doc = @@ -257,6 +257,17 @@ char const* torrent_finished_alert_doc = "It contains a `torrent_handle` to the torrent in question. This alert\n" "is generated as severity level `alert.severity_levels.info`."; +char const* torrent_paused_alert_doc = + "This alert is generated when a torrent switches from being a\n" + "active to paused.\n" + "It contains a `torrent_handle` to the torrent in question. This alert\n" + "is generated as severity level `alert.severity_levels.warning`."; + +char const* storage_moved_alert_doc = + "This alert is generated when a torrent moves storage.\n" + "It contains a `torrent_handle` to the torrent in question. This alert\n" + "is generated as severity level `alert.severity_levels.warning`."; + char const* metadata_failed_alert_doc = "This alert is generated when the metadata has been completely\n" "received and the info-hash failed to match it. i.e. the\n" diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp index 10d18ff94..f8fb30bdf 100755 --- a/libtorrent/bindings/python/src/extensions.cpp +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -142,7 +142,6 @@ void bind_extensions() // TODO move to it's own file class_("peer_connection", no_init); - class_ >("torrent_plugin", no_init); def("create_ut_pex_plugin", create_ut_pex_plugin); def("create_metadata_plugin", create_metadata_plugin); } diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 4ea7a1711..480659537 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -46,7 +46,7 @@ extern char const* session_set_max_connections_doc; extern char const* session_set_max_half_open_connections_doc; extern char const* session_set_settings_doc; extern char const* session_set_pe_settings_doc; -extern char const* session_get_pe_settings_doc; +extern char const* session_get_pe_settings_doc; extern char const* session_set_severity_level_doc; extern char const* session_pop_alert_doc; extern char const* session_start_upnp_doc; @@ -86,11 +86,10 @@ namespace torrent_handle add_torrent(session& s, torrent_info const& ti , boost::filesystem::path const& save, entry const& resume - , bool compact, int block_size) + , bool compact, bool paused) { allow_threading_guard guard; - return s.add_torrent(ti, save, resume, compact, block_size - , default_storage_constructor); + return s.add_torrent(ti, save, resume, compact, paused, default_storage_constructor); } } // namespace unnamed @@ -175,7 +174,7 @@ void bind_session() "add_torrent", &add_torrent , ( arg("torrent_info"), "save_path", arg("resume_data") = entry() - , arg("compact_mode") = true, arg("block_size") = 16 * 1024 + , arg("compact_mode") = true, arg("paused") = false ) , session_add_torrent_doc ) diff --git a/libtorrent/bindings/python/src/session_settings.cpp b/libtorrent/bindings/python/src/session_settings.cpp index f584956b2..c19dfa4d4 100755 --- a/libtorrent/bindings/python/src/session_settings.cpp +++ b/libtorrent/bindings/python/src/session_settings.cpp @@ -47,7 +47,7 @@ void bind_session_settings() .value("http", proxy_settings::http) .value("http_pw", proxy_settings::http_pw) ; - class_("proxy_settings") + class_("proxy_settings") .def_readwrite("hostname", &proxy_settings::hostname) .def_readwrite("port", &proxy_settings::port) .def_readwrite("password", &proxy_settings::password) @@ -64,7 +64,7 @@ void bind_session_settings() enum_("enc_level") .value("rc4", pe_settings::rc4) .value("plaintext", pe_settings::plaintext) - .value("both", pe_settings::both) + .value("both", pe_settings::both) ; class_("pe_settings") diff --git a/libtorrent/bindings/python/src/torrent_info.cpp b/libtorrent/bindings/python/src/torrent_info.cpp index 301c4a5bf..a17c449e3 100755 --- a/libtorrent/bindings/python/src/torrent_info.cpp +++ b/libtorrent/bindings/python/src/torrent_info.cpp @@ -16,7 +16,6 @@ namespace return i.trackers().begin(); } - std::vector::const_iterator end_trackers(torrent_info& i) { return i.trackers().end(); @@ -41,6 +40,29 @@ namespace return result; } + std::vector::const_iterator begin_files(torrent_info& i, bool storage) + { + return i.begin_files(storage); + } + + std::vector::const_iterator end_files(torrent_info& i, bool storage) + { + return i.end_files(storage); + } + + //list files(torrent_info const& ti, bool storage) { + list files(torrent_info const& ti, bool storage) { + list result; + + typedef std::vector list_type; + + for (list_type::const_iterator i = ti.begin_files(storage); i != ti.end_files(storage); ++i) + result.append(*i); + + return result; + } + + } // namespace unnamed void bind_torrent_info() @@ -71,9 +93,9 @@ void bind_torrent_info() .def("hash_for_piece", &torrent_info::hash_for_piece, copy) .def("piece_size", &torrent_info::piece_size) - .def("num_files", &torrent_info::num_files) + .def("num_files", &torrent_info::num_files, (arg("storage")=false)) .def("file_at", &torrent_info::file_at, return_internal_reference<>()) - .def("files", range(&torrent_info::begin_files, &torrent_info::end_files)) + .def("files", &files, (arg("storage")=false)) .def("priv", &torrent_info::priv) .def("set_priv", &torrent_info::set_priv) @@ -84,9 +106,8 @@ void bind_torrent_info() .def("add_node", &add_node) .def("nodes", &nodes) ; - class_("file_entry") - .add_property( + .add_property( "path" , make_getter( &file_entry::path, return_value_policy() diff --git a/libtorrent/include/asio/CVS/Entries b/libtorrent/include/asio/CVS/Entries deleted file mode 100644 index 43749a985..000000000 --- a/libtorrent/include/asio/CVS/Entries +++ /dev/null @@ -1,45 +0,0 @@ -/basic_datagram_socket.hpp/1.40/Thu Jan 4 05:44:43 2007// -/basic_deadline_timer.hpp/1.23/Sun May 20 00:49:02 2007// -/basic_io_object.hpp/1.8/Thu Jan 4 05:44:43 2007// -/basic_socket.hpp/1.16/Mon Jan 8 22:12:45 2007// -/basic_socket_acceptor.hpp/1.58/Fri Feb 9 05:47:48 2007// -/basic_socket_iostream.hpp/1.8/Thu Jan 18 11:41:36 2007// -/basic_socket_streambuf.hpp/1.6/Thu Jan 18 11:41:36 2007// -/basic_stream_socket.hpp/1.69/Mon Jan 8 22:12:45 2007// -/basic_streambuf.hpp/1.12/Thu Jan 4 10:23:31 2007// -/buffer.hpp/1.23/Thu Jun 21 14:03:36 2007// -/buffered_read_stream.hpp/1.17/Thu Jan 4 05:44:43 2007// -/buffered_read_stream_fwd.hpp/1.5/Thu Jan 4 05:44:43 2007// -/buffered_stream.hpp/1.32/Thu Jan 4 05:44:43 2007// -/buffered_stream_fwd.hpp/1.9/Thu Jan 4 05:44:43 2007// -/buffered_write_stream.hpp/1.17/Thu Jan 4 05:44:43 2007// -/buffered_write_stream_fwd.hpp/1.5/Thu Jan 4 05:44:43 2007// -/completion_condition.hpp/1.5/Thu Jan 4 05:44:43 2007// -/datagram_socket_service.hpp/1.34/Mon Jan 8 22:12:45 2007// -/deadline_timer.hpp/1.6/Thu Jan 4 05:44:43 2007// -/deadline_timer_service.hpp/1.29/Mon Jan 8 02:47:13 2007// -/error.hpp/1.39/Mon Jan 8 22:12:45 2007// -/error_code.hpp/1.4/Mon Jan 8 22:12:45 2007// -/handler_alloc_hook.hpp/1.11/Thu Jan 4 05:44:43 2007// -/handler_invoke_hook.hpp/1.3/Thu Jan 4 05:44:43 2007// -/io_service.hpp/1.24/Sun May 20 00:49:02 2007// -/is_read_buffered.hpp/1.6/Thu Jan 4 05:44:43 2007// -/is_write_buffered.hpp/1.6/Thu Jan 4 05:44:43 2007// -/placeholders.hpp/1.10/Thu Jan 4 05:44:43 2007// -/read.hpp/1.22/Thu Jan 4 05:44:43 2007// -/read_until.hpp/1.8/Thu Jan 4 05:44:44 2007// -/socket_acceptor_service.hpp/1.34/Fri Feb 9 05:47:48 2007// -/socket_base.hpp/1.23/Mon Jan 8 23:45:36 2007// -/ssl.hpp/1.4/Thu Jan 4 05:44:44 2007// -/strand.hpp/1.6/Tue May 8 13:13:55 2007// -/stream_socket_service.hpp/1.35/Mon Jan 8 22:12:46 2007// -/streambuf.hpp/1.3/Thu Jan 4 05:44:44 2007// -/system_error.hpp/1.3/Thu Jan 4 05:44:44 2007// -/thread.hpp/1.15/Thu Jan 4 05:44:44 2007// -/time_traits.hpp/1.9/Thu Jan 4 05:44:44 2007// -/version.hpp/1.1/Tue May 8 12:17:36 2007// -/write.hpp/1.21/Thu Jan 4 05:44:44 2007// -D/detail//// -D/impl//// -D/ip//// -D/ssl//// diff --git a/libtorrent/include/asio/CVS/Repository b/libtorrent/include/asio/CVS/Repository deleted file mode 100644 index 2a32176a2..000000000 --- a/libtorrent/include/asio/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -asio/include/asio diff --git a/libtorrent/include/asio/CVS/Root b/libtorrent/include/asio/CVS/Root deleted file mode 100644 index a7505d52a..000000000 --- a/libtorrent/include/asio/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/basic_socket.hpp b/libtorrent/include/asio/basic_socket.hpp index b0dc52e48..2b2521b69 100644 --- a/libtorrent/include/asio/basic_socket.hpp +++ b/libtorrent/include/asio/basic_socket.hpp @@ -238,6 +238,9 @@ public: * with the asio::error::operation_aborted error. * * @throws asio::system_error Thrown on failure. + * + * @note For portable behaviour with respect to graceful closure of a + * connected socket, call shutdown() before closing the socket. */ void close() { @@ -265,6 +268,9 @@ public: * // An error occurred. * } * @endcode + * + * @note For portable behaviour with respect to graceful closure of a + * connected socket, call shutdown() before closing the socket. */ asio::error_code close(asio::error_code& ec) { diff --git a/libtorrent/include/asio/buffer.hpp b/libtorrent/include/asio/buffer.hpp index 7e5dc76c8..9fe76178c 100644 --- a/libtorrent/include/asio/buffer.hpp +++ b/libtorrent/include/asio/buffer.hpp @@ -542,9 +542,10 @@ inline const_buffers_1 buffer(const PodType (&data)[N], ? N * sizeof(PodType) : max_size_in_bytes)); } -#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) \ + || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) -// Borland C++ thinks the overloads: +// Borland C++ and Sun Studio think the overloads: // // unspecified buffer(boost::array& array ...); // @@ -610,6 +611,7 @@ buffer(boost::array& data, std::size_t max_size_in_bytes) } #else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + // || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) /// Create a new modifiable buffer that represents the given POD array. template @@ -650,6 +652,7 @@ inline const_buffers_1 buffer(boost::array& data, } #endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + // || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) /// Create a new non-modifiable buffer that represents the given POD array. template diff --git a/libtorrent/include/asio/completion_condition.hpp b/libtorrent/include/asio/completion_condition.hpp index 42696d599..939a4a8f3 100644 --- a/libtorrent/include/asio/completion_condition.hpp +++ b/libtorrent/include/asio/completion_condition.hpp @@ -71,6 +71,28 @@ private: /// Return a completion condition function object that indicates that a read or /// write operation should continue until all of the data has been transferred, /// or until an error occurs. +/** + * This function is used to create an object, of unspecified type, that meets + * CompletionCondition requirements. + * + * @par Example + * Reading until a buffer is full: + * @code + * boost::array buf; + * asio::error_code ec; + * std::size_t n = asio::read( + * sock, asio::buffer(buf), + * asio::transfer_all(), ec); + * if (ec) + * { + * // An error occurred. + * } + * else + * { + * // n == 128 + * } + * @endcode + */ #if defined(GENERATING_DOCUMENTATION) unspecified transfer_all(); #else @@ -83,6 +105,28 @@ inline detail::transfer_all_t transfer_all() /// Return a completion condition function object that indicates that a read or /// write operation should continue until a minimum number of bytes has been /// transferred, or until an error occurs. +/** + * This function is used to create an object, of unspecified type, that meets + * CompletionCondition requirements. + * + * @par Example + * Reading until a buffer is full or contains at least 64 bytes: + * @code + * boost::array buf; + * asio::error_code ec; + * std::size_t n = asio::read( + * sock, asio::buffer(buf), + * asio::transfer_at_least(64), ec); + * if (ec) + * { + * // An error occurred. + * } + * else + * { + * // n >= 64 && n <= 128 + * } + * @endcode + */ #if defined(GENERATING_DOCUMENTATION) unspecified transfer_at_least(std::size_t minimum); #else diff --git a/libtorrent/include/asio/detail/CVS/Entries b/libtorrent/include/asio/detail/CVS/Entries deleted file mode 100644 index e6deb1bfd..000000000 --- a/libtorrent/include/asio/detail/CVS/Entries +++ /dev/null @@ -1,74 +0,0 @@ -/bind_handler.hpp/1.18/Thu Jan 4 05:44:44 2007// -/buffer_resize_guard.hpp/1.9/Thu Jan 4 05:44:44 2007// -/buffered_stream_storage.hpp/1.5/Thu Jan 4 05:44:44 2007// -/call_stack.hpp/1.3/Thu Jan 4 05:44:44 2007// -/const_buffers_iterator.hpp/1.3/Thu Jan 4 05:44:44 2007// -/consuming_buffers.hpp/1.7/Sat Jan 13 13:41:09 2007// -/deadline_timer_service.hpp/1.7/Mon Jan 8 02:47:13 2007// -/epoll_reactor.hpp/1.40/Thu Jan 4 05:44:44 2007// -/epoll_reactor_fwd.hpp/1.3/Thu Jan 4 05:44:44 2007// -/event.hpp/1.13/Thu Jan 4 05:44:44 2007// -/fd_set_adapter.hpp/1.5/Thu Jan 4 05:44:44 2007// -/handler_alloc_helpers.hpp/1.8/Thu Jan 4 05:44:44 2007// -/handler_invoke_helpers.hpp/1.2/Thu Jan 4 05:44:44 2007// -/hash_map.hpp/1.19/Thu Mar 22 21:13:13 2007// -/io_control.hpp/1.5/Thu Jan 4 05:44:44 2007// -/kqueue_reactor.hpp/1.30/Thu Mar 22 21:08:02 2007// -/kqueue_reactor_fwd.hpp/1.3/Thu Jan 4 05:44:44 2007// -/local_free_on_block_exit.hpp/1.2/Thu Jan 4 05:44:44 2007// -/mutex.hpp/1.13/Thu Jan 4 05:44:44 2007// -/noncopyable.hpp/1.3/Thu Jan 4 05:44:44 2007// -/null_event.hpp/1.3/Thu Jan 4 05:44:44 2007// -/null_mutex.hpp/1.3/Thu Jan 4 05:44:44 2007// -/null_signal_blocker.hpp/1.3/Thu Jan 4 05:44:44 2007// -/null_thread.hpp/1.5/Mon Jan 8 22:12:46 2007// -/null_tss_ptr.hpp/1.3/Thu Jan 4 05:44:44 2007// -/old_win_sdk_compat.hpp/1.5/Sat May 12 08:16:25 2007// -/pipe_select_interrupter.hpp/1.11/Thu Jan 4 05:44:44 2007// -/pop_options.hpp/1.10/Thu Jan 4 05:44:44 2007// -/posix_event.hpp/1.16/Thu Jan 4 05:44:44 2007// -/posix_fd_set_adapter.hpp/1.4/Tue Feb 13 07:13:29 2007// -/posix_mutex.hpp/1.15/Thu Jan 4 05:44:44 2007// -/posix_signal_blocker.hpp/1.10/Sat Feb 17 22:57:37 2007// -/posix_thread.hpp/1.17/Thu Jan 4 05:44:45 2007// -/posix_tss_ptr.hpp/1.10/Thu Jan 4 05:44:45 2007// -/push_options.hpp/1.16/Thu Jan 4 05:44:45 2007// -/reactive_socket_service.hpp/1.59/Sun May 13 23:00:01 2007// -/reactor_op_queue.hpp/1.24/Thu Jan 4 05:44:45 2007// -/resolver_service.hpp/1.11/Thu Jan 4 09:06:56 2007// -/scoped_lock.hpp/1.9/Thu Jan 4 05:44:45 2007// -/select_interrupter.hpp/1.10/Thu Jan 4 05:44:45 2007// -/select_reactor.hpp/1.49/Thu Jan 4 05:44:45 2007// -/select_reactor_fwd.hpp/1.2/Thu Jan 4 05:44:45 2007// -/service_base.hpp/1.2/Thu Jan 4 05:44:45 2007// -/service_id.hpp/1.2/Thu Jan 4 05:44:45 2007// -/service_registry.hpp/1.19/Tue Feb 13 12:06:43 2007// -/service_registry_fwd.hpp/1.2/Thu Jan 4 05:44:45 2007// -/signal_blocker.hpp/1.10/Thu Jan 4 05:44:45 2007// -/signal_init.hpp/1.11/Thu Jan 4 05:44:45 2007// -/socket_holder.hpp/1.10/Thu Jan 4 05:44:45 2007// -/socket_ops.hpp/1.74/Mon May 21 12:34:39 2007// -/socket_option.hpp/1.7/Sat Feb 17 22:57:37 2007// -/socket_select_interrupter.hpp/1.15/Thu May 10 23:48:52 2007// -/socket_types.hpp/1.41/Sun May 13 07:59:21 2007// -/strand_service.hpp/1.15/Thu Jan 4 05:44:45 2007// -/task_io_service.hpp/1.18/Wed Feb 14 13:26:21 2007// -/task_io_service_fwd.hpp/1.2/Thu Jan 4 05:44:45 2007// -/thread.hpp/1.13/Thu Jan 4 05:44:45 2007// -/throw_error.hpp/1.3/Thu Jan 4 05:44:45 2007// -/timer_queue.hpp/1.6/Sun Apr 22 07:07:15 2007// -/timer_queue_base.hpp/1.2/Thu Jan 4 05:44:45 2007// -/tss_ptr.hpp/1.8/Thu Jan 4 05:44:45 2007// -/win_event.hpp/1.14/Thu Jan 4 05:44:45 2007// -/win_fd_set_adapter.hpp/1.4/Thu Jan 4 05:44:45 2007// -/win_iocp_io_service.hpp/1.24/Mon Jan 8 01:09:14 2007// -/win_iocp_io_service_fwd.hpp/1.4/Thu Jan 4 05:44:45 2007// -/win_iocp_operation.hpp/1.16/Thu Jan 4 05:44:45 2007// -/win_iocp_socket_service.hpp/1.75/Sat May 12 09:07:32 2007// -/win_mutex.hpp/1.16/Thu Jan 4 05:44:45 2007// -/win_signal_blocker.hpp/1.9/Thu Jan 4 05:44:45 2007// -/win_thread.hpp/1.20/Thu Jan 4 05:44:45 2007// -/win_tss_ptr.hpp/1.10/Thu Jan 4 05:44:45 2007// -/winsock_init.hpp/1.16/Thu Jan 4 05:44:45 2007// -/wrapped_handler.hpp/1.11/Thu Jan 4 05:44:45 2007// -D diff --git a/libtorrent/include/asio/detail/CVS/Repository b/libtorrent/include/asio/detail/CVS/Repository deleted file mode 100644 index 711cf30c4..000000000 --- a/libtorrent/include/asio/detail/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -asio/include/asio/detail diff --git a/libtorrent/include/asio/detail/CVS/Root b/libtorrent/include/asio/detail/CVS/Root deleted file mode 100644 index a7505d52a..000000000 --- a/libtorrent/include/asio/detail/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/detail/epoll_reactor.hpp b/libtorrent/include/asio/detail/epoll_reactor.hpp index d55e86454..e260c5194 100644 --- a/libtorrent/include/asio/detail/epoll_reactor.hpp +++ b/libtorrent/include/asio/detail/epoll_reactor.hpp @@ -157,7 +157,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -190,7 +191,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -219,7 +221,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -250,7 +253,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); except_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -331,7 +335,10 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - return timer_queue.cancel_timer(token); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; } private: @@ -347,16 +354,13 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -365,12 +369,7 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -398,59 +397,45 @@ private: } else { - if (events[i].events & (EPOLLERR | EPOLLHUP)) + bool more_reads = false; + bool more_writes = false; + bool more_except = false; + asio::error_code ec; + + // Exception operations must be processed first to ensure that any + // out-of-band data is read before normal data. + if (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP)) + more_except = except_op_queue_.dispatch_operation(descriptor, ec); + else + more_except = except_op_queue_.has_operation(descriptor); + + if (events[i].events & (EPOLLIN | EPOLLERR | EPOLLHUP)) + more_reads = read_op_queue_.dispatch_operation(descriptor, ec); + else + more_reads = read_op_queue_.has_operation(descriptor); + + if (events[i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) + more_writes = write_op_queue_.dispatch_operation(descriptor, ec); + else + more_writes = write_op_queue_.has_operation(descriptor); + + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLERR | EPOLLHUP; + if (more_reads) + ev.events |= EPOLLIN; + if (more_writes) + ev.events |= EPOLLOUT; + if (more_except) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) { - asio::error_code ec; - except_op_queue_.dispatch_all_operations(descriptor, ec); + ec = asio::error_code(errno, + asio::error::system_category); read_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); - - epoll_event ev = { 0, { 0 } }; - ev.events = 0; - ev.data.fd = descriptor; - epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); - } - else - { - bool more_reads = false; - bool more_writes = false; - bool more_except = false; - asio::error_code ec; - - // Exception operations must be processed first to ensure that any - // out-of-band data is read before normal data. - if (events[i].events & EPOLLPRI) - more_except = except_op_queue_.dispatch_operation(descriptor, ec); - else - more_except = except_op_queue_.has_operation(descriptor); - - if (events[i].events & EPOLLIN) - more_reads = read_op_queue_.dispatch_operation(descriptor, ec); - else - more_reads = read_op_queue_.has_operation(descriptor); - - if (events[i].events & EPOLLOUT) - more_writes = write_op_queue_.dispatch_operation(descriptor, ec); - else - more_writes = write_op_queue_.has_operation(descriptor); - - epoll_event ev = { 0, { 0 } }; - ev.events = EPOLLERR | EPOLLHUP; - if (more_reads) - ev.events |= EPOLLIN; - if (more_writes) - ev.events |= EPOLLOUT; - if (more_except) - ev.events |= EPOLLPRI; - ev.data.fd = descriptor; - int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); - if (result != 0) - { - ec = asio::error_code(errno, asio::native_ecat); - read_op_queue_.dispatch_all_operations(descriptor, ec); - write_op_queue_.dispatch_all_operations(descriptor, ec); - except_op_queue_.dispatch_all_operations(descriptor, ec); - } + except_op_queue_.dispatch_all_operations(descriptor, ec); } } } @@ -458,19 +443,17 @@ private: write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } // Issue any pending cancellations. for (size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); } // Run the select loop in the thread. @@ -507,8 +490,10 @@ private: int fd = epoll_create(epoll_size); if (fd == -1) { - boost::throw_exception(asio::system_error( - asio::error_code(errno, asio::native_ecat), + boost::throw_exception( + asio::system_error( + asio::error_code(errno, + asio::error::system_category), "epoll")); } return fd; @@ -566,6 +551,22 @@ private: interrupter_.interrupt(); } + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -590,6 +591,10 @@ private: // The timer queues. std::vector timer_queues_; + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/kqueue_reactor.hpp b/libtorrent/include/asio/detail/kqueue_reactor.hpp index 6628803af..5fffc6788 100644 --- a/libtorrent/include/asio/detail/kqueue_reactor.hpp +++ b/libtorrent/include/asio/detail/kqueue_reactor.hpp @@ -150,7 +150,8 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -176,7 +177,8 @@ public: EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -201,7 +203,8 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -224,7 +227,8 @@ public: EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -238,7 +242,8 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -321,7 +326,10 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - return timer_queue.cancel_timer(token); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; } private: @@ -337,16 +345,13 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -355,12 +360,7 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -397,7 +397,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::native_ecat); + events[i].data, asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -427,7 +427,8 @@ private: EV_SET(&event, descriptor, EVFILT_READ, EV_DELETE, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code error(errno, asio::native_ecat); + asio::error_code error(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -439,7 +440,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::native_ecat); + events[i].data, asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, error); } else @@ -456,7 +457,8 @@ private: EV_SET(&event, descriptor, EVFILT_WRITE, EV_DELETE, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code error(errno, asio::native_ecat); + asio::error_code error(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, error); } } @@ -466,19 +468,17 @@ private: write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } // Issue any pending cancellations. for (std::size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); } // Run the select loop in the thread. @@ -512,8 +512,10 @@ private: int fd = kqueue(); if (fd == -1) { - boost::throw_exception(asio::system_error( - asio::error_code(errno, asio::native_ecat), + boost::throw_exception( + asio::system_error( + asio::error_code(errno, + asio::error::system_category), "kqueue")); } return fd; @@ -573,6 +575,22 @@ private: interrupter_.interrupt(); } + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -597,6 +615,10 @@ private: // The timer queues. std::vector timer_queues_; + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/null_event.hpp b/libtorrent/include/asio/detail/null_event.hpp index df522ce0f..99bcbc6a4 100644 --- a/libtorrent/include/asio/detail/null_event.hpp +++ b/libtorrent/include/asio/detail/null_event.hpp @@ -43,17 +43,20 @@ public: } // Signal the event. - void signal() + template + void signal(Lock&) { } // Reset the event. - void clear() + template + void clear(Lock&) { } // Wait for the event to become signalled. - void wait() + template + void wait(Lock&) { } }; diff --git a/libtorrent/include/asio/detail/posix_event.hpp b/libtorrent/include/asio/detail/posix_event.hpp index 408c23bb9..b48586f15 100644 --- a/libtorrent/include/asio/detail/posix_event.hpp +++ b/libtorrent/include/asio/detail/posix_event.hpp @@ -24,10 +24,12 @@ #if defined(BOOST_HAS_PTHREADS) #include "asio/detail/push_options.hpp" +#include #include #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -42,21 +44,11 @@ public: posix_event() : signalled_(false) { - int error = ::pthread_mutex_init(&mutex_, 0); + int error = ::pthread_cond_init(&cond_, 0); if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), - "event"); - boost::throw_exception(e); - } - - error = ::pthread_cond_init(&cond_, 0); - if (error != 0) - { - ::pthread_mutex_destroy(&mutex_); - asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "event"); boost::throw_exception(e); } @@ -66,37 +58,37 @@ public: ~posix_event() { ::pthread_cond_destroy(&cond_); - ::pthread_mutex_destroy(&mutex_); } // Signal the event. - void signal() + template + void signal(Lock& lock) { - ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + BOOST_ASSERT(lock.locked()); + (void)lock; signalled_ = true; ::pthread_cond_signal(&cond_); // Ignore EINVAL. - ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } // Reset the event. - void clear() + template + void clear(Lock& lock) { - ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + BOOST_ASSERT(lock.locked()); + (void)lock; signalled_ = false; - ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } // Wait for the event to become signalled. - void wait() + template + void wait(Lock& lock) { - ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + BOOST_ASSERT(lock.locked()); while (!signalled_) - ::pthread_cond_wait(&cond_, &mutex_); // Ignore EINVAL. - ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. + ::pthread_cond_wait(&cond_, &lock.mutex().mutex_); // Ignore EINVAL. } private: - ::pthread_mutex_t mutex_; ::pthread_cond_t cond_; bool signalled_; }; diff --git a/libtorrent/include/asio/detail/posix_mutex.hpp b/libtorrent/include/asio/detail/posix_mutex.hpp index 154089f3c..6067880fb 100644 --- a/libtorrent/include/asio/detail/posix_mutex.hpp +++ b/libtorrent/include/asio/detail/posix_mutex.hpp @@ -28,6 +28,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/scoped_lock.hpp" @@ -35,6 +36,8 @@ namespace asio { namespace detail { +class posix_event; + class posix_mutex : private noncopyable { @@ -48,7 +51,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } @@ -67,7 +70,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } @@ -80,13 +83,14 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } } private: + friend class posix_event; ::pthread_mutex_t mutex_; }; diff --git a/libtorrent/include/asio/detail/posix_thread.hpp b/libtorrent/include/asio/detail/posix_thread.hpp index f01b40428..6e5815426 100644 --- a/libtorrent/include/asio/detail/posix_thread.hpp +++ b/libtorrent/include/asio/detail/posix_thread.hpp @@ -29,6 +29,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -52,7 +53,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/posix_tss_ptr.hpp b/libtorrent/include/asio/detail/posix_tss_ptr.hpp index 93fce3479..dda329c40 100644 --- a/libtorrent/include/asio/detail/posix_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/posix_tss_ptr.hpp @@ -28,6 +28,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -46,7 +47,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/reactive_socket_service.hpp b/libtorrent/include/asio/detail/reactive_socket_service.hpp index d5b8e4fc8..9c0075821 100644 --- a/libtorrent/include/asio/detail/reactive_socket_service.hpp +++ b/libtorrent/include/asio/detail/reactive_socket_service.hpp @@ -86,7 +86,7 @@ public: }; // The maximum number of buffers to support in a single operation. - enum { max_buffers = 16 }; + enum { max_buffers = 64 < max_iov_len ? 64 : max_iov_len }; // Constructor. reactive_socket_service(asio::io_service& io_service) @@ -157,7 +157,7 @@ public: if (int err = reactor_.register_descriptor(sock.get())) { - ec = asio::error_code(err, asio::native_ecat); + ec = asio::error_code(err, asio::error::system_category); return ec; } @@ -181,7 +181,7 @@ public: if (int err = reactor_.register_descriptor(native_socket)) { - ec = asio::error_code(err, asio::native_ecat); + ec = asio::error_code(err, asio::error::system_category); return ec; } @@ -1124,7 +1124,7 @@ public: bool operator()(const asio::error_code& result) { // Check whether the operation was successful. - if (result != 0) + if (result) { io_service_.post(bind_handler(handler_, result, 0)); return true; @@ -1489,7 +1489,7 @@ public: if (connect_error) { ec = asio::error_code(connect_error, - asio::native_ecat); + asio::error::system_category); io_service_.post(bind_handler(handler_, ec)); return true; } diff --git a/libtorrent/include/asio/detail/scoped_lock.hpp b/libtorrent/include/asio/detail/scoped_lock.hpp index 64c77cbab..57f8cb8f6 100644 --- a/libtorrent/include/asio/detail/scoped_lock.hpp +++ b/libtorrent/include/asio/detail/scoped_lock.hpp @@ -63,6 +63,18 @@ public: } } + // Test whether the lock is held. + bool locked() const + { + return locked_; + } + + // Get the underlying mutex. + Mutex& mutex() + { + return mutex_; + } + private: // The underlying mutex. Mutex& mutex_; diff --git a/libtorrent/include/asio/detail/select_reactor.hpp b/libtorrent/include/asio/detail/select_reactor.hpp index 83f093ae6..079ec2de8 100644 --- a/libtorrent/include/asio/detail/select_reactor.hpp +++ b/libtorrent/include/asio/detail/select_reactor.hpp @@ -229,7 +229,10 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - return timer_queue.cancel_timer(token); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; } private: @@ -245,16 +248,13 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -263,12 +263,7 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -321,19 +316,17 @@ private: write_op_queue_.dispatch_cancellations(); } for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } // Issue any pending cancellations. for (size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); } // Run the select loop in the thread. @@ -414,6 +407,22 @@ private: interrupter_.interrupt(); } + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -435,6 +444,10 @@ private: // The timer queues. std::vector timer_queues_; + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/service_registry.hpp b/libtorrent/include/asio/detail/service_registry.hpp index bd1c3ea5b..3a9e9f620 100644 --- a/libtorrent/include/asio/detail/service_registry.hpp +++ b/libtorrent/include/asio/detail/service_registry.hpp @@ -166,7 +166,8 @@ private: } // Check if a service matches the given id. - bool service_id_matches(const asio::io_service::service& service, + static bool service_id_matches( + const asio::io_service::service& service, const asio::io_service::id& id) { return service.id_ == &id; @@ -174,7 +175,8 @@ private: // Check if a service matches the given id. template - bool service_id_matches(const asio::io_service::service& service, + static bool service_id_matches( + const asio::io_service::service& service, const asio::detail::service_id& /*id*/) { return service.type_info_ != 0 && *service.type_info_ == typeid(Service); diff --git a/libtorrent/include/asio/detail/socket_ops.hpp b/libtorrent/include/asio/detail/socket_ops.hpp index 4b38c6ee8..98f3b0f64 100644 --- a/libtorrent/include/asio/detail/socket_ops.hpp +++ b/libtorrent/include/asio/detail/socket_ops.hpp @@ -52,9 +52,10 @@ inline ReturnType error_wrapper(ReturnType return_value, asio::error_code& ec) { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) - ec = asio::error_code(WSAGetLastError(), asio::native_ecat); + ec = asio::error_code(WSAGetLastError(), + asio::error::system_category); #else - ec = asio::error_code(errno, asio::native_ecat); + ec = asio::error_code(errno, asio::error::system_category); #endif return return_value; } @@ -923,6 +924,13 @@ inline void gai_free(void* p) ::operator delete(p); } +inline void gai_strcpy(char* target, const char* source, std::size_t max_size) +{ + using namespace std; + *target = 0; + strncat(target, source, max_size); +} + enum { gai_clone_flag = 1 << 30 }; inline int gai_aistruct(addrinfo_type*** next, const addrinfo_type* hints, @@ -1292,14 +1300,15 @@ inline int getaddrinfo_emulation(const char* host, const char* service, if (host != 0 && host[0] != '\0' && hptr->h_name && hptr->h_name[0] && (hints.ai_flags & AI_CANONNAME) && canon == 0) { - canon = gai_alloc(strlen(hptr->h_name) + 1); + std::size_t canon_len = strlen(hptr->h_name) + 1; + canon = gai_alloc(canon_len); if (canon == 0) { freeaddrinfo_emulation(aihead); socket_ops::freehostent(hptr); return EAI_MEMORY; } - strcpy(canon, hptr->h_name); + gai_strcpy(canon, hptr->h_name, canon_len); } // Create an addrinfo structure for each returned address. @@ -1335,13 +1344,14 @@ inline int getaddrinfo_emulation(const char* host, const char* service, } else { - aihead->ai_canonname = gai_alloc(strlen(search[0].host) + 1); + std::size_t canonname_len = strlen(search[0].host) + 1; + aihead->ai_canonname = gai_alloc(canonname_len); if (aihead->ai_canonname == 0) { freeaddrinfo_emulation(aihead); return EAI_MEMORY; } - strcpy(aihead->ai_canonname, search[0].host); + gai_strcpy(aihead->ai_canonname, search[0].host, canonname_len); } } gai_free(canon); @@ -1424,8 +1434,7 @@ inline asio::error_code getnameinfo_emulation( *dot = 0; } } - *host = '\0'; - strncat(host, hptr->h_name, hostlen); + gai_strcpy(host, hptr->h_name, hostlen); socket_ops::freehostent(hptr); } else @@ -1463,8 +1472,7 @@ inline asio::error_code getnameinfo_emulation( servent* sptr = ::getservbyport(port, (flags & NI_DGRAM) ? "udp" : 0); if (sptr && sptr->s_name && sptr->s_name[0] != '\0') { - *serv = '\0'; - strncat(serv, sptr->s_name, servlen); + gai_strcpy(serv, sptr->s_name, servlen); } else { @@ -1504,6 +1512,12 @@ inline asio::error_code translate_addrinfo_error(int error) case EAI_MEMORY: return asio::error::no_memory; case EAI_NONAME: +#if defined(EAI_ADDRFAMILY) + case EAI_ADDRFAMILY: +#endif +#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) + case EAI_NODATA: +#endif return asio::error::host_not_found; case EAI_SERVICE: return asio::error::service_not_found; @@ -1512,10 +1526,10 @@ inline asio::error_code translate_addrinfo_error(int error) default: // Possibly the non-portable EAI_SYSTEM. #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) return asio::error_code( - WSAGetLastError(), asio::native_ecat); + WSAGetLastError(), asio::error::system_category); #else return asio::error_code( - errno, asio::native_ecat); + errno, asio::error::system_category); #endif } } diff --git a/libtorrent/include/asio/detail/socket_option.hpp b/libtorrent/include/asio/detail/socket_option.hpp index ee867e6b2..1a03936ab 100644 --- a/libtorrent/include/asio/detail/socket_option.hpp +++ b/libtorrent/include/asio/detail/socket_option.hpp @@ -110,8 +110,19 @@ public: template void resize(const Protocol&, std::size_t s) { - if (s != sizeof(value_)) + // On some platforms (e.g. Windows Vista), the getsockopt function will + // return the size of a boolean socket option as one byte, even though a + // four byte integer was passed in. + switch (s) + { + case sizeof(char): + value_ = *reinterpret_cast(&value_) ? 1 : 0; + break; + case sizeof(value_): + break; + default: throw std::length_error("boolean socket option resize"); + } } private: diff --git a/libtorrent/include/asio/detail/socket_types.hpp b/libtorrent/include/asio/detail/socket_types.hpp index 49d1c7fc2..02c3a78d5 100644 --- a/libtorrent/include/asio/detail/socket_types.hpp +++ b/libtorrent/include/asio/detail/socket_types.hpp @@ -98,6 +98,7 @@ # include # include # include +# include # if defined(__sun) # include # include @@ -141,6 +142,11 @@ const int shutdown_both = SD_BOTH; const int message_peek = MSG_PEEK; const int message_out_of_band = MSG_OOB; const int message_do_not_route = MSG_DONTROUTE; +# if defined (_WIN32_WINNT) +const int max_iov_len = 64; +# else +const int max_iov_len = 16; +# endif #else typedef int socket_type; const int invalid_socket = -1; @@ -166,6 +172,7 @@ const int shutdown_both = SHUT_RDWR; const int message_peek = MSG_PEEK; const int message_out_of_band = MSG_OOB; const int message_do_not_route = MSG_DONTROUTE; +const int max_iov_len = IOV_MAX; #endif const int custom_socket_option_level = 0xA5100000; const int enable_connection_aborted_option = 1; diff --git a/libtorrent/include/asio/detail/strand_service.hpp b/libtorrent/include/asio/detail/strand_service.hpp index f10289090..d987cb98d 100644 --- a/libtorrent/include/asio/detail/strand_service.hpp +++ b/libtorrent/include/asio/detail/strand_service.hpp @@ -239,6 +239,7 @@ public: #else BOOST_ASSERT(size <= strand_impl::handler_storage_type::size); #endif + (void)size; return impl_->handler_storage_.address(); } @@ -415,14 +416,14 @@ public: } else { - asio::detail::mutex::scoped_lock lock(impl->mutex_); - // Allocate and construct an object to wrap the handler. typedef handler_wrapper value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, handler); + asio::detail::mutex::scoped_lock lock(impl->mutex_); + if (impl->current_handler_ == 0) { // This handler now has the lock, so can be dispatched immediately. @@ -455,14 +456,14 @@ public: template void post(implementation_type& impl, Handler handler) { - asio::detail::mutex::scoped_lock lock(impl->mutex_); - // Allocate and construct an object to wrap the handler. typedef handler_wrapper value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, handler); + asio::detail::mutex::scoped_lock lock(impl->mutex_); + if (impl->current_handler_ == 0) { // This handler now has the lock, so can be dispatched immediately. diff --git a/libtorrent/include/asio/detail/task_io_service.hpp b/libtorrent/include/asio/detail/task_io_service.hpp index 07df1c18a..802d7ea95 100644 --- a/libtorrent/include/asio/detail/task_io_service.hpp +++ b/libtorrent/include/asio/detail/task_io_service.hpp @@ -40,6 +40,7 @@ public: : asio::detail::service_base >(io_service), mutex_(), task_(use_service(io_service)), + task_interrupted_(true), outstanding_work_(0), handler_queue_(&task_handler_), handler_queue_end_(&task_handler_), @@ -80,8 +81,7 @@ public: typename call_stack::context ctx(this); idle_thread_info this_idle_thread; - this_idle_thread.prev = &this_idle_thread; - this_idle_thread.next = &this_idle_thread; + this_idle_thread.next = 0; asio::detail::mutex::scoped_lock lock(mutex_); @@ -98,8 +98,7 @@ public: typename call_stack::context ctx(this); idle_thread_info this_idle_thread; - this_idle_thread.prev = &this_idle_thread; - this_idle_thread.next = &this_idle_thread; + this_idle_thread.next = 0; asio::detail::mutex::scoped_lock lock(mutex_); @@ -134,7 +133,7 @@ public: void stop() { asio::detail::mutex::scoped_lock lock(mutex_); - stop_all_threads(); + stop_all_threads(lock); } // Reset in preparation for a subsequent run invocation. @@ -156,7 +155,7 @@ public: { asio::detail::mutex::scoped_lock lock(mutex_); if (--outstanding_work_ == 0) - stop_all_threads(); + stop_all_threads(lock); } // Request invocation of the given handler. @@ -201,9 +200,14 @@ public: ++outstanding_work_; // Wake up a thread to execute the handler. - if (!interrupt_one_idle_thread()) - if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) + if (!interrupt_one_idle_thread(lock)) + { + if (!task_interrupted_) + { + task_interrupted_ = true; task_.interrupt(); + } + } } private: @@ -214,7 +218,7 @@ private: { if (outstanding_work_ == 0 && !stopped_) { - stop_all_threads(); + stop_all_threads(lock); ec = asio::error_code(); return 0; } @@ -230,11 +234,14 @@ private: handler_queue_ = h->next_; if (handler_queue_ == 0) handler_queue_end_ = 0; - bool more_handlers = (handler_queue_ != 0); - lock.unlock(); + h->next_ = 0; if (h == &task_handler_) { + bool more_handlers = (handler_queue_ != 0); + task_interrupted_ = more_handlers || polling; + lock.unlock(); + // If the task has already run and we're polling then we're done. if (task_has_run && polling) { @@ -252,6 +259,7 @@ private: } else { + lock.unlock(); handler_cleanup c(lock, *this); // Invoke the handler. May throw an exception. @@ -264,31 +272,10 @@ private: else if (this_idle_thread) { // Nothing to run right now, so just wait for work to do. - if (first_idle_thread_) - { - this_idle_thread->next = first_idle_thread_; - this_idle_thread->prev = first_idle_thread_->prev; - first_idle_thread_->prev->next = this_idle_thread; - first_idle_thread_->prev = this_idle_thread; - } + this_idle_thread->next = first_idle_thread_; first_idle_thread_ = this_idle_thread; - this_idle_thread->wakeup_event.clear(); - lock.unlock(); - this_idle_thread->wakeup_event.wait(); - lock.lock(); - if (this_idle_thread->next == this_idle_thread) - { - first_idle_thread_ = 0; - } - else - { - if (first_idle_thread_ == this_idle_thread) - first_idle_thread_ = this_idle_thread->next; - this_idle_thread->next->prev = this_idle_thread->prev; - this_idle_thread->prev->next = this_idle_thread->next; - this_idle_thread->next = this_idle_thread; - this_idle_thread->prev = this_idle_thread; - } + this_idle_thread->wakeup_event.clear(lock); + this_idle_thread->wakeup_event.wait(lock); } else { @@ -302,39 +289,44 @@ private: } // Stop the task and all idle threads. - void stop_all_threads() + void stop_all_threads( + asio::detail::mutex::scoped_lock& lock) { stopped_ = true; - interrupt_all_idle_threads(); - if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) + interrupt_all_idle_threads(lock); + if (!task_interrupted_) + { + task_interrupted_ = true; task_.interrupt(); + } } // Interrupt a single idle thread. Returns true if a thread was interrupted, // false if no running thread could be found to interrupt. - bool interrupt_one_idle_thread() + bool interrupt_one_idle_thread( + asio::detail::mutex::scoped_lock& lock) { if (first_idle_thread_) { - first_idle_thread_->wakeup_event.signal(); - first_idle_thread_ = first_idle_thread_->next; + idle_thread_info* idle_thread = first_idle_thread_; + first_idle_thread_ = idle_thread->next; + idle_thread->next = 0; + idle_thread->wakeup_event.signal(lock); return true; } return false; } // Interrupt all idle threads. - void interrupt_all_idle_threads() + void interrupt_all_idle_threads( + asio::detail::mutex::scoped_lock& lock) { - if (first_idle_thread_) + while (first_idle_thread_) { - first_idle_thread_->wakeup_event.signal(); - idle_thread_info* current_idle_thread = first_idle_thread_->next; - while (current_idle_thread != first_idle_thread_) - { - current_idle_thread->wakeup_event.signal(); - current_idle_thread = current_idle_thread->next; - } + idle_thread_info* idle_thread = first_idle_thread_; + first_idle_thread_ = idle_thread->next; + idle_thread->next = 0; + idle_thread->wakeup_event.signal(lock); } } @@ -440,6 +432,7 @@ private: { // Reinsert the task at the end of the handler queue. lock_.lock(); + task_io_service_.task_interrupted_ = true; task_io_service_.task_handler_.next_ = 0; if (task_io_service_.handler_queue_end_) { @@ -478,7 +471,7 @@ private: { lock_.lock(); if (--task_io_service_.outstanding_work_ == 0) - task_io_service_.stop_all_threads(); + task_io_service_.stop_all_threads(lock_); } private: @@ -503,6 +496,9 @@ private: } } task_handler_; + // Whether the task has been interrupted. + bool task_interrupted_; + // The count of unfinished work. int outstanding_work_; @@ -522,7 +518,6 @@ private: struct idle_thread_info { event wakeup_event; - idle_thread_info* prev; idle_thread_info* next; }; diff --git a/libtorrent/include/asio/detail/timer_queue.hpp b/libtorrent/include/asio/detail/timer_queue.hpp index af1e36bd5..7735e87cf 100644 --- a/libtorrent/include/asio/detail/timer_queue.hpp +++ b/libtorrent/include/asio/detail/timer_queue.hpp @@ -48,7 +48,9 @@ public: // Constructor. timer_queue() : timers_(), - heap_() + heap_(), + cancelled_timers_(0), + cleanup_timers_(0) { } @@ -111,12 +113,17 @@ public: { timer_base* t = heap_[0]; remove_timer(t); + t->prev_ = 0; + t->next_ = cleanup_timers_; + cleanup_timers_ = t; t->invoke(asio::error_code()); } } - // Cancel the timer with the given token. The handler will be invoked - // immediately with the result operation_aborted. + // Cancel the timers with the given token. Any timers pending for the token + // will be notified that they have been cancelled next time + // dispatch_cancellations is called. Returns the number of timers that were + // cancelled. std::size_t cancel_timer(void* timer_token) { std::size_t num_cancelled = 0; @@ -129,7 +136,9 @@ public: { timer_base* next = t->next_; remove_timer(t); - t->invoke(asio::error::operation_aborted); + t->prev_ = 0; + t->next_ = cancelled_timers_; + cancelled_timers_ = t; t = next; ++num_cancelled; } @@ -137,6 +146,31 @@ public: return num_cancelled; } + // Dispatch any pending cancels for timers. + virtual void dispatch_cancellations() + { + while (cancelled_timers_) + { + timer_base* this_timer = cancelled_timers_; + cancelled_timers_ = this_timer->next_; + this_timer->next_ = cleanup_timers_; + cleanup_timers_ = this_timer; + this_timer->invoke(asio::error::operation_aborted); + } + } + + // Destroy timers that are waiting to be cleaned up. + virtual void cleanup_timers() + { + while (cleanup_timers_) + { + timer_base* next_timer = cleanup_timers_->next_; + cleanup_timers_->next_ = 0; + cleanup_timers_->destroy(); + cleanup_timers_ = next_timer; + } + } + // Destroy all timers. virtual void destroy_timers() { @@ -151,6 +185,7 @@ public: } heap_.clear(); timers_.clear(); + cleanup_timers(); } private: @@ -238,8 +273,7 @@ private: static void invoke_handler(timer_base* base, const asio::error_code& result) { - std::auto_ptr > t(static_cast*>(base)); - t->handler_(result); + static_cast*>(base)->handler_(result); } // Destroy the handler. @@ -338,6 +372,12 @@ private: // The heap of timers, with the earliest timer at the front. std::vector heap_; + + // The list of timers to be cancelled. + timer_base* cancelled_timers_; + + // The list of timers to be destroyed. + timer_base* cleanup_timers_; }; } // namespace detail diff --git a/libtorrent/include/asio/detail/timer_queue_base.hpp b/libtorrent/include/asio/detail/timer_queue_base.hpp index c8be49748..6cf25747a 100644 --- a/libtorrent/include/asio/detail/timer_queue_base.hpp +++ b/libtorrent/include/asio/detail/timer_queue_base.hpp @@ -44,6 +44,12 @@ public: // Dispatch all ready timers. virtual void dispatch_timers() = 0; + // Dispatch any pending cancels for timers. + virtual void dispatch_cancellations() = 0; + + // Destroy timers that are waiting to be cleaned up. + virtual void cleanup_timers() = 0; + // Destroy all timers. virtual void destroy_timers() = 0; }; diff --git a/libtorrent/include/asio/detail/win_event.hpp b/libtorrent/include/asio/detail/win_event.hpp index 8de9383da..c73ed56ea 100644 --- a/libtorrent/include/asio/detail/win_event.hpp +++ b/libtorrent/include/asio/detail/win_event.hpp @@ -23,11 +23,13 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" #include "asio/detail/push_options.hpp" +#include #include #include "asio/detail/pop_options.hpp" @@ -46,7 +48,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "event"); boost::throw_exception(e); } @@ -59,21 +62,31 @@ public: } // Signal the event. - void signal() + template + void signal(Lock& lock) { + BOOST_ASSERT(lock.locked()); + (void)lock; ::SetEvent(event_); } // Reset the event. - void clear() + template + void clear(Lock& lock) { + BOOST_ASSERT(lock.locked()); + (void)lock; ::ResetEvent(event_); } // Wait for the event to become signalled. - void wait() + template + void wait(Lock& lock) { + BOOST_ASSERT(lock.locked()); + lock.unlock(); ::WaitForSingleObject(event_, INFINITE); + lock.lock(); } private: diff --git a/libtorrent/include/asio/detail/win_iocp_io_service.hpp b/libtorrent/include/asio/detail/win_iocp_io_service.hpp index 4957fb01a..61eeb1745 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service.hpp @@ -63,7 +63,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "iocp"); boost::throw_exception(e); } @@ -173,7 +174,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "pqcs"); boost::throw_exception(e); } @@ -228,7 +230,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "pqcs"); boost::throw_exception(e); } @@ -247,7 +250,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "pqcs"); boost::throw_exception(e); } @@ -312,7 +316,7 @@ private: { DWORD last_error = ::GetLastError(); ec = asio::error_code(last_error, - asio::native_ecat); + asio::error::system_category); return 0; } diff --git a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp index 184fdfa18..26eacae2a 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp @@ -21,6 +21,8 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/detail/socket_types.hpp" + // This service is only supported on Win32 (NT4 and later). #if !defined(ASIO_DISABLE_IOCP) #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) diff --git a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp index 007286e8d..17d1d5887 100644 --- a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp @@ -137,7 +137,7 @@ public: enum { enable_connection_aborted = 1, // User wants connection_aborted errors. - user_set_linger = 2, // The user set the linger option. + close_might_block = 2, // User set linger option for blocking close. user_set_non_blocking = 4 // The user wants a non-blocking socket. }; @@ -170,7 +170,7 @@ public: typedef detail::select_reactor reactor_type; // The maximum number of buffers to support in a single operation. - enum { max_buffers = 16 }; + enum { max_buffers = 64 < max_iov_len ? 64 : max_iov_len }; // Constructor. win_iocp_socket_service(asio::io_service& io_service) @@ -192,7 +192,7 @@ public: while (impl) { asio::error_code ignored_ec; - close(*impl, ignored_ec); + close_for_destruction(*impl); impl = impl->next_; } } @@ -217,34 +217,7 @@ public: // Destroy a socket implementation. void destroy(implementation_type& impl) { - if (impl.socket_ != invalid_socket) - { - // Check if the reactor was created, in which case we need to close the - // socket on the reactor as well to cancel any operations that might be - // running there. - reactor_type* reactor = static_cast( - interlocked_compare_exchange_pointer( - reinterpret_cast(&reactor_), 0, 0)); - if (reactor) - reactor->close_descriptor(impl.socket_); - - if (impl.flags_ & implementation_type::user_set_linger) - { - ::linger opt; - opt.l_onoff = 0; - opt.l_linger = 0; - asio::error_code ignored_ec; - socket_ops::setsockopt(impl.socket_, - SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); - } - - asio::error_code ignored_ec; - socket_ops::close(impl.socket_, ignored_ec); - impl.socket_ = invalid_socket; - impl.flags_ = 0; - impl.cancel_token_.reset(); - impl.safe_cancellation_thread_id_ = 0; - } + close_for_destruction(impl); // Remove implementation from linked list of all implementations. asio::detail::mutex::scoped_lock lock(mutex_); @@ -353,6 +326,25 @@ public: { ec = asio::error::bad_descriptor; } + else if (FARPROC cancel_io_ex_ptr = ::GetProcAddress( + ::GetModuleHandle("KERNEL32"), "CancelIoEx")) + { + // The version of Windows supports cancellation from any thread. + typedef BOOL (WINAPI* cancel_io_ex_t)(HANDLE, LPOVERLAPPED); + cancel_io_ex_t cancel_io_ex = (cancel_io_ex_t)cancel_io_ex_ptr; + socket_type sock = impl.socket_; + HANDLE sock_as_handle = reinterpret_cast(sock); + if (!cancel_io_ex(sock_as_handle, 0)) + { + DWORD last_error = ::GetLastError(); + ec = asio::error_code(last_error, + asio::error::system_category); + } + else + { + ec = asio::error_code(); + } + } else if (impl.safe_cancellation_thread_id_ == 0) { // No operations have been started, so there's nothing to cancel. @@ -367,7 +359,8 @@ public: if (!::CancelIo(sock_as_handle)) { DWORD last_error = ::GetLastError(); - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); } else { @@ -475,7 +468,12 @@ public: if (option.level(impl.protocol_) == SOL_SOCKET && option.name(impl.protocol_) == SO_LINGER) { - impl.flags_ |= implementation_type::user_set_linger; + const ::linger* linger_option = + reinterpret_cast(option.data(impl.protocol_)); + if (linger_option->l_onoff != 0 && linger_option->l_linger != 0) + impl.flags_ |= implementation_type::close_might_block; + else + impl.flags_ &= ~implementation_type::close_might_block; } socket_ops::setsockopt(impl.socket_, @@ -668,7 +666,8 @@ public: last_error = WSAECONNRESET; else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } @@ -719,7 +718,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -821,7 +821,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -865,7 +866,8 @@ public: DWORD last_error = ::WSAGetLastError(); if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } @@ -914,7 +916,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -997,7 +1000,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1051,7 +1055,8 @@ public: last_error = WSAECONNRESET; else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } if (bytes_transferred == 0) @@ -1109,7 +1114,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -1216,7 +1222,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1262,7 +1269,8 @@ public: DWORD last_error = ::WSAGetLastError(); if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } if (bytes_transferred == 0) @@ -1328,7 +1336,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -1422,7 +1431,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1659,7 +1669,8 @@ public: ptr.reset(); // Call the handler. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); asio_handler_invoke_helpers::invoke( detail::bind_handler(handler, ec), &handler); } @@ -1759,7 +1770,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec)); } } @@ -1835,8 +1847,8 @@ public: // If connection failed then post the handler with the error code. if (connect_error) { - ec = asio::error_code( - connect_error, asio::native_ecat); + ec = asio::error_code(connect_error, + asio::error::system_category); io_service_.post(bind_handler(handler_, ec)); return true; } @@ -1950,26 +1962,66 @@ public: } private: - // Helper function to provide InterlockedCompareExchangePointer functionality - // on very old Platform SDKs. + // Helper function to close a socket when the associated object is being + // destroyed. + void close_for_destruction(implementation_type& impl) + { + if (is_open(impl)) + { + // Check if the reactor was created, in which case we need to close the + // socket on the reactor as well to cancel any operations that might be + // running there. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (reactor) + reactor->close_descriptor(impl.socket_); + + // The socket destructor must not block. If the user has changed the + // linger option to block in the foreground, we will change it back to the + // default so that the closure is performed in the background. + if (impl.flags_ & implementation_type::close_might_block) + { + ::linger opt; + opt.l_onoff = 0; + opt.l_linger = 0; + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, + SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); + } + + asio::error_code ignored_ec; + socket_ops::close(impl.socket_, ignored_ec); + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + } + } + + // Helper function to emulate InterlockedCompareExchangePointer functionality + // for: + // - very old Platform SDKs; and + // - platform SDKs where MSVC's /Wp64 option causes spurious warnings. void* interlocked_compare_exchange_pointer(void** dest, void* exch, void* cmp) { -#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) +#if defined(_M_IX86) return reinterpret_cast(InterlockedCompareExchange( - reinterpret_cast(dest), reinterpret_cast(exch), + reinterpret_cast(dest), reinterpret_cast(exch), reinterpret_cast(cmp))); #else return InterlockedCompareExchangePointer(dest, exch, cmp); #endif } - // Helper function to provide InterlockedExchangePointer functionality on very - // old Platform SDKs. + // Helper function to emulate InterlockedExchangePointer functionality for: + // - very old Platform SDKs; and + // - platform SDKs where MSVC's /Wp64 option causes spurious warnings. void* interlocked_exchange_pointer(void** dest, void* val) { -#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) +#if defined(_M_IX86) return reinterpret_cast(InterlockedExchange( - reinterpret_cast(dest), reinterpret_cast(val))); + reinterpret_cast(dest), reinterpret_cast(val))); #else return InterlockedExchangePointer(dest, val); #endif diff --git a/libtorrent/include/asio/detail/win_mutex.hpp b/libtorrent/include/asio/detail/win_mutex.hpp index 82659831f..4d1bc20c2 100644 --- a/libtorrent/include/asio/detail/win_mutex.hpp +++ b/libtorrent/include/asio/detail/win_mutex.hpp @@ -23,6 +23,7 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -48,7 +49,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } @@ -67,7 +68,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_thread.hpp b/libtorrent/include/asio/detail/win_thread.hpp index a6c9b15d2..c6bd61af5 100644 --- a/libtorrent/include/asio/detail/win_thread.hpp +++ b/libtorrent/include/asio/detail/win_thread.hpp @@ -23,6 +23,7 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -54,7 +55,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_tss_ptr.hpp b/libtorrent/include/asio/detail/win_tss_ptr.hpp index d3e2f8161..d84810d41 100644 --- a/libtorrent/include/asio/detail/win_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/win_tss_ptr.hpp @@ -23,6 +23,7 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -47,7 +48,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/winsock_init.hpp b/libtorrent/include/asio/detail/winsock_init.hpp index 67c69e8ce..874d2b77b 100644 --- a/libtorrent/include/asio/detail/winsock_init.hpp +++ b/libtorrent/include/asio/detail/winsock_init.hpp @@ -85,7 +85,8 @@ public: if (this != &instance_ && ref_->result() != 0) { asio::system_error e( - asio::error_code(ref_->result(), asio::native_ecat), + asio::error_code(ref_->result(), + asio::error::system_category), "winsock"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/wrapped_handler.hpp b/libtorrent/include/asio/detail/wrapped_handler.hpp index f757fd3dc..913a795dc 100644 --- a/libtorrent/include/asio/detail/wrapped_handler.hpp +++ b/libtorrent/include/asio/detail/wrapped_handler.hpp @@ -17,6 +17,10 @@ #include "asio/detail/push_options.hpp" +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + #include "asio/detail/bind_handler.hpp" #include "asio/detail/handler_alloc_helpers.hpp" #include "asio/detail/handler_invoke_helpers.hpp" @@ -30,7 +34,9 @@ class wrapped_handler public: typedef void result_type; - wrapped_handler(Dispatcher& dispatcher, Handler handler) + wrapped_handler( + typename boost::add_reference::type dispatcher, + Handler handler) : dispatcher_(dispatcher), handler_(handler) { @@ -117,7 +123,7 @@ public: } //private: - Dispatcher& dispatcher_; + Dispatcher dispatcher_; Handler handler_; }; @@ -171,9 +177,9 @@ inline void asio_handler_invoke(const Function& function, function, this_handler->handler_)); } -template +template inline void asio_handler_invoke(const Function& function, - rewrapped_handler* this_handler) + rewrapped_handler* this_handler) { asio_handler_invoke_helpers::invoke( function, &this_handler->context_); diff --git a/libtorrent/include/asio/error.hpp b/libtorrent/include/asio/error.hpp index 935cc6796..a8316be2c 100644 --- a/libtorrent/include/asio/error.hpp +++ b/libtorrent/include/asio/error.hpp @@ -37,327 +37,195 @@ /// INTERNAL ONLY. # define ASIO_WIN_OR_POSIX(e_win, e_posix) implementation_defined #elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) -# define ASIO_NATIVE_ERROR(e) \ - asio::error_code(e, \ - asio::native_ecat) -# define ASIO_SOCKET_ERROR(e) \ - asio::error_code(WSA ## e, \ - asio::native_ecat) -# define ASIO_NETDB_ERROR(e) \ - asio::error_code(WSA ## e, \ - asio::native_ecat) -# define ASIO_GETADDRINFO_ERROR(e) \ - asio::error_code(WSA ## e, \ - asio::native_ecat) -# define ASIO_MISC_ERROR(e) \ - asio::error_code(e, \ - asio::misc_ecat) +# define ASIO_NATIVE_ERROR(e) e +# define ASIO_SOCKET_ERROR(e) WSA ## e +# define ASIO_NETDB_ERROR(e) WSA ## e +# define ASIO_GETADDRINFO_ERROR(e) WSA ## e # define ASIO_WIN_OR_POSIX(e_win, e_posix) e_win #else -# define ASIO_NATIVE_ERROR(e) \ - asio::error_code(e, \ - asio::native_ecat) -# define ASIO_SOCKET_ERROR(e) \ - asio::error_code(e, \ - asio::native_ecat) -# define ASIO_NETDB_ERROR(e) \ - asio::error_code(e, \ - asio::netdb_ecat) -# define ASIO_GETADDRINFO_ERROR(e) \ - asio::error_code(e, \ - asio::addrinfo_ecat) -# define ASIO_MISC_ERROR(e) \ - asio::error_code(e, \ - asio::misc_ecat) +# define ASIO_NATIVE_ERROR(e) e +# define ASIO_SOCKET_ERROR(e) e +# define ASIO_NETDB_ERROR(e) e +# define ASIO_GETADDRINFO_ERROR(e) e # define ASIO_WIN_OR_POSIX(e_win, e_posix) e_posix #endif namespace asio { +namespace error { -namespace detail { - -/// Hack to keep asio library header-file-only. -template -class error_base +enum basic_errors { -public: - // boostify: error category declarations go here. - /// Permission denied. - static const asio::error_code access_denied; + access_denied = ASIO_SOCKET_ERROR(EACCES), /// Address family not supported by protocol. - static const asio::error_code address_family_not_supported; + address_family_not_supported = ASIO_SOCKET_ERROR(EAFNOSUPPORT), /// Address already in use. - static const asio::error_code address_in_use; + address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE), /// Transport endpoint is already connected. - static const asio::error_code already_connected; - - /// Already open. - static const asio::error_code already_open; + already_connected = ASIO_SOCKET_ERROR(EISCONN), /// Operation already in progress. - static const asio::error_code already_started; + already_started = ASIO_SOCKET_ERROR(EALREADY), /// A connection has been aborted. - static const asio::error_code connection_aborted; + connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED), /// Connection refused. - static const asio::error_code connection_refused; + connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED), /// Connection reset by peer. - static const asio::error_code connection_reset; + connection_reset = ASIO_SOCKET_ERROR(ECONNRESET), /// Bad file descriptor. - static const asio::error_code bad_descriptor; - - /// End of file or stream. - static const asio::error_code eof; + bad_descriptor = ASIO_SOCKET_ERROR(EBADF), /// Bad address. - static const asio::error_code fault; - - /// Host not found (authoritative). - static const asio::error_code host_not_found; - - /// Host not found (non-authoritative). - static const asio::error_code host_not_found_try_again; + fault = ASIO_SOCKET_ERROR(EFAULT), /// No route to host. - static const asio::error_code host_unreachable; + host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH), /// Operation now in progress. - static const asio::error_code in_progress; + in_progress = ASIO_SOCKET_ERROR(EINPROGRESS), /// Interrupted system call. - static const asio::error_code interrupted; + interrupted = ASIO_SOCKET_ERROR(EINTR), /// Invalid argument. - static const asio::error_code invalid_argument; + invalid_argument = ASIO_SOCKET_ERROR(EINVAL), /// Message too long. - static const asio::error_code message_size; + message_size = ASIO_SOCKET_ERROR(EMSGSIZE), /// Network is down. - static const asio::error_code network_down; + network_down = ASIO_SOCKET_ERROR(ENETDOWN), /// Network dropped connection on reset. - static const asio::error_code network_reset; + network_reset = ASIO_SOCKET_ERROR(ENETRESET), /// Network is unreachable. - static const asio::error_code network_unreachable; + network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH), /// Too many open files. - static const asio::error_code no_descriptors; + no_descriptors = ASIO_SOCKET_ERROR(EMFILE), /// No buffer space available. - static const asio::error_code no_buffer_space; - - /// The query is valid but does not have associated address data. - static const asio::error_code no_data; + no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS), /// Cannot allocate memory. - static const asio::error_code no_memory; + no_memory = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), + ASIO_NATIVE_ERROR(ENOMEM)), /// Operation not permitted. - static const asio::error_code no_permission; + no_permission = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), + ASIO_NATIVE_ERROR(EPERM)), /// Protocol not available. - static const asio::error_code no_protocol_option; - - /// A non-recoverable error occurred. - static const asio::error_code no_recovery; + no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT), /// Transport endpoint is not connected. - static const asio::error_code not_connected; - - /// Element not found. - static const asio::error_code not_found; + not_connected = ASIO_SOCKET_ERROR(ENOTCONN), /// Socket operation on non-socket. - static const asio::error_code not_socket; + not_socket = ASIO_SOCKET_ERROR(ENOTSOCK), /// Operation cancelled. - static const asio::error_code operation_aborted; + operation_aborted = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), + ASIO_NATIVE_ERROR(ECANCELED)), /// Operation not supported. - static const asio::error_code operation_not_supported; - - /// The service is not supported for the given socket type. - static const asio::error_code service_not_found; - - /// The socket type is not supported. - static const asio::error_code socket_type_not_supported; + operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP), /// Cannot send after transport endpoint shutdown. - static const asio::error_code shut_down; + shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN), /// Connection timed out. - static const asio::error_code timed_out; + timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT), /// Resource temporarily unavailable. - static const asio::error_code try_again; + try_again = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_RETRY), + ASIO_NATIVE_ERROR(EAGAIN)), /// The socket is marked non-blocking and the requested operation would block. - static const asio::error_code would_block; + would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK) +}; -private: - error_base(); +enum netdb_errors +{ + /// Host not found (authoritative). + host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND), + + /// Host not found (non-authoritative). + host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN), + + /// The query is valid but does not have associated address data. + no_data = ASIO_NETDB_ERROR(NO_DATA), + + /// A non-recoverable error occurred. + no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY) +}; + +enum addrinfo_errors +{ + /// The service is not supported for the given socket type. + service_not_found = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), + ASIO_GETADDRINFO_ERROR(EAI_SERVICE)), + + /// The socket type is not supported. + socket_type_not_supported = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), + ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)) +}; + +enum misc_errors +{ + /// Already open. + already_open = 1, + + /// End of file or stream. + eof, + + /// Element not found. + not_found }; // boostify: error category definitions go here. -template const asio::error_code -error_base::access_denied = ASIO_SOCKET_ERROR(EACCES); - -template const asio::error_code -error_base::address_family_not_supported = ASIO_SOCKET_ERROR( - EAFNOSUPPORT); - -template const asio::error_code -error_base::address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE); - -template const asio::error_code -error_base::already_connected = ASIO_SOCKET_ERROR(EISCONN); - -template const asio::error_code -error_base::already_open = ASIO_MISC_ERROR(1); - -template const asio::error_code -error_base::already_started = ASIO_SOCKET_ERROR(EALREADY); - -template const asio::error_code -error_base::connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED); - -template const asio::error_code -error_base::connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED); - -template const asio::error_code -error_base::connection_reset = ASIO_SOCKET_ERROR(ECONNRESET); - -template const asio::error_code -error_base::bad_descriptor = ASIO_SOCKET_ERROR(EBADF); - -template const asio::error_code -error_base::eof = ASIO_MISC_ERROR(2); - -template const asio::error_code -error_base::fault = ASIO_SOCKET_ERROR(EFAULT); - -template const asio::error_code -error_base::host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND); - -template const asio::error_code -error_base::host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN); - -template const asio::error_code -error_base::host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH); - -template const asio::error_code -error_base::in_progress = ASIO_SOCKET_ERROR(EINPROGRESS); - -template const asio::error_code -error_base::interrupted = ASIO_SOCKET_ERROR(EINTR); - -template const asio::error_code -error_base::invalid_argument = ASIO_SOCKET_ERROR(EINVAL); - -template const asio::error_code -error_base::message_size = ASIO_SOCKET_ERROR(EMSGSIZE); - -template const asio::error_code -error_base::network_down = ASIO_SOCKET_ERROR(ENETDOWN); - -template const asio::error_code -error_base::network_reset = ASIO_SOCKET_ERROR(ENETRESET); - -template const asio::error_code -error_base::network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH); - -template const asio::error_code -error_base::no_descriptors = ASIO_SOCKET_ERROR(EMFILE); - -template const asio::error_code -error_base::no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS); - -template const asio::error_code -error_base::no_data = ASIO_NETDB_ERROR(NO_DATA); - -template const asio::error_code -error_base::no_memory = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), - ASIO_NATIVE_ERROR(ENOMEM)); - -template const asio::error_code -error_base::no_permission = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), - ASIO_NATIVE_ERROR(EPERM)); - -template const asio::error_code -error_base::no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT); - -template const asio::error_code -error_base::no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY); - -template const asio::error_code -error_base::not_connected = ASIO_SOCKET_ERROR(ENOTCONN); - -template const asio::error_code -error_base::not_found = ASIO_MISC_ERROR(3); - -template const asio::error_code -error_base::not_socket = ASIO_SOCKET_ERROR(ENOTSOCK); - -template const asio::error_code -error_base::operation_aborted = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), - ASIO_NATIVE_ERROR(ECANCELED)); - -template const asio::error_code -error_base::operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP); - -template const asio::error_code -error_base::service_not_found = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), - ASIO_GETADDRINFO_ERROR(EAI_SERVICE)); - -template const asio::error_code -error_base::socket_type_not_supported = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), - ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)); - -template const asio::error_code -error_base::shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN); - -template const asio::error_code -error_base::timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT); - -template const asio::error_code -error_base::try_again = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_RETRY), - ASIO_NATIVE_ERROR(EAGAIN)); - -template const asio::error_code -error_base::would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK); - -} // namespace detail - -/// Contains error constants. -class error : public asio::detail::error_base +inline asio::error_code make_error_code(basic_errors e) { -private: - error(); -}; + return asio::error_code(static_cast(e), system_category); +} +inline asio::error_code make_error_code(netdb_errors e) +{ + return asio::error_code(static_cast(e), netdb_category); +} + +inline asio::error_code make_error_code(addrinfo_errors e) +{ + return asio::error_code(static_cast(e), addrinfo_category); +} + +inline asio::error_code make_error_code(misc_errors e) +{ + return asio::error_code(static_cast(e), misc_category); +} + +} // namespace error } // namespace asio #undef ASIO_NATIVE_ERROR #undef ASIO_SOCKET_ERROR #undef ASIO_NETDB_ERROR #undef ASIO_GETADDRINFO_ERROR -#undef ASIO_MISC_ERROR #undef ASIO_WIN_OR_POSIX #include "asio/impl/error_code.ipp" diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp index 0614490e2..0941a8c00 100644 --- a/libtorrent/include/asio/error_code.hpp +++ b/libtorrent/include/asio/error_code.hpp @@ -32,24 +32,27 @@ namespace asio { -/// Available error code categories. -enum error_category +namespace error { - /// Native error codes. - native_ecat = ASIO_WIN_OR_POSIX(0, 0), + /// Available error code categories. + enum error_category + { + /// System error codes. + system_category = ASIO_WIN_OR_POSIX(0, 0), - /// Error codes from NetDB functions. - netdb_ecat = ASIO_WIN_OR_POSIX(native_ecat, 1), + /// Error codes from NetDB functions. + netdb_category = ASIO_WIN_OR_POSIX(system_category, 1), - /// Error codes from getaddrinfo. - addrinfo_ecat = ASIO_WIN_OR_POSIX(native_ecat, 2), + /// Error codes from getaddrinfo. + addrinfo_category = ASIO_WIN_OR_POSIX(system_category, 2), - /// Miscellaneous error codes. - misc_ecat = ASIO_WIN_OR_POSIX(3, 3), + /// Miscellaneous error codes. + misc_category = ASIO_WIN_OR_POSIX(3, 3), - /// SSL error codes. - ssl_ecat = ASIO_WIN_OR_POSIX(4, 4) -}; + /// SSL error codes. + ssl_category = ASIO_WIN_OR_POSIX(4, 4) + }; +} // namespace error /// Class to represent an error code value. class error_code @@ -61,17 +64,24 @@ public: /// Default constructor. error_code() : value_(0), - category_(native_ecat) + category_(error::system_category) { } /// Construct with specific error code and category. - error_code(value_type v, error_category c) + error_code(value_type v, error::error_category c) : value_(v), category_(c) { } + /// Construct from an error code enum. + template + error_code(ErrorEnum e) + { + *this = make_error_code(e); + } + /// Get the error value. value_type value() const { @@ -79,7 +89,7 @@ public: } /// Get the error category. - error_category category() const + error::error_category category() const { return category_; } @@ -125,7 +135,7 @@ private: value_type value_; // The category associated with the error code. - error_category category_; + error::error_category category_; }; } // namespace asio diff --git a/libtorrent/include/asio/impl/CVS/Entries b/libtorrent/include/asio/impl/CVS/Entries deleted file mode 100644 index eadcea08f..000000000 --- a/libtorrent/include/asio/impl/CVS/Entries +++ /dev/null @@ -1,6 +0,0 @@ -/error_code.ipp/1.6/Sun Mar 25 14:06:36 2007// -/io_service.ipp/1.12/Mon Jan 8 01:04:08 2007// -/read.ipp/1.16/Sat Jan 13 13:30:12 2007// -/read_until.ipp/1.10/Sun Jan 7 08:05:53 2007// -/write.ipp/1.14/Sat Jan 13 13:30:12 2007// -D diff --git a/libtorrent/include/asio/impl/CVS/Repository b/libtorrent/include/asio/impl/CVS/Repository deleted file mode 100644 index c5e1b5327..000000000 --- a/libtorrent/include/asio/impl/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -asio/include/asio/impl diff --git a/libtorrent/include/asio/impl/CVS/Root b/libtorrent/include/asio/impl/CVS/Root deleted file mode 100644 index a7505d52a..000000000 --- a/libtorrent/include/asio/impl/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/impl/error_code.ipp b/libtorrent/include/asio/impl/error_code.ipp index da2f98833..f66b6fd94 100644 --- a/libtorrent/include/asio/impl/error_code.ipp +++ b/libtorrent/include/asio/impl/error_code.ipp @@ -35,10 +35,12 @@ inline std::string error_code::message() const return "Already open."; if (*this == error::not_found) return "Not found."; - if (category_ == ssl_ecat) + if (category_ == error::ssl_category) return "SSL error."; #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) value_type value = value_; + if (category() != error::system_category && *this != error::eof) + return "asio error"; if (*this == error::eof) value = ERROR_HANDLE_EOF; char* msg = 0; @@ -76,6 +78,8 @@ inline std::string error_code::message() const return "Service not found."; if (*this == error::socket_type_not_supported) return "Socket type not supported."; + if (category() != error::system_category) + return "asio error"; #if defined(__sun) || defined(__QNX__) return strerror(value_); #elif defined(__MACH__) && defined(__APPLE__) \ diff --git a/libtorrent/include/asio/impl/io_service.ipp b/libtorrent/include/asio/impl/io_service.ipp index e973619d1..f51d3697d 100644 --- a/libtorrent/include/asio/impl/io_service.ipp +++ b/libtorrent/include/asio/impl/io_service.ipp @@ -128,11 +128,11 @@ template #if defined(GENERATING_DOCUMENTATION) unspecified #else -inline detail::wrapped_handler +inline detail::wrapped_handler #endif io_service::wrap(Handler handler) { - return detail::wrapped_handler(*this, handler); + return detail::wrapped_handler(*this, handler); } inline io_service::work::work(asio::io_service& io_service) diff --git a/libtorrent/include/asio/impl/read_until.ipp b/libtorrent/include/asio/impl/read_until.ipp index 64c15ec7d..8b69a11c6 100644 --- a/libtorrent/include/asio/impl/read_until.ipp +++ b/libtorrent/include/asio/impl/read_until.ipp @@ -311,7 +311,8 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - handler_(error::not_found, bytes); + asio::error_code ec(error::not_found); + handler_(ec, bytes); return; } @@ -388,7 +389,8 @@ void async_read_until(AsyncReadStream& s, // No match. Check if buffer is full. if (b.size() == b.max_size()) { - s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + asio::error_code ec(error::not_found); + s.io_service().post(detail::bind_handler(handler, ec, 0)); return; } @@ -469,7 +471,8 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - handler_(error::not_found, bytes); + asio::error_code ec(error::not_found); + handler_(ec, bytes); return; } @@ -559,7 +562,8 @@ void async_read_until(AsyncReadStream& s, // Check if buffer is full. if (b.size() == b.max_size()) { - s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + asio::error_code ec(error::not_found); + s.io_service().post(detail::bind_handler(handler, ec, 0)); return; } @@ -641,7 +645,8 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - handler_(error::not_found, bytes); + asio::error_code ec(error::not_found); + handler_(ec, bytes); return; } @@ -731,7 +736,8 @@ void async_read_until(AsyncReadStream& s, // Check if buffer is full. if (b.size() == b.max_size()) { - s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + asio::error_code ec(error::not_found); + s.io_service().post(detail::bind_handler(handler, ec, 0)); return; } diff --git a/libtorrent/include/asio/io_service.hpp b/libtorrent/include/asio/io_service.hpp index b694545db..2101e56c4 100644 --- a/libtorrent/include/asio/io_service.hpp +++ b/libtorrent/include/asio/io_service.hpp @@ -320,7 +320,7 @@ public: #if defined(GENERATING_DOCUMENTATION) unspecified #else - detail::wrapped_handler + detail::wrapped_handler #endif wrap(Handler handler); diff --git a/libtorrent/include/asio/ip/CVS/Entries b/libtorrent/include/asio/ip/CVS/Entries deleted file mode 100644 index 5da45ff93..000000000 --- a/libtorrent/include/asio/ip/CVS/Entries +++ /dev/null @@ -1,17 +0,0 @@ -/address.hpp/1.9/Thu Jan 4 05:44:46 2007// -/address_v4.hpp/1.11/Thu Jan 4 05:44:46 2007// -/address_v6.hpp/1.11/Mon Feb 19 01:46:31 2007// -/basic_endpoint.hpp/1.16/Wed Feb 14 13:26:26 2007// -/basic_resolver.hpp/1.5/Thu Jan 4 05:44:46 2007// -/basic_resolver_entry.hpp/1.5/Thu Jan 4 05:44:46 2007// -/basic_resolver_iterator.hpp/1.10/Sun Apr 8 23:47:05 2007// -/basic_resolver_query.hpp/1.12/Thu Jan 4 05:44:46 2007// -/host_name.hpp/1.5/Thu Jan 4 05:44:46 2007// -/multicast.hpp/1.8/Sat Feb 17 22:57:39 2007// -/resolver_query_base.hpp/1.3/Thu Jan 4 05:44:46 2007// -/resolver_service.hpp/1.7/Thu Jan 4 05:44:46 2007// -/tcp.hpp/1.14/Sun May 20 00:49:02 2007// -/udp.hpp/1.12/Sun May 20 00:49:02 2007// -/unicast.hpp/1.2/Sat Feb 17 22:57:39 2007// -/v6_only.hpp/1.2/Sun May 13 07:59:22 2007// -D/detail//// diff --git a/libtorrent/include/asio/ip/CVS/Repository b/libtorrent/include/asio/ip/CVS/Repository deleted file mode 100644 index 96de0dd58..000000000 --- a/libtorrent/include/asio/ip/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -asio/include/asio/ip diff --git a/libtorrent/include/asio/ip/CVS/Root b/libtorrent/include/asio/ip/CVS/Root deleted file mode 100644 index a7505d52a..000000000 --- a/libtorrent/include/asio/ip/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ip/basic_endpoint.hpp b/libtorrent/include/asio/ip/basic_endpoint.hpp index 3ca91dc03..3d1316e22 100644 --- a/libtorrent/include/asio/ip/basic_endpoint.hpp +++ b/libtorrent/include/asio/ip/basic_endpoint.hpp @@ -172,7 +172,7 @@ public: /// The protocol associated with the endpoint. protocol_type protocol() const { - if (is_v4()) + if (is_v4(data_)) return InternetProtocol::v4(); return InternetProtocol::v6(); } @@ -192,7 +192,7 @@ public: /// Get the underlying size of the endpoint in the native type. size_type size() const { - if (is_v4()) + if (is_v4(data_)) return sizeof(asio::detail::sockaddr_in4_type); else return sizeof(asio::detail::sockaddr_in6_type); @@ -218,7 +218,7 @@ public: /// the host's byte order. unsigned short port() const { - if (is_v4()) + if (is_v4(data_)) { return asio::detail::socket_ops::network_to_host_short( reinterpret_cast( @@ -236,7 +236,7 @@ public: /// the host's byte order. void port(unsigned short port_num) { - if (is_v4()) + if (is_v4(data_)) { reinterpret_cast(data_).sin_port = asio::detail::socket_ops::host_to_network_short(port_num); @@ -252,7 +252,7 @@ public: asio::ip::address address() const { using namespace std; // For memcpy. - if (is_v4()) + if (is_v4(data_)) { const asio::detail::sockaddr_in4_type& data = reinterpret_cast( @@ -306,15 +306,27 @@ public: private: // Helper function to determine whether the endpoint is IPv4. - bool is_v4() const - { #if defined(_AIX) - return data_.__ss_family == AF_INET; -#else - return data_.ss_family == AF_INET; -#endif + template struct is_v4_helper {}; + + template + static bool is_v4(const T& ss, is_v4_helper* = 0) + { + return ss.ss_family == AF_INET; } + template + static bool is_v4(const T& ss, is_v4_helper* = 0) + { + return ss.__ss_family == AF_INET; + } +#else + static bool is_v4(const asio::detail::sockaddr_storage_type& ss) + { + return ss.ss_family == AF_INET; + } +#endif + // The underlying IP socket address. asio::detail::sockaddr_storage_type data_; }; diff --git a/libtorrent/include/asio/ssl/CVS/Entries b/libtorrent/include/asio/ssl/CVS/Entries deleted file mode 100644 index b15450e7b..000000000 --- a/libtorrent/include/asio/ssl/CVS/Entries +++ /dev/null @@ -1,8 +0,0 @@ -/basic_context.hpp/1.11/Thu Dec 21 12:29:03 2006// -/context.hpp/1.3/Mon Apr 10 12:17:44 2006// -/context_base.hpp/1.6/Sun Sep 24 07:46:22 2006// -/context_service.hpp/1.11/Fri Dec 29 02:01:24 2006// -/stream.hpp/1.13/Thu Dec 21 12:29:03 2006// -/stream_base.hpp/1.4/Wed Nov 30 01:57:07 2005// -/stream_service.hpp/1.10/Fri Dec 29 02:01:24 2006// -D/detail//// diff --git a/libtorrent/include/asio/ssl/CVS/Repository b/libtorrent/include/asio/ssl/CVS/Repository deleted file mode 100644 index a0555e83a..000000000 --- a/libtorrent/include/asio/ssl/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -asio/include/asio/ssl diff --git a/libtorrent/include/asio/ssl/CVS/Root b/libtorrent/include/asio/ssl/CVS/Root deleted file mode 100644 index a7505d52a..000000000 --- a/libtorrent/include/asio/ssl/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp index b7a564464..5fd3ebba4 100755 --- a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp @@ -174,12 +174,12 @@ public: if (error_code == SSL_ERROR_SYSCALL) { return handler_(asio::error_code( - sys_error_code, asio::native_ecat), rc); + sys_error_code, asio::error::system_category), rc); } else { return handler_(asio::error_code( - error_code, asio::ssl_ecat), rc); + error_code, asio::error::ssl_category), rc); } } diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp index b6b6711dc..954e39ef5 100755 --- a/libtorrent/include/libtorrent/alert.hpp +++ b/libtorrent/include/libtorrent/alert.hpp @@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #ifdef _MSC_VER @@ -56,6 +55,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/time.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" #ifndef TORRENT_MAX_ALERT_TYPES #define TORRENT_MAX_ALERT_TYPES 15 diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 48491bca4..36c13c5ab 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_connection.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -223,7 +224,7 @@ namespace libtorrent { block_downloading_alert( const torrent_handle& h - , std::string& speedmsg + , char const* speedmsg , int block_num , int piece_num , const std::string& msg) @@ -261,6 +262,17 @@ namespace libtorrent { return std::auto_ptr(new torrent_paused_alert(*this)); } }; + struct TORRENT_EXPORT torrent_checked_alert: torrent_alert + { + torrent_checked_alert(torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::info, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new torrent_checked_alert(*this)); } + }; + + struct TORRENT_EXPORT url_seed_alert: torrent_alert { url_seed_alert( diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp new file mode 100644 index 000000000..62425809e --- /dev/null +++ b/libtorrent/include/libtorrent/assert.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#ifndef NDEBUG +#if defined __linux__ && defined __GNUC__ +#ifdef assert +#undef assert +#endif + +void assert_fail(const char* expr, int line, char const* file, char const* function); + +#define assert(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) + +#endif + +#else +#ifndef assert +#define assert(x) (void) +#endif +#endif + diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 207016898..0389bf3dc 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -83,6 +83,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type.hpp" #include "libtorrent/connection_queue.hpp" #include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -240,12 +241,12 @@ namespace libtorrent bool is_listening() const; torrent_handle add_torrent( - torrent_info const& ti + boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused); torrent_handle add_torrent( char const* tracker_url @@ -254,8 +255,8 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused); void remove_torrent(torrent_handle const& h); @@ -273,8 +274,21 @@ namespace libtorrent void set_max_connections(int limit); void set_max_uploads(int limit); - int num_uploads() const; - int num_connections() const; + int max_connections() const { return m_max_connections; } + int max_uploads() const { return m_max_uploads; } + int max_half_open_connections() const { return m_half_open.limit(); } + + int num_uploads() const { return m_num_unchoked; } + int num_connections() const + { return m_connections.size(); } + + void unchoke_peer(peer_connection& c) + { + torrent* t = c.associated_torrent().lock().get(); + assert(t); + if (t->unchoke_peer(c)) + ++m_num_unchoked; + } session_status status() const; void set_peer_id(peer_id const& id); @@ -417,6 +431,28 @@ namespace libtorrent int m_max_uploads; int m_max_connections; + // the number of unchoked peers + int m_num_unchoked; + + // this is initialized to the unchoke_interval + // session_setting and decreased every second. + // when it reaches zero, it is reset to the + // unchoke_interval and the unchoke set is + // recomputed. + int m_unchoke_time_scaler; + + // works like unchoke_time_scaler but it + // is only decresed when the unchoke set + // is recomputed, and when it reaches zero, + // the optimistic unchoke is moved to another peer. + int m_optimistic_unchoke_time_scaler; + + // works like unchoke_time_scaler. Each time + // it reaches 0, and all the connections are + // used, the worst connection will be disconnected + // from the torrent with the most peers + int m_disconnect_time_scaler; + // statistics gathered from all torrents. stat m_stat; @@ -459,7 +495,7 @@ namespace libtorrent // This implements a round robin. int m_next_connect_torrent; #ifndef NDEBUG - void check_invariant(const char *place = 0); + void check_invariant() const; #endif #ifdef TORRENT_STATS diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 75e1f1d4e..03d4f65ae 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -33,9 +33,6 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED #define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED -#include "libtorrent/socket.hpp" -#include "libtorrent/invariant_check.hpp" - #include #include #include @@ -44,11 +41,17 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include "libtorrent/socket.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" + using boost::weak_ptr; using boost::shared_ptr; using boost::intrusive_ptr; using boost::bind; +//#define TORRENT_VERBOSE_BANDWIDTH_LIMIT + namespace libtorrent { // the maximum block of bandwidth quota to @@ -237,8 +240,10 @@ struct bandwidth_manager i = j; } } - - if (m_queue.size() == 1) hand_out_bandwidth(); +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::cerr << " req_bandwidht. m_queue.size() = " << m_queue.size() << std::endl; +#endif + if (!m_queue.empty()) hand_out_bandwidth(); } #ifndef NDEBUG @@ -337,10 +342,18 @@ private: // available bandwidth to hand out int amount = limit - m_current_quota; +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::cerr << " hand_out_bandwidht. m_queue.size() = " << m_queue.size() + << " amount = " << amount + << " limit = " << limit + << " m_current_quota = " << m_current_quota << std::endl; +#endif + while (!m_queue.empty() && amount > 0) { assert(amount == limit - m_current_quota); bw_queue_entry qe = m_queue.front(); + assert(qe.max_block_size > 0); m_queue.pop_front(); shared_ptr t = qe.peer->associated_torrent().lock(); @@ -374,13 +387,12 @@ private: // block size must be smaller for lower rates. This is because // the history window is one second, and the block will be forgotten // after one second. - int block_size = (std::min)(qe.max_block_size - , (std::min)(qe.peer->bandwidth_throttle(m_channel) - , m_limit / 10)); + int block_size = (std::min)(qe.peer->bandwidth_throttle(m_channel) + , m_limit / 10); if (block_size < min_bandwidth_block_size) { - block_size = min_bandwidth_block_size; + block_size = (std::min)(int(min_bandwidth_block_size), m_limit); } else if (block_size > max_bandwidth_block_size) { @@ -399,7 +411,11 @@ private: / (m_limit / max_bandwidth_block_size); } } + if (block_size > qe.max_block_size) block_size = qe.max_block_size; +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; +#endif if (amount < block_size / 2) { m_queue.push_front(qe); diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index a142b5864..9e670c10b 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -79,6 +79,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/entry.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + #if defined(_MSC_VER) namespace std { diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp new file mode 100644 index 000000000..bdfe30b6e --- /dev/null +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BROADCAST_SOCKET_HPP_INCLUDED +#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include +#include +#include + +namespace libtorrent +{ + + bool is_local(address const& a); + address_v4 guess_local_address(asio::io_service&); + + typedef boost::function receive_handler_t; + + class broadcast_socket + { + public: + broadcast_socket(asio::io_service& ios, udp::endpoint const& multicast_endpoint + , receive_handler_t const& handler); + + void send(char const* buffer, int size, asio::error_code& ec); + void close(); + + private: + + struct socket_entry + { + socket_entry(boost::shared_ptr const& s): socket(s) {} + boost::shared_ptr socket; + char buffer[1024]; + udp::endpoint remote; + }; + + void on_receive(socket_entry* s, asio::error_code const& ec + , std::size_t bytes_transferred); + + std::list m_sockets; + udp::endpoint m_multicast_endpoint; + receive_handler_t m_on_receive; + + }; +} + +#endif + diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index beec94979..0fcba89a8 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -65,7 +65,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -122,8 +121,16 @@ namespace libtorrent msg_request, msg_piece, msg_cancel, + // DHT extension msg_dht_port, - // extension protocol message + // FAST extension + msg_suggest_piece = 0xd, + msg_have_all, + msg_have_none, + msg_reject_request, + msg_allowed_fast, + + // extension protocol message msg_extended = 20, num_supported_messages @@ -174,8 +181,17 @@ namespace libtorrent void on_request(int received); void on_piece(int received); void on_cancel(int received); + + // DHT extension void on_dht_port(int received); + // FAST extension + void on_suggest_piece(int received); + void on_have_all(int received); + void on_have_none(int received); + void on_reject_request(int received); + void on_allowed_fast(int received); + void on_extended(int received); void on_extended_handshake(); @@ -201,7 +217,16 @@ namespace libtorrent void write_metadata(std::pair req); void write_metadata_request(std::pair req); void write_keepalive(); + + // DHT extension void write_dht_port(int listen_port); + + // FAST extension + void write_have_all(); + void write_have_none(); + void write_reject_request(peer_request const&); + void write_allow_fast(int piece); + void on_connected(); void on_metadata(); @@ -325,6 +350,7 @@ namespace libtorrent bool m_supports_extensions; #endif bool m_supports_dht_port; + bool m_supports_fast; #ifndef TORRENT_DISABLE_ENCRYPTION // this is set to true after the encryption method has been diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index 0cb44225a..0f37edcbd 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -34,8 +34,9 @@ POSSIBILITY OF SUCH DAMAGE. //#define TORRENT_BUFFER_DEBUG -#include "libtorrent/invariant_check.hpp" #include +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/config.hpp b/libtorrent/include/libtorrent/config.hpp index b36d4da22..1281ab84c 100755 --- a/libtorrent/include/libtorrent/config.hpp +++ b/libtorrent/include/libtorrent/config.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_CONFIG_HPP_INCLUDED #include +#include "libtorrent/assert.hpp" #if defined(__GNUC__) && __GNUC__ >= 4 diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp index 17be248bf..b3b7cde86 100644 --- a/libtorrent/include/libtorrent/connection_queue.hpp +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "libtorrent/socket.hpp" #include "libtorrent/time.hpp" @@ -88,6 +89,10 @@ private: int m_half_open_limit; deadline_timer m_timer; + + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + #ifndef NDEBUG bool m_in_timeout_function; #endif diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp index 436b695f6..1bb645a8e 100755 --- a/libtorrent/include/libtorrent/debug.hpp +++ b/libtorrent/include/libtorrent/debug.hpp @@ -80,3 +80,4 @@ namespace libtorrent } #endif // TORRENT_DEBUG_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp index 59e29803d..7fd6c8c53 100755 --- a/libtorrent/include/libtorrent/entry.hpp +++ b/libtorrent/include/libtorrent/entry.hpp @@ -64,10 +64,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include "libtorrent/size_type.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp new file mode 100644 index 000000000..0c6063a2b --- /dev/null +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" + +namespace libtorrent +{ + std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); +} + +#endif + diff --git a/libtorrent/include/libtorrent/extensions.hpp b/libtorrent/include/libtorrent/extensions.hpp index 5f8172649..44fff9c36 100644 --- a/libtorrent/include/libtorrent/extensions.hpp +++ b/libtorrent/include/libtorrent/extensions.hpp @@ -131,6 +131,15 @@ namespace libtorrent virtual bool on_bitfield(std::vector const& bitfield) { return false; } + virtual bool on_have_all() + { return false; } + + virtual bool on_have_none() + { return false; } + + virtual bool on_allowed_fast(int index) + { return false; } + virtual bool on_request(peer_request const& req) { return false; } diff --git a/libtorrent/include/libtorrent/fingerprint.hpp b/libtorrent/include/libtorrent/fingerprint.hpp index d7e5a5fc6..712be6979 100755 --- a/libtorrent/include/libtorrent/fingerprint.hpp +++ b/libtorrent/include/libtorrent/fingerprint.hpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -91,3 +92,4 @@ namespace libtorrent } #endif // TORRENT_FINGERPRINT_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/hasher.hpp b/libtorrent/include/libtorrent/hasher.hpp index 932f2b100..71b7f9ede 100755 --- a/libtorrent/include/libtorrent/hasher.hpp +++ b/libtorrent/include/libtorrent/hasher.hpp @@ -33,11 +33,11 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_HASHER_HPP_INCLUDED #define TORRENT_HASHER_HPP_INCLUDED -#include #include #include "libtorrent/peer_id.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" #include "zlib.h" #ifdef TORRENT_USE_OPENSSL diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index 409213857..ccc145413 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -44,13 +44,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/http_tracker_connection.hpp" #include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { +struct http_connection; + typedef boost::function http_handler; +typedef boost::function http_connect_handler; + // TODO: add bind interface // when bottled, the last two arguments to the handler @@ -58,11 +63,13 @@ typedef boost::function, boost::noncopyable { http_connection(asio::io_service& ios, connection_queue& cc - , http_handler handler, bool bottled = true) + , http_handler const& handler, bool bottled = true + , http_connect_handler const& ch = http_connect_handler()) : m_sock(ios) , m_read_pos(0) , m_resolver(ios) , m_handler(handler) + , m_connect_handler(ch) , m_timer(ios) , m_last_receive(time_now()) , m_bottled(bottled) @@ -92,6 +99,8 @@ struct http_connection : boost::enable_shared_from_this, boost: , time_duration timeout, bool handle_redirect = true); void close(); + tcp::socket const& socket() const { return m_sock; } + private: void on_resolve(asio::error_code const& e @@ -112,6 +121,7 @@ private: tcp::resolver m_resolver; http_parser m_parser; http_handler m_handler; + http_connect_handler m_connect_handler; deadline_timer m_timer; time_duration m_timeout; ptime m_last_receive; diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index 35d529504..b3f35084c 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -73,6 +73,8 @@ namespace libtorrent T header(char const* key) const; std::string const& protocol() const { return m_protocol; } int status_code() const { return m_status_code; } + std::string const& method() const { return m_method; } + std::string const& path() const { return m_path; } std::string message() const { return m_server_message; } buffer::const_interval get_body() const; bool header_finished() const { return m_state == read_body; } @@ -85,6 +87,8 @@ namespace libtorrent private: int m_recv_pos; int m_status_code; + std::string m_method; + std::string m_path; std::string m_protocol; std::string m_server_message; @@ -176,3 +180,4 @@ namespace libtorrent #endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index a432bc350..ed6944ebb 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -34,14 +34,17 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_INTRUSIVE_PTR_BASE #include -#include #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { template struct intrusive_ptr_base { + intrusive_ptr_base(const intrusive_ptr_base& b) + : m_refs(0) {} + friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) { assert(s->m_refs >= 0); diff --git a/libtorrent/include/libtorrent/invariant_check.hpp b/libtorrent/include/libtorrent/invariant_check.hpp index c6eacf338..3eaacf34c 100755 --- a/libtorrent/include/libtorrent/invariant_check.hpp +++ b/libtorrent/include/libtorrent/invariant_check.hpp @@ -5,7 +5,7 @@ #ifndef TORRENT_INVARIANT_ACCESS_HPP_INCLUDED #define TORRENT_INVARIANT_ACCESS_HPP_INCLUDED -#include +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/ip_filter.hpp b/libtorrent/include/libtorrent/ip_filter.hpp index 8b1793c3a..7b8cc0e17 100644 --- a/libtorrent/include/libtorrent/ip_filter.hpp +++ b/libtorrent/include/libtorrent/ip_filter.hpp @@ -33,6 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_IP_FILTER_HPP #define TORRENT_IP_FILTER_HPP +#include +#include + #ifdef _MSC_VER #pragma warning(push, 1) #endif @@ -48,8 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/socket.hpp" -#include -#include +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/kademlia/node.hpp b/libtorrent/include/libtorrent/kademlia/node.hpp index 850333043..ee75e7f0a 100644 --- a/libtorrent/include/libtorrent/kademlia/node.hpp +++ b/libtorrent/include/libtorrent/kademlia/node.hpp @@ -34,7 +34,6 @@ POSSIBILITY OF SUCH DAMAGE. #define NODE_HPP #include -#include #include #include @@ -45,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include diff --git a/libtorrent/include/libtorrent/kademlia/node_id.hpp b/libtorrent/include/libtorrent/kademlia/node_id.hpp index eb4d6c539..5e732acac 100644 --- a/libtorrent/include/libtorrent/kademlia/node_id.hpp +++ b/libtorrent/include/libtorrent/kademlia/node_id.hpp @@ -33,10 +33,10 @@ POSSIBILITY OF SUCH DAMAGE. #define NODE_ID_HPP #include -#include #include #include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { namespace dht { diff --git a/libtorrent/include/libtorrent/kademlia/routing_table.hpp b/libtorrent/include/libtorrent/kademlia/routing_table.hpp index 45a7dd762..9e10a3483 100644 --- a/libtorrent/include/libtorrent/kademlia/routing_table.hpp +++ b/libtorrent/include/libtorrent/kademlia/routing_table.hpp @@ -50,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace libtorrent { namespace dht { diff --git a/libtorrent/include/libtorrent/lsd.hpp b/libtorrent/include/libtorrent/lsd.hpp index 9ffbcdfc3..e8eaf0df1 100644 --- a/libtorrent/include/libtorrent/lsd.hpp +++ b/libtorrent/include/libtorrent/lsd.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_id.hpp" +#include "libtorrent/broadcast_socket.hpp" #include #include @@ -58,35 +59,26 @@ public: , peer_callback_t const& cb); ~lsd(); - void rebind(address const& listen_interface); +// void rebind(address const& listen_interface); void announce(sha1_hash const& ih, int listen_port); void close(); private: - static address_v4 lsd_multicast_address; - static udp::endpoint lsd_multicast_endpoint; - void resend_announce(asio::error_code const& e, std::string msg); - void on_announce(asio::error_code const& e + void on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); - void setup_receive(); +// void setup_receive(); peer_callback_t m_callback; // current retry count int m_retry_count; - // used to receive responses in - char m_receive_buffer[1024]; - - // the endpoint we received the message from - udp::endpoint m_remote; - // the udp socket used to send and receive // multicast messages on - datagram_socket m_socket; + broadcast_socket m_socket; // used to resend udp packets in case // they time out diff --git a/libtorrent/include/libtorrent/pe_crypto.hpp b/libtorrent/include/libtorrent/pe_crypto.hpp index 91616c42d..e2276dee6 100644 --- a/libtorrent/include/libtorrent/pe_crypto.hpp +++ b/libtorrent/include/libtorrent/pe_crypto.hpp @@ -35,13 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED #define TORRENT_PE_CRYPTO_HPP_INCLUDED -#include - #include #include #include -#include "peer_id.hpp" // For sha1_hash +#include "libtorrent/peer_id.hpp" // For sha1_hash +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 31bcde94a..ea16a8d0a 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -64,7 +64,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -73,6 +72,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" #include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -131,6 +131,8 @@ namespace libtorrent enum peer_speed_t { slow, medium, fast }; peer_speed_t peer_speed(); + void send_allowed_set(); + #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::shared_ptr); #endif @@ -151,11 +153,17 @@ namespace libtorrent int upload_limit() const { return m_upload_limit; } int download_limit() const { return m_download_limit; } - bool prefer_whole_pieces() const - { return m_prefer_whole_pieces; } + int prefer_whole_pieces() const + { + if (on_parole()) return 1; + return m_prefer_whole_pieces; + } - void prefer_whole_pieces(bool b) - { m_prefer_whole_pieces = b; } + bool on_parole() const + { return peer_info_struct() && peer_info_struct()->on_parole; } + + void prefer_whole_pieces(int num) + { m_prefer_whole_pieces = num; } bool request_large_blocks() const { return m_request_large_blocks; } @@ -186,9 +194,9 @@ namespace libtorrent void set_pid(const peer_id& pid) { m_peer_id = pid; } bool has_piece(int i) const; - const std::deque& download_queue() const; - const std::deque& request_queue() const; - const std::deque& upload_queue() const; + std::deque const& download_queue() const; + std::deque const& request_queue() const; + std::deque const& upload_queue() const; bool is_interesting() const { return m_interesting; } bool is_choked() const { return m_choked; } @@ -211,12 +219,14 @@ namespace libtorrent void add_stat(size_type downloaded, size_type uploaded); // is called once every second by the main loop - void second_tick(float tick_interval); + void second_tick(float tick_interval) throw(); boost::shared_ptr get_socket() const { return m_socket; } tcp::endpoint const& remote() const { return m_remote; } std::vector const& get_bitfield() const; + std::vector const& allowed_fast(); + std::vector const& suggested_pieces() const { return m_suggested_pieces; } void timed_out(); // this will cause this peer_connection to be disconnected. @@ -294,7 +304,14 @@ namespace libtorrent void incoming_piece(peer_request const& p, char const* data); void incoming_piece_fragment(); void incoming_cancel(peer_request const& r); + void incoming_dht_port(int listen_port); + + void incoming_reject_request(peer_request const& r); + void incoming_have_all(); + void incoming_have_none(); + void incoming_allowed_fast(int index); + void incoming_suggest(int index); // the following functions appends messages // to the send buffer @@ -373,6 +390,9 @@ namespace libtorrent virtual void write_keepalive() = 0; virtual void write_piece(peer_request const& r, char const* buffer) = 0; + virtual void write_reject_request(peer_request const& r) = 0; + virtual void write_allow_fast(int piece) = 0; + virtual void on_connected() = 0; virtual void on_tick() {} @@ -482,6 +502,11 @@ namespace libtorrent // the time we sent a request to // this peer the last time ptime m_last_request; + // the time we received the last + // piece request from the peer + ptime m_last_incoming_request; + // the time when we unchoked this peer + ptime m_last_unchoke; int m_packet_size; int m_recv_pos; @@ -529,7 +554,7 @@ namespace libtorrent // set to the torrent it belongs to. boost::weak_ptr m_torrent; // is true if it was we that connected to the peer - // and false if we got an incomming connection + // and false if we got an incoming connection // could be considered: true = local, false = remote bool m_active; @@ -563,6 +588,10 @@ namespace libtorrent // the pieces the other end have std::vector m_have_piece; + // this is set to true when a have_all + // message is received. This information + // is used to fill the bitmask in init() + bool m_have_all; // the number of pieces this peer // has. Must be the same as @@ -575,7 +604,7 @@ namespace libtorrent std::deque m_requests; // the blocks we have reserved in the piece - // picker and will send to this peer. + // picker and will request from this peer. std::deque m_request_queue; // the queue of blocks we have requested @@ -643,12 +672,13 @@ namespace libtorrent bool m_writing; bool m_reading; - // if set to true, this peer will always prefer - // to request entire pieces, rather than blocks. - // if it is false, the download rate limit setting + // if set to non-zero, this peer will always prefer + // to request entire n pieces, rather than blocks. + // where n is the value of this variable. + // if it is 0, the download rate limit setting // will be used to determine if whole pieces // are preferred. - bool m_prefer_whole_pieces; + int m_prefer_whole_pieces; // if this is true, the blocks picked by the piece // picker will be merged before passed to the @@ -695,6 +725,18 @@ namespace libtorrent // was last updated ptime m_remote_dl_update; + // the pieces we will send to the peer + // if requested (regardless of choke state) + std::set m_accept_fast; + + // the pieces the peer will send us if + // requested (regardless of choke state) + std::vector m_allowed_fast; + + // pieces that has been suggested to be + // downloaded from this peer + std::vector m_suggested_pieces; + // the number of bytes send to the disk-io // thread that hasn't yet been completely written. int m_outstanding_writing_bytes; diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp index b66c1d4bc..57303e2fd 100755 --- a/libtorrent/include/libtorrent/peer_id.hpp +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include #include #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index 15ad34a7a..046df2a6b 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -56,10 +56,11 @@ namespace libtorrent connecting = 0x80, queued = 0x100, on_parole = 0x200, - seed = 0x400 + seed = 0x400, + optimistic_unchoke = 0x800 #ifndef TORRENT_DISABLE_ENCRYPTION - , rc4_encrypted = 0x800, - plaintext_encrypted = 0x1000 + , rc4_encrypted = 0x100000, + plaintext_encrypted = 0x200000 #endif }; @@ -116,6 +117,11 @@ namespace libtorrent // for yet int download_queue_length; + // the number of requests that is + // tried to be maintained (this is + // typically a function of download speed) + int target_dl_queue_length; + // this is the number of requests // the peer has sent to us // that we haven't sent yet diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index 54df003ef..94f274a27 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -35,7 +35,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #ifdef _MSC_VER @@ -52,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -191,11 +191,33 @@ namespace libtorrent // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! // The last argument is the policy::peer pointer for the peer that // we'll download from. - void pick_pieces(const std::vector& pieces + void pick_pieces(std::vector const& pieces , std::vector& interesting_blocks - , int num_pieces, bool prefer_whole_pieces + , int num_pieces, int prefer_whole_pieces , void* peer, piece_state_t speed - , bool rarest_first) const; + , bool rarest_first, bool on_parole + , std::vector const& suggested_pieces) const; + + // picks blocks from each of the pieces in the piece_list + // vector that is also in the piece bitmask. The blocks + // are added to interesting_blocks, and busy blocks are + // added to backup_blocks. num blocks is the number of + // blocks to be picked. Blocks are not picked from pieces + // that are being downloaded + int add_blocks(std::vector const& piece_list + , const std::vector& pieces + , std::vector& interesting_blocks + , int num_blocks, int prefer_whole_pieces + , void* peer, std::vector const& ignore) const; + + // picks blocks only from downloading pieces + int add_blocks_downloading( + std::vector const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, int prefer_whole_pieces + , void* peer, piece_state_t speed + , bool on_parole) const; // clears the peer pointer in all downloading pieces with this // peer pointer @@ -253,6 +275,8 @@ namespace libtorrent #ifndef NDEBUG // used in debug mode void check_invariant(const torrent* t = 0) const; + void verify_pick(std::vector const& picked + , std::vector const& bitfield) const; #endif // functor that compares indices on downloading_pieces @@ -271,6 +295,10 @@ namespace libtorrent private: + bool can_pick(int piece, std::vector const& bitmask) const; + std::pair expand_piece(int piece, int whole_pieces + , std::vector const& have) const; + struct piece_pos { piece_pos() {} @@ -320,9 +348,9 @@ namespace libtorrent int priority(int limit) const { - if (filtered() || have()) return 0; + if (downloading || filtered() || have()) return 0; // pieces we are currently downloading have high priority - int prio = downloading ? (std::min)(1, int(peer_count)) : peer_count * 2; + int prio = peer_count * 2; // if the peer_count is 0 or 1, the priority cannot be higher if (prio <= 1) return prio; if (prio >= limit * 2) prio = limit * 2; @@ -358,14 +386,6 @@ namespace libtorrent void move(int vec_index, int elem_index); void sort_piece(std::vector::iterator dp); - int add_interesting_blocks(const std::vector& piece_list - , const std::vector& pieces - , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, bool prefer_whole_pieces - , void* peer, piece_state_t speed - , bool ignore_downloading_pieces) const; - downloading_piece& add_download_piece(); void erase_download_piece(std::vector::iterator i); diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 6c976d047..7a789ec8c 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -89,7 +89,7 @@ namespace libtorrent void new_connection(peer_connection& c); // the given connection was just closed - void connection_closed(const peer_connection& c); + void connection_closed(const peer_connection& c) throw(); // the peer has got at least one interesting piece void peer_is_interesting(peer_connection& c); @@ -155,6 +155,13 @@ namespace libtorrent // this is true if the peer is a seed bool seed; + // true if this peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another peer, this peer will be choked + // if this is true + bool optimistically_unchoked; + // the time when this peer was optimistically unchoked // the last time. libtorrent::ptime last_optimistically_unchoked; @@ -203,25 +210,18 @@ namespace libtorrent peer_connection* connection; }; - int num_peers() const - { - return m_peers.size(); - } + int num_peers() const { return m_peers.size(); } - int num_uploads() const - { - return m_num_unchoked; - } - typedef std::list::iterator iterator; typedef std::list::const_iterator const_iterator; iterator begin_peer() { return m_peers.begin(); } iterator end_peer() { return m_peers.end(); } bool connect_one_peer(); + bool disconnect_one_peer(); private: - +/* bool unchoke_one_peer(); void choke_one_peer(); iterator find_choke_candidate(); @@ -233,8 +233,7 @@ namespace libtorrent void seed_choke_one_peer(); iterator find_seed_choke_candidate(); iterator find_seed_unchoke_candidate(); - - bool disconnect_one_peer(); +*/ iterator find_disconnect_candidate(); iterator find_connect_candidate(); @@ -242,10 +241,6 @@ namespace libtorrent torrent* m_torrent; - // the number of unchoked peers - // at any given time - int m_num_unchoked; - // free download we have got that hasn't // been distributed yet. size_type m_available_free_upload; @@ -253,7 +248,7 @@ namespace libtorrent // if there is a connection limit, // we disconnect one peer every minute in hope of // establishing a connection with a better peer - ptime m_last_optimistic_disconnect; +// ptime m_last_optimistic_disconnect; }; } diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 38206f32c..2ce19349e 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -53,6 +53,7 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif +#include "libtorrent/config.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/alert.hpp" @@ -60,7 +61,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/fingerprint.hpp" -#include "libtorrent/resource_request.hpp" #include "libtorrent/storage.hpp" #ifdef _MSC_VER @@ -141,22 +141,16 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , int block_size = 16 * 1024 - , storage_constructor_type sc = default_storage_constructor); + , bool paused = false + , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED; - // ==== deprecated, this is for backwards compatibility only - // instead, use one of the other add_torrent overloads torrent_handle add_torrent( - entry const& e + boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , int block_size = 16 * 1024 - , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED - { - return add_torrent(torrent_info(e), save_path, resume_data - , compact_mode, block_size, sc); - } + , bool paused = false + , storage_constructor_type sc = default_storage_constructor); torrent_handle add_torrent( char const* tracker_url @@ -165,7 +159,7 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , int block_size = 16 * 1024 + , bool paused = false , storage_constructor_type sc = default_storage_constructor); session_proxy abort() { return session_proxy(m_impl); } @@ -243,6 +237,7 @@ namespace libtorrent int upload_rate_limit() const; int download_rate_limit() const; + int max_half_open_connections() const; void set_upload_rate_limit(int bytes_per_second); void set_download_rate_limit(int bytes_per_second); @@ -265,12 +260,6 @@ namespace libtorrent void stop_natpmp(); void stop_upnp(); - // Resource management used for global limits. - resource_request m_ul_bandwidth_quota; - resource_request m_dl_bandwidth_quota; - resource_request m_uploads_quota; - resource_request m_connections_quota; - private: // just a way to initialize boost.filesystem diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index ebc30eae3..3a145c687 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -105,9 +105,11 @@ namespace libtorrent , send_redundant_have(false) , lazy_bitfields(true) , inactivity_timeout(600) - , unchoke_interval(20) + , unchoke_interval(15) + , optimistic_unchoke_multiplier(4) , num_want(200) , initial_picker_threshold(4) + , allowed_fast_set_size(10) , max_outstanding_disk_bytes_per_connection(64 * 1024) #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) @@ -241,6 +243,10 @@ namespace libtorrent // the number of seconds between chokes/unchokes int unchoke_interval; + // the number of unchoke intervals between + // optimistic unchokes + int optimistic_unchoke_multiplier; + // if this is set, this IP will be reported do the // tracker in the ip= parameter. address announce_ip; @@ -252,6 +258,10 @@ namespace libtorrent // random pieces instead of rarest first. int initial_picker_threshold; + // the number of allowed pieces to send to peers + // that supports the fast extensions + int allowed_fast_set_size; + // the maximum number of bytes a connection may have // pending in the disk write queue before its download // rate is being throttled. This prevents fast downloads diff --git a/libtorrent/include/libtorrent/stat.hpp b/libtorrent/include/libtorrent/stat.hpp index 2424d5d6c..24e477a37 100755 --- a/libtorrent/include/libtorrent/stat.hpp +++ b/libtorrent/include/libtorrent/stat.hpp @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/size_type.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 8a10c7148..e52196c76 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #ifdef _MSC_VER @@ -147,10 +148,11 @@ namespace libtorrent }; typedef storage_interface* (&storage_constructor_type)( - torrent_info const&, fs::path const& + boost::intrusive_ptr, fs::path const& , file_pool&); - TORRENT_EXPORT storage_interface* default_storage_constructor(torrent_info const& ti + TORRENT_EXPORT storage_interface* default_storage_constructor( + boost::intrusive_ptr ti , fs::path const& path, file_pool& fp); // returns true if the filesystem the path relies on supports @@ -169,7 +171,7 @@ namespace libtorrent piece_manager( boost::shared_ptr const& torrent - , torrent_info const& ti + , boost::intrusive_ptr ti , fs::path const& path , file_pool& fp , disk_io_thread& io @@ -199,7 +201,8 @@ namespace libtorrent void async_read( peer_request const& r - , boost::function const& handler); + , boost::function const& handler + , char* buffer = 0); void async_write( peer_request const& r @@ -227,7 +230,7 @@ namespace libtorrent { return m_compact_mode; } #ifndef NDEBUG - std::string name() const { return m_info.name(); } + std::string name() const { return m_info->name(); } #endif private: @@ -283,7 +286,7 @@ namespace libtorrent // a bitmask representing the pieces we have std::vector m_have_piece; - torrent_info const& m_info; + boost::intrusive_ptr m_info; // slots that haven't had any file storage allocated std::vector m_unallocated_slots; @@ -313,12 +316,6 @@ namespace libtorrent mutable boost::recursive_mutex m_mutex; - bool m_allocating; - boost::mutex m_allocating_monitor; - boost::condition m_allocating_condition; - - // these states are used while checking/allocating the torrent - enum { // the default initial state state_none, @@ -333,6 +330,11 @@ namespace libtorrent } m_state; int m_current_slot; + // this is saved in case we need to instantiate a new + // storage (osed when remapping files) + storage_constructor_type m_storage_constructor; + + // temporary buffer used while checking std::vector m_piece_data; // this maps a piece hash to piece index. It will be @@ -340,6 +342,8 @@ namespace libtorrent // isn't needed) std::multimap m_hash_to_piece; + // this map contains partial hashes for downloading + // pieces. std::map m_piece_hasher; disk_io_thread& m_io_thread; diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index 2227fc932..27d61af9d 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -55,6 +55,7 @@ namespace libtorrent || _POSIX_MONOTONIC_CLOCK < 0)) || defined (TORRENT_USE_BOOST_DATE_TIME) #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -85,6 +86,7 @@ namespace libtorrent #include #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -170,6 +172,7 @@ namespace asio #include #include +#include "libtorrent/assert.hpp" // high precision timer for darwin intel and ppc @@ -249,6 +252,7 @@ namespace libtorrent #define WIN32_LEAN_AND_MEAN #endif #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -335,6 +339,7 @@ namespace libtorrent #elif defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -385,4 +390,4 @@ namespace libtorrent #endif #endif - + diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 2eef2656b..bcc54899f 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -62,13 +62,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/tracker_manager.hpp" #include "libtorrent/stat.hpp" #include "libtorrent/alert.hpp" -#include "libtorrent/resource_request.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/config.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/bandwidth_manager.hpp" #include "libtorrent/storage.hpp" #include "libtorrent/hasher.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -98,13 +98,13 @@ namespace libtorrent torrent( aux::session_impl& ses , aux::checker_impl& checker - , torrent_info const& tf + , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused); // used with metadata-less torrents // (the metadata is downloaded from the peers) @@ -118,8 +118,8 @@ namespace libtorrent , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused); ~torrent(); @@ -154,10 +154,6 @@ namespace libtorrent bool verify_resume_data(entry& rd, std::string& error) { assert(m_storage); return m_storage->verify_resume_data(rd, error); } - // is called every second by session. This will - // caclulate the upload/download and number - // of connections this torrent needs. And prepare - // it for being used by allocate_resources. void second_tick(stat& accumulator, float tick_interval); // debug purpose only @@ -254,6 +250,15 @@ namespace libtorrent void remove_url_seed(std::string const& url) { m_web_seeds.erase(url); } + std::set url_seeds() const + { return m_web_seeds; } + + bool free_upload_slots() const + { return m_num_uploads < m_max_uploads; } + + void choke_peer(peer_connection& c); + bool unchoke_peer(peer_connection& c); + // used by peer_connection to attach itself to a torrent // since incoming connections don't know what torrent // they're a part of until they have received an info_hash. @@ -465,14 +470,14 @@ namespace libtorrent bool is_seed() const { return valid_metadata() - && m_num_pieces == m_torrent_file.num_pieces(); + && m_num_pieces == m_torrent_file->num_pieces(); } // this is true if we have all the pieces that we want bool is_finished() const { if (is_seed()) return true; - return valid_metadata() && m_torrent_file.num_pieces() + return valid_metadata() && m_torrent_file->num_pieces() - m_num_pieces - m_picker->num_filtered() == 0; } @@ -494,7 +499,7 @@ namespace libtorrent } piece_manager& filesystem(); torrent_info const& torrent_file() const - { return m_torrent_file; } + { return *m_torrent_file; } std::vector const& trackers() const { return m_trackers; } @@ -516,11 +521,6 @@ namespace libtorrent // -------------------------------------------- // RESOURCE MANAGEMENT - void distribute_resources(float tick_interval); - - resource_request m_uploads_quota; - resource_request m_connections_quota; - void set_peer_upload_limit(tcp::endpoint ip, int limit); void set_peer_download_limit(tcp::endpoint ip, int limit); @@ -530,7 +530,9 @@ namespace libtorrent int download_limit() const; void set_max_uploads(int limit); + int max_uploads() const { return m_max_uploads; } void set_max_connections(int limit); + int max_connections() const { return m_max_connections; } void move_storage(fs::path const& save_path); // unless this returns true, new connections must wait @@ -538,7 +540,7 @@ namespace libtorrent bool ready_for_connections() const { return m_connections_initialized; } bool valid_metadata() const - { return m_torrent_file.is_valid(); } + { return m_torrent_file->is_valid(); } // parses the info section from the given // bencoded tree and moves the torrent @@ -562,7 +564,7 @@ namespace libtorrent void update_peer_interest(); - torrent_info m_torrent_file; + boost::intrusive_ptr m_torrent_file; // is set to true when the torrent has // been aborted. @@ -705,9 +707,9 @@ namespace libtorrent // determine the timeout until next try. int m_failed_trackers; - // this is a counter that is increased every - // second, and when it reaches 10, the policy::pulse() - // is called and the time scaler is reset to 0. + // this is a counter that is decreased every + // second, and when it reaches 0, the policy::pulse() + // is called and the time scaler is reset to 10. int m_time_scaler; // the bitmask that says which pieces we have @@ -774,6 +776,15 @@ namespace libtorrent session_settings const& m_settings; storage_constructor_type m_storage_constructor; + + // the maximum number of uploads for this torrent + int m_max_uploads; + + // the number of unchoked peers in this torrent + int m_num_uploads; + + // the maximum number of connections for this torrent + int m_max_connections; #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list > extension_list_t; diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 3f7ae5bcc..31a39c38e 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_TORRENT_HANDLE_HPP_INCLUDED #include +#include #ifdef _MSC_VER #pragma warning(push, 1) @@ -273,7 +274,9 @@ namespace libtorrent std::vector const& trackers() const; void replace_trackers(std::vector const&) const; - void add_url_seed(std::string const& url); + void add_url_seed(std::string const& url) const; + void remove_url_seed(std::string const& url) const; + std::set url_seeds() const; bool has_metadata() const; const torrent_info& get_torrent_info() const; diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index a2d6c4ef9..492fda48d 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -57,6 +57,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_request.hpp" #include "libtorrent/config.hpp" #include "libtorrent/time.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -71,7 +73,7 @@ namespace libtorrent size_type offset; // the offset of this file inside the torrent size_type size; // the size of this file // if the path was incorrectly encoded, this is - // the origianal corrupt encoded string. It is + // the original corrupt encoded string. It is // preserved in order to be able to reproduce // the correct info-hash boost::shared_ptr orig_path; @@ -96,7 +98,7 @@ namespace libtorrent virtual const char* what() const throw() { return "invalid torrent file"; } }; - class TORRENT_EXPORT torrent_info + class TORRENT_EXPORT torrent_info : public intrusive_ptr_base { public: @@ -115,8 +117,12 @@ namespace libtorrent void add_file(fs::path file, size_type size); void add_url_seed(std::string const& url); - std::vector map_block(int piece, size_type offset, int size) const; - peer_request map_file(int file, size_type offset, int size) const; + bool remap_files(std::vector > const& map); + + std::vector map_block(int piece, size_type offset + , int size, bool storage = false) const; + peer_request map_file(int file, size_type offset, int size + , bool storage = false) const; std::vector const& url_seeds() const { @@ -128,15 +134,60 @@ namespace libtorrent typedef std::vector::const_reverse_iterator reverse_file_iterator; // list the files in the torrent file - file_iterator begin_files() const { return m_files.begin(); } - file_iterator end_files() const { return m_files.end(); } - reverse_file_iterator rbegin_files() const { return m_files.rbegin(); } - reverse_file_iterator rend_files() const { return m_files.rend(); } + file_iterator begin_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.begin(); + else + return m_remapped_files.begin(); + } - int num_files() const - { assert(m_piece_length > 0); return (int)m_files.size(); } - const file_entry& file_at(int index) const - { assert(index >= 0 && index < (int)m_files.size()); return m_files[index]; } + file_iterator end_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.end(); + else + return m_remapped_files.end(); + } + + reverse_file_iterator rbegin_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.rbegin(); + else + return m_remapped_files.rbegin(); + } + + reverse_file_iterator rend_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.rend(); + else + return m_remapped_files.rend(); + } + + int num_files(bool storage = false) const + { + assert(m_piece_length > 0); + if (!storage || m_remapped_files.empty()) + return (int)m_files.size(); + else + return (int)m_remapped_files.size(); + } + + const file_entry& file_at(int index, bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + { + assert(index >= 0 && index < (int)m_files.size()); + return m_files[index]; + } + else + { + assert(index >= 0 && index < (int)m_remapped_files.size()); + return m_remapped_files[index]; + } + } const std::vector& trackers() const { return m_urls; } @@ -218,6 +269,13 @@ namespace libtorrent // the list of files that this torrent consists of std::vector m_files; + // this vector is typically empty. If it is not + // empty, it means the user has re-mapped the + // files in this torrent to diffefrent names + // on disk. This is only used when reading and + // writing the disk. + std::vector m_remapped_files; + nodes_t m_nodes; // the sum of all filesizes @@ -264,8 +322,10 @@ namespace libtorrent entry m_extra_info; #ifndef NDEBUG + public: // this is set to true when seed_free() is called bool m_half_metadata; + private: #endif }; diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index d4b701aad..fc0650631 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_UPNP_HPP #include "libtorrent/socket.hpp" +#include "libtorrent/broadcast_socket.hpp" #include "libtorrent/http_connection.hpp" #include "libtorrent/connection_queue.hpp" @@ -56,9 +57,6 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -bool is_local(address const& a); -address_v4 guess_local_address(asio::io_service&); - // int: external tcp port // int: external udp port // std::string: error message @@ -72,8 +70,6 @@ public: , portmap_callback_t const& cb); ~upnp(); - void rebind(address const& listen_interface); - // maps the ports, if a port is set to 0 // it will not be mapped void set_mappings(int tcp, int udp); @@ -90,7 +86,7 @@ private: void update_mapping(int i, int port); void resend_request(asio::error_code const& e); - void on_reply(asio::error_code const& e + void on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); void discover_device(); @@ -106,12 +102,15 @@ private: , int mapping); void on_expire(asio::error_code const& e); - void post(rootdevice& d, std::stringstream const& s - , std::string const& soap_action); void map_port(rootdevice& d, int i); void unmap_port(rootdevice& d, int i); void disable(); + void delete_port_mapping(rootdevice& d, int i); + void create_port_mapping(http_connection& c, rootdevice& d, int i); + void post(upnp::rootdevice const& d, std::string const& soap + , std::string const& soap_action); + struct mapping_t { mapping_t() @@ -198,18 +197,13 @@ private: // current retry count int m_retry_count; - // used to receive responses in - char m_receive_buffer[1024]; + asio::io_service& m_io_service; - // the endpoint we received the message from - udp::endpoint m_remote; + asio::strand m_strand; - // the local address we're listening on - address_v4 m_local_ip; - // the udp socket used to send and receive // multicast messages on the network - datagram_socket m_socket; + broadcast_socket m_socket; // used to resend udp packets in case // they time out @@ -217,8 +211,6 @@ private: // timer used to refresh mappings deadline_timer m_refresh_timer; - - asio::strand m_strand; bool m_disabled; bool m_closing; diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index ba7450c0a..1290f14a1 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -65,7 +65,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -126,6 +125,8 @@ namespace libtorrent void write_piece(peer_request const& r, char const* buffer) { assert(false); } void write_keepalive() {} void on_connected(); + void write_reject_request(peer_request const&) {} + void write_allow_fast(int) {} #ifndef NDEBUG void check_invariant() const; diff --git a/libtorrent/src/Makefile.am b/libtorrent/src/Makefile.am index fabe68b65..671cb75e5 100644 --- a/libtorrent/src/Makefile.am +++ b/libtorrent/src/Makefile.am @@ -12,8 +12,8 @@ kademlia/rpc_manager.cpp \ kademlia/traversal_algorithm.cpp endif -libtorrent_la_SOURCES = allocate_resources.cpp \ -entry.cpp escape_string.cpp \ +libtorrent_la_SOURCES = entry.cpp escape_string.cpp \ +enum_net.cpp broadcast_socket.cpp \ peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \ stat.cpp storage.cpp torrent.cpp torrent_handle.cpp pe_crypto.cpp \ @@ -28,16 +28,16 @@ $(kademlia_sources) noinst_HEADERS = \ $(top_srcdir)/include/libtorrent/alert.hpp \ $(top_srcdir)/include/libtorrent/alert_types.hpp \ -$(top_srcdir)/include/libtorrent/allocate_resources.hpp \ -$(top_srcdir)/include/libtorrent/aux_/allocate_resources_impl.hpp \ $(top_srcdir)/include/libtorrent/aux_/session_impl.hpp \ $(top_srcdir)/include/libtorrent/bandwidth_manager.hpp \ $(top_srcdir)/include/libtorrent/bencode.hpp \ +$(top_srcdir)/include/libtorrent/broadcast_socket.hpp \ $(top_srcdir)/include/libtorrent/buffer.hpp \ $(top_srcdir)/include/libtorrent/connection_queue.hpp \ $(top_srcdir)/include/libtorrent/debug.hpp \ $(top_srcdir)/include/libtorrent/disk_io_thread.hpp \ $(top_srcdir)/include/libtorrent/entry.hpp \ +$(top_srcdir)/include/libtorrent/enum_net.hpp \ $(top_srcdir)/include/libtorrent/escape_string.hpp \ $(top_srcdir)/include/libtorrent/extensions.hpp \ $(top_srcdir)/include/libtorrent/extensions/metadata_transfer.hpp \ @@ -71,7 +71,6 @@ $(top_srcdir)/include/libtorrent/peer_request.hpp \ $(top_srcdir)/include/libtorrent/piece_block_progress.hpp \ $(top_srcdir)/include/libtorrent/piece_picker.hpp \ $(top_srcdir)/include/libtorrent/policy.hpp \ -$(top_srcdir)/include/libtorrent/resource_request.hpp \ $(top_srcdir)/include/libtorrent/session.hpp \ $(top_srcdir)/include/libtorrent/size_type.hpp \ $(top_srcdir)/include/libtorrent/socket.hpp \ diff --git a/libtorrent/src/assert.cpp b/libtorrent/src/assert.cpp new file mode 100644 index 000000000..da79a745b --- /dev/null +++ b/libtorrent/src/assert.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NDEBUG + +#include +#include +#include + +void assert_fail(char const* expr, int line, char const* file, char const* function) +{ + + fprintf(stderr, "assertion failed. Please file a bugreport at " + "http://code.rasterbar.com/libtorrent/newticket\n" + "Please include the following information:\n\n" + "file: '%s'\n" + "line: %d\n" + "function: %s\n" + "expression: %s\n" + "stack:\n", file, line, function, expr); + + void* stack[50]; + int size = backtrace(stack, 50); + char** symbols = backtrace_symbols(stack, size); + + for (int i = 0; i < size; ++i) + { + fprintf(stderr, "%d: %s\n", i, symbols[i]); + } + + free(symbols); + exit(1); +} + +#endif + diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp new file mode 100644 index 000000000..a937fc11b --- /dev/null +++ b/libtorrent/src/broadcast_socket.cpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + bool is_local(address const& a) + { + if (a.is_v6()) return false; + address_v4 a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 + || (ip & 0xfff00000) == 0xac100000 + || (ip & 0xffff0000) == 0xc0a80000); + } + + address_v4 guess_local_address(asio::io_service& ios) + { + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + // ignore the loopback + if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; + // ignore addresses that are not on a local network + if (!is_local(i->endpoint().address())) continue; + // ignore non-IPv4 addresses + if (i->endpoint().address().is_v4()) break; + } + if (i == udp::resolver_iterator()) return address_v4::any(); + return i->endpoint().address().to_v4(); + } + + broadcast_socket::broadcast_socket(asio::io_service& ios + , udp::endpoint const& multicast_endpoint + , receive_handler_t const& handler) + : m_multicast_endpoint(multicast_endpoint) + , m_on_receive(handler) + { + assert(m_multicast_endpoint.address().is_v4()); + assert(m_multicast_endpoint.address().to_v4().is_multicast()); + + using namespace asio::ip::multicast; + + asio::error_code ec; + std::vector
interfaces = enum_net_interfaces(ios, ec); + + for (std::vector
::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + // only broadcast to IPv4 addresses that are not local + if (!i->is_v4() || !is_local(*i)) continue; + // ignore the loopback interface + if (i->to_v4() == address_v4((127 << 24) + 1)) continue; + + boost::shared_ptr s(new datagram_socket(ios)); + s->open(udp::v4(), ec); + if (ec) continue; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) continue; + s->bind(udp::endpoint(*i, 0), ec); + if (ec) continue; + s->set_option(join_group(multicast_endpoint.address()), ec); + if (ec) continue; + s->set_option(outbound_interface(i->to_v4()), ec); + if (ec) continue; + s->set_option(hops(255)); + m_sockets.push_back(socket_entry(s)); + socket_entry& se = m_sockets.back(); + s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + } + } + + void broadcast_socket::send(char const* buffer, int size, asio::error_code& ec) + { + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + asio::error_code e; + i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); +// std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; + if (e) ec = e; + } + } + + void broadcast_socket::on_receive(socket_entry* s, asio::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec || bytes_transferred == 0) return; + m_on_receive(s->remote, s->buffer, bytes_transferred); + s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer)) + , s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2)); + } + + void broadcast_socket::close() + { + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + i->socket->close(); + } + } +} + + diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 5b63f26cd..11a39675f 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -75,7 +75,14 @@ namespace libtorrent &bt_peer_connection::on_piece, &bt_peer_connection::on_cancel, &bt_peer_connection::on_dht_port, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + // FAST extension messages + &bt_peer_connection::on_suggest_piece, + &bt_peer_connection::on_have_all, + &bt_peer_connection::on_have_none, + &bt_peer_connection::on_reject_request, + &bt_peer_connection::on_allowed_fast, + 0, 0, &bt_peer_connection::on_extended }; @@ -93,6 +100,7 @@ namespace libtorrent , m_supports_extensions(false) #endif , m_supports_dht_port(false) + , m_supports_fast(false) #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) @@ -124,6 +132,7 @@ namespace libtorrent , m_supports_extensions(false) #endif , m_supports_dht_port(false) + , m_supports_fast(false) #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) @@ -226,6 +235,10 @@ namespace libtorrent boost::shared_ptr t = associated_torrent().lock(); assert(t); write_bitfield(t->pieces()); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.get_dht_settings().service_port); +#endif } void bt_peer_connection::write_dht_port(int listen_port) @@ -246,6 +259,75 @@ namespace libtorrent setup_send(); } + void bt_peer_connection::write_have_all() + { + INVARIANT_CHECK; + assert(m_sent_handshake && !m_sent_bitfield); +#ifndef NDEBUG + m_sent_bitfield = true; +#endif +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> HAVE_ALL\n"; +#endif + char buf[] = {0,0,0,1, msg_have_all}; + send_buffer(buf, buf + sizeof(buf)); + } + + void bt_peer_connection::write_have_none() + { + INVARIANT_CHECK; + assert(m_sent_handshake && !m_sent_bitfield); +#ifndef NDEBUG + m_sent_bitfield = true; +#endif +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> HAVE_NONE\n"; +#endif + char buf[] = {0,0,0,1, msg_have_none}; + send_buffer(buf, buf + sizeof(buf)); + } + + void bt_peer_connection::write_reject_request(peer_request const& r) + { + INVARIANT_CHECK; + + assert(m_sent_handshake && m_sent_bitfield); + assert(associated_torrent().lock()->valid_metadata()); + + char buf[] = {0,0,0,13, msg_reject_request}; + + buffer::interval i = allocate_send_buffer(17); + + std::copy(buf, buf + 5, i.begin); + i.begin += 5; + + // index + detail::write_int32(r.piece, i.begin); + // begin + detail::write_int32(r.start, i.begin); + // length + detail::write_int32(r.length, i.begin); + assert(i.begin == i.end); + + setup_send(); + } + + void bt_peer_connection::write_allow_fast(int piece) + { + INVARIANT_CHECK; + + assert(m_sent_handshake && m_sent_bitfield); + assert(associated_torrent().lock()->valid_metadata()); + + char buf[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; + + char* ptr = buf + 5; + detail::write_int32(piece, ptr); + send_buffer(buf, buf + sizeof(buf)); + } + void bt_peer_connection::get_specific_peer_info(peer_info& p) const { assert(!associated_torrent().expired()); @@ -628,14 +710,17 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT // indicate that we support the DHT messages - *(i.begin + 7) = 0x01; + *(i.begin + 7) |= 0x01; #endif #ifndef TORRENT_DISABLE_EXTENSIONS // we support extensions - *(i.begin + 5) = 0x10; + *(i.begin + 5) |= 0x10; #endif + // we support FAST extension + *(i.begin + 7) |= 0x04; + i.begin += 8; // info hash @@ -721,6 +806,20 @@ namespace libtorrent if (!packet_finished()) return; incoming_choke(); + if (!m_supports_fast) + { + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + while (!request_queue().empty()) + { + piece_block const& b = request_queue().front(); + peer_request r; + r.piece = b.piece_index; + r.start = b.block_index * t->block_size(); + r.length = t->block_size(); + incoming_reject_request(r); + } + } } // ----------------------------- @@ -939,6 +1038,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (!m_supports_dht_port) + throw protocol_error("got 'dht_port' message from peer that doesn't support it"); + assert(received > 0); if (packet_size() != 3) throw protocol_error("'dht_port' message size != 3"); @@ -953,6 +1055,80 @@ namespace libtorrent incoming_dht_port(listen_port); } + void bt_peer_connection::on_suggest_piece(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'suggest_piece' without FAST extension support"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + const char* ptr = recv_buffer.begin + 1; + int piece = detail::read_uint32(ptr); + incoming_suggest(piece); + } + + void bt_peer_connection::on_have_all(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'have_all' without FAST extension support"); + m_statistics.received_bytes(0, received); + incoming_have_all(); + } + + void bt_peer_connection::on_have_none(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'have_none' without FAST extension support"); + m_statistics.received_bytes(0, received); + incoming_have_none(); + } + + void bt_peer_connection::on_reject_request(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'reject_request' without FAST extension support"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_reject_request(r); + } + + void bt_peer_connection::on_allowed_fast(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'allowed_fast' without FAST extension support"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + buffer::const_interval recv_buffer = receive_buffer(); + const char* ptr = recv_buffer.begin + 1; + int index = detail::read_int32(ptr); + + incoming_allowed_fast(index); + } + // ----------------------------- // --------- EXTENDED ---------- // ----------------------------- @@ -1175,6 +1351,22 @@ namespace libtorrent assert(m_sent_handshake && !m_sent_bitfield); assert(t->valid_metadata()); + // in this case, have_all or have_none should be sent instead + assert(!m_supports_fast || !t->is_seed() || t->num_pieces() != 0); + + if (m_supports_fast && t->is_seed()) + { + write_have_all(); + send_allowed_set(); + return; + } + else if (m_supports_fast && t->num_pieces() == 0) + { + write_have_none(); + send_allowed_set(); + return; + } + int num_pieces = bitfield.size(); int lazy_pieces[50]; int num_lazy_pieces = 0; @@ -1183,7 +1375,7 @@ namespace libtorrent assert(t->is_seed() == (std::count(bitfield.begin(), bitfield.end(), true) == num_pieces)); if (t->is_seed() && m_ses.settings().lazy_bitfields) { - num_lazy_pieces = std::min(50, num_pieces / 10); + num_lazy_pieces = (std::min)(50, num_pieces / 10); if (num_lazy_pieces < 1) num_lazy_pieces = 1; for (int i = 0; i < num_pieces; ++i) { @@ -1251,6 +1443,9 @@ namespace libtorrent #endif } } + + if (m_supports_fast) + send_allowed_set(); } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1546,7 +1741,7 @@ namespace libtorrent if (m_sync_bytes_read >= 512) throw protocol_error("sync hash not found within 532 bytes"); - cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+20) - m_sync_bytes_read)); + cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+20) - m_sync_bytes_read)); assert(!packet_finished()); return; @@ -1684,7 +1879,7 @@ namespace libtorrent if (m_sync_bytes_read >= 512) throw protocol_error("sync verification constant not found within 520 bytes"); - cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+8) - m_sync_bytes_read)); + cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+8) - m_sync_bytes_read)); assert(!packet_finished()); return; @@ -2016,6 +2211,9 @@ namespace libtorrent if (recv_buffer[7] & 0x01) m_supports_dht_port = true; + if (recv_buffer[7] & 0x04) + m_supports_fast = true; + // ok, now we have got enough of the handshake. Is this connection // attached to a torrent? if (!t) @@ -2049,10 +2247,10 @@ namespace libtorrent assert(t); // if this is a local connection, we have already - // send the handshake + // sent the handshake if (!is_local()) write_handshake(); - if (t->valid_metadata()) - write_bitfield(t->pieces()); +// if (t->valid_metadata()) +// write_bitfield(t->pieces()); assert(t->get_policy().has_connection(this)); @@ -2125,11 +2323,6 @@ namespace libtorrent throw protocol_error("closing connection to ourself"); } -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.get_dht_settings().service_port); -#endif - m_client_version = identify_client(pid); boost::optional f = client_fingerprint(pid); if (f && std::equal(f->name, f->name + 2, "BC")) @@ -2181,6 +2374,14 @@ namespace libtorrent m_state = read_packet_size; reset_recv_buffer(4); + if (t->valid_metadata()) + { + write_bitfield(t->pieces()); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.get_dht_settings().service_port); +#endif + } assert(!packet_finished()); return; diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index 859205ed0..0b3f5ff54 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -54,6 +54,8 @@ namespace libtorrent , boost::function const& on_timeout , time_duration timeout) { + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; m_queue.push_back(entry()); @@ -68,6 +70,8 @@ namespace libtorrent void connection_queue::done(int ticket) { + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; std::list::iterator i = std::find_if(m_queue.begin() @@ -148,6 +152,8 @@ namespace libtorrent void connection_queue::on_timeout(asio::error_code const& e) { + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; #ifndef NDEBUG function_guard guard_(m_in_timeout_function); @@ -158,21 +164,35 @@ namespace libtorrent ptime next_expire = max_time(); ptime now = time_now(); + std::list timed_out; for (std::list::iterator i = m_queue.begin(); !m_queue.empty() && i != m_queue.end();) { if (i->connecting && i->expires < now) { - boost::function on_timeout = i->on_timeout; - m_queue.erase(i++); + std::list::iterator j = i; + ++i; + timed_out.splice(timed_out.end(), m_queue, j, i); --m_num_connecting; - try { on_timeout(); } catch (std::exception&) {} continue; } if (i->expires < next_expire) next_expire = i->expires; ++i; } + + // we don't want to call the timeout callback while we're locked + // since that is a recepie for dead-locks + l.unlock(); + + for (std::list::iterator i = timed_out.begin() + , end(timed_out.end()); i != end; ++i) + { + try { i->on_timeout(); } catch (std::exception&) {} + } + + l.lock(); + if (next_expire < max_time()) { m_timer.expires_at(next_expire); diff --git a/libtorrent/src/deluge_core.cpp b/libtorrent/src/deluge_core.cpp deleted file mode 100644 index 930c7d496..000000000 --- a/libtorrent/src/deluge_core.cpp +++ /dev/null @@ -1,1439 +0,0 @@ -/* - * Copyright 2006 Alon Zakai ('Kripken') - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * In addition, as a special exception, the copyright holders give - * permission to link the code of portions of this program with the OpenSSL - * library. - * You must obey the GNU General Public License in all respects for all of - * the code used other than OpenSSL. If you modify file(s) with this - * exception, you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete - * this exception statement from your version. If you delete this exception - * statement from all source files in the program, then also delete it here. - * - * Thank You: Some code portions were derived from BSD-licensed work by - * Arvid Norberg, and GPL-licensed work by Christophe Dumez - */ - -//------------------ -// TODO: -// -// The DHT capability requires UDP. We need to check that this port is in fact -// open, just like the normal TCP port for bittorrent. -// -//----------------- -#include - -#include -#include -#include - -#include "libtorrent/entry.hpp" -#include "libtorrent/bencode.hpp" -#include "libtorrent/session.hpp" -#include "libtorrent/identify_client.hpp" -#include "libtorrent/alert_types.hpp" -#include "libtorrent/storage.hpp" -#include "libtorrent/hasher.hpp" -#include "libtorrent/ip_filter.hpp" -#include "libtorrent/upnp.hpp" -#include "libtorrent/file_pool.hpp" -#include "libtorrent/natpmp.hpp" -#include "libtorrent/extensions/metadata_transfer.hpp" -#include "libtorrent/extensions/ut_pex.hpp" -using namespace boost::filesystem; -using namespace libtorrent; - -//---------------- -// CONSTANTS -//---------------- - -#ifdef AMD64 -#define python_long int -#else -#define python_long long -#endif - -#define EVENT_NULL 0 -#define EVENT_FINISHED 1 -#define EVENT_PEER_ERROR 2 -#define EVENT_INVALID_REQUEST 3 -#define EVENT_FILE_ERROR 4 -#define EVENT_HASH_FAILED_ERROR 5 -#define EVENT_PEER_BAN_ERROR 6 -#define EVENT_FASTRESUME_REJECTED_ERROR 8 -#define EVENT_TRACKER 9 -#define EVENT_OTHER 10 - -#define STATE_QUEUED 0 -#define STATE_CHECKING 1 -#define STATE_CONNECTING 2 -#define STATE_DOWNLOADING_META 3 -#define STATE_DOWNLOADING 4 -#define STATE_FINISHED 5 -#define STATE_SEEDING 6 -#define STATE_ALLOCATING 7 - -#define DHT_ROUTER_PORT 6881 - -//----------------- -// TYPES -//----------------- - -typedef long unique_ID_t; -typedef std::vector filter_out_t; -typedef std::string torrent_name_t; - -struct torrent_t -{ - torrent_handle handle; - unique_ID_t unique_ID; -}; - -typedef std::vector torrents_t; -typedef torrents_t::iterator torrents_t_iterator; - -//--------------------------- -// MODULE-GLOBAL VARIABLES -//--------------------------- - -long M_unique_counter = 0; -session_settings *M_settings = NULL; -pe_settings *M_pe_settings = NULL; -proxy_settings *M_proxy_settings = NULL; -session *M_ses = NULL; -PyObject *M_constants = NULL; -ip_filter *M_the_filter = NULL; -torrents_t *M_torrents = NULL; - -//------------------------ -// Exception types & macro -//------------------------ - -static PyObject *DelugeError = NULL; -static PyObject *InvalidEncodingError = NULL; -static PyObject *FilesystemError = NULL; -static PyObject *DuplicateTorrentError = NULL; -static PyObject *InvalidTorrentError = NULL; - -#define RAISE_PTR(e,s) { printf("Raising error: %s\r\n", s); PyErr_SetString(e, s); return NULL; } -#define RAISE_INT(e,s) { printf("Raising error: %s\r\n", s); PyErr_SetString(e, s); return -1; } - -//--------------------- -// Internal functions -//--------------------- - -bool empty_name_check(const std::string & name) -{ - return 1; -} - - -long handle_exists(torrent_handle &handle) -{ - for (unsigned long i = 0; i < M_torrents->size(); i++) - if ((*M_torrents)[i].handle == handle) - return 1; - - return 0; -} - - -long get_torrent_index(torrent_handle &handle) -{ - for (unsigned long i = 0; i < M_torrents->size(); i++) - if ((*M_torrents)[i].handle == handle) - { - // printf("Found: %li\r\n", i); - return i; - } - - RAISE_INT(DelugeError, "Handle not found."); -} - - -long get_index_from_unique_ID(long unique_ID) -{ - assert(M_handles->size() == M_unique_IDs->size()); - - for (unsigned long i = 0; i < M_torrents->size(); i++) - if ((*M_torrents)[i].unique_ID == unique_ID) - return i; - - RAISE_INT(DelugeError, "No such unique_ID."); -} - - -long internal_add_torrent(std::string const& torrent_name, -float preferred_ratio, -bool compact_mode, -boost::filesystem::path const& save_path) -{ - - std::ifstream in(torrent_name.c_str(), std::ios_base::binary); - in.unsetf(std::ios_base::skipws); - entry e; - e = bdecode(std::istream_iterator(in), std::istream_iterator()); - - torrent_info t(e); - - entry resume_data; - try - { - std::stringstream s; - s << torrent_name << ".fastresume"; - boost::filesystem::ifstream resumeFile(s.str(), std::ios_base::binary); - resumeFile.unsetf(std::ios_base::skipws); - resume_data = bdecode(std::istream_iterator(resumeFile), - std::istream_iterator()); - } - catch (invalid_encoding&) - { - } - catch (boost::filesystem::filesystem_error&) {} - - // Create new torrent object - - torrent_t new_torrent; - - torrent_handle h = M_ses->add_torrent(t, save_path, resume_data, compact_mode, 16 * 1024); - // h.set_max_connections(60); // at some point we should use this - h.set_max_uploads(-1); - h.set_ratio(preferred_ratio); - new_torrent.handle = h; - - new_torrent.unique_ID = M_unique_counter; - M_unique_counter++; - - M_torrents->push_back(new_torrent); - - return (new_torrent.unique_ID); -} - - -void internal_remove_torrent(long index) -{ - assert(index < M_torrents->size()); - - torrent_handle& h = M_torrents->at(index).handle; - - M_ses->remove_torrent(h); - - torrents_t_iterator it = M_torrents->begin() + index; - M_torrents->erase(it); -} - - -long get_peer_index(tcp::endpoint addr, std::vector const& peers) -{ - long index = -1; - - for (unsigned long i = 0; i < peers.size(); i++) - if (peers[i].ip == addr) - index = i; - - return index; -} - - -// The following function contains code by Christophe Dumez and Arvid Norberg -void internal_add_files(torrent_info& t, -boost::filesystem::path const& p, -boost::filesystem::path const& l) -{ - // change default checker, perhaps? - boost::filesystem::path f(p / l); - if (is_directory(f)) - { - for (boost::filesystem::directory_iterator i(f), end; i != end; ++i) - internal_add_files(t, p, l / i->leaf()); - } else - t.add_file(l, file_size(f)); -} - - -long count_DHT_peers(entry &state) -{ - long num_peers = 0; - entry *nodes = state.find_key("nodes"); - if (nodes) - { - entry::list_type &peers = nodes->list(); - entry::list_type::const_iterator i; - i = peers.begin(); - - while (i != peers.end()) - { - num_peers++; - i++; - } - } - - return num_peers; -} - - -//===================== -// External functions -//===================== - -static PyObject *torrent_pre_init(PyObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "OOOOO", &DelugeError, - &InvalidEncodingError, - &FilesystemError, - &DuplicateTorrentError, - &InvalidTorrentError)) - return NULL; - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_init(PyObject *self, PyObject *args) -{ - printf("deluge_core; using libtorrent %s. Compiled with NDEBUG value: %d\r\n", - LIBTORRENT_VERSION, - NDEBUG); - - // Tell Boost that we are on *NIX, so bloody '.'s are ok inside a directory name! - boost::filesystem::path::default_name_check(empty_name_check); - - char *client_ID, *user_agent; - python_long v1,v2,v3,v4; - - if (!PyArg_ParseTuple(args, "siiiis", &client_ID, &v1, &v2, &v3, &v4, &user_agent)) - return NULL; - - M_settings = new session_settings; - M_ses = new session(fingerprint(client_ID, v1, v2, v3, v4)); - - M_torrents = new torrents_t; - M_torrents->reserve(10); // pretty cheap, just 10 - - // Init values - - M_settings->user_agent = std::string(user_agent); - - M_ses->set_max_half_open_connections(-1); - M_ses->set_download_rate_limit(-1); - M_ses->set_upload_rate_limit(-1); - - M_ses->set_settings(*M_settings); - M_ses->set_severity_level(alert::debug); - - M_ses->add_extension(&libtorrent::create_metadata_plugin); - - M_constants = Py_BuildValue("{s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i}", - "EVENT_NULL", EVENT_NULL, - "EVENT_FINISHED", EVENT_FINISHED, - "EVENT_PEER_ERROR", EVENT_PEER_ERROR, - "EVENT_INVALID_REQUEST", EVENT_INVALID_REQUEST, - "EVENT_FILE_ERROR", EVENT_FILE_ERROR, - "EVENT_HASH_FAILED_ERROR", EVENT_HASH_FAILED_ERROR, - "EVENT_PEER_BAN_ERROR", EVENT_PEER_BAN_ERROR, - "EVENT_FASTRESUME_REJECTED_ERROR", EVENT_FASTRESUME_REJECTED_ERROR, - "EVENT_TRACKER", EVENT_TRACKER, - "EVENT_OTHER", EVENT_OTHER, - "STATE_QUEUED", STATE_QUEUED, - "STATE_CHECKING", STATE_CHECKING, - "STATE_CONNECTING", STATE_CONNECTING, - "STATE_DOWNLOADING_META", STATE_DOWNLOADING_META, - "STATE_DOWNLOADING", STATE_DOWNLOADING, - "STATE_FINISHED", STATE_FINISHED, - "STATE_SEEDING", STATE_SEEDING, - "STATE_ALLOCATING", STATE_ALLOCATING); - - Py_INCREF(Py_None); return Py_None; -}; - -static PyObject *torrent_quit(PyObject *self, PyObject *args) -{ - M_settings->stop_tracker_timeout = 5; - M_ses->set_settings(*M_settings); - printf("core: removing torrents...\r\n"); - delete M_torrents; - printf("core: removing settings...\r\n"); - delete M_settings; - printf("core: shutting down session...\r\n"); - delete M_ses; // 100% CPU... - Py_DECREF(M_constants); - - printf("core shut down.\r\n"); - - Py_INCREF(Py_None); return Py_None; -}; - -static PyObject *torrent_save_fastresume(PyObject *self, PyObject *args) -{ - python_long unique_ID; - const char *torrent_name; - if (!PyArg_ParseTuple(args, "is", &unique_ID, &torrent_name)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - torrent_handle& h = M_torrents->at(index).handle; - // For valid torrents, save fastresume data - if (h.is_valid() && h.has_metadata()) - { - h.pause(); - - entry data = h.write_resume_data(); - - std::stringstream s; - s << torrent_name << ".fastresume"; - - boost::filesystem::ofstream out(s.str(), std::ios_base::binary); - - out.unsetf(std::ios_base::skipws); - - bencode(std::ostream_iterator(out), data); - - h.resume(); - - Py_INCREF(Py_None); return Py_None; - } else - RAISE_PTR(DelugeError, "Invalid handle or no metadata for fastresume."); -} - - -static PyObject *torrent_set_max_half_open(PyObject *self, PyObject *args) -{ - python_long arg; - if (!PyArg_ParseTuple(args, "i", &arg)) - return NULL; - - M_ses->set_max_half_open_connections(arg); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_set_download_rate_limit(PyObject *self, PyObject *args) -{ - python_long arg; - if (!PyArg_ParseTuple(args, "i", &arg)) - return NULL; - - // printf("Capping download to %d bytes per second\r\n", (int)arg); - M_ses->set_download_rate_limit(arg); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_set_upload_rate_limit(PyObject *self, PyObject *args) -{ - python_long arg; - if (!PyArg_ParseTuple(args, "i", &arg)) - return NULL; - - // printf("Capping upload to %d bytes per second\r\n", (int)arg); - M_ses->set_upload_rate_limit(arg); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_set_listen_on(PyObject *self, PyObject *args) -{ - PyObject *port_vec; - if (!PyArg_ParseTuple(args, "O", &port_vec)) - return NULL; - - M_ses->listen_on(std::make_pair( PyInt_AsLong(PyList_GetItem(port_vec, 0)), - PyInt_AsLong(PyList_GetItem(port_vec, 1))), ""); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_is_listening(PyObject *self, PyObject *args) -{ - long ret = (M_ses->is_listening() != 0); - - return Py_BuildValue("i", ret); -} - - -static PyObject *torrent_listening_port(PyObject *self, PyObject *args) -{ - return Py_BuildValue("i", (python_long)M_ses->listen_port()); -} - - -static PyObject *torrent_set_max_uploads(PyObject *self, PyObject *args) -{ - python_long max_up; - if (!PyArg_ParseTuple(args, "i", &max_up)) - return NULL; - - M_ses->set_max_uploads(max_up); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_set_max_connections(PyObject *self, PyObject *args) -{ - python_long max_conn; - if (!PyArg_ParseTuple(args, "i", &max_conn)) - return NULL; - - // printf("Setting max connections: %d\r\n", max_conn); - M_ses->set_max_connections(max_conn); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_add_torrent(PyObject *self, PyObject *args) -{ - const char *name, *save_dir; - python_long compact; - if (!PyArg_ParseTuple(args, "ssi", &name, &save_dir, &compact)) - return NULL; - - boost::filesystem::path save_dir_2 (save_dir, empty_name_check); - try - { - long ret = internal_add_torrent(name, 0, compact, save_dir_2); - if (PyErr_Occurred()) - return NULL; - else - return Py_BuildValue("i", ret); - } - catch (invalid_encoding&) - { RAISE_PTR(InvalidEncodingError, ""); } - catch (invalid_torrent_file&) - { RAISE_PTR(InvalidTorrentError, ""); } - catch (boost::filesystem::filesystem_error&) - { RAISE_PTR(FilesystemError, ""); } - catch (duplicate_torrent&) - { RAISE_PTR(DuplicateTorrentError, "libtorrent reports this is a duplicate torrent"); } -} - - -static PyObject *torrent_remove_torrent(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - internal_remove_torrent(index); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_get_num_torrents(PyObject *self, PyObject *args) -{ - return Py_BuildValue("i", M_torrents->size()); -} - - -static PyObject *torrent_reannounce(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - M_torrents->at(index).handle.force_reannounce(); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_pause(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - M_torrents->at(index).handle.pause(); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_resume(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - M_torrents->at(index).handle.resume(); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_get_torrent_state(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - torrent_t &t = M_torrents->at(index); - torrent_status s = t.handle.status(); - const torrent_info &i = t.handle.get_torrent_info(); - - std::vector peers; - t.handle.get_peer_info(peers); - - long connected_seeds = 0; - long connected_peers = 0; - long total_seeds = 0; - long total_peers = 0; - - for (unsigned long i = 0; i < peers.size(); i++) { - - connected_peers = s.num_peers - s.num_seeds; - - connected_seeds = s.num_seeds; - - total_seeds = s.num_complete != -1? s.num_complete : connected_seeds; - - total_peers = s.num_incomplete != -1? s.num_incomplete : connected_peers; - } - - return Py_BuildValue("{s:s,s:i,s:i,s:l,s:l,s:f,s:f,s:f,s:L,s:L,s:b,s:s,s:s,s:f,s:L,s:L,s:l,s:i,s:i,s:L,s:L,s:i,s:l,s:l,s:b,s:b,s:L,s:L,s:L}", - "name", t.handle.get_torrent_info().name().c_str(), - "num_files", t.handle.get_torrent_info().num_files(), - "state", s.state, - "num_peers", connected_peers, - "num_seeds", connected_seeds, - "distributed_copies", s.distributed_copies, - "download_rate", s.download_rate, - "upload_rate", s.upload_rate, - "total_download", s.total_download, - "total_upload", s.total_upload, - "tracker_ok", !s.current_tracker.empty(), - "next_announce", boost::posix_time::to_simple_string(s.next_announce).c_str(), - "tracker", s.current_tracker.c_str(), - "progress", s.progress, - "total_payload_download", s.total_payload_download, - "total_payload_upload", s.total_payload_upload, - "pieces", long(s.pieces), // this is really a std::vector* - "pieces_done", s.num_pieces, - "block_size", s.block_size, - "total_size", i.total_size(), - "piece_length", i.piece_length(), - "num_pieces", i.num_pieces(), - "total_peers", total_peers, - "total_seeds", total_seeds, - "is_paused", t.handle.is_paused(), - "is_seed", t.handle.is_seed(), - "total_done", s.total_done, - "total_wanted", s.total_wanted, - "total_wanted_done", s.total_wanted_done); -}; - -static PyObject *torrent_pop_event(PyObject *self, PyObject *args) -{ - std::auto_ptr a; - - a = M_ses->pop_alert(); - - alert *popped_alert = a.get(); - - if (!popped_alert) - { - Py_INCREF(Py_None); return Py_None; - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i}", "event_type", EVENT_FINISHED, - "unique_ID", - M_torrents->at(index).unique_ID); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - peer_id peer_ID = (dynamic_cast(popped_alert))->pid; - std::string peer_IP = - (dynamic_cast(popped_alert))->ip.address().to_string(); - - return Py_BuildValue("{s:i,s:s,s:s,s:s}", "event_type", EVENT_PEER_ERROR, - "client_ID", identify_client(peer_ID).c_str(), - "ip", peer_IP.c_str(), - "message", a->msg().c_str()); - } else if (dynamic_cast(popped_alert)) - { - peer_id peer_ID = (dynamic_cast(popped_alert))->pid; - - return Py_BuildValue("{s:i,s:s,s:s}", - "event_type", EVENT_INVALID_REQUEST, - "client_ID", identify_client(peer_ID).c_str(), - "message", a->msg().c_str()); - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s}", - "event_type", EVENT_FILE_ERROR, - "unique_ID", M_torrents->at(index).unique_ID, - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:i,s:s}", - "event_type", EVENT_HASH_FAILED_ERROR, - "unique_ID", M_torrents->at(index).unique_ID, - "piece_index", - long((dynamic_cast(popped_alert))->piece_index), - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - std::string peer_IP = (dynamic_cast(popped_alert))->ip.address().to_string(); - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s,s:s}", - "event_type", EVENT_PEER_BAN_ERROR, - "unique_ID", M_torrents->at(index).unique_ID, - "ip", peer_IP.c_str(), - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s}", - "event_type", EVENT_FASTRESUME_REJECTED_ERROR, - "unique_ID", M_torrents->at(index).unique_ID, - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s,s:s}", - "event_type", EVENT_TRACKER, - "unique_ID", - M_torrents->at(index).unique_ID, - "tracker_status", "Announce sent", - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s,s:s}", - "event_type", EVENT_TRACKER, - "unique_ID", - M_torrents->at(index).unique_ID, - "tracker_status", "Bad response (status code=?)", - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s,s:s}", - "event_type", EVENT_TRACKER, - "unique_ID", - M_torrents->at(index).unique_ID, - "tracker_status", "Announce succeeded", - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } else if (dynamic_cast(popped_alert)) - { - torrent_handle handle = (dynamic_cast(popped_alert))->handle; - long index = get_torrent_index(handle); - if (PyErr_Occurred()) - return NULL; - - if (handle_exists(handle)) - return Py_BuildValue("{s:i,s:i,s:s,s:s}", - "event_type", EVENT_TRACKER, - "unique_ID", - M_torrents->at(index).unique_ID, - "tracker_status", "Warning in response", - "message", a->msg().c_str()); - else - { Py_INCREF(Py_None); return Py_None; } - } - - return Py_BuildValue("{s:i,s:s}", "event_type", EVENT_OTHER, - "message", a->msg().c_str() ); -} - - -static PyObject *torrent_get_session_info(PyObject *self, PyObject *args) -{ - session_status s = M_ses->status(); - - return Py_BuildValue("{s:l,s:f,s:f,s:f,s:f,s:l}", - "has_incoming_connections", long(s.has_incoming_connections), - "upload_rate", float(s.upload_rate), - "download_rate", float(s.download_rate), - "payload_upload_rate", float(s.payload_upload_rate), - "payload_download_rate", float(s.payload_download_rate), - "num_peers", long(s.num_peers)); -} - - -static PyObject *torrent_get_peer_info(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - std::vector peers; - M_torrents->at(index).handle.get_peer_info(peers); - - PyObject *peer_info; - PyObject *ret = PyTuple_New(peers.size()); - PyObject *curr_piece, *py_pieces; - - for (unsigned long i = 0; i < peers.size(); i++) - { - std::vector &pieces = peers[i].pieces; - unsigned long pieces_had = 0; - - py_pieces = PyTuple_New(pieces.size()); - - for (unsigned long piece = 0; piece < pieces.size(); piece++) - { - if (pieces[piece]) - pieces_had++; - - curr_piece = Py_BuildValue("i", long(pieces[piece])); - PyTuple_SetItem(py_pieces, piece, curr_piece); - } - - peer_info = Py_BuildValue( - "{s:f,s:L,s:f,s:L,s:i,s:i,s:b,s:b,s:b,s:b,s:b,s:b,s:b,s:b,s:b,s:s,s:b,s:s,s:f,s:O,s:b,s:b}", - "download_speed", peers[i].down_speed, - "total_download", peers[i].total_download, - "upload_speed", peers[i].up_speed, - "total_upload", peers[i].total_upload, - "download_queue_length", peers[i].download_queue_length, - "upload_queue_length", peers[i].upload_queue_length, - "is_interesting", ((peers[i].flags & peer_info::interesting) != 0), - "is_choked", ((peers[i].flags & peer_info::choked) != 0), - "is_remote_interested", ((peers[i].flags & peer_info::remote_interested) != 0), - "is_remote_choked", ((peers[i].flags & peer_info::remote_choked) != 0), - "supports_extensions", ((peers[i].flags & peer_info::supports_extensions) != 0), - "is_local_connection", ((peers[i].flags & peer_info::local_connection) != 0), - "is_awaiting_handshake", ((peers[i].flags & peer_info::handshake) != 0), - "is_connecting", ((peers[i].flags & peer_info::connecting) != 0), - "is_queued", ((peers[i].flags & peer_info::queued) != 0), - "client", peers[i].client.c_str(), - "is_seed", ((peers[i].flags & peer_info::seed) != 0), - "ip", peers[i].ip.address().to_string().c_str(), - "peer_has", float(float(pieces_had)*100.0/pieces.size()), - "pieces", py_pieces, - "rc4_encrypted", ((peers[i].flags & peer_info::rc4_encrypted) != 0), - "plaintext_encrypted", ((peers[i].flags & peer_info::plaintext_encrypted) != 0) - ); - - Py_DECREF(py_pieces); // Assuming the previous line does NOT steal the ref, then this is - // needed! - - PyTuple_SetItem(ret, i, peer_info); - }; - - return ret; -}; - -static PyObject *torrent_get_file_info(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - std::vector temp_files; - - PyObject *file_info; - - std::vector progresses; - - torrent_t &t = M_torrents->at(index); - t.handle.file_progress(progresses); - - torrent_info::file_iterator start = - t.handle.get_torrent_info().begin_files(); - torrent_info::file_iterator end = - t.handle.get_torrent_info().end_files(); - - long fileIndex = 0; - - for(torrent_info::file_iterator i = start; i != end; ++i) - { - file_entry const &currFile = (*i); - - file_info = Py_BuildValue( - "{s:s,s:d,s:d,s:f}", - "path", currFile.path.string().c_str(), - "offset", double(currFile.offset), - "size", double(currFile.size), - "progress", progresses[i - start]*100.0 - ); - - fileIndex++; - - temp_files.push_back(file_info); - }; - - PyObject *ret = PyTuple_New(temp_files.size()); - - for (unsigned long i = 0; i < temp_files.size(); i++) - PyTuple_SetItem(ret, i, temp_files[i]); - - return ret; -}; - -static PyObject *torrent_set_filter_out(PyObject *self, PyObject *args) -{ - python_long unique_ID; - PyObject *filter_out_object; - if (!PyArg_ParseTuple(args, "iO", &unique_ID, &filter_out_object)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - torrent_t &t = M_torrents->at(index); - long num_files = t.handle.get_torrent_info().num_files(); - assert(PyList_Size(filter_out_object) == num_files); - - filter_out_t filter_out(num_files); - - for (long i = 0; i < num_files; i++) - { - filter_out.at(i) = - PyInt_AsLong(PyList_GetItem(filter_out_object, i)); - }; - - t.handle.filter_files(filter_out); - - Py_INCREF(Py_None); return Py_None; -} - - -/*static PyObject *torrent_get_unique_IDs(PyObject *self, PyObject *args) -{ - PyObject *ret = PyTuple_New(M_torrents.size()); - PyObject *temp; - - for (unsigned long i = 0; i < M_torrents.size(); i++) - { - temp = Py_BuildValue("i", M_torrents->at(i).unique_ID) - - PyTuple_SetItem(ret, i, temp); - }; - - return ret; -};*/ - -static PyObject *torrent_constants(PyObject *self, PyObject *args) -{ - Py_INCREF(M_constants); return M_constants; -} - - -static PyObject *torrent_start_DHT(PyObject *self, PyObject *args) -{ - const char *DHT_path; - if (!PyArg_ParseTuple(args, "s", &DHT_path)) - return NULL; - - // printf("Loading DHT state from %s\r\n", DHT_path); - - boost::filesystem::path tempPath(DHT_path, empty_name_check); - boost::filesystem::ifstream DHT_state_file(tempPath, std::ios_base::binary); - DHT_state_file.unsetf(std::ios_base::skipws); - - entry DHT_state; - try - { - DHT_state = bdecode(std::istream_iterator(DHT_state_file), - std::istream_iterator()); - M_ses->start_dht(DHT_state); - // printf("DHT state recovered.\r\n"); - - // // Print out the state data from the FILE (not the session!) - // printf("Number of DHT peers in recovered state: %ld\r\n", count_DHT_peers(DHT_state)); - - } - catch (std::exception&) - { - printf("No DHT file to resume\r\n"); - M_ses->start_dht(); - } - - M_ses->add_dht_router(std::make_pair(std::string("router.bittorrent.com"), - DHT_ROUTER_PORT)); - M_ses->add_dht_router(std::make_pair(std::string("router.utorrent.com"), - DHT_ROUTER_PORT)); - M_ses->add_dht_router(std::make_pair(std::string("router.bitcomet.com"), - DHT_ROUTER_PORT)); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_stop_DHT(PyObject *self, PyObject *args) -{ - const char *DHT_path; - if (!PyArg_ParseTuple(args, "s", &DHT_path)) - return NULL; - - // printf("Saving DHT state to %s\r\n", DHT_path); - - boost::filesystem::path tempPath = boost::filesystem::path(DHT_path, empty_name_check); - - try - { - entry DHT_state = M_ses->dht_state(); - - // printf("Number of DHT peers in state, saving: %ld\r\n", count_DHT_peers(DHT_state)); - - boost::filesystem::ofstream out(tempPath, std::ios_base::binary); - out.unsetf(std::ios_base::skipws); - bencode(std::ostream_iterator(out), DHT_state); - } - catch (std::exception& e) - { - printf("An error occured in saving DHT\r\n"); - std::cerr << e.what() << "\n"; - } - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_get_DHT_info(PyObject *self, PyObject *args) -{ - entry DHT_state = M_ses->dht_state(); - - return Py_BuildValue("l", python_long(count_DHT_peers(DHT_state))); - - /* - // DHT_state.print(cout); - entry *nodes = DHT_state.find_key("nodes"); - if (!nodes) - return Py_BuildValue("l", -1); // No nodes - we are just starting up... - - entry::list_type &peers = nodes->list(); - entry::list_type::const_iterator i; - - python_long num_peers = 0; - - i = peers.begin(); - while (i != peers.end()) - { - num_peers++; - i++; - } - - return Py_BuildValue("l", num_peers); - */ -} - - -// Create Torrents: call with something like: -// create_torrent("mytorrent.torrent", "directory or file to make a torrent out of", -// "tracker1\ntracker2\ntracker3", "no comment", 256, "Deluge"); -// That makes a torrent with pieces of 256K, with "Deluge" as the creator string. -// -// The following function contains code by Christophe Dumez and Arvid Norberg -static PyObject *torrent_create_torrent(PyObject *self, PyObject *args) -{ - using namespace libtorrent; - using namespace boost::filesystem; - - //path::default_name_check(no_check); - - char *destination, *comment, *creator_str, *input, *trackers; - python_long piece_size; - if (!PyArg_ParseTuple(args, "ssssis", - &destination, &input, &trackers, &comment, &piece_size, &creator_str)) - return NULL; - - piece_size = piece_size * 1024; - - try - { - torrent_info t; - boost::filesystem::path full_path = complete(boost::filesystem::path(input)); - boost::filesystem::ofstream out(complete(boost::filesystem::path(destination)), std::ios_base::binary); - - internal_add_files(t, full_path.branch_path(), full_path.leaf()); - t.set_piece_size(piece_size); - - file_pool fp; - boost::scoped_ptr st(default_storage_constructor(t, full_path.branch_path(), fp)); - - std::string stdTrackers(trackers); - unsigned long index = 0, next = stdTrackers.find("\n"); - while (1 == 1) - { - t.add_tracker(stdTrackers.substr(index, next-index)); - index = next + 1; - if (next >= stdTrackers.length()) - break; - next = stdTrackers.find("\n", index); - if (next == std::string::npos) - break; - } - - int num = t.num_pieces(); - std::vector buf(piece_size); - for (int i = 0; i < num; ++i) - { - st->read(&buf[0], i, 0, t.piece_size(i)); - hasher h(&buf[0], t.piece_size(i)); - t.set_hash(i, h.final()); - } - - t.set_creator(creator_str); - t.set_comment(comment); - - entry e = t.create_torrent(); - bencode(std::ostream_iterator(out), e); - return Py_BuildValue("l", 1); - } catch (std::exception& e) - { - // std::cerr << e.what() << "\n"; - // return Py_BuildValue("l", 0); - RAISE_PTR(DelugeError, e.what()); - return Py_BuildValue("l", 0); - } -} - - -static PyObject *torrent_reset_IP_filter(PyObject *self, PyObject *args) -{ - // Remove existing filter, if there is one - if (M_the_filter != NULL) - delete M_the_filter; - - M_the_filter = new ip_filter(); - - M_ses->set_ip_filter(*M_the_filter); - - Py_INCREF(Py_None); return Py_None; -} - - -static PyObject *torrent_add_range_to_IP_filter(PyObject *self, PyObject *args) -{ - if (M_the_filter == NULL) { - RAISE_PTR(DelugeError, "No filter defined, use reset_IP_filter"); - } - - char *start, *end; - if (!PyArg_ParseTuple(args, "ss", &start, &end)) - return NULL; - - address_v4 inet_start = address_v4::from_string(start); - address_v4 inet_end = address_v4::from_string(end); - M_the_filter->add_rule(inet_start, inet_end, ip_filter::blocked); - - Py_INCREF(Py_None); return Py_None; -} - -static PyObject *torrent_use_upnp(PyObject *self, PyObject *args) -{ - python_long action; - PyArg_ParseTuple(args, "i", &action); - - if (action){ - M_ses->start_upnp(); - } - else{ - M_ses->stop_upnp(); - } - - Py_INCREF(Py_None); return Py_None; - -} - -static PyObject *torrent_use_natpmp(PyObject *self, PyObject *args) -{ - python_long action; - - PyArg_ParseTuple(args, "i", &action); - - if (action){ - M_ses->start_natpmp(); - } - else{ - M_ses->stop_natpmp(); - } - - Py_INCREF(Py_None); return Py_None; -} - -static PyObject *torrent_use_utpex(PyObject *self, PyObject *args) -{ - python_long action; - - PyArg_ParseTuple(args, "i", &action); - - if (action){ - M_ses->add_extension(&libtorrent::create_ut_pex_plugin); - } - - Py_INCREF(Py_None); return Py_None; -} - -static PyObject *torrent_pe_settings(PyObject *self, PyObject *args) -{ - M_pe_settings = new pe_settings(); - libtorrent::pe_settings::enc_policy out, in, prefer; - libtorrent::pe_settings::enc_level level; - - PyArg_ParseTuple(args, "iiii", &out, &in, &level, &prefer); - - M_pe_settings->out_enc_policy = out; - M_pe_settings->in_enc_policy = in; - M_pe_settings->allowed_enc_level = level; - M_pe_settings->prefer_rc4 = prefer; - - M_ses->set_pe_settings(*M_pe_settings); - - return Py_None; -} - -static PyObject *torrent_set_ratio(PyObject *self, PyObject *args) -{ - python_long unique_ID; - float num; - if (!PyArg_ParseTuple(args, "if", &unique_ID, &num)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - M_torrents->at(index).handle.set_ratio(num); - - Py_INCREF(Py_None); return Py_None; -} - -static PyObject *torrent_proxy_settings(PyObject *self, PyObject *args) -{ - M_proxy_settings = new proxy_settings(); - - char *server, *login, *pasw; - int portnum; - libtorrent::proxy_settings::proxy_type proxytype; - bool peerproxy, trackerproxy, dhtproxy; - - PyArg_ParseTuple(args, "sssiibbb", &server, &login, &pasw, &portnum, &proxytype, &peerproxy, &trackerproxy, &dhtproxy); - - M_proxy_settings->type = proxytype; - M_proxy_settings->username = login; - M_proxy_settings->password = pasw; - M_proxy_settings->hostname = server; - M_proxy_settings->port = portnum; - - if (peerproxy) { - M_ses->set_peer_proxy(*M_proxy_settings); - } - - if (trackerproxy) { - M_ses->set_tracker_proxy(*M_proxy_settings); - } - - if (dhtproxy) { - M_ses->set_dht_proxy(*M_proxy_settings); - } - - return Py_None; -} - -static PyObject *torrent_get_trackers(PyObject *self, PyObject *args) -{ - python_long unique_ID; - if (!PyArg_ParseTuple(args, "i", &unique_ID)) - return NULL; - - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - torrent_handle& h = M_torrents->at(index).handle; - std::string trackerslist; - if (h.is_valid() && h.has_metadata()) - { - for (std::vector::const_iterator i = h.trackers().begin(); - i != h.trackers().end(); ++i) - { - trackerslist = trackerslist + i->url +"\n"; - } - } - return Py_BuildValue("s",trackerslist.c_str()); -} - -static PyObject *torrent_replace_trackers(PyObject *self, PyObject *args) -{ - python_long unique_ID; - const char* tracker; - if (!PyArg_ParseTuple(args, "iz", &unique_ID, &tracker)) - return NULL; - long index = get_index_from_unique_ID(unique_ID); - if (PyErr_Occurred()) - return NULL; - - torrent_handle& h = M_torrents->at(index).handle; - - std::vector trackerlist; - - std::istringstream trackers(tracker); - std::string line; - - while (std::getline(trackers, line)) { - libtorrent::announce_entry a_entry(line); - trackerlist.push_back(a_entry); - } - h.replace_trackers(trackerlist); - h.force_reannounce(); - return Py_None; -} -//==================== -// Python Module data -//==================== - -static PyMethodDef deluge_core_methods[] = -{ - {"pe_settings", torrent_pe_settings, METH_VARARGS, "."}, - {"pre_init", torrent_pre_init, METH_VARARGS, "."}, - {"init", torrent_init, METH_VARARGS, "."}, - {"quit", torrent_quit, METH_VARARGS, "."}, - {"save_fastresume", torrent_save_fastresume, METH_VARARGS, "."}, - {"set_max_half_open", torrent_set_max_half_open, METH_VARARGS, "."}, - {"set_download_rate_limit", torrent_set_download_rate_limit, METH_VARARGS, "."}, - {"set_upload_rate_limit", torrent_set_upload_rate_limit, METH_VARARGS, "."}, - {"set_listen_on", torrent_set_listen_on, METH_VARARGS, "."}, - {"is_listening", torrent_is_listening, METH_VARARGS, "."}, - {"listening_port", torrent_listening_port, METH_VARARGS, "."}, - {"set_max_uploads", torrent_set_max_uploads, METH_VARARGS, "."}, - {"set_max_connections", torrent_set_max_connections, METH_VARARGS, "."}, - {"add_torrent", torrent_add_torrent, METH_VARARGS, "."}, - {"remove_torrent", torrent_remove_torrent, METH_VARARGS, "."}, - {"get_num_torrents", torrent_get_num_torrents, METH_VARARGS, "."}, - {"reannounce", torrent_reannounce, METH_VARARGS, "."}, - {"pause", torrent_pause, METH_VARARGS, "."}, - {"resume", torrent_resume, METH_VARARGS, "."}, - {"get_torrent_state", torrent_get_torrent_state, METH_VARARGS, "."}, - {"pop_event", torrent_pop_event, METH_VARARGS, "."}, - {"get_session_info", torrent_get_session_info, METH_VARARGS, "."}, - {"get_peer_info", torrent_get_peer_info, METH_VARARGS, "."}, - {"get_file_info", torrent_get_file_info, METH_VARARGS, "."}, - {"set_filter_out", torrent_set_filter_out, METH_VARARGS, "."}, - {"constants", torrent_constants, METH_VARARGS, "."}, - {"start_DHT", torrent_start_DHT, METH_VARARGS, "."}, - {"stop_DHT", torrent_stop_DHT, METH_VARARGS, "."}, - {"get_DHT_info", torrent_get_DHT_info, METH_VARARGS, "."}, - {"create_torrent", torrent_create_torrent, METH_VARARGS, "."}, - {"reset_IP_filter", torrent_reset_IP_filter, METH_VARARGS, "."}, - {"add_range_to_IP_filter", torrent_add_range_to_IP_filter, METH_VARARGS, "."}, - {"use_upnp", torrent_use_upnp, METH_VARARGS, "."}, - {"use_natpmp", torrent_use_natpmp, METH_VARARGS, "."}, - {"use_utpex", torrent_use_utpex, METH_VARARGS, "."}, - {"set_ratio", torrent_set_ratio, METH_VARARGS, "."}, - {"proxy_settings", torrent_proxy_settings, METH_VARARGS, "."}, - {"get_trackers", torrent_get_trackers, METH_VARARGS, "."}, - {"replace_trackers", torrent_replace_trackers, METH_VARARGS, "."}, - {NULL} -}; - -PyMODINIT_FUNC -initdeluge_core(void) -{ - Py_InitModule("deluge_core", deluge_core_methods); -}; diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 6bc357506..e07a884cf 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -179,29 +179,35 @@ namespace libtorrent int ret = 0; + bool free_buffer = true; try { // std::cerr << "DISK THREAD: executing job: " << j.action << std::endl; switch (j.action) { case disk_io_job::read: - l.lock(); - j.buffer = (char*)m_pool.ordered_malloc(); - l.unlock(); if (j.buffer == 0) { - ret = -1; - j.str = "out of memory"; + l.lock(); + j.buffer = (char*)m_pool.ordered_malloc(); + l.unlock(); + assert(j.buffer_size <= m_block_size); + if (j.buffer == 0) + { + ret = -1; + j.str = "out of memory"; + break; + } } else { - assert(j.buffer_size <= m_block_size); - ret = j.storage->read_impl(j.buffer, j.piece, j.offset - , j.buffer_size); - - // simulates slow drives - // usleep(300); + free_buffer = false; } + ret = j.storage->read_impl(j.buffer, j.piece, j.offset + , j.buffer_size); + + // simulates slow drives + // usleep(300); break; case disk_io_job::write: assert(j.buffer); @@ -240,7 +246,7 @@ namespace libtorrent try { if (handler) handler(ret, j); } catch (std::exception&) {} - if (j.buffer) + if (j.buffer && free_buffer) { l.lock(); m_pool.ordered_free(j.buffer); diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp new file mode 100644 index 000000000..54af814d6 --- /dev/null +++ b/libtorrent/src/enum_net.cpp @@ -0,0 +1,133 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#if defined __linux__ || defined __MACH__ +#include +#include +#include +#endif + +#include "libtorrent/enum_net.hpp" + +namespace libtorrent +{ + std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) + { + static std::vector
ret; + if (!ret.empty()) return ret; + +#if defined __linux__ || defined __MACH__ || defined(__FreeBSD__) + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = asio::error::fault; + return ret; + } + ifconf ifc; + char buf[1024]; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) + { + close(s); + ec = asio::error::fault; + return ret; + } + close(s); + + char *ifr = (char*)ifc.ifc_req; + int remaining = ifc.ifc_len; + + while (remaining) + { + ifreq const& item = *reinterpret_cast(ifr); + if (item.ifr_addr.sa_family == AF_INET) + { + ret.push_back(address::from_string( + inet_ntoa(((sockaddr_in const*)&item.ifr_addr)->sin_addr))); + } + +#if defined __MACH__ || defined(__FreeBSD__) + int current_size = item.ifr_addr.sa_len + IFNAMSIZ; +#elif defined __linux__ + int current_size = sizeof(ifreq); +#endif + ifr += current_size; + remaining -= current_size; + } + +#elif defined WIN32 + + SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == SOCKET_ERROR) + { + ec = asio::error::fault; + return ret; + } + + INTERFACE_INFO buffer[30]; + DWORD size; + + if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, + sizeof(buffer), &size, 0, 0) != 0) + { + closesocket(s); + ec = asio::error::fault; + return ret; + } + closesocket(s); + + int n = size / sizeof(INTERFACE_INFO); + + for (int i = 0; i < n; ++i) + { + sockaddr_in *sockaddr = (sockaddr_in*)&buffer[i].iiAddress; + address a(address::from_string(inet_ntoa(sockaddr->sin_addr))); + if (a == address_v4::any()) continue; + ret.push_back(a); + } + +#else + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + ret.push_back(i->endpoint().address()); + } +#endif + return ret; + } + +} + + diff --git a/libtorrent/src/escape_string.cpp b/libtorrent/src/escape_string.cpp index 02a4fa085..faff3de95 100755 --- a/libtorrent/src/escape_string.cpp +++ b/libtorrent/src/escape_string.cpp @@ -33,13 +33,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include -#include #include #include #include #include #include +#include "libtorrent/assert.hpp" + namespace libtorrent { std::string unescape_string(std::string const& s) diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 53798cae1..2b306ca6d 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -42,6 +42,8 @@ using boost::bind; namespace libtorrent { + enum { max_bottled_buffer = 1024 * 1024 }; + void http_connection::get(std::string const& url, time_duration timeout , bool handle_redirect) { @@ -165,6 +167,7 @@ void http_connection::on_connect(asio::error_code const& e if (!e) { m_last_receive = time_now(); + if (m_connect_handler) m_connect_handler(*this); asio::async_write(m_sock, asio::buffer(sendbuffer) , bind(&http_connection::on_write, shared_from_this(), _1)); } @@ -310,8 +313,8 @@ void http_connection::on_read(asio::error_code const& e } if (int(m_recvbuffer.size()) == m_read_pos) - m_recvbuffer.resize((std::min)(m_read_pos + 2048, 1024*500)); - if (m_read_pos == 1024 * 500) + m_recvbuffer.resize((std::min)(m_read_pos + 2048, int(max_bottled_buffer))); + if (m_read_pos == max_bottled_buffer) { close(); if (m_bottled && m_called) return; diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 936f8292a..2a3c4449a 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -148,13 +148,19 @@ namespace libtorrent pos = newline; line >> m_protocol; - if (m_protocol.substr(0, 5) != "HTTP/") + if (m_protocol.substr(0, 5) == "HTTP/") { - throw std::runtime_error("unknown protocol in HTTP response: " - + m_protocol + " line: " + std::string(pos, newline)); + line >> m_status_code; + std::getline(line, m_server_message); + } + else + { + m_method = m_protocol; + std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower); + m_protocol.clear(); + line >> m_path >> m_protocol; + m_status_code = 0; } - line >> m_status_code; - std::getline(line, m_server_message); m_state = read_header; } @@ -250,7 +256,7 @@ namespace libtorrent assert(m_state == read_body); if (m_content_length >= 0) return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos - , m_recv_buffer.begin + std::min(m_recv_pos + , m_recv_buffer.begin + (std::min)(m_recv_pos , m_body_start_pos + m_content_length)); else return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos @@ -408,7 +414,7 @@ namespace libtorrent { m_send_buffer += "numwant="; m_send_buffer += boost::lexical_cast( - std::min(req.num_want, 999)); + (std::min)(req.num_want, 999)); m_send_buffer += '&'; } if (m_settings.announce_ip != address() && !url_has_argument(request, "ip")) @@ -459,14 +465,16 @@ namespace libtorrent m_send_buffer += "\r\n\r\n"; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); + cb->debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); std::stringstream info_hash_str; info_hash_str << req.info_hash; - requester().debug_log("info_hash: " + cb->debug_log("info_hash: " + boost::lexical_cast(req.info_hash)); - requester().debug_log("name lookup: " + hostname); + cb->debug_log("name lookup: " + hostname); } #endif @@ -491,8 +499,9 @@ namespace libtorrent void http_tracker_connection::name_lookup(asio::error_code const& error , tcp::resolver::iterator i) try { + boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker name lookup handler called"); + if (cb) cb->debug_log("tracker name lookup handler called"); #endif if (error == asio::error::operation_aborted) return; if (m_timed_out) return; @@ -504,7 +513,7 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker name lookup successful"); + if (cb) cb->debug_log("tracker name lookup successful"); #endif restart_read_timeout(); @@ -519,11 +528,11 @@ namespace libtorrent if (target == end) { assert(target_address.address().is_v4() != bind_interface().is_v4()); - if (has_requester()) + if (cb) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - requester().tracker_warning("the tracker only resolves to an " + cb->tracker_warning("the tracker only resolves to an " + tracker_address_type + " address, and you're listening on an " + bind_address_type + " socket. This may prevent you from receiving incoming connections."); } @@ -533,7 +542,7 @@ namespace libtorrent target_address = *target; } - if (has_requester()) requester().m_tracker_address = target_address; + if (cb) cb->m_tracker_address = target_address; m_socket = instantiate_connection(m_name_lookup.io_service(), m_proxy); if (m_proxy.type == proxy_settings::http @@ -574,7 +583,8 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker connection successful"); + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("tracker connection successful"); #endif restart_read_timeout(); @@ -598,7 +608,8 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker send data completed"); + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("tracker send data completed"); #endif restart_read_timeout(); assert(m_buffer.size() - m_recv_pos > 0); @@ -634,7 +645,8 @@ namespace libtorrent restart_read_timeout(); assert(bytes_transferred > 0); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker connection reading " + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("tracker connection reading " + boost::lexical_cast(bytes_transferred)); #endif @@ -700,6 +712,8 @@ namespace libtorrent } std::string location = m_parser.header("location"); + + boost::shared_ptr cb = requester(); if (m_parser.status_code() >= 300 && m_parser.status_code() < 400) { @@ -720,9 +734,9 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("Redirecting to \"" + location + "\""); + if (cb) cb->debug_log("Redirecting to \"" + location + "\""); #endif - if (has_requester()) requester().tracker_warning("Redirecting to \"" + location + "\""); + if (cb) cb->tracker_warning("Redirecting to \"" + location + "\""); tracker_request req = tracker_req(); req.url = location; @@ -745,20 +759,18 @@ namespace libtorrent std::string content_encoding = m_parser.header("content-encoding"); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("content-encoding: \"" + content_encoding + "\""); + if (cb) cb->debug_log("content-encoding: \"" + content_encoding + "\""); #endif if (content_encoding == "gzip" || content_encoding == "x-gzip") { - boost::shared_ptr r = m_requester.lock(); - - if (!r) + if (!cb) { close(); return; } m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_parser.body_start()); - if (inflate_gzip(m_buffer, tracker_request(), r.get(), + if (inflate_gzip(m_buffer, tracker_request(), cb.get(), m_settings.tracker_maximum_response_length)) { close(); @@ -835,7 +847,8 @@ namespace libtorrent void http_tracker_connection::parse(entry const& e) { - if (!has_requester()) return; + boost::shared_ptr cb = requester(); + if (!cb) return; try { @@ -852,8 +865,7 @@ namespace libtorrent try { entry const& warning = e["warning message"]; - if (has_requester()) - requester().tracker_warning(warning.string()); + cb->tracker_warning(warning.string()); } catch(type_error const&) {} @@ -867,7 +879,7 @@ namespace libtorrent entry scrape_data = e["files"][ih]; int complete = scrape_data["complete"].integer(); int incomplete = scrape_data["incomplete"].integer(); - requester().tracker_response(tracker_request(), peer_list, 0, complete + cb->tracker_response(tracker_request(), peer_list, 0, complete , incomplete); return; } @@ -884,12 +896,7 @@ namespace libtorrent peer_entry p; p.pid.clear(); - std::stringstream ip_str; - ip_str << (int)detail::read_uint8(i) << "."; - ip_str << (int)detail::read_uint8(i) << "."; - ip_str << (int)detail::read_uint8(i) << "."; - ip_str << (int)detail::read_uint8(i); - p.ip = ip_str.str(); + p.ip = detail::read_v4_address(i).to_string(); p.port = detail::read_uint16(i); peer_list.push_back(p); } @@ -904,6 +911,22 @@ namespace libtorrent } } + if (entry const* ipv6_peers = e.find_key("peers6")) + { + std::string const& peers = ipv6_peers->string(); + for (std::string::const_iterator i = peers.begin(); + i != peers.end();) + { + if (std::distance(i, peers.end()) < 18) break; + + peer_entry p; + p.pid.clear(); + p.ip = detail::read_v6_address(i).to_string(); + p.port = detail::read_uint16(i); + peer_list.push_back(p); + } + } + // look for optional scrape info int complete = -1; int incomplete = -1; @@ -914,18 +937,19 @@ namespace libtorrent try { incomplete = e["incomplete"].integer(); } catch(type_error&) {} - requester().tracker_response(tracker_request(), peer_list, interval, complete + cb->tracker_response(tracker_request(), peer_list, interval, complete , incomplete); } catch(type_error& e) { - requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); } catch(std::runtime_error& e) { - requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); } } } + diff --git a/libtorrent/src/identify_client.cpp b/libtorrent/src/identify_client.cpp index 26ddb51dc..7fa808f20 100755 --- a/libtorrent/src/identify_client.cpp +++ b/libtorrent/src/identify_client.cpp @@ -184,6 +184,7 @@ namespace , {"SB", "Swiftbit"} , {"SN", "ShareNet"} , {"SS", "SwarmScope"} + , {"ST", "SymTorrent"} , {"SZ", "Shareaza"} , {"S~", "Shareaza (beta)"} , {"T", "BitTornado"} @@ -194,12 +195,57 @@ namespace , {"U", "UPnP"} , {"UL", "uLeecher"} , {"UT", "uTorrent"} + , {"XL", "Xunlei"} , {"XT", "XanTorrent"} , {"XX", "Xtorrent"} , {"ZT", "ZipTorrent"} , {"lt", "rTorrent"} , {"pX", "pHoeniX"} , {"qB", "qBittorrent"} + , {"st", "SharkTorrent"} + }; + + struct generic_map_entry + { + int offset; + char const* id; + char const* name; + }; + // non-standard names + generic_map_entry generic_mappings[] = + { + {0, "Deadman Walking-", "Deadman"} + , {5, "Azureus", "Azureus 2.0.3.2"} + , {0, "DansClient", "XanTorrent"} + , {4, "btfans", "SimpleBT"} + , {0, "PRC.P---", "Bittorrent Plus! II"} + , {0, "P87.P---", "Bittorrent Plus!"} + , {0, "S587Plus", "Bittorrent Plus!"} + , {0, "martini", "Martini Man"} + , {0, "Plus---", "Bittorrent Plus"} + , {0, "turbobt", "TurboBT"} + , {0, "a00---0", "Swarmy"} + , {0, "a02---0", "Swarmy"} + , {0, "T00---0", "Teeweety"} + , {0, "BTDWV-", "Deadman Walking"} + , {2, "BS", "BitSpirit"} + , {0, "Pando-", "Pando"} + , {0, "LIME", "LimeWire"} + , {0, "btuga", "BTugaXP"} + , {0, "oernu", "BTugaXP"} + , {0, "Mbrst", "Burst!"} + , {0, "PEERAPP", "PeerApp"} + , {0, "Plus", "Plus!"} + , {0, "-Qt-", "Qt"} + , {0, "exbc", "BitComet"} + , {0, "DNA", "BitTorrent DNA"} + , {0, "-G3", "G3 Torrent"} + , {0, "-FG", "FlashGet"} + , {0, "-ML", "MLdonkey"} + , {0, "XBT", "XBT"} + , {0, "OP", "Opera"} + , {2, "RS", "Rufus"} + , {0, "AZ2500BT", "BitTyrant"} }; bool compare_id(map_entry const& lhs, map_entry const& rhs) @@ -281,30 +327,13 @@ namespace libtorrent // non standard encodings // ---------------------- - if (find_string(PID, "Deadman Walking-")) return "Deadman"; - if (find_string(PID + 5, "Azureus")) return "Azureus 2.0.3.2"; - if (find_string(PID, "DansClient")) return "XanTorrent"; - if (find_string(PID + 4, "btfans")) return "SimpleBT"; - if (find_string(PID, "PRC.P---")) return "Bittorrent Plus! II"; - if (find_string(PID, "P87.P---")) return "Bittorrent Plus!"; - if (find_string(PID, "S587Plus")) return "Bittorrent Plus!"; - if (find_string(PID, "martini")) return "Martini Man"; - if (find_string(PID, "Plus---")) return "Bittorrent Plus"; - if (find_string(PID, "turbobt")) return "TurboBT"; - if (find_string(PID, "a00---0")) return "Swarmy"; - if (find_string(PID, "a02---0")) return "Swarmy"; - if (find_string(PID, "T00---0")) return "Teeweety"; - if (find_string(PID, "BTDWV-")) return "Deadman Walking"; - if (find_string(PID + 2, "BS")) return "BitSpirit"; - if (find_string(PID, "btuga")) return "BTugaXP"; - if (find_string(PID, "oernu")) return "BTugaXP"; - if (find_string(PID, "Mbrst")) return "Burst!"; - if (find_string(PID, "Plus")) return "Plus!"; - if (find_string(PID, "-Qt-")) return "Qt"; - if (find_string(PID, "exbc")) return "BitComet"; - if (find_string(PID, "-G3")) return "G3 Torrent"; - if (find_string(PID, "XBT")) return "XBT"; - if (find_string(PID, "OP")) return "Opera"; + int num_generic_mappings = sizeof(generic_mappings) / sizeof(generic_mappings[0]); + + for (int i = 0; i < num_generic_mappings; ++i) + { + generic_map_entry const& e = generic_mappings[i]; + if (find_string(PID + e.offset, e.id)) return e.name; + } if (find_string(PID, "-BOW") && PID[7] == '-') return "Bits on Wheels " + std::string(PID + 4, PID + 7); diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp index 0c7d9d276..a3849ed69 100644 --- a/libtorrent/src/kademlia/closest_nodes.cpp +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include "libtorrent/assert.hpp" namespace libtorrent { namespace dht { diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index eda6cd864..c9908a163 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -237,6 +237,7 @@ namespace libtorrent { namespace dht try { if (e) return; + if (!m_socket.is_open()) return; time_duration d = m_dht.connection_timeout(); m_connection_timer.expires_from_now(d); m_connection_timer.async_wait(m_strand.wrap(bind(&dht_tracker::connection_timeout, self(), _1))); @@ -254,6 +255,7 @@ namespace libtorrent { namespace dht try { if (e) return; + if (!m_socket.is_open()) return; time_duration d = m_dht.refresh_timeout(); m_refresh_timer.expires_from_now(d); m_refresh_timer.async_wait(m_strand.wrap( @@ -276,8 +278,9 @@ namespace libtorrent { namespace dht try { if (e) return; + if (!m_socket.is_open()) return; m_timer.expires_from_now(minutes(tick_period)); - m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, this, _1))); + m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, self(), _1))); ptime now = time_now(); if (now - m_last_new_key > minutes(key_refresh)) @@ -388,6 +391,7 @@ namespace libtorrent { namespace dht try { if (error == asio::error::operation_aborted) return; + if (!m_socket.is_open()) return; int current_buffer = m_buffer; m_buffer = (m_buffer + 1) & 1; @@ -716,6 +720,7 @@ namespace libtorrent { namespace dht , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; + if (!m_socket.is_open()) return; add_node(host->endpoint()); } catch (std::exception&) @@ -734,6 +739,7 @@ namespace libtorrent { namespace dht , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; + if (!m_socket.is_open()) return; m_dht.add_router_node(host->endpoint()); } catch (std::exception&) diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp index 4ed413714..ad06c515d 100644 --- a/libtorrent/src/kademlia/node_id.cpp +++ b/libtorrent/src/kademlia/node_id.cpp @@ -34,10 +34,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/assert.hpp" using boost::bind; diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index 76f25548d..d7590ec47 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/io.hpp" #include "libtorrent/http_tracker_connection.hpp" + #include #include #include @@ -52,76 +53,22 @@ namespace libtorrent address_v4 guess_local_address(asio::io_service&); } -address_v4 lsd::lsd_multicast_address; -udp::endpoint lsd::lsd_multicast_endpoint; - lsd::lsd(io_service& ios, address const& listen_interface , peer_callback_t const& cb) : m_callback(cb) , m_retry_count(0) - , m_socket(ios) + , m_socket(ios, udp::endpoint(address_v4::from_string("239.192.152.143"), 6771) + , bind(&lsd::on_announce, this, _1, _2, _3)) , m_broadcast_timer(ios) , m_disabled(false) { - // Bittorrent Local discovery multicast address and port - lsd_multicast_address = address_v4::from_string("239.192.152.143"); - lsd_multicast_endpoint = udp::endpoint(lsd_multicast_address, 6771); - #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log.open("lsd.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - assert(lsd_multicast_address.is_multicast()); - rebind(listen_interface); } lsd::~lsd() {} -void lsd::rebind(address const& listen_interface) -{ - address_v4 local_ip = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) - { - local_ip = listen_interface.to_v4(); - } - - try - { - // the local interface hasn't changed - if (m_socket.is_open() - && m_socket.local_endpoint().address() == local_ip) - return; - - m_socket.close(); - - using namespace asio::ip::multicast; - - m_socket.open(udp::v4()); - m_socket.set_option(datagram_socket::reuse_address(true)); - m_socket.bind(udp::endpoint(local_ip, 6771)); - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "local ip: " << local_ip << std::endl; -#endif - - m_socket.set_option(join_group(lsd_multicast_address)); - m_socket.set_option(outbound_interface(local_ip)); - m_socket.set_option(enable_loopback(true)); - m_socket.set_option(hops(255)); - } - catch (std::exception& e) - { -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "socket multicast error " << e.what() - << ". disabling local service discovery" << std::endl; -#endif - m_disabled = true; - return; - } - m_disabled = false; - - setup_receive(); -} - void lsd::announce(sha1_hash const& ih, int listen_port) { if (m_disabled) return; @@ -136,8 +83,7 @@ void lsd::announce(sha1_hash const& ih, int listen_port) m_retry_count = 0; asio::error_code ec; - m_socket.send_to(asio::buffer(msg.c_str(), msg.size() - 1) - , lsd_multicast_endpoint, 0, ec); + m_socket.send(msg.c_str(), int(msg.size()), ec); if (ec) { m_disabled = true; @@ -157,8 +103,8 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try { if (e) return; - m_socket.send_to(asio::buffer(msg, msg.size() - 1) - , lsd_multicast_endpoint); + asio::error_code ec; + m_socket.send(msg.c_str(), int(msg.size()), ec); ++m_retry_count; if (m_retry_count >= 5) @@ -170,14 +116,13 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try catch (std::exception&) {} -void lsd::on_announce(asio::error_code const& e +void lsd::on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) { using namespace libtorrent::detail; - if (e) return; - char* p = m_receive_buffer; - char* end = m_receive_buffer + bytes_transferred; + char* p = buffer; + char* end = buffer + bytes_transferred; char* line = std::find(p, end, '\n'); for (char* i = p; i < line; ++i) *i = std::tolower(*i); #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) @@ -190,7 +135,6 @@ void lsd::on_announce(asio::error_code const& e m_log << time_now_string() << " *** assumed 'bt-search', ignoring" << std::endl; #endif - setup_receive(); return; } p = line + 1; @@ -223,25 +167,15 @@ void lsd::on_announce(asio::error_code const& e { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log << time_now_string() - << " *** incoming local announce " << m_remote.address() + << " *** incoming local announce " << from.address() << ":" << port << " ih: " << ih << std::endl; #endif // we got an announce, pass it on through the callback - try { m_callback(tcp::endpoint(m_remote.address(), port), ih); } + try { m_callback(tcp::endpoint(from.address(), port), ih); } catch (std::exception&) {} } - setup_receive(); } -void lsd::setup_receive() try -{ - assert(m_socket.is_open()); - m_socket.async_receive_from(asio::buffer(m_receive_buffer - , sizeof(m_receive_buffer)), m_remote, bind(&lsd::on_announce, this, _1, _2)); -} -catch (std::exception&) -{} - void lsd::close() { m_socket.close(); diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index 97635cdb9..a19dd3d3f 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -523,7 +523,7 @@ namespace libtorrent { namespace if (num_blocks < 1) num_blocks = 1; assert(num_blocks <= 128); - int min_element = std::numeric_limits::max(); + int min_element = (std::numeric_limits::max)(); int best_index = 0; for (int i = 0; i < 256 - num_blocks + 1; ++i) { diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index 0a5932a56..bdcabce9a 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -32,11 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" -#include -#include #include #include +#include "libtorrent/natpmp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" + using boost::bind; using namespace libtorrent; diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index 437c93e2c..981eca63d 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_DISABLE_ENCRYPTION -#include #include #include #include #include "libtorrent/pe_crypto.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index b73e32896..ad2102f0d 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" +#include "libtorrent/assert.hpp" using boost::bind; using boost::shared_ptr; @@ -76,6 +77,8 @@ namespace libtorrent , m_timeout(m_ses.settings().peer_timeout) , m_last_piece(time_now()) , m_last_request(time_now()) + , m_last_incoming_request(min_time()) + , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) , m_current_send_buffer(0) @@ -93,6 +96,7 @@ namespace libtorrent , m_choked(true) , m_failed(false) , m_ignore_bandwidth_limits(false) + , m_have_all(false) , m_num_pieces(0) , m_desired_queue_size(2) , m_free_upload(0) @@ -108,8 +112,8 @@ namespace libtorrent , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_non_prioritized(false) - , m_upload_limit(resource_request::inf) - , m_download_limit(resource_request::inf) + , m_upload_limit(bandwidth_limit::inf) + , m_download_limit(bandwidth_limit::inf) , m_peer_info(peerinfo) , m_speed(slow) , m_connection_ticket(-1) @@ -153,6 +157,8 @@ namespace libtorrent , m_timeout(m_ses.settings().peer_timeout) , m_last_piece(time_now()) , m_last_request(time_now()) + , m_last_incoming_request(min_time()) + , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) , m_current_send_buffer(0) @@ -168,6 +174,7 @@ namespace libtorrent , m_choked(true) , m_failed(false) , m_ignore_bandwidth_limits(false) + , m_have_all(false) , m_num_pieces(0) , m_desired_queue_size(2) , m_free_upload(0) @@ -183,10 +190,11 @@ namespace libtorrent , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_non_prioritized(false) - , m_upload_limit(resource_request::inf) - , m_download_limit(resource_request::inf) + , m_upload_limit(bandwidth_limit::inf) + , m_download_limit(bandwidth_limit::inf) , m_peer_info(peerinfo) , m_speed(slow) + , m_connection_ticket(-1) , m_remote_bytes_dled(0) , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) @@ -251,6 +259,67 @@ namespace libtorrent } #endif + void peer_connection::send_allowed_set() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + int num_allowed_pieces = m_ses.settings().allowed_fast_set_size; + int num_pieces = t->torrent_file().num_pieces(); + + if (num_allowed_pieces >= num_pieces) + { + for (int i = 0; i < num_pieces; ++i) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> ALLOWED_FAST [ " << i << " ]\n"; +#endif + write_allow_fast(i); + m_accept_fast.insert(i); + } + return; + } + + std::string x; + address const& addr = m_remote.address(); + if (addr.is_v4()) + { + address_v4::bytes_type bytes = addr.to_v4().to_bytes(); + x.assign((char*)&bytes[0], bytes.size()); + } + else + { + address_v6::bytes_type bytes = addr.to_v6().to_bytes(); + x.assign((char*)&bytes[0], bytes.size()); + } + x.append((char*)&t->torrent_file().info_hash()[0], 20); + + sha1_hash hash = hasher(&x[0], x.size()).final(); + for (;;) + { + char* p = (char*)&hash[0]; + for (int i = 0; i < 5; ++i) + { + int piece = detail::read_uint32(p) % num_pieces; + if (m_accept_fast.find(piece) == m_accept_fast.end()) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> ALLOWED_FAST [ " << piece << " ]\n"; +#endif + write_allow_fast(piece); + m_accept_fast.insert(piece); + if (int(m_accept_fast.size()) >= num_allowed_pieces + || int(m_accept_fast.size()) == num_pieces) return; + } + } + hash = hasher((char*)&hash[0], 20).final(); + } + } + void peer_connection::init() { INVARIANT_CHECK; @@ -260,7 +329,7 @@ namespace libtorrent assert(t->valid_metadata()); assert(t->ready_for_connections()); - m_have_piece.resize(t->torrent_file().num_pieces(), false); + m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); // now that we have a piece_picker, // update it with this peers pieces @@ -274,7 +343,7 @@ namespace libtorrent // if this is a web seed. we don't have a peer_info struct if (m_peer_info) m_peer_info->seed = true; // if we're a seed too, disconnect - if (t->is_seed()) + if (t->is_finished()) { throw std::runtime_error("seed to seed connection redundant, disconnecting"); } @@ -331,7 +400,12 @@ namespace libtorrent { // dont announce during handshake if (in_handshake()) return; - + + // remove suggested pieces that we have + std::vector::iterator i = std::find( + m_suggested_pieces.begin(), m_suggested_pieces.end(), index); + if (i != m_suggested_pieces.end()) m_suggested_pieces.erase(i); + // optimization, don't send have messages // to peers that already have the piece if (!m_ses.settings().send_redundant_have @@ -378,8 +452,6 @@ namespace libtorrent void peer_connection::add_stat(size_type downloaded, size_type uploaded) { - INVARIANT_CHECK; - m_statistics.add_stat(downloaded, uploaded); } @@ -449,6 +521,7 @@ namespace libtorrent assert(t); assert(t->valid_metadata()); + torrent_info const& ti = t->torrent_file(); return p.piece >= 0 && p.piece < t->torrent_file().num_pieces() @@ -456,35 +529,30 @@ namespace libtorrent && p.start >= 0 && (p.length == t->block_size() || (p.length < t->block_size() - && p.piece == t->torrent_file().num_pieces()-1 - && p.start + p.length == t->torrent_file().piece_size(p.piece)) + && p.piece == ti.num_pieces()-1 + && p.start + p.length == ti.piece_size(p.piece)) || (m_request_large_blocks - && p.length <= t->torrent_file().piece_size(p.piece))) - && p.start + p.length <= t->torrent_file().piece_size(p.piece) + && p.length <= ti.piece_length() * m_prefer_whole_pieces == 0 ? + 1 : m_prefer_whole_pieces)) + && p.piece * size_type(ti.piece_length()) + p.start + p.length + <= ti.total_size() && (p.start % t->block_size() == 0); } - - struct disconnect_torrent - { - disconnect_torrent(boost::weak_ptr& t): m_t(&t) {} - ~disconnect_torrent() { if (m_t) m_t->reset(); } - void cancel() { m_t = 0; } - private: - boost::weak_ptr* m_t; - }; - + void peer_connection::attach_to_torrent(sha1_hash const& ih) { INVARIANT_CHECK; assert(!m_disconnecting); - m_torrent = m_ses.find_torrent(ih); - - boost::shared_ptr t = m_torrent.lock(); + assert(m_torrent.expired()); + boost::weak_ptr wpt = m_ses.find_torrent(ih); + boost::shared_ptr t = wpt.lock(); if (t && t->is_aborted()) { - m_torrent.reset(); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** the torrent has been aborted\n"; +#endif t.reset(); } @@ -492,12 +560,18 @@ namespace libtorrent { // we couldn't find the torrent! #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " couldn't find a torrent with the given info_hash: " << ih << "\n"; + (*m_logger) << " *** couldn't find a torrent with the given info_hash: " << ih << "\n"; + (*m_logger) << " torrents:\n"; + session_impl::torrent_map const& torrents = m_ses.m_torrents; + for (session_impl::torrent_map::const_iterator i = torrents.begin() + , end(torrents.end()); i != end; ++i) + { + (*m_logger) << " " << i->second->torrent_file().info_hash() << "\n"; + } #endif throw std::runtime_error("got info-hash that is not in our session"); } - disconnect_torrent disconnect(m_torrent); if (t->is_paused()) { // paused torrents will not accept @@ -508,21 +582,27 @@ namespace libtorrent throw std::runtime_error("connection rejected by paused torrent"); } + assert(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. t->attach_peer(this); + m_torrent = wpt; + + assert(!m_torrent.expired()); // if the torrent isn't ready to accept // connections yet, we'll have to wait with // our initialization if (t->ready_for_connections()) init(); + assert(!m_torrent.expired()); + // assume the other end has no pieces // if we don't have valid metadata yet, // leave the vector unallocated assert(m_num_pieces == 0); std::fill(m_have_piece.begin(), m_have_piece.end(), false); - disconnect.cancel(); + assert(!m_torrent.expired()); } // message handlers @@ -588,6 +668,101 @@ namespace libtorrent m_request_queue.clear(); } + bool match_request(peer_request const& r, piece_block const& b, int block_size) + { + if (b.piece_index != r.piece) return false; + if (b.block_index != r.start / block_size) return false; + if (r.start % block_size != 0) return false; + return true; + } + + // ----------------------------- + // -------- REJECT PIECE ------- + // ----------------------------- + + void peer_connection::incoming_reject_request(peer_request const& r) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + std::deque::iterator i = std::find_if( + m_download_queue.begin(), m_download_queue.end() + , bind(match_request, boost::cref(r), _1, t->block_size())); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== REJECT_PIECE [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; +#endif + + piece_block b(-1, 0); + if (i != m_download_queue.end()) + { + b = *i; + m_download_queue.erase(i); + } + else + { + i = std::find_if(m_request_queue.begin(), m_request_queue.end() + , bind(match_request, boost::cref(r), _1, t->block_size())); + + if (i != m_request_queue.end()) + { + b = *i; + m_request_queue.erase(i); + } + } + + if (b.piece_index != -1 && !t->is_seed()) + { + piece_picker& p = t->picker(); + p.abort_download(b); + } +#ifdef TORRENT_VERBOSE_LOGGING + else + { + (*m_logger) << time_now_string() + << " *** PIECE NOT IN REQUEST QUEUE\n"; + } +#endif + if (m_request_queue.empty()) + { + if (m_download_queue.size() < 2) + { + request_a_block(*t, *this); + } + send_block_requests(); + } + } + + // ----------------------------- + // -------- REJECT PIECE ------- + // ----------------------------- + + void peer_connection::incoming_suggest(int index) + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== SUGGEST_PIECE [ piece: " << index << " ]\n"; +#endif + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + + if (t->have_piece(index)) return; + + if (m_suggested_pieces.size() > 9) + m_suggested_pieces.erase(m_suggested_pieces.begin()); + m_suggested_pieces.push_back(index); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ** SUGGEST_PIECE [ piece: " << index << " added to set: " << m_suggested_pieces.size() << " ]\n"; +#endif + } + // ----------------------------- // ---------- UNCHOKE ---------- // ----------------------------- @@ -742,7 +917,7 @@ namespace libtorrent { assert(m_peer_info); m_peer_info->seed = true; - if (t->is_seed()) + if (t->is_finished()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -798,11 +973,12 @@ namespace libtorrent { m_have_piece = bitfield; m_num_pieces = std::count(bitfield.begin(), bitfield.end(), true); - - if (m_peer_info) m_peer_info->seed = true; + if (m_peer_info) m_peer_info->seed = (m_num_pieces == int(bitfield.size())); return; } + assert(t->valid_metadata()); + int num_pieces = std::count(bitfield.begin(), bitfield.end(), true); if (num_pieces == int(m_have_piece.size())) { @@ -812,7 +988,7 @@ namespace libtorrent // if this is a web seed. we don't have a peer_info struct if (m_peer_info) m_peer_info->seed = true; // if we're a seed too, disconnect - if (t->is_seed()) + if (t->is_finished()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -906,6 +1082,7 @@ namespace libtorrent "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif + write_reject_request(r); return; } @@ -925,6 +1102,7 @@ namespace libtorrent "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif + write_reject_request(r); return; } @@ -947,11 +1125,20 @@ namespace libtorrent #endif // if we have choked the client // ignore the request - if (m_choked) - return; - - m_requests.push_back(r); - fill_send_buffer(); + if (m_choked && m_accept_fast.find(r.piece) == m_accept_fast.end()) + { + write_reject_request(r); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " *** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]\n"; +#endif + } + else + { + m_requests.push_back(r); + m_last_incoming_request = time_now(); + fill_send_buffer(); + } } else { @@ -968,6 +1155,7 @@ namespace libtorrent "block_limit: " << t->block_size() << " ]\n"; #endif + write_reject_request(r); ++m_num_invalid_requests; if (t->alerts().should_post(alert::debug)) @@ -977,7 +1165,7 @@ namespace libtorrent , t->get_handle() , m_remote , m_peer_id - , "peer sent an illegal piece request, ignoring")); + , "peer sent an illegal piece request")); } } } @@ -1131,11 +1319,8 @@ namespace libtorrent "request queue ***\n"; #endif t->received_redundant_data(p.length); - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } + request_a_block(*t, *this); + send_block_requests(); return; } @@ -1144,11 +1329,8 @@ namespace libtorrent { t->received_redundant_data(p.length); - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } + request_a_block(*t, *this); + send_block_requests(); return; } @@ -1205,15 +1387,11 @@ namespace libtorrent block_finished.block_index, block_finished.piece_index, "block finished")); } - if (!has_peer_choked() && !t->is_seed() && !m_torrent.expired()) + if (!t->is_seed() && !m_torrent.expired()) { // this is a free function defined in policy.cpp request_a_block(*t, *this); - try - { - send_block_requests(); - } - catch (std::exception const&) {} + send_block_requests(); } #ifndef NDEBUG @@ -1295,6 +1473,146 @@ namespace libtorrent #endif } + // ----------------------------- + // --------- HAVE ALL ---------- + // ----------------------------- + + void peer_connection::incoming_have_all() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== HAVE_ALL\n"; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_have_all()) return; + } +#endif + + m_have_all = true; + + if (m_peer_info) m_peer_info->seed = true; + + // if we don't have metadata yet + // just remember the bitmask + // don't update the piecepicker + // (since it doesn't exist yet) + if (!t->ready_for_connections()) + { + // TODO: this might need something more + // so that once we have the metadata + // we can construct a full bitfield + return; + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** THIS IS A SEED ***\n"; +#endif + + // if we're a seed too, disconnect + if (t->is_finished()) + throw protocol_error("seed to seed connection redundant, disconnecting"); + + assert(!m_have_piece.empty()); + std::fill(m_have_piece.begin(), m_have_piece.end(), true); + m_num_pieces = m_have_piece.size(); + + t->peer_has_all(); + if (!t->is_finished()) + t->get_policy().peer_is_interesting(*this); + } + + // ----------------------------- + // --------- HAVE NONE --------- + // ----------------------------- + + void peer_connection::incoming_have_none() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== HAVE_NONE\n"; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_have_none()) return; + } +#endif + + if (m_peer_info) m_peer_info->seed = false; + assert(!m_have_piece.empty() || !t->ready_for_connections()); + } + + // ----------------------------- + // ------- ALLOWED FAST -------- + // ----------------------------- + + void peer_connection::incoming_allowed_fast(int index) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== ALLOWED_FAST [ " << index << " ]\n"; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_allowed_fast(index)) return; + } +#endif + + // if we already have the piece, we can + // ignore this message + if (t->valid_metadata() + && t->have_piece(index)) + return; + + m_allowed_fast.push_back(index); + + // if the peer has the piece and we want + // to download it, request it + if (int(m_have_piece.size()) > index + && m_have_piece[index] + && t->has_picker() + && t->picker().piece_priority(index) > 0) + { + t->get_policy().peer_is_interesting(*this); + } + } + + std::vector const& peer_connection::allowed_fast() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + m_allowed_fast.erase(std::remove_if(m_allowed_fast.begin() + , m_allowed_fast.end(), bind(&torrent::have_piece, t, _1)) + , m_allowed_fast.end()); + + // TODO: sort the allowed fast set in priority order + return m_allowed_fast; + } + void peer_connection::add_request(piece_block const& block) { INVARIANT_CHECK; @@ -1308,10 +1626,11 @@ namespace libtorrent assert(block.block_index >= 0); assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); assert(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); + assert(!t->have_piece(block.piece_index)); piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); - std::string speedmsg; + char const* speedmsg = 0; if (speed == fast) { speedmsg = "fast"; @@ -1329,7 +1648,7 @@ namespace libtorrent } t->picker().mark_as_downloading(block, peer_info_struct(), state); - if (t->alerts().should_post(alert::info)) + if (t->alerts().should_post(alert::info)) { t->alerts().post_alert(block_downloading_alert(t->get_handle(), speedmsg, block.block_index, block.piece_index, "block downloading")); @@ -1380,7 +1699,7 @@ namespace libtorrent int block_offset = block.block_index * t->block_size(); int block_size - = std::min((int)t->torrent_file().piece_size(block.piece_index)-block_offset, + = (std::min)((int)t->torrent_file().piece_size(block.piece_index)-block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1403,6 +1722,8 @@ namespace libtorrent { INVARIANT_CHECK; + assert(!m_peer_info || !m_peer_info->optimistically_unchoked); + if (m_choked) return; write_choke(); m_choked = true; @@ -1421,15 +1742,8 @@ namespace libtorrent { INVARIANT_CHECK; -#ifndef NDEBUG - // TODO: once the policy lowers the interval for optimistic - // unchoke, increase this value that interval - // this condition cannot be guaranteed since if peers disconnect - // a new one will be unchoked ignoring when it was last choked - //assert(time_now() - m_last_choke > seconds(9)); -#endif - if (!m_choked) return; + m_last_unchoke = time_now(); write_unchoke(); m_choked = false; @@ -1470,13 +1784,9 @@ namespace libtorrent { INVARIANT_CHECK; - if (has_peer_choked()) return; - boost::shared_ptr t = m_torrent.lock(); assert(t); - assert(!has_peer_choked()); - if ((int)m_download_queue.size() >= m_desired_queue_size) return; while (!m_request_queue.empty() @@ -1485,7 +1795,7 @@ namespace libtorrent piece_block block = m_request_queue.front(); int block_offset = block.block_index * t->block_size(); - int block_size = std::min((int)t->torrent_file().piece_size( + int block_size = (std::min)((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1509,23 +1819,29 @@ namespace libtorrent // blocks that are in the same piece into larger requests if (m_request_large_blocks) { - while (!m_request_queue.empty() - && m_request_queue.front().piece_index == r.piece - && m_request_queue.front().block_index == block.block_index + 1) + int blocks_per_piece = t->torrent_file().piece_length() / t->block_size(); + + while (!m_request_queue.empty()) { + // check to see if this block is connected to the previous one + // if it is, merge them, otherwise, break this merge loop + piece_block const& front = m_request_queue.front(); + if (front.piece_index * blocks_per_piece + front.block_index + != block.piece_index * blocks_per_piece + block.block_index + 1) + break; block = m_request_queue.front(); m_request_queue.pop_front(); m_download_queue.push_back(block); -/* + #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() - << " *** REQUEST-QUEUE** [ " + << " *** MERGING REQUEST ** [ " "piece: " << block.piece_index << " | " "block: " << block.block_index << " ]\n"; #endif -*/ + block_offset = block.block_index * t->block_size(); - block_size = std::min((int)t->torrent_file().piece_size( + block_size = (std::min)((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1628,7 +1944,7 @@ namespace libtorrent void peer_connection::set_upload_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = resource_request::inf; + if (limit == -1) limit = (std::numeric_limits::max)(); if (limit < 10) limit = 10; m_upload_limit = limit; m_bandwidth_limit[upload_channel].throttle(m_upload_limit); @@ -1637,7 +1953,7 @@ namespace libtorrent void peer_connection::set_download_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = resource_request::inf; + if (limit == -1) limit = (std::numeric_limits::max)(); if (limit < 10) limit = 10; m_download_limit = limit; m_bandwidth_limit[download_channel].throttle(m_download_limit); @@ -1655,7 +1971,7 @@ namespace libtorrent // if we have an infinite ratio, just say we have downloaded // much more than we have uploaded. And we'll keep uploading. if (ratio == 0.f) - return std::numeric_limits::max(); + return (std::numeric_limits::max)(); return m_free_upload + static_cast(m_statistics.total_payload_download() * ratio) @@ -1703,8 +2019,9 @@ namespace libtorrent p.load_balancing = total_free_upload(); - p.download_queue_length = (int)download_queue().size(); - p.upload_queue_length = (int)upload_queue().size(); + p.download_queue_length = int(download_queue().size() + m_request_queue.size()); + p.target_dl_queue_length = int(desired_queue_size()); + p.upload_queue_length = int(upload_queue().size()); if (boost::optional ret = downloading_piece_progress()) { @@ -1724,7 +2041,7 @@ namespace libtorrent p.pieces = get_bitfield(); ptime now = time_now(); p.last_request = now - m_last_request; - p.last_active = now - std::max(m_last_sent, m_last_receive); + p.last_active = now - (std::max)(m_last_sent, m_last_receive); // this will set the flags so that we can update them later p.flags = 0; @@ -1737,6 +2054,7 @@ namespace libtorrent p.failcount = peer_info_struct()->failcount; p.num_hashfails = peer_info_struct()->hashfails; p.flags |= peer_info_struct()->on_parole ? peer_info::on_parole : 0; + p.flags |= peer_info_struct()->optimistically_unchoked ? peer_info::optimistic_unchoke : 0; p.remote_dl_rate = m_remote_dl_rate; } else @@ -1772,10 +2090,13 @@ namespace libtorrent if (m_packet_size >= m_recv_pos) m_recv_buffer.resize(m_packet_size); } - void peer_connection::second_tick(float tick_interval) + void peer_connection::second_tick(float tick_interval) throw() { INVARIANT_CHECK; + try + { + ptime now(time_now()); boost::shared_ptr t = m_torrent.lock(); @@ -1854,11 +2175,8 @@ namespace libtorrent m_assume_fifo = true; - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } + request_a_block(*t, *this); + send_block_requests(); } } @@ -1869,7 +2187,7 @@ namespace libtorrent // maintain the share ratio given by m_ratio // with all peers. - if (t->is_seed() || is_choked() || t->ratio() == 0.0f) + if (t->is_finished() || is_choked() || t->ratio() == 0.0f) { // if we have downloaded more than one piece more // than we have uploaded OR if we are a seed @@ -1891,14 +2209,14 @@ namespace libtorrent if (t->ratio() != 1.f) soon_downloaded = (size_type)(soon_downloaded*(double)t->ratio()); - double upload_speed_limit = std::min((soon_downloaded - have_uploaded + double upload_speed_limit = (std::min)((soon_downloaded - have_uploaded + bias) / break_even_time, double(m_upload_limit)); - upload_speed_limit = std::min(upload_speed_limit, - (double)std::numeric_limits::max()); + upload_speed_limit = (std::min)(upload_speed_limit, + (double)(std::numeric_limits::max)()); m_bandwidth_limit[upload_channel].throttle( - std::min(std::max((int)upload_speed_limit, 20) + (std::min)((std::max)((int)upload_speed_limit, 20) , m_upload_limit)); } @@ -1917,43 +2235,14 @@ namespace libtorrent } fill_send_buffer(); -/* - size_type diff = share_diff(); - - enum { block_limit = 2 }; // how many blocks difference is considered unfair - - // if the peer has been choked, send the current piece - // as fast as possible - if (diff > block_limit*m_torrent->block_size() || m_torrent->is_seed() || is_choked()) - { - // if we have downloaded more than one piece more - // than we have uploaded OR if we are a seed - // have an unlimited upload rate - m_ul_bandwidth_quota.wanted = std::numeric_limits::max(); } - else + catch (std::exception& e) { - float ratio = m_torrent->ratio(); - // if we have downloaded too much, response with an - // upload rate of 10 kB/s more than we dowlload - // if we have uploaded too much, send with a rate of - // 10 kB/s less than we receive - int bias = 0; - if (diff > -block_limit*m_torrent->block_size()) - { - bias = static_cast(m_statistics.download_rate() * ratio) / 2; - if (bias < 10*1024) bias = 10*1024; - } - else - { - bias = -static_cast(m_statistics.download_rate() * ratio) / 2; - } - m_ul_bandwidth_quota.wanted = static_cast(m_statistics.download_rate()) + bias; - - // the maximum send_quota given our download rate from this peer - if (m_ul_bandwidth_quota.wanted < 256) m_ul_bandwidth_quota.wanted = 256; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "**ERROR**: " << e.what() << "\n"; +#endif + m_ses.connection_failed(m_socket, remote(), e.what()); } -*/ } void peer_connection::fill_send_buffer() @@ -1988,20 +2277,6 @@ namespace libtorrent m_reading_bytes += r.length; m_requests.erase(m_requests.begin()); -/* - if (m_requests.empty() - && m_num_invalid_requests > 0 - && is_peer_interested() - && !is_seed()) - { - // this will make the peer clear - // its download queue and re-request - // pieces. Hopefully it will not - // send invalid requests then - send_choke(); - send_unchoke(); - } -*/ } } @@ -2542,9 +2817,14 @@ namespace libtorrent void peer_connection::check_invariant() const { if (m_peer_info) + { assert(m_peer_info->connection == this || m_peer_info->connection == 0); - + + if (m_peer_info->optimistically_unchoked) + assert(!is_choked()); + } + boost::shared_ptr t = m_torrent.lock(); if (!t) { @@ -2632,9 +2912,20 @@ namespace libtorrent // if the peer hasn't said a thing for a certain // time, it is considered to have timed out time_duration d; - d = time_now() - m_last_receive; + d = now - m_last_receive; if (d > seconds(m_timeout)) return true; + // disconnect peers that we unchoked, but + // they didn't send a request within 20 seconds. + // but only if we're a seed + boost::shared_ptr t = m_torrent.lock(); + d = now - (std::max)(m_last_unchoke, m_last_incoming_request); + if (m_requests.empty() + && !m_choked + && m_peer_interested + && t && t->is_finished() + && d > seconds(20)) return true; + // TODO: as long as we have less than 95% of the // global (or local) connection limit, connections should // never time out for another reason diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index ddc2c2f5a..8fa623fa3 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -167,6 +167,7 @@ namespace libtorrent return; assert(sequenced_download_threshold > 0); + if (sequenced_download_threshold <= 0) return; int old_limit = m_sequenced_download_threshold; m_sequenced_download_threshold = sequenced_download_threshold; @@ -191,22 +192,22 @@ namespace libtorrent // the previous max availability was reached // we need to shuffle that bucket, if not, we // don't have to do anything - if (int(m_piece_info.size()) > old_limit) + if (int(m_piece_info.size()) > old_limit * 2) { - info_t& in = m_piece_info[old_limit]; + info_t& in = m_piece_info[old_limit * 2]; std::random_shuffle(in.begin(), in.end()); int c = 0; for (info_t::iterator i = in.begin() , end(in.end()); i != end; ++i) { m_piece_map[*i].index = c++; - assert(m_piece_map[*i].priority(old_limit) == old_limit); + assert(m_piece_map[*i].priority(old_limit) == old_limit * 2); } } } - else if (int(m_piece_info.size()) > sequenced_download_threshold) + else if (int(m_piece_info.size()) > sequenced_download_threshold * 2) { - info_t& in = m_piece_info[sequenced_download_threshold]; + info_t& in = m_piece_info[sequenced_download_threshold * 2]; std::sort(in.begin(), in.end()); int c = 0; for (info_t::iterator i = in.begin() @@ -214,7 +215,7 @@ namespace libtorrent { m_piece_map[*i].index = c++; assert(m_piece_map[*i].priority( - sequenced_download_threshold) == sequenced_download_threshold); + sequenced_download_threshold) == sequenced_download_threshold * 2); } } } @@ -262,8 +263,23 @@ namespace libtorrent } m_downloads.erase(i); } + #ifndef NDEBUG + void piece_picker::verify_pick(std::vector const& picked + , std::vector const& bitfield) const + { + assert(bitfield.size() == m_piece_map.size()); + for (std::vector::const_iterator i = picked.begin() + , end(picked.end()); i != end; ++i) + { + assert(i->piece_index >= 0); + assert(i->piece_index < int(bitfield.size())); + assert(bitfield[i->piece_index]); + assert(!m_piece_map[i->piece_index].have()); + } + } + void piece_picker::check_invariant(const torrent* t) const { assert(sizeof(piece_pos) == 4); @@ -394,6 +410,7 @@ namespace libtorrent assert(!t->have_piece(index)); int prio = i->priority(m_sequenced_download_threshold); + assert(prio < int(m_piece_info.size())); if (prio > 0) { const std::vector& vec = m_piece_info[prio]; @@ -447,7 +464,7 @@ namespace libtorrent if (i->have()) ++peer_count; if (min_availability > peer_count) { - min_availability = i->peer_count; + min_availability = peer_count; fraction_part += integer_part; integer_part = 1; } @@ -638,12 +655,13 @@ namespace libtorrent if (dp == m_downloads.begin()) return; int complete = dp->writing + dp->finished; for (std::vector::iterator i = dp, j(dp-1); - i != m_downloads.begin() && j != m_downloads.begin(); --i, --j) + i != m_downloads.begin(); --i, --j) { assert(j >= m_downloads.begin()); if (j->finished + j->writing >= complete) return; using std::swap; swap(*j, *i); + if (j == m_downloads.begin()) break; } } @@ -739,6 +757,7 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); + assert(prev_prio < int(m_piece_info.size())); ++i->peer_count; // if the assumption that the priority would // increase by 2 when increasing the availability @@ -828,6 +847,8 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); + assert(prev_prio < int(m_piece_info.size())); + assert(pushed_out_index < int(m_piece_info.size())); assert(i->peer_count > 0); --i->peer_count; // if the assumption that the priority would @@ -879,6 +900,7 @@ namespace libtorrent piece_pos& p = m_piece_map[i]; int index = p.index; int prev_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); assert(p.peer_count < piece_pos::max_peer_count); p.peer_count++; @@ -913,6 +935,7 @@ namespace libtorrent piece_pos& p = m_piece_map[i]; int prev_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); int index = p.index; assert(p.peer_count > 0); @@ -937,6 +960,7 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; int info_index = p.index; int priority = p.priority(m_sequenced_download_threshold); + assert(priority < int(m_piece_info.size())); assert(p.downloading == 1); assert(!p.have()); @@ -980,6 +1004,7 @@ namespace libtorrent if (new_piece_priority == int(p.piece_priority)) return false; int prev_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); bool ret = false; if (new_piece_priority == piece_pos::filter_priority @@ -1003,6 +1028,7 @@ namespace libtorrent p.piece_priority = new_piece_priority; int new_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); if (new_priority == prev_priority) return false; @@ -1068,8 +1094,9 @@ namespace libtorrent // or slow once they're started. void piece_picker::pick_pieces(const std::vector& pieces , std::vector& interesting_blocks - , int num_blocks, bool prefer_whole_pieces - , void* peer, piece_state_t speed, bool rarest_first) const + , int num_blocks, int prefer_whole_pieces + , void* peer, piece_state_t speed, bool rarest_first + , bool on_parole, std::vector const& suggested_pieces) const { TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(num_blocks > 0); @@ -1085,59 +1112,84 @@ namespace libtorrent // blocks belonging to a piece that others have // downloaded to std::vector backup_blocks; + // suggested pieces for each vector is put in this vector + std::vector suggested_bucket; + const std::vector empty_vector; // When prefer_whole_pieces is set (usually set when downloading from // fast peers) the partial pieces will not be prioritized, but actually // ignored as long as possible. All blocks found in downloading // pieces are regarded as backup blocks - bool ignore_downloading_pieces = false; - if (prefer_whole_pieces) + + num_blocks = add_blocks_downloading(pieces + , interesting_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer, speed, on_parole); + + if (num_blocks <= 0) return; + + if (rarest_first) { - std::vector downloading_pieces; - downloading_pieces.reserve(m_downloads.size()); - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) + // this loop will loop from pieces with priority 1 and up + // until we either reach the end of the piece list or + // has filled the interesting_blocks with num_blocks + // blocks. + + // +1 is to ignore pieces that no peer has. The bucket with index 0 contains + // pieces that 0 other peers have. bucket will point to a bucket with + // pieces with the same priority. It will be iterated in priority + // order (high priority/rare pices first). The content of each + // bucket is randomized + for (std::vector >::const_iterator bucket + = m_piece_info.begin() + 1; num_blocks > 0 && bucket != m_piece_info.end(); + ++bucket) { - downloading_pieces.push_back(i->index); + if (bucket->empty()) continue; + if (!suggested_pieces.empty()) + { + int bucket_index = bucket - m_piece_info.begin(); + suggested_bucket.clear(); + for (std::vector::const_iterator i = suggested_pieces.begin() + , end(suggested_pieces.end()); i != end; ++i) + { + assert(*i >= 0); + assert(*i < int(m_piece_map.size())); + if (!can_pick(*i, pieces)) continue; + if (m_piece_map[*i].priority(m_sequenced_download_threshold) == bucket_index) + suggested_bucket.push_back(*i); + } + if (!suggested_bucket.empty()) + { + num_blocks = add_blocks(suggested_bucket, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, empty_vector); + if (num_blocks == 0) break; + } + } + num_blocks = add_blocks(*bucket, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, suggested_bucket); + assert(num_blocks >= 0); } - add_interesting_blocks(downloading_pieces, pieces - , backup_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); - ignore_downloading_pieces = true; } - - // this loop will loop from pieces with priority 1 and up - // until we either reach the end of the piece list or - // has filled the interesting_blocks with num_blocks - // blocks. - - // +1 is to ignore pieces that no peer has. The bucket with index 0 contains - // pieces that 0 other peers have. bucket will point to a bucket with - // pieces with the same priority. It will be iterated in priority - // order (high priority/rare pices first). The content of each - // bucket is randomized - for (std::vector >::const_iterator bucket - = m_piece_info.begin() + 1; bucket != m_piece_info.end(); - ++bucket) + else { - if (bucket->empty()) continue; - num_blocks = add_interesting_blocks(*bucket, pieces - , interesting_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); - assert(num_blocks >= 0); - if (num_blocks == 0) return; - if (rarest_first) continue; - // we're not using rarest first (only for the first // bucket, since that's where the currently downloading // pieces are) + int start_piece = rand() % m_piece_map.size(); + + // if we have suggested pieces, try to find one of those instead + for (std::vector::const_iterator i = suggested_pieces.begin() + , end(suggested_pieces.end()); i != end; ++i) + { + if (!can_pick(*i, pieces)) continue; + start_piece = *i; + break; + } + int piece = start_piece; while (num_blocks > 0) { - int start_piece = rand() % m_piece_map.size(); - int piece = start_piece; - while (!pieces[piece] - || m_piece_map[piece].index == piece_pos::we_have_index - || m_piece_map[piece].priority(m_sequenced_download_threshold) < 2) + while (!can_pick(piece, pieces)) { ++piece; if (piece == int(m_piece_map.size())) piece = 0; @@ -1145,27 +1197,44 @@ namespace libtorrent if (piece == start_piece) return; } - assert(m_piece_map[piece].downloading == false); - - int num_blocks_in_piece = blocks_in_piece(piece); - - if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) - num_blocks_in_piece = num_blocks; - for (int j = 0; j < num_blocks_in_piece; ++j) - interesting_blocks.push_back(piece_block(piece, j)); - num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); + int start, end; + boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces); + for (int k = start; k < end; ++k) + { + assert(m_piece_map[piece].downloading == false); + assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + int num_blocks_in_piece = blocks_in_piece(k); + if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.push_back(piece_block(k, j)); + --num_blocks; + } + } + piece = end; + if (piece == int(m_piece_map.size())) piece = 0; + // could not find any more pieces + if (piece == start_piece) return; } - if (num_blocks == 0) return; - break; + } - assert(num_blocks > 0); + if (num_blocks <= 0) return; if (!backup_blocks.empty()) interesting_blocks.insert(interesting_blocks.end() , backup_blocks.begin(), backup_blocks.end()); } + bool piece_picker::can_pick(int piece, std::vector const& bitmask) const + { + return bitmask[piece] + && !m_piece_map[piece].have() + && !m_piece_map[piece].downloading + && !m_piece_map[piece].filtered(); + } + void piece_picker::clear_peer(void* peer) { for (std::vector::iterator i = m_block_info.begin() @@ -1203,17 +1272,12 @@ namespace libtorrent } } - int piece_picker::add_interesting_blocks(std::vector const& piece_list + int piece_picker::add_blocks(std::vector const& piece_list , std::vector const& pieces , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, bool prefer_whole_pieces - , void* peer, piece_state_t speed - , bool ignore_downloading_pieces) const + , int num_blocks, int prefer_whole_pieces + , void* peer, std::vector const& ignore) const { - // if we have less than 1% of the pieces, ignore speed priorities and just try - // to finish any downloading piece - bool ignore_speed_categories = (m_num_have * 100 / m_piece_map.size()) < 1; for (std::vector::const_iterator i = piece_list.begin(); i != piece_list.end(); ++i) { @@ -1224,111 +1288,196 @@ namespace libtorrent // skip it if (!pieces[*i]) continue; + // ignore pieces found in the ignore list + if (std::find(ignore.begin(), ignore.end(), *i) != ignore.end()) continue; + + // skip the piece is the priority is 0 + assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + int num_blocks_in_piece = blocks_in_piece(*i); - if (m_piece_map[*i].downloading == 1) + assert(m_piece_map[*i].downloading == 0); + assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + + // pick a new piece + if (prefer_whole_pieces == 0) { - if (ignore_downloading_pieces) continue; - std::vector::const_iterator p - = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); - assert(p != m_downloads.end()); - - // is true if all the other pieces that are currently - // requested from this piece are from the same - // peer as 'peer'. - bool exclusive; - bool exclusive_active; - boost::tie(exclusive, exclusive_active) - = requested_from(*p, num_blocks_in_piece, peer); - - // this means that this partial piece has - // been downloaded/requested partially from - // another peer that isn't us. And since - // we prefer whole pieces, add this piece's - // blocks to the backup list. If the prioritized - // blocks aren't enough, blocks from this list - // will be picked. - if (prefer_whole_pieces && !exclusive) - { - for (int j = 0; j < num_blocks_in_piece; ++j) - { - block_info const& info = p->info[j]; - if (info.state == block_info::state_finished - || info.state == block_info::state_writing) - continue; - if (info.state == block_info::state_requested - && info.peer == peer) continue; - backup_blocks.push_back(piece_block(*i, j)); - } - continue; - } - - for (int j = 0; j < num_blocks_in_piece; ++j) - { - // ignore completed blocks - block_info const& info = p->info[j]; - if (info.state == block_info::state_finished - || info.state == block_info::state_writing) - continue; - // ignore blocks requested from this peer already - if (info.state == block_info::state_requested - && info.peer == peer) - continue; - // if the piece is fast and the peer is slow, or vice versa, - // add the block as a backup. - // override this behavior if all the other blocks - // have been requested from the same peer or - // if the state of the piece is none (the - // piece will in that case change state). - if (p->state != none && p->state != speed - && !exclusive_active - && !ignore_speed_categories) - { - backup_blocks.push_back(piece_block(*i, j)); - continue; - } - // this block is interesting (we don't have it - // yet). But it may already have been requested - // from another peer. We have to add it anyway - // to allow the requester to determine if the - // block should be requested from more than one - // peer. If it is being downloaded, we continue - // to look for blocks until we have num_blocks - // blocks that have not been requested from any - // other peer. - if (p->info[j].state == block_info::state_none) - { - interesting_blocks.push_back(piece_block(*i, j)); - // we have found a block that's free to download - num_blocks--; - // if we prefer whole pieces, continue picking from this - // piece even though we have num_blocks - if (prefer_whole_pieces) continue; - assert(num_blocks >= 0); - if (num_blocks == 0) return num_blocks; - } - else - { - backup_blocks.push_back(piece_block(*i, j)); - } - } - assert(num_blocks >= 0 || prefer_whole_pieces); - if (num_blocks < 0) num_blocks = 0; - } - else - { - if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + if (num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; for (int j = 0; j < num_blocks_in_piece; ++j) interesting_blocks.push_back(piece_block(*i, j)); - num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); + num_blocks -= num_blocks_in_piece; + } + else + { + int start, end; + boost::tie(start, end) = expand_piece(*i, prefer_whole_pieces, pieces); + for (int k = start; k < end; ++k) + { + assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + num_blocks_in_piece = blocks_in_piece(k); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.push_back(piece_block(k, j)); + --num_blocks; + } + } + } + if (num_blocks <= 0) + { +#ifndef NDEBUG + verify_pick(interesting_blocks, pieces); +#endif + return 0; } - assert(num_blocks >= 0); - if (num_blocks == 0) return num_blocks; } +#ifndef NDEBUG + verify_pick(interesting_blocks, pieces); +#endif return num_blocks; } + int piece_picker::add_blocks_downloading(std::vector const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, int prefer_whole_pieces + , void* peer, piece_state_t speed, bool on_parole) const + { + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + + int num_blocks_in_piece = blocks_in_piece(i->index); + + // is true if all the other pieces that are currently + // requested from this piece are from the same + // peer as 'peer'. + bool exclusive; + bool exclusive_active; + boost::tie(exclusive, exclusive_active) + = requested_from(*i, num_blocks_in_piece, peer); + + // peers on parole are only allowed to pick blocks from + // pieces that only they have downloaded/requested from + if (on_parole && !exclusive) continue; + + if (prefer_whole_pieces > 0 && !exclusive_active) continue; + + // don't pick too many back-up blocks + if (i->state != none + && i->state != speed + && !exclusive_active + && int(backup_blocks.size()) >= num_blocks) + continue; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + // ignore completed blocks and already requested blocks + block_info const& info = i->info[j]; + if (info.state == block_info::state_finished + || info.state == block_info::state_writing + || info.state == block_info::state_requested) + continue; + + assert(i->info[j].state == block_info::state_none); + + // if the piece is fast and the peer is slow, or vice versa, + // add the block as a backup. + // override this behavior if all the other blocks + // have been requested from the same peer or + // if the state of the piece is none (the + // piece will in that case change state). + if (i->state != none && i->state != speed + && !exclusive_active) + { + backup_blocks.push_back(piece_block(i->index, j)); + continue; + } + + // this block is interesting (we don't have it + // yet). + interesting_blocks.push_back(piece_block(i->index, j)); + // we have found a block that's free to download + num_blocks--; + // if we prefer whole pieces, continue picking from this + // piece even though we have num_blocks + if (prefer_whole_pieces > 0) continue; + assert(num_blocks >= 0); + if (num_blocks <= 0) break; + } + if (num_blocks <= 0) break; + } + + assert(num_blocks >= 0 || prefer_whole_pieces > 0); + +#ifndef NDEBUG + verify_pick(interesting_blocks, pieces); + verify_pick(backup_blocks, pieces); +#endif + + if (num_blocks <= 0) return 0; + if (on_parole) return num_blocks; + + int to_copy; + if (prefer_whole_pieces == 0) + to_copy = (std::min)(int(backup_blocks.size()), num_blocks); + else + to_copy = int(backup_blocks.size()); + + interesting_blocks.insert(interesting_blocks.end() + , backup_blocks.begin(), backup_blocks.begin() + to_copy); + num_blocks -= to_copy; + backup_blocks.clear(); + + if (num_blocks <= 0) return 0; + + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + + int num_blocks_in_piece = blocks_in_piece(i->index); + + // fill in with blocks requested from other peers + // as backups + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = i->info[j]; + if (info.state != block_info::state_requested + || info.peer == peer) + continue; + backup_blocks.push_back(piece_block(i->index, j)); + } + } +#ifndef NDEBUG + verify_pick(backup_blocks, pieces); +#endif + return num_blocks; + } + + std::pair piece_picker::expand_piece(int piece, int whole_pieces + , std::vector const& have) const + { + if (whole_pieces == 0) return std::make_pair(piece, piece + 1); + + int start = piece - 1; + int lower_limit = piece - whole_pieces; + if (lower_limit < -1) lower_limit = -1; + while (start > lower_limit + && can_pick(start, have)) + --start; + ++start; + assert(start >= 0); + int end = piece + 1; + int upper_limit = start + whole_pieces; + if (upper_limit > int(m_piece_map.size())) upper_limit = int(m_piece_map.size()); + while (end < upper_limit + && can_pick(end, have)) + ++end; + return std::make_pair(start, end); + } + bool piece_picker::is_piece_finished(int index) const { assert(index < (int)m_piece_map.size()); @@ -1414,11 +1563,14 @@ namespace libtorrent assert(block.block_index >= 0); assert(block.piece_index < (int)m_piece_map.size()); assert(block.block_index < blocks_in_piece(block.piece_index)); + assert(!m_piece_map[block.piece_index].have()); piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { int prio = p.priority(m_sequenced_download_threshold); + assert(prio < int(m_piece_info.size())); + assert(prio > 0); p.downloading = 1; move(prio, p.index); @@ -1529,6 +1681,7 @@ namespace libtorrent assert(peer == 0); int prio = p.priority(m_sequenced_download_threshold); + assert(prio < int(m_piece_info.size())); p.downloading = 1; if (prio > 0) move(prio, p.index); else assert(p.priority(m_sequenced_download_threshold) == 0); @@ -1650,9 +1803,12 @@ namespace libtorrent { erase_download_piece(i); piece_pos& p = m_piece_map[block.piece_index]; - int prio = p.priority(m_sequenced_download_threshold); + int prev_prio = p.priority(m_sequenced_download_threshold); + assert(prev_prio < int(m_piece_info.size())); p.downloading = 0; - if (prio > 0) move(prio, p.index); + int prio = p.priority(m_sequenced_download_threshold); + if (prev_prio == 0 && prio > 0) add(block.piece_index); + else if (prio > 0) move(prio, p.index); assert(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 572f48d35..6e81da0d5 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -83,7 +83,7 @@ namespace // (and we should not consider it free). If the share diff is // negative, there's no free download to get from this peer. size_type diff = i->second->share_diff(); - assert(diff < std::numeric_limits::max()); + assert(diff < (std::numeric_limits::max)()); if (i->second->is_peer_interested() || diff <= 0) continue; @@ -110,7 +110,7 @@ namespace for (torrent::peer_iterator i = start; i != end; ++i) { size_type d = i->second->share_diff(); - assert(d < std::numeric_limits::max()); + assert(d < (std::numeric_limits::max)()); total_diff += d; if (!i->second->is_peer_interested() || i->second->share_diff() >= 0) continue; ++num_peers; @@ -120,7 +120,7 @@ namespace size_type upload_share; if (total_diff >= 0) { - upload_share = std::min(free_upload, total_diff) / num_peers; + upload_share = (std::min)(free_upload, total_diff) / num_peers; } else { @@ -187,17 +187,19 @@ namespace libtorrent // have only one piece that we don't have, and it's the // same piece for both peers. Then they might get into an // infinite loop, fighting to request the same blocks. - void request_a_block( - torrent& t - , peer_connection& c) + void request_a_block(torrent& t, peer_connection& c) { - assert(!t.is_seed()); - assert(!c.has_peer_choked()); + if (t.is_seed()) return; + + assert(t.valid_metadata()); assert(c.peer_info_struct() != 0 || !dynamic_cast(&c)); int num_requests = c.desired_queue_size() - (int)c.download_queue().size() - (int)c.request_queue().size(); +#ifdef TORRENT_VERBOSE_LOGGING + (*c.m_logger) << time_now_string() << " PIECE_PICKER [ req: " << num_requests << " ]\n"; +#endif assert(c.desired_queue_size() > 0); // if our request queue is already full, we // don't have to make any new requests yet @@ -207,16 +209,15 @@ namespace libtorrent std::vector interesting_pieces; interesting_pieces.reserve(100); - bool prefer_whole_pieces = c.prefer_whole_pieces() - || (c.peer_info_struct() && c.peer_info_struct()->on_parole); + int prefer_whole_pieces = c.prefer_whole_pieces(); bool rarest_first = t.num_pieces() >= t.settings().initial_picker_threshold; - if (!prefer_whole_pieces) + if (prefer_whole_pieces == 0) { prefer_whole_pieces = c.statistics().download_payload_rate() * t.settings().whole_pieces_threshold - > t.torrent_file().piece_length(); + > t.torrent_file().piece_length() ? 1 : 0; } // if we prefer whole pieces, the piece picker will pick at least @@ -231,18 +232,6 @@ namespace libtorrent else if (speed == peer_connection::medium) state = piece_picker::medium; else state = piece_picker::slow; - // picks the interesting pieces from this peer - // the integer is the number of pieces that - // should be guaranteed to be available for download - // (if num_requests is too big, too many pieces are - // picked and cpu-time is wasted) - // the last argument is if we should prefer whole pieces - // for this peer. If we're downloading one piece in 20 seconds - // then use this mode. - p.pick_pieces(c.get_bitfield(), interesting_pieces - , num_requests, prefer_whole_pieces, c.peer_info_struct() - , state, rarest_first); - // this vector is filled with the interesting pieces // that some other peer is currently downloading // we should then compare this peer's download speed @@ -251,14 +240,56 @@ namespace libtorrent std::vector busy_pieces; busy_pieces.reserve(num_requests); + std::vector const& suggested = c.suggested_pieces(); + std::vector const& bitfield = c.get_bitfield(); + + if (c.has_peer_choked()) + { + // if we are choked we can only pick pieces from the + // allowed fast set. The allowed fast set is sorted + // in ascending priority order + std::vector const& allowed_fast = c.allowed_fast(); + + // build a bitmask with only the allowed pieces in it + std::vector mask(c.get_bitfield().size(), false); + for (std::vector::const_iterator i = allowed_fast.begin() + , end(allowed_fast.end()); i != end; ++i) + if (bitfield[*i]) mask[*i] = true; + + p.pick_pieces(mask, interesting_pieces + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, rarest_first, c.on_parole(), suggested); + } + else + { + // picks the interesting pieces from this peer + // the integer is the number of pieces that + // should be guaranteed to be available for download + // (if num_requests is too big, too many pieces are + // picked and cpu-time is wasted) + // the last argument is if we should prefer whole pieces + // for this peer. If we're downloading one piece in 20 seconds + // then use this mode. + p.pick_pieces(bitfield, interesting_pieces + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, rarest_first, c.on_parole(), suggested); + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*c.m_logger) << time_now_string() << " PIECE_PICKER [ php: " << prefer_whole_pieces + << " picked: " << interesting_pieces.size() << " ]\n"; +#endif + std::deque const& dq = c.download_queue(); + std::deque const& rq = c.request_queue(); for (std::vector::iterator i = interesting_pieces.begin(); i != interesting_pieces.end(); ++i) { + if (prefer_whole_pieces == 0 && num_requests <= 0) break; + if (p.is_requested(*i)) { + if (num_requests <= 0) break; // don't request pieces we already have in our request queue - const std::deque& dq = c.download_queue(); - const std::deque& rq = c.request_queue(); if (std::find(dq.begin(), dq.end(), *i) != dq.end() || std::find(rq.begin(), rq.end(), *i) != rq.end()) continue; @@ -277,13 +308,13 @@ namespace libtorrent num_requests--; } - // in this case, we could not find any blocks - // that was free. If we couldn't find any busy - // blocks as well, we cannot download anything - // more from this peer. - - if (busy_pieces.empty() || num_requests == 0) + if (busy_pieces.empty() || num_requests <= 0) { + // in this case, we could not find any blocks + // that was free. If we couldn't find any busy + // blocks as well, we cannot download anything + // more from this peer. + c.send_block_requests(); return; } @@ -308,9 +339,8 @@ namespace libtorrent policy::policy(torrent* t) : m_torrent(t) - , m_num_unchoked(0) , m_available_free_upload(0) - , m_last_optimistic_disconnect(min_time()) +// , m_last_optimistic_disconnect(min_time()) { assert(t); } // disconnects and removes all peers that are now filtered @@ -352,7 +382,7 @@ namespace libtorrent m_peers.erase(i++); } } - +/* // finds the peer that has the worst download rate // and returns it. May return 0 if all peers are // choked. @@ -361,7 +391,7 @@ namespace libtorrent INVARIANT_CHECK; iterator worst_peer = m_peers.end(); - size_type min_weight = std::numeric_limits::min(); + size_type min_weight = (std::numeric_limits::min)(); #ifndef NDEBUG int unchoked_counter = m_num_unchoked; @@ -434,13 +464,13 @@ namespace libtorrent } return unchoke_peer; } - +*/ policy::iterator policy::find_disconnect_candidate() { INVARIANT_CHECK; iterator disconnect_peer = m_peers.end(); - double slowest_transfer_rate = std::numeric_limits::max(); + double slowest_transfer_rate = (std::numeric_limits::max)(); ptime now = time_now(); @@ -483,7 +513,8 @@ namespace libtorrent policy::iterator policy::find_connect_candidate() { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; ptime now = time_now(); ptime min_connect_time(now); @@ -491,6 +522,7 @@ namespace libtorrent int max_failcount = m_torrent->settings().max_failcount; int min_reconnect_time = m_torrent->settings().min_reconnect_time; + bool finished = m_torrent->is_finished(); aux::session_impl& ses = m_torrent->session(); @@ -499,7 +531,7 @@ namespace libtorrent if (i->connection) continue; if (i->banned) continue; if (i->type == peer::not_connectable) continue; - if (i->seed && m_torrent->is_seed()) continue; + if (i->seed && finished) continue; if (i->failcount >= max_failcount) continue; if (now - i->connected < seconds(i->failcount * min_reconnect_time)) continue; @@ -519,7 +551,7 @@ namespace libtorrent return candidate; } - +/* policy::iterator policy::find_seed_choke_candidate() { INVARIANT_CHECK; @@ -625,7 +657,7 @@ namespace libtorrent --m_num_unchoked; } } - +*/ void policy::pulse() { INVARIANT_CHECK; @@ -657,7 +689,7 @@ namespace libtorrent // ------------------------------------- // maintain the number of connections // ------------------------------------- - +/* // count the number of connected peers except for peers // that are currently in the process of disconnecting int num_connected_peers = 0; @@ -669,10 +701,9 @@ namespace libtorrent ++num_connected_peers; } - if (m_torrent->m_connections_quota.given != std::numeric_limits::max()) + if (m_torrent->max_connections() != (std::numeric_limits::max)()) { - - int max_connections = m_torrent->m_connections_quota.given; + int max_connections = m_torrent->max_connections(); if (num_connected_peers >= max_connections) { @@ -700,7 +731,7 @@ namespace libtorrent --num_connected_peers; } } - +*/ // ------------------------ // upload shift // ------------------------ @@ -731,7 +762,7 @@ namespace libtorrent , m_torrent->end() , m_available_free_upload); } - +/* // ------------------------ // seed choking policy // ------------------------ @@ -847,6 +878,7 @@ namespace libtorrent while (m_num_unchoked < m_torrent->m_uploads_quota.given && unchoke_one_peer()); } +*/ } int policy::count_choked() const @@ -879,7 +911,8 @@ namespace libtorrent // override at a time assert(c.remote() == c.get_socket()->remote_endpoint()); - if (m_torrent->num_peers() >= m_torrent->m_connections_quota.given + if (m_torrent->num_peers() >= m_torrent->max_connections() + && m_torrent->session().num_connections() >= m_torrent->session().max_connections() && c.remote().address() != m_torrent->current_tracker().address()) { throw protocol_error("too many connections, refusing incoming connection"); // cause a disconnect @@ -961,16 +994,17 @@ namespace libtorrent i->connection = &c; assert(i->connection); i->connected = time_now(); - m_last_optimistic_disconnect = time_now(); +// m_last_optimistic_disconnect = time_now(); } void policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int src, char flags) { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; // just ignore the obviously invalid entries - if(remote.address() == address() || remote.port() == 0) + if (remote.address() == address() || remote.port() == 0) return; aux::session_impl& ses = m_torrent->session(); @@ -1056,7 +1090,10 @@ namespace libtorrent if (i->failcount > 0 && src != peer_info::dht) --i->failcount; - if (flags & 0x02) i->seed = true; + // if we're connected to this peer + // we already know if it's a seed or not + // so we don't have to trust this source + if ((flags & 0x02) && !i->connection) i->seed = true; if (i->connection) { @@ -1146,14 +1183,38 @@ namespace libtorrent // In that case we don't care if people are leeching, they // can't pay for their downloads anyway. if (c.is_choked() - && m_num_unchoked < m_torrent->m_uploads_quota.given + && m_torrent->session().num_uploads() < m_torrent->session().max_uploads() && (m_torrent->ratio() == 0 || c.share_diff() >= -free_upload_amount - || m_torrent->is_seed())) + || m_torrent->is_finished())) { - c.send_unchoke(); - ++m_num_unchoked; + m_torrent->session().unchoke_peer(c); } +#if defined(TORRENT_VERBOSE_LOGGING) + else if (c.is_choked()) + { + std::string reason; + if (m_torrent->session().num_uploads() >= m_torrent->session().max_uploads()) + { + reason = "the number of uploads (" + + boost::lexical_cast(m_torrent->session().num_uploads()) + + ") is more than or equal to the limit (" + + boost::lexical_cast(m_torrent->session().max_uploads()) + + ")"; + } + else + { + reason = "the share ratio (" + + boost::lexical_cast(c.share_diff()) + + ") is <= free_upload_amount (" + + boost::lexical_cast(int(free_upload_amount)) + + ") and we are not seeding and the ratio (" + + boost::lexical_cast(m_torrent->ratio()) + + ")is non-zero"; + } + (*c.m_logger) << time_now_string() << " DID NOT UNCHOKE [ " << reason << " ]\n"; + } +#endif } // called when a peer is no longer interested in us @@ -1163,7 +1224,7 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { - assert(c.share_diff() < std::numeric_limits::max()); + assert(c.share_diff() < (std::numeric_limits::max)()); size_type diff = c.share_diff(); if (diff > 0 && c.is_seed()) { @@ -1185,7 +1246,7 @@ namespace libtorrent } */ } - +/* bool policy::unchoke_one_peer() { INVARIANT_CHECK; @@ -1214,7 +1275,7 @@ namespace libtorrent p->connection->send_choke(); --m_num_unchoked; } - +*/ bool policy::connect_one_peer() { INVARIANT_CHECK; @@ -1230,7 +1291,7 @@ namespace libtorrent try { - p->connected = m_last_optimistic_disconnect = time_now(); + p->connected = time_now(); p->connection = m_torrent->connect_to_peer(&*p); if (p->connection == 0) return false; p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); @@ -1263,33 +1324,33 @@ namespace libtorrent } // this is called whenever a peer connection is closed - void policy::connection_closed(const peer_connection& c) try + void policy::connection_closed(const peer_connection& c) throw() { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; -// assert(c.is_disconnecting()); - bool unchoked = false; + peer* p = c.peer_info_struct(); - iterator i = std::find_if( + assert((std::find_if( m_peers.begin() , m_peers.end() - , match_peer_connection(c)); - + , match_peer_connection(c)) + != m_peers.end()) == (p != 0)); + // if we couldn't find the connection in our list, just ignore it. - if (i == m_peers.end()) return; - assert(i->connection == &c); - i->connection = 0; + if (p == 0) return; - i->connected = time_now(); - if (!c.is_choked() && !m_torrent->is_aborted()) - { - unchoked = true; - } + assert(p->connection == &c); + + p->connection = 0; + p->optimistically_unchoked = false; + + p->connected = time_now(); if (c.failed()) { - ++i->failcount; -// i->connected = time_now(); + ++p->failcount; +// p->connected = time_now(); } // if the share ratio is 0 (infinite), the @@ -1298,28 +1359,11 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { assert(c.associated_torrent().lock().get() == m_torrent); - assert(c.share_diff() < std::numeric_limits::max()); + assert(c.share_diff() < (std::numeric_limits::max)()); m_available_free_upload += c.share_diff(); } - i->prev_amount_download += c.statistics().total_payload_download(); - i->prev_amount_upload += c.statistics().total_payload_upload(); - - if (unchoked) - { - // if the peer that is diconnecting is unchoked - // then unchoke another peer in order to maintain - // the total number of unchoked peers - --m_num_unchoked; - if (m_torrent->is_seed()) seed_unchoke_one_peer(); - else unchoke_one_peer(); - } - } - catch (std::exception& e) - { -#ifndef NDEBUG - std::string err = e.what(); -#endif - assert(false); + p->prev_amount_download += c.statistics().total_payload_download(); + p->prev_amount_upload += c.statistics().total_payload_upload(); } void policy::peer_is_interesting(peer_connection& c) @@ -1327,17 +1371,21 @@ namespace libtorrent INVARIANT_CHECK; c.send_interested(); - if (c.has_peer_choked()) return; + if (c.has_peer_choked() + && c.allowed_fast().empty()) + return; request_a_block(*m_torrent, c); } #ifndef NDEBUG bool policy::has_connection(const peer_connection* c) { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; assert(c); - assert(c->remote() == c->get_socket()->remote_endpoint()); + try { assert(c->remote() == c->get_socket()->remote_endpoint()); } + catch (std::exception&) {} return std::find_if( m_peers.begin() @@ -1348,12 +1396,11 @@ namespace libtorrent void policy::check_invariant() const { if (m_torrent->is_aborted()) return; - int actual_unchoked = 0; int connected_peers = 0; int total_connections = 0; int nonempty_connections = 0; - + std::set
unique_test; for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) @@ -1372,15 +1419,17 @@ namespace libtorrent , boost::bind(std::equal_to(), _1, p.connection)) != conns.end()); } + if (p.optimistically_unchoked) + { + assert(p.connection); + assert(!p.connection->is_choked()); + } assert(p.connection->peer_info_struct() == 0 || p.connection->peer_info_struct() == &p); ++nonempty_connections; if (!p.connection->is_disconnecting()) ++connected_peers; - if (!p.connection->is_choked()) ++actual_unchoked; } -// assert(actual_unchoked <= m_torrent->m_uploads_quota.given); - assert(actual_unchoked == m_num_unchoked); int num_torrent_peers = 0; for (torrent::const_peer_iterator i = m_torrent->begin(); @@ -1447,6 +1496,7 @@ namespace libtorrent , failcount(0) , hashfails(0) , seed(false) + , optimistically_unchoked(false) , last_optimistically_unchoked(min_time()) , connected(min_time()) , trust_points(0) diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 485c90d62..1d7a070c2 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -68,7 +68,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" @@ -180,11 +179,26 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size + , bool paused , storage_constructor_type sc) { + assert(!ti.m_half_metadata); + boost::intrusive_ptr tip(new torrent_info(ti)); + return m_impl->add_torrent(tip, save_path, resume_data + , compact_mode, sc, paused); + } + + torrent_handle session::add_torrent( + boost::intrusive_ptr ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , bool paused + , storage_constructor_type sc) + { + assert(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data - , compact_mode, block_size, sc); + , compact_mode, sc, paused); } torrent_handle session::add_torrent( @@ -194,11 +208,11 @@ namespace libtorrent , fs::path const& save_path , entry const& e , bool compact_mode - , int block_size + , bool paused , storage_constructor_type sc) { return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e - , compact_mode, block_size, sc); + , compact_mode, sc, paused); } void session::remove_torrent(const torrent_handle& h) @@ -337,6 +351,11 @@ namespace libtorrent m_impl->set_max_connections(limit); } + int session::max_half_open_connections() const + { + return m_impl->max_half_open_connections(); + } + void session::set_max_half_open_connections(int limit) { m_impl->set_max_half_open_connections(limit); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 81f48a3ce..0aeb84bbe 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -68,7 +68,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" @@ -223,6 +222,12 @@ namespace detail if (!m_ses.is_aborted()) { m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr)); + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(torrent_checked_alert( + processing->torrent_ptr->get_handle() + , "torrent finished checking")); + } if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) { m_ses.m_alerts.post_alert(torrent_finished_alert( @@ -345,6 +350,12 @@ namespace detail processing->torrent_ptr->files_checked(processing->unfinished_pieces); m_ses.m_torrents.insert(std::make_pair( processing->info_hash, processing->torrent_ptr)); + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(torrent_checked_alert( + processing->torrent_ptr->get_handle() + , "torrent finished checking")); + } if (processing->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) { @@ -505,8 +516,12 @@ namespace detail , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) , m_external_listen_port(0) , m_abort(false) - , m_max_uploads(-1) - , m_max_connections(-1) + , m_max_uploads(8) + , m_max_connections(200) + , m_num_unchoked(0) + , m_unchoke_time_scaler(0) + , m_optimistic_unchoke_time_scaler(0) + , m_disconnect_time_scaler(0) , m_incoming_connection(false) , m_last_tick(time_now()) #ifndef TORRENT_DISABLE_DHT @@ -517,6 +532,11 @@ namespace detail , m_next_connect_torrent(0) , m_checker_impl(*this) { +#ifdef WIN32 + // windows XP has a limit of 10 simultaneous connections + m_half_open.limit(8); +#endif + m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; m_bandwidth_manager[peer_connection::upload_channel] = &m_upload_channel; @@ -609,6 +629,9 @@ namespace detail void session_impl::set_ip_filter(ip_filter const& f) { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_ip_filter = f; // Close connections whose endpoint is filtered @@ -621,6 +644,9 @@ namespace detail void session_impl::set_settings(session_settings const& s) { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + assert(s.connection_speed > 0); assert(s.file_pool_size > 0); @@ -640,22 +666,25 @@ namespace detail try { // create listener socket - m_listen_socket = boost::shared_ptr(new socket_acceptor(m_io_service)); + m_listen_socket.reset(new socket_acceptor(m_io_service)); for(;;) { try { m_listen_socket->open(m_listen_interface.protocol()); + m_listen_socket->set_option(socket_acceptor::reuse_address(true)); m_listen_socket->bind(m_listen_interface); m_listen_socket->listen(); + m_listen_interface = m_listen_socket->local_endpoint(); m_external_listen_port = m_listen_interface.port(); break; } catch (asio::system_error& e) { // TODO: make sure this is correct - if (e.code() == asio::error::host_not_found) + if (e.code() == asio::error::host_not_found + || m_listen_interface.port() == 0) { if (m_alerts.should_post(alert::fatal)) { @@ -700,14 +729,18 @@ namespace detail } } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (m_listen_socket) { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << "listening on port: " << m_listen_interface.port() << " external port: " << m_external_listen_port << "\n"; - } #endif - if (m_listen_socket) async_accept(); + async_accept(); + if (m_natpmp.get()) + m_natpmp->set_mappings(m_listen_interface.port(), 0); + if (m_upnp.get()) + m_upnp->set_mappings(m_listen_interface.port(), 0); + } } void session_impl::async_accept() @@ -733,7 +766,6 @@ namespace detail if (m_abort) return; - async_accept(); if (e) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -742,8 +774,12 @@ namespace detail (*m_logger) << msg << "\n"; #endif assert(m_listen_socket.unique()); + // try any random port + m_listen_interface.port(0); + open_listen_port(); return; } + async_accept(); // we got a connection request! m_incoming_connection = true; @@ -765,8 +801,30 @@ namespace detail return; } + // check if we have any active torrents + // if we don't reject the connection + if (m_torrents.empty()) + { + return; + } + else + { + bool has_active_torrent = false; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + if (!i->second->is_paused()) + { + has_active_torrent = true; + break; + } + } + if (!has_active_torrent) + return; + } + boost::intrusive_ptr c( - new bt_peer_connection(*this, s, 0)); + new bt_peer_connection(*this, s, 0)); #ifndef NDEBUG c->m_in_constructor = false; #endif @@ -787,6 +845,9 @@ namespace detail #endif { mutex_t::scoped_lock l(m_mutex); + +// too expensive +// INVARIANT_CHECK; connection_map::iterator p = m_connections.find(s); @@ -818,10 +879,16 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); +// too expensive +// INVARIANT_CHECK; + assert(p->is_disconnecting()); connection_map::iterator i = m_connections.find(p->get_socket()); if (i != m_connections.end()) + { + if (!i->second->is_choked()) --m_num_unchoked; m_connections.erase(i); + } } void session_impl::set_peer_id(peer_id const& id) @@ -840,6 +907,8 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + if (e) { #if defined(TORRENT_LOGGING) @@ -901,7 +970,9 @@ namespace detail // round robin fashion, so that every torrent is // equallt likely to connect to a peer - if (!m_torrents.empty() && m_half_open.free_slots()) + if (!m_torrents.empty() + && m_half_open.free_slots() + && num_connections() < m_max_connections) { // this is the maximum number of connections we will // attempt this tick @@ -918,11 +989,13 @@ namespace detail { torrent& t = *i->second; if (t.want_more_peers()) + { if (t.try_connect_peer()) { --max_connections; steps_since_last_connect = 0; } + } ++m_next_connect_torrent; ++steps_since_last_connect; ++i; @@ -940,6 +1013,8 @@ namespace detail // if we should not make any more connections // attempts this tick, abort if (max_connections == 0) break; + // maintain the global limit on number of connections + if (num_connections() >= m_max_connections) break; } } @@ -976,18 +1051,7 @@ namespace detail continue; } - try - { - c.keep_alive(); - } - catch (std::exception& exc) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*c.m_logger) << "**ERROR**: " << exc.what() << "\n"; -#endif - c.set_failed(); - c.disconnect(); - } + c.keep_alive(); } // check each torrent for tracker updates @@ -1019,30 +1083,183 @@ namespace detail } m_stat.second_tick(tick_interval); - // distribute the maximum upload rate among the torrents - assert(m_max_uploads >= -1); - assert(m_max_connections >= -1); - - allocate_resources(m_max_uploads == -1 - ? std::numeric_limits::max() - : m_max_uploads - , m_torrents - , &torrent::m_uploads_quota); - - allocate_resources(m_max_connections == -1 - ? std::numeric_limits::max() - : m_max_connections - , m_torrents - , &torrent::m_connections_quota); - - for (std::map >::iterator i - = m_torrents.begin(); i != m_torrents.end(); ++i) + // -------------------------------------------------------------- + // unchoke set and optimistic unchoke calculations + // -------------------------------------------------------------- + m_unchoke_time_scaler--; + if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) { -#ifndef NDEBUG - i->second->check_invariant(); -#endif - i->second->distribute_resources(tick_interval); + m_unchoke_time_scaler = settings().unchoke_interval; + + std::vector peers; + for (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = i->second.get(); + torrent* t = p->associated_torrent().lock().get(); + if (!p->peer_info_struct() + || t == 0 + || !p->is_peer_interested() + || p->is_disconnecting() + || p->is_connecting() + || (p->share_diff() < -free_upload_amount + && !t->is_seed())) + { + if (!i->second->is_choked() && t) + { + policy::peer* pi = p->peer_info_struct(); + if (pi && pi->optimistically_unchoked) + { + pi->optimistically_unchoked = false; + // force a new optimistic unchoke + m_optimistic_unchoke_time_scaler = 0; + } + t->choke_peer(*i->second); + } + continue; + } + peers.push_back(i->second.get()); + } + + // sort the peers that are eligible for unchoke by download rate and secondary + // by total upload. The reason for this is, if all torrents are being seeded, + // the download rate will be 0, and the peers we have sent the least to should + // be unchoked + std::sort(peers.begin(), peers.end() + , bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _1)) + < bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _2))); + + std::stable_sort(peers.begin(), peers.end() + , bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _1)) + > bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _2))); + + // reserve one upload slot for optimistic unchokes + int unchoke_set_size = m_max_uploads - 1; + + m_num_unchoked = 0; + // go through all the peers and unchoke the first ones and choke + // all the other ones. + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection* p = *i; + assert(p); + torrent* t = p->associated_torrent().lock().get(); + assert(t); + if (unchoke_set_size > 0) + { + if (p->is_choked()) + { + if (!t->unchoke_peer(*p)) + continue; + } + + --unchoke_set_size; + ++m_num_unchoked; + + assert(p->peer_info_struct()); + if (p->peer_info_struct()->optimistically_unchoked) + { + // force a new optimistic unchoke + m_optimistic_unchoke_time_scaler = 0; + p->peer_info_struct()->optimistically_unchoked = false; + } + } + else + { + assert(p->peer_info_struct()); + if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) + t->choke_peer(*p); + if (!p->is_choked()) + ++m_num_unchoked; + } + } + + m_optimistic_unchoke_time_scaler--; + if (m_optimistic_unchoke_time_scaler <= 0) + { + m_optimistic_unchoke_time_scaler + = settings().optimistic_unchoke_multiplier; + + // find the peer that has been waiting the longest to be optimistically + // unchoked + connection_map::iterator current_optimistic_unchoke = m_connections.end(); + connection_map::iterator optimistic_unchoke_candidate = m_connections.end(); + ptime last_unchoke = max_time(); + + for (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = i->second.get(); + assert(p); + policy::peer* pi = p->peer_info_struct(); + if (!pi) continue; + torrent* t = p->associated_torrent().lock().get(); + if (!t) continue; + + if (pi->optimistically_unchoked) + { + assert(!p->is_choked()); + assert(current_optimistic_unchoke == m_connections.end()); + current_optimistic_unchoke = i; + } + + if (pi->last_optimistically_unchoked < last_unchoke + && !p->is_connecting() + && !p->is_disconnecting() + && p->is_peer_interested() + && t->free_upload_slots() + && p->is_choked()) + { + last_unchoke = pi->last_optimistically_unchoked; + optimistic_unchoke_candidate = i; + } + } + + if (optimistic_unchoke_candidate != m_connections.end() + && optimistic_unchoke_candidate != current_optimistic_unchoke) + { + if (current_optimistic_unchoke != m_connections.end()) + { + torrent* t = current_optimistic_unchoke->second->associated_torrent().lock().get(); + assert(t); + current_optimistic_unchoke->second->peer_info_struct()->optimistically_unchoked = false; + t->choke_peer(*current_optimistic_unchoke->second); + } + else + { + ++m_num_unchoked; + } + + torrent* t = optimistic_unchoke_candidate->second->associated_torrent().lock().get(); + assert(t); + bool ret = t->unchoke_peer(*optimistic_unchoke_candidate->second); + assert(ret); + optimistic_unchoke_candidate->second->peer_info_struct()->optimistically_unchoked = true; + } + } + } + + // -------------------------------------------------------------- + // disconnect peers when we have too many + // -------------------------------------------------------------- + --m_disconnect_time_scaler; + if (m_disconnect_time_scaler <= 0) + { + m_disconnect_time_scaler = 60; + + // every 60 seconds, disconnect the worst peer + // if we have reached the connection limit + if (num_connections() >= max_connections() && !m_torrents.empty()) + { + torrent_map::iterator i = std::max_element(m_torrents.begin(), m_torrents.end() + , bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _1)) + < bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _2))); + + assert(i != m_torrents.end()); + i->second->get_policy().disconnect_one_peer(); + } } } catch (std::exception& exc) @@ -1052,29 +1269,7 @@ namespace detail assert(false); #endif }; // msvc 7.1 seems to require this -/* - void session_impl::connection_completed( - boost::intrusive_ptr const& p) try - { - mutex_t::scoped_lock l(m_mutex); - connection_map::iterator i = m_half_open.find(p->get_socket()); - m_connections.insert(std::make_pair(p->get_socket(), p)); - assert(i != m_half_open.end()); - if (i != m_half_open.end()) m_half_open.erase(i); - - if (m_abort) return; - - process_connection_queue(); - } - catch (std::exception& e) - { -#ifndef NDEBUG - std::cerr << e.what() << std::endl; - assert(false); -#endif - }; -*/ void session_impl::operator()() { eh_initializer(); @@ -1083,10 +1278,6 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); open_listen_port(); - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); } ptime timer = time_now(); @@ -1156,6 +1347,9 @@ namespace detail } } + // close listen socket + m_listen_socket.reset(); + ptime start(time_now()); l.unlock(); @@ -1275,47 +1469,36 @@ namespace detail } torrent_handle session_impl::add_torrent( - torrent_info const& ti + boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size - , storage_constructor_type sc) + , storage_constructor_type sc + , bool paused) { // if you get this assert, you haven't managed to // open a listen port. call listen_on() first. assert(m_external_listen_port > 0); - - // make sure the block_size is an even power of 2 -#ifndef NDEBUG - for (int i = 0; i < 32; ++i) - { - if (block_size & (1 << i)) - { - assert((block_size & ~(1 << i)) == 0); - break; - } - } -#endif - assert(!save_path.empty()); - if (ti.begin_files() == ti.end_files()) + if (ti->begin_files() == ti->end_files()) throw std::runtime_error("no files in torrent"); // lock the session and the checker thread (the order is important!) mutex_t::scoped_lock l(m_mutex); mutex::scoped_lock l2(m_checker_impl.m_mutex); + INVARIANT_CHECK; + if (is_aborted()) throw std::runtime_error("session is closing"); // is the torrent already active? - if (!find_torrent(ti.info_hash()).expired()) + if (!find_torrent(ti->info_hash()).expired()) throw duplicate_torrent(); // is the torrent currently being checked? - if (m_checker_impl.find_torrent(ti.info_hash())) + if (m_checker_impl.find_torrent(ti->info_hash())) throw duplicate_torrent(); // create the torrent and the data associated with @@ -1323,8 +1506,8 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, ti, save_path - , m_listen_interface, compact_mode, block_size - , settings(), sc)); + , m_listen_interface, compact_mode, 16 * 1024 + , sc, paused)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1340,13 +1523,13 @@ namespace detail new aux::piece_checker_data); d->torrent_ptr = torrent_ptr; d->save_path = save_path; - d->info_hash = ti.info_hash(); + d->info_hash = ti->info_hash(); d->resume_data = resume_data; #ifndef TORRENT_DISABLE_DHT if (m_dht) { - torrent_info::nodes_t const& nodes = ti.nodes(); + torrent_info::nodes_t const& nodes = ti->nodes(); std::for_each(nodes.begin(), nodes.end(), bind( (void(dht::dht_tracker::*)(std::pair const&)) &dht::dht_tracker::add_node @@ -1360,7 +1543,7 @@ namespace detail // job in its queue m_checker_impl.m_cond.notify_one(); - return torrent_handle(this, &m_checker_impl, ti.info_hash()); + return torrent_handle(this, &m_checker_impl, ti->info_hash()); } torrent_handle session_impl::add_torrent( @@ -1370,20 +1553,9 @@ namespace detail , fs::path const& save_path , entry const& , bool compact_mode - , int block_size - , storage_constructor_type sc) + , storage_constructor_type sc + , bool paused) { - // make sure the block_size is an even power of 2 -#ifndef NDEBUG - for (int i = 0; i < 32; ++i) - { - if (block_size & (1 << i)) - { - assert((block_size & ~(1 << i)) == 0); - break; - } - } -#endif // TODO: support resume data in this case assert(!save_path.empty()); @@ -1399,6 +1571,8 @@ namespace detail // lock the session session_impl::mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + // is the torrent already active? if (!find_torrent(info_hash).expired()) throw duplicate_torrent(); @@ -1411,8 +1585,8 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, tracker_url, info_hash, name - , save_path, m_listen_interface, compact_mode, block_size - , settings(), sc)); + , save_path, m_listen_interface, compact_mode, 16 * 1024 + , sc, paused)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1437,6 +1611,9 @@ namespace detail assert(h.m_ses != 0); mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + session_impl::torrent_map::iterator i = m_torrents.find(h.m_info_hash); if (i != m_torrents.end()) @@ -1499,6 +1676,8 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + tcp::endpoint new_interface; if (net_interface && std::strlen(net_interface) > 0) new_interface = tcp::endpoint(address::from_string(net_interface), port_range.first); @@ -1522,21 +1701,6 @@ namespace detail bool new_listen_address = m_listen_interface.address() != new_interface.address(); - if (new_listen_address) - { - if (m_natpmp.get()) - m_natpmp->rebind(new_interface.address()); - if (m_upnp.get()) - m_upnp->rebind(new_interface.address()); - if (m_lsd.get()) - m_lsd->rebind(new_interface.address()); - } - - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); - #ifndef TORRENT_DISABLE_DHT if ((new_listen_address || m_dht_same_port) && m_dht) { @@ -1578,6 +1742,8 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + boost::shared_ptr t = find_torrent(ih).lock(); if (!t) return; // don't add peers from lsd to private torrents @@ -1632,6 +1798,9 @@ namespace detail session_status session_impl::status() const { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + session_status s; s.has_incoming_connections = m_incoming_connection; s.num_peers = (int)m_connections.size(); @@ -1673,6 +1842,9 @@ namespace detail void session_impl::start_dht(entry const& startup_state) { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + if (m_dht) { m_dht->stop(); @@ -1832,6 +2004,10 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_uploads = limit; } @@ -1839,6 +2015,10 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_connections = limit; } @@ -1846,7 +2026,10 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); - + + INVARIANT_CHECK; + + if (limit <= 0) limit = (std::numeric_limits::max)(); m_half_open.limit(limit); } @@ -1854,7 +2037,10 @@ namespace detail { assert(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); - if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; + + INVARIANT_CHECK; + + if (bytes_per_second <= 0) bytes_per_second = bandwidth_limit::inf; m_bandwidth_manager[peer_connection::download_channel]->throttle(bytes_per_second); } @@ -1862,31 +2048,20 @@ namespace detail { assert(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); - if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; + + INVARIANT_CHECK; + + if (bytes_per_second <= 0) bytes_per_second = bandwidth_limit::inf; m_bandwidth_manager[peer_connection::upload_channel]->throttle(bytes_per_second); } - int session_impl::num_uploads() const - { - int uploads = 0; - mutex_t::scoped_lock l(m_mutex); - for (torrent_map::const_iterator i = m_torrents.begin() - , end(m_torrents.end()); i != end; i++) - { - uploads += i->second->get_policy().num_uploads(); - } - return uploads; - } - - int session_impl::num_connections() const - { - mutex_t::scoped_lock l(m_mutex); - return m_connections.size(); - } - std::auto_ptr session_impl::pop_alert() { mutex_t::scoped_lock l(m_mutex); + +// too expensive +// INVARIANT_CHECK; + if (m_alerts.pending()) return m_alerts.get(); return std::auto_ptr(0); @@ -1901,20 +2076,26 @@ namespace detail int session_impl::upload_rate_limit() const { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + int ret = m_bandwidth_manager[peer_connection::upload_channel]->throttle(); - return ret == std::numeric_limits::max() ? -1 : ret; + return ret == (std::numeric_limits::max)() ? -1 : ret; } int session_impl::download_rate_limit() const { mutex_t::scoped_lock l(m_mutex); int ret = m_bandwidth_manager[peer_connection::download_channel]->throttle(); - return ret == std::numeric_limits::max() ? -1 : ret; + return ret == (std::numeric_limits::max)() ? -1 : ret; } void session_impl::start_lsd() { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_lsd.reset(new lsd(m_io_service , m_listen_interface.address() , bind(&session_impl::on_lsd_peer, this, _1, _2))); @@ -1923,6 +2104,9 @@ namespace detail void session_impl::start_natpmp() { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_natpmp.reset(new natpmp(m_io_service , m_listen_interface.address() , bind(&session_impl::on_port_mapping @@ -1938,6 +2122,9 @@ namespace detail void session_impl::start_upnp() { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_upnp.reset(new upnp(m_io_service, m_half_open , m_listen_interface.address() , m_settings.user_agent @@ -1975,20 +2162,35 @@ namespace detail #ifndef NDEBUG - void session_impl::check_invariant(const char *place) + void session_impl::check_invariant() const { - assert(place); - for (connection_map::iterator i = m_connections.begin(); + assert(m_max_connections > 0); + assert(m_max_uploads > 0); + int unchokes = 0; + int num_optimistic = 0; + for (connection_map::const_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { assert(i->second); boost::shared_ptr t = i->second->associated_torrent().lock(); - if (t) + if (!i->second->is_choked()) ++unchokes; + if (i->second->peer_info_struct() + && i->second->peer_info_struct()->optimistically_unchoked) + { + ++num_optimistic; + assert(!i->second->is_choked()); + } + if (t && i->second->peer_info_struct()) { assert(t->get_policy().has_connection(boost::get_pointer(i->second))); } } + assert(num_optimistic == 0 || num_optimistic == 1); + if (m_num_unchoked != unchokes) + { + assert(false); + } } #endif @@ -2101,7 +2303,7 @@ namespace detail const std::string& bitmask = (*i)["bitmask"].string(); - const int num_bitmask_bytes = std::max(num_blocks_per_piece / 8, 1); + const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); if ((int)bitmask.size() != num_bitmask_bytes) { error = "invalid size of bitmask (" + boost::lexical_cast(bitmask.size()) + ")"; @@ -2110,7 +2312,7 @@ namespace detail for (int j = 0; j < num_bitmask_bytes; ++j) { unsigned char bits = bitmask[j]; - int num_bits = std::min(num_blocks_per_piece - j*8, 8); + int num_bits = (std::min)(num_blocks_per_piece - j*8, 8); for (int k = 0; k < num_bits; ++k) { const int bit = j * 8 + k; diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp index b1679c4ac..a6b5544e4 100644 --- a/libtorrent/src/socks5_stream.cpp +++ b/libtorrent/src/socks5_stream.cpp @@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include "libtorrent/socks5_stream.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index b23a2e858..e1ddd20ae 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -88,6 +88,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif +#if defined(__FreeBSD__) +// for statfs() +#include +#include +#endif + #if defined(_WIN32) && defined(UNICODE) #include @@ -247,8 +253,8 @@ namespace libtorrent { p = complete(p); std::vector > sizes; - for (torrent_info::file_iterator i = t.begin_files(); - i != t.end_files(); ++i) + for (torrent_info::file_iterator i = t.begin_files(true); + i != t.end_files(true); ++i) { size_type size = 0; std::time_t time = 0; @@ -287,7 +293,7 @@ namespace libtorrent , bool compact_mode , std::string* error) { - if ((int)sizes.size() != t.num_files()) + if ((int)sizes.size() != t.num_files(true)) { if (error) *error = "mismatching number of files"; return false; @@ -296,8 +302,8 @@ namespace libtorrent std::vector >::const_iterator s = sizes.begin(); - for (torrent_info::file_iterator i = t.begin_files(); - i != t.end_files(); ++i, ++s) + for (torrent_info::file_iterator i = t.begin_files(true); + i != t.end_files(true); ++i, ++s) { size_type size = 0; std::time_t time = 0; @@ -342,50 +348,14 @@ namespace libtorrent return true; } - struct thread_safe_storage - { - thread_safe_storage(std::size_t n) - : slots(n, false) - {} - - boost::mutex mutex; - boost::condition condition; - std::vector slots; - }; - - struct slot_lock - { - slot_lock(thread_safe_storage& s, int slot_) - : storage_(s) - , slot(slot_) - { - assert(slot_>=0 && slot_ < (int)s.slots.size()); - boost::mutex::scoped_lock lock(storage_.mutex); - - while (storage_.slots[slot]) - storage_.condition.wait(lock); - storage_.slots[slot] = true; - } - - ~slot_lock() - { - storage_.slots[slot] = false; - storage_.condition.notify_all(); - } - - thread_safe_storage& storage_; - int slot; - }; - - class storage : public storage_interface, thread_safe_storage, boost::noncopyable + class storage : public storage_interface, boost::noncopyable { public: - storage(torrent_info const& info, fs::path const& path, file_pool& fp) - : thread_safe_storage(info.num_pieces()) - , m_info(info) + storage(boost::intrusive_ptr info, fs::path const& path, file_pool& fp) + : m_info(info) , m_files(fp) { - assert(info.begin_files() != info.end_files()); + assert(info->begin_files(true) != info->end_files(true)); m_save_path = fs::complete(path); assert(m_save_path.is_complete()); } @@ -405,11 +375,9 @@ namespace libtorrent size_type read_impl(char* buf, int slot, int offset, int size, bool fill_zero); ~storage() - { - m_files.release(this); - } + { m_files.release(this); } - torrent_info const& m_info; + boost::intrusive_ptr m_info; fs::path m_save_path; // the file pool is typically stored in // the session, to make all storage @@ -448,8 +416,8 @@ namespace libtorrent { // first, create all missing directories fs::path last_path; - for (torrent_info::file_iterator file_iter = m_info.begin_files(), - end_iter = m_info.end_files(); file_iter != end_iter; ++file_iter) + for (torrent_info::file_iterator file_iter = m_info->begin_files(true), + end_iter = m_info->end_files(true); file_iter != end_iter; ++file_iter) { fs::path dir = (m_save_path / file_iter->path).branch_path(); @@ -497,7 +465,7 @@ namespace libtorrent void storage::write_resume_data(entry& rd) const { std::vector > file_sizes - = get_filesizes(m_info, m_save_path); + = get_filesizes(*m_info, m_save_path); rd["file sizes"] = entry::list_type(); entry::list_type& fl = rd["file sizes"].list(); @@ -531,7 +499,7 @@ namespace libtorrent } entry::list_type& slots = rd["slots"].list(); - bool seed = int(slots.size()) == m_info.num_pieces() + bool seed = int(slots.size()) == m_info->num_pieces() && std::find_if(slots.begin(), slots.end() , boost::bind(std::less() , boost::bind((size_type const& (entry::*)() const) @@ -546,11 +514,11 @@ namespace libtorrent if (seed) { - if (m_info.num_files() != (int)file_sizes.size()) + if (m_info->num_files(true) != (int)file_sizes.size()) { error = "the number of files does not match the torrent (num: " + boost::lexical_cast(file_sizes.size()) + " actual: " - + boost::lexical_cast(m_info.num_files()) + ")"; + + boost::lexical_cast(m_info->num_files(true)) + ")"; return false; } @@ -558,8 +526,8 @@ namespace libtorrent fs = file_sizes.begin(); // the resume data says we have the entire torrent // make sure the file sizes are the right ones - for (torrent_info::file_iterator i = m_info.begin_files() - , end(m_info.end_files()); i != end; ++i, ++fs) + for (torrent_info::file_iterator i = m_info->begin_files(true) + , end(m_info->end_files(true)); i != end; ++i, ++fs) { if (i->size != fs->first) { @@ -572,7 +540,7 @@ namespace libtorrent return true; } - return match_filesizes(m_info, m_save_path, file_sizes + return match_filesizes(*m_info, m_save_path, file_sizes , !full_allocation_mode, &error); } @@ -611,11 +579,11 @@ namespace libtorrent m_files.release(this); #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 - old_path = safe_convert((m_save_path / m_info.name()).string()); - new_path = safe_convert((save_path / m_info.name()).string()); + old_path = safe_convert((m_save_path / m_info->name()).string()); + new_path = safe_convert((save_path / m_info->name()).string()); #else - old_path = m_save_path / m_info.name(); - new_path = save_path / m_info.name(); + old_path = m_save_path / m_info->name(); + new_path = save_path / m_info->name(); #endif try @@ -637,7 +605,7 @@ namespace libtorrent /* void storage::shuffle() { - int num_pieces = m_info.num_pieces(); + int num_pieces = m_info->num_pieces(); std::vector pieces(num_pieces); for (std::vector::iterator i = pieces.begin(); @@ -654,7 +622,7 @@ namespace libtorrent { const int slot_index = targets[i]; const int piece_index = pieces[i]; - const int slot_size =static_cast(m_info.piece_size(slot_index)); + const int slot_size =static_cast(m_info->piece_size(slot_index)); std::vector buf(slot_size); read(&buf[0], piece_index, 0, slot_size); write(&buf[0], slot_index, 0, slot_size); @@ -665,7 +633,7 @@ namespace libtorrent void storage::move_slot(int src_slot, int dst_slot) { - int piece_size = m_info.piece_size(dst_slot); + int piece_size = m_info->piece_size(dst_slot); m_scratch_buffer.resize(piece_size); read_impl(&m_scratch_buffer[0], src_slot, 0, piece_size, true); write(&m_scratch_buffer[0], dst_slot, 0, piece_size); @@ -674,9 +642,9 @@ namespace libtorrent void storage::swap_slots(int slot1, int slot2) { // the size of the target slot is the size of the piece - int piece_size = m_info.piece_length(); - int piece1_size = m_info.piece_size(slot2); - int piece2_size = m_info.piece_size(slot1); + int piece_size = m_info->piece_length(); + int piece1_size = m_info->piece_size(slot2); + int piece2_size = m_info->piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); @@ -687,10 +655,10 @@ namespace libtorrent void storage::swap_slots3(int slot1, int slot2, int slot3) { // the size of the target slot is the size of the piece - int piece_size = m_info.piece_length(); - int piece1_size = m_info.piece_size(slot2); - int piece2_size = m_info.piece_size(slot3); - int piece3_size = m_info.piece_size(slot1); + int piece_size = m_info->piece_length(); + int piece1_size = m_info->piece_size(slot2); + int piece2_size = m_info->piece_size(slot3); + int piece3_size = m_info->piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); @@ -717,27 +685,25 @@ namespace libtorrent , bool fill_zero) { assert(buf != 0); - assert(slot >= 0 && slot < m_info.num_pieces()); + assert(slot >= 0 && slot < m_info->num_pieces()); assert(offset >= 0); - assert(offset < m_info.piece_size(slot)); + assert(offset < m_info->piece_size(slot)); assert(size > 0); - slot_lock lock(*this, slot); - #ifndef NDEBUG std::vector slices - = m_info.map_block(slot, offset, size); + = m_info->map_block(slot, offset, size, true); assert(!slices.empty()); #endif - size_type start = slot * (size_type)m_info.piece_length() + offset; - assert(start + size <= m_info.total_size()); + size_type start = slot * (size_type)m_info->piece_length() + offset; + assert(start + size <= m_info->total_size()); // find the file iterator and file offset size_type file_offset = start; std::vector::const_iterator file_iter; - for (file_iter = m_info.begin_files();;) + for (file_iter = m_info->begin_files(true);;) { if (file_offset < file_iter->size) break; @@ -770,7 +736,7 @@ namespace libtorrent #endif int left_to_read = size; - int slot_size = static_cast(m_info.piece_size(slot)); + int slot_size = static_cast(m_info->piece_size(slot)); if (offset + left_to_read > slot_size) left_to_read = slot_size - offset; @@ -795,7 +761,7 @@ namespace libtorrent assert(int(slices.size()) > counter); size_type slice_size = slices[counter].size; assert(slice_size == read_bytes); - assert(m_info.file_at(slices[counter].file_index).path + assert(m_info->file_at(slices[counter].file_index, true).path == file_iter->path); #endif @@ -845,32 +811,30 @@ namespace libtorrent { assert(buf != 0); assert(slot >= 0); - assert(slot < m_info.num_pieces()); + assert(slot < m_info->num_pieces()); assert(offset >= 0); assert(size > 0); - slot_lock lock(*this, slot); - #ifndef NDEBUG std::vector slices - = m_info.map_block(slot, offset, size); + = m_info->map_block(slot, offset, size, true); assert(!slices.empty()); #endif - size_type start = slot * (size_type)m_info.piece_length() + offset; + size_type start = slot * (size_type)m_info->piece_length() + offset; // find the file iterator and file offset size_type file_offset = start; std::vector::const_iterator file_iter; - for (file_iter = m_info.begin_files();;) + for (file_iter = m_info->begin_files(true);;) { if (file_offset < file_iter->size) break; file_offset -= file_iter->size; ++file_iter; - assert(file_iter != m_info.end_files()); + assert(file_iter != m_info->end_files(true)); } fs::path p(m_save_path / file_iter->path); @@ -890,7 +854,7 @@ namespace libtorrent } int left_to_write = size; - int slot_size = static_cast(m_info.piece_size(slot)); + int slot_size = static_cast(m_info->piece_size(slot)); if (offset + left_to_write > slot_size) left_to_write = slot_size - offset; @@ -914,7 +878,7 @@ namespace libtorrent { assert(int(slices.size()) > counter); assert(slices[counter].size == write_bytes); - assert(m_info.file_at(slices[counter].file_index).path + assert(m_info->file_at(slices[counter].file_index, true).path == file_iter->path); assert(buf_pos >= 0); @@ -942,7 +906,7 @@ namespace libtorrent #endif ++file_iter; - assert(file_iter != m_info.end_files()); + assert(file_iter != m_info->end_files(true)); fs::path p = m_save_path / file_iter->path; file_offset = 0; out = m_files.open_file( @@ -953,7 +917,7 @@ namespace libtorrent } } - storage_interface* default_storage_constructor(torrent_info const& ti + storage_interface* default_storage_constructor(boost::intrusive_ptr ti , fs::path const& path, file_pool& fp) { return new storage(ti, path, fp); @@ -1067,7 +1031,7 @@ namespace libtorrent piece_manager::piece_manager( boost::shared_ptr const& torrent - , torrent_info const& ti + , boost::intrusive_ptr ti , fs::path const& save_path , file_pool& fp , disk_io_thread& io @@ -1077,7 +1041,7 @@ namespace libtorrent , m_fill_mode(true) , m_info(ti) , m_save_path(complete(save_path)) - , m_allocating(false) + , m_storage_constructor(sc) , m_io_thread(io) , m_torrent(torrent) { @@ -1119,7 +1083,8 @@ namespace libtorrent void piece_manager::async_read( peer_request const& r - , boost::function const& handler) + , boost::function const& handler + , char* buffer) { disk_io_job j; j.storage = this; @@ -1127,7 +1092,10 @@ namespace libtorrent j.piece = r.piece; j.offset = r.start; j.buffer_size = r.length; - assert(r.length <= 16 * 1024); + j.buffer = buffer; + // if a buffer is not specified, only one block can be read + // since that is the size of the pool allocator's buffers + assert(r.length <= 16 * 1024 || buffer != 0); m_io_thread.add_job(j, handler); } @@ -1180,7 +1148,7 @@ namespace libtorrent int slot = m_piece_to_slot[piece]; assert(slot != has_no_slot); - return m_storage->hash_for_slot(slot, ph, m_info.piece_size(piece)); + return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece)); } void piece_manager::release_files_impl() @@ -1240,7 +1208,7 @@ namespace libtorrent int piece_manager::slot_for_piece(int piece_index) const { - assert(piece_index >= 0 && piece_index < m_info.num_pieces()); + assert(piece_index >= 0 && piece_index < m_info->num_pieces()); return m_piece_to_slot[piece_index]; } @@ -1251,13 +1219,13 @@ namespace libtorrent try { assert(slot_index >= 0); - assert(slot_index < m_info.num_pieces()); + assert(slot_index < m_info->num_pieces()); assert(block_size > 0); adler32_crc crc; std::vector buf(block_size); - int num_blocks = static_cast(m_info.piece_size(slot_index)) / block_size; - int last_block_size = static_cast(m_info.piece_size(slot_index)) % block_size; + int num_blocks = static_cast(m_info->piece_size(slot_index)) / block_size; + int last_block_size = static_cast(m_info->piece_size(slot_index)) % block_size; if (last_block_size == 0) last_block_size = block_size; for (int i = 0; i < num_blocks-1; ++i) @@ -1350,11 +1318,11 @@ namespace libtorrent { // INVARIANT_CHECK; - assert((int)have_pieces.size() == m_info.num_pieces()); + assert((int)have_pieces.size() == m_info->num_pieces()); - const int piece_size = static_cast(m_info.piece_length()); - const int last_piece_size = static_cast(m_info.piece_size( - m_info.num_pieces() - 1)); + const int piece_size = static_cast(m_info->piece_length()); + const int last_piece_size = static_cast(m_info->piece_size( + m_info->num_pieces() - 1)); assert((int)piece_data.size() >= last_piece_size); @@ -1510,7 +1478,7 @@ namespace libtorrent INVARIANT_CHECK; - assert(m_info.piece_length() > 0); + assert(m_info->piece_length() > 0); m_compact_mode = compact_mode; @@ -1520,13 +1488,13 @@ namespace libtorrent // by check_pieces. // m_storage->shuffle(); - m_piece_to_slot.resize(m_info.num_pieces(), has_no_slot); - m_slot_to_piece.resize(m_info.num_pieces(), unallocated); + m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); + m_slot_to_piece.resize(m_info->num_pieces(), unallocated); m_free_slots.clear(); m_unallocated_slots.clear(); pieces.clear(); - pieces.resize(m_info.num_pieces(), false); + pieces.resize(m_info->num_pieces(), false); num_pieces = 0; // if we have fast-resume info @@ -1631,7 +1599,7 @@ namespace libtorrent return std::make_pair(false, 1.f); } - if (int(m_unallocated_slots.size()) == m_info.num_pieces() + if (int(m_unallocated_slots.size()) == m_info->num_pieces() && !m_fill_mode) { // if there is not a single file on disk, just @@ -1673,8 +1641,8 @@ namespace libtorrent assert(!m_fill_mode); std::vector().swap(m_unallocated_slots); std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); - m_free_slots.resize(m_info.num_pieces()); - for (int i = 0; i < m_info.num_pieces(); ++i) + m_free_slots.resize(m_info->num_pieces()); + for (int i = 0; i < m_info->num_pieces(); ++i) m_free_slots[i] = i; } @@ -1694,15 +1662,15 @@ namespace libtorrent if (m_hash_to_piece.empty()) { m_current_slot = 0; - for (int i = 0; i < m_info.num_pieces(); ++i) + for (int i = 0; i < m_info->num_pieces(); ++i) { - m_hash_to_piece.insert(std::make_pair(m_info.hash_for_piece(i), i)); + m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); } std::fill(pieces.begin(), pieces.end(), false); } - m_piece_data.resize(int(m_info.piece_length())); - int piece_size = int(m_info.piece_size(m_current_slot)); + m_piece_data.resize(int(m_info->piece_length())); + int piece_size = int(m_info->piece_size(m_current_slot)); int num_read = m_storage->read(&m_piece_data[0] , m_current_slot, 0, piece_size); @@ -1905,9 +1873,9 @@ namespace libtorrent { // find the file that failed, and skip all the blocks in that file size_type file_offset = 0; - size_type current_offset = m_current_slot * m_info.piece_length(); - for (torrent_info::file_iterator i = m_info.begin_files(); - i != m_info.end_files(); ++i) + size_type current_offset = m_current_slot * m_info->piece_length(); + for (torrent_info::file_iterator i = m_info->begin_files(true); + i != m_info->end_files(true); ++i) { file_offset += i->size; if (file_offset > current_offset) break; @@ -1915,8 +1883,8 @@ namespace libtorrent assert(file_offset > current_offset); int skip_blocks = static_cast( - (file_offset - current_offset + m_info.piece_length() - 1) - / m_info.piece_length()); + (file_offset - current_offset + m_info->piece_length() - 1) + / m_info->piece_length()); for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) { @@ -1929,9 +1897,9 @@ namespace libtorrent } ++m_current_slot; - if (m_current_slot >= m_info.num_pieces()) + if (m_current_slot >= m_info->num_pieces()) { - assert(m_current_slot == m_info.num_pieces()); + assert(m_current_slot == m_info->num_pieces()); // clear the memory we've been using std::vector().swap(m_piece_data); @@ -1943,7 +1911,7 @@ namespace libtorrent assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - return std::make_pair(false, (float)m_current_slot / m_info.num_pieces()); + return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); } int piece_manager::allocate_slot_for_piece(int piece_index) @@ -1985,7 +1953,7 @@ namespace libtorrent // special case to make sure we don't use the last slot // when we shouldn't, since it's smaller than ordinary slots - if (*iter == m_info.num_pieces() - 1 && piece_index != *iter) + if (*iter == m_info->num_pieces() - 1 && piece_index != *iter) { if (m_free_slots.size() == 1) allocate_slots(1); @@ -2090,13 +2058,13 @@ namespace libtorrent } else if (m_fill_mode) { - int piece_size = int(m_info.piece_size(pos)); + int piece_size = int(m_info->piece_size(pos)); int offset = 0; for (; piece_size > 0; piece_size -= stack_buffer_size , offset += stack_buffer_size) { m_storage->write(zeroes, pos, offset - , std::min(piece_size, stack_buffer_size)); + , (std::min)(piece_size, stack_buffer_size)); } written = true; } @@ -2116,8 +2084,8 @@ namespace libtorrent boost::recursive_mutex::scoped_lock lock(m_mutex); if (m_piece_to_slot.empty()) return; - assert((int)m_piece_to_slot.size() == m_info.num_pieces()); - assert((int)m_slot_to_piece.size() == m_info.num_pieces()); + assert((int)m_piece_to_slot.size() == m_info->num_pieces()); + assert((int)m_slot_to_piece.size() == m_info->num_pieces()); for (std::vector::const_iterator i = m_free_slots.begin(); i != m_free_slots.end(); ++i) @@ -2139,7 +2107,7 @@ namespace libtorrent == m_unallocated_slots.end()); } - for (int i = 0; i < m_info.num_pieces(); ++i) + for (int i = 0; i < m_info->num_pieces(); ++i) { // Check domain of piece_to_slot's elements if (m_piece_to_slot[i] != has_no_slot) @@ -2227,7 +2195,7 @@ namespace libtorrent s << "index\tslot\tpiece\n"; - for (int i = 0; i < m_info.num_pieces(); ++i) + for (int i = 0; i < m_info->num_pieces(); ++i) { s << i << "\t" << m_slot_to_piece[i] << "\t"; s << m_piece_to_slot[i] << "\n"; diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 13309c1e7..ddf8a9164 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -72,6 +72,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/assert.hpp" using namespace libtorrent; using boost::tuples::tuple; @@ -150,16 +151,16 @@ namespace libtorrent torrent::torrent( session_impl& ses , aux::checker_impl& checker - , torrent_info const& tf + , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc) + , storage_constructor_type sc + , bool paused) : m_torrent_file(tf) , m_abort(false) - , m_paused(false) + , m_paused(paused) , m_just_paused(false) , m_event(tracker_request::started) , m_block_size(0) @@ -181,7 +182,7 @@ namespace libtorrent , m_ses(ses) , m_checker(checker) , m_picker(0) - , m_trackers(m_torrent_file.trackers()) + , m_trackers(m_torrent_file->trackers()) , m_last_working_tracker(-1) , m_currently_trying_tracker(0) , m_failed_trackers(0) @@ -197,24 +198,15 @@ namespace libtorrent , m_compact_mode(compact_mode) , m_default_block_size(block_size) , m_connections_initialized(true) - , m_settings(s) + , m_settings(ses.settings()) , m_storage_constructor(sc) + , m_max_uploads((std::numeric_limits::max)()) + , m_num_uploads(0) + , m_max_connections((std::numeric_limits::max)()) { -#ifndef NDEBUG - m_initial_done = 0; -#endif - - m_uploads_quota.min = 0; - m_connections_quota.min = 2; - // this will be corrected the next time the main session - // distributes resources, i.e. on average in 0.5 seconds - m_connections_quota.given = 100; - m_uploads_quota.max = std::numeric_limits::max(); - m_connections_quota.max = std::numeric_limits::max(); m_policy.reset(new policy(this)); } - torrent::torrent( session_impl& ses , aux::checker_impl& checker @@ -225,11 +217,11 @@ namespace libtorrent , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc) - : m_torrent_file(info_hash) + , storage_constructor_type sc + , bool paused) + : m_torrent_file(new torrent_info(info_hash)) , m_abort(false) - , m_paused(false) + , m_paused(paused) , m_just_paused(false) , m_event(tracker_request::started) , m_block_size(0) @@ -266,28 +258,20 @@ namespace libtorrent , m_compact_mode(compact_mode) , m_default_block_size(block_size) , m_connections_initialized(false) - , m_settings(s) + , m_settings(ses.settings()) , m_storage_constructor(sc) + , m_max_uploads((std::numeric_limits::max)()) + , m_num_uploads(0) + , m_max_connections((std::numeric_limits::max)()) { -#ifndef NDEBUG - m_initial_done = 0; -#endif - INVARIANT_CHECK; if (name) m_name.reset(new std::string(name)); - m_uploads_quota.min = 0; - m_connections_quota.min = 2; - // this will be corrected the next time the main session - // distributes resources, i.e. on average in 0.5 seconds - m_connections_quota.given = 100; - m_uploads_quota.max = std::numeric_limits::max(); - m_connections_quota.max = std::numeric_limits::max(); if (tracker_url) { m_trackers.push_back(announce_entry(tracker_url)); - m_torrent_file.add_tracker(tracker_url); + m_torrent_file->add_tracker(tracker_url); } m_policy.reset(new policy(this)); @@ -296,7 +280,7 @@ namespace libtorrent void torrent::start() { boost::weak_ptr self(shared_from_this()); - if (m_torrent_file.is_valid()) init(); + if (m_torrent_file->is_valid()) init(); m_announce_timer.expires_from_now(seconds(1)); m_announce_timer.async_wait(m_ses.m_strand.wrap( bind(&torrent::on_announce_disp, self, _1))); @@ -306,7 +290,7 @@ namespace libtorrent bool torrent::should_announce_dht() const { // don't announce private torrents - if (m_torrent_file.is_valid() && m_torrent_file.priv()) return false; + if (m_torrent_file->is_valid() && m_torrent_file->priv()) return false; if (m_trackers.empty()) return true; @@ -329,6 +313,14 @@ namespace libtorrent INVARIANT_CHECK; +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** DESTRUCTING TORRENT\n"; + } +#endif + assert(m_abort); if (!m_connections.empty()) disconnect_all(); @@ -336,7 +328,7 @@ namespace libtorrent std::string torrent::name() const { - if (valid_metadata()) return m_torrent_file.name(); + if (valid_metadata()) return m_torrent_file->name(); if (m_name) return *m_name; return ""; } @@ -352,22 +344,22 @@ namespace libtorrent // shared_from_this() void torrent::init() { - assert(m_torrent_file.is_valid()); - assert(m_torrent_file.num_files() > 0); - assert(m_torrent_file.total_size() >= 0); + assert(m_torrent_file->is_valid()); + assert(m_torrent_file->num_files() > 0); + assert(m_torrent_file->total_size() >= 0); - m_have_pieces.resize(m_torrent_file.num_pieces(), false); + m_have_pieces.resize(m_torrent_file->num_pieces(), false); // the shared_from_this() will create an intentional // cycle of ownership, se the hpp file for description. m_owning_storage = new piece_manager(shared_from_this(), m_torrent_file , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor); m_storage = m_owning_storage.get(); - m_block_size = calculate_block_size(m_torrent_file, m_default_block_size); + m_block_size = calculate_block_size(*m_torrent_file, m_default_block_size); m_picker.reset(new piece_picker( - static_cast(m_torrent_file.piece_length() / m_block_size) - , static_cast((m_torrent_file.total_size()+m_block_size-1)/m_block_size))); + static_cast(m_torrent_file->piece_length() / m_block_size) + , static_cast((m_torrent_file->total_size()+m_block_size-1)/m_block_size))); - std::vector const& url_seeds = m_torrent_file.url_seeds(); + std::vector const& url_seeds = m_torrent_file->url_seeds(); std::copy(url_seeds.begin(), url_seeds.end(), std::inserter(m_web_seeds , m_web_seeds.begin())); } @@ -395,7 +387,7 @@ namespace libtorrent { boost::weak_ptr self(shared_from_this()); - if (!m_torrent_file.priv()) + if (!m_torrent_file->priv()) { // announce on local network every 5 minutes m_announce_timer.expires_from_now(minutes(5)); @@ -403,7 +395,7 @@ namespace libtorrent bind(&torrent::on_announce_disp, self, _1))); // announce with the local discovery service - m_ses.announce_lsd(m_torrent_file.info_hash()); + m_ses.announce_lsd(m_torrent_file->info_hash()); } else { @@ -421,7 +413,7 @@ namespace libtorrent // TODO: There should be a way to abort an announce operation on the dht. // when the torrent is destructed assert(m_ses.m_external_listen_port > 0); - m_ses.m_dht->announce(m_torrent_file.info_hash() + m_ses.m_dht->announce(m_torrent_file->info_hash() , m_ses.m_external_listen_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } @@ -467,7 +459,7 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_torrent_file.trackers().empty()) return false; + if (m_torrent_file->trackers().empty()) return false; if (m_just_paused) { @@ -617,7 +609,7 @@ namespace libtorrent // if we don't have the metadata yet, we // cannot tell how big the torrent is. if (!valid_metadata()) return -1; - return m_torrent_file.total_size() + return m_torrent_file->total_size() - quantized_bytes_done(); } @@ -627,23 +619,23 @@ namespace libtorrent if (!valid_metadata()) return 0; - if (m_torrent_file.num_pieces() == 0) + if (m_torrent_file->num_pieces() == 0) return 0; - if (is_seed()) return m_torrent_file.total_size(); + if (is_seed()) return m_torrent_file->total_size(); - const int last_piece = m_torrent_file.num_pieces() - 1; + const int last_piece = m_torrent_file->num_pieces() - 1; size_type total_done - = m_num_pieces * m_torrent_file.piece_length(); + = m_num_pieces * m_torrent_file->piece_length(); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { - int corr = m_torrent_file.piece_size(last_piece) - - m_torrent_file.piece_length(); + int corr = m_torrent_file->piece_size(last_piece) + - m_torrent_file->piece_length(); total_done += corr; } return total_done; @@ -656,42 +648,42 @@ namespace libtorrent { INVARIANT_CHECK; - if (!valid_metadata() || m_torrent_file.num_pieces() == 0) + if (!valid_metadata() || m_torrent_file->num_pieces() == 0) return tuple(0,0); - const int last_piece = m_torrent_file.num_pieces() - 1; + const int last_piece = m_torrent_file->num_pieces() - 1; if (is_seed()) - return make_tuple(m_torrent_file.total_size() - , m_torrent_file.total_size()); + return make_tuple(m_torrent_file->total_size() + , m_torrent_file->total_size()); size_type wanted_done = (m_num_pieces - m_picker->num_have_filtered()) - * m_torrent_file.piece_length(); + * m_torrent_file->piece_length(); size_type total_done - = m_num_pieces * m_torrent_file.piece_length(); - assert(m_num_pieces < m_torrent_file.num_pieces()); + = m_num_pieces * m_torrent_file->piece_length(); + assert(m_num_pieces < m_torrent_file->num_pieces()); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { - int corr = m_torrent_file.piece_size(last_piece) - - m_torrent_file.piece_length(); + int corr = m_torrent_file->piece_size(last_piece) + - m_torrent_file->piece_length(); total_done += corr; if (m_picker->piece_priority(last_piece) != 0) wanted_done += corr; } - assert(total_done <= m_torrent_file.total_size()); - assert(wanted_done <= m_torrent_file.total_size()); + assert(total_done <= m_torrent_file->total_size()); + assert(wanted_done <= m_torrent_file->total_size()); const std::vector& dl_queue = m_picker->get_download_queue(); const int blocks_per_piece = static_cast( - m_torrent_file.piece_length() / m_block_size); + m_torrent_file->piece_length() / m_block_size); for (std::vector::const_iterator i = dl_queue.begin(); i != dl_queue.end(); ++i) @@ -724,15 +716,15 @@ namespace libtorrent == piece_picker::block_info::state_finished) { corr -= m_block_size; - corr += m_torrent_file.piece_size(last_piece) % m_block_size; + corr += m_torrent_file->piece_size(last_piece) % m_block_size; } total_done += corr; if (m_picker->piece_priority(index) != 0) wanted_done += corr; } - assert(total_done <= m_torrent_file.total_size()); - assert(wanted_done <= m_torrent_file.total_size()); + assert(total_done <= m_torrent_file->total_size()); + assert(wanted_done <= m_torrent_file->total_size()); std::map downloading_piece; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -762,10 +754,10 @@ namespace libtorrent } #ifndef NDEBUG assert(p->bytes_downloaded <= p->full_block_bytes); - int last_piece = m_torrent_file.num_pieces() - 1; + int last_piece = m_torrent_file->num_pieces() - 1; if (p->piece_index == last_piece - && p->block_index == m_torrent_file.piece_size(last_piece) / block_size()) - assert(p->full_block_bytes == m_torrent_file.piece_size(last_piece) % block_size()); + && p->block_index == m_torrent_file->piece_size(last_piece) / block_size()) + assert(p->full_block_bytes == m_torrent_file->piece_size(last_piece) % block_size()); else assert(p->full_block_bytes == block_size()); #endif @@ -781,7 +773,7 @@ namespace libtorrent #ifndef NDEBUG - if (total_done >= m_torrent_file.total_size()) + if (total_done >= m_torrent_file->total_size()) { std::copy(m_have_pieces.begin(), m_have_pieces.end() , std::ostream_iterator(std::cerr, " ")); @@ -812,8 +804,8 @@ namespace libtorrent } - assert(total_done <= m_torrent_file.total_size()); - assert(wanted_done <= m_torrent_file.total_size()); + assert(total_done <= m_torrent_file->total_size()); + assert(wanted_done <= m_torrent_file->total_size()); #endif @@ -910,14 +902,14 @@ namespace libtorrent // think that it has received all of it until this function // resets the download queue. So, we cannot do the // invariant check here since it assumes: - // (total_done == m_torrent_file.total_size()) => is_seed() + // (total_done == m_torrent_file->total_size()) => is_seed() // INVARIANT_CHECK; assert(m_storage); assert(m_storage->refcount() > 0); assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); if (m_ses.m_alerts.should_post(alert::info)) { @@ -926,7 +918,7 @@ namespace libtorrent m_ses.m_alerts.post_alert(hash_failed_alert(get_handle(), index, s.str())); } // increase the total amount of failed bytes - m_total_failed_bytes += m_torrent_file.piece_size(index); + m_total_failed_bytes += m_torrent_file->piece_size(index); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1020,6 +1012,15 @@ namespace libtorrent m_event = tracker_request::stopped; // disconnect all peers and close all // files belonging to the torrents + +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** ABORTING TORRENT\n"; + } +#endif + disconnect_all(); if (m_owning_storage.get()) m_storage->async_release_files(); m_owning_storage = 0; @@ -1040,7 +1041,7 @@ namespace libtorrent // INVARIANT_CHECK; assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1083,7 +1084,7 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file.seed_free(); + m_torrent_file->seed_free(); } } @@ -1117,7 +1118,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); bool filter_updated = m_picker->set_piece_priority(index, priority); if (filter_updated) update_peer_interest(); @@ -1133,7 +1134,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); return m_picker->piece_priority(index); } @@ -1169,7 +1170,7 @@ namespace libtorrent if (is_seed()) { pieces.clear(); - pieces.resize(m_torrent_file.num_pieces(), 1); + pieces.resize(m_torrent_file->num_pieces(), 1); return; } @@ -1194,20 +1195,20 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert(int(files.size()) == m_torrent_file.num_files()); + assert(int(files.size()) == m_torrent_file->num_files()); size_type position = 0; - if (m_torrent_file.num_pieces() == 0) return; + if (m_torrent_file->num_pieces() == 0) return; - int piece_length = m_torrent_file.piece_length(); + int piece_length = m_torrent_file->piece_length(); // initialize the piece priorities to 0, then only allow // setting higher priorities - std::vector pieces(m_torrent_file.num_pieces(), 0); + std::vector pieces(m_torrent_file->num_pieces(), 0); for (int i = 0; i < int(files.size()); ++i) { size_type start = position; - size_type size = m_torrent_file.file_at(i).size; + size_type size = m_torrent_file->file_at(i).size; if (size == 0) continue; position += size; // mark all pieces of the file with this file's priority @@ -1243,7 +1244,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); m_picker->set_piece_priority(index, filter ? 1 : 0); update_peer_interest(); @@ -1280,7 +1281,7 @@ namespace libtorrent assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); return m_picker->piece_priority(index) == 0; } @@ -1294,7 +1295,7 @@ namespace libtorrent if (is_seed()) { bitmask.clear(); - bitmask.resize(m_torrent_file.num_pieces(), false); + bitmask.resize(m_torrent_file->num_pieces(), false); return; } @@ -1311,20 +1312,20 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert((int)bitmask.size() == m_torrent_file.num_files()); + assert((int)bitmask.size() == m_torrent_file->num_files()); size_type position = 0; - if (m_torrent_file.num_pieces()) + if (m_torrent_file->num_pieces()) { - int piece_length = m_torrent_file.piece_length(); + int piece_length = m_torrent_file->piece_length(); // mark all pieces as filtered, then clear the bits for files // that should be downloaded - std::vector piece_filter(m_torrent_file.num_pieces(), true); + std::vector piece_filter(m_torrent_file->num_pieces(), true); for (int i = 0; i < (int)bitmask.size(); ++i) { size_type start = position; - position += m_torrent_file.file_at(i).size; + position += m_torrent_file->file_at(i).size; // is the file selected for download? if (!bitmask[i]) { @@ -1359,7 +1360,7 @@ namespace libtorrent m_next_request = time_now() + seconds(tracker_retry_delay_max); tracker_request req; - req.info_hash = m_torrent_file.info_hash(); + req.info_hash = m_torrent_file->info_hash(); req.pid = m_ses.get_peer_id(); req.downloaded = m_stat.total_payload_download(); req.uploaded = m_stat.total_payload_upload(); @@ -1383,6 +1384,27 @@ namespace libtorrent return req; } + void torrent::choke_peer(peer_connection& c) + { + INVARIANT_CHECK; + + assert(!c.is_choked()); + assert(m_num_uploads > 0); + c.send_choke(); + --m_num_uploads; + } + + bool torrent::unchoke_peer(peer_connection& c) + { + INVARIANT_CHECK; + + assert(c.is_choked()); + if (m_num_uploads >= m_max_uploads) return false; + c.send_unchoke(); + ++m_num_uploads; + return true; + } + void torrent::cancel_block(piece_block block) { for (peer_iterator i = m_connections.begin() @@ -1394,7 +1416,7 @@ namespace libtorrent void torrent::remove_peer(peer_connection* p) try { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(p != 0); @@ -1433,6 +1455,9 @@ namespace libtorrent } } + if (!p->is_choked()) + --m_num_uploads; + m_policy->connection_closed(*p); p->set_peer_info(0); m_connections.erase(i); @@ -1807,6 +1832,7 @@ namespace libtorrent #endif assert(want_more_peers()); + assert(m_ses.num_connections() < m_ses.max_connections()); tcp::endpoint const& a(peerinfo->ip); assert((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); @@ -1858,8 +1884,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_torrent_file.is_valid()); - m_torrent_file.parse_info_section(metadata); + assert(!m_torrent_file->is_valid()); + m_torrent_file->parse_info_section(metadata); init(); @@ -1869,12 +1895,12 @@ namespace libtorrent new aux::piece_checker_data); d->torrent_ptr = shared_from_this(); d->save_path = m_save_path; - d->info_hash = m_torrent_file.info_hash(); + d->info_hash = m_torrent_file->info_hash(); // add the torrent to the queue to be checked m_checker.m_torrents.push_back(d); typedef session_impl::torrent_map torrent_map; torrent_map::iterator i = m_ses.m_torrents.find( - m_torrent_file.info_hash()); + m_torrent_file->info_hash()); assert(i != m_ses.m_torrents.end()); m_ses.m_torrents.erase(i); // and notify the thread that it got another @@ -1890,7 +1916,7 @@ namespace libtorrent void torrent::attach_peer(peer_connection* p) { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(p != 0); assert(!p->is_local()); @@ -1955,7 +1981,7 @@ namespace libtorrent bool torrent::want_more_peers() const { - return int(m_connections.size()) < m_connections_quota.given + return int(m_connections.size()) < m_max_connections && m_ses.m_half_open.free_slots() && !m_paused; } @@ -1994,7 +2020,9 @@ namespace libtorrent , boost::intrusive_ptr const& p , bool non_prioritized) { + assert(m_bandwidth_limit[channel].throttle() > 0); int block_size = m_bandwidth_limit[channel].throttle() / 10; + if (block_size <= 0) block_size = 1; if (m_bandwidth_limit[channel].max_assignable() > 0) { @@ -2122,7 +2150,7 @@ namespace libtorrent if ((unsigned)m_currently_trying_tracker >= m_trackers.size()) { int delay = tracker_retry_delay_min - + std::min(m_failed_trackers, (int)tracker_failed_max) + + (std::min)(m_failed_trackers, (int)tracker_failed_max) * (tracker_retry_delay_max - tracker_retry_delay_min) / tracker_failed_max; @@ -2181,14 +2209,12 @@ namespace libtorrent } pause(); } -#ifndef NDEBUG - m_initial_done = boost::get<0>(bytes_done()); -#endif return done; } std::pair torrent::check_files() { + assert(m_torrent_file->is_valid()); INVARIANT_CHECK; assert(m_owning_storage.get()); @@ -2216,9 +2242,6 @@ namespace libtorrent pause(); } -#ifndef NDEBUG - m_initial_done = boost::get<0>(bytes_done()); -#endif return progress; } @@ -2227,6 +2250,7 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + assert(m_torrent_file->is_valid()); INVARIANT_CHECK; if (!is_seed()) @@ -2257,7 +2281,7 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file.seed_free(); + m_torrent_file->seed_free(); } if (!m_connections_initialized) @@ -2286,9 +2310,6 @@ namespace libtorrent } } } -#ifndef NDEBUG - m_initial_done = boost::get<0>(bytes_done()); -#endif } alert_manager& torrent::alerts() const @@ -2340,7 +2361,7 @@ namespace libtorrent torrent_handle torrent::get_handle() const { - return torrent_handle(&m_ses, &m_checker, m_torrent_file.info_hash()); + return torrent_handle(&m_ses, &m_checker, m_torrent_file->info_hash()); } session_settings const& torrent::settings() const @@ -2351,9 +2372,7 @@ namespace libtorrent #ifndef NDEBUG void torrent::check_invariant() const { -// size_type download = m_stat.total_payload_download(); -// size_type done = boost::get<0>(bytes_done()); -// assert(download >= done - m_initial_done); + int num_uploads = 0; std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) { @@ -2364,10 +2383,12 @@ namespace libtorrent for (std::deque::const_iterator i = p.download_queue().begin() , end(p.download_queue().end()); i != end; ++i) ++num_requests[*i]; + if (!p.is_choked()) ++num_uploads; torrent* associated_torrent = p.associated_torrent().lock().get(); if (associated_torrent != this) assert(false); } + assert(num_uploads == m_num_uploads); if (has_picker()) { @@ -2380,7 +2401,7 @@ namespace libtorrent if (valid_metadata()) { - assert(m_abort || int(m_have_pieces.size()) == m_torrent_file.num_pieces()); + assert(m_abort || int(m_have_pieces.size()) == m_torrent_file->num_pieces()); } else { @@ -2388,12 +2409,12 @@ namespace libtorrent } size_type total_done = quantized_bytes_done(); - if (m_torrent_file.is_valid()) + if (m_torrent_file->is_valid()) { if (is_seed()) - assert(total_done == m_torrent_file.total_size()); + assert(total_done == m_torrent_file->total_size()); else - assert(total_done != m_torrent_file.total_size()); + assert(total_done != m_torrent_file->total_size()); } else { @@ -2404,7 +2425,7 @@ namespace libtorrent assert(m_num_pieces == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); assert(!valid_metadata() || m_block_size > 0); - assert(!valid_metadata() || (m_torrent_file.piece_length() % m_block_size) == 0); + assert(!valid_metadata() || (m_torrent_file->piece_length() % m_block_size) == 0); // if (is_seed()) assert(m_picker.get() == 0); } #endif @@ -2425,15 +2446,15 @@ namespace libtorrent void torrent::set_max_uploads(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); - m_uploads_quota.max = std::max(m_uploads_quota.min, limit); + if (limit <= 0) limit = (std::numeric_limits::max)(); + m_max_uploads = limit; } void torrent::set_max_connections(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); - m_connections_quota.max = std::max(m_connections_quota.min, limit); + if (limit <= 0) limit = (std::numeric_limits::max)(); + m_max_connections = limit; } void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) @@ -2455,7 +2476,7 @@ namespace libtorrent void torrent::set_upload_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); + if (limit <= 0) limit = (std::numeric_limits::max)(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::upload_channel].throttle(limit); } @@ -2463,14 +2484,14 @@ namespace libtorrent int torrent::upload_limit() const { int limit = m_bandwidth_limit[peer_connection::upload_channel].throttle(); - if (limit == std::numeric_limits::max()) limit = -1; + if (limit == (std::numeric_limits::max)()) limit = -1; return limit; } void torrent::set_download_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); + if (limit <= 0) limit = (std::numeric_limits::max)(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::download_channel].throttle(limit); } @@ -2478,7 +2499,7 @@ namespace libtorrent int torrent::download_limit() const { int limit = m_bandwidth_limit[peer_connection::download_channel].throttle(); - if (limit == std::numeric_limits::max()) limit = -1; + if (limit == (std::numeric_limits::max)()) limit = -1; return limit; } @@ -2496,6 +2517,14 @@ namespace libtorrent } #endif +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** PAUSING TORRENT\n"; + } +#endif + disconnect_all(); m_paused = true; // tell the tracker that we stopped @@ -2528,10 +2557,6 @@ namespace libtorrent #endif m_paused = false; - m_uploads_quota.min = 0; - m_connections_quota.min = 2; - m_uploads_quota.max = std::numeric_limits::max(); - m_connections_quota.max = std::numeric_limits::max(); // tell the tracker that we're back m_event = tracker_request::started; @@ -2545,10 +2570,6 @@ namespace libtorrent { INVARIANT_CHECK; - m_connections_quota.used = (int)m_connections.size(); - m_uploads_quota.used = m_policy->num_uploads(); - m_uploads_quota.max = (int)m_connections.size(); - #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2561,10 +2582,6 @@ namespace libtorrent { // let the stats fade out to 0 m_stat.second_tick(tick_interval); - m_connections_quota.min = 0; - m_connections_quota.max = 0; - m_uploads_quota.min = 0; - m_uploads_quota.max = 0; return; } @@ -2623,6 +2640,13 @@ namespace libtorrent } accumulator += m_stat; m_stat.second_tick(tick_interval); + + m_time_scaler--; + if (m_time_scaler <= 0) + { + m_time_scaler = 10; + m_policy->pulse(); + } } bool torrent::try_connect_peer() @@ -2631,18 +2655,6 @@ namespace libtorrent return m_policy->connect_one_peer(); } - void torrent::distribute_resources(float tick_interval) - { - INVARIANT_CHECK; - - m_time_scaler--; - if (m_time_scaler <= 0) - { - m_time_scaler = settings().unchoke_interval; - m_policy->pulse(); - } - } - void torrent::async_verify_piece(int piece_index, boost::function const& f) { INVARIANT_CHECK; @@ -2650,7 +2662,7 @@ namespace libtorrent assert(m_storage); assert(m_storage->refcount() > 0); assert(piece_index >= 0); - assert(piece_index < m_torrent_file.num_pieces()); + assert(piece_index < m_torrent_file->num_pieces()); assert(piece_index < (int)m_have_pieces.size()); m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified @@ -2662,7 +2674,7 @@ namespace libtorrent { sha1_hash h(j.str); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - f(m_torrent_file.hash_for_piece(j.piece) == h); + f(m_torrent_file->hash_for_piece(j.piece) == h); } const tcp::endpoint& torrent::current_tracker() const @@ -2678,12 +2690,12 @@ namespace libtorrent assert(valid_metadata()); fp.clear(); - fp.resize(m_torrent_file.num_files(), 0.f); + fp.resize(m_torrent_file->num_files(), 0.f); - for (int i = 0; i < m_torrent_file.num_files(); ++i) + for (int i = 0; i < m_torrent_file->num_files(); ++i) { - peer_request ret = m_torrent_file.map_file(i, 0, 0); - size_type size = m_torrent_file.file_at(i).size; + peer_request ret = m_torrent_file->map_file(i, 0, 0); + size_type size = m_torrent_file->file_at(i).size; // zero sized files are considered // 100% done all the time @@ -2696,7 +2708,7 @@ namespace libtorrent size_type done = 0; while (size > 0) { - size_type bytes_step = std::min(m_torrent_file.piece_size(ret.piece) + size_type bytes_step = (std::min)(m_torrent_file->piece_size(ret.piece) - ret.start, size); if (m_have_pieces[ret.piece]) done += bytes_step; ++ret.piece; @@ -2705,7 +2717,7 @@ namespace libtorrent } assert(size == 0); - fp[i] = static_cast(done) / m_torrent_file.file_at(i).size; + fp[i] = static_cast(done) / m_torrent_file->file_at(i).size; } } @@ -2764,10 +2776,10 @@ namespace libtorrent = m_trackers[m_last_working_tracker].url; } - st.num_uploads = m_uploads_quota.used; - st.uploads_limit = m_uploads_quota.given; - st.num_connections = m_connections_quota.used; - st.connections_limit = m_connections_quota.given; + st.num_uploads = m_num_uploads; + st.uploads_limit = m_max_uploads; + st.num_connections = int(m_connections.size()); + st.connections_limit = m_max_connections; // if we don't have any metadata, stop here if (!valid_metadata()) @@ -2780,7 +2792,7 @@ namespace libtorrent // TODO: add a progress member to the torrent that will be used in this case // and that may be set by a plugin // if (m_metadata_size == 0) st.progress = 0.f; -// else st.progress = std::min(1.f, m_metadata_progress / (float)m_metadata_size); +// else st.progress = (std::min)(1.f, m_metadata_progress / (float)m_metadata_size); st.progress = 0.f; st.block_size = 0; @@ -2792,21 +2804,21 @@ namespace libtorrent // fill in status that depends on metadata - st.total_wanted = m_torrent_file.total_size(); + st.total_wanted = m_torrent_file->total_size(); if (m_picker.get() && (m_picker->num_filtered() > 0 || m_picker->num_have_filtered() > 0)) { int filtered_pieces = m_picker->num_filtered() + m_picker->num_have_filtered(); - int last_piece_index = m_torrent_file.num_pieces() - 1; + int last_piece_index = m_torrent_file->num_pieces() - 1; if (m_picker->piece_priority(last_piece_index) == 0) { - st.total_wanted -= m_torrent_file.piece_size(last_piece_index); + st.total_wanted -= m_torrent_file->piece_size(last_piece_index); --filtered_pieces; } - st.total_wanted -= filtered_pieces * m_torrent_file.piece_length(); + st.total_wanted -= filtered_pieces * m_torrent_file->piece_length(); } assert(st.total_wanted >= st.total_wanted_done); @@ -2824,7 +2836,7 @@ namespace libtorrent } else if (is_seed()) { - assert(st.total_done == m_torrent_file.total_size()); + assert(st.total_done == m_torrent_file->total_size()); st.state = torrent_status::seeding; } else if (st.total_wanted_done == st.total_wanted) diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 4538e66e8..3cf1f2bac 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -89,26 +89,22 @@ namespace libtorrent throw invalid_handle(); } - template - Ret call_member( + boost::shared_ptr find_torrent( session_impl* ses , aux::checker_impl* chk - , sha1_hash const& hash - , F f) + , sha1_hash const& hash) { if (ses == 0) throw_invalid_handle(); if (chk) { - mutex::scoped_lock l(chk->m_mutex); aux::piece_checker_data* d = chk->find_torrent(hash); - if (d != 0) return f(*d->torrent_ptr); + if (d != 0) return d->torrent_ptr; } { - session_impl::mutex_t::scoped_lock l(ses->m_mutex); boost::shared_ptr t = ses->find_torrent(hash).lock(); - if (t) return f(*t); + if (t) return t; } // throwing directly instead of calling @@ -133,16 +129,18 @@ namespace libtorrent assert(max_uploads >= 2 || max_uploads == -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_max_uploads, _1, max_uploads)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_max_uploads(max_uploads); } void torrent_handle::use_interface(const char* net_interface) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::use_interface, _1, net_interface)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->use_interface(net_interface); } void torrent_handle::set_max_connections(int max_connections) const @@ -151,8 +149,9 @@ namespace libtorrent assert(max_connections >= 2 || max_connections == -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_max_connections, _1, max_connections)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_max_connections(max_connections); } void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const @@ -160,8 +159,9 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_peer_upload_limit, _1, ip, limit)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_peer_upload_limit(ip, limit); } void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const @@ -169,8 +169,9 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_peer_download_limit, _1, ip, limit)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_peer_download_limit(ip, limit); } void torrent_handle::set_upload_limit(int limit) const @@ -179,15 +180,17 @@ namespace libtorrent assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_upload_limit, _1, limit)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_upload_limit(limit); } int torrent_handle::upload_limit() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::upload_limit, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->upload_limit(); } void torrent_handle::set_download_limit(int limit) const @@ -196,15 +199,18 @@ namespace libtorrent assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_download_limit, _1, limit)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_download_limit(limit); } int torrent_handle::download_limit() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::download_limit, _1)); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->download_limit(); } void torrent_handle::move_storage( @@ -212,48 +218,54 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::move_storage, _1, save_path)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->move_storage(save_path); } bool torrent_handle::has_metadata() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::valid_metadata, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->valid_metadata(); } bool torrent_handle::is_seed() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::is_seed, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->is_seed(); } bool torrent_handle::is_paused() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::is_paused, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->is_paused(); } void torrent_handle::pause() const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::pause, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->pause(); } void torrent_handle::resume() const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::resume, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->resume(); } void torrent_handle::set_tracker_login(std::string const& name @@ -261,8 +273,9 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_tracker_login, _1, name, password)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_tracker_login(name, password); } void torrent_handle::file_progress(std::vector& progress) @@ -342,15 +355,18 @@ namespace libtorrent void torrent_handle::set_sequenced_download_threshold(int threshold) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_sequenced_download_threshold, _1, threshold)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_sequenced_download_threshold(threshold); } std::string torrent_handle::name() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::name, _1)); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->name(); } @@ -358,40 +374,45 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::piece_availability, _1, boost::ref(avail))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->piece_availability(avail); } void torrent_handle::piece_priority(int index, int priority) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_piece_priority, _1, index, priority)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_piece_priority(index, priority); } int torrent_handle::piece_priority(int index) const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::piece_priority, _1, index)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->piece_priority(index); } void torrent_handle::prioritize_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::prioritize_pieces, _1, boost::cref(pieces))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->prioritize_pieces(pieces); } std::vector torrent_handle::piece_priorities() const { INVARIANT_CHECK; std::vector ret; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::piece_priorities, _1, boost::ref(ret))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->piece_priorities(ret); return ret; } @@ -399,8 +420,9 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::prioritize_files, _1, boost::cref(files))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->prioritize_files(files); } // ============ start deprecation =============== @@ -408,38 +430,43 @@ namespace libtorrent void torrent_handle::filter_piece(int index, bool filter) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_piece, _1, index, filter)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filter_piece(index, filter); } void torrent_handle::filter_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_pieces, _1, boost::cref(pieces))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filter_pieces(pieces); } bool torrent_handle::is_piece_filtered(int index) const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::is_piece_filtered, _1, index)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->is_piece_filtered(index); } std::vector torrent_handle::filtered_pieces() const { INVARIANT_CHECK; std::vector ret; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filtered_pieces, _1, boost::ref(ret))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filtered_pieces(ret); return ret; } void torrent_handle::filter_files(std::vector const& files) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_files, _1, files)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filter_files(files); } // ============ end deprecation =============== @@ -449,16 +476,36 @@ namespace libtorrent { INVARIANT_CHECK; - return call_member const&>(m_ses - , m_chk, m_info_hash, bind(&torrent::trackers, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->trackers(); } - void torrent_handle::add_url_seed(std::string const& url) + void torrent_handle::add_url_seed(std::string const& url) const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::add_url_seed, _1, url)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->add_url_seed(url); + } + + void torrent_handle::remove_url_seed(std::string const& url) const + { + INVARIANT_CHECK; + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->remove_url_seed(url); + } + + std::set torrent_handle::url_seeds() const + { + INVARIANT_CHECK; + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->url_seeds(); } void torrent_handle::replace_trackers( @@ -466,17 +513,19 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::replace_trackers, _1, urls)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->replace_trackers(urls); } torrent_info const& torrent_handle::get_torrent_info() const { INVARIANT_CHECK; - - if (!has_metadata()) throw_invalid_handle(); - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::torrent_file, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + boost::shared_ptr t = find_torrent(m_ses, m_chk, m_info_hash); + if (!t->valid_metadata()) throw_invalid_handle(); + return t->torrent_file(); } bool torrent_handle::is_valid() const @@ -561,12 +610,12 @@ namespace libtorrent std::string bitmask; const int num_bitmask_bytes - = std::max(num_blocks_per_piece / 8, 1); + = (std::max)(num_blocks_per_piece / 8, 1); for (int j = 0; j < num_bitmask_bytes; ++j) { unsigned char v = 0; - int bits = std::min(num_blocks_per_piece - j*8, 8); + int bits = (std::min)(num_blocks_per_piece - j*8, 8); for (int k = 0; k < bits; ++k) v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) ? (1 << k) : 0; @@ -625,8 +674,9 @@ namespace libtorrent { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::save_path, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->save_path(); } void torrent_handle::connect_peer(tcp::endpoint const& adr, int source) const @@ -693,23 +743,26 @@ namespace libtorrent if (ratio < 1.f && ratio > 0.f) ratio = 1.f; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_ratio, _1, ratio)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_ratio(ratio); } #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void torrent_handle::resolve_countries(bool r) { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::resolve_countries, _1, r)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->resolve_countries(r); } bool torrent_handle::resolve_countries() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::resolving_countries, _1)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->resolving_countries(); } #endif diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index 4ea09aefd..d6dc27fa2 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -400,7 +400,9 @@ namespace libtorrent { if (i->first == "pieces" || i->first == "piece length" - || i->first == "length") + || i->first == "length" +// || i->first == "files" + || i->first == "name") continue; m_extra_info[i->first] = i->second; } @@ -674,7 +676,7 @@ namespace libtorrent { assert(m_piece_length > 0); - if ((m_urls.empty() && m_nodes.empty()) || m_files.empty()) + if (m_files.empty()) { // TODO: throw something here // throw @@ -824,8 +826,33 @@ namespace libtorrent m_nodes.push_back(node); } + bool torrent_info::remap_files(std::vector > const& map) + { + typedef std::vector > files_t; + + size_type offset = 0; + m_remapped_files.resize(map.size()); + + for (int i = 0; i < int(map.size()); ++i) + { + file_entry& fe = m_remapped_files[i]; + fe.path = map[i].first; + fe.offset = offset; + fe.size = map[i].second; + offset += fe.size; + } + if (offset != total_size()) + { + m_remapped_files.clear(); + return false; + } + + return true; + } + std::vector torrent_info::map_block(int piece, size_type offset - , int size) const + , int size, bool storage) const { assert(num_files() > 0); std::vector ret; @@ -839,9 +866,9 @@ namespace libtorrent std::vector::const_iterator file_iter; int counter = 0; - for (file_iter = begin_files();; ++counter, ++file_iter) + for (file_iter = begin_files(storage);; ++counter, ++file_iter) { - assert(file_iter != end_files()); + assert(file_iter != end_files(storage)); if (file_offset < file_iter->size) { file_slice f; @@ -862,11 +889,11 @@ namespace libtorrent } peer_request torrent_info::map_file(int file_index, size_type file_offset - , int size) const + , int size, bool storage) const { - assert(file_index < (int)m_files.size()); + assert(file_index < num_files(storage)); assert(file_index >= 0); - size_type offset = file_offset + m_files[file_index].offset; + size_type offset = file_offset + file_at(file_index, storage).offset; peer_request ret; ret.piece = offset / piece_length(); diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 7bd511588..358ac3838 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -256,7 +256,7 @@ namespace libtorrent { // available input is 1,2 or 3 bytes // since we read 3 bytes at a time at most - int available_input = std::min(3, (int)std::distance(i, s.end())); + int available_input = (std::min)(3, (int)std::distance(i, s.end())); // clear input buffer std::fill(inbuf, inbuf+3, 0); @@ -305,7 +305,7 @@ namespace libtorrent m_start_time = time_now(); m_read_time = time_now(); - m_timeout.expires_at(std::min( + m_timeout.expires_at((std::min)( m_read_time + seconds(m_read_timeout) , m_start_time + seconds(m_completion_timeout))); m_timeout.async_wait(m_strand.wrap(bind( @@ -341,7 +341,7 @@ namespace libtorrent return; } - m_timeout.expires_at(std::min( + m_timeout.expires_at((std::min)( m_read_time + seconds(m_read_timeout) , m_start_time + seconds(m_completion_timeout))); m_timeout.async_wait(m_strand.wrap( diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index aefff41b1..33168c2bd 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -53,38 +53,10 @@ POSSIBILITY OF SUCH DAMAGE. using boost::bind; using namespace libtorrent; -address_v4 upnp::upnp_multicast_address; -udp::endpoint upnp::upnp_multicast_endpoint; - namespace libtorrent { - bool is_local(address const& a) - { - if (a.is_v6()) return false; - address_v4 a4 = a.to_v4(); - unsigned long ip = a4.to_ulong(); - return ((ip & 0xff000000) == 0x0a000000 - || (ip & 0xfff00000) == 0xac100000 - || (ip & 0xffff0000) == 0xc0a80000); - } - - address_v4 guess_local_address(asio::io_service& ios) - { - // make a best guess of the interface we're using and its IP - udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); - for (;i != udp::resolver_iterator(); ++i) - { - // ignore the loopback - if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; - // ignore addresses that are not on a local network - if (!is_local(i->endpoint().address())) continue; - // ignore non-IPv4 addresses - if (i->endpoint().address().is_v4()) break; - } - if (i == udp::resolver_iterator()) return address_v4::any(); - return i->endpoint().address().to_v4(); - } + bool is_local(address const& a); + address_v4 guess_local_address(asio::io_service&); } upnp::upnp(io_service& ios, connection_queue& cc @@ -95,89 +67,27 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_user_agent(user_agent) , m_callback(cb) , m_retry_count(0) - , m_socket(ios) + , m_io_service(ios) + , m_strand(ios) + , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900) + , m_strand.wrap(bind(&upnp::on_reply, this, _1, _2, _3)), false) , m_broadcast_timer(ios) , m_refresh_timer(ios) - , m_strand(ios) , m_disabled(false) , m_closing(false) , m_cc(cc) { - // UPnP multicast address and port - upnp_multicast_address = address_v4::from_string("239.255.255.250"); - upnp_multicast_endpoint = udp::endpoint(upnp_multicast_address, 1900); - #ifdef TORRENT_UPNP_LOGGING m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - rebind(listen_interface); + m_retry_count = 0; + discover_device(); } upnp::~upnp() { } -void upnp::rebind(address const& listen_interface) try -{ - address_v4 bind_to = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) - { - m_local_ip = listen_interface.to_v4(); - bind_to = listen_interface.to_v4(); - if (!is_local(m_local_ip)) - { - // the local address seems to be an external - // internet address. Assume it is not behind a NAT - throw std::runtime_error("local IP is not on a local network"); - } - } - else - { - m_local_ip = guess_local_address(m_socket.io_service()); - bind_to = address_v4::any(); - } - - if (!is_local(m_local_ip)) - { - throw std::runtime_error("local host is probably not on a NATed " - "network. disabling UPnP"); - } - -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " local ip: " << m_local_ip.to_string() - << " bind to: " << bind_to.to_string() << std::endl; -#endif - - // the local interface hasn't changed - if (m_socket.is_open() - && m_socket.local_endpoint().address() == m_local_ip) - return; - - m_socket.close(); - - using namespace asio::ip::multicast; - - m_socket.open(udp::v4()); - m_socket.set_option(datagram_socket::reuse_address(true)); - m_socket.bind(udp::endpoint(bind_to, 0)); - - m_socket.set_option(join_group(upnp_multicast_address)); - m_socket.set_option(outbound_interface(bind_to)); - m_socket.set_option(hops(255)); - m_disabled = false; - - m_retry_count = 0; - discover_device(); -} -catch (std::exception& e) -{ - disable(); - std::stringstream msg; - msg << "UPnP portmapping disabled: " << e.what(); - m_callback(0, 0, msg.str()); -}; - void upnp::discover_device() try { const char msearch[] = @@ -188,20 +98,20 @@ void upnp::discover_device() try "MX:3\r\n" "\r\n\r\n"; - m_socket.async_receive_from(asio::buffer(m_receive_buffer - , sizeof(m_receive_buffer)), m_remote, m_strand.wrap(bind( - &upnp::on_reply, this, _1, _2))); - asio::error_code ec; #ifdef TORRENT_DEBUG_UPNP // simulate packet loss if (m_retry_count & 1) #endif - m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1) - , upnp_multicast_endpoint, 0, ec); + m_socket.send(msearch, sizeof(msearch) - 1, ec); if (ec) { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> Broadcast FAILED: " << ec.message() << std::endl + << "aborting" << std::endl; +#endif disable(); return; } @@ -219,7 +129,7 @@ void upnp::discover_device() try catch (std::exception&) { disable(); -} +}; void upnp::set_mappings(int tcp, int udp) { @@ -292,7 +202,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -313,17 +223,16 @@ try catch (std::exception&) { assert(false); -} +}; #endif -void upnp::on_reply(asio::error_code const& e +void upnp::on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) #ifndef NDEBUG try #endif { using namespace libtorrent::detail; - if (e) return; // parse out the url for the device @@ -338,29 +247,45 @@ try EXT: Cache-Control:max-age=180 DATE: Fri, 02 Jan 1970 08:10:38 GMT + + a notification looks like this: + + NOTIFY * HTTP/1.1 + Host:239.255.255.250:1900 + NT:urn:schemas-upnp-org:device:MediaServer:1 + NTS:ssdp:alive + Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e + USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1 + Cache-Control:max-age=900 + Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0 + */ http_parser p; try { - p.incoming(buffer::const_interval(m_receive_buffer - , m_receive_buffer + bytes_transferred)); + p.incoming(buffer::const_interval(buffer + , buffer + bytes_transferred)); } catch (std::exception& e) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with incorrect HTTP packet: " - << e.what() << ". Ignoring device" << std::endl; + << " <== Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl; #endif return; } - if (p.status_code() != 200) + if (p.status_code() != 200 && p.method() != "notify") { #ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " <== Rootdevice responded with HTTP status: " << p.status_code() - << ". Ignoring device" << std::endl; + if (p.method().empty()) + m_log << time_now_string() + << " <== Device responded with HTTP status: " << p.status_code() + << ". Ignoring device" << std::endl; + else + m_log << time_now_string() + << " <== Device with HTTP method: " << p.method() + << ". Ignoring device" << std::endl; #endif return; } @@ -431,6 +356,8 @@ try { d.mapping[0].need_update = true; d.mapping[0].local_port = m_tcp_local_port; + if (d.mapping[0].external_port == 0) + d.mapping[0].external_port = d.mapping[0].local_port; #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl; #endif @@ -439,6 +366,8 @@ try { d.mapping[1].need_update = true; d.mapping[1].local_port = m_udp_local_port; + if (d.mapping[1].external_port == 0) + d.mapping[1].external_port = d.mapping[1].local_port; #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl; #endif @@ -463,7 +392,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -488,7 +417,7 @@ catch (std::exception&) }; #endif -void upnp::post(rootdevice& d, std::stringstream const& soap +void upnp::post(upnp::rootdevice const& d, std::string const& soap , std::string const& soap_action) { std::stringstream header; @@ -496,12 +425,40 @@ void upnp::post(rootdevice& d, std::stringstream const& soap header << "POST " << d.control_url << " HTTP/1.1\r\n" "Host: " << d.hostname << ":" << d.port << "\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "Content-Length: " << soap.str().size() << "\r\n" - "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str(); + "Content-Length: " << soap.size() << "\r\n" + "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap; d.upnp_connection->sendbuffer = header.str(); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> sending: " << header.str() << std::endl; +#endif + +} + +void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) +{ + std::string soap_action = "AddPortMapping"; + + std::stringstream soap; + + soap << "\n" + "" + ""; + + soap << "" + "" << d.mapping[i].external_port << "" + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" + "" << d.mapping[i].local_port << "" + "" << c.socket().local_endpoint().address().to_string() << "" + "1" + "" << m_user_agent << "" + "" << d.lease_duration << ""; + soap << ""; + + post(d, soap.str(), soap_action); } void upnp::map_port(rootdevice& d, int i) @@ -522,14 +479,21 @@ void upnp::map_port(rootdevice& d, int i) assert(!d.upnp_connection); assert(d.service_namespace); - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2 - , boost::ref(d), i)))); + , boost::ref(d), i)), true + , bind(&upnp::create_port_mapping, this, _1, boost::ref(d), i))); - std::string soap_action = "AddPortMapping"; + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); +} +void upnp::delete_port_mapping(rootdevice& d, int i) +{ std::stringstream soap; + std::string soap_action = "DeletePortMapping"; + soap << "\n" "" @@ -537,20 +501,10 @@ void upnp::map_port(rootdevice& d, int i) soap << "" "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" - "" << d.mapping[i].local_port << "" - "" << m_local_ip.to_string() << "" - "1" - "" << m_user_agent << "" - "" << d.lease_duration << ""; + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; soap << ""; - - post(d, soap, soap_action); -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> AddPortMapping: " << soap.str() << std::endl; -#endif + post(d, soap.str(), soap_action); } // requires the mutex to be locked @@ -568,29 +522,13 @@ void upnp::unmap_port(rootdevice& d, int i) } return; } - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2 - , boost::ref(d), i)))); + , boost::ref(d), i)), true + , bind(&upnp::delete_port_mapping, this, boost::ref(d), i))); - std::string soap_action = "DeletePortMapping"; - - std::stringstream soap; - - soap << "\n" - "" - ""; - - soap << "" - "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; - soap << ""; - - post(d, soap, soap_action); -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> DeletePortMapping: " << soap.str() << std::endl; -#endif + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); } namespace @@ -838,16 +776,9 @@ void upnp::on_upnp_map_response(asio::error_code const& e m_devices.erase(d); return; } - - if (p.status_code() != 200) - { -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " <== error while adding portmap: " << p.message() << std::endl; -#endif - m_devices.erase(d); - return; - } + + // We don't want to ignore responses with return codes other than 200 + // since those might contain valid UPnP error codes error_code_parse_state s; xml_parse((char*)p.get_body().begin, (char*)p.get_body().end @@ -1066,3 +997,4 @@ void upnp::close() } } + diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index 6c6745f30..a307fc9cb 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -69,9 +69,6 @@ namespace libtorrent { INVARIANT_CHECK; - // we always prefer downloading entire - // pieces from web seeds - prefer_whole_pieces(true); // we want large blocks as well, so // we can request more bytes at once request_large_blocks(true); @@ -80,6 +77,10 @@ namespace libtorrent shared_ptr tor = t.lock(); assert(tor); int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); + + // we always prefer downloading 1 MB chunks + // from web seeds + prefer_whole_pieces((1024 * 1024) / tor->torrent_file().piece_length()); // multiply with the blocks per piece since that many requests are // merged into one http request @@ -178,13 +179,16 @@ namespace libtorrent int size = r.length; const int block_size = t->block_size(); + const int piece_size = t->torrent_file().piece_length(); + peer_request pr; while (size > 0) { - int request_size = std::min(block_size, size); - peer_request pr = {r.piece, r.start + r.length - size - , request_size}; + int request_offset = r.start + r.length - size; + pr.start = request_offset % piece_size; + pr.length = (std::min)(block_size, size); + pr.piece = r.piece + request_offset / piece_size; m_requests.push_back(pr); - size -= request_size; + size -= pr.length; } proxy_settings const& ps = m_ses.web_seed_proxy(); @@ -477,8 +481,11 @@ namespace libtorrent peer_request front_request = m_requests.front(); - if (in_range.piece != front_request.piece - || in_range.start > front_request.start + int(m_piece.size())) + size_type rs = size_type(in_range.piece) * info.piece_length() + in_range.start; + size_type re = rs + in_range.length; + size_type fs = size_type(front_request.piece) * info.piece_length() + front_request.start; + size_type fe = fs + front_request.length; + if (fs < rs || fe > re) { throw std::runtime_error("invalid range in HTTP response"); } @@ -486,7 +493,7 @@ namespace libtorrent // skip the http header and the blocks we've already read. The // http_body.begin is now in sync with the request at the front // of the request queue - assert(in_range.start - int(m_piece.size()) <= front_request.start); +// assert(in_range.start - int(m_piece.size()) <= front_request.start); // the http response body consists of 3 parts // 1. the middle of a block or the ending of a block @@ -510,7 +517,7 @@ namespace libtorrent // m_piece as buffer. int piece_size = int(m_piece.size()); - int copy_size = std::min(std::min(front_request.length - piece_size + int copy_size = (std::min)((std::min)(front_request.length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); m_piece.resize(piece_size + copy_size); assert(copy_size > 0); @@ -568,7 +575,7 @@ namespace libtorrent && (m_received_body + recv_buffer.left() >= range_end - range_start)) { int piece_size = int(m_piece.size()); - int copy_size = std::min(std::min(m_requests.front().length - piece_size + int copy_size = (std::min)((std::min)(m_requests.front().length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); assert(copy_size >= 0); if (copy_size > 0) From a9961adda782f4defb88531bf9fb9c9f08c3c0ca Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 00:05:59 +0000 Subject: [PATCH 0106/1009] finish sync --- libtorrent/include/libtorrent/assert.hpp | 6 +- .../include/libtorrent/aux_/session_impl.hpp | 10 +- .../include/libtorrent/bandwidth_manager.hpp | 25 +- .../include/libtorrent/broadcast_socket.hpp | 6 +- libtorrent/include/libtorrent/config.hpp | 1 - .../include/libtorrent/disk_io_thread.hpp | 15 + libtorrent/include/libtorrent/extensions.hpp | 6 + .../extensions/metadata_transfer.hpp | 2 +- .../include/libtorrent/extensions/ut_pex.hpp | 2 +- .../libtorrent/http_tracker_connection.hpp | 1 - .../include/libtorrent/intrusive_ptr_base.hpp | 2 +- libtorrent/include/libtorrent/peer_info.hpp | 3 +- .../include/libtorrent/piece_picker.hpp | 2 +- libtorrent/include/libtorrent/session.hpp | 8 +- .../include/libtorrent/session_impl.hpp | 594 ++++++++++++++++++ libtorrent/include/libtorrent/storage.hpp | 6 +- .../include/libtorrent/torrent_handle.hpp | 1 + .../include/libtorrent/tracker_manager.hpp | 3 +- libtorrent/src/Makefile.am | 3 +- libtorrent/src/assert.cpp | 10 +- libtorrent/src/broadcast_socket.cpp | 84 ++- libtorrent/src/bt_peer_connection.cpp | 16 +- libtorrent/src/disk_io_thread.cpp | 53 +- libtorrent/src/enum_net.cpp | 13 +- libtorrent/src/file.cpp | 19 +- libtorrent/src/http_tracker_connection.cpp | 1 - libtorrent/src/metadata_transfer.cpp | 2 +- libtorrent/src/peer_connection.cpp | 34 +- libtorrent/src/piece_picker.cpp | 87 ++- libtorrent/src/policy.cpp | 44 +- libtorrent/src/session.cpp | 14 +- libtorrent/src/session_impl.cpp | 12 +- libtorrent/src/storage.cpp | 24 +- libtorrent/src/torrent.cpp | 16 +- libtorrent/src/torrent_handle.cpp | 239 +++++-- libtorrent/src/tracker_manager.cpp | 16 +- libtorrent/src/udp_tracker_connection.cpp | 38 +- libtorrent/src/upnp.cpp | 1 - libtorrent/src/ut_pex.cpp | 71 ++- 39 files changed, 1277 insertions(+), 213 deletions(-) create mode 100644 libtorrent/include/libtorrent/session_impl.hpp diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp index 62425809e..6577acc46 100644 --- a/libtorrent/include/libtorrent/assert.hpp +++ b/libtorrent/include/libtorrent/assert.hpp @@ -33,12 +33,14 @@ POSSIBILITY OF SUCH DAMAGE. #include #ifndef NDEBUG -#if defined __linux__ && defined __GNUC__ +#if (defined __linux__ || defined __MACH__) && defined __GNUC__ #ifdef assert #undef assert #endif -void assert_fail(const char* expr, int line, char const* file, char const* function); +#include "libtorrent/config.hpp" + +TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file, char const* function); #define assert(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 0389bf3dc..9300a1ce3 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -185,7 +185,7 @@ namespace libtorrent ~session_impl(); #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*)> ext); + void add_extension(boost::function(torrent*, void*)> ext); #endif void operator()(); @@ -246,7 +246,8 @@ namespace libtorrent , entry const& resume_data , bool compact_mode , storage_constructor_type sc - , bool paused); + , bool paused + , void* userdata); torrent_handle add_torrent( char const* tracker_url @@ -256,7 +257,8 @@ namespace libtorrent , entry const& resume_data , bool compact_mode , storage_constructor_type sc - , bool paused); + , bool paused + , void* userdata); void remove_torrent(torrent_handle const& h); @@ -519,7 +521,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list(torrent*)> > extension_list_t; + torrent_plugin>(torrent*, void*)> > extension_list_t; extension_list_t m_extensions; #endif diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 03d4f65ae..38aa67f43 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -180,6 +180,7 @@ struct bandwidth_manager , m_limit(bandwidth_limit::inf) , m_current_quota(0) , m_channel(channel) + , m_in_hand_out_bandwidth(false) {} void throttle(int limit) throw() @@ -328,6 +329,10 @@ private: void hand_out_bandwidth() throw() { + // if we're already handing out bandwidth, just return back + // to the loop further down on the callstack + if (m_in_hand_out_bandwidth) return; + m_in_hand_out_bandwidth = true; #ifndef NDEBUG try { #endif @@ -361,6 +366,7 @@ private: if (qe.peer->is_disconnecting()) { t->expire_bandwidth(m_channel, qe.max_block_size); + assert(amount == limit - m_current_quota); continue; } @@ -374,6 +380,7 @@ private: if (max_assignable == 0) { t->expire_bandwidth(m_channel, qe.max_block_size); + assert(amount == limit - m_current_quota); continue; } @@ -388,15 +395,15 @@ private: // the history window is one second, and the block will be forgotten // after one second. int block_size = (std::min)(qe.peer->bandwidth_throttle(m_channel) - , m_limit / 10); + , limit / 10); if (block_size < min_bandwidth_block_size) { - block_size = (std::min)(int(min_bandwidth_block_size), m_limit); + block_size = (std::min)(int(min_bandwidth_block_size), limit); } else if (block_size > max_bandwidth_block_size) { - if (m_limit == bandwidth_limit::inf) + if (limit == bandwidth_limit::inf) { block_size = max_bandwidth_block_size; } @@ -407,8 +414,8 @@ private: // as possible // TODO: move this calculcation to where the limit // is changed - block_size = m_limit - / (m_limit / max_bandwidth_block_size); + block_size = limit + / (limit / max_bandwidth_block_size); } } if (block_size > qe.max_block_size) block_size = qe.max_block_size; @@ -428,18 +435,21 @@ private: int hand_out_amount = (std::min)((std::min)(block_size, max_assignable) , amount); assert(hand_out_amount > 0); + assert(amount == limit - m_current_quota); amount -= hand_out_amount; assert(hand_out_amount <= qe.max_block_size); t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); qe.peer->assign_bandwidth(m_channel, hand_out_amount); add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); + assert(amount == limit - m_current_quota); } #ifndef NDEBUG } catch (std::exception& e) { assert(false); }; #endif + m_in_hand_out_bandwidth = false; } @@ -472,6 +482,11 @@ private: // this is the channel within the consumers // that bandwidth is assigned to (upload or download) int m_channel; + + // this is true while we're in the hand_out_bandwidth loop + // to prevent recursive invocations to interfere + bool m_in_hand_out_bandwidth; + }; } diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index bdfe30b6e..23be67b0d 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -42,6 +42,9 @@ namespace libtorrent { bool is_local(address const& a); + bool is_loopback(address const& addr); + bool is_multicast(address const& addr); + address_v4 guess_local_address(asio::io_service&); typedef boost::function -#include "libtorrent/assert.hpp" #if defined(__GNUC__) && __GNUC__ >= 4 diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index 16ee0bca4..61ca9bc53 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -30,6 +30,10 @@ POSSIBILITY OF SUCH DAMAGE. */ +#ifdef TORRENT_DISK_STATS +#include +#endif + #include "libtorrent/storage.hpp" #include #include @@ -50,6 +54,7 @@ namespace libtorrent , buffer_size(0) , piece(0) , offset(0) + , priority(0) {} enum action_t @@ -72,6 +77,12 @@ namespace libtorrent // to the error message std::string str; + // priority decides whether or not this + // job will skip entries in the queue or + // not. It always skips in front of entries + // with lower priority + int priority; + // this is called when operation completes boost::function callback; }; @@ -115,6 +126,10 @@ namespace libtorrent int m_block_size; #endif +#ifdef TORRENT_DISK_STATS + std::ofstream m_log; +#endif + // thread for performing blocking disk io operations boost::thread m_disk_io_thread; }; diff --git a/libtorrent/include/libtorrent/extensions.hpp b/libtorrent/include/libtorrent/extensions.hpp index 44fff9c36..fd48588e1 100644 --- a/libtorrent/include/libtorrent/extensions.hpp +++ b/libtorrent/include/libtorrent/extensions.hpp @@ -149,6 +149,12 @@ namespace libtorrent virtual bool on_cancel(peer_request const& req) { return false; } + virtual bool on_reject(peer_request const& req) + { return false; } + + virtual bool on_suggest(int index) + { return false; } + // called when an extended message is received. If returning true, // the message is not processed by any other plugin and if false // is returned the next plugin in the chain will receive it to diff --git a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp index 210642161..c42136d70 100644 --- a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp +++ b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp @@ -48,7 +48,7 @@ namespace libtorrent { struct torrent_plugin; class torrent; - TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*); + TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*, void*); } #endif // TORRENT_METADATA_TRANSFER_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/extensions/ut_pex.hpp b/libtorrent/include/libtorrent/extensions/ut_pex.hpp index efd9ab4f6..ebf6aa834 100644 --- a/libtorrent/include/libtorrent/extensions/ut_pex.hpp +++ b/libtorrent/include/libtorrent/extensions/ut_pex.hpp @@ -48,7 +48,7 @@ namespace libtorrent { struct torrent_plugin; class torrent; - TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*); + TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*, void*); } #endif // TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index b3f35084c..76c3aac98 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -180,4 +180,3 @@ namespace libtorrent #endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED - diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index ed6944ebb..d2c35ffe3 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -42,7 +42,7 @@ namespace libtorrent template struct intrusive_ptr_base { - intrusive_ptr_base(const intrusive_ptr_base& b) + intrusive_ptr_base(intrusive_ptr_base const&) : m_refs(0) {} friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index 046df2a6b..b07acffd4 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -72,7 +72,8 @@ namespace libtorrent dht = 0x2, pex = 0x4, lsd = 0x8, - resume_data = 0x10 + resume_data = 0x10, + incoming = 0x20 }; int source; diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index 94f274a27..64f6203d5 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -233,7 +233,7 @@ namespace libtorrent bool is_finished(piece_block block) const; // marks this piece-block as queued for downloading - void mark_as_downloading(piece_block block, void* peer + bool mark_as_downloading(piece_block block, void* peer , piece_state_t s); void mark_as_writing(piece_block block, void* peer); void mark_as_finished(piece_block block, void* peer); diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 2ce19349e..3a9eb563b 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -150,7 +150,8 @@ namespace libtorrent , entry const& resume_data = entry() , bool compact_mode = true , bool paused = false - , storage_constructor_type sc = default_storage_constructor); + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); torrent_handle add_torrent( char const* tracker_url @@ -160,7 +161,8 @@ namespace libtorrent , entry const& resume_data = entry() , bool compact_mode = true , bool paused = false - , storage_constructor_type sc = default_storage_constructor); + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); session_proxy abort() { return session_proxy(m_impl); } @@ -181,7 +183,7 @@ namespace libtorrent #endif #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*)> ext); + void add_extension(boost::function(torrent*, void*)> ext); #endif void set_ip_filter(ip_filter const& f); diff --git a/libtorrent/include/libtorrent/session_impl.hpp b/libtorrent/include/libtorrent/session_impl.hpp new file mode 100644 index 000000000..67c3fef1d --- /dev/null +++ b/libtorrent/include/libtorrent/session_impl.hpp @@ -0,0 +1,594 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED +#define TORRENT_SESSION_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/connection_queue.hpp" +#include "libtorrent/disk_io_thread.hpp" + +namespace libtorrent +{ + + namespace fs = boost::filesystem; + + namespace aux + { + struct session_impl; + + // this data is shared between the main thread and the + // thread that initialize pieces + struct piece_checker_data + { + piece_checker_data() + : processing(false), progress(0.f), abort(false) {} + + boost::shared_ptr torrent_ptr; + fs::path save_path; + + sha1_hash info_hash; + + void parse_resume_data( + const entry& rd + , const torrent_info& info + , std::string& error); + + std::vector piece_map; + std::vector unfinished_pieces; + std::vector block_info; + std::vector peers; + entry resume_data; + + // this is true if this torrent is being processed (checked) + // if it is not being processed, then it can be removed from + // the queue without problems, otherwise the abort flag has + // to be set. + bool processing; + + // is filled in by storage::initialize_pieces() + // and represents the progress. It should be a + // value in the range [0, 1] + float progress; + + // abort defaults to false and is typically + // filled in by torrent_handle when the user + // aborts the torrent + bool abort; + }; + + struct checker_impl: boost::noncopyable + { + checker_impl(session_impl& s): m_ses(s), m_abort(false) {} + void operator()(); + piece_checker_data* find_torrent(const sha1_hash& info_hash); + void remove_torrent(sha1_hash const& info_hash); + +#ifndef NDEBUG + void check_invariant() const; +#endif + + // when the files has been checked + // the torrent is added to the session + session_impl& m_ses; + + mutable boost::mutex m_mutex; + boost::condition m_cond; + + // a list of all torrents that are currently in queue + // or checking their files + std::deque > m_torrents; + std::deque > m_processing; + + bool m_abort; + }; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + struct tracker_logger; +#endif + + // this is the link between the main thread and the + // thread started to run the main downloader loop + struct session_impl: boost::noncopyable + { +#ifndef NDEBUG + friend class ::libtorrent::peer_connection; +#endif + friend struct checker_impl; + friend class invariant_access; + typedef std::map + , boost::intrusive_ptr > + connection_map; + typedef std::map > torrent_map; + + session_impl( + std::pair listen_port_range + , fingerprint const& cl_fprint + , char const* listen_interface = "0.0.0.0"); + ~session_impl(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::function(torrent*)> ext); +#endif + void operator()(); + + void open_listen_port(); + + void async_accept(); + void on_incoming_connection(boost::shared_ptr const& s + , boost::weak_ptr const& as, asio::error_code const& e); + + // must be locked to access the data + // in this struct + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + + boost::weak_ptr find_torrent(const sha1_hash& info_hash); + peer_id const& get_peer_id() const { return m_peer_id; } + + void close_connection(boost::intrusive_ptr const& p); + void connection_failed(boost::shared_ptr const& s + , tcp::endpoint const& a, char const* message); + + void set_settings(session_settings const& s); + session_settings const& settings() const { return m_settings; } + +#ifndef TORRENT_DISABLE_DHT + void add_dht_node(std::pair const& node); + void add_dht_node(udp::endpoint n); + void add_dht_router(std::pair const& node); + void set_dht_settings(dht_settings const& s); + dht_settings const& get_dht_settings() const { return m_dht_settings; } + void start_dht(entry const& startup_state); + void stop_dht(); + entry dht_state() const; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + void set_pe_settings(pe_settings const& settings); + pe_settings const& get_pe_settings() const { return m_pe_settings; } +#endif + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); + + bool is_aborted() const { return m_abort; } + + void set_ip_filter(ip_filter const& f); + void set_port_filter(port_filter const& f); + + bool listen_on( + std::pair const& port_range + , const char* net_interface = 0); + bool is_listening() const; + + torrent_handle add_torrent( + torrent_info const& ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc); + + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc); + + void remove_torrent(torrent_handle const& h); + + std::vector get_torrents(); + + void set_severity_level(alert::severity_t s); + std::auto_ptr pop_alert(); + + int upload_rate_limit() const; + int download_rate_limit() const; + + void set_download_rate_limit(int bytes_per_second); + void set_upload_rate_limit(int bytes_per_second); + void set_max_half_open_connections(int limit); + void set_max_connections(int limit); + void set_max_uploads(int limit); + + int max_connections() const { return m_max_connections; } + int max_uploads() const { return m_max_uploads; } + + int num_uploads() const { return m_num_unchoked; } + int num_connections() const + { return m_connections.size(); } + + void unchoke_peer(peer_connection& c) + { + c.send_unchoke(); + ++m_num_unchoked; + } + + session_status status() const; + void set_peer_id(peer_id const& id); + void set_key(int key); + unsigned short listen_port() const; + + void abort(); + + torrent_handle find_torrent_handle(sha1_hash const& info_hash); + + void announce_lsd(sha1_hash const& ih); + + void set_peer_proxy(proxy_settings const& s) + { m_peer_proxy = s; } + void set_web_seed_proxy(proxy_settings const& s) + { m_web_seed_proxy = s; } + void set_tracker_proxy(proxy_settings const& s) + { m_tracker_proxy = s; } + + proxy_settings const& peer_proxy() const + { return m_peer_proxy; } + proxy_settings const& web_seed_proxy() const + { return m_web_seed_proxy; } + proxy_settings const& tracker_proxy() const + { return m_tracker_proxy; } + +#ifndef TORRENT_DISABLE_DHT + void set_dht_proxy(proxy_settings const& s) + { m_dht_proxy = s; } + proxy_settings const& dht_proxy() const + { return m_dht_proxy; } +#endif + + void start_lsd(); + void start_natpmp(); + void start_upnp(); + + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + // handles delayed alerts + alert_manager m_alerts; + +// private: + + void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); + + // this is where all active sockets are stored. + // the selector can sleep while there's no activity on + // them + io_service m_io_service; + asio::strand m_strand; + + // the file pool that all storages in this session's + // torrents uses. It sets a limit on the number of + // open files by this session. + // file pool must be destructed after the torrents + // since they will still have references to it + // when they are destructed. + file_pool m_files; + + // handles disk io requests asynchronously + disk_io_thread m_disk_thread; + + // this is a list of half-open tcp connections + // (only outgoing connections) + // this has to be one of the last + // members to be destructed + connection_queue m_half_open; + + // the bandwidth manager is responsible for + // handing out bandwidth to connections that + // asks for it, it can also throttle the + // rate. + bandwidth_manager m_download_channel; + bandwidth_manager m_upload_channel; + + bandwidth_manager* m_bandwidth_manager[2]; + + tracker_manager m_tracker_manager; + torrent_map m_torrents; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + + // filters incoming connections + ip_filter m_ip_filter; + + // filters outgoing connections + port_filter m_port_filter; + + // the peer id that is generated at the start of the session + peer_id m_peer_id; + + // the key is an id that is used to identify the + // client with the tracker only. It is randomized + // at startup + int m_key; + + // the range of ports we try to listen on + std::pair m_listen_port_range; + + // the ip-address of the interface + // we are supposed to listen on. + // if the ip is set to zero, it means + // that we should let the os decide which + // interface to listen on + tcp::endpoint m_listen_interface; + + // this is typically set to the same as the local + // listen port. In case a NAT port forward was + // successfully opened, this will be set to the + // port that is open on the external (NAT) interface + // on the NAT box itself. This is the port that has + // to be published to peers, since this is the port + // the client is reachable through. + int m_external_listen_port; + + boost::shared_ptr m_listen_socket; + + // the settings for the client + session_settings m_settings; + // the proxy settings for different + // kinds of connections + proxy_settings m_peer_proxy; + proxy_settings m_web_seed_proxy; + proxy_settings m_tracker_proxy; +#ifndef TORRENT_DISABLE_DHT + proxy_settings m_dht_proxy; +#endif + + // set to true when the session object + // is being destructed and the thread + // should exit + volatile bool m_abort; + + int m_max_uploads; + int m_max_connections; + + // the number of unchoked peers + int m_num_unchoked; + + // this is initialized to the unchoke_interval + // session_setting and decreased every second. + // when it reaches zero, it is reset to the + // unchoke_interval and the unchoke set is + // recomputed. + int m_unchoke_time_scaler; + + // works like unchoke_time_scaler but it + // is only decresed when the unchoke set + // is recomputed, and when it reaches zero, + // the optimistic unchoke is moved to another peer. + int m_optimistic_unchoke_time_scaler; + + // works like unchoke_time_scaler. Each time + // it reaches 0, and all the connections are + // used, the worst connection will be disconnected + // from the torrent with the most peers + int m_disconnect_time_scaler; + + // statistics gathered from all torrents. + stat m_stat; + + // is false by default and set to true when + // the first incoming connection is established + // this is used to know if the client is behind + // NAT or not. + bool m_incoming_connection; + + void second_tick(asio::error_code const& e); + ptime m_last_tick; + +#ifndef TORRENT_DISABLE_DHT + boost::intrusive_ptr m_dht; + dht_settings m_dht_settings; + // if this is set to true, the dht listen port + // will be set to the same as the tcp listen port + // and will be synchronlized with it as it changes + // it defaults to true + bool m_dht_same_port; + + // see m_external_listen_port. This is the same + // but for the udp port used by the DHT. + int m_external_udp_port; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + pe_settings m_pe_settings; +#endif + + boost::shared_ptr m_natpmp; + boost::shared_ptr m_upnp; + boost::shared_ptr m_lsd; + + // the timer used to fire the second_tick + deadline_timer m_timer; + + // the index of the torrent that will be offered to + // connect to a peer next time second_tick is called. + // This implements a round robin. + int m_next_connect_torrent; +#ifndef NDEBUG + void check_invariant(const char *place = 0); +#endif + +#ifdef TORRENT_STATS + // logger used to write bandwidth usage statistics + std::ofstream m_stats_logger; + int m_second_counter; +#endif +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr create_log(std::string const& name + , int instance, bool append = true); + + // this list of tracker loggers serves as tracker_callbacks when + // shutting down. This list is just here to keep them alive during + // whe shutting down process + std::list > m_tracker_loggers; + + public: + boost::shared_ptr m_logger; + private: +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list(torrent*)> > extension_list_t; + + extension_list_t m_extensions; +#endif + + // data shared between the main thread + // and the checker thread + checker_impl m_checker_impl; + + // the main working thread + boost::scoped_ptr m_thread; + + // the thread that calls initialize_pieces() + // on all torrents before they start downloading + boost::scoped_ptr m_checker_thread; + }; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + struct tracker_logger : request_callback + { + tracker_logger(session_impl& ses): m_ses(ses) {} + void tracker_warning(std::string const& str) + { + debug_log("*** tracker warning: " + str); + } + + void tracker_response(tracker_request const& + , std::vector& peers + , int interval + , int complete + , int incomplete) + { + std::stringstream s; + s << "TRACKER RESPONSE:\n" + "interval: " << interval << "\n" + "peers:\n"; + for (std::vector::const_iterator i = peers.begin(); + i != peers.end(); ++i) + { + s << " " << std::setfill(' ') << std::setw(16) << i->ip + << " " << std::setw(5) << std::dec << i->port << " "; + if (!i->pid.is_all_zeros()) s << " " << i->pid; + s << "\n"; + } + debug_log(s.str()); + } + + void tracker_request_timed_out( + tracker_request const&) + { + debug_log("*** tracker timed out"); + } + + void tracker_request_error( + tracker_request const& + , int response_code + , const std::string& str) + { + debug_log(std::string("*** tracker error: ") + + boost::lexical_cast(response_code) + ": " + + str); + } + + void debug_log(const std::string& line) + { + (*m_ses.m_logger) << line << "\n"; + } + session_impl& m_ses; + }; +#endif + + } +} + + +#endif + diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index e52196c76..9db79ea3d 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -202,7 +202,8 @@ namespace libtorrent void async_read( peer_request const& r , boost::function const& handler - , char* buffer = 0); + , char* buffer = 0 + , int priority = 0); void async_write( peer_request const& r @@ -343,7 +344,8 @@ namespace libtorrent std::multimap m_hash_to_piece; // this map contains partial hashes for downloading - // pieces. + // pieces. This is only accessed from within the + // disk-io thread. std::map m_piece_hasher; disk_io_thread& m_io_thread; diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 31a39c38e..287c57305 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -399,6 +399,7 @@ namespace libtorrent , m_info_hash(h) { assert(m_ses != 0); + assert(m_chk != 0); } #ifndef NDEBUG diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index 1435ceda6..57f7bd851 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -194,11 +194,10 @@ namespace libtorrent , address bind_interface , boost::weak_ptr r); - request_callback& requester(); + boost::shared_ptr requester(); virtual ~tracker_connection() {} tracker_request const& tracker_req() const { return m_req; } - bool has_requester() const { return !m_requester.expired(); } void fail(int code, char const* msg); void fail_timeout(); diff --git a/libtorrent/src/Makefile.am b/libtorrent/src/Makefile.am index 671cb75e5..3ab9f73bd 100644 --- a/libtorrent/src/Makefile.am +++ b/libtorrent/src/Makefile.am @@ -13,7 +13,7 @@ kademlia/traversal_algorithm.cpp endif libtorrent_la_SOURCES = entry.cpp escape_string.cpp \ -enum_net.cpp broadcast_socket.cpp \ +assert.cpp enum_net.cpp broadcast_socket.cpp \ peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \ stat.cpp storage.cpp torrent.cpp torrent_handle.cpp pe_crypto.cpp \ @@ -28,6 +28,7 @@ $(kademlia_sources) noinst_HEADERS = \ $(top_srcdir)/include/libtorrent/alert.hpp \ $(top_srcdir)/include/libtorrent/alert_types.hpp \ +$(top_srcdir)/include/libtorrent/assert.hpp \ $(top_srcdir)/include/libtorrent/aux_/session_impl.hpp \ $(top_srcdir)/include/libtorrent/bandwidth_manager.hpp \ $(top_srcdir)/include/libtorrent/bencode.hpp \ diff --git a/libtorrent/src/assert.cpp b/libtorrent/src/assert.cpp index da79a745b..b4f011978 100644 --- a/libtorrent/src/assert.cpp +++ b/libtorrent/src/assert.cpp @@ -34,7 +34,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include +#if defined __linux__ && defined __GNUC__ #include +#endif void assert_fail(char const* expr, int line, char const* file, char const* function) { @@ -48,6 +51,7 @@ void assert_fail(char const* expr, int line, char const* file, char const* funct "expression: %s\n" "stack:\n", file, line, function, expr); +#if defined __linux__ && defined __GNUC__ void* stack[50]; int size = backtrace(stack, 50); char** symbols = backtrace_symbols(stack, size); @@ -58,7 +62,11 @@ void assert_fail(char const* expr, int line, char const* file, char const* funct } free(symbols); - exit(1); +#endif + // send SIGINT to the current process + // to break into the debugger + raise(SIGINT); + abort(); } #endif diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index a937fc11b..3aaadcc81 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -43,7 +43,7 @@ namespace libtorrent { bool is_local(address const& a) { - if (a.is_v6()) return false; + if (a.is_v6()) return a.to_v6().is_link_local(); address_v4 a4 = a.to_v4(); unsigned long ip = a4.to_ulong(); return ((ip & 0xff000000) == 0x0a000000 @@ -58,25 +58,40 @@ namespace libtorrent udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); for (;i != udp::resolver_iterator(); ++i) { - // ignore the loopback - if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; - // ignore addresses that are not on a local network - if (!is_local(i->endpoint().address())) continue; + address const& a = i->endpoint().address(); // ignore non-IPv4 addresses - if (i->endpoint().address().is_v4()) break; + if (!a.is_v4()) break; + // ignore the loopback + if (a.to_v4() == address_v4::loopback()) continue; } if (i == udp::resolver_iterator()) return address_v4::any(); return i->endpoint().address().to_v4(); } + bool is_loopback(address const& addr) + { + if (addr.is_v4()) + return addr.to_v4() == address_v4::loopback(); + else + return addr.to_v6() == address_v6::loopback(); + } + + bool is_multicast(address const& addr) + { + if (addr.is_v4()) + return addr.to_v4().is_multicast(); + else + return addr.to_v6().is_multicast(); + } + broadcast_socket::broadcast_socket(asio::io_service& ios , udp::endpoint const& multicast_endpoint - , receive_handler_t const& handler) + , receive_handler_t const& handler + , bool loopback) : m_multicast_endpoint(multicast_endpoint) , m_on_receive(handler) { - assert(m_multicast_endpoint.address().is_v4()); - assert(m_multicast_endpoint.address().to_v4().is_multicast()); + assert(is_multicast(m_multicast_endpoint.address())); using namespace asio::ip::multicast; @@ -87,26 +102,51 @@ namespace libtorrent , end(interfaces.end()); i != end; ++i) { // only broadcast to IPv4 addresses that are not local - if (!i->is_v4() || !is_local(*i)) continue; - // ignore the loopback interface - if (i->to_v4() == address_v4((127 << 24) + 1)) continue; + if (!is_local(*i)) continue; + // only multicast on compatible networks + if (i->is_v4() != multicast_endpoint.address().is_v4()) continue; + // ignore any loopback interface + if (is_loopback(*i)) continue; boost::shared_ptr s(new datagram_socket(ios)); - s->open(udp::v4(), ec); + if (i->is_v4()) + { + s->open(udp::v4(), ec); + if (ec) continue; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) continue; + s->bind(udp::endpoint(address_v4::any(), multicast_endpoint.port()), ec); + if (ec) continue; + s->set_option(join_group(multicast_endpoint.address()), ec); + if (ec) continue; + s->set_option(outbound_interface(i->to_v4()), ec); + if (ec) continue; + } + else + { + s->open(udp::v6(), ec); + if (ec) continue; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) continue; + s->bind(udp::endpoint(address_v6::any(), multicast_endpoint.port()), ec); + if (ec) continue; + s->set_option(join_group(multicast_endpoint.address()), ec); + if (ec) continue; +// s->set_option(outbound_interface(i->to_v6()), ec); +// if (ec) continue; + } + s->set_option(hops(255), ec); if (ec) continue; - s->set_option(datagram_socket::reuse_address(true), ec); + s->set_option(enable_loopback(loopback), ec); if (ec) continue; - s->bind(udp::endpoint(*i, 0), ec); - if (ec) continue; - s->set_option(join_group(multicast_endpoint.address()), ec); - if (ec) continue; - s->set_option(outbound_interface(i->to_v4()), ec); - if (ec) continue; - s->set_option(hops(255)); m_sockets.push_back(socket_entry(s)); socket_entry& se = m_sockets.back(); s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); +#ifndef NDEBUG +// std::cerr << "broadcast socket [ if: " << i->to_v4().to_string() +// << " group: " << multicast_endpoint.address() << " ]" << std::endl; +#endif } } @@ -117,7 +157,9 @@ namespace libtorrent { asio::error_code e; i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); +#ifndef NDEBUG // std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; +#endif if (e) ec = e; } } diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 11a39675f..ab61d98f9 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -50,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION #include "libtorrent/pe_crypto.hpp" @@ -1221,7 +1222,7 @@ namespace libtorrent { tcp::endpoint adr(remote().address() , (unsigned short)listen_port->integer()); - t->get_policy().peer_from_tracker(adr, pid(), 0, 0); + t->get_policy().peer_from_tracker(adr, pid(), peer_info::incoming, 0); } } // there should be a version too @@ -1474,6 +1475,19 @@ namespace libtorrent detail::write_address(remote().address(), out); handshake["yourip"] = remote_address; handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; + asio::error_code ec; + std::vector
const& interfaces = enum_net_interfaces(get_socket()->io_service(), ec); + for (std::vector
::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + // TODO: only use global IPv6 addresses + if (!i->is_v6() || i->to_v6().is_link_local()) continue; + std::string ipv6_address; + std::back_insert_iterator out(ipv6_address); + detail::write_address(*i, out); + handshake["ipv6"] = ipv6_address; + break; + } // loop backwards, to make the first extension be the last // to fill in the handshake (i.e. give the first extensions priority) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index e07a884cf..4fb2cfb3a 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -34,6 +34,24 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/disk_io_thread.hpp" +#ifdef TORRENT_DISK_STATS + +#include "libtorrent/time.hpp" +#include + +namespace +{ + std::string log_time() + { + using namespace libtorrent; + static ptime start = time_now(); + return boost::lexical_cast( + total_milliseconds(time_now() - start)); + } +} + +#endif + namespace libtorrent { @@ -45,7 +63,12 @@ namespace libtorrent , m_block_size(block_size) #endif , m_disk_io_thread(boost::ref(*this)) - {} + { + +#ifdef TORRENT_DISK_STATS + m_log.open("disk_io_thread.log", std::ios::trunc); +#endif + } disk_io_thread::~disk_io_thread() { @@ -89,8 +112,15 @@ namespace libtorrent namespace { + // The semantic of this operator is: + // shouls lhs come before rhs in the job queue bool operator<(disk_io_job const& lhs, disk_io_job const& rhs) { + // NOTE: comparison inverted to make higher priority + // skip _in_front_of_ lower priority + if (lhs.priority > rhs.priority) return true; + if (lhs.priority < rhs.priority) return false; + if (lhs.storage.get() < rhs.storage.get()) return true; if (lhs.storage.get() > rhs.storage.get()) return false; if (lhs.piece < rhs.piece) return true; @@ -165,6 +195,9 @@ namespace libtorrent { for (;;) { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " idle" << std::endl; +#endif boost::mutex::scoped_lock l(m_mutex); while (m_jobs.empty() && !m_abort) m_signal.wait(l); @@ -182,10 +215,16 @@ namespace libtorrent bool free_buffer = true; try { +#ifdef TORRENT_DISK_STATS + ptime start = time_now(); +#endif // std::cerr << "DISK THREAD: executing job: " << j.action << std::endl; switch (j.action) { case disk_io_job::read: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " read " << j.buffer_size << std::endl; +#endif if (j.buffer == 0) { l.lock(); @@ -210,6 +249,9 @@ namespace libtorrent // usleep(300); break; case disk_io_job::write: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " write " << j.buffer_size << std::endl; +#endif assert(j.buffer); assert(j.buffer_size <= m_block_size); j.storage->write_impl(j.buffer, j.piece, j.offset @@ -220,16 +262,25 @@ namespace libtorrent break; case disk_io_job::hash: { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " hash" << std::endl; +#endif sha1_hash h = j.storage->hash_for_piece_impl(j.piece); j.str.resize(20); std::memcpy(&j.str[0], &h[0], 20); } break; case disk_io_job::move_storage: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " move" << std::endl; +#endif ret = j.storage->move_storage_impl(j.str) ? 1 : 0; j.str = j.storage->save_path().string(); break; case disk_io_job::release_files: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " release" << std::endl; +#endif j.storage->release_files_impl(); break; } diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index 54af814d6..172719793 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -72,8 +72,17 @@ namespace libtorrent ifreq const& item = *reinterpret_cast(ifr); if (item.ifr_addr.sa_family == AF_INET) { - ret.push_back(address::from_string( - inet_ntoa(((sockaddr_in const*)&item.ifr_addr)->sin_addr))); + typedef asio::ip::address_v4::bytes_type bytes_t; + bytes_t b; + memcpy(&b[0], &((sockaddr_in const*)&item.ifr_addr)->sin_addr, b.size()); + ret.push_back(address_v4(b)); + } + else if (item.ifr_addr.sa_family == AF_INET6) + { + typedef asio::ip::address_v6::bytes_type bytes_t; + bytes_t b; + memcpy(&b[0], &((sockaddr_in6 const*)&item.ifr_addr)->sin6_addr, b.size()); + ret.push_back(address_v6(b)); } #if defined __MACH__ || defined(__FreeBSD__) diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 3d568d1f7..72876d528 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -247,19 +247,16 @@ namespace libtorrent void set_size(size_type s) { - size_type pos = tell(); - // Only set size if current file size not equals s. - // 2 as "m" argument is to be sure seek() sets SEEK_END on - // all compilers. - if(s != seek(0, 2)) +#ifdef _WIN32 +#error file.cpp is for posix systems only. use file_win.cpp on windows +#else + if (ftruncate(m_fd, s) < 0) { - seek(s - 1); - char dummy = 0; - read(&dummy, 1); - seek(s - 1); - write(&dummy, 1); + std::stringstream msg; + msg << "ftruncate failed: '" << strerror(errno); + throw file_error(msg.str()); } - seek(pos); +#endif } size_type seek(size_type offset, int m = 1) diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 2a3c4449a..0a0e59c48 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -952,4 +952,3 @@ namespace libtorrent } - diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index a19dd3d3f..0623b156f 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -556,7 +556,7 @@ namespace libtorrent { namespace namespace libtorrent { - boost::shared_ptr create_metadata_plugin(torrent* t) + boost::shared_ptr create_metadata_plugin(torrent* t, void*) { return boost::shared_ptr(new metadata_plugin(*t)); } diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index ad2102f0d..25bc0ba63 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -687,6 +687,14 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); assert(t); +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_reject(r)) return; + } +#endif + std::deque::iterator i = std::find_if( m_download_queue.begin(), m_download_queue.end() , bind(match_request, boost::cref(r), _1, t->block_size())); @@ -743,7 +751,7 @@ namespace libtorrent void peer_connection::incoming_suggest(int index) { INVARIANT_CHECK; - + #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " <== SUGGEST_PIECE [ piece: " << index << " ]\n"; @@ -751,6 +759,14 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); if (!t) return; +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_suggest(index)) return; + } +#endif + if (t->have_piece(index)) return; if (m_suggested_pieces.size() > 9) @@ -1647,7 +1663,9 @@ namespace libtorrent state = piece_picker::slow; } - t->picker().mark_as_downloading(block, peer_info_struct(), state); + if (!t->picker().mark_as_downloading(block, peer_info_struct(), state)) + return; + if (t->alerts().should_post(alert::info)) { t->alerts().post_alert(block_downloading_alert(t->get_handle(), @@ -1934,7 +1952,6 @@ namespace libtorrent } t->remove_peer(this); - m_torrent.reset(); } @@ -2838,6 +2855,8 @@ namespace libtorrent return; } + assert(t->connection_for(remote()) != 0 || m_in_constructor); + if (!m_in_constructor && t->connection_for(remote()) != this && !m_ses.settings().allow_multiple_connections_per_ip) { @@ -2897,11 +2916,6 @@ namespace libtorrent // TODO: the timeout should be called by an event INVARIANT_CHECK; -#ifndef NDEBUG - // allow step debugging without timing out - return false; -#endif - ptime now(time_now()); // if the socket is still connecting, don't @@ -2915,6 +2929,10 @@ namespace libtorrent d = now - m_last_receive; if (d > seconds(m_timeout)) return true; + // if it takes more than 5 seconds to receive + // handshake, disconnect + if (in_handshake() && d > seconds(5)) return true; + // disconnect peers that we unchoked, but // they didn't send a request within 20 seconds. // but only if we're a seed diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 8fa623fa3..398573d33 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -1229,6 +1229,7 @@ namespace libtorrent bool piece_picker::can_pick(int piece, std::vector const& bitmask) const { + assert(piece >= 0 && piece < int(m_piece_map.size())); return bitmask[piece] && !m_piece_map[piece].have() && !m_piece_map[piece].downloading @@ -1375,10 +1376,8 @@ namespace libtorrent { // ignore completed blocks and already requested blocks block_info const& info = i->info[j]; - if (info.state == block_info::state_finished - || info.state == block_info::state_writing - || info.state == block_info::state_requested) - continue; + if (info.state != block_info::state_none) + continue; assert(i->info[j].state == block_info::state_none); @@ -1432,6 +1431,80 @@ namespace libtorrent if (num_blocks <= 0) return 0; + if (prefer_whole_pieces > 0) + { + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + int num_blocks_in_piece = blocks_in_piece(i->index); + bool exclusive; + bool exclusive_active; + boost::tie(exclusive, exclusive_active) + = requested_from(*i, num_blocks_in_piece, peer); + + if (exclusive_active) continue; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = i->info[j]; + if (info.state != block_info::state_none) continue; + backup_blocks.push_back(piece_block(i->index, j)); + } + } + } + + if (int(backup_blocks.size()) >= num_blocks) return num_blocks; + + +#ifndef NDEBUG +// make sure that we at this point has added requests to all unrequested blocks +// in all downloading pieces + + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + + int num_blocks_in_piece = blocks_in_piece(i->index); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = i->info[j]; + if (info.state != block_info::state_none) continue; + std::vector::iterator k = std::find( + interesting_blocks.begin(), interesting_blocks.end() + , piece_block(i->index, j)); + if (k != interesting_blocks.end()) continue; + + k = std::find(backup_blocks.begin() + , backup_blocks.end(), piece_block(i->index, j)); + if (k != backup_blocks.end()) continue; + + std::cerr << "interesting blocks:" << std::endl; + for (k = interesting_blocks.begin(); k != interesting_blocks.end(); ++k) + std::cerr << "(" << k->piece_index << ", " << k->block_index << ") "; + std::cerr << std::endl; + std::cerr << "backup blocks:" << std::endl; + for (k = backup_blocks.begin(); k != backup_blocks.end(); ++k) + std::cerr << "(" << k->piece_index << ", " << k->block_index << ") "; + std::cerr << std::endl; + std::cerr << "num_blocks: " << num_blocks << std::endl; + + for (std::vector::const_iterator l = m_downloads.begin() + , end(m_downloads.end()); l != end; ++l) + { + std::cerr << l->index << " : "; + int num_blocks_in_piece = blocks_in_piece(l->index); + for (int m = 0; m < num_blocks_in_piece; ++m) + std::cerr << l->info[m].state; + std::cerr << std::endl; + } + + assert(false); + } + } +#endif + for (std::vector::const_iterator i = m_downloads.begin() , end(m_downloads.end()); i != end; ++i) { @@ -1554,7 +1627,7 @@ namespace libtorrent } - void piece_picker::mark_as_downloading(piece_block block + bool piece_picker::mark_as_downloading(piece_block block , void* peer, piece_state_t state) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1589,6 +1662,9 @@ namespace libtorrent = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); assert(i != m_downloads.end()); block_info& info = i->info[block.block_index]; + if (info.state == block_info::state_writing + || info.state == block_info::state_finished) + return false; assert(info.state == block_info::state_none || (info.state == block_info::state_requested && (info.num_peers > 0))); @@ -1601,6 +1677,7 @@ namespace libtorrent ++info.num_peers; if (i->state == none) i->state = state; } + return true; } int piece_picker::num_peers(piece_block block) const diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 6e81da0d5..4faed837e 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -138,28 +138,28 @@ namespace return free_upload; } - struct match_peer_ip + struct match_peer_address { - match_peer_ip(address const& ip) - : m_ip(ip) + match_peer_address(address const& addr) + : m_addr(addr) {} bool operator()(policy::peer const& p) const - { return p.ip.address() == m_ip; } + { return p.ip.address() == m_addr; } - address const& m_ip; + address const& m_addr; }; - struct match_peer_id + struct match_peer_endpoint { - match_peer_id(peer_id const& id_) - : m_id(id_) + match_peer_endpoint(tcp::endpoint const& ep) + : m_ep(ep) {} bool operator()(policy::peer const& p) const - { return p.connection && p.connection->pid() == m_id; } + { return p.ip == m_ep; } - peer_id const& m_id; + tcp::endpoint const& m_ep; }; struct match_peer_connection @@ -939,7 +939,7 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_ip(c.remote().address())); + , match_peer_address(c.remote().address())); } if (i != m_peers.end()) @@ -1029,14 +1029,14 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_id(pid)); + , match_peer_endpoint(remote)); } else { i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_ip(remote.address())); + , match_peer_address(remote.address())); } if (i == m_peers.end()) @@ -1291,9 +1291,15 @@ namespace libtorrent try { + INVARIANT_CHECK; p->connected = time_now(); p->connection = m_torrent->connect_to_peer(&*p); - if (p->connection == 0) return false; + assert(p->connection == m_torrent->connection_for(p->ip)); + if (p->connection == 0) + { + ++p->failcount; + return false; + } p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); p->prev_amount_download = 0; p->prev_amount_upload = 0; @@ -1305,6 +1311,7 @@ namespace libtorrent (*m_torrent->session().m_logger) << "*** CONNECTION FAILED '" << e.what() << "'\n"; #endif + std::cerr << e.what() << std::endl; ++p->failcount; return false; } @@ -1402,15 +1409,22 @@ namespace libtorrent int nonempty_connections = 0; std::set
unique_test; + std::set unique_test2; for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { peer const& p = *i; if (!m_torrent->settings().allow_multiple_connections_per_ip) assert(unique_test.find(p.ip.address()) == unique_test.end()); + assert(unique_test2.find(p.ip) == unique_test2.end()); unique_test.insert(p.ip.address()); + unique_test2.insert(p.ip); ++total_connections; - if (!p.connection) continue; + if (!p.connection) + { +// assert(m_torrent->connection_for(p.ip) == 0); + continue; + } if (!m_torrent->settings().allow_multiple_connections_per_ip) { std::vector conns; diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 1d7a070c2..6298b3c2c 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -132,7 +132,7 @@ namespace libtorrent m_impl->abort(); } - void session::add_extension(boost::function(torrent*)> ext) + void session::add_extension(boost::function(torrent*, void*)> ext) { m_impl->add_extension(ext); } @@ -185,7 +185,7 @@ namespace libtorrent assert(!ti.m_half_metadata); boost::intrusive_ptr tip(new torrent_info(ti)); return m_impl->add_torrent(tip, save_path, resume_data - , compact_mode, sc, paused); + , compact_mode, sc, paused, 0); } torrent_handle session::add_torrent( @@ -194,11 +194,12 @@ namespace libtorrent , entry const& resume_data , bool compact_mode , bool paused - , storage_constructor_type sc) + , storage_constructor_type sc + , void* userdata) { assert(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data - , compact_mode, sc, paused); + , compact_mode, sc, paused, userdata); } torrent_handle session::add_torrent( @@ -209,10 +210,11 @@ namespace libtorrent , entry const& e , bool compact_mode , bool paused - , storage_constructor_type sc) + , storage_constructor_type sc + , void* userdata) { return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e - , compact_mode, sc, paused); + , compact_mode, sc, paused, userdata); } void session::remove_torrent(const torrent_handle& h) diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 0aeb84bbe..6e1129b99 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -593,7 +593,7 @@ namespace detail #ifndef TORRENT_DISABLE_EXTENSIONS void session_impl::add_extension( - boost::function(torrent*)> ext) + boost::function(torrent*, void*)> ext) { m_extensions.push_back(ext); } @@ -1474,7 +1474,8 @@ namespace detail , entry const& resume_data , bool compact_mode , storage_constructor_type sc - , bool paused) + , bool paused + , void* userdata) { // if you get this assert, you haven't managed to // open a listen port. call listen_on() first. @@ -1514,7 +1515,7 @@ namespace detail for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - boost::shared_ptr tp((*i)(torrent_ptr.get())); + boost::shared_ptr tp((*i)(torrent_ptr.get(), userdata)); if (tp) torrent_ptr->add_extension(tp); } #endif @@ -1554,7 +1555,8 @@ namespace detail , entry const& , bool compact_mode , storage_constructor_type sc - , bool paused) + , bool paused + , void* userdata) { // TODO: support resume data in this case @@ -1593,7 +1595,7 @@ namespace detail for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - boost::shared_ptr tp((*i)(torrent_ptr.get())); + boost::shared_ptr tp((*i)(torrent_ptr.get(), userdata)); if (tp) torrent_ptr->add_extension(tp); } #endif diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index e1ddd20ae..dbf6a9382 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -403,13 +403,19 @@ namespace libtorrent assert(ph.offset == 0 || partial_copy.final() == partial.final()); #endif int slot_size = piece_size - ph.offset; - if (slot_size == 0) return ph.h.final(); - m_scratch_buffer.resize(slot_size); - read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); - ph.h.update(&m_scratch_buffer[0], slot_size); + if (slot_size > 0) + { + m_scratch_buffer.resize(slot_size); + read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); + ph.h.update(&m_scratch_buffer[0], slot_size); + } +#ifndef NDEBUG sha1_hash ret = ph.h.final(); - assert(whole.final() == ret); + assert(ret == whole.final()); return ret; +#else + return ph.h.final(); +#endif } void storage::initialize(bool allocate_files) @@ -996,9 +1002,6 @@ namespace libtorrent int err = statfs(query_path.native_directory_string().c_str(), &buf); if (err == 0) { -#ifndef NDEBUG - std::cerr << "buf.f_type " << std::hex << buf.f_type << std::endl; -#endif switch (buf.f_type) { case 0x5346544e: // NTFS @@ -1084,7 +1087,8 @@ namespace libtorrent void piece_manager::async_read( peer_request const& r , boost::function const& handler - , char* buffer) + , char* buffer + , int priority) { disk_io_job j; j.storage = this; @@ -1093,6 +1097,7 @@ namespace libtorrent j.offset = r.start; j.buffer_size = r.length; j.buffer = buffer; + j.priority = priority; // if a buffer is not specified, only one block can be read // since that is the size of the pool allocator's buffers assert(r.length <= 16 * 1024 || buffer != 0); @@ -1295,6 +1300,7 @@ namespace libtorrent if (i != m_piece_hasher.end()) { assert(i->second.offset > 0); + assert(offset >= i->second.offset); if (offset == i->second.offset) { i->second.offset += size; diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index ddf8a9164..252461bc6 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -1653,6 +1653,7 @@ namespace libtorrent assert(m_connections.find(a) == m_connections.end()); // add the newly connected peer to this torrent's peer list + assert(m_connections.find(a) == m_connections.end()); m_connections.insert( std::make_pair(a, boost::get_pointer(c))); m_ses.m_connections.insert(std::make_pair(s, c)); @@ -1669,8 +1670,8 @@ namespace libtorrent #endif // TODO: post an error alert! - std::map::iterator i = m_connections.find(a); - if (i != m_connections.end()) m_connections.erase(i); +// std::map::iterator i = m_connections.find(a); +// if (i != m_connections.end()) m_connections.erase(i); m_ses.connection_failed(s, a, e.what()); c->disconnect(); } @@ -1857,6 +1858,8 @@ namespace libtorrent try { + assert(m_connections.find(a) == m_connections.end()); + // add the newly connected peer to this torrent's peer list m_connections.insert( std::make_pair(a, boost::get_pointer(c))); @@ -1869,6 +1872,7 @@ namespace libtorrent } catch (std::exception& e) { + assert(false); // TODO: post an error alert! std::map::iterator i = m_connections.find(a); if (i != m_connections.end()) m_connections.erase(i); @@ -1925,6 +1929,7 @@ namespace libtorrent = m_connections.find(p->remote()); if (c != m_connections.end()) { + assert(p != c->second); // we already have a peer_connection to this ip. // It may currently be waiting for completing a // connection attempt that might fail. So, @@ -1948,6 +1953,7 @@ namespace libtorrent throw protocol_error("session is closing"); } + assert(m_connections.find(p->remote()) == m_connections.end()); peer_iterator ci = m_connections.insert( std::make_pair(p->remote(), p)).first; try @@ -2408,6 +2414,12 @@ namespace libtorrent assert(m_abort || m_have_pieces.empty()); } +/* for (policy::const_iterator i = m_policy->begin_peer() + , end(m_policy->end_peer()); i != end; ++i) + { + assert(i->connection == const_cast(this)->connection_for(i->ip)); + } +*/ size_type total_done = quantized_bytes_done(); if (m_torrent_file->is_valid()) { diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 3cf1f2bac..ebef802a8 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -94,18 +94,11 @@ namespace libtorrent , aux::checker_impl* chk , sha1_hash const& hash) { - if (ses == 0) throw_invalid_handle(); + aux::piece_checker_data* d = chk->find_torrent(hash); + if (d != 0) return d->torrent_ptr; - if (chk) - { - aux::piece_checker_data* d = chk->find_torrent(hash); - if (d != 0) return d->torrent_ptr; - } - - { - boost::shared_ptr t = ses->find_torrent(hash).lock(); - if (t) return t; - } + boost::shared_ptr t = ses->find_torrent(hash).lock(); + if (t) return t; // throwing directly instead of calling // the throw_invalid_handle() function @@ -118,7 +111,7 @@ namespace libtorrent void torrent_handle::check_invariant() const { - assert((m_ses == 0 && m_chk == 0) || (m_ses != 0)); + assert((m_ses == 0 && m_chk == 0) || (m_ses != 0 && m_chk != 0)); } #endif @@ -127,6 +120,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(max_uploads >= 2 || max_uploads == -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); @@ -138,6 +134,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->use_interface(net_interface); @@ -147,6 +146,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(max_connections >= 2 || max_connections == -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); @@ -159,6 +161,9 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->set_peer_upload_limit(ip, limit); @@ -169,6 +174,9 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->set_peer_download_limit(ip, limit); @@ -178,6 +186,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(limit >= -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); @@ -188,6 +199,10 @@ namespace libtorrent int torrent_handle::upload_limit() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->upload_limit(); @@ -197,6 +212,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(limit >= -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); @@ -208,6 +226,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->download_limit(); @@ -218,6 +239,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->move_storage(save_path); @@ -227,6 +251,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->valid_metadata(); @@ -236,6 +263,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->is_seed(); @@ -245,6 +275,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->is_paused(); @@ -254,6 +287,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->pause(); @@ -263,6 +299,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->resume(); @@ -273,6 +312,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->set_tracker_login(name, password); @@ -283,31 +325,27 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); - if (m_chk) + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) { - mutex::scoped_lock l(m_chk->m_mutex); - - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) + if (!d->processing) { - if (!d->processing) - { - torrent_info const& info = d->torrent_ptr->torrent_file(); - progress.clear(); - progress.resize(info.num_files(), 0.f); - return; - } - d->torrent_ptr->file_progress(progress); + torrent_info const& info = d->torrent_ptr->torrent_file(); + progress.clear(); + progress.resize(info.num_files(), 0.f); return; } + d->torrent_ptr->file_progress(progress); + return; } - { - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->file_progress(progress); - } + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->file_progress(progress); throw_invalid_handle(); } @@ -317,36 +355,32 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); - if (m_chk) + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) { - mutex::scoped_lock l(m_chk->m_mutex); + torrent_status st; - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) + if (d->processing) { - torrent_status st; - - if (d->processing) - { - if (d->torrent_ptr->is_allocating()) - st.state = torrent_status::allocating; - else - st.state = torrent_status::checking_files; - } + if (d->torrent_ptr->is_allocating()) + st.state = torrent_status::allocating; else - st.state = torrent_status::queued_for_checking; - st.progress = d->progress; - st.paused = d->torrent_ptr->is_paused(); - return st; + st.state = torrent_status::checking_files; } + else + st.state = torrent_status::queued_for_checking; + st.progress = d->progress; + st.paused = d->torrent_ptr->is_paused(); + return st; } - { - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->status(); - } + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->status(); throw_invalid_handle(); return torrent_status(); @@ -355,6 +389,10 @@ namespace libtorrent void torrent_handle::set_sequenced_download_threshold(int threshold) const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->set_sequenced_download_threshold(threshold); @@ -364,6 +402,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->name(); @@ -374,6 +415,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->piece_availability(avail); @@ -383,6 +427,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->set_piece_priority(index, priority); @@ -392,6 +439,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->piece_priority(index); @@ -401,6 +451,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->prioritize_pieces(pieces); @@ -409,6 +462,10 @@ namespace libtorrent std::vector torrent_handle::piece_priorities() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + std::vector ret; session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -420,6 +477,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->prioritize_files(files); @@ -430,6 +490,10 @@ namespace libtorrent void torrent_handle::filter_piece(int index, bool filter) const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->filter_piece(index, filter); @@ -438,6 +502,10 @@ namespace libtorrent void torrent_handle::filter_pieces(std::vector const& pieces) const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->filter_pieces(pieces); @@ -446,6 +514,10 @@ namespace libtorrent bool torrent_handle::is_piece_filtered(int index) const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->is_piece_filtered(index); @@ -454,6 +526,10 @@ namespace libtorrent std::vector torrent_handle::filtered_pieces() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + std::vector ret; session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -464,6 +540,10 @@ namespace libtorrent void torrent_handle::filter_files(std::vector const& files) const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->filter_files(files); @@ -476,6 +556,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->trackers(); @@ -485,6 +568,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->add_url_seed(url); @@ -494,6 +580,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->remove_url_seed(url); @@ -503,6 +592,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->url_seeds(); @@ -513,6 +605,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->replace_trackers(urls); @@ -521,6 +616,10 @@ namespace libtorrent torrent_info const& torrent_handle::get_torrent_info() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); boost::shared_ptr t = find_torrent(m_ses, m_chk, m_info_hash); @@ -533,16 +632,14 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) return false; + assert(m_chk); - if (m_chk) - { - mutex::scoped_lock l(m_chk->m_mutex); - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) return true; - } + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) return true; { - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::weak_ptr t = m_ses->find_torrent(m_info_hash); if (!t.expired()) return true; } @@ -556,6 +653,7 @@ namespace libtorrent std::vector piece_index; if (m_ses == 0) return entry(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -674,6 +772,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->save_path(); @@ -684,6 +785,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -712,6 +814,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -726,6 +829,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -738,8 +842,10 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(ratio >= 0.f); - if (ratio < 1.f && ratio > 0.f) ratio = 1.f; @@ -752,6 +858,10 @@ namespace libtorrent void torrent_handle::resolve_countries(bool r) { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); find_torrent(m_ses, m_chk, m_info_hash)->resolve_countries(r); @@ -760,6 +870,10 @@ namespace libtorrent bool torrent_handle::resolve_countries() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); return find_torrent(m_ses, m_chk, m_info_hash)->resolving_countries(); @@ -770,8 +884,10 @@ namespace libtorrent { INVARIANT_CHECK; - v.clear(); if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + v.clear(); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); @@ -803,6 +919,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 358ac3838..981eb4caf 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -365,23 +365,22 @@ namespace libtorrent , m_req(req) {} - request_callback& tracker_connection::requester() + boost::shared_ptr tracker_connection::requester() { - boost::shared_ptr r = m_requester.lock(); - assert(r); - return *r; + return m_requester.lock(); } void tracker_connection::fail(int code, char const* msg) { - if (has_requester()) requester().tracker_request_error( - m_req, code, msg); + boost::shared_ptr cb = requester(); + if (cb) cb->tracker_request_error(m_req, code, msg); close(); } void tracker_connection::fail_timeout() { - if (has_requester()) requester().tracker_request_timed_out(m_req); + boost::shared_ptr cb = requester(); + if (cb) cb->tracker_request_timed_out(m_req); close(); } @@ -548,7 +547,8 @@ namespace libtorrent m_connections.push_back(con); - if (con->has_requester()) con->requester().m_manager = this; + boost::shared_ptr cb = con->requester(); + if (cb) cb->m_manager = this; } catch (std::exception& e) { diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index d08abd359..cd500d98c 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -110,8 +110,9 @@ namespace libtorrent return; } + boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("udp tracker name lookup successful"); + if (cb) cb->debug_log("udp tracker name lookup successful"); #endif restart_read_timeout(); @@ -126,11 +127,11 @@ namespace libtorrent if (target == end) { assert(target_address.address().is_v4() != bind_interface().is_v4()); - if (has_requester()) + if (cb) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - requester().tracker_warning("the tracker only resolves to an " + cb->tracker_warning("the tracker only resolves to an " + tracker_address_type + " address, and you're listening on an " + bind_address_type + " socket. This may prevent you from receiving incoming connections."); } @@ -140,7 +141,7 @@ namespace libtorrent target_address = *target; } - if (has_requester()) requester().m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); + if (cb) cb->m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); m_target = target_address; m_socket.reset(new datagram_socket(m_name_lookup.io_service())); m_socket->open(target_address.protocol()); @@ -163,9 +164,10 @@ namespace libtorrent void udp_tracker_connection::send_udp_connect() { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("==> UDP_TRACKER_CONNECT [" + cb->debug_log("==> UDP_TRACKER_CONNECT [" + lexical_cast(tracker_req().info_hash) + "]"); } #endif @@ -259,9 +261,10 @@ namespace libtorrent m_connection_id = detail::read_int64(ptr); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + cb->debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + lexical_cast(m_connection_id) + "]"); } #endif @@ -321,9 +324,10 @@ namespace libtorrent detail::write_uint16(0, out); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("==> UDP_TRACKER_ANNOUNCE [" + cb->debug_log("==> UDP_TRACKER_ANNOUNCE [" + lexical_cast(req.info_hash) + "]"); } #endif @@ -431,14 +435,15 @@ namespace libtorrent return; } + boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + if (cb) { - requester().debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); + cb->debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); } #endif - if (!has_requester()) + if (!cb) { m_man.remove_request(this); return; @@ -459,7 +464,7 @@ namespace libtorrent peer_list.push_back(e); } - requester().tracker_response(tracker_req(), peer_list, interval + cb->tracker_response(tracker_req(), peer_list, interval , complete, incomplete); m_man.remove_request(this); @@ -534,14 +539,15 @@ namespace libtorrent /*int downloaded = */detail::read_int32(buf); int incomplete = detail::read_int32(buf); - if (!has_requester()) + boost::shared_ptr cb = requester(); + if (!cb) { m_man.remove_request(this); return; } std::vector peer_list; - requester().tracker_response(tracker_req(), peer_list, 0 + cb->tracker_response(tracker_req(), peer_list, 0 , complete, incomplete); m_man.remove_request(this); diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 33168c2bd..87f950b48 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -997,4 +997,3 @@ void upnp::close() } } - diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 18cbf6c2f..18fe715ee 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -67,8 +67,6 @@ namespace libtorrent { namespace if (!p.is_local()) return false; // don't send out peers that we haven't successfully connected to if (p.is_connecting()) return false; - // ut pex does not support IPv6 - if (!p.remote().address().is_v4()) return false; return true; } @@ -98,9 +96,15 @@ namespace libtorrent { namespace std::string& pla = pex["added"].string(); std::string& pld = pex["dropped"].string(); std::string& plf = pex["added.f"].string(); + std::string& pla6 = pex["added6"].string(); + std::string& pld6 = pex["dropped6"].string(); + std::string& plf6 = pex["added6.f"].string(); std::back_insert_iterator pla_out(pla); std::back_insert_iterator pld_out(pld); std::back_insert_iterator plf_out(plf); + std::back_insert_iterator pla6_out(pla6); + std::back_insert_iterator pld6_out(pld6); + std::back_insert_iterator plf6_out(plf6); std::set dropped; m_old_peers.swap(dropped); @@ -123,8 +127,6 @@ namespace libtorrent { namespace bt_peer_connection* p = dynamic_cast(i->second); if (!p) continue; - // i->first was added since the last time - detail::write_endpoint(i->first, pla_out); // no supported flags to set yet // 0x01 - peer supports encryption // 0x02 - peer is a seed @@ -132,7 +134,17 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif - detail::write_uint8(flags, plf_out); + // i->first was added since the last time + if (i->first.address().is_v4()) + { + detail::write_endpoint(i->first, pla_out); + detail::write_uint8(flags, plf_out); + } + else + { + detail::write_endpoint(i->first, pla6_out); + detail::write_uint8(flags, plf6_out); + } ++num_added; } else @@ -146,8 +158,10 @@ namespace libtorrent { namespace for (std::set::const_iterator i = dropped.begin() , end(dropped.end());i != end; ++i) { - if (!i->address().is_v4()) continue; - detail::write_endpoint(*i, pld_out); + if (i->address().is_v4()) + detail::write_endpoint(*i, pld_out); + else + detail::write_endpoint(*i, pld6_out); } m_ut_pex_msg.clear(); @@ -227,6 +241,28 @@ namespace libtorrent { namespace char flags = detail::read_uint8(fin); p.peer_from_tracker(adr, pid, peer_info::pex, flags); } + + if (entry const* p6 = pex_msg.find_key("added6")) + { + std::string const& peers6 = p6->string(); + std::string const& peer6_flags = pex_msg["added6.f"].string(); + + int num_peers = peers6.length() / 18; + char const* in = peers6.c_str(); + char const* fin = peer6_flags.c_str(); + + if (int(peer6_flags.size()) != num_peers) + return true; + + peer_id pid(0); + policy& p = m_torrent.get_policy(); + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v6_endpoint(in); + char flags = detail::read_uint8(fin); + p.peer_from_tracker(adr, pid, peer_info::pex, flags); + } + } } catch (std::exception&) { @@ -279,8 +315,13 @@ namespace libtorrent { namespace pex["dropped"].string(); std::string& pla = pex["added"].string(); std::string& plf = pex["added.f"].string(); + pex["dropped6"].string(); + std::string& pla6 = pex["added6"].string(); + std::string& plf6 = pex["added6.f"].string(); std::back_insert_iterator pla_out(pla); std::back_insert_iterator plf_out(plf); + std::back_insert_iterator pla6_out(pla6); + std::back_insert_iterator plf6_out(plf6); int num_added = 0; for (torrent::peer_iterator i = m_torrent.begin() @@ -295,8 +336,6 @@ namespace libtorrent { namespace bt_peer_connection* p = dynamic_cast(i->second); if (!p) continue; - // i->first was added since the last time - detail::write_endpoint(i->first, pla_out); // no supported flags to set yet // 0x01 - peer supports encryption // 0x02 - peer is a seed @@ -304,7 +343,17 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif - detail::write_uint8(flags, plf_out); + // i->first was added since the last time + if (i->first.address().is_v4()) + { + detail::write_endpoint(i->first, pla_out); + detail::write_uint8(flags, plf_out); + } + else + { + detail::write_endpoint(i->first, pla6_out); + detail::write_uint8(flags, plf6_out); + } ++num_added; } std::vector pex_msg; @@ -347,7 +396,7 @@ namespace libtorrent { namespace namespace libtorrent { - boost::shared_ptr create_ut_pex_plugin(torrent* t) + boost::shared_ptr create_ut_pex_plugin(torrent* t, void*) { if (t->torrent_file().priv()) { From 4ca7ced7578056cbcd9a2b703113045b114c97c2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 00:08:12 +0000 Subject: [PATCH 0107/1009] Add status icons to torrentview. --- deluge/common.py | 1 + deluge/ui/gtkui/listview.py | 97 ++++++++++++++++++---------------- deluge/ui/gtkui/torrentview.py | 30 ++++++++++- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 7490de859..1be89d5f9 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -89,6 +89,7 @@ def get_pixmap(fname): """Returns a pixmap file included with deluge""" return pkg_resources.resource_filename("deluge", os.path.join("data", \ "pixmaps", fname)) + def get_logo(size): """Returns a deluge logo pixbuf based on the size parameter.""" import gtk diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 73cc6ce7a..681cbf70b 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -40,7 +40,6 @@ import deluge.common from deluge.log import LOG as log # Cell data functions to pass to add_func_column() - def cell_data_speed(column, cell, model, row, data): """Display value as a speed, eg. 2 KiB/s""" speed = int(model.get_value(row, data)) @@ -244,8 +243,9 @@ class ListView: return - def add_column(self, header, renderer, col_types, hidden, position, - status_field, sortid, text=0, value=0, function=None): + def add_column(self, header, render, col_types, hidden, position, + status_field, sortid, text=0, value=0, pixbuf=0, function=None, + column_type=None): """Adds a column to the ListView""" # Add the column types to liststore_columns column_indices = [] @@ -272,43 +272,37 @@ class ListView: # Create a new list with the added column self.create_new_liststore() - if type(renderer) is not tuple and type(renderer) is not list: - renderer = [renderer] - - column = gtk.TreeViewColumn(header) - for render in renderer: - if type(render) is gtk.CellRendererText: - # Check to see if this is a function column or not - if function is None: - # Not a function column, so lets treat it as a regular - # text col - column.pack_start(render) - column.add_attribute(render, "text", - self.columns[header].column_indices[text]) - else: - # This is a function column - column.pack_start(render, True) - if len(self.columns[header].column_indices) > 1: - column.set_cell_data_func(render, function, - tuple(self.columns[header].column_indices)) - else: - column.set_cell_data_func(render, function, - self.columns[header].column_indices[0]) - elif type(render) is gtk.CellRendererProgress: - # This is a progress column - column.pack_start(render) - column.add_attribute(render, "text", + column = gtk.TreeViewColumn(header) + if column_type == "text": + column.pack_start(render) + column.add_attribute(render, "text", self.columns[header].column_indices[text]) - column.add_attribute(render, "value", - self.columns[header].column_indices[value]) - - elif type(render) is gtk.CellRendererPixbuf: - # This is a pixbuf column - column.pack_start(render, expand=False) - column.add_attribute(render, "pixbuf", - self.columns[header].column_indices[pixbuf]) + elif column_type == "func": + column.pack_start(render, True) + if len(self.columns[header].column_indices) > 1: + column.set_cell_data_func(render, function, + tuple(self.columns[header].column_indices)) else: - column.pack_start(render) + column.set_cell_data_func(render, function, + self.columns[header].column_indices[0]) + elif column_type == "progress": + column.pack_start(render) + column.add_attribute(render, "text", + self.columns[header].column_indices[text]) + column.add_attribute(render, "value", + self.columns[header].column_indices[value]) + elif column_type == "texticon": + column.pack_start(render[pixbuf]) + if function is not None: + column.set_cell_data_func(render[pixbuf], function, + self.columns[header].column_indices[pixbuf]) + column.pack_start(render[text]) + column.add_attribute(render[text], "text", + self.columns[header].column_indices[text]) + elif column_type == None: + return + + column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) @@ -332,23 +326,26 @@ class ListView: def add_text_column(self, header, col_type=str, hidden=False, position=None, status_field=None, - sortid=0): + sortid=0, + column_type="text"): """Add a text column to the listview. Only the header name is required. """ render = gtk.CellRendererText() self.add_column(header, render, col_type, hidden, position, - status_field, sortid, text=0) + status_field, sortid, column_type=column_type) return True def add_func_column(self, header, function, col_types, sortid=0, - hidden=False, position=None, status_field=None): + hidden=False, position=None, status_field=None, + column_type="func"): """Add a function column to the listview. Need a header name, the function and the column types.""" render = gtk.CellRendererText() self.add_column(header, render, col_types, hidden, position, - status_field, sortid, function=function) + status_field, sortid, column_type=column_type, + function=function) return True @@ -356,25 +353,31 @@ class ListView: sortid=0, hidden=False, position=None, - status_field=None): + status_field=None, + column_type="progress"): """Add a progress column to the listview.""" render = gtk.CellRendererProgress() self.add_column(header, render, col_types, hidden, position, - status_field, sortid, value=0, text=1) + status_field, sortid, column_type=column_type, + value=0, text=1) return True - def add_texticon_column(self, header, col_types=[gtk.gdk.Pixbuf, str], + def add_texticon_column(self, header, col_types=[int, str], sortid=0, hidden=False, position=None, - status_field=None): + status_field=None, + column_type="texticon", + function=None): """Adds a texticon column to the listview.""" render1 = gtk.CellRendererPixbuf() render2 = gtk.CellRendererText() self.add_column(header, (render1, render2), col_types, hidden, - position, status_field, sortid, text=1, pixbuf=0) + position, status_field, sortid, + column_type=column_type, function=function, + pixbuf=0, text=1) return True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 1b482b6b8..d628c6117 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -38,10 +38,34 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext +import deluge.common import deluge.ui.functions as functions from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview +def cell_data_statusicon(column, cell, model, row, data): + """Display text with an icon""" + state = model.get_value(row, data) + if state == deluge.common.TORRENT_STATE.index("Connecting"): + fname = "downloading16.png" + if state == deluge.common.TORRENT_STATE.index("Downloading"): + fname = "downloading16.png" + if state == deluge.common.TORRENT_STATE.index("Downloading Metadata"): + fname = "downloading16.png" + if state == deluge.common.TORRENT_STATE.index("Queued"): + fname = "inactive16.png" + if state == deluge.common.TORRENT_STATE.index("Checking"): + fname = "downloading16.png" + if state == deluge.common.TORRENT_STATE.index("Allocating"): + fname = "downloading16.png" + if state == deluge.common.TORRENT_STATE.index("Finished"): + fname = "seeding16.png" + if state == deluge.common.TORRENT_STATE.index("Seeding"): + fname = "seeding16.png" + + icon = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap(fname)) + cell.set_property("pixbuf", icon) + class TorrentView(listview.ListView): """TorrentView handles the listing of torrents.""" def __init__(self, window): @@ -59,7 +83,8 @@ class TorrentView(listview.ListView): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) - self.add_text_column("Name", status_field=["name"]) + self.add_texticon_column("Name", status_field=["state", "name"], + function=cell_data_statusicon) self.add_func_column("Size", listview.cell_data_size, [long], @@ -143,6 +168,9 @@ class TorrentView(listview.ListView): status_keys.append(field) columns_to_update.append(column) + # Remove duplicate keys + columns_to_update = list(set(columns_to_update)) + # If there is nothing in status_keys then we must not continue if status_keys is []: return From 4dd7e383587ac06f1fdb2e8f6130383c4e95c161 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 00:09:25 +0000 Subject: [PATCH 0108/1009] Remove Makefile.am as we do not use it. --- libtorrent/src/Makefile.am | 100 ------------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 libtorrent/src/Makefile.am diff --git a/libtorrent/src/Makefile.am b/libtorrent/src/Makefile.am deleted file mode 100644 index 3ab9f73bd..000000000 --- a/libtorrent/src/Makefile.am +++ /dev/null @@ -1,100 +0,0 @@ -lib_LTLIBRARIES = libtorrent.la - -if USE_DHT -kademlia_sources = kademlia/closest_nodes.cpp \ -kademlia/dht_tracker.cpp \ -kademlia/find_data.cpp \ -kademlia/node.cpp \ -kademlia/node_id.cpp \ -kademlia/refresh.cpp \ -kademlia/routing_table.cpp \ -kademlia/rpc_manager.cpp \ -kademlia/traversal_algorithm.cpp -endif - -libtorrent_la_SOURCES = entry.cpp escape_string.cpp \ -assert.cpp enum_net.cpp broadcast_socket.cpp \ -peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ -natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \ -stat.cpp storage.cpp torrent.cpp torrent_handle.cpp pe_crypto.cpp \ -torrent_info.cpp tracker_manager.cpp http_connection.cpp \ -http_tracker_connection.cpp udp_tracker_connection.cpp \ -alert.cpp identify_client.cpp ip_filter.cpp file.cpp metadata_transfer.cpp \ -logger.cpp file_pool.cpp ut_pex.cpp lsd.cpp upnp.cpp instantiate_connection.cpp \ -socks5_stream.cpp socks4_stream.cpp http_stream.cpp connection_queue.cpp \ -disk_io_thread.cpp \ -$(kademlia_sources) - -noinst_HEADERS = \ -$(top_srcdir)/include/libtorrent/alert.hpp \ -$(top_srcdir)/include/libtorrent/alert_types.hpp \ -$(top_srcdir)/include/libtorrent/assert.hpp \ -$(top_srcdir)/include/libtorrent/aux_/session_impl.hpp \ -$(top_srcdir)/include/libtorrent/bandwidth_manager.hpp \ -$(top_srcdir)/include/libtorrent/bencode.hpp \ -$(top_srcdir)/include/libtorrent/broadcast_socket.hpp \ -$(top_srcdir)/include/libtorrent/buffer.hpp \ -$(top_srcdir)/include/libtorrent/connection_queue.hpp \ -$(top_srcdir)/include/libtorrent/debug.hpp \ -$(top_srcdir)/include/libtorrent/disk_io_thread.hpp \ -$(top_srcdir)/include/libtorrent/entry.hpp \ -$(top_srcdir)/include/libtorrent/enum_net.hpp \ -$(top_srcdir)/include/libtorrent/escape_string.hpp \ -$(top_srcdir)/include/libtorrent/extensions.hpp \ -$(top_srcdir)/include/libtorrent/extensions/metadata_transfer.hpp \ -$(top_srcdir)/include/libtorrent/extensions/logger.hpp \ -$(top_srcdir)/include/libtorrent/extensions/ut_pex.hpp \ -$(top_srcdir)/include/libtorrent/file.hpp \ -$(top_srcdir)/include/libtorrent/file_pool.hpp \ -$(top_srcdir)/include/libtorrent/fingerprint.hpp \ -$(top_srcdir)/include/libtorrent/hasher.hpp \ -$(top_srcdir)/include/libtorrent/http_connection.hpp \ -$(top_srcdir)/include/libtorrent/http_stream.hpp \ -$(top_srcdir)/include/libtorrent/session_settings.hpp \ -$(top_srcdir)/include/libtorrent/http_tracker_connection.hpp \ -$(top_srcdir)/include/libtorrent/identify_client.hpp \ -$(top_srcdir)/include/libtorrent/instantiate_connection.hpp \ -$(top_srcdir)/include/libtorrent/intrusive_ptr_base.hpp \ -$(top_srcdir)/include/libtorrent/invariant_check.hpp \ -$(top_srcdir)/include/libtorrent/io.hpp \ -$(top_srcdir)/include/libtorrent/ip_filter.hpp \ -$(top_srcdir)/include/libtorrent/lsd.hpp \ -$(top_srcdir)/include/libtorrent/peer.hpp \ -$(top_srcdir)/include/libtorrent/peer_connection.hpp \ -$(top_srcdir)/include/libtorrent/bt_peer_connection.hpp \ -$(top_srcdir)/include/libtorrent/web_peer_connection.hpp \ -$(top_srcdir)/include/libtorrent/pe_crypto.hpp \ -$(top_srcdir)/include/libtorrent/natpmp.hpp \ -$(top_srcdir)/include/libtorrent/pch.hpp \ -$(top_srcdir)/include/libtorrent/peer_id.hpp \ -$(top_srcdir)/include/libtorrent/peer_info.hpp \ -$(top_srcdir)/include/libtorrent/peer_request.hpp \ -$(top_srcdir)/include/libtorrent/piece_block_progress.hpp \ -$(top_srcdir)/include/libtorrent/piece_picker.hpp \ -$(top_srcdir)/include/libtorrent/policy.hpp \ -$(top_srcdir)/include/libtorrent/session.hpp \ -$(top_srcdir)/include/libtorrent/size_type.hpp \ -$(top_srcdir)/include/libtorrent/socket.hpp \ -$(top_srcdir)/include/libtorrent/socket_type.hpp \ -$(top_srcdir)/include/libtorrent/socks4_stream.hpp \ -$(top_srcdir)/include/libtorrent/socks5_stream.hpp \ -$(top_srcdir)/include/libtorrent/stat.hpp \ -$(top_srcdir)/include/libtorrent/storage.hpp \ -$(top_srcdir)/include/libtorrent/time.hpp \ -$(top_srcdir)/include/libtorrent/torrent.hpp \ -$(top_srcdir)/include/libtorrent/torrent_handle.hpp \ -$(top_srcdir)/include/libtorrent/torrent_info.hpp \ -$(top_srcdir)/include/libtorrent/tracker_manager.hpp \ -$(top_srcdir)/include/libtorrent/udp_tracker_connection.hpp \ -$(top_srcdir)/include/libtorrent/utf8.hpp \ -$(top_srcdir)/include/libtorrent/xml_parse.hpp \ -$(top_srcdir)/include/libtorrent/variant_stream.hpp \ -$(top_srcdir)/include/libtorrent/version.hpp - - -libtorrent_la_LDFLAGS = $(LDFLAGS) -release @VERSION@ -libtorrent_la_LIBADD = @ZLIB@ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ - -AM_CXXFLAGS= -ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @ZLIBINCL@ @DEBUGFLAGS@ @PTHREAD_CFLAGS@ -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -AM_LDFLAGS= $(LDFLAGS) -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ - From 0b746d2ab14088c57c943cf1550ed4cc8cb59f4d Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 00:22:38 +0000 Subject: [PATCH 0109/1009] revert lt sync for later testing --- libtorrent/bindings/python/src/alert.cpp | 15 +- libtorrent/bindings/python/src/docstrings.cpp | 21 +- libtorrent/bindings/python/src/extensions.cpp | 1 + libtorrent/bindings/python/src/session.cpp | 9 +- .../bindings/python/src/session_settings.cpp | 4 +- .../bindings/python/src/torrent_info.cpp | 31 +- libtorrent/include/asio/basic_socket.hpp | 6 - libtorrent/include/asio/buffer.hpp | 7 +- .../include/asio/completion_condition.hpp | 44 -- .../include/asio/detail/epoll_reactor.hpp | 155 +++-- .../include/asio/detail/kqueue_reactor.hpp | 82 +-- libtorrent/include/asio/detail/null_event.hpp | 9 +- .../include/asio/detail/posix_event.hpp | 40 +- .../include/asio/detail/posix_mutex.hpp | 10 +- .../include/asio/detail/posix_thread.hpp | 3 +- .../include/asio/detail/posix_tss_ptr.hpp | 3 +- .../asio/detail/reactive_socket_service.hpp | 10 +- .../include/asio/detail/scoped_lock.hpp | 12 - .../include/asio/detail/select_reactor.hpp | 51 +- .../include/asio/detail/service_registry.hpp | 6 +- libtorrent/include/asio/detail/socket_ops.hpp | 38 +- .../include/asio/detail/socket_option.hpp | 13 +- .../include/asio/detail/socket_types.hpp | 7 - .../include/asio/detail/strand_service.hpp | 9 +- .../include/asio/detail/task_io_service.hpp | 99 +-- .../include/asio/detail/timer_queue.hpp | 52 +- .../include/asio/detail/timer_queue_base.hpp | 6 - libtorrent/include/asio/detail/win_event.hpp | 21 +- .../asio/detail/win_iocp_io_service.hpp | 14 +- .../asio/detail/win_iocp_io_service_fwd.hpp | 2 - .../asio/detail/win_iocp_socket_service.hpp | 166 ++--- libtorrent/include/asio/detail/win_mutex.hpp | 5 +- libtorrent/include/asio/detail/win_thread.hpp | 4 +- .../include/asio/detail/win_tss_ptr.hpp | 4 +- .../include/asio/detail/winsock_init.hpp | 3 +- .../include/asio/detail/wrapped_handler.hpp | 14 +- libtorrent/include/asio/error.hpp | 424 +++++++----- libtorrent/include/asio/error_code.hpp | 44 +- libtorrent/include/asio/impl/error_code.ipp | 6 +- libtorrent/include/asio/impl/io_service.ipp | 4 +- libtorrent/include/asio/impl/read_until.ipp | 18 +- libtorrent/include/asio/io_service.hpp | 2 +- libtorrent/include/asio/ip/basic_endpoint.hpp | 32 +- .../asio/ssl/detail/openssl_operation.hpp | 4 +- libtorrent/include/libtorrent/alert.hpp | 2 +- libtorrent/include/libtorrent/alert_types.hpp | 14 +- libtorrent/include/libtorrent/assert.hpp | 54 -- .../include/libtorrent/aux_/session_impl.hpp | 58 +- .../include/libtorrent/bandwidth_manager.hpp | 55 +- libtorrent/include/libtorrent/bencode.hpp | 2 - .../include/libtorrent/broadcast_socket.hpp | 84 --- .../include/libtorrent/bt_peer_connection.hpp | 30 +- libtorrent/include/libtorrent/buffer.hpp | 3 +- .../include/libtorrent/connection_queue.hpp | 5 - libtorrent/include/libtorrent/debug.hpp | 1 - .../include/libtorrent/disk_io_thread.hpp | 15 - libtorrent/include/libtorrent/entry.hpp | 2 +- libtorrent/include/libtorrent/enum_net.hpp | 44 -- libtorrent/include/libtorrent/extensions.hpp | 15 - .../extensions/metadata_transfer.hpp | 2 +- .../include/libtorrent/extensions/ut_pex.hpp | 2 +- libtorrent/include/libtorrent/fingerprint.hpp | 2 - libtorrent/include/libtorrent/hasher.hpp | 2 +- .../include/libtorrent/http_connection.hpp | 12 +- .../libtorrent/http_tracker_connection.hpp | 4 - .../include/libtorrent/intrusive_ptr_base.hpp | 5 +- .../include/libtorrent/invariant_check.hpp | 2 +- libtorrent/include/libtorrent/ip_filter.hpp | 6 +- .../include/libtorrent/kademlia/node.hpp | 2 +- .../include/libtorrent/kademlia/node_id.hpp | 2 +- .../libtorrent/kademlia/routing_table.hpp | 1 - libtorrent/include/libtorrent/lsd.hpp | 18 +- libtorrent/include/libtorrent/pe_crypto.hpp | 5 +- .../include/libtorrent/peer_connection.hpp | 72 +- libtorrent/include/libtorrent/peer_id.hpp | 2 +- libtorrent/include/libtorrent/peer_info.hpp | 15 +- .../include/libtorrent/piece_picker.hpp | 50 +- libtorrent/include/libtorrent/policy.hpp | 31 +- libtorrent/include/libtorrent/session.hpp | 33 +- .../include/libtorrent/session_impl.hpp | 594 ----------------- .../include/libtorrent/session_settings.hpp | 12 +- libtorrent/include/libtorrent/stat.hpp | 1 - libtorrent/include/libtorrent/storage.hpp | 30 +- libtorrent/include/libtorrent/time.hpp | 7 +- libtorrent/include/libtorrent/torrent.hpp | 57 +- .../include/libtorrent/torrent_handle.hpp | 6 +- .../include/libtorrent/torrent_info.hpp | 84 +-- .../include/libtorrent/tracker_manager.hpp | 3 +- libtorrent/include/libtorrent/upnp.hpp | 28 +- .../libtorrent/web_peer_connection.hpp | 3 +- libtorrent/src/assert.cpp | 73 --- libtorrent/src/broadcast_socket.cpp | 186 ------ libtorrent/src/bt_peer_connection.cpp | 245 +------ libtorrent/src/connection_queue.cpp | 26 +- libtorrent/src/disk_io_thread.cpp | 83 +-- libtorrent/src/enum_net.cpp | 142 ---- libtorrent/src/escape_string.cpp | 3 +- libtorrent/src/file.cpp | 19 +- libtorrent/src/http_connection.cpp | 7 +- libtorrent/src/http_tracker_connection.cpp | 101 ++- libtorrent/src/identify_client.cpp | 77 +-- libtorrent/src/kademlia/closest_nodes.cpp | 1 - libtorrent/src/kademlia/dht_tracker.cpp | 8 +- libtorrent/src/kademlia/node_id.cpp | 2 +- libtorrent/src/lsd.cpp | 88 ++- libtorrent/src/metadata_transfer.cpp | 4 +- libtorrent/src/natpmp.cpp | 6 +- libtorrent/src/pe_crypto.cpp | 263 ++++---- libtorrent/src/peer_connection.cpp | 617 +++++------------- libtorrent/src/piece_picker.cpp | 549 +++++----------- libtorrent/src/policy.cpp | 306 ++++----- libtorrent/src/session.cpp | 35 +- libtorrent/src/session_impl.cpp | 518 +++++---------- libtorrent/src/socks5_stream.cpp | 1 - libtorrent/src/storage.cpp | 254 +++---- libtorrent/src/torrent.cpp | 382 +++++------ libtorrent/src/torrent_handle.cpp | 460 ++++--------- libtorrent/src/torrent_info.cpp | 43 +- libtorrent/src/tracker_manager.cpp | 22 +- libtorrent/src/udp_tracker_connection.cpp | 38 +- libtorrent/src/upnp.cpp | 269 +++++--- libtorrent/src/ut_pex.cpp | 71 +- libtorrent/src/web_peer_connection.cpp | 31 +- 123 files changed, 2461 insertions(+), 5456 deletions(-) diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index 3e188a3ce..f34cf4b5d 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -25,8 +25,6 @@ extern char const* peer_error_alert_doc; extern char const* invalid_request_alert_doc; extern char const* peer_request_doc; extern char const* torrent_finished_alert_doc; -extern char const* torrent_paused_alert_doc; -extern char const* storage_moved_alert_doc; extern char const* metadata_failed_alert_doc; extern char const* metadata_received_alert_doc; extern char const* fastresume_rejected_alert_doc; @@ -142,18 +140,7 @@ void bind_alert() ) .def_readonly("handle", &torrent_finished_alert::handle) ; - - class_, noncopyable>( - "torrent_paused_alert", torrent_paused_alert_doc, no_init - ) - .def_readonly("handle", &torrent_paused_alert::handle) - ; - - class_, noncopyable>( - "storage_moved_alert", storage_moved_alert_doc, no_init - ) - .def_readonly("handle", &storage_moved_alert::handle) - ; + class_, noncopyable>( "metadata_failed_alert", metadata_failed_alert_doc, no_init ) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index e4a99ba31..fbd0a157c 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -164,14 +164,14 @@ char const* session_set_severity_level_doc = ""; char const* session_pop_alert_doc = ""; -char const* session_start_upnp_doc = +char const* session_start_upnp_doc = ""; -char const* session_stop_upnp_doc = +char const* session_stop_upnp_doc = ""; -char const* session_start_natpmp_doc = - ""; -char const* session_stop_natpmp_doc = + char const* session_start_natpmp_doc = ""; +char const* session_stop_natpmp_doc = + ""; // -- alert ----------------------------------------------------------------- char const* alert_doc = @@ -257,17 +257,6 @@ char const* torrent_finished_alert_doc = "It contains a `torrent_handle` to the torrent in question. This alert\n" "is generated as severity level `alert.severity_levels.info`."; -char const* torrent_paused_alert_doc = - "This alert is generated when a torrent switches from being a\n" - "active to paused.\n" - "It contains a `torrent_handle` to the torrent in question. This alert\n" - "is generated as severity level `alert.severity_levels.warning`."; - -char const* storage_moved_alert_doc = - "This alert is generated when a torrent moves storage.\n" - "It contains a `torrent_handle` to the torrent in question. This alert\n" - "is generated as severity level `alert.severity_levels.warning`."; - char const* metadata_failed_alert_doc = "This alert is generated when the metadata has been completely\n" "received and the info-hash failed to match it. i.e. the\n" diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp index f8fb30bdf..10d18ff94 100755 --- a/libtorrent/bindings/python/src/extensions.cpp +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -142,6 +142,7 @@ void bind_extensions() // TODO move to it's own file class_("peer_connection", no_init); + class_ >("torrent_plugin", no_init); def("create_ut_pex_plugin", create_ut_pex_plugin); def("create_metadata_plugin", create_metadata_plugin); } diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 480659537..4ea7a1711 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -46,7 +46,7 @@ extern char const* session_set_max_connections_doc; extern char const* session_set_max_half_open_connections_doc; extern char const* session_set_settings_doc; extern char const* session_set_pe_settings_doc; -extern char const* session_get_pe_settings_doc; +extern char const* session_get_pe_settings_doc; extern char const* session_set_severity_level_doc; extern char const* session_pop_alert_doc; extern char const* session_start_upnp_doc; @@ -86,10 +86,11 @@ namespace torrent_handle add_torrent(session& s, torrent_info const& ti , boost::filesystem::path const& save, entry const& resume - , bool compact, bool paused) + , bool compact, int block_size) { allow_threading_guard guard; - return s.add_torrent(ti, save, resume, compact, paused, default_storage_constructor); + return s.add_torrent(ti, save, resume, compact, block_size + , default_storage_constructor); } } // namespace unnamed @@ -174,7 +175,7 @@ void bind_session() "add_torrent", &add_torrent , ( arg("torrent_info"), "save_path", arg("resume_data") = entry() - , arg("compact_mode") = true, arg("paused") = false + , arg("compact_mode") = true, arg("block_size") = 16 * 1024 ) , session_add_torrent_doc ) diff --git a/libtorrent/bindings/python/src/session_settings.cpp b/libtorrent/bindings/python/src/session_settings.cpp index c19dfa4d4..f584956b2 100755 --- a/libtorrent/bindings/python/src/session_settings.cpp +++ b/libtorrent/bindings/python/src/session_settings.cpp @@ -47,7 +47,7 @@ void bind_session_settings() .value("http", proxy_settings::http) .value("http_pw", proxy_settings::http_pw) ; - class_("proxy_settings") + class_("proxy_settings") .def_readwrite("hostname", &proxy_settings::hostname) .def_readwrite("port", &proxy_settings::port) .def_readwrite("password", &proxy_settings::password) @@ -64,7 +64,7 @@ void bind_session_settings() enum_("enc_level") .value("rc4", pe_settings::rc4) .value("plaintext", pe_settings::plaintext) - .value("both", pe_settings::both) + .value("both", pe_settings::both) ; class_("pe_settings") diff --git a/libtorrent/bindings/python/src/torrent_info.cpp b/libtorrent/bindings/python/src/torrent_info.cpp index a17c449e3..301c4a5bf 100755 --- a/libtorrent/bindings/python/src/torrent_info.cpp +++ b/libtorrent/bindings/python/src/torrent_info.cpp @@ -16,6 +16,7 @@ namespace return i.trackers().begin(); } + std::vector::const_iterator end_trackers(torrent_info& i) { return i.trackers().end(); @@ -40,29 +41,6 @@ namespace return result; } - std::vector::const_iterator begin_files(torrent_info& i, bool storage) - { - return i.begin_files(storage); - } - - std::vector::const_iterator end_files(torrent_info& i, bool storage) - { - return i.end_files(storage); - } - - //list files(torrent_info const& ti, bool storage) { - list files(torrent_info const& ti, bool storage) { - list result; - - typedef std::vector list_type; - - for (list_type::const_iterator i = ti.begin_files(storage); i != ti.end_files(storage); ++i) - result.append(*i); - - return result; - } - - } // namespace unnamed void bind_torrent_info() @@ -93,9 +71,9 @@ void bind_torrent_info() .def("hash_for_piece", &torrent_info::hash_for_piece, copy) .def("piece_size", &torrent_info::piece_size) - .def("num_files", &torrent_info::num_files, (arg("storage")=false)) + .def("num_files", &torrent_info::num_files) .def("file_at", &torrent_info::file_at, return_internal_reference<>()) - .def("files", &files, (arg("storage")=false)) + .def("files", range(&torrent_info::begin_files, &torrent_info::end_files)) .def("priv", &torrent_info::priv) .def("set_priv", &torrent_info::set_priv) @@ -106,8 +84,9 @@ void bind_torrent_info() .def("add_node", &add_node) .def("nodes", &nodes) ; + class_("file_entry") - .add_property( + .add_property( "path" , make_getter( &file_entry::path, return_value_policy() diff --git a/libtorrent/include/asio/basic_socket.hpp b/libtorrent/include/asio/basic_socket.hpp index 2b2521b69..b0dc52e48 100644 --- a/libtorrent/include/asio/basic_socket.hpp +++ b/libtorrent/include/asio/basic_socket.hpp @@ -238,9 +238,6 @@ public: * with the asio::error::operation_aborted error. * * @throws asio::system_error Thrown on failure. - * - * @note For portable behaviour with respect to graceful closure of a - * connected socket, call shutdown() before closing the socket. */ void close() { @@ -268,9 +265,6 @@ public: * // An error occurred. * } * @endcode - * - * @note For portable behaviour with respect to graceful closure of a - * connected socket, call shutdown() before closing the socket. */ asio::error_code close(asio::error_code& ec) { diff --git a/libtorrent/include/asio/buffer.hpp b/libtorrent/include/asio/buffer.hpp index 9fe76178c..7e5dc76c8 100644 --- a/libtorrent/include/asio/buffer.hpp +++ b/libtorrent/include/asio/buffer.hpp @@ -542,10 +542,9 @@ inline const_buffers_1 buffer(const PodType (&data)[N], ? N * sizeof(PodType) : max_size_in_bytes)); } -#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) \ - || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) -// Borland C++ and Sun Studio think the overloads: +// Borland C++ thinks the overloads: // // unspecified buffer(boost::array& array ...); // @@ -611,7 +610,6 @@ buffer(boost::array& data, std::size_t max_size_in_bytes) } #else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) - // || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) /// Create a new modifiable buffer that represents the given POD array. template @@ -652,7 +650,6 @@ inline const_buffers_1 buffer(boost::array& data, } #endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) - // || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) /// Create a new non-modifiable buffer that represents the given POD array. template diff --git a/libtorrent/include/asio/completion_condition.hpp b/libtorrent/include/asio/completion_condition.hpp index 939a4a8f3..42696d599 100644 --- a/libtorrent/include/asio/completion_condition.hpp +++ b/libtorrent/include/asio/completion_condition.hpp @@ -71,28 +71,6 @@ private: /// Return a completion condition function object that indicates that a read or /// write operation should continue until all of the data has been transferred, /// or until an error occurs. -/** - * This function is used to create an object, of unspecified type, that meets - * CompletionCondition requirements. - * - * @par Example - * Reading until a buffer is full: - * @code - * boost::array buf; - * asio::error_code ec; - * std::size_t n = asio::read( - * sock, asio::buffer(buf), - * asio::transfer_all(), ec); - * if (ec) - * { - * // An error occurred. - * } - * else - * { - * // n == 128 - * } - * @endcode - */ #if defined(GENERATING_DOCUMENTATION) unspecified transfer_all(); #else @@ -105,28 +83,6 @@ inline detail::transfer_all_t transfer_all() /// Return a completion condition function object that indicates that a read or /// write operation should continue until a minimum number of bytes has been /// transferred, or until an error occurs. -/** - * This function is used to create an object, of unspecified type, that meets - * CompletionCondition requirements. - * - * @par Example - * Reading until a buffer is full or contains at least 64 bytes: - * @code - * boost::array buf; - * asio::error_code ec; - * std::size_t n = asio::read( - * sock, asio::buffer(buf), - * asio::transfer_at_least(64), ec); - * if (ec) - * { - * // An error occurred. - * } - * else - * { - * // n >= 64 && n <= 128 - * } - * @endcode - */ #if defined(GENERATING_DOCUMENTATION) unspecified transfer_at_least(std::size_t minimum); #else diff --git a/libtorrent/include/asio/detail/epoll_reactor.hpp b/libtorrent/include/asio/detail/epoll_reactor.hpp index e260c5194..d55e86454 100644 --- a/libtorrent/include/asio/detail/epoll_reactor.hpp +++ b/libtorrent/include/asio/detail/epoll_reactor.hpp @@ -157,8 +157,7 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -191,8 +190,7 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -221,8 +219,7 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -253,8 +250,7 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); write_op_queue_.dispatch_all_operations(descriptor, ec); except_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -335,10 +331,7 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - std::size_t n = timer_queue.cancel_timer(token); - if (n > 0) - interrupter_.interrupt(); - return n; + return timer_queue.cancel_timer(token); } private: @@ -354,13 +347,16 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); - for (std::size_t i = 0; i < timer_queues_.size(); ++i) - timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); return; } @@ -369,7 +365,12 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); return; } @@ -397,45 +398,59 @@ private: } else { - bool more_reads = false; - bool more_writes = false; - bool more_except = false; - asio::error_code ec; - - // Exception operations must be processed first to ensure that any - // out-of-band data is read before normal data. - if (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP)) - more_except = except_op_queue_.dispatch_operation(descriptor, ec); - else - more_except = except_op_queue_.has_operation(descriptor); - - if (events[i].events & (EPOLLIN | EPOLLERR | EPOLLHUP)) - more_reads = read_op_queue_.dispatch_operation(descriptor, ec); - else - more_reads = read_op_queue_.has_operation(descriptor); - - if (events[i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) - more_writes = write_op_queue_.dispatch_operation(descriptor, ec); - else - more_writes = write_op_queue_.has_operation(descriptor); - - epoll_event ev = { 0, { 0 } }; - ev.events = EPOLLERR | EPOLLHUP; - if (more_reads) - ev.events |= EPOLLIN; - if (more_writes) - ev.events |= EPOLLOUT; - if (more_except) - ev.events |= EPOLLPRI; - ev.data.fd = descriptor; - int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); - if (result != 0) + if (events[i].events & (EPOLLERR | EPOLLHUP)) { - ec = asio::error_code(errno, - asio::error::system_category); + asio::error_code ec; + except_op_queue_.dispatch_all_operations(descriptor, ec); read_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); - except_op_queue_.dispatch_all_operations(descriptor, ec); + + epoll_event ev = { 0, { 0 } }; + ev.events = 0; + ev.data.fd = descriptor; + epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + } + else + { + bool more_reads = false; + bool more_writes = false; + bool more_except = false; + asio::error_code ec; + + // Exception operations must be processed first to ensure that any + // out-of-band data is read before normal data. + if (events[i].events & EPOLLPRI) + more_except = except_op_queue_.dispatch_operation(descriptor, ec); + else + more_except = except_op_queue_.has_operation(descriptor); + + if (events[i].events & EPOLLIN) + more_reads = read_op_queue_.dispatch_operation(descriptor, ec); + else + more_reads = read_op_queue_.has_operation(descriptor); + + if (events[i].events & EPOLLOUT) + more_writes = write_op_queue_.dispatch_operation(descriptor, ec); + else + more_writes = write_op_queue_.has_operation(descriptor); + + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLERR | EPOLLHUP; + if (more_reads) + ev.events |= EPOLLIN; + if (more_writes) + ev.events |= EPOLLOUT; + if (more_except) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) + { + ec = asio::error_code(errno, asio::native_ecat); + read_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } } } } @@ -443,17 +458,19 @@ private: write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); for (std::size_t i = 0; i < timer_queues_.size(); ++i) - { timer_queues_[i]->dispatch_timers(); - timer_queues_[i]->dispatch_cancellations(); - } // Issue any pending cancellations. for (size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); } // Run the select loop in the thread. @@ -490,10 +507,8 @@ private: int fd = epoll_create(epoll_size); if (fd == -1) { - boost::throw_exception( - asio::system_error( - asio::error_code(errno, - asio::error::system_category), + boost::throw_exception(asio::system_error( + asio::error_code(errno, asio::native_ecat), "epoll")); } return fd; @@ -551,22 +566,6 @@ private: interrupter_.interrupt(); } - // Clean up operations and timers. We must not hold the lock since the - // destructors may make calls back into this reactor. We make a copy of the - // vector of timer queues since the original may be modified while the lock - // is not held. - void cleanup_operations_and_timers( - asio::detail::mutex::scoped_lock& lock) - { - timer_queues_for_cleanup_ = timer_queues_; - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); - for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) - timer_queues_for_cleanup_[i]->cleanup_timers(); - } - // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -591,10 +590,6 @@ private: // The timer queues. std::vector timer_queues_; - // A copy of the timer queues, used when cleaning up timers. The copy is - // stored as a class data member to avoid unnecessary memory allocation. - std::vector timer_queues_for_cleanup_; - // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/kqueue_reactor.hpp b/libtorrent/include/asio/detail/kqueue_reactor.hpp index 5fffc6788..6628803af 100644 --- a/libtorrent/include/asio/detail/kqueue_reactor.hpp +++ b/libtorrent/include/asio/detail/kqueue_reactor.hpp @@ -150,8 +150,7 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -177,8 +176,7 @@ public: EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -203,8 +201,7 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -227,8 +224,7 @@ public: EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -242,8 +238,7 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, - asio::error::system_category); + asio::error_code ec(errno, asio::native_ecat); except_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -326,10 +321,7 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - std::size_t n = timer_queue.cancel_timer(token); - if (n > 0) - interrupter_.interrupt(); - return n; + return timer_queue.cancel_timer(token); } private: @@ -345,13 +337,16 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); - for (std::size_t i = 0; i < timer_queues_.size(); ++i) - timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); return; } @@ -360,7 +355,12 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); return; } @@ -397,7 +397,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::error::system_category); + events[i].data, asio::native_ecat); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -427,8 +427,7 @@ private: EV_SET(&event, descriptor, EVFILT_READ, EV_DELETE, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code error(errno, - asio::error::system_category); + asio::error_code error(errno, asio::native_ecat); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -440,7 +439,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::error::system_category); + events[i].data, asio::native_ecat); write_op_queue_.dispatch_all_operations(descriptor, error); } else @@ -457,8 +456,7 @@ private: EV_SET(&event, descriptor, EVFILT_WRITE, EV_DELETE, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code error(errno, - asio::error::system_category); + asio::error_code error(errno, asio::native_ecat); write_op_queue_.dispatch_all_operations(descriptor, error); } } @@ -468,17 +466,19 @@ private: write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); for (std::size_t i = 0; i < timer_queues_.size(); ++i) - { timer_queues_[i]->dispatch_timers(); - timer_queues_[i]->dispatch_cancellations(); - } // Issue any pending cancellations. for (std::size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); } // Run the select loop in the thread. @@ -512,10 +512,8 @@ private: int fd = kqueue(); if (fd == -1) { - boost::throw_exception( - asio::system_error( - asio::error_code(errno, - asio::error::system_category), + boost::throw_exception(asio::system_error( + asio::error_code(errno, asio::native_ecat), "kqueue")); } return fd; @@ -575,22 +573,6 @@ private: interrupter_.interrupt(); } - // Clean up operations and timers. We must not hold the lock since the - // destructors may make calls back into this reactor. We make a copy of the - // vector of timer queues since the original may be modified while the lock - // is not held. - void cleanup_operations_and_timers( - asio::detail::mutex::scoped_lock& lock) - { - timer_queues_for_cleanup_ = timer_queues_; - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); - for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) - timer_queues_for_cleanup_[i]->cleanup_timers(); - } - // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -615,10 +597,6 @@ private: // The timer queues. std::vector timer_queues_; - // A copy of the timer queues, used when cleaning up timers. The copy is - // stored as a class data member to avoid unnecessary memory allocation. - std::vector timer_queues_for_cleanup_; - // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/null_event.hpp b/libtorrent/include/asio/detail/null_event.hpp index 99bcbc6a4..df522ce0f 100644 --- a/libtorrent/include/asio/detail/null_event.hpp +++ b/libtorrent/include/asio/detail/null_event.hpp @@ -43,20 +43,17 @@ public: } // Signal the event. - template - void signal(Lock&) + void signal() { } // Reset the event. - template - void clear(Lock&) + void clear() { } // Wait for the event to become signalled. - template - void wait(Lock&) + void wait() { } }; diff --git a/libtorrent/include/asio/detail/posix_event.hpp b/libtorrent/include/asio/detail/posix_event.hpp index b48586f15..408c23bb9 100644 --- a/libtorrent/include/asio/detail/posix_event.hpp +++ b/libtorrent/include/asio/detail/posix_event.hpp @@ -24,12 +24,10 @@ #if defined(BOOST_HAS_PTHREADS) #include "asio/detail/push_options.hpp" -#include #include #include #include "asio/detail/pop_options.hpp" -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -44,11 +42,21 @@ public: posix_event() : signalled_(false) { - int error = ::pthread_cond_init(&cond_, 0); + int error = ::pthread_mutex_init(&mutex_, 0); if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), + "event"); + boost::throw_exception(e); + } + + error = ::pthread_cond_init(&cond_, 0); + if (error != 0) + { + ::pthread_mutex_destroy(&mutex_); + asio::system_error e( + asio::error_code(error, asio::native_ecat), "event"); boost::throw_exception(e); } @@ -58,37 +66,37 @@ public: ~posix_event() { ::pthread_cond_destroy(&cond_); + ::pthread_mutex_destroy(&mutex_); } // Signal the event. - template - void signal(Lock& lock) + void signal() { - BOOST_ASSERT(lock.locked()); - (void)lock; + ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. signalled_ = true; ::pthread_cond_signal(&cond_); // Ignore EINVAL. + ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } // Reset the event. - template - void clear(Lock& lock) + void clear() { - BOOST_ASSERT(lock.locked()); - (void)lock; + ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. signalled_ = false; + ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } // Wait for the event to become signalled. - template - void wait(Lock& lock) + void wait() { - BOOST_ASSERT(lock.locked()); + ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. while (!signalled_) - ::pthread_cond_wait(&cond_, &lock.mutex().mutex_); // Ignore EINVAL. + ::pthread_cond_wait(&cond_, &mutex_); // Ignore EINVAL. + ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } private: + ::pthread_mutex_t mutex_; ::pthread_cond_t cond_; bool signalled_; }; diff --git a/libtorrent/include/asio/detail/posix_mutex.hpp b/libtorrent/include/asio/detail/posix_mutex.hpp index 6067880fb..154089f3c 100644 --- a/libtorrent/include/asio/detail/posix_mutex.hpp +++ b/libtorrent/include/asio/detail/posix_mutex.hpp @@ -28,7 +28,6 @@ #include #include "asio/detail/pop_options.hpp" -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/scoped_lock.hpp" @@ -36,8 +35,6 @@ namespace asio { namespace detail { -class posix_event; - class posix_mutex : private noncopyable { @@ -51,7 +48,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "mutex"); boost::throw_exception(e); } @@ -70,7 +67,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "mutex"); boost::throw_exception(e); } @@ -83,14 +80,13 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "mutex"); boost::throw_exception(e); } } private: - friend class posix_event; ::pthread_mutex_t mutex_; }; diff --git a/libtorrent/include/asio/detail/posix_thread.hpp b/libtorrent/include/asio/detail/posix_thread.hpp index 6e5815426..f01b40428 100644 --- a/libtorrent/include/asio/detail/posix_thread.hpp +++ b/libtorrent/include/asio/detail/posix_thread.hpp @@ -29,7 +29,6 @@ #include #include "asio/detail/pop_options.hpp" -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -53,7 +52,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/posix_tss_ptr.hpp b/libtorrent/include/asio/detail/posix_tss_ptr.hpp index dda329c40..93fce3479 100644 --- a/libtorrent/include/asio/detail/posix_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/posix_tss_ptr.hpp @@ -28,7 +28,6 @@ #include #include "asio/detail/pop_options.hpp" -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -47,7 +46,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/reactive_socket_service.hpp b/libtorrent/include/asio/detail/reactive_socket_service.hpp index 9c0075821..d5b8e4fc8 100644 --- a/libtorrent/include/asio/detail/reactive_socket_service.hpp +++ b/libtorrent/include/asio/detail/reactive_socket_service.hpp @@ -86,7 +86,7 @@ public: }; // The maximum number of buffers to support in a single operation. - enum { max_buffers = 64 < max_iov_len ? 64 : max_iov_len }; + enum { max_buffers = 16 }; // Constructor. reactive_socket_service(asio::io_service& io_service) @@ -157,7 +157,7 @@ public: if (int err = reactor_.register_descriptor(sock.get())) { - ec = asio::error_code(err, asio::error::system_category); + ec = asio::error_code(err, asio::native_ecat); return ec; } @@ -181,7 +181,7 @@ public: if (int err = reactor_.register_descriptor(native_socket)) { - ec = asio::error_code(err, asio::error::system_category); + ec = asio::error_code(err, asio::native_ecat); return ec; } @@ -1124,7 +1124,7 @@ public: bool operator()(const asio::error_code& result) { // Check whether the operation was successful. - if (result) + if (result != 0) { io_service_.post(bind_handler(handler_, result, 0)); return true; @@ -1489,7 +1489,7 @@ public: if (connect_error) { ec = asio::error_code(connect_error, - asio::error::system_category); + asio::native_ecat); io_service_.post(bind_handler(handler_, ec)); return true; } diff --git a/libtorrent/include/asio/detail/scoped_lock.hpp b/libtorrent/include/asio/detail/scoped_lock.hpp index 57f8cb8f6..64c77cbab 100644 --- a/libtorrent/include/asio/detail/scoped_lock.hpp +++ b/libtorrent/include/asio/detail/scoped_lock.hpp @@ -63,18 +63,6 @@ public: } } - // Test whether the lock is held. - bool locked() const - { - return locked_; - } - - // Get the underlying mutex. - Mutex& mutex() - { - return mutex_; - } - private: // The underlying mutex. Mutex& mutex_; diff --git a/libtorrent/include/asio/detail/select_reactor.hpp b/libtorrent/include/asio/detail/select_reactor.hpp index 079ec2de8..83f093ae6 100644 --- a/libtorrent/include/asio/detail/select_reactor.hpp +++ b/libtorrent/include/asio/detail/select_reactor.hpp @@ -229,10 +229,7 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - std::size_t n = timer_queue.cancel_timer(token); - if (n > 0) - interrupter_.interrupt(); - return n; + return timer_queue.cancel_timer(token); } private: @@ -248,13 +245,16 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); - for (std::size_t i = 0; i < timer_queues_.size(); ++i) - timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); return; } @@ -263,7 +263,12 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); return; } @@ -316,17 +321,19 @@ private: write_op_queue_.dispatch_cancellations(); } for (std::size_t i = 0; i < timer_queues_.size(); ++i) - { timer_queues_[i]->dispatch_timers(); - timer_queues_[i]->dispatch_cancellations(); - } // Issue any pending cancellations. for (size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - cleanup_operations_and_timers(lock); + // Clean up operations. We must not hold the lock since the operations may + // make calls back into this reactor. + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); } // Run the select loop in the thread. @@ -407,22 +414,6 @@ private: interrupter_.interrupt(); } - // Clean up operations and timers. We must not hold the lock since the - // destructors may make calls back into this reactor. We make a copy of the - // vector of timer queues since the original may be modified while the lock - // is not held. - void cleanup_operations_and_timers( - asio::detail::mutex::scoped_lock& lock) - { - timer_queues_for_cleanup_ = timer_queues_; - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); - for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) - timer_queues_for_cleanup_[i]->cleanup_timers(); - } - // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -444,10 +435,6 @@ private: // The timer queues. std::vector timer_queues_; - // A copy of the timer queues, used when cleaning up timers. The copy is - // stored as a class data member to avoid unnecessary memory allocation. - std::vector timer_queues_for_cleanup_; - // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/service_registry.hpp b/libtorrent/include/asio/detail/service_registry.hpp index 3a9e9f620..bd1c3ea5b 100644 --- a/libtorrent/include/asio/detail/service_registry.hpp +++ b/libtorrent/include/asio/detail/service_registry.hpp @@ -166,8 +166,7 @@ private: } // Check if a service matches the given id. - static bool service_id_matches( - const asio::io_service::service& service, + bool service_id_matches(const asio::io_service::service& service, const asio::io_service::id& id) { return service.id_ == &id; @@ -175,8 +174,7 @@ private: // Check if a service matches the given id. template - static bool service_id_matches( - const asio::io_service::service& service, + bool service_id_matches(const asio::io_service::service& service, const asio::detail::service_id& /*id*/) { return service.type_info_ != 0 && *service.type_info_ == typeid(Service); diff --git a/libtorrent/include/asio/detail/socket_ops.hpp b/libtorrent/include/asio/detail/socket_ops.hpp index 98f3b0f64..4b38c6ee8 100644 --- a/libtorrent/include/asio/detail/socket_ops.hpp +++ b/libtorrent/include/asio/detail/socket_ops.hpp @@ -52,10 +52,9 @@ inline ReturnType error_wrapper(ReturnType return_value, asio::error_code& ec) { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) - ec = asio::error_code(WSAGetLastError(), - asio::error::system_category); + ec = asio::error_code(WSAGetLastError(), asio::native_ecat); #else - ec = asio::error_code(errno, asio::error::system_category); + ec = asio::error_code(errno, asio::native_ecat); #endif return return_value; } @@ -924,13 +923,6 @@ inline void gai_free(void* p) ::operator delete(p); } -inline void gai_strcpy(char* target, const char* source, std::size_t max_size) -{ - using namespace std; - *target = 0; - strncat(target, source, max_size); -} - enum { gai_clone_flag = 1 << 30 }; inline int gai_aistruct(addrinfo_type*** next, const addrinfo_type* hints, @@ -1300,15 +1292,14 @@ inline int getaddrinfo_emulation(const char* host, const char* service, if (host != 0 && host[0] != '\0' && hptr->h_name && hptr->h_name[0] && (hints.ai_flags & AI_CANONNAME) && canon == 0) { - std::size_t canon_len = strlen(hptr->h_name) + 1; - canon = gai_alloc(canon_len); + canon = gai_alloc(strlen(hptr->h_name) + 1); if (canon == 0) { freeaddrinfo_emulation(aihead); socket_ops::freehostent(hptr); return EAI_MEMORY; } - gai_strcpy(canon, hptr->h_name, canon_len); + strcpy(canon, hptr->h_name); } // Create an addrinfo structure for each returned address. @@ -1344,14 +1335,13 @@ inline int getaddrinfo_emulation(const char* host, const char* service, } else { - std::size_t canonname_len = strlen(search[0].host) + 1; - aihead->ai_canonname = gai_alloc(canonname_len); + aihead->ai_canonname = gai_alloc(strlen(search[0].host) + 1); if (aihead->ai_canonname == 0) { freeaddrinfo_emulation(aihead); return EAI_MEMORY; } - gai_strcpy(aihead->ai_canonname, search[0].host, canonname_len); + strcpy(aihead->ai_canonname, search[0].host); } } gai_free(canon); @@ -1434,7 +1424,8 @@ inline asio::error_code getnameinfo_emulation( *dot = 0; } } - gai_strcpy(host, hptr->h_name, hostlen); + *host = '\0'; + strncat(host, hptr->h_name, hostlen); socket_ops::freehostent(hptr); } else @@ -1472,7 +1463,8 @@ inline asio::error_code getnameinfo_emulation( servent* sptr = ::getservbyport(port, (flags & NI_DGRAM) ? "udp" : 0); if (sptr && sptr->s_name && sptr->s_name[0] != '\0') { - gai_strcpy(serv, sptr->s_name, servlen); + *serv = '\0'; + strncat(serv, sptr->s_name, servlen); } else { @@ -1512,12 +1504,6 @@ inline asio::error_code translate_addrinfo_error(int error) case EAI_MEMORY: return asio::error::no_memory; case EAI_NONAME: -#if defined(EAI_ADDRFAMILY) - case EAI_ADDRFAMILY: -#endif -#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) - case EAI_NODATA: -#endif return asio::error::host_not_found; case EAI_SERVICE: return asio::error::service_not_found; @@ -1526,10 +1512,10 @@ inline asio::error_code translate_addrinfo_error(int error) default: // Possibly the non-portable EAI_SYSTEM. #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) return asio::error_code( - WSAGetLastError(), asio::error::system_category); + WSAGetLastError(), asio::native_ecat); #else return asio::error_code( - errno, asio::error::system_category); + errno, asio::native_ecat); #endif } } diff --git a/libtorrent/include/asio/detail/socket_option.hpp b/libtorrent/include/asio/detail/socket_option.hpp index 1a03936ab..ee867e6b2 100644 --- a/libtorrent/include/asio/detail/socket_option.hpp +++ b/libtorrent/include/asio/detail/socket_option.hpp @@ -110,19 +110,8 @@ public: template void resize(const Protocol&, std::size_t s) { - // On some platforms (e.g. Windows Vista), the getsockopt function will - // return the size of a boolean socket option as one byte, even though a - // four byte integer was passed in. - switch (s) - { - case sizeof(char): - value_ = *reinterpret_cast(&value_) ? 1 : 0; - break; - case sizeof(value_): - break; - default: + if (s != sizeof(value_)) throw std::length_error("boolean socket option resize"); - } } private: diff --git a/libtorrent/include/asio/detail/socket_types.hpp b/libtorrent/include/asio/detail/socket_types.hpp index 02c3a78d5..49d1c7fc2 100644 --- a/libtorrent/include/asio/detail/socket_types.hpp +++ b/libtorrent/include/asio/detail/socket_types.hpp @@ -98,7 +98,6 @@ # include # include # include -# include # if defined(__sun) # include # include @@ -142,11 +141,6 @@ const int shutdown_both = SD_BOTH; const int message_peek = MSG_PEEK; const int message_out_of_band = MSG_OOB; const int message_do_not_route = MSG_DONTROUTE; -# if defined (_WIN32_WINNT) -const int max_iov_len = 64; -# else -const int max_iov_len = 16; -# endif #else typedef int socket_type; const int invalid_socket = -1; @@ -172,7 +166,6 @@ const int shutdown_both = SHUT_RDWR; const int message_peek = MSG_PEEK; const int message_out_of_band = MSG_OOB; const int message_do_not_route = MSG_DONTROUTE; -const int max_iov_len = IOV_MAX; #endif const int custom_socket_option_level = 0xA5100000; const int enable_connection_aborted_option = 1; diff --git a/libtorrent/include/asio/detail/strand_service.hpp b/libtorrent/include/asio/detail/strand_service.hpp index d987cb98d..f10289090 100644 --- a/libtorrent/include/asio/detail/strand_service.hpp +++ b/libtorrent/include/asio/detail/strand_service.hpp @@ -239,7 +239,6 @@ public: #else BOOST_ASSERT(size <= strand_impl::handler_storage_type::size); #endif - (void)size; return impl_->handler_storage_.address(); } @@ -416,14 +415,14 @@ public: } else { + asio::detail::mutex::scoped_lock lock(impl->mutex_); + // Allocate and construct an object to wrap the handler. typedef handler_wrapper value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, handler); - asio::detail::mutex::scoped_lock lock(impl->mutex_); - if (impl->current_handler_ == 0) { // This handler now has the lock, so can be dispatched immediately. @@ -456,14 +455,14 @@ public: template void post(implementation_type& impl, Handler handler) { + asio::detail::mutex::scoped_lock lock(impl->mutex_); + // Allocate and construct an object to wrap the handler. typedef handler_wrapper value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, handler); - asio::detail::mutex::scoped_lock lock(impl->mutex_); - if (impl->current_handler_ == 0) { // This handler now has the lock, so can be dispatched immediately. diff --git a/libtorrent/include/asio/detail/task_io_service.hpp b/libtorrent/include/asio/detail/task_io_service.hpp index 802d7ea95..07df1c18a 100644 --- a/libtorrent/include/asio/detail/task_io_service.hpp +++ b/libtorrent/include/asio/detail/task_io_service.hpp @@ -40,7 +40,6 @@ public: : asio::detail::service_base >(io_service), mutex_(), task_(use_service(io_service)), - task_interrupted_(true), outstanding_work_(0), handler_queue_(&task_handler_), handler_queue_end_(&task_handler_), @@ -81,7 +80,8 @@ public: typename call_stack::context ctx(this); idle_thread_info this_idle_thread; - this_idle_thread.next = 0; + this_idle_thread.prev = &this_idle_thread; + this_idle_thread.next = &this_idle_thread; asio::detail::mutex::scoped_lock lock(mutex_); @@ -98,7 +98,8 @@ public: typename call_stack::context ctx(this); idle_thread_info this_idle_thread; - this_idle_thread.next = 0; + this_idle_thread.prev = &this_idle_thread; + this_idle_thread.next = &this_idle_thread; asio::detail::mutex::scoped_lock lock(mutex_); @@ -133,7 +134,7 @@ public: void stop() { asio::detail::mutex::scoped_lock lock(mutex_); - stop_all_threads(lock); + stop_all_threads(); } // Reset in preparation for a subsequent run invocation. @@ -155,7 +156,7 @@ public: { asio::detail::mutex::scoped_lock lock(mutex_); if (--outstanding_work_ == 0) - stop_all_threads(lock); + stop_all_threads(); } // Request invocation of the given handler. @@ -200,14 +201,9 @@ public: ++outstanding_work_; // Wake up a thread to execute the handler. - if (!interrupt_one_idle_thread(lock)) - { - if (!task_interrupted_) - { - task_interrupted_ = true; + if (!interrupt_one_idle_thread()) + if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) task_.interrupt(); - } - } } private: @@ -218,7 +214,7 @@ private: { if (outstanding_work_ == 0 && !stopped_) { - stop_all_threads(lock); + stop_all_threads(); ec = asio::error_code(); return 0; } @@ -234,14 +230,11 @@ private: handler_queue_ = h->next_; if (handler_queue_ == 0) handler_queue_end_ = 0; - h->next_ = 0; + bool more_handlers = (handler_queue_ != 0); + lock.unlock(); if (h == &task_handler_) { - bool more_handlers = (handler_queue_ != 0); - task_interrupted_ = more_handlers || polling; - lock.unlock(); - // If the task has already run and we're polling then we're done. if (task_has_run && polling) { @@ -259,7 +252,6 @@ private: } else { - lock.unlock(); handler_cleanup c(lock, *this); // Invoke the handler. May throw an exception. @@ -272,10 +264,31 @@ private: else if (this_idle_thread) { // Nothing to run right now, so just wait for work to do. - this_idle_thread->next = first_idle_thread_; + if (first_idle_thread_) + { + this_idle_thread->next = first_idle_thread_; + this_idle_thread->prev = first_idle_thread_->prev; + first_idle_thread_->prev->next = this_idle_thread; + first_idle_thread_->prev = this_idle_thread; + } first_idle_thread_ = this_idle_thread; - this_idle_thread->wakeup_event.clear(lock); - this_idle_thread->wakeup_event.wait(lock); + this_idle_thread->wakeup_event.clear(); + lock.unlock(); + this_idle_thread->wakeup_event.wait(); + lock.lock(); + if (this_idle_thread->next == this_idle_thread) + { + first_idle_thread_ = 0; + } + else + { + if (first_idle_thread_ == this_idle_thread) + first_idle_thread_ = this_idle_thread->next; + this_idle_thread->next->prev = this_idle_thread->prev; + this_idle_thread->prev->next = this_idle_thread->next; + this_idle_thread->next = this_idle_thread; + this_idle_thread->prev = this_idle_thread; + } } else { @@ -289,44 +302,39 @@ private: } // Stop the task and all idle threads. - void stop_all_threads( - asio::detail::mutex::scoped_lock& lock) + void stop_all_threads() { stopped_ = true; - interrupt_all_idle_threads(lock); - if (!task_interrupted_) - { - task_interrupted_ = true; + interrupt_all_idle_threads(); + if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) task_.interrupt(); - } } // Interrupt a single idle thread. Returns true if a thread was interrupted, // false if no running thread could be found to interrupt. - bool interrupt_one_idle_thread( - asio::detail::mutex::scoped_lock& lock) + bool interrupt_one_idle_thread() { if (first_idle_thread_) { - idle_thread_info* idle_thread = first_idle_thread_; - first_idle_thread_ = idle_thread->next; - idle_thread->next = 0; - idle_thread->wakeup_event.signal(lock); + first_idle_thread_->wakeup_event.signal(); + first_idle_thread_ = first_idle_thread_->next; return true; } return false; } // Interrupt all idle threads. - void interrupt_all_idle_threads( - asio::detail::mutex::scoped_lock& lock) + void interrupt_all_idle_threads() { - while (first_idle_thread_) + if (first_idle_thread_) { - idle_thread_info* idle_thread = first_idle_thread_; - first_idle_thread_ = idle_thread->next; - idle_thread->next = 0; - idle_thread->wakeup_event.signal(lock); + first_idle_thread_->wakeup_event.signal(); + idle_thread_info* current_idle_thread = first_idle_thread_->next; + while (current_idle_thread != first_idle_thread_) + { + current_idle_thread->wakeup_event.signal(); + current_idle_thread = current_idle_thread->next; + } } } @@ -432,7 +440,6 @@ private: { // Reinsert the task at the end of the handler queue. lock_.lock(); - task_io_service_.task_interrupted_ = true; task_io_service_.task_handler_.next_ = 0; if (task_io_service_.handler_queue_end_) { @@ -471,7 +478,7 @@ private: { lock_.lock(); if (--task_io_service_.outstanding_work_ == 0) - task_io_service_.stop_all_threads(lock_); + task_io_service_.stop_all_threads(); } private: @@ -496,9 +503,6 @@ private: } } task_handler_; - // Whether the task has been interrupted. - bool task_interrupted_; - // The count of unfinished work. int outstanding_work_; @@ -518,6 +522,7 @@ private: struct idle_thread_info { event wakeup_event; + idle_thread_info* prev; idle_thread_info* next; }; diff --git a/libtorrent/include/asio/detail/timer_queue.hpp b/libtorrent/include/asio/detail/timer_queue.hpp index 7735e87cf..af1e36bd5 100644 --- a/libtorrent/include/asio/detail/timer_queue.hpp +++ b/libtorrent/include/asio/detail/timer_queue.hpp @@ -48,9 +48,7 @@ public: // Constructor. timer_queue() : timers_(), - heap_(), - cancelled_timers_(0), - cleanup_timers_(0) + heap_() { } @@ -113,17 +111,12 @@ public: { timer_base* t = heap_[0]; remove_timer(t); - t->prev_ = 0; - t->next_ = cleanup_timers_; - cleanup_timers_ = t; t->invoke(asio::error_code()); } } - // Cancel the timers with the given token. Any timers pending for the token - // will be notified that they have been cancelled next time - // dispatch_cancellations is called. Returns the number of timers that were - // cancelled. + // Cancel the timer with the given token. The handler will be invoked + // immediately with the result operation_aborted. std::size_t cancel_timer(void* timer_token) { std::size_t num_cancelled = 0; @@ -136,9 +129,7 @@ public: { timer_base* next = t->next_; remove_timer(t); - t->prev_ = 0; - t->next_ = cancelled_timers_; - cancelled_timers_ = t; + t->invoke(asio::error::operation_aborted); t = next; ++num_cancelled; } @@ -146,31 +137,6 @@ public: return num_cancelled; } - // Dispatch any pending cancels for timers. - virtual void dispatch_cancellations() - { - while (cancelled_timers_) - { - timer_base* this_timer = cancelled_timers_; - cancelled_timers_ = this_timer->next_; - this_timer->next_ = cleanup_timers_; - cleanup_timers_ = this_timer; - this_timer->invoke(asio::error::operation_aborted); - } - } - - // Destroy timers that are waiting to be cleaned up. - virtual void cleanup_timers() - { - while (cleanup_timers_) - { - timer_base* next_timer = cleanup_timers_->next_; - cleanup_timers_->next_ = 0; - cleanup_timers_->destroy(); - cleanup_timers_ = next_timer; - } - } - // Destroy all timers. virtual void destroy_timers() { @@ -185,7 +151,6 @@ public: } heap_.clear(); timers_.clear(); - cleanup_timers(); } private: @@ -273,7 +238,8 @@ private: static void invoke_handler(timer_base* base, const asio::error_code& result) { - static_cast*>(base)->handler_(result); + std::auto_ptr > t(static_cast*>(base)); + t->handler_(result); } // Destroy the handler. @@ -372,12 +338,6 @@ private: // The heap of timers, with the earliest timer at the front. std::vector heap_; - - // The list of timers to be cancelled. - timer_base* cancelled_timers_; - - // The list of timers to be destroyed. - timer_base* cleanup_timers_; }; } // namespace detail diff --git a/libtorrent/include/asio/detail/timer_queue_base.hpp b/libtorrent/include/asio/detail/timer_queue_base.hpp index 6cf25747a..c8be49748 100644 --- a/libtorrent/include/asio/detail/timer_queue_base.hpp +++ b/libtorrent/include/asio/detail/timer_queue_base.hpp @@ -44,12 +44,6 @@ public: // Dispatch all ready timers. virtual void dispatch_timers() = 0; - // Dispatch any pending cancels for timers. - virtual void dispatch_cancellations() = 0; - - // Destroy timers that are waiting to be cleaned up. - virtual void cleanup_timers() = 0; - // Destroy all timers. virtual void destroy_timers() = 0; }; diff --git a/libtorrent/include/asio/detail/win_event.hpp b/libtorrent/include/asio/detail/win_event.hpp index c73ed56ea..8de9383da 100644 --- a/libtorrent/include/asio/detail/win_event.hpp +++ b/libtorrent/include/asio/detail/win_event.hpp @@ -23,13 +23,11 @@ #if defined(BOOST_WINDOWS) -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" #include "asio/detail/push_options.hpp" -#include #include #include "asio/detail/pop_options.hpp" @@ -48,8 +46,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "event"); boost::throw_exception(e); } @@ -62,31 +59,21 @@ public: } // Signal the event. - template - void signal(Lock& lock) + void signal() { - BOOST_ASSERT(lock.locked()); - (void)lock; ::SetEvent(event_); } // Reset the event. - template - void clear(Lock& lock) + void clear() { - BOOST_ASSERT(lock.locked()); - (void)lock; ::ResetEvent(event_); } // Wait for the event to become signalled. - template - void wait(Lock& lock) + void wait() { - BOOST_ASSERT(lock.locked()); - lock.unlock(); ::WaitForSingleObject(event_, INFINITE); - lock.lock(); } private: diff --git a/libtorrent/include/asio/detail/win_iocp_io_service.hpp b/libtorrent/include/asio/detail/win_iocp_io_service.hpp index 61eeb1745..4957fb01a 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service.hpp @@ -63,8 +63,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "iocp"); boost::throw_exception(e); } @@ -174,8 +173,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "pqcs"); boost::throw_exception(e); } @@ -230,8 +228,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "pqcs"); boost::throw_exception(e); } @@ -250,8 +247,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "pqcs"); boost::throw_exception(e); } @@ -316,7 +312,7 @@ private: { DWORD last_error = ::GetLastError(); ec = asio::error_code(last_error, - asio::error::system_category); + asio::native_ecat); return 0; } diff --git a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp index 26eacae2a..184fdfa18 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp @@ -21,8 +21,6 @@ #include #include "asio/detail/pop_options.hpp" -#include "asio/detail/socket_types.hpp" - // This service is only supported on Win32 (NT4 and later). #if !defined(ASIO_DISABLE_IOCP) #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) diff --git a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp index 17d1d5887..007286e8d 100644 --- a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp @@ -137,7 +137,7 @@ public: enum { enable_connection_aborted = 1, // User wants connection_aborted errors. - close_might_block = 2, // User set linger option for blocking close. + user_set_linger = 2, // The user set the linger option. user_set_non_blocking = 4 // The user wants a non-blocking socket. }; @@ -170,7 +170,7 @@ public: typedef detail::select_reactor reactor_type; // The maximum number of buffers to support in a single operation. - enum { max_buffers = 64 < max_iov_len ? 64 : max_iov_len }; + enum { max_buffers = 16 }; // Constructor. win_iocp_socket_service(asio::io_service& io_service) @@ -192,7 +192,7 @@ public: while (impl) { asio::error_code ignored_ec; - close_for_destruction(*impl); + close(*impl, ignored_ec); impl = impl->next_; } } @@ -217,7 +217,34 @@ public: // Destroy a socket implementation. void destroy(implementation_type& impl) { - close_for_destruction(impl); + if (impl.socket_ != invalid_socket) + { + // Check if the reactor was created, in which case we need to close the + // socket on the reactor as well to cancel any operations that might be + // running there. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (reactor) + reactor->close_descriptor(impl.socket_); + + if (impl.flags_ & implementation_type::user_set_linger) + { + ::linger opt; + opt.l_onoff = 0; + opt.l_linger = 0; + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, + SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); + } + + asio::error_code ignored_ec; + socket_ops::close(impl.socket_, ignored_ec); + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + } // Remove implementation from linked list of all implementations. asio::detail::mutex::scoped_lock lock(mutex_); @@ -326,25 +353,6 @@ public: { ec = asio::error::bad_descriptor; } - else if (FARPROC cancel_io_ex_ptr = ::GetProcAddress( - ::GetModuleHandle("KERNEL32"), "CancelIoEx")) - { - // The version of Windows supports cancellation from any thread. - typedef BOOL (WINAPI* cancel_io_ex_t)(HANDLE, LPOVERLAPPED); - cancel_io_ex_t cancel_io_ex = (cancel_io_ex_t)cancel_io_ex_ptr; - socket_type sock = impl.socket_; - HANDLE sock_as_handle = reinterpret_cast(sock); - if (!cancel_io_ex(sock_as_handle, 0)) - { - DWORD last_error = ::GetLastError(); - ec = asio::error_code(last_error, - asio::error::system_category); - } - else - { - ec = asio::error_code(); - } - } else if (impl.safe_cancellation_thread_id_ == 0) { // No operations have been started, so there's nothing to cancel. @@ -359,8 +367,7 @@ public: if (!::CancelIo(sock_as_handle)) { DWORD last_error = ::GetLastError(); - ec = asio::error_code(last_error, - asio::error::system_category); + ec = asio::error_code(last_error, asio::native_ecat); } else { @@ -468,12 +475,7 @@ public: if (option.level(impl.protocol_) == SOL_SOCKET && option.name(impl.protocol_) == SO_LINGER) { - const ::linger* linger_option = - reinterpret_cast(option.data(impl.protocol_)); - if (linger_option->l_onoff != 0 && linger_option->l_linger != 0) - impl.flags_ |= implementation_type::close_might_block; - else - impl.flags_ &= ~implementation_type::close_might_block; + impl.flags_ |= implementation_type::user_set_linger; } socket_ops::setsockopt(impl.socket_, @@ -666,8 +668,7 @@ public: last_error = WSAECONNRESET; else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, - asio::error::system_category); + ec = asio::error_code(last_error, asio::native_ecat); return 0; } @@ -718,8 +719,7 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -821,8 +821,7 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -866,8 +865,7 @@ public: DWORD last_error = ::WSAGetLastError(); if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, - asio::error::system_category); + ec = asio::error_code(last_error, asio::native_ecat); return 0; } @@ -916,8 +914,7 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -1000,8 +997,7 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1055,8 +1051,7 @@ public: last_error = WSAECONNRESET; else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, - asio::error::system_category); + ec = asio::error_code(last_error, asio::native_ecat); return 0; } if (bytes_transferred == 0) @@ -1114,8 +1109,7 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -1222,8 +1216,7 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1269,8 +1262,7 @@ public: DWORD last_error = ::WSAGetLastError(); if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, - asio::error::system_category); + ec = asio::error_code(last_error, asio::native_ecat); return 0; } if (bytes_transferred == 0) @@ -1336,8 +1328,7 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -1431,8 +1422,7 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1669,8 +1659,7 @@ public: ptr.reset(); // Call the handler. - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); asio_handler_invoke_helpers::invoke( detail::bind_handler(handler, ec), &handler); } @@ -1770,8 +1759,7 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, - asio::error::system_category); + asio::error_code ec(last_error, asio::native_ecat); iocp_service_.post(bind_handler(handler, ec)); } } @@ -1847,8 +1835,8 @@ public: // If connection failed then post the handler with the error code. if (connect_error) { - ec = asio::error_code(connect_error, - asio::error::system_category); + ec = asio::error_code( + connect_error, asio::native_ecat); io_service_.post(bind_handler(handler_, ec)); return true; } @@ -1962,66 +1950,26 @@ public: } private: - // Helper function to close a socket when the associated object is being - // destroyed. - void close_for_destruction(implementation_type& impl) - { - if (is_open(impl)) - { - // Check if the reactor was created, in which case we need to close the - // socket on the reactor as well to cancel any operations that might be - // running there. - reactor_type* reactor = static_cast( - interlocked_compare_exchange_pointer( - reinterpret_cast(&reactor_), 0, 0)); - if (reactor) - reactor->close_descriptor(impl.socket_); - - // The socket destructor must not block. If the user has changed the - // linger option to block in the foreground, we will change it back to the - // default so that the closure is performed in the background. - if (impl.flags_ & implementation_type::close_might_block) - { - ::linger opt; - opt.l_onoff = 0; - opt.l_linger = 0; - asio::error_code ignored_ec; - socket_ops::setsockopt(impl.socket_, - SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); - } - - asio::error_code ignored_ec; - socket_ops::close(impl.socket_, ignored_ec); - impl.socket_ = invalid_socket; - impl.flags_ = 0; - impl.cancel_token_.reset(); - impl.safe_cancellation_thread_id_ = 0; - } - } - - // Helper function to emulate InterlockedCompareExchangePointer functionality - // for: - // - very old Platform SDKs; and - // - platform SDKs where MSVC's /Wp64 option causes spurious warnings. + // Helper function to provide InterlockedCompareExchangePointer functionality + // on very old Platform SDKs. void* interlocked_compare_exchange_pointer(void** dest, void* exch, void* cmp) { -#if defined(_M_IX86) +#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) return reinterpret_cast(InterlockedCompareExchange( - reinterpret_cast(dest), reinterpret_cast(exch), + reinterpret_cast(dest), reinterpret_cast(exch), reinterpret_cast(cmp))); #else return InterlockedCompareExchangePointer(dest, exch, cmp); #endif } - // Helper function to emulate InterlockedExchangePointer functionality for: - // - very old Platform SDKs; and - // - platform SDKs where MSVC's /Wp64 option causes spurious warnings. + // Helper function to provide InterlockedExchangePointer functionality on very + // old Platform SDKs. void* interlocked_exchange_pointer(void** dest, void* val) { -#if defined(_M_IX86) +#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) return reinterpret_cast(InterlockedExchange( - reinterpret_cast(dest), reinterpret_cast(val))); + reinterpret_cast(dest), reinterpret_cast(val))); #else return InterlockedExchangePointer(dest, val); #endif diff --git a/libtorrent/include/asio/detail/win_mutex.hpp b/libtorrent/include/asio/detail/win_mutex.hpp index 4d1bc20c2..82659831f 100644 --- a/libtorrent/include/asio/detail/win_mutex.hpp +++ b/libtorrent/include/asio/detail/win_mutex.hpp @@ -23,7 +23,6 @@ #if defined(BOOST_WINDOWS) -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -49,7 +48,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "mutex"); boost::throw_exception(e); } @@ -68,7 +67,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, asio::native_ecat), "mutex"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_thread.hpp b/libtorrent/include/asio/detail/win_thread.hpp index c6bd61af5..a6c9b15d2 100644 --- a/libtorrent/include/asio/detail/win_thread.hpp +++ b/libtorrent/include/asio/detail/win_thread.hpp @@ -23,7 +23,6 @@ #if defined(BOOST_WINDOWS) -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -55,8 +54,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_tss_ptr.hpp b/libtorrent/include/asio/detail/win_tss_ptr.hpp index d84810d41..d3e2f8161 100644 --- a/libtorrent/include/asio/detail/win_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/win_tss_ptr.hpp @@ -23,7 +23,6 @@ #if defined(BOOST_WINDOWS) -#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -48,8 +47,7 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, - asio::error::system_category), + asio::error_code(last_error, asio::native_ecat), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/winsock_init.hpp b/libtorrent/include/asio/detail/winsock_init.hpp index 874d2b77b..67c69e8ce 100644 --- a/libtorrent/include/asio/detail/winsock_init.hpp +++ b/libtorrent/include/asio/detail/winsock_init.hpp @@ -85,8 +85,7 @@ public: if (this != &instance_ && ref_->result() != 0) { asio::system_error e( - asio::error_code(ref_->result(), - asio::error::system_category), + asio::error_code(ref_->result(), asio::native_ecat), "winsock"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/wrapped_handler.hpp b/libtorrent/include/asio/detail/wrapped_handler.hpp index 913a795dc..f757fd3dc 100644 --- a/libtorrent/include/asio/detail/wrapped_handler.hpp +++ b/libtorrent/include/asio/detail/wrapped_handler.hpp @@ -17,10 +17,6 @@ #include "asio/detail/push_options.hpp" -#include "asio/detail/push_options.hpp" -#include -#include "asio/detail/pop_options.hpp" - #include "asio/detail/bind_handler.hpp" #include "asio/detail/handler_alloc_helpers.hpp" #include "asio/detail/handler_invoke_helpers.hpp" @@ -34,9 +30,7 @@ class wrapped_handler public: typedef void result_type; - wrapped_handler( - typename boost::add_reference::type dispatcher, - Handler handler) + wrapped_handler(Dispatcher& dispatcher, Handler handler) : dispatcher_(dispatcher), handler_(handler) { @@ -123,7 +117,7 @@ public: } //private: - Dispatcher dispatcher_; + Dispatcher& dispatcher_; Handler handler_; }; @@ -177,9 +171,9 @@ inline void asio_handler_invoke(const Function& function, function, this_handler->handler_)); } -template +template inline void asio_handler_invoke(const Function& function, - rewrapped_handler* this_handler) + rewrapped_handler* this_handler) { asio_handler_invoke_helpers::invoke( function, &this_handler->context_); diff --git a/libtorrent/include/asio/error.hpp b/libtorrent/include/asio/error.hpp index a8316be2c..935cc6796 100644 --- a/libtorrent/include/asio/error.hpp +++ b/libtorrent/include/asio/error.hpp @@ -37,195 +37,327 @@ /// INTERNAL ONLY. # define ASIO_WIN_OR_POSIX(e_win, e_posix) implementation_defined #elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) -# define ASIO_NATIVE_ERROR(e) e -# define ASIO_SOCKET_ERROR(e) WSA ## e -# define ASIO_NETDB_ERROR(e) WSA ## e -# define ASIO_GETADDRINFO_ERROR(e) WSA ## e +# define ASIO_NATIVE_ERROR(e) \ + asio::error_code(e, \ + asio::native_ecat) +# define ASIO_SOCKET_ERROR(e) \ + asio::error_code(WSA ## e, \ + asio::native_ecat) +# define ASIO_NETDB_ERROR(e) \ + asio::error_code(WSA ## e, \ + asio::native_ecat) +# define ASIO_GETADDRINFO_ERROR(e) \ + asio::error_code(WSA ## e, \ + asio::native_ecat) +# define ASIO_MISC_ERROR(e) \ + asio::error_code(e, \ + asio::misc_ecat) # define ASIO_WIN_OR_POSIX(e_win, e_posix) e_win #else -# define ASIO_NATIVE_ERROR(e) e -# define ASIO_SOCKET_ERROR(e) e -# define ASIO_NETDB_ERROR(e) e -# define ASIO_GETADDRINFO_ERROR(e) e +# define ASIO_NATIVE_ERROR(e) \ + asio::error_code(e, \ + asio::native_ecat) +# define ASIO_SOCKET_ERROR(e) \ + asio::error_code(e, \ + asio::native_ecat) +# define ASIO_NETDB_ERROR(e) \ + asio::error_code(e, \ + asio::netdb_ecat) +# define ASIO_GETADDRINFO_ERROR(e) \ + asio::error_code(e, \ + asio::addrinfo_ecat) +# define ASIO_MISC_ERROR(e) \ + asio::error_code(e, \ + asio::misc_ecat) # define ASIO_WIN_OR_POSIX(e_win, e_posix) e_posix #endif namespace asio { -namespace error { -enum basic_errors +namespace detail { + +/// Hack to keep asio library header-file-only. +template +class error_base { +public: + // boostify: error category declarations go here. + /// Permission denied. - access_denied = ASIO_SOCKET_ERROR(EACCES), + static const asio::error_code access_denied; /// Address family not supported by protocol. - address_family_not_supported = ASIO_SOCKET_ERROR(EAFNOSUPPORT), + static const asio::error_code address_family_not_supported; /// Address already in use. - address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE), + static const asio::error_code address_in_use; /// Transport endpoint is already connected. - already_connected = ASIO_SOCKET_ERROR(EISCONN), + static const asio::error_code already_connected; + + /// Already open. + static const asio::error_code already_open; /// Operation already in progress. - already_started = ASIO_SOCKET_ERROR(EALREADY), + static const asio::error_code already_started; /// A connection has been aborted. - connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED), + static const asio::error_code connection_aborted; /// Connection refused. - connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED), + static const asio::error_code connection_refused; /// Connection reset by peer. - connection_reset = ASIO_SOCKET_ERROR(ECONNRESET), + static const asio::error_code connection_reset; /// Bad file descriptor. - bad_descriptor = ASIO_SOCKET_ERROR(EBADF), - - /// Bad address. - fault = ASIO_SOCKET_ERROR(EFAULT), - - /// No route to host. - host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH), - - /// Operation now in progress. - in_progress = ASIO_SOCKET_ERROR(EINPROGRESS), - - /// Interrupted system call. - interrupted = ASIO_SOCKET_ERROR(EINTR), - - /// Invalid argument. - invalid_argument = ASIO_SOCKET_ERROR(EINVAL), - - /// Message too long. - message_size = ASIO_SOCKET_ERROR(EMSGSIZE), - - /// Network is down. - network_down = ASIO_SOCKET_ERROR(ENETDOWN), - - /// Network dropped connection on reset. - network_reset = ASIO_SOCKET_ERROR(ENETRESET), - - /// Network is unreachable. - network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH), - - /// Too many open files. - no_descriptors = ASIO_SOCKET_ERROR(EMFILE), - - /// No buffer space available. - no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS), - - /// Cannot allocate memory. - no_memory = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), - ASIO_NATIVE_ERROR(ENOMEM)), - - /// Operation not permitted. - no_permission = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), - ASIO_NATIVE_ERROR(EPERM)), - - /// Protocol not available. - no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT), - - /// Transport endpoint is not connected. - not_connected = ASIO_SOCKET_ERROR(ENOTCONN), - - /// Socket operation on non-socket. - not_socket = ASIO_SOCKET_ERROR(ENOTSOCK), - - /// Operation cancelled. - operation_aborted = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), - ASIO_NATIVE_ERROR(ECANCELED)), - - /// Operation not supported. - operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP), - - /// Cannot send after transport endpoint shutdown. - shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN), - - /// Connection timed out. - timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT), - - /// Resource temporarily unavailable. - try_again = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_RETRY), - ASIO_NATIVE_ERROR(EAGAIN)), - - /// The socket is marked non-blocking and the requested operation would block. - would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK) -}; - -enum netdb_errors -{ - /// Host not found (authoritative). - host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND), - - /// Host not found (non-authoritative). - host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN), - - /// The query is valid but does not have associated address data. - no_data = ASIO_NETDB_ERROR(NO_DATA), - - /// A non-recoverable error occurred. - no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY) -}; - -enum addrinfo_errors -{ - /// The service is not supported for the given socket type. - service_not_found = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), - ASIO_GETADDRINFO_ERROR(EAI_SERVICE)), - - /// The socket type is not supported. - socket_type_not_supported = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), - ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)) -}; - -enum misc_errors -{ - /// Already open. - already_open = 1, + static const asio::error_code bad_descriptor; /// End of file or stream. - eof, + static const asio::error_code eof; + + /// Bad address. + static const asio::error_code fault; + + /// Host not found (authoritative). + static const asio::error_code host_not_found; + + /// Host not found (non-authoritative). + static const asio::error_code host_not_found_try_again; + + /// No route to host. + static const asio::error_code host_unreachable; + + /// Operation now in progress. + static const asio::error_code in_progress; + + /// Interrupted system call. + static const asio::error_code interrupted; + + /// Invalid argument. + static const asio::error_code invalid_argument; + + /// Message too long. + static const asio::error_code message_size; + + /// Network is down. + static const asio::error_code network_down; + + /// Network dropped connection on reset. + static const asio::error_code network_reset; + + /// Network is unreachable. + static const asio::error_code network_unreachable; + + /// Too many open files. + static const asio::error_code no_descriptors; + + /// No buffer space available. + static const asio::error_code no_buffer_space; + + /// The query is valid but does not have associated address data. + static const asio::error_code no_data; + + /// Cannot allocate memory. + static const asio::error_code no_memory; + + /// Operation not permitted. + static const asio::error_code no_permission; + + /// Protocol not available. + static const asio::error_code no_protocol_option; + + /// A non-recoverable error occurred. + static const asio::error_code no_recovery; + + /// Transport endpoint is not connected. + static const asio::error_code not_connected; /// Element not found. - not_found + static const asio::error_code not_found; + + /// Socket operation on non-socket. + static const asio::error_code not_socket; + + /// Operation cancelled. + static const asio::error_code operation_aborted; + + /// Operation not supported. + static const asio::error_code operation_not_supported; + + /// The service is not supported for the given socket type. + static const asio::error_code service_not_found; + + /// The socket type is not supported. + static const asio::error_code socket_type_not_supported; + + /// Cannot send after transport endpoint shutdown. + static const asio::error_code shut_down; + + /// Connection timed out. + static const asio::error_code timed_out; + + /// Resource temporarily unavailable. + static const asio::error_code try_again; + + /// The socket is marked non-blocking and the requested operation would block. + static const asio::error_code would_block; + +private: + error_base(); }; // boostify: error category definitions go here. -inline asio::error_code make_error_code(basic_errors e) -{ - return asio::error_code(static_cast(e), system_category); -} +template const asio::error_code +error_base::access_denied = ASIO_SOCKET_ERROR(EACCES); -inline asio::error_code make_error_code(netdb_errors e) -{ - return asio::error_code(static_cast(e), netdb_category); -} +template const asio::error_code +error_base::address_family_not_supported = ASIO_SOCKET_ERROR( + EAFNOSUPPORT); -inline asio::error_code make_error_code(addrinfo_errors e) -{ - return asio::error_code(static_cast(e), addrinfo_category); -} +template const asio::error_code +error_base::address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE); -inline asio::error_code make_error_code(misc_errors e) -{ - return asio::error_code(static_cast(e), misc_category); -} +template const asio::error_code +error_base::already_connected = ASIO_SOCKET_ERROR(EISCONN); + +template const asio::error_code +error_base::already_open = ASIO_MISC_ERROR(1); + +template const asio::error_code +error_base::already_started = ASIO_SOCKET_ERROR(EALREADY); + +template const asio::error_code +error_base::connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED); + +template const asio::error_code +error_base::connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED); + +template const asio::error_code +error_base::connection_reset = ASIO_SOCKET_ERROR(ECONNRESET); + +template const asio::error_code +error_base::bad_descriptor = ASIO_SOCKET_ERROR(EBADF); + +template const asio::error_code +error_base::eof = ASIO_MISC_ERROR(2); + +template const asio::error_code +error_base::fault = ASIO_SOCKET_ERROR(EFAULT); + +template const asio::error_code +error_base::host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND); + +template const asio::error_code +error_base::host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN); + +template const asio::error_code +error_base::host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH); + +template const asio::error_code +error_base::in_progress = ASIO_SOCKET_ERROR(EINPROGRESS); + +template const asio::error_code +error_base::interrupted = ASIO_SOCKET_ERROR(EINTR); + +template const asio::error_code +error_base::invalid_argument = ASIO_SOCKET_ERROR(EINVAL); + +template const asio::error_code +error_base::message_size = ASIO_SOCKET_ERROR(EMSGSIZE); + +template const asio::error_code +error_base::network_down = ASIO_SOCKET_ERROR(ENETDOWN); + +template const asio::error_code +error_base::network_reset = ASIO_SOCKET_ERROR(ENETRESET); + +template const asio::error_code +error_base::network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH); + +template const asio::error_code +error_base::no_descriptors = ASIO_SOCKET_ERROR(EMFILE); + +template const asio::error_code +error_base::no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS); + +template const asio::error_code +error_base::no_data = ASIO_NETDB_ERROR(NO_DATA); + +template const asio::error_code +error_base::no_memory = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), + ASIO_NATIVE_ERROR(ENOMEM)); + +template const asio::error_code +error_base::no_permission = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), + ASIO_NATIVE_ERROR(EPERM)); + +template const asio::error_code +error_base::no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT); + +template const asio::error_code +error_base::no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY); + +template const asio::error_code +error_base::not_connected = ASIO_SOCKET_ERROR(ENOTCONN); + +template const asio::error_code +error_base::not_found = ASIO_MISC_ERROR(3); + +template const asio::error_code +error_base::not_socket = ASIO_SOCKET_ERROR(ENOTSOCK); + +template const asio::error_code +error_base::operation_aborted = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), + ASIO_NATIVE_ERROR(ECANCELED)); + +template const asio::error_code +error_base::operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP); + +template const asio::error_code +error_base::service_not_found = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), + ASIO_GETADDRINFO_ERROR(EAI_SERVICE)); + +template const asio::error_code +error_base::socket_type_not_supported = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), + ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)); + +template const asio::error_code +error_base::shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN); + +template const asio::error_code +error_base::timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT); + +template const asio::error_code +error_base::try_again = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_RETRY), + ASIO_NATIVE_ERROR(EAGAIN)); + +template const asio::error_code +error_base::would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK); + +} // namespace detail + +/// Contains error constants. +class error : public asio::detail::error_base +{ +private: + error(); +}; -} // namespace error } // namespace asio #undef ASIO_NATIVE_ERROR #undef ASIO_SOCKET_ERROR #undef ASIO_NETDB_ERROR #undef ASIO_GETADDRINFO_ERROR +#undef ASIO_MISC_ERROR #undef ASIO_WIN_OR_POSIX #include "asio/impl/error_code.ipp" diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp index 0941a8c00..0614490e2 100644 --- a/libtorrent/include/asio/error_code.hpp +++ b/libtorrent/include/asio/error_code.hpp @@ -32,27 +32,24 @@ namespace asio { -namespace error +/// Available error code categories. +enum error_category { - /// Available error code categories. - enum error_category - { - /// System error codes. - system_category = ASIO_WIN_OR_POSIX(0, 0), + /// Native error codes. + native_ecat = ASIO_WIN_OR_POSIX(0, 0), - /// Error codes from NetDB functions. - netdb_category = ASIO_WIN_OR_POSIX(system_category, 1), + /// Error codes from NetDB functions. + netdb_ecat = ASIO_WIN_OR_POSIX(native_ecat, 1), - /// Error codes from getaddrinfo. - addrinfo_category = ASIO_WIN_OR_POSIX(system_category, 2), + /// Error codes from getaddrinfo. + addrinfo_ecat = ASIO_WIN_OR_POSIX(native_ecat, 2), - /// Miscellaneous error codes. - misc_category = ASIO_WIN_OR_POSIX(3, 3), + /// Miscellaneous error codes. + misc_ecat = ASIO_WIN_OR_POSIX(3, 3), - /// SSL error codes. - ssl_category = ASIO_WIN_OR_POSIX(4, 4) - }; -} // namespace error + /// SSL error codes. + ssl_ecat = ASIO_WIN_OR_POSIX(4, 4) +}; /// Class to represent an error code value. class error_code @@ -64,24 +61,17 @@ public: /// Default constructor. error_code() : value_(0), - category_(error::system_category) + category_(native_ecat) { } /// Construct with specific error code and category. - error_code(value_type v, error::error_category c) + error_code(value_type v, error_category c) : value_(v), category_(c) { } - /// Construct from an error code enum. - template - error_code(ErrorEnum e) - { - *this = make_error_code(e); - } - /// Get the error value. value_type value() const { @@ -89,7 +79,7 @@ public: } /// Get the error category. - error::error_category category() const + error_category category() const { return category_; } @@ -135,7 +125,7 @@ private: value_type value_; // The category associated with the error code. - error::error_category category_; + error_category category_; }; } // namespace asio diff --git a/libtorrent/include/asio/impl/error_code.ipp b/libtorrent/include/asio/impl/error_code.ipp index f66b6fd94..da2f98833 100644 --- a/libtorrent/include/asio/impl/error_code.ipp +++ b/libtorrent/include/asio/impl/error_code.ipp @@ -35,12 +35,10 @@ inline std::string error_code::message() const return "Already open."; if (*this == error::not_found) return "Not found."; - if (category_ == error::ssl_category) + if (category_ == ssl_ecat) return "SSL error."; #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) value_type value = value_; - if (category() != error::system_category && *this != error::eof) - return "asio error"; if (*this == error::eof) value = ERROR_HANDLE_EOF; char* msg = 0; @@ -78,8 +76,6 @@ inline std::string error_code::message() const return "Service not found."; if (*this == error::socket_type_not_supported) return "Socket type not supported."; - if (category() != error::system_category) - return "asio error"; #if defined(__sun) || defined(__QNX__) return strerror(value_); #elif defined(__MACH__) && defined(__APPLE__) \ diff --git a/libtorrent/include/asio/impl/io_service.ipp b/libtorrent/include/asio/impl/io_service.ipp index f51d3697d..e973619d1 100644 --- a/libtorrent/include/asio/impl/io_service.ipp +++ b/libtorrent/include/asio/impl/io_service.ipp @@ -128,11 +128,11 @@ template #if defined(GENERATING_DOCUMENTATION) unspecified #else -inline detail::wrapped_handler +inline detail::wrapped_handler #endif io_service::wrap(Handler handler) { - return detail::wrapped_handler(*this, handler); + return detail::wrapped_handler(*this, handler); } inline io_service::work::work(asio::io_service& io_service) diff --git a/libtorrent/include/asio/impl/read_until.ipp b/libtorrent/include/asio/impl/read_until.ipp index 8b69a11c6..64c15ec7d 100644 --- a/libtorrent/include/asio/impl/read_until.ipp +++ b/libtorrent/include/asio/impl/read_until.ipp @@ -311,8 +311,7 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - asio::error_code ec(error::not_found); - handler_(ec, bytes); + handler_(error::not_found, bytes); return; } @@ -389,8 +388,7 @@ void async_read_until(AsyncReadStream& s, // No match. Check if buffer is full. if (b.size() == b.max_size()) { - asio::error_code ec(error::not_found); - s.io_service().post(detail::bind_handler(handler, ec, 0)); + s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); return; } @@ -471,8 +469,7 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - asio::error_code ec(error::not_found); - handler_(ec, bytes); + handler_(error::not_found, bytes); return; } @@ -562,8 +559,7 @@ void async_read_until(AsyncReadStream& s, // Check if buffer is full. if (b.size() == b.max_size()) { - asio::error_code ec(error::not_found); - s.io_service().post(detail::bind_handler(handler, ec, 0)); + s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); return; } @@ -645,8 +641,7 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - asio::error_code ec(error::not_found); - handler_(ec, bytes); + handler_(error::not_found, bytes); return; } @@ -736,8 +731,7 @@ void async_read_until(AsyncReadStream& s, // Check if buffer is full. if (b.size() == b.max_size()) { - asio::error_code ec(error::not_found); - s.io_service().post(detail::bind_handler(handler, ec, 0)); + s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); return; } diff --git a/libtorrent/include/asio/io_service.hpp b/libtorrent/include/asio/io_service.hpp index 2101e56c4..b694545db 100644 --- a/libtorrent/include/asio/io_service.hpp +++ b/libtorrent/include/asio/io_service.hpp @@ -320,7 +320,7 @@ public: #if defined(GENERATING_DOCUMENTATION) unspecified #else - detail::wrapped_handler + detail::wrapped_handler #endif wrap(Handler handler); diff --git a/libtorrent/include/asio/ip/basic_endpoint.hpp b/libtorrent/include/asio/ip/basic_endpoint.hpp index 3d1316e22..3ca91dc03 100644 --- a/libtorrent/include/asio/ip/basic_endpoint.hpp +++ b/libtorrent/include/asio/ip/basic_endpoint.hpp @@ -172,7 +172,7 @@ public: /// The protocol associated with the endpoint. protocol_type protocol() const { - if (is_v4(data_)) + if (is_v4()) return InternetProtocol::v4(); return InternetProtocol::v6(); } @@ -192,7 +192,7 @@ public: /// Get the underlying size of the endpoint in the native type. size_type size() const { - if (is_v4(data_)) + if (is_v4()) return sizeof(asio::detail::sockaddr_in4_type); else return sizeof(asio::detail::sockaddr_in6_type); @@ -218,7 +218,7 @@ public: /// the host's byte order. unsigned short port() const { - if (is_v4(data_)) + if (is_v4()) { return asio::detail::socket_ops::network_to_host_short( reinterpret_cast( @@ -236,7 +236,7 @@ public: /// the host's byte order. void port(unsigned short port_num) { - if (is_v4(data_)) + if (is_v4()) { reinterpret_cast(data_).sin_port = asio::detail::socket_ops::host_to_network_short(port_num); @@ -252,7 +252,7 @@ public: asio::ip::address address() const { using namespace std; // For memcpy. - if (is_v4(data_)) + if (is_v4()) { const asio::detail::sockaddr_in4_type& data = reinterpret_cast( @@ -306,26 +306,14 @@ public: private: // Helper function to determine whether the endpoint is IPv4. + bool is_v4() const + { #if defined(_AIX) - template struct is_v4_helper {}; - - template - static bool is_v4(const T& ss, is_v4_helper* = 0) - { - return ss.ss_family == AF_INET; - } - - template - static bool is_v4(const T& ss, is_v4_helper* = 0) - { - return ss.__ss_family == AF_INET; - } + return data_.__ss_family == AF_INET; #else - static bool is_v4(const asio::detail::sockaddr_storage_type& ss) - { - return ss.ss_family == AF_INET; - } + return data_.ss_family == AF_INET; #endif + } // The underlying IP socket address. asio::detail::sockaddr_storage_type data_; diff --git a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp index 5fd3ebba4..b7a564464 100755 --- a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp @@ -174,12 +174,12 @@ public: if (error_code == SSL_ERROR_SYSCALL) { return handler_(asio::error_code( - sys_error_code, asio::error::system_category), rc); + sys_error_code, asio::native_ecat), rc); } else { return handler_(asio::error_code( - error_code, asio::error::ssl_category), rc); + error_code, asio::ssl_ecat), rc); } } diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp index 954e39ef5..b6b6711dc 100755 --- a/libtorrent/include/libtorrent/alert.hpp +++ b/libtorrent/include/libtorrent/alert.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #ifdef _MSC_VER @@ -55,7 +56,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/time.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" #ifndef TORRENT_MAX_ALERT_TYPES #define TORRENT_MAX_ALERT_TYPES 15 diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 36c13c5ab..48491bca4 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -38,7 +38,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_connection.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -224,7 +223,7 @@ namespace libtorrent { block_downloading_alert( const torrent_handle& h - , char const* speedmsg + , std::string& speedmsg , int block_num , int piece_num , const std::string& msg) @@ -262,17 +261,6 @@ namespace libtorrent { return std::auto_ptr(new torrent_paused_alert(*this)); } }; - struct TORRENT_EXPORT torrent_checked_alert: torrent_alert - { - torrent_checked_alert(torrent_handle const& h, std::string const& msg) - : torrent_alert(h, alert::info, msg) - {} - - virtual std::auto_ptr clone() const - { return std::auto_ptr(new torrent_checked_alert(*this)); } - }; - - struct TORRENT_EXPORT url_seed_alert: torrent_alert { url_seed_alert( diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp index 6577acc46..e69de29bb 100644 --- a/libtorrent/include/libtorrent/assert.hpp +++ b/libtorrent/include/libtorrent/assert.hpp @@ -1,54 +0,0 @@ -/* - -Copyright (c) 2007, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#include - -#ifndef NDEBUG -#if (defined __linux__ || defined __MACH__) && defined __GNUC__ -#ifdef assert -#undef assert -#endif - -#include "libtorrent/config.hpp" - -TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file, char const* function); - -#define assert(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) - -#endif - -#else -#ifndef assert -#define assert(x) (void) -#endif -#endif - diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 9300a1ce3..207016898 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -83,7 +83,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type.hpp" #include "libtorrent/connection_queue.hpp" #include "libtorrent/disk_io_thread.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -185,7 +184,7 @@ namespace libtorrent ~session_impl(); #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*, void*)> ext); + void add_extension(boost::function(torrent*)> ext); #endif void operator()(); @@ -241,13 +240,12 @@ namespace libtorrent bool is_listening() const; torrent_handle add_torrent( - boost::intrusive_ptr ti + torrent_info const& ti , fs::path const& save_path , entry const& resume_data , bool compact_mode - , storage_constructor_type sc - , bool paused - , void* userdata); + , int block_size + , storage_constructor_type sc); torrent_handle add_torrent( char const* tracker_url @@ -256,9 +254,8 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data , bool compact_mode - , storage_constructor_type sc - , bool paused - , void* userdata); + , int block_size + , storage_constructor_type sc); void remove_torrent(torrent_handle const& h); @@ -276,21 +273,8 @@ namespace libtorrent void set_max_connections(int limit); void set_max_uploads(int limit); - int max_connections() const { return m_max_connections; } - int max_uploads() const { return m_max_uploads; } - int max_half_open_connections() const { return m_half_open.limit(); } - - int num_uploads() const { return m_num_unchoked; } - int num_connections() const - { return m_connections.size(); } - - void unchoke_peer(peer_connection& c) - { - torrent* t = c.associated_torrent().lock().get(); - assert(t); - if (t->unchoke_peer(c)) - ++m_num_unchoked; - } + int num_uploads() const; + int num_connections() const; session_status status() const; void set_peer_id(peer_id const& id); @@ -433,28 +417,6 @@ namespace libtorrent int m_max_uploads; int m_max_connections; - // the number of unchoked peers - int m_num_unchoked; - - // this is initialized to the unchoke_interval - // session_setting and decreased every second. - // when it reaches zero, it is reset to the - // unchoke_interval and the unchoke set is - // recomputed. - int m_unchoke_time_scaler; - - // works like unchoke_time_scaler but it - // is only decresed when the unchoke set - // is recomputed, and when it reaches zero, - // the optimistic unchoke is moved to another peer. - int m_optimistic_unchoke_time_scaler; - - // works like unchoke_time_scaler. Each time - // it reaches 0, and all the connections are - // used, the worst connection will be disconnected - // from the torrent with the most peers - int m_disconnect_time_scaler; - // statistics gathered from all torrents. stat m_stat; @@ -497,7 +459,7 @@ namespace libtorrent // This implements a round robin. int m_next_connect_torrent; #ifndef NDEBUG - void check_invariant() const; + void check_invariant(const char *place = 0); #endif #ifdef TORRENT_STATS @@ -521,7 +483,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list(torrent*, void*)> > extension_list_t; + torrent_plugin>(torrent*)> > extension_list_t; extension_list_t m_extensions; #endif diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 38aa67f43..75e1f1d4e 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -33,6 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED #define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED +#include "libtorrent/socket.hpp" +#include "libtorrent/invariant_check.hpp" + #include #include #include @@ -41,17 +44,11 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include "libtorrent/socket.hpp" -#include "libtorrent/invariant_check.hpp" -#include "libtorrent/assert.hpp" - using boost::weak_ptr; using boost::shared_ptr; using boost::intrusive_ptr; using boost::bind; -//#define TORRENT_VERBOSE_BANDWIDTH_LIMIT - namespace libtorrent { // the maximum block of bandwidth quota to @@ -180,7 +177,6 @@ struct bandwidth_manager , m_limit(bandwidth_limit::inf) , m_current_quota(0) , m_channel(channel) - , m_in_hand_out_bandwidth(false) {} void throttle(int limit) throw() @@ -241,10 +237,8 @@ struct bandwidth_manager i = j; } } -#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " req_bandwidht. m_queue.size() = " << m_queue.size() << std::endl; -#endif - if (!m_queue.empty()) hand_out_bandwidth(); + + if (m_queue.size() == 1) hand_out_bandwidth(); } #ifndef NDEBUG @@ -329,10 +323,6 @@ private: void hand_out_bandwidth() throw() { - // if we're already handing out bandwidth, just return back - // to the loop further down on the callstack - if (m_in_hand_out_bandwidth) return; - m_in_hand_out_bandwidth = true; #ifndef NDEBUG try { #endif @@ -347,18 +337,10 @@ private: // available bandwidth to hand out int amount = limit - m_current_quota; -#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " hand_out_bandwidht. m_queue.size() = " << m_queue.size() - << " amount = " << amount - << " limit = " << limit - << " m_current_quota = " << m_current_quota << std::endl; -#endif - while (!m_queue.empty() && amount > 0) { assert(amount == limit - m_current_quota); bw_queue_entry qe = m_queue.front(); - assert(qe.max_block_size > 0); m_queue.pop_front(); shared_ptr t = qe.peer->associated_torrent().lock(); @@ -366,7 +348,6 @@ private: if (qe.peer->is_disconnecting()) { t->expire_bandwidth(m_channel, qe.max_block_size); - assert(amount == limit - m_current_quota); continue; } @@ -380,7 +361,6 @@ private: if (max_assignable == 0) { t->expire_bandwidth(m_channel, qe.max_block_size); - assert(amount == limit - m_current_quota); continue; } @@ -394,16 +374,17 @@ private: // block size must be smaller for lower rates. This is because // the history window is one second, and the block will be forgotten // after one second. - int block_size = (std::min)(qe.peer->bandwidth_throttle(m_channel) - , limit / 10); + int block_size = (std::min)(qe.max_block_size + , (std::min)(qe.peer->bandwidth_throttle(m_channel) + , m_limit / 10)); if (block_size < min_bandwidth_block_size) { - block_size = (std::min)(int(min_bandwidth_block_size), limit); + block_size = min_bandwidth_block_size; } else if (block_size > max_bandwidth_block_size) { - if (limit == bandwidth_limit::inf) + if (m_limit == bandwidth_limit::inf) { block_size = max_bandwidth_block_size; } @@ -414,15 +395,11 @@ private: // as possible // TODO: move this calculcation to where the limit // is changed - block_size = limit - / (limit / max_bandwidth_block_size); + block_size = m_limit + / (m_limit / max_bandwidth_block_size); } } - if (block_size > qe.max_block_size) block_size = qe.max_block_size; -#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; -#endif if (amount < block_size / 2) { m_queue.push_front(qe); @@ -435,21 +412,18 @@ private: int hand_out_amount = (std::min)((std::min)(block_size, max_assignable) , amount); assert(hand_out_amount > 0); - assert(amount == limit - m_current_quota); amount -= hand_out_amount; assert(hand_out_amount <= qe.max_block_size); t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); qe.peer->assign_bandwidth(m_channel, hand_out_amount); add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); - assert(amount == limit - m_current_quota); } #ifndef NDEBUG } catch (std::exception& e) { assert(false); }; #endif - m_in_hand_out_bandwidth = false; } @@ -482,11 +456,6 @@ private: // this is the channel within the consumers // that bandwidth is assigned to (upload or download) int m_channel; - - // this is true while we're in the hand_out_bandwidth loop - // to prevent recursive invocations to interfere - bool m_in_hand_out_bandwidth; - }; } diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index 9e670c10b..a142b5864 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -79,8 +79,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/entry.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" - #if defined(_MSC_VER) namespace std { diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index 23be67b0d..e69de29bb 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -1,84 +0,0 @@ -/* - -Copyright (c) 2007, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#ifndef TORRENT_BROADCAST_SOCKET_HPP_INCLUDED -#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED - -#include "libtorrent/socket.hpp" -#include -#include -#include - -namespace libtorrent -{ - - bool is_local(address const& a); - bool is_loopback(address const& addr); - bool is_multicast(address const& addr); - - address_v4 guess_local_address(asio::io_service&); - - typedef boost::function receive_handler_t; - - class broadcast_socket - { - public: - broadcast_socket(asio::io_service& ios, udp::endpoint const& multicast_endpoint - , receive_handler_t const& handler, bool loopback = true); - ~broadcast_socket() { close(); } - - void send(char const* buffer, int size, asio::error_code& ec); - void close(); - - private: - - struct socket_entry - { - socket_entry(boost::shared_ptr const& s): socket(s) {} - boost::shared_ptr socket; - char buffer[1024]; - udp::endpoint remote; - }; - - void on_receive(socket_entry* s, asio::error_code const& ec - , std::size_t bytes_transferred); - - std::list m_sockets; - udp::endpoint m_multicast_endpoint; - receive_handler_t m_on_receive; - - }; -} - -#endif - diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index 0fcba89a8..beec94979 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -65,6 +65,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -121,16 +122,8 @@ namespace libtorrent msg_request, msg_piece, msg_cancel, - // DHT extension msg_dht_port, - // FAST extension - msg_suggest_piece = 0xd, - msg_have_all, - msg_have_none, - msg_reject_request, - msg_allowed_fast, - - // extension protocol message + // extension protocol message msg_extended = 20, num_supported_messages @@ -181,17 +174,8 @@ namespace libtorrent void on_request(int received); void on_piece(int received); void on_cancel(int received); - - // DHT extension void on_dht_port(int received); - // FAST extension - void on_suggest_piece(int received); - void on_have_all(int received); - void on_have_none(int received); - void on_reject_request(int received); - void on_allowed_fast(int received); - void on_extended(int received); void on_extended_handshake(); @@ -217,16 +201,7 @@ namespace libtorrent void write_metadata(std::pair req); void write_metadata_request(std::pair req); void write_keepalive(); - - // DHT extension void write_dht_port(int listen_port); - - // FAST extension - void write_have_all(); - void write_have_none(); - void write_reject_request(peer_request const&); - void write_allow_fast(int piece); - void on_connected(); void on_metadata(); @@ -350,7 +325,6 @@ namespace libtorrent bool m_supports_extensions; #endif bool m_supports_dht_port; - bool m_supports_fast; #ifndef TORRENT_DISABLE_ENCRYPTION // this is set to true after the encryption method has been diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index 0f37edcbd..0cb44225a 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -34,9 +34,8 @@ POSSIBILITY OF SUCH DAMAGE. //#define TORRENT_BUFFER_DEBUG -#include #include "libtorrent/invariant_check.hpp" -#include "libtorrent/assert.hpp" +#include namespace libtorrent { diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp index b3b7cde86..17be248bf 100644 --- a/libtorrent/include/libtorrent/connection_queue.hpp +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include "libtorrent/socket.hpp" #include "libtorrent/time.hpp" @@ -89,10 +88,6 @@ private: int m_half_open_limit; deadline_timer m_timer; - - typedef boost::recursive_mutex mutex_t; - mutable mutex_t m_mutex; - #ifndef NDEBUG bool m_in_timeout_function; #endif diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp index 1bb645a8e..436b695f6 100755 --- a/libtorrent/include/libtorrent/debug.hpp +++ b/libtorrent/include/libtorrent/debug.hpp @@ -80,4 +80,3 @@ namespace libtorrent } #endif // TORRENT_DEBUG_HPP_INCLUDED - diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index 61ca9bc53..16ee0bca4 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -30,10 +30,6 @@ POSSIBILITY OF SUCH DAMAGE. */ -#ifdef TORRENT_DISK_STATS -#include -#endif - #include "libtorrent/storage.hpp" #include #include @@ -54,7 +50,6 @@ namespace libtorrent , buffer_size(0) , piece(0) , offset(0) - , priority(0) {} enum action_t @@ -77,12 +72,6 @@ namespace libtorrent // to the error message std::string str; - // priority decides whether or not this - // job will skip entries in the queue or - // not. It always skips in front of entries - // with lower priority - int priority; - // this is called when operation completes boost::function callback; }; @@ -126,10 +115,6 @@ namespace libtorrent int m_block_size; #endif -#ifdef TORRENT_DISK_STATS - std::ofstream m_log; -#endif - // thread for performing blocking disk io operations boost::thread m_disk_io_thread; }; diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp index 7fd6c8c53..59e29803d 100755 --- a/libtorrent/include/libtorrent/entry.hpp +++ b/libtorrent/include/libtorrent/entry.hpp @@ -64,10 +64,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "libtorrent/size_type.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp index 0c6063a2b..e69de29bb 100644 --- a/libtorrent/include/libtorrent/enum_net.hpp +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -1,44 +0,0 @@ -/* - -Copyright (c) 2007, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#ifndef TORRENT_ENUM_NET_HPP_INCLUDED -#define TORRENT_ENUM_NET_HPP_INCLUDED - -#include "libtorrent/socket.hpp" - -namespace libtorrent -{ - std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); -} - -#endif - diff --git a/libtorrent/include/libtorrent/extensions.hpp b/libtorrent/include/libtorrent/extensions.hpp index fd48588e1..5f8172649 100644 --- a/libtorrent/include/libtorrent/extensions.hpp +++ b/libtorrent/include/libtorrent/extensions.hpp @@ -131,15 +131,6 @@ namespace libtorrent virtual bool on_bitfield(std::vector const& bitfield) { return false; } - virtual bool on_have_all() - { return false; } - - virtual bool on_have_none() - { return false; } - - virtual bool on_allowed_fast(int index) - { return false; } - virtual bool on_request(peer_request const& req) { return false; } @@ -149,12 +140,6 @@ namespace libtorrent virtual bool on_cancel(peer_request const& req) { return false; } - virtual bool on_reject(peer_request const& req) - { return false; } - - virtual bool on_suggest(int index) - { return false; } - // called when an extended message is received. If returning true, // the message is not processed by any other plugin and if false // is returned the next plugin in the chain will receive it to diff --git a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp index c42136d70..210642161 100644 --- a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp +++ b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp @@ -48,7 +48,7 @@ namespace libtorrent { struct torrent_plugin; class torrent; - TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*, void*); + TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*); } #endif // TORRENT_METADATA_TRANSFER_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/extensions/ut_pex.hpp b/libtorrent/include/libtorrent/extensions/ut_pex.hpp index ebf6aa834..efd9ab4f6 100644 --- a/libtorrent/include/libtorrent/extensions/ut_pex.hpp +++ b/libtorrent/include/libtorrent/extensions/ut_pex.hpp @@ -48,7 +48,7 @@ namespace libtorrent { struct torrent_plugin; class torrent; - TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*, void*); + TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*); } #endif // TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/fingerprint.hpp b/libtorrent/include/libtorrent/fingerprint.hpp index 712be6979..d7e5a5fc6 100755 --- a/libtorrent/include/libtorrent/fingerprint.hpp +++ b/libtorrent/include/libtorrent/fingerprint.hpp @@ -37,7 +37,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/peer_id.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -92,4 +91,3 @@ namespace libtorrent } #endif // TORRENT_FINGERPRINT_HPP_INCLUDED - diff --git a/libtorrent/include/libtorrent/hasher.hpp b/libtorrent/include/libtorrent/hasher.hpp index 71b7f9ede..932f2b100 100755 --- a/libtorrent/include/libtorrent/hasher.hpp +++ b/libtorrent/include/libtorrent/hasher.hpp @@ -33,11 +33,11 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_HASHER_HPP_INCLUDED #define TORRENT_HASHER_HPP_INCLUDED +#include #include #include "libtorrent/peer_id.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" #include "zlib.h" #ifdef TORRENT_USE_OPENSSL diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index ccc145413..409213857 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -44,18 +44,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/http_tracker_connection.hpp" #include "libtorrent/time.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { -struct http_connection; - typedef boost::function http_handler; -typedef boost::function http_connect_handler; - // TODO: add bind interface // when bottled, the last two arguments to the handler @@ -63,13 +58,11 @@ typedef boost::function http_connect_handler; struct http_connection : boost::enable_shared_from_this, boost::noncopyable { http_connection(asio::io_service& ios, connection_queue& cc - , http_handler const& handler, bool bottled = true - , http_connect_handler const& ch = http_connect_handler()) + , http_handler handler, bool bottled = true) : m_sock(ios) , m_read_pos(0) , m_resolver(ios) , m_handler(handler) - , m_connect_handler(ch) , m_timer(ios) , m_last_receive(time_now()) , m_bottled(bottled) @@ -99,8 +92,6 @@ struct http_connection : boost::enable_shared_from_this, boost: , time_duration timeout, bool handle_redirect = true); void close(); - tcp::socket const& socket() const { return m_sock; } - private: void on_resolve(asio::error_code const& e @@ -121,7 +112,6 @@ private: tcp::resolver m_resolver; http_parser m_parser; http_handler m_handler; - http_connect_handler m_connect_handler; deadline_timer m_timer; time_duration m_timeout; ptime m_last_receive; diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index 76c3aac98..35d529504 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -73,8 +73,6 @@ namespace libtorrent T header(char const* key) const; std::string const& protocol() const { return m_protocol; } int status_code() const { return m_status_code; } - std::string const& method() const { return m_method; } - std::string const& path() const { return m_path; } std::string message() const { return m_server_message; } buffer::const_interval get_body() const; bool header_finished() const { return m_state == read_body; } @@ -87,8 +85,6 @@ namespace libtorrent private: int m_recv_pos; int m_status_code; - std::string m_method; - std::string m_path; std::string m_protocol; std::string m_server_message; diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index d2c35ffe3..a432bc350 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -34,17 +34,14 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_INTRUSIVE_PTR_BASE #include +#include #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { template struct intrusive_ptr_base { - intrusive_ptr_base(intrusive_ptr_base const&) - : m_refs(0) {} - friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) { assert(s->m_refs >= 0); diff --git a/libtorrent/include/libtorrent/invariant_check.hpp b/libtorrent/include/libtorrent/invariant_check.hpp index 3eaacf34c..c6eacf338 100755 --- a/libtorrent/include/libtorrent/invariant_check.hpp +++ b/libtorrent/include/libtorrent/invariant_check.hpp @@ -5,7 +5,7 @@ #ifndef TORRENT_INVARIANT_ACCESS_HPP_INCLUDED #define TORRENT_INVARIANT_ACCESS_HPP_INCLUDED -#include "libtorrent/assert.hpp" +#include namespace libtorrent { diff --git a/libtorrent/include/libtorrent/ip_filter.hpp b/libtorrent/include/libtorrent/ip_filter.hpp index 7b8cc0e17..8b1793c3a 100644 --- a/libtorrent/include/libtorrent/ip_filter.hpp +++ b/libtorrent/include/libtorrent/ip_filter.hpp @@ -33,9 +33,6 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_IP_FILTER_HPP #define TORRENT_IP_FILTER_HPP -#include -#include - #ifdef _MSC_VER #pragma warning(push, 1) #endif @@ -51,7 +48,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/socket.hpp" -#include "libtorrent/assert.hpp" +#include +#include namespace libtorrent { diff --git a/libtorrent/include/libtorrent/kademlia/node.hpp b/libtorrent/include/libtorrent/kademlia/node.hpp index ee75e7f0a..850333043 100644 --- a/libtorrent/include/libtorrent/kademlia/node.hpp +++ b/libtorrent/include/libtorrent/kademlia/node.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define NODE_HPP #include +#include #include #include @@ -44,7 +45,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include diff --git a/libtorrent/include/libtorrent/kademlia/node_id.hpp b/libtorrent/include/libtorrent/kademlia/node_id.hpp index 5e732acac..eb4d6c539 100644 --- a/libtorrent/include/libtorrent/kademlia/node_id.hpp +++ b/libtorrent/include/libtorrent/kademlia/node_id.hpp @@ -33,10 +33,10 @@ POSSIBILITY OF SUCH DAMAGE. #define NODE_ID_HPP #include +#include #include #include "libtorrent/peer_id.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { namespace dht { diff --git a/libtorrent/include/libtorrent/kademlia/routing_table.hpp b/libtorrent/include/libtorrent/kademlia/routing_table.hpp index 9e10a3483..45a7dd762 100644 --- a/libtorrent/include/libtorrent/kademlia/routing_table.hpp +++ b/libtorrent/include/libtorrent/kademlia/routing_table.hpp @@ -50,7 +50,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include namespace libtorrent { namespace dht { diff --git a/libtorrent/include/libtorrent/lsd.hpp b/libtorrent/include/libtorrent/lsd.hpp index e8eaf0df1..9ffbcdfc3 100644 --- a/libtorrent/include/libtorrent/lsd.hpp +++ b/libtorrent/include/libtorrent/lsd.hpp @@ -35,7 +35,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_id.hpp" -#include "libtorrent/broadcast_socket.hpp" #include #include @@ -59,26 +58,35 @@ public: , peer_callback_t const& cb); ~lsd(); -// void rebind(address const& listen_interface); + void rebind(address const& listen_interface); void announce(sha1_hash const& ih, int listen_port); void close(); private: + static address_v4 lsd_multicast_address; + static udp::endpoint lsd_multicast_endpoint; + void resend_announce(asio::error_code const& e, std::string msg); - void on_announce(udp::endpoint const& from, char* buffer + void on_announce(asio::error_code const& e , std::size_t bytes_transferred); -// void setup_receive(); + void setup_receive(); peer_callback_t m_callback; // current retry count int m_retry_count; + // used to receive responses in + char m_receive_buffer[1024]; + + // the endpoint we received the message from + udp::endpoint m_remote; + // the udp socket used to send and receive // multicast messages on - broadcast_socket m_socket; + datagram_socket m_socket; // used to resend udp packets in case // they time out diff --git a/libtorrent/include/libtorrent/pe_crypto.hpp b/libtorrent/include/libtorrent/pe_crypto.hpp index e2276dee6..91616c42d 100644 --- a/libtorrent/include/libtorrent/pe_crypto.hpp +++ b/libtorrent/include/libtorrent/pe_crypto.hpp @@ -35,12 +35,13 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED #define TORRENT_PE_CRYPTO_HPP_INCLUDED +#include + #include #include #include -#include "libtorrent/peer_id.hpp" // For sha1_hash -#include "libtorrent/assert.hpp" +#include "peer_id.hpp" // For sha1_hash namespace libtorrent { diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index ea16a8d0a..31bcde94a 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -64,6 +64,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -72,7 +73,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" #include "libtorrent/intrusive_ptr_base.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -131,8 +131,6 @@ namespace libtorrent enum peer_speed_t { slow, medium, fast }; peer_speed_t peer_speed(); - void send_allowed_set(); - #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::shared_ptr); #endif @@ -153,17 +151,11 @@ namespace libtorrent int upload_limit() const { return m_upload_limit; } int download_limit() const { return m_download_limit; } - int prefer_whole_pieces() const - { - if (on_parole()) return 1; - return m_prefer_whole_pieces; - } + bool prefer_whole_pieces() const + { return m_prefer_whole_pieces; } - bool on_parole() const - { return peer_info_struct() && peer_info_struct()->on_parole; } - - void prefer_whole_pieces(int num) - { m_prefer_whole_pieces = num; } + void prefer_whole_pieces(bool b) + { m_prefer_whole_pieces = b; } bool request_large_blocks() const { return m_request_large_blocks; } @@ -194,9 +186,9 @@ namespace libtorrent void set_pid(const peer_id& pid) { m_peer_id = pid; } bool has_piece(int i) const; - std::deque const& download_queue() const; - std::deque const& request_queue() const; - std::deque const& upload_queue() const; + const std::deque& download_queue() const; + const std::deque& request_queue() const; + const std::deque& upload_queue() const; bool is_interesting() const { return m_interesting; } bool is_choked() const { return m_choked; } @@ -219,14 +211,12 @@ namespace libtorrent void add_stat(size_type downloaded, size_type uploaded); // is called once every second by the main loop - void second_tick(float tick_interval) throw(); + void second_tick(float tick_interval); boost::shared_ptr get_socket() const { return m_socket; } tcp::endpoint const& remote() const { return m_remote; } std::vector const& get_bitfield() const; - std::vector const& allowed_fast(); - std::vector const& suggested_pieces() const { return m_suggested_pieces; } void timed_out(); // this will cause this peer_connection to be disconnected. @@ -304,14 +294,7 @@ namespace libtorrent void incoming_piece(peer_request const& p, char const* data); void incoming_piece_fragment(); void incoming_cancel(peer_request const& r); - void incoming_dht_port(int listen_port); - - void incoming_reject_request(peer_request const& r); - void incoming_have_all(); - void incoming_have_none(); - void incoming_allowed_fast(int index); - void incoming_suggest(int index); // the following functions appends messages // to the send buffer @@ -390,9 +373,6 @@ namespace libtorrent virtual void write_keepalive() = 0; virtual void write_piece(peer_request const& r, char const* buffer) = 0; - virtual void write_reject_request(peer_request const& r) = 0; - virtual void write_allow_fast(int piece) = 0; - virtual void on_connected() = 0; virtual void on_tick() {} @@ -502,11 +482,6 @@ namespace libtorrent // the time we sent a request to // this peer the last time ptime m_last_request; - // the time we received the last - // piece request from the peer - ptime m_last_incoming_request; - // the time when we unchoked this peer - ptime m_last_unchoke; int m_packet_size; int m_recv_pos; @@ -554,7 +529,7 @@ namespace libtorrent // set to the torrent it belongs to. boost::weak_ptr m_torrent; // is true if it was we that connected to the peer - // and false if we got an incoming connection + // and false if we got an incomming connection // could be considered: true = local, false = remote bool m_active; @@ -588,10 +563,6 @@ namespace libtorrent // the pieces the other end have std::vector m_have_piece; - // this is set to true when a have_all - // message is received. This information - // is used to fill the bitmask in init() - bool m_have_all; // the number of pieces this peer // has. Must be the same as @@ -604,7 +575,7 @@ namespace libtorrent std::deque m_requests; // the blocks we have reserved in the piece - // picker and will request from this peer. + // picker and will send to this peer. std::deque m_request_queue; // the queue of blocks we have requested @@ -672,13 +643,12 @@ namespace libtorrent bool m_writing; bool m_reading; - // if set to non-zero, this peer will always prefer - // to request entire n pieces, rather than blocks. - // where n is the value of this variable. - // if it is 0, the download rate limit setting + // if set to true, this peer will always prefer + // to request entire pieces, rather than blocks. + // if it is false, the download rate limit setting // will be used to determine if whole pieces // are preferred. - int m_prefer_whole_pieces; + bool m_prefer_whole_pieces; // if this is true, the blocks picked by the piece // picker will be merged before passed to the @@ -725,18 +695,6 @@ namespace libtorrent // was last updated ptime m_remote_dl_update; - // the pieces we will send to the peer - // if requested (regardless of choke state) - std::set m_accept_fast; - - // the pieces the peer will send us if - // requested (regardless of choke state) - std::vector m_allowed_fast; - - // pieces that has been suggested to be - // downloaded from this peer - std::vector m_suggested_pieces; - // the number of bytes send to the disk-io // thread that hasn't yet been completely written. int m_outstanding_writing_bytes; diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp index 57303e2fd..b66c1d4bc 100755 --- a/libtorrent/include/libtorrent/peer_id.hpp +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include #include #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index b07acffd4..15ad34a7a 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -56,11 +56,10 @@ namespace libtorrent connecting = 0x80, queued = 0x100, on_parole = 0x200, - seed = 0x400, - optimistic_unchoke = 0x800 + seed = 0x400 #ifndef TORRENT_DISABLE_ENCRYPTION - , rc4_encrypted = 0x100000, - plaintext_encrypted = 0x200000 + , rc4_encrypted = 0x800, + plaintext_encrypted = 0x1000 #endif }; @@ -72,8 +71,7 @@ namespace libtorrent dht = 0x2, pex = 0x4, lsd = 0x8, - resume_data = 0x10, - incoming = 0x20 + resume_data = 0x10 }; int source; @@ -118,11 +116,6 @@ namespace libtorrent // for yet int download_queue_length; - // the number of requests that is - // tried to be maintained (this is - // typically a function of download speed) - int target_dl_queue_length; - // this is the number of requests // the peer has sent to us // that we haven't sent yet diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index 64f6203d5..54df003ef 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #ifdef _MSC_VER @@ -51,7 +52,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -191,33 +191,11 @@ namespace libtorrent // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! // The last argument is the policy::peer pointer for the peer that // we'll download from. - void pick_pieces(std::vector const& pieces + void pick_pieces(const std::vector& pieces , std::vector& interesting_blocks - , int num_pieces, int prefer_whole_pieces + , int num_pieces, bool prefer_whole_pieces , void* peer, piece_state_t speed - , bool rarest_first, bool on_parole - , std::vector const& suggested_pieces) const; - - // picks blocks from each of the pieces in the piece_list - // vector that is also in the piece bitmask. The blocks - // are added to interesting_blocks, and busy blocks are - // added to backup_blocks. num blocks is the number of - // blocks to be picked. Blocks are not picked from pieces - // that are being downloaded - int add_blocks(std::vector const& piece_list - , const std::vector& pieces - , std::vector& interesting_blocks - , int num_blocks, int prefer_whole_pieces - , void* peer, std::vector const& ignore) const; - - // picks blocks only from downloading pieces - int add_blocks_downloading( - std::vector const& pieces - , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, int prefer_whole_pieces - , void* peer, piece_state_t speed - , bool on_parole) const; + , bool rarest_first) const; // clears the peer pointer in all downloading pieces with this // peer pointer @@ -233,7 +211,7 @@ namespace libtorrent bool is_finished(piece_block block) const; // marks this piece-block as queued for downloading - bool mark_as_downloading(piece_block block, void* peer + void mark_as_downloading(piece_block block, void* peer , piece_state_t s); void mark_as_writing(piece_block block, void* peer); void mark_as_finished(piece_block block, void* peer); @@ -275,8 +253,6 @@ namespace libtorrent #ifndef NDEBUG // used in debug mode void check_invariant(const torrent* t = 0) const; - void verify_pick(std::vector const& picked - , std::vector const& bitfield) const; #endif // functor that compares indices on downloading_pieces @@ -295,10 +271,6 @@ namespace libtorrent private: - bool can_pick(int piece, std::vector const& bitmask) const; - std::pair expand_piece(int piece, int whole_pieces - , std::vector const& have) const; - struct piece_pos { piece_pos() {} @@ -348,9 +320,9 @@ namespace libtorrent int priority(int limit) const { - if (downloading || filtered() || have()) return 0; + if (filtered() || have()) return 0; // pieces we are currently downloading have high priority - int prio = peer_count * 2; + int prio = downloading ? (std::min)(1, int(peer_count)) : peer_count * 2; // if the peer_count is 0 or 1, the priority cannot be higher if (prio <= 1) return prio; if (prio >= limit * 2) prio = limit * 2; @@ -386,6 +358,14 @@ namespace libtorrent void move(int vec_index, int elem_index); void sort_piece(std::vector::iterator dp); + int add_interesting_blocks(const std::vector& piece_list + , const std::vector& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, bool prefer_whole_pieces + , void* peer, piece_state_t speed + , bool ignore_downloading_pieces) const; + downloading_piece& add_download_piece(); void erase_download_piece(std::vector::iterator i); diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 7a789ec8c..6c976d047 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -89,7 +89,7 @@ namespace libtorrent void new_connection(peer_connection& c); // the given connection was just closed - void connection_closed(const peer_connection& c) throw(); + void connection_closed(const peer_connection& c); // the peer has got at least one interesting piece void peer_is_interesting(peer_connection& c); @@ -155,13 +155,6 @@ namespace libtorrent // this is true if the peer is a seed bool seed; - // true if this peer currently is unchoked - // because of an optimistic unchoke. - // when the optimistic unchoke is moved to - // another peer, this peer will be choked - // if this is true - bool optimistically_unchoked; - // the time when this peer was optimistically unchoked // the last time. libtorrent::ptime last_optimistically_unchoked; @@ -210,18 +203,25 @@ namespace libtorrent peer_connection* connection; }; - int num_peers() const { return m_peers.size(); } + int num_peers() const + { + return m_peers.size(); + } + int num_uploads() const + { + return m_num_unchoked; + } + typedef std::list::iterator iterator; typedef std::list::const_iterator const_iterator; iterator begin_peer() { return m_peers.begin(); } iterator end_peer() { return m_peers.end(); } bool connect_one_peer(); - bool disconnect_one_peer(); private: -/* + bool unchoke_one_peer(); void choke_one_peer(); iterator find_choke_candidate(); @@ -233,7 +233,8 @@ namespace libtorrent void seed_choke_one_peer(); iterator find_seed_choke_candidate(); iterator find_seed_unchoke_candidate(); -*/ + + bool disconnect_one_peer(); iterator find_disconnect_candidate(); iterator find_connect_candidate(); @@ -241,6 +242,10 @@ namespace libtorrent torrent* m_torrent; + // the number of unchoked peers + // at any given time + int m_num_unchoked; + // free download we have got that hasn't // been distributed yet. size_type m_available_free_upload; @@ -248,7 +253,7 @@ namespace libtorrent // if there is a connection limit, // we disconnect one peer every minute in hope of // establishing a connection with a better peer -// ptime m_last_optimistic_disconnect; + ptime m_last_optimistic_disconnect; }; } diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 3a9eb563b..38206f32c 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -53,7 +53,6 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif -#include "libtorrent/config.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/alert.hpp" @@ -61,6 +60,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/fingerprint.hpp" +#include "libtorrent/resource_request.hpp" #include "libtorrent/storage.hpp" #ifdef _MSC_VER @@ -141,17 +141,22 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , bool paused = false - , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED; + , int block_size = 16 * 1024 + , storage_constructor_type sc = default_storage_constructor); + // ==== deprecated, this is for backwards compatibility only + // instead, use one of the other add_torrent overloads torrent_handle add_torrent( - boost::intrusive_ptr ti + entry const& e , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , bool paused = false - , storage_constructor_type sc = default_storage_constructor - , void* userdata = 0); + , int block_size = 16 * 1024 + , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED + { + return add_torrent(torrent_info(e), save_path, resume_data + , compact_mode, block_size, sc); + } torrent_handle add_torrent( char const* tracker_url @@ -160,9 +165,8 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , bool paused = false - , storage_constructor_type sc = default_storage_constructor - , void* userdata = 0); + , int block_size = 16 * 1024 + , storage_constructor_type sc = default_storage_constructor); session_proxy abort() { return session_proxy(m_impl); } @@ -183,7 +187,7 @@ namespace libtorrent #endif #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*, void*)> ext); + void add_extension(boost::function(torrent*)> ext); #endif void set_ip_filter(ip_filter const& f); @@ -239,7 +243,6 @@ namespace libtorrent int upload_rate_limit() const; int download_rate_limit() const; - int max_half_open_connections() const; void set_upload_rate_limit(int bytes_per_second); void set_download_rate_limit(int bytes_per_second); @@ -262,6 +265,12 @@ namespace libtorrent void stop_natpmp(); void stop_upnp(); + // Resource management used for global limits. + resource_request m_ul_bandwidth_quota; + resource_request m_dl_bandwidth_quota; + resource_request m_uploads_quota; + resource_request m_connections_quota; + private: // just a way to initialize boost.filesystem diff --git a/libtorrent/include/libtorrent/session_impl.hpp b/libtorrent/include/libtorrent/session_impl.hpp index 67c3fef1d..e69de29bb 100644 --- a/libtorrent/include/libtorrent/session_impl.hpp +++ b/libtorrent/include/libtorrent/session_impl.hpp @@ -1,594 +0,0 @@ -/* - -Copyright (c) 2006, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED -#define TORRENT_SESSION_IMPL_HPP_INCLUDED - -#include -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -#pragma warning(push, 1) -#endif - -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#include "libtorrent/torrent_handle.hpp" -#include "libtorrent/entry.hpp" -#include "libtorrent/torrent_info.hpp" -#include "libtorrent/socket.hpp" -#include "libtorrent/peer_connection.hpp" -#include "libtorrent/peer_id.hpp" -#include "libtorrent/policy.hpp" -#include "libtorrent/tracker_manager.hpp" -#include "libtorrent/peer_info.hpp" -#include "libtorrent/alert.hpp" -#include "libtorrent/fingerprint.hpp" -#include "libtorrent/debug.hpp" -#include "libtorrent/peer_request.hpp" -#include "libtorrent/piece_block_progress.hpp" -#include "libtorrent/ip_filter.hpp" -#include "libtorrent/config.hpp" -#include "libtorrent/session_settings.hpp" -#include "libtorrent/kademlia/dht_tracker.hpp" -#include "libtorrent/session_status.hpp" -#include "libtorrent/session.hpp" -#include "libtorrent/stat.hpp" -#include "libtorrent/file_pool.hpp" -#include "libtorrent/bandwidth_manager.hpp" -#include "libtorrent/natpmp.hpp" -#include "libtorrent/upnp.hpp" -#include "libtorrent/lsd.hpp" -#include "libtorrent/socket_type.hpp" -#include "libtorrent/connection_queue.hpp" -#include "libtorrent/disk_io_thread.hpp" - -namespace libtorrent -{ - - namespace fs = boost::filesystem; - - namespace aux - { - struct session_impl; - - // this data is shared between the main thread and the - // thread that initialize pieces - struct piece_checker_data - { - piece_checker_data() - : processing(false), progress(0.f), abort(false) {} - - boost::shared_ptr torrent_ptr; - fs::path save_path; - - sha1_hash info_hash; - - void parse_resume_data( - const entry& rd - , const torrent_info& info - , std::string& error); - - std::vector piece_map; - std::vector unfinished_pieces; - std::vector block_info; - std::vector peers; - entry resume_data; - - // this is true if this torrent is being processed (checked) - // if it is not being processed, then it can be removed from - // the queue without problems, otherwise the abort flag has - // to be set. - bool processing; - - // is filled in by storage::initialize_pieces() - // and represents the progress. It should be a - // value in the range [0, 1] - float progress; - - // abort defaults to false and is typically - // filled in by torrent_handle when the user - // aborts the torrent - bool abort; - }; - - struct checker_impl: boost::noncopyable - { - checker_impl(session_impl& s): m_ses(s), m_abort(false) {} - void operator()(); - piece_checker_data* find_torrent(const sha1_hash& info_hash); - void remove_torrent(sha1_hash const& info_hash); - -#ifndef NDEBUG - void check_invariant() const; -#endif - - // when the files has been checked - // the torrent is added to the session - session_impl& m_ses; - - mutable boost::mutex m_mutex; - boost::condition m_cond; - - // a list of all torrents that are currently in queue - // or checking their files - std::deque > m_torrents; - std::deque > m_processing; - - bool m_abort; - }; - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - struct tracker_logger; -#endif - - // this is the link between the main thread and the - // thread started to run the main downloader loop - struct session_impl: boost::noncopyable - { -#ifndef NDEBUG - friend class ::libtorrent::peer_connection; -#endif - friend struct checker_impl; - friend class invariant_access; - typedef std::map - , boost::intrusive_ptr > - connection_map; - typedef std::map > torrent_map; - - session_impl( - std::pair listen_port_range - , fingerprint const& cl_fprint - , char const* listen_interface = "0.0.0.0"); - ~session_impl(); - -#ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*)> ext); -#endif - void operator()(); - - void open_listen_port(); - - void async_accept(); - void on_incoming_connection(boost::shared_ptr const& s - , boost::weak_ptr const& as, asio::error_code const& e); - - // must be locked to access the data - // in this struct - typedef boost::recursive_mutex mutex_t; - mutable mutex_t m_mutex; - - boost::weak_ptr find_torrent(const sha1_hash& info_hash); - peer_id const& get_peer_id() const { return m_peer_id; } - - void close_connection(boost::intrusive_ptr const& p); - void connection_failed(boost::shared_ptr const& s - , tcp::endpoint const& a, char const* message); - - void set_settings(session_settings const& s); - session_settings const& settings() const { return m_settings; } - -#ifndef TORRENT_DISABLE_DHT - void add_dht_node(std::pair const& node); - void add_dht_node(udp::endpoint n); - void add_dht_router(std::pair const& node); - void set_dht_settings(dht_settings const& s); - dht_settings const& get_dht_settings() const { return m_dht_settings; } - void start_dht(entry const& startup_state); - void stop_dht(); - entry dht_state() const; -#endif - -#ifndef TORRENT_DISABLE_ENCRYPTION - void set_pe_settings(pe_settings const& settings); - pe_settings const& get_pe_settings() const { return m_pe_settings; } -#endif - - // called when a port mapping is successful, or a router returns - // a failure to map a port - void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); - - bool is_aborted() const { return m_abort; } - - void set_ip_filter(ip_filter const& f); - void set_port_filter(port_filter const& f); - - bool listen_on( - std::pair const& port_range - , const char* net_interface = 0); - bool is_listening() const; - - torrent_handle add_torrent( - torrent_info const& ti - , fs::path const& save_path - , entry const& resume_data - , bool compact_mode - , int block_size - , storage_constructor_type sc); - - torrent_handle add_torrent( - char const* tracker_url - , sha1_hash const& info_hash - , char const* name - , fs::path const& save_path - , entry const& resume_data - , bool compact_mode - , int block_size - , storage_constructor_type sc); - - void remove_torrent(torrent_handle const& h); - - std::vector get_torrents(); - - void set_severity_level(alert::severity_t s); - std::auto_ptr pop_alert(); - - int upload_rate_limit() const; - int download_rate_limit() const; - - void set_download_rate_limit(int bytes_per_second); - void set_upload_rate_limit(int bytes_per_second); - void set_max_half_open_connections(int limit); - void set_max_connections(int limit); - void set_max_uploads(int limit); - - int max_connections() const { return m_max_connections; } - int max_uploads() const { return m_max_uploads; } - - int num_uploads() const { return m_num_unchoked; } - int num_connections() const - { return m_connections.size(); } - - void unchoke_peer(peer_connection& c) - { - c.send_unchoke(); - ++m_num_unchoked; - } - - session_status status() const; - void set_peer_id(peer_id const& id); - void set_key(int key); - unsigned short listen_port() const; - - void abort(); - - torrent_handle find_torrent_handle(sha1_hash const& info_hash); - - void announce_lsd(sha1_hash const& ih); - - void set_peer_proxy(proxy_settings const& s) - { m_peer_proxy = s; } - void set_web_seed_proxy(proxy_settings const& s) - { m_web_seed_proxy = s; } - void set_tracker_proxy(proxy_settings const& s) - { m_tracker_proxy = s; } - - proxy_settings const& peer_proxy() const - { return m_peer_proxy; } - proxy_settings const& web_seed_proxy() const - { return m_web_seed_proxy; } - proxy_settings const& tracker_proxy() const - { return m_tracker_proxy; } - -#ifndef TORRENT_DISABLE_DHT - void set_dht_proxy(proxy_settings const& s) - { m_dht_proxy = s; } - proxy_settings const& dht_proxy() const - { return m_dht_proxy; } -#endif - - void start_lsd(); - void start_natpmp(); - void start_upnp(); - - void stop_lsd(); - void stop_natpmp(); - void stop_upnp(); - - // handles delayed alerts - alert_manager m_alerts; - -// private: - - void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); - - // this is where all active sockets are stored. - // the selector can sleep while there's no activity on - // them - io_service m_io_service; - asio::strand m_strand; - - // the file pool that all storages in this session's - // torrents uses. It sets a limit on the number of - // open files by this session. - // file pool must be destructed after the torrents - // since they will still have references to it - // when they are destructed. - file_pool m_files; - - // handles disk io requests asynchronously - disk_io_thread m_disk_thread; - - // this is a list of half-open tcp connections - // (only outgoing connections) - // this has to be one of the last - // members to be destructed - connection_queue m_half_open; - - // the bandwidth manager is responsible for - // handing out bandwidth to connections that - // asks for it, it can also throttle the - // rate. - bandwidth_manager m_download_channel; - bandwidth_manager m_upload_channel; - - bandwidth_manager* m_bandwidth_manager[2]; - - tracker_manager m_tracker_manager; - torrent_map m_torrents; - - // this maps sockets to their peer_connection - // object. It is the complete list of all connected - // peers. - connection_map m_connections; - - // filters incoming connections - ip_filter m_ip_filter; - - // filters outgoing connections - port_filter m_port_filter; - - // the peer id that is generated at the start of the session - peer_id m_peer_id; - - // the key is an id that is used to identify the - // client with the tracker only. It is randomized - // at startup - int m_key; - - // the range of ports we try to listen on - std::pair m_listen_port_range; - - // the ip-address of the interface - // we are supposed to listen on. - // if the ip is set to zero, it means - // that we should let the os decide which - // interface to listen on - tcp::endpoint m_listen_interface; - - // this is typically set to the same as the local - // listen port. In case a NAT port forward was - // successfully opened, this will be set to the - // port that is open on the external (NAT) interface - // on the NAT box itself. This is the port that has - // to be published to peers, since this is the port - // the client is reachable through. - int m_external_listen_port; - - boost::shared_ptr m_listen_socket; - - // the settings for the client - session_settings m_settings; - // the proxy settings for different - // kinds of connections - proxy_settings m_peer_proxy; - proxy_settings m_web_seed_proxy; - proxy_settings m_tracker_proxy; -#ifndef TORRENT_DISABLE_DHT - proxy_settings m_dht_proxy; -#endif - - // set to true when the session object - // is being destructed and the thread - // should exit - volatile bool m_abort; - - int m_max_uploads; - int m_max_connections; - - // the number of unchoked peers - int m_num_unchoked; - - // this is initialized to the unchoke_interval - // session_setting and decreased every second. - // when it reaches zero, it is reset to the - // unchoke_interval and the unchoke set is - // recomputed. - int m_unchoke_time_scaler; - - // works like unchoke_time_scaler but it - // is only decresed when the unchoke set - // is recomputed, and when it reaches zero, - // the optimistic unchoke is moved to another peer. - int m_optimistic_unchoke_time_scaler; - - // works like unchoke_time_scaler. Each time - // it reaches 0, and all the connections are - // used, the worst connection will be disconnected - // from the torrent with the most peers - int m_disconnect_time_scaler; - - // statistics gathered from all torrents. - stat m_stat; - - // is false by default and set to true when - // the first incoming connection is established - // this is used to know if the client is behind - // NAT or not. - bool m_incoming_connection; - - void second_tick(asio::error_code const& e); - ptime m_last_tick; - -#ifndef TORRENT_DISABLE_DHT - boost::intrusive_ptr m_dht; - dht_settings m_dht_settings; - // if this is set to true, the dht listen port - // will be set to the same as the tcp listen port - // and will be synchronlized with it as it changes - // it defaults to true - bool m_dht_same_port; - - // see m_external_listen_port. This is the same - // but for the udp port used by the DHT. - int m_external_udp_port; -#endif - -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings m_pe_settings; -#endif - - boost::shared_ptr m_natpmp; - boost::shared_ptr m_upnp; - boost::shared_ptr m_lsd; - - // the timer used to fire the second_tick - deadline_timer m_timer; - - // the index of the torrent that will be offered to - // connect to a peer next time second_tick is called. - // This implements a round robin. - int m_next_connect_torrent; -#ifndef NDEBUG - void check_invariant(const char *place = 0); -#endif - -#ifdef TORRENT_STATS - // logger used to write bandwidth usage statistics - std::ofstream m_stats_logger; - int m_second_counter; -#endif -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr create_log(std::string const& name - , int instance, bool append = true); - - // this list of tracker loggers serves as tracker_callbacks when - // shutting down. This list is just here to keep them alive during - // whe shutting down process - std::list > m_tracker_loggers; - - public: - boost::shared_ptr m_logger; - private: -#endif - -#ifndef TORRENT_DISABLE_EXTENSIONS - typedef std::list(torrent*)> > extension_list_t; - - extension_list_t m_extensions; -#endif - - // data shared between the main thread - // and the checker thread - checker_impl m_checker_impl; - - // the main working thread - boost::scoped_ptr m_thread; - - // the thread that calls initialize_pieces() - // on all torrents before they start downloading - boost::scoped_ptr m_checker_thread; - }; - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - struct tracker_logger : request_callback - { - tracker_logger(session_impl& ses): m_ses(ses) {} - void tracker_warning(std::string const& str) - { - debug_log("*** tracker warning: " + str); - } - - void tracker_response(tracker_request const& - , std::vector& peers - , int interval - , int complete - , int incomplete) - { - std::stringstream s; - s << "TRACKER RESPONSE:\n" - "interval: " << interval << "\n" - "peers:\n"; - for (std::vector::const_iterator i = peers.begin(); - i != peers.end(); ++i) - { - s << " " << std::setfill(' ') << std::setw(16) << i->ip - << " " << std::setw(5) << std::dec << i->port << " "; - if (!i->pid.is_all_zeros()) s << " " << i->pid; - s << "\n"; - } - debug_log(s.str()); - } - - void tracker_request_timed_out( - tracker_request const&) - { - debug_log("*** tracker timed out"); - } - - void tracker_request_error( - tracker_request const& - , int response_code - , const std::string& str) - { - debug_log(std::string("*** tracker error: ") - + boost::lexical_cast(response_code) + ": " - + str); - } - - void debug_log(const std::string& line) - { - (*m_ses.m_logger) << line << "\n"; - } - session_impl& m_ses; - }; -#endif - - } -} - - -#endif - diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index 3a145c687..ebc30eae3 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -105,11 +105,9 @@ namespace libtorrent , send_redundant_have(false) , lazy_bitfields(true) , inactivity_timeout(600) - , unchoke_interval(15) - , optimistic_unchoke_multiplier(4) + , unchoke_interval(20) , num_want(200) , initial_picker_threshold(4) - , allowed_fast_set_size(10) , max_outstanding_disk_bytes_per_connection(64 * 1024) #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) @@ -243,10 +241,6 @@ namespace libtorrent // the number of seconds between chokes/unchokes int unchoke_interval; - // the number of unchoke intervals between - // optimistic unchokes - int optimistic_unchoke_multiplier; - // if this is set, this IP will be reported do the // tracker in the ip= parameter. address announce_ip; @@ -258,10 +252,6 @@ namespace libtorrent // random pieces instead of rarest first. int initial_picker_threshold; - // the number of allowed pieces to send to peers - // that supports the fast extensions - int allowed_fast_set_size; - // the maximum number of bytes a connection may have // pending in the disk write queue before its download // rate is being throttled. This prevents fast downloads diff --git a/libtorrent/include/libtorrent/stat.hpp b/libtorrent/include/libtorrent/stat.hpp index 24e477a37..2424d5d6c 100755 --- a/libtorrent/include/libtorrent/stat.hpp +++ b/libtorrent/include/libtorrent/stat.hpp @@ -40,7 +40,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/size_type.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 9db79ea3d..8a10c7148 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -43,7 +43,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #ifdef _MSC_VER @@ -148,11 +147,10 @@ namespace libtorrent }; typedef storage_interface* (&storage_constructor_type)( - boost::intrusive_ptr, fs::path const& + torrent_info const&, fs::path const& , file_pool&); - TORRENT_EXPORT storage_interface* default_storage_constructor( - boost::intrusive_ptr ti + TORRENT_EXPORT storage_interface* default_storage_constructor(torrent_info const& ti , fs::path const& path, file_pool& fp); // returns true if the filesystem the path relies on supports @@ -171,7 +169,7 @@ namespace libtorrent piece_manager( boost::shared_ptr const& torrent - , boost::intrusive_ptr ti + , torrent_info const& ti , fs::path const& path , file_pool& fp , disk_io_thread& io @@ -201,9 +199,7 @@ namespace libtorrent void async_read( peer_request const& r - , boost::function const& handler - , char* buffer = 0 - , int priority = 0); + , boost::function const& handler); void async_write( peer_request const& r @@ -231,7 +227,7 @@ namespace libtorrent { return m_compact_mode; } #ifndef NDEBUG - std::string name() const { return m_info->name(); } + std::string name() const { return m_info.name(); } #endif private: @@ -287,7 +283,7 @@ namespace libtorrent // a bitmask representing the pieces we have std::vector m_have_piece; - boost::intrusive_ptr m_info; + torrent_info const& m_info; // slots that haven't had any file storage allocated std::vector m_unallocated_slots; @@ -317,6 +313,12 @@ namespace libtorrent mutable boost::recursive_mutex m_mutex; + bool m_allocating; + boost::mutex m_allocating_monitor; + boost::condition m_allocating_condition; + + // these states are used while checking/allocating the torrent + enum { // the default initial state state_none, @@ -331,11 +333,6 @@ namespace libtorrent } m_state; int m_current_slot; - // this is saved in case we need to instantiate a new - // storage (osed when remapping files) - storage_constructor_type m_storage_constructor; - - // temporary buffer used while checking std::vector m_piece_data; // this maps a piece hash to piece index. It will be @@ -343,9 +340,6 @@ namespace libtorrent // isn't needed) std::multimap m_hash_to_piece; - // this map contains partial hashes for downloading - // pieces. This is only accessed from within the - // disk-io thread. std::map m_piece_hasher; disk_io_thread& m_io_thread; diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index 27d61af9d..2227fc932 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -55,7 +55,6 @@ namespace libtorrent || _POSIX_MONOTONIC_CLOCK < 0)) || defined (TORRENT_USE_BOOST_DATE_TIME) #include -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -86,7 +85,6 @@ namespace libtorrent #include #include -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -172,7 +170,6 @@ namespace asio #include #include -#include "libtorrent/assert.hpp" // high precision timer for darwin intel and ppc @@ -252,7 +249,6 @@ namespace libtorrent #define WIN32_LEAN_AND_MEAN #endif #include -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -339,7 +335,6 @@ namespace libtorrent #elif defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 #include -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -390,4 +385,4 @@ namespace libtorrent #endif #endif - + diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index bcc54899f..2eef2656b 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -62,13 +62,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/tracker_manager.hpp" #include "libtorrent/stat.hpp" #include "libtorrent/alert.hpp" +#include "libtorrent/resource_request.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/config.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/bandwidth_manager.hpp" #include "libtorrent/storage.hpp" #include "libtorrent/hasher.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -98,13 +98,13 @@ namespace libtorrent torrent( aux::session_impl& ses , aux::checker_impl& checker - , boost::intrusive_ptr tf + , torrent_info const& tf , fs::path const& save_path , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , storage_constructor_type sc - , bool paused); + , session_settings const& s + , storage_constructor_type sc); // used with metadata-less torrents // (the metadata is downloaded from the peers) @@ -118,8 +118,8 @@ namespace libtorrent , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , storage_constructor_type sc - , bool paused); + , session_settings const& s + , storage_constructor_type sc); ~torrent(); @@ -154,6 +154,10 @@ namespace libtorrent bool verify_resume_data(entry& rd, std::string& error) { assert(m_storage); return m_storage->verify_resume_data(rd, error); } + // is called every second by session. This will + // caclulate the upload/download and number + // of connections this torrent needs. And prepare + // it for being used by allocate_resources. void second_tick(stat& accumulator, float tick_interval); // debug purpose only @@ -250,15 +254,6 @@ namespace libtorrent void remove_url_seed(std::string const& url) { m_web_seeds.erase(url); } - std::set url_seeds() const - { return m_web_seeds; } - - bool free_upload_slots() const - { return m_num_uploads < m_max_uploads; } - - void choke_peer(peer_connection& c); - bool unchoke_peer(peer_connection& c); - // used by peer_connection to attach itself to a torrent // since incoming connections don't know what torrent // they're a part of until they have received an info_hash. @@ -470,14 +465,14 @@ namespace libtorrent bool is_seed() const { return valid_metadata() - && m_num_pieces == m_torrent_file->num_pieces(); + && m_num_pieces == m_torrent_file.num_pieces(); } // this is true if we have all the pieces that we want bool is_finished() const { if (is_seed()) return true; - return valid_metadata() && m_torrent_file->num_pieces() + return valid_metadata() && m_torrent_file.num_pieces() - m_num_pieces - m_picker->num_filtered() == 0; } @@ -499,7 +494,7 @@ namespace libtorrent } piece_manager& filesystem(); torrent_info const& torrent_file() const - { return *m_torrent_file; } + { return m_torrent_file; } std::vector const& trackers() const { return m_trackers; } @@ -521,6 +516,11 @@ namespace libtorrent // -------------------------------------------- // RESOURCE MANAGEMENT + void distribute_resources(float tick_interval); + + resource_request m_uploads_quota; + resource_request m_connections_quota; + void set_peer_upload_limit(tcp::endpoint ip, int limit); void set_peer_download_limit(tcp::endpoint ip, int limit); @@ -530,9 +530,7 @@ namespace libtorrent int download_limit() const; void set_max_uploads(int limit); - int max_uploads() const { return m_max_uploads; } void set_max_connections(int limit); - int max_connections() const { return m_max_connections; } void move_storage(fs::path const& save_path); // unless this returns true, new connections must wait @@ -540,7 +538,7 @@ namespace libtorrent bool ready_for_connections() const { return m_connections_initialized; } bool valid_metadata() const - { return m_torrent_file->is_valid(); } + { return m_torrent_file.is_valid(); } // parses the info section from the given // bencoded tree and moves the torrent @@ -564,7 +562,7 @@ namespace libtorrent void update_peer_interest(); - boost::intrusive_ptr m_torrent_file; + torrent_info m_torrent_file; // is set to true when the torrent has // been aborted. @@ -707,9 +705,9 @@ namespace libtorrent // determine the timeout until next try. int m_failed_trackers; - // this is a counter that is decreased every - // second, and when it reaches 0, the policy::pulse() - // is called and the time scaler is reset to 10. + // this is a counter that is increased every + // second, and when it reaches 10, the policy::pulse() + // is called and the time scaler is reset to 0. int m_time_scaler; // the bitmask that says which pieces we have @@ -776,15 +774,6 @@ namespace libtorrent session_settings const& m_settings; storage_constructor_type m_storage_constructor; - - // the maximum number of uploads for this torrent - int m_max_uploads; - - // the number of unchoked peers in this torrent - int m_num_uploads; - - // the maximum number of connections for this torrent - int m_max_connections; #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list > extension_list_t; diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 287c57305..3f7ae5bcc 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -34,7 +34,6 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_TORRENT_HANDLE_HPP_INCLUDED #include -#include #ifdef _MSC_VER #pragma warning(push, 1) @@ -274,9 +273,7 @@ namespace libtorrent std::vector const& trackers() const; void replace_trackers(std::vector const&) const; - void add_url_seed(std::string const& url) const; - void remove_url_seed(std::string const& url) const; - std::set url_seeds() const; + void add_url_seed(std::string const& url); bool has_metadata() const; const torrent_info& get_torrent_info() const; @@ -399,7 +396,6 @@ namespace libtorrent , m_info_hash(h) { assert(m_ses != 0); - assert(m_chk != 0); } #ifndef NDEBUG diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index 492fda48d..a2d6c4ef9 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -57,8 +57,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_request.hpp" #include "libtorrent/config.hpp" #include "libtorrent/time.hpp" -#include "libtorrent/intrusive_ptr_base.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { @@ -73,7 +71,7 @@ namespace libtorrent size_type offset; // the offset of this file inside the torrent size_type size; // the size of this file // if the path was incorrectly encoded, this is - // the original corrupt encoded string. It is + // the origianal corrupt encoded string. It is // preserved in order to be able to reproduce // the correct info-hash boost::shared_ptr orig_path; @@ -98,7 +96,7 @@ namespace libtorrent virtual const char* what() const throw() { return "invalid torrent file"; } }; - class TORRENT_EXPORT torrent_info : public intrusive_ptr_base + class TORRENT_EXPORT torrent_info { public: @@ -117,12 +115,8 @@ namespace libtorrent void add_file(fs::path file, size_type size); void add_url_seed(std::string const& url); - bool remap_files(std::vector > const& map); - - std::vector map_block(int piece, size_type offset - , int size, bool storage = false) const; - peer_request map_file(int file, size_type offset, int size - , bool storage = false) const; + std::vector map_block(int piece, size_type offset, int size) const; + peer_request map_file(int file, size_type offset, int size) const; std::vector const& url_seeds() const { @@ -134,60 +128,15 @@ namespace libtorrent typedef std::vector::const_reverse_iterator reverse_file_iterator; // list the files in the torrent file - file_iterator begin_files(bool storage = false) const - { - if (!storage || m_remapped_files.empty()) - return m_files.begin(); - else - return m_remapped_files.begin(); - } + file_iterator begin_files() const { return m_files.begin(); } + file_iterator end_files() const { return m_files.end(); } + reverse_file_iterator rbegin_files() const { return m_files.rbegin(); } + reverse_file_iterator rend_files() const { return m_files.rend(); } - file_iterator end_files(bool storage = false) const - { - if (!storage || m_remapped_files.empty()) - return m_files.end(); - else - return m_remapped_files.end(); - } - - reverse_file_iterator rbegin_files(bool storage = false) const - { - if (!storage || m_remapped_files.empty()) - return m_files.rbegin(); - else - return m_remapped_files.rbegin(); - } - - reverse_file_iterator rend_files(bool storage = false) const - { - if (!storage || m_remapped_files.empty()) - return m_files.rend(); - else - return m_remapped_files.rend(); - } - - int num_files(bool storage = false) const - { - assert(m_piece_length > 0); - if (!storage || m_remapped_files.empty()) - return (int)m_files.size(); - else - return (int)m_remapped_files.size(); - } - - const file_entry& file_at(int index, bool storage = false) const - { - if (!storage || m_remapped_files.empty()) - { - assert(index >= 0 && index < (int)m_files.size()); - return m_files[index]; - } - else - { - assert(index >= 0 && index < (int)m_remapped_files.size()); - return m_remapped_files[index]; - } - } + int num_files() const + { assert(m_piece_length > 0); return (int)m_files.size(); } + const file_entry& file_at(int index) const + { assert(index >= 0 && index < (int)m_files.size()); return m_files[index]; } const std::vector& trackers() const { return m_urls; } @@ -269,13 +218,6 @@ namespace libtorrent // the list of files that this torrent consists of std::vector m_files; - // this vector is typically empty. If it is not - // empty, it means the user has re-mapped the - // files in this torrent to diffefrent names - // on disk. This is only used when reading and - // writing the disk. - std::vector m_remapped_files; - nodes_t m_nodes; // the sum of all filesizes @@ -322,10 +264,8 @@ namespace libtorrent entry m_extra_info; #ifndef NDEBUG - public: // this is set to true when seed_free() is called bool m_half_metadata; - private: #endif }; diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index 57f7bd851..1435ceda6 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -194,10 +194,11 @@ namespace libtorrent , address bind_interface , boost::weak_ptr r); - boost::shared_ptr requester(); + request_callback& requester(); virtual ~tracker_connection() {} tracker_request const& tracker_req() const { return m_req; } + bool has_requester() const { return !m_requester.expired(); } void fail(int code, char const* msg); void fail_timeout(); diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index fc0650631..d4b701aad 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -34,7 +34,6 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_UPNP_HPP #include "libtorrent/socket.hpp" -#include "libtorrent/broadcast_socket.hpp" #include "libtorrent/http_connection.hpp" #include "libtorrent/connection_queue.hpp" @@ -57,6 +56,9 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { +bool is_local(address const& a); +address_v4 guess_local_address(asio::io_service&); + // int: external tcp port // int: external udp port // std::string: error message @@ -70,6 +72,8 @@ public: , portmap_callback_t const& cb); ~upnp(); + void rebind(address const& listen_interface); + // maps the ports, if a port is set to 0 // it will not be mapped void set_mappings(int tcp, int udp); @@ -86,7 +90,7 @@ private: void update_mapping(int i, int port); void resend_request(asio::error_code const& e); - void on_reply(udp::endpoint const& from, char* buffer + void on_reply(asio::error_code const& e , std::size_t bytes_transferred); void discover_device(); @@ -102,15 +106,12 @@ private: , int mapping); void on_expire(asio::error_code const& e); + void post(rootdevice& d, std::stringstream const& s + , std::string const& soap_action); void map_port(rootdevice& d, int i); void unmap_port(rootdevice& d, int i); void disable(); - void delete_port_mapping(rootdevice& d, int i); - void create_port_mapping(http_connection& c, rootdevice& d, int i); - void post(upnp::rootdevice const& d, std::string const& soap - , std::string const& soap_action); - struct mapping_t { mapping_t() @@ -197,13 +198,18 @@ private: // current retry count int m_retry_count; - asio::io_service& m_io_service; + // used to receive responses in + char m_receive_buffer[1024]; - asio::strand m_strand; + // the endpoint we received the message from + udp::endpoint m_remote; + // the local address we're listening on + address_v4 m_local_ip; + // the udp socket used to send and receive // multicast messages on the network - broadcast_socket m_socket; + datagram_socket m_socket; // used to resend udp packets in case // they time out @@ -211,6 +217,8 @@ private: // timer used to refresh mappings deadline_timer m_refresh_timer; + + asio::strand m_strand; bool m_disabled; bool m_closing; diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index 1290f14a1..ba7450c0a 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -65,6 +65,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -125,8 +126,6 @@ namespace libtorrent void write_piece(peer_request const& r, char const* buffer) { assert(false); } void write_keepalive() {} void on_connected(); - void write_reject_request(peer_request const&) {} - void write_allow_fast(int) {} #ifndef NDEBUG void check_invariant() const; diff --git a/libtorrent/src/assert.cpp b/libtorrent/src/assert.cpp index b4f011978..e69de29bb 100644 --- a/libtorrent/src/assert.cpp +++ b/libtorrent/src/assert.cpp @@ -1,73 +0,0 @@ -/* - -Copyright (c) 2007, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#ifndef NDEBUG - -#include -#include -#include -#if defined __linux__ && defined __GNUC__ -#include -#endif - -void assert_fail(char const* expr, int line, char const* file, char const* function) -{ - - fprintf(stderr, "assertion failed. Please file a bugreport at " - "http://code.rasterbar.com/libtorrent/newticket\n" - "Please include the following information:\n\n" - "file: '%s'\n" - "line: %d\n" - "function: %s\n" - "expression: %s\n" - "stack:\n", file, line, function, expr); - -#if defined __linux__ && defined __GNUC__ - void* stack[50]; - int size = backtrace(stack, 50); - char** symbols = backtrace_symbols(stack, size); - - for (int i = 0; i < size; ++i) - { - fprintf(stderr, "%d: %s\n", i, symbols[i]); - } - - free(symbols); -#endif - // send SIGINT to the current process - // to break into the debugger - raise(SIGINT); - abort(); -} - -#endif - diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 3aaadcc81..e69de29bb 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -1,186 +0,0 @@ -/* - -Copyright (c) 2007, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#include -#include -#include - -#include "libtorrent/socket.hpp" -#include "libtorrent/enum_net.hpp" -#include "libtorrent/broadcast_socket.hpp" -#include "libtorrent/assert.hpp" - -namespace libtorrent -{ - bool is_local(address const& a) - { - if (a.is_v6()) return a.to_v6().is_link_local(); - address_v4 a4 = a.to_v4(); - unsigned long ip = a4.to_ulong(); - return ((ip & 0xff000000) == 0x0a000000 - || (ip & 0xfff00000) == 0xac100000 - || (ip & 0xffff0000) == 0xc0a80000); - } - - address_v4 guess_local_address(asio::io_service& ios) - { - // make a best guess of the interface we're using and its IP - udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); - for (;i != udp::resolver_iterator(); ++i) - { - address const& a = i->endpoint().address(); - // ignore non-IPv4 addresses - if (!a.is_v4()) break; - // ignore the loopback - if (a.to_v4() == address_v4::loopback()) continue; - } - if (i == udp::resolver_iterator()) return address_v4::any(); - return i->endpoint().address().to_v4(); - } - - bool is_loopback(address const& addr) - { - if (addr.is_v4()) - return addr.to_v4() == address_v4::loopback(); - else - return addr.to_v6() == address_v6::loopback(); - } - - bool is_multicast(address const& addr) - { - if (addr.is_v4()) - return addr.to_v4().is_multicast(); - else - return addr.to_v6().is_multicast(); - } - - broadcast_socket::broadcast_socket(asio::io_service& ios - , udp::endpoint const& multicast_endpoint - , receive_handler_t const& handler - , bool loopback) - : m_multicast_endpoint(multicast_endpoint) - , m_on_receive(handler) - { - assert(is_multicast(m_multicast_endpoint.address())); - - using namespace asio::ip::multicast; - - asio::error_code ec; - std::vector
interfaces = enum_net_interfaces(ios, ec); - - for (std::vector
::const_iterator i = interfaces.begin() - , end(interfaces.end()); i != end; ++i) - { - // only broadcast to IPv4 addresses that are not local - if (!is_local(*i)) continue; - // only multicast on compatible networks - if (i->is_v4() != multicast_endpoint.address().is_v4()) continue; - // ignore any loopback interface - if (is_loopback(*i)) continue; - - boost::shared_ptr s(new datagram_socket(ios)); - if (i->is_v4()) - { - s->open(udp::v4(), ec); - if (ec) continue; - s->set_option(datagram_socket::reuse_address(true), ec); - if (ec) continue; - s->bind(udp::endpoint(address_v4::any(), multicast_endpoint.port()), ec); - if (ec) continue; - s->set_option(join_group(multicast_endpoint.address()), ec); - if (ec) continue; - s->set_option(outbound_interface(i->to_v4()), ec); - if (ec) continue; - } - else - { - s->open(udp::v6(), ec); - if (ec) continue; - s->set_option(datagram_socket::reuse_address(true), ec); - if (ec) continue; - s->bind(udp::endpoint(address_v6::any(), multicast_endpoint.port()), ec); - if (ec) continue; - s->set_option(join_group(multicast_endpoint.address()), ec); - if (ec) continue; -// s->set_option(outbound_interface(i->to_v6()), ec); -// if (ec) continue; - } - s->set_option(hops(255), ec); - if (ec) continue; - s->set_option(enable_loopback(loopback), ec); - if (ec) continue; - m_sockets.push_back(socket_entry(s)); - socket_entry& se = m_sockets.back(); - s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) - , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); -#ifndef NDEBUG -// std::cerr << "broadcast socket [ if: " << i->to_v4().to_string() -// << " group: " << multicast_endpoint.address() << " ]" << std::endl; -#endif - } - } - - void broadcast_socket::send(char const* buffer, int size, asio::error_code& ec) - { - for (std::list::iterator i = m_sockets.begin() - , end(m_sockets.end()); i != end; ++i) - { - asio::error_code e; - i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); -#ifndef NDEBUG -// std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; -#endif - if (e) ec = e; - } - } - - void broadcast_socket::on_receive(socket_entry* s, asio::error_code const& ec - , std::size_t bytes_transferred) - { - if (ec || bytes_transferred == 0) return; - m_on_receive(s->remote, s->buffer, bytes_transferred); - s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer)) - , s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2)); - } - - void broadcast_socket::close() - { - for (std::list::iterator i = m_sockets.begin() - , end(m_sockets.end()); i != end; ++i) - { - i->socket->close(); - } - } -} - - diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index ab61d98f9..5b63f26cd 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -50,7 +50,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" -#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION #include "libtorrent/pe_crypto.hpp" @@ -76,14 +75,7 @@ namespace libtorrent &bt_peer_connection::on_piece, &bt_peer_connection::on_cancel, &bt_peer_connection::on_dht_port, - 0, 0, 0, - // FAST extension messages - &bt_peer_connection::on_suggest_piece, - &bt_peer_connection::on_have_all, - &bt_peer_connection::on_have_none, - &bt_peer_connection::on_reject_request, - &bt_peer_connection::on_allowed_fast, - 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &bt_peer_connection::on_extended }; @@ -101,7 +93,6 @@ namespace libtorrent , m_supports_extensions(false) #endif , m_supports_dht_port(false) - , m_supports_fast(false) #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) @@ -133,7 +124,6 @@ namespace libtorrent , m_supports_extensions(false) #endif , m_supports_dht_port(false) - , m_supports_fast(false) #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) @@ -236,10 +226,6 @@ namespace libtorrent boost::shared_ptr t = associated_torrent().lock(); assert(t); write_bitfield(t->pieces()); -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.get_dht_settings().service_port); -#endif } void bt_peer_connection::write_dht_port(int listen_port) @@ -260,75 +246,6 @@ namespace libtorrent setup_send(); } - void bt_peer_connection::write_have_all() - { - INVARIANT_CHECK; - assert(m_sent_handshake && !m_sent_bitfield); -#ifndef NDEBUG - m_sent_bitfield = true; -#endif -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " ==> HAVE_ALL\n"; -#endif - char buf[] = {0,0,0,1, msg_have_all}; - send_buffer(buf, buf + sizeof(buf)); - } - - void bt_peer_connection::write_have_none() - { - INVARIANT_CHECK; - assert(m_sent_handshake && !m_sent_bitfield); -#ifndef NDEBUG - m_sent_bitfield = true; -#endif -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " ==> HAVE_NONE\n"; -#endif - char buf[] = {0,0,0,1, msg_have_none}; - send_buffer(buf, buf + sizeof(buf)); - } - - void bt_peer_connection::write_reject_request(peer_request const& r) - { - INVARIANT_CHECK; - - assert(m_sent_handshake && m_sent_bitfield); - assert(associated_torrent().lock()->valid_metadata()); - - char buf[] = {0,0,0,13, msg_reject_request}; - - buffer::interval i = allocate_send_buffer(17); - - std::copy(buf, buf + 5, i.begin); - i.begin += 5; - - // index - detail::write_int32(r.piece, i.begin); - // begin - detail::write_int32(r.start, i.begin); - // length - detail::write_int32(r.length, i.begin); - assert(i.begin == i.end); - - setup_send(); - } - - void bt_peer_connection::write_allow_fast(int piece) - { - INVARIANT_CHECK; - - assert(m_sent_handshake && m_sent_bitfield); - assert(associated_torrent().lock()->valid_metadata()); - - char buf[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; - - char* ptr = buf + 5; - detail::write_int32(piece, ptr); - send_buffer(buf, buf + sizeof(buf)); - } - void bt_peer_connection::get_specific_peer_info(peer_info& p) const { assert(!associated_torrent().expired()); @@ -711,17 +628,14 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT // indicate that we support the DHT messages - *(i.begin + 7) |= 0x01; + *(i.begin + 7) = 0x01; #endif #ifndef TORRENT_DISABLE_EXTENSIONS // we support extensions - *(i.begin + 5) |= 0x10; + *(i.begin + 5) = 0x10; #endif - // we support FAST extension - *(i.begin + 7) |= 0x04; - i.begin += 8; // info hash @@ -807,20 +721,6 @@ namespace libtorrent if (!packet_finished()) return; incoming_choke(); - if (!m_supports_fast) - { - boost::shared_ptr t = associated_torrent().lock(); - assert(t); - while (!request_queue().empty()) - { - piece_block const& b = request_queue().front(); - peer_request r; - r.piece = b.piece_index; - r.start = b.block_index * t->block_size(); - r.length = t->block_size(); - incoming_reject_request(r); - } - } } // ----------------------------- @@ -1039,9 +939,6 @@ namespace libtorrent { INVARIANT_CHECK; - if (!m_supports_dht_port) - throw protocol_error("got 'dht_port' message from peer that doesn't support it"); - assert(received > 0); if (packet_size() != 3) throw protocol_error("'dht_port' message size != 3"); @@ -1056,80 +953,6 @@ namespace libtorrent incoming_dht_port(listen_port); } - void bt_peer_connection::on_suggest_piece(int received) - { - INVARIANT_CHECK; - - if (!m_supports_fast) - throw protocol_error("got 'suggest_piece' without FAST extension support"); - - m_statistics.received_bytes(0, received); - if (!packet_finished()) return; - - buffer::const_interval recv_buffer = receive_buffer(); - - const char* ptr = recv_buffer.begin + 1; - int piece = detail::read_uint32(ptr); - incoming_suggest(piece); - } - - void bt_peer_connection::on_have_all(int received) - { - INVARIANT_CHECK; - - if (!m_supports_fast) - throw protocol_error("got 'have_all' without FAST extension support"); - m_statistics.received_bytes(0, received); - incoming_have_all(); - } - - void bt_peer_connection::on_have_none(int received) - { - INVARIANT_CHECK; - - if (!m_supports_fast) - throw protocol_error("got 'have_none' without FAST extension support"); - m_statistics.received_bytes(0, received); - incoming_have_none(); - } - - void bt_peer_connection::on_reject_request(int received) - { - INVARIANT_CHECK; - - if (!m_supports_fast) - throw protocol_error("got 'reject_request' without FAST extension support"); - - m_statistics.received_bytes(0, received); - if (!packet_finished()) return; - - buffer::const_interval recv_buffer = receive_buffer(); - - peer_request r; - const char* ptr = recv_buffer.begin + 1; - r.piece = detail::read_int32(ptr); - r.start = detail::read_int32(ptr); - r.length = detail::read_int32(ptr); - - incoming_reject_request(r); - } - - void bt_peer_connection::on_allowed_fast(int received) - { - INVARIANT_CHECK; - - if (!m_supports_fast) - throw protocol_error("got 'allowed_fast' without FAST extension support"); - - m_statistics.received_bytes(0, received); - if (!packet_finished()) return; - buffer::const_interval recv_buffer = receive_buffer(); - const char* ptr = recv_buffer.begin + 1; - int index = detail::read_int32(ptr); - - incoming_allowed_fast(index); - } - // ----------------------------- // --------- EXTENDED ---------- // ----------------------------- @@ -1222,7 +1045,7 @@ namespace libtorrent { tcp::endpoint adr(remote().address() , (unsigned short)listen_port->integer()); - t->get_policy().peer_from_tracker(adr, pid(), peer_info::incoming, 0); + t->get_policy().peer_from_tracker(adr, pid(), 0, 0); } } // there should be a version too @@ -1352,22 +1175,6 @@ namespace libtorrent assert(m_sent_handshake && !m_sent_bitfield); assert(t->valid_metadata()); - // in this case, have_all or have_none should be sent instead - assert(!m_supports_fast || !t->is_seed() || t->num_pieces() != 0); - - if (m_supports_fast && t->is_seed()) - { - write_have_all(); - send_allowed_set(); - return; - } - else if (m_supports_fast && t->num_pieces() == 0) - { - write_have_none(); - send_allowed_set(); - return; - } - int num_pieces = bitfield.size(); int lazy_pieces[50]; int num_lazy_pieces = 0; @@ -1376,7 +1183,7 @@ namespace libtorrent assert(t->is_seed() == (std::count(bitfield.begin(), bitfield.end(), true) == num_pieces)); if (t->is_seed() && m_ses.settings().lazy_bitfields) { - num_lazy_pieces = (std::min)(50, num_pieces / 10); + num_lazy_pieces = std::min(50, num_pieces / 10); if (num_lazy_pieces < 1) num_lazy_pieces = 1; for (int i = 0; i < num_pieces; ++i) { @@ -1444,9 +1251,6 @@ namespace libtorrent #endif } } - - if (m_supports_fast) - send_allowed_set(); } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1475,19 +1279,6 @@ namespace libtorrent detail::write_address(remote().address(), out); handshake["yourip"] = remote_address; handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; - asio::error_code ec; - std::vector
const& interfaces = enum_net_interfaces(get_socket()->io_service(), ec); - for (std::vector
::const_iterator i = interfaces.begin() - , end(interfaces.end()); i != end; ++i) - { - // TODO: only use global IPv6 addresses - if (!i->is_v6() || i->to_v6().is_link_local()) continue; - std::string ipv6_address; - std::back_insert_iterator out(ipv6_address); - detail::write_address(*i, out); - handshake["ipv6"] = ipv6_address; - break; - } // loop backwards, to make the first extension be the last // to fill in the handshake (i.e. give the first extensions priority) @@ -1755,7 +1546,7 @@ namespace libtorrent if (m_sync_bytes_read >= 512) throw protocol_error("sync hash not found within 532 bytes"); - cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+20) - m_sync_bytes_read)); + cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+20) - m_sync_bytes_read)); assert(!packet_finished()); return; @@ -1893,7 +1684,7 @@ namespace libtorrent if (m_sync_bytes_read >= 512) throw protocol_error("sync verification constant not found within 520 bytes"); - cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+8) - m_sync_bytes_read)); + cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+8) - m_sync_bytes_read)); assert(!packet_finished()); return; @@ -2225,9 +2016,6 @@ namespace libtorrent if (recv_buffer[7] & 0x01) m_supports_dht_port = true; - if (recv_buffer[7] & 0x04) - m_supports_fast = true; - // ok, now we have got enough of the handshake. Is this connection // attached to a torrent? if (!t) @@ -2261,10 +2049,10 @@ namespace libtorrent assert(t); // if this is a local connection, we have already - // sent the handshake + // send the handshake if (!is_local()) write_handshake(); -// if (t->valid_metadata()) -// write_bitfield(t->pieces()); + if (t->valid_metadata()) + write_bitfield(t->pieces()); assert(t->get_policy().has_connection(this)); @@ -2337,6 +2125,11 @@ namespace libtorrent throw protocol_error("closing connection to ourself"); } +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.get_dht_settings().service_port); +#endif + m_client_version = identify_client(pid); boost::optional f = client_fingerprint(pid); if (f && std::equal(f->name, f->name + 2, "BC")) @@ -2388,14 +2181,6 @@ namespace libtorrent m_state = read_packet_size; reset_recv_buffer(4); - if (t->valid_metadata()) - { - write_bitfield(t->pieces()); -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.get_dht_settings().service_port); -#endif - } assert(!packet_finished()); return; diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index 0b3f5ff54..859205ed0 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -54,8 +54,6 @@ namespace libtorrent , boost::function const& on_timeout , time_duration timeout) { - mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; m_queue.push_back(entry()); @@ -70,8 +68,6 @@ namespace libtorrent void connection_queue::done(int ticket) { - mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; std::list::iterator i = std::find_if(m_queue.begin() @@ -152,8 +148,6 @@ namespace libtorrent void connection_queue::on_timeout(asio::error_code const& e) { - mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; #ifndef NDEBUG function_guard guard_(m_in_timeout_function); @@ -164,35 +158,21 @@ namespace libtorrent ptime next_expire = max_time(); ptime now = time_now(); - std::list timed_out; for (std::list::iterator i = m_queue.begin(); !m_queue.empty() && i != m_queue.end();) { if (i->connecting && i->expires < now) { - std::list::iterator j = i; - ++i; - timed_out.splice(timed_out.end(), m_queue, j, i); + boost::function on_timeout = i->on_timeout; + m_queue.erase(i++); --m_num_connecting; + try { on_timeout(); } catch (std::exception&) {} continue; } if (i->expires < next_expire) next_expire = i->expires; ++i; } - - // we don't want to call the timeout callback while we're locked - // since that is a recepie for dead-locks - l.unlock(); - - for (std::list::iterator i = timed_out.begin() - , end(timed_out.end()); i != end; ++i) - { - try { i->on_timeout(); } catch (std::exception&) {} - } - - l.lock(); - if (next_expire < max_time()) { m_timer.expires_at(next_expire); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 4fb2cfb3a..6bc357506 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -34,24 +34,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/disk_io_thread.hpp" -#ifdef TORRENT_DISK_STATS - -#include "libtorrent/time.hpp" -#include - -namespace -{ - std::string log_time() - { - using namespace libtorrent; - static ptime start = time_now(); - return boost::lexical_cast( - total_milliseconds(time_now() - start)); - } -} - -#endif - namespace libtorrent { @@ -63,12 +45,7 @@ namespace libtorrent , m_block_size(block_size) #endif , m_disk_io_thread(boost::ref(*this)) - { - -#ifdef TORRENT_DISK_STATS - m_log.open("disk_io_thread.log", std::ios::trunc); -#endif - } + {} disk_io_thread::~disk_io_thread() { @@ -112,15 +89,8 @@ namespace libtorrent namespace { - // The semantic of this operator is: - // shouls lhs come before rhs in the job queue bool operator<(disk_io_job const& lhs, disk_io_job const& rhs) { - // NOTE: comparison inverted to make higher priority - // skip _in_front_of_ lower priority - if (lhs.priority > rhs.priority) return true; - if (lhs.priority < rhs.priority) return false; - if (lhs.storage.get() < rhs.storage.get()) return true; if (lhs.storage.get() > rhs.storage.get()) return false; if (lhs.piece < rhs.piece) return true; @@ -195,9 +165,6 @@ namespace libtorrent { for (;;) { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " idle" << std::endl; -#endif boost::mutex::scoped_lock l(m_mutex); while (m_jobs.empty() && !m_abort) m_signal.wait(l); @@ -212,46 +179,31 @@ namespace libtorrent int ret = 0; - bool free_buffer = true; try { -#ifdef TORRENT_DISK_STATS - ptime start = time_now(); -#endif // std::cerr << "DISK THREAD: executing job: " << j.action << std::endl; switch (j.action) { case disk_io_job::read: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " read " << j.buffer_size << std::endl; -#endif + l.lock(); + j.buffer = (char*)m_pool.ordered_malloc(); + l.unlock(); if (j.buffer == 0) { - l.lock(); - j.buffer = (char*)m_pool.ordered_malloc(); - l.unlock(); - assert(j.buffer_size <= m_block_size); - if (j.buffer == 0) - { - ret = -1; - j.str = "out of memory"; - break; - } + ret = -1; + j.str = "out of memory"; } else { - free_buffer = false; - } - ret = j.storage->read_impl(j.buffer, j.piece, j.offset - , j.buffer_size); + assert(j.buffer_size <= m_block_size); + ret = j.storage->read_impl(j.buffer, j.piece, j.offset + , j.buffer_size); - // simulates slow drives - // usleep(300); + // simulates slow drives + // usleep(300); + } break; case disk_io_job::write: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " write " << j.buffer_size << std::endl; -#endif assert(j.buffer); assert(j.buffer_size <= m_block_size); j.storage->write_impl(j.buffer, j.piece, j.offset @@ -262,25 +214,16 @@ namespace libtorrent break; case disk_io_job::hash: { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " hash" << std::endl; -#endif sha1_hash h = j.storage->hash_for_piece_impl(j.piece); j.str.resize(20); std::memcpy(&j.str[0], &h[0], 20); } break; case disk_io_job::move_storage: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " move" << std::endl; -#endif ret = j.storage->move_storage_impl(j.str) ? 1 : 0; j.str = j.storage->save_path().string(); break; case disk_io_job::release_files: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " release" << std::endl; -#endif j.storage->release_files_impl(); break; } @@ -297,7 +240,7 @@ namespace libtorrent try { if (handler) handler(ret, j); } catch (std::exception&) {} - if (j.buffer && free_buffer) + if (j.buffer) { l.lock(); m_pool.ordered_free(j.buffer); diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index 172719793..e69de29bb 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -1,142 +0,0 @@ -/* - -Copyright (c) 2007, Arvid Norberg -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#if defined __linux__ || defined __MACH__ -#include -#include -#include -#endif - -#include "libtorrent/enum_net.hpp" - -namespace libtorrent -{ - std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) - { - static std::vector
ret; - if (!ret.empty()) return ret; - -#if defined __linux__ || defined __MACH__ || defined(__FreeBSD__) - int s = socket(AF_INET, SOCK_DGRAM, 0); - if (s < 0) - { - ec = asio::error::fault; - return ret; - } - ifconf ifc; - char buf[1024]; - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) - { - close(s); - ec = asio::error::fault; - return ret; - } - close(s); - - char *ifr = (char*)ifc.ifc_req; - int remaining = ifc.ifc_len; - - while (remaining) - { - ifreq const& item = *reinterpret_cast(ifr); - if (item.ifr_addr.sa_family == AF_INET) - { - typedef asio::ip::address_v4::bytes_type bytes_t; - bytes_t b; - memcpy(&b[0], &((sockaddr_in const*)&item.ifr_addr)->sin_addr, b.size()); - ret.push_back(address_v4(b)); - } - else if (item.ifr_addr.sa_family == AF_INET6) - { - typedef asio::ip::address_v6::bytes_type bytes_t; - bytes_t b; - memcpy(&b[0], &((sockaddr_in6 const*)&item.ifr_addr)->sin6_addr, b.size()); - ret.push_back(address_v6(b)); - } - -#if defined __MACH__ || defined(__FreeBSD__) - int current_size = item.ifr_addr.sa_len + IFNAMSIZ; -#elif defined __linux__ - int current_size = sizeof(ifreq); -#endif - ifr += current_size; - remaining -= current_size; - } - -#elif defined WIN32 - - SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); - if (s == SOCKET_ERROR) - { - ec = asio::error::fault; - return ret; - } - - INTERFACE_INFO buffer[30]; - DWORD size; - - if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, - sizeof(buffer), &size, 0, 0) != 0) - { - closesocket(s); - ec = asio::error::fault; - return ret; - } - closesocket(s); - - int n = size / sizeof(INTERFACE_INFO); - - for (int i = 0; i < n; ++i) - { - sockaddr_in *sockaddr = (sockaddr_in*)&buffer[i].iiAddress; - address a(address::from_string(inet_ntoa(sockaddr->sin_addr))); - if (a == address_v4::any()) continue; - ret.push_back(a); - } - -#else - // make a best guess of the interface we're using and its IP - udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); - for (;i != udp::resolver_iterator(); ++i) - { - ret.push_back(i->endpoint().address()); - } -#endif - return ret; - } - -} - - diff --git a/libtorrent/src/escape_string.cpp b/libtorrent/src/escape_string.cpp index faff3de95..02a4fa085 100755 --- a/libtorrent/src/escape_string.cpp +++ b/libtorrent/src/escape_string.cpp @@ -33,14 +33,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include +#include #include #include #include #include #include -#include "libtorrent/assert.hpp" - namespace libtorrent { std::string unescape_string(std::string const& s) diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 72876d528..3d568d1f7 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -247,16 +247,19 @@ namespace libtorrent void set_size(size_type s) { -#ifdef _WIN32 -#error file.cpp is for posix systems only. use file_win.cpp on windows -#else - if (ftruncate(m_fd, s) < 0) + size_type pos = tell(); + // Only set size if current file size not equals s. + // 2 as "m" argument is to be sure seek() sets SEEK_END on + // all compilers. + if(s != seek(0, 2)) { - std::stringstream msg; - msg << "ftruncate failed: '" << strerror(errno); - throw file_error(msg.str()); + seek(s - 1); + char dummy = 0; + read(&dummy, 1); + seek(s - 1); + write(&dummy, 1); } -#endif + seek(pos); } size_type seek(size_type offset, int m = 1) diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 2b306ca6d..53798cae1 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -42,8 +42,6 @@ using boost::bind; namespace libtorrent { - enum { max_bottled_buffer = 1024 * 1024 }; - void http_connection::get(std::string const& url, time_duration timeout , bool handle_redirect) { @@ -167,7 +165,6 @@ void http_connection::on_connect(asio::error_code const& e if (!e) { m_last_receive = time_now(); - if (m_connect_handler) m_connect_handler(*this); asio::async_write(m_sock, asio::buffer(sendbuffer) , bind(&http_connection::on_write, shared_from_this(), _1)); } @@ -313,8 +310,8 @@ void http_connection::on_read(asio::error_code const& e } if (int(m_recvbuffer.size()) == m_read_pos) - m_recvbuffer.resize((std::min)(m_read_pos + 2048, int(max_bottled_buffer))); - if (m_read_pos == max_bottled_buffer) + m_recvbuffer.resize((std::min)(m_read_pos + 2048, 1024*500)); + if (m_read_pos == 1024 * 500) { close(); if (m_bottled && m_called) return; diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 0a0e59c48..936f8292a 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -148,19 +148,13 @@ namespace libtorrent pos = newline; line >> m_protocol; - if (m_protocol.substr(0, 5) == "HTTP/") + if (m_protocol.substr(0, 5) != "HTTP/") { - line >> m_status_code; - std::getline(line, m_server_message); - } - else - { - m_method = m_protocol; - std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower); - m_protocol.clear(); - line >> m_path >> m_protocol; - m_status_code = 0; + throw std::runtime_error("unknown protocol in HTTP response: " + + m_protocol + " line: " + std::string(pos, newline)); } + line >> m_status_code; + std::getline(line, m_server_message); m_state = read_header; } @@ -256,7 +250,7 @@ namespace libtorrent assert(m_state == read_body); if (m_content_length >= 0) return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos - , m_recv_buffer.begin + (std::min)(m_recv_pos + , m_recv_buffer.begin + std::min(m_recv_pos , m_body_start_pos + m_content_length)); else return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos @@ -414,7 +408,7 @@ namespace libtorrent { m_send_buffer += "numwant="; m_send_buffer += boost::lexical_cast( - (std::min)(req.num_want, 999)); + std::min(req.num_want, 999)); m_send_buffer += '&'; } if (m_settings.announce_ip != address() && !url_has_argument(request, "ip")) @@ -465,16 +459,14 @@ namespace libtorrent m_send_buffer += "\r\n\r\n"; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - - boost::shared_ptr cb = requester(); - if (cb) + if (has_requester()) { - cb->debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); + requester().debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); std::stringstream info_hash_str; info_hash_str << req.info_hash; - cb->debug_log("info_hash: " + requester().debug_log("info_hash: " + boost::lexical_cast(req.info_hash)); - cb->debug_log("name lookup: " + hostname); + requester().debug_log("name lookup: " + hostname); } #endif @@ -499,9 +491,8 @@ namespace libtorrent void http_tracker_connection::name_lookup(asio::error_code const& error , tcp::resolver::iterator i) try { - boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("tracker name lookup handler called"); + if (has_requester()) requester().debug_log("tracker name lookup handler called"); #endif if (error == asio::error::operation_aborted) return; if (m_timed_out) return; @@ -513,7 +504,7 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("tracker name lookup successful"); + if (has_requester()) requester().debug_log("tracker name lookup successful"); #endif restart_read_timeout(); @@ -528,11 +519,11 @@ namespace libtorrent if (target == end) { assert(target_address.address().is_v4() != bind_interface().is_v4()); - if (cb) + if (has_requester()) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - cb->tracker_warning("the tracker only resolves to an " + requester().tracker_warning("the tracker only resolves to an " + tracker_address_type + " address, and you're listening on an " + bind_address_type + " socket. This may prevent you from receiving incoming connections."); } @@ -542,7 +533,7 @@ namespace libtorrent target_address = *target; } - if (cb) cb->m_tracker_address = target_address; + if (has_requester()) requester().m_tracker_address = target_address; m_socket = instantiate_connection(m_name_lookup.io_service(), m_proxy); if (m_proxy.type == proxy_settings::http @@ -583,8 +574,7 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) cb->debug_log("tracker connection successful"); + if (has_requester()) requester().debug_log("tracker connection successful"); #endif restart_read_timeout(); @@ -608,8 +598,7 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) cb->debug_log("tracker send data completed"); + if (has_requester()) requester().debug_log("tracker send data completed"); #endif restart_read_timeout(); assert(m_buffer.size() - m_recv_pos > 0); @@ -645,8 +634,7 @@ namespace libtorrent restart_read_timeout(); assert(bytes_transferred > 0); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) cb->debug_log("tracker connection reading " + if (has_requester()) requester().debug_log("tracker connection reading " + boost::lexical_cast(bytes_transferred)); #endif @@ -712,8 +700,6 @@ namespace libtorrent } std::string location = m_parser.header("location"); - - boost::shared_ptr cb = requester(); if (m_parser.status_code() >= 300 && m_parser.status_code() < 400) { @@ -734,9 +720,9 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("Redirecting to \"" + location + "\""); + if (has_requester()) requester().debug_log("Redirecting to \"" + location + "\""); #endif - if (cb) cb->tracker_warning("Redirecting to \"" + location + "\""); + if (has_requester()) requester().tracker_warning("Redirecting to \"" + location + "\""); tracker_request req = tracker_req(); req.url = location; @@ -759,18 +745,20 @@ namespace libtorrent std::string content_encoding = m_parser.header("content-encoding"); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("content-encoding: \"" + content_encoding + "\""); + if (has_requester()) requester().debug_log("content-encoding: \"" + content_encoding + "\""); #endif if (content_encoding == "gzip" || content_encoding == "x-gzip") { - if (!cb) + boost::shared_ptr r = m_requester.lock(); + + if (!r) { close(); return; } m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_parser.body_start()); - if (inflate_gzip(m_buffer, tracker_request(), cb.get(), + if (inflate_gzip(m_buffer, tracker_request(), r.get(), m_settings.tracker_maximum_response_length)) { close(); @@ -847,8 +835,7 @@ namespace libtorrent void http_tracker_connection::parse(entry const& e) { - boost::shared_ptr cb = requester(); - if (!cb) return; + if (!has_requester()) return; try { @@ -865,7 +852,8 @@ namespace libtorrent try { entry const& warning = e["warning message"]; - cb->tracker_warning(warning.string()); + if (has_requester()) + requester().tracker_warning(warning.string()); } catch(type_error const&) {} @@ -879,7 +867,7 @@ namespace libtorrent entry scrape_data = e["files"][ih]; int complete = scrape_data["complete"].integer(); int incomplete = scrape_data["incomplete"].integer(); - cb->tracker_response(tracker_request(), peer_list, 0, complete + requester().tracker_response(tracker_request(), peer_list, 0, complete , incomplete); return; } @@ -896,7 +884,12 @@ namespace libtorrent peer_entry p; p.pid.clear(); - p.ip = detail::read_v4_address(i).to_string(); + std::stringstream ip_str; + ip_str << (int)detail::read_uint8(i) << "."; + ip_str << (int)detail::read_uint8(i) << "."; + ip_str << (int)detail::read_uint8(i) << "."; + ip_str << (int)detail::read_uint8(i); + p.ip = ip_str.str(); p.port = detail::read_uint16(i); peer_list.push_back(p); } @@ -911,22 +904,6 @@ namespace libtorrent } } - if (entry const* ipv6_peers = e.find_key("peers6")) - { - std::string const& peers = ipv6_peers->string(); - for (std::string::const_iterator i = peers.begin(); - i != peers.end();) - { - if (std::distance(i, peers.end()) < 18) break; - - peer_entry p; - p.pid.clear(); - p.ip = detail::read_v6_address(i).to_string(); - p.port = detail::read_uint16(i); - peer_list.push_back(p); - } - } - // look for optional scrape info int complete = -1; int incomplete = -1; @@ -937,16 +914,16 @@ namespace libtorrent try { incomplete = e["incomplete"].integer(); } catch(type_error&) {} - cb->tracker_response(tracker_request(), peer_list, interval, complete + requester().tracker_response(tracker_request(), peer_list, interval, complete , incomplete); } catch(type_error& e) { - cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); } catch(std::runtime_error& e) { - cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); } } diff --git a/libtorrent/src/identify_client.cpp b/libtorrent/src/identify_client.cpp index 7fa808f20..26ddb51dc 100755 --- a/libtorrent/src/identify_client.cpp +++ b/libtorrent/src/identify_client.cpp @@ -184,7 +184,6 @@ namespace , {"SB", "Swiftbit"} , {"SN", "ShareNet"} , {"SS", "SwarmScope"} - , {"ST", "SymTorrent"} , {"SZ", "Shareaza"} , {"S~", "Shareaza (beta)"} , {"T", "BitTornado"} @@ -195,57 +194,12 @@ namespace , {"U", "UPnP"} , {"UL", "uLeecher"} , {"UT", "uTorrent"} - , {"XL", "Xunlei"} , {"XT", "XanTorrent"} , {"XX", "Xtorrent"} , {"ZT", "ZipTorrent"} , {"lt", "rTorrent"} , {"pX", "pHoeniX"} , {"qB", "qBittorrent"} - , {"st", "SharkTorrent"} - }; - - struct generic_map_entry - { - int offset; - char const* id; - char const* name; - }; - // non-standard names - generic_map_entry generic_mappings[] = - { - {0, "Deadman Walking-", "Deadman"} - , {5, "Azureus", "Azureus 2.0.3.2"} - , {0, "DansClient", "XanTorrent"} - , {4, "btfans", "SimpleBT"} - , {0, "PRC.P---", "Bittorrent Plus! II"} - , {0, "P87.P---", "Bittorrent Plus!"} - , {0, "S587Plus", "Bittorrent Plus!"} - , {0, "martini", "Martini Man"} - , {0, "Plus---", "Bittorrent Plus"} - , {0, "turbobt", "TurboBT"} - , {0, "a00---0", "Swarmy"} - , {0, "a02---0", "Swarmy"} - , {0, "T00---0", "Teeweety"} - , {0, "BTDWV-", "Deadman Walking"} - , {2, "BS", "BitSpirit"} - , {0, "Pando-", "Pando"} - , {0, "LIME", "LimeWire"} - , {0, "btuga", "BTugaXP"} - , {0, "oernu", "BTugaXP"} - , {0, "Mbrst", "Burst!"} - , {0, "PEERAPP", "PeerApp"} - , {0, "Plus", "Plus!"} - , {0, "-Qt-", "Qt"} - , {0, "exbc", "BitComet"} - , {0, "DNA", "BitTorrent DNA"} - , {0, "-G3", "G3 Torrent"} - , {0, "-FG", "FlashGet"} - , {0, "-ML", "MLdonkey"} - , {0, "XBT", "XBT"} - , {0, "OP", "Opera"} - , {2, "RS", "Rufus"} - , {0, "AZ2500BT", "BitTyrant"} }; bool compare_id(map_entry const& lhs, map_entry const& rhs) @@ -327,13 +281,30 @@ namespace libtorrent // non standard encodings // ---------------------- - int num_generic_mappings = sizeof(generic_mappings) / sizeof(generic_mappings[0]); - - for (int i = 0; i < num_generic_mappings; ++i) - { - generic_map_entry const& e = generic_mappings[i]; - if (find_string(PID + e.offset, e.id)) return e.name; - } + if (find_string(PID, "Deadman Walking-")) return "Deadman"; + if (find_string(PID + 5, "Azureus")) return "Azureus 2.0.3.2"; + if (find_string(PID, "DansClient")) return "XanTorrent"; + if (find_string(PID + 4, "btfans")) return "SimpleBT"; + if (find_string(PID, "PRC.P---")) return "Bittorrent Plus! II"; + if (find_string(PID, "P87.P---")) return "Bittorrent Plus!"; + if (find_string(PID, "S587Plus")) return "Bittorrent Plus!"; + if (find_string(PID, "martini")) return "Martini Man"; + if (find_string(PID, "Plus---")) return "Bittorrent Plus"; + if (find_string(PID, "turbobt")) return "TurboBT"; + if (find_string(PID, "a00---0")) return "Swarmy"; + if (find_string(PID, "a02---0")) return "Swarmy"; + if (find_string(PID, "T00---0")) return "Teeweety"; + if (find_string(PID, "BTDWV-")) return "Deadman Walking"; + if (find_string(PID + 2, "BS")) return "BitSpirit"; + if (find_string(PID, "btuga")) return "BTugaXP"; + if (find_string(PID, "oernu")) return "BTugaXP"; + if (find_string(PID, "Mbrst")) return "Burst!"; + if (find_string(PID, "Plus")) return "Plus!"; + if (find_string(PID, "-Qt-")) return "Qt"; + if (find_string(PID, "exbc")) return "BitComet"; + if (find_string(PID, "-G3")) return "G3 Torrent"; + if (find_string(PID, "XBT")) return "XBT"; + if (find_string(PID, "OP")) return "Opera"; if (find_string(PID, "-BOW") && PID[7] == '-') return "Bits on Wheels " + std::string(PID + 4, PID + 7); diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp index a3849ed69..0c7d9d276 100644 --- a/libtorrent/src/kademlia/closest_nodes.cpp +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -35,7 +35,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include "libtorrent/assert.hpp" namespace libtorrent { namespace dht { diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index c9908a163..eda6cd864 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -237,7 +237,6 @@ namespace libtorrent { namespace dht try { if (e) return; - if (!m_socket.is_open()) return; time_duration d = m_dht.connection_timeout(); m_connection_timer.expires_from_now(d); m_connection_timer.async_wait(m_strand.wrap(bind(&dht_tracker::connection_timeout, self(), _1))); @@ -255,7 +254,6 @@ namespace libtorrent { namespace dht try { if (e) return; - if (!m_socket.is_open()) return; time_duration d = m_dht.refresh_timeout(); m_refresh_timer.expires_from_now(d); m_refresh_timer.async_wait(m_strand.wrap( @@ -278,9 +276,8 @@ namespace libtorrent { namespace dht try { if (e) return; - if (!m_socket.is_open()) return; m_timer.expires_from_now(minutes(tick_period)); - m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, self(), _1))); + m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, this, _1))); ptime now = time_now(); if (now - m_last_new_key > minutes(key_refresh)) @@ -391,7 +388,6 @@ namespace libtorrent { namespace dht try { if (error == asio::error::operation_aborted) return; - if (!m_socket.is_open()) return; int current_buffer = m_buffer; m_buffer = (m_buffer + 1) & 1; @@ -720,7 +716,6 @@ namespace libtorrent { namespace dht , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; - if (!m_socket.is_open()) return; add_node(host->endpoint()); } catch (std::exception&) @@ -739,7 +734,6 @@ namespace libtorrent { namespace dht , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; - if (!m_socket.is_open()) return; m_dht.add_router_node(host->endpoint()); } catch (std::exception&) diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp index ad06c515d..4ed413714 100644 --- a/libtorrent/src/kademlia/node_id.cpp +++ b/libtorrent/src/kademlia/node_id.cpp @@ -34,10 +34,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include "libtorrent/kademlia/node_id.hpp" -#include "libtorrent/assert.hpp" using boost::bind; diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index d7590ec47..76f25548d 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -35,7 +35,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/io.hpp" #include "libtorrent/http_tracker_connection.hpp" - #include #include #include @@ -53,22 +52,76 @@ namespace libtorrent address_v4 guess_local_address(asio::io_service&); } +address_v4 lsd::lsd_multicast_address; +udp::endpoint lsd::lsd_multicast_endpoint; + lsd::lsd(io_service& ios, address const& listen_interface , peer_callback_t const& cb) : m_callback(cb) , m_retry_count(0) - , m_socket(ios, udp::endpoint(address_v4::from_string("239.192.152.143"), 6771) - , bind(&lsd::on_announce, this, _1, _2, _3)) + , m_socket(ios) , m_broadcast_timer(ios) , m_disabled(false) { + // Bittorrent Local discovery multicast address and port + lsd_multicast_address = address_v4::from_string("239.192.152.143"); + lsd_multicast_endpoint = udp::endpoint(lsd_multicast_address, 6771); + #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log.open("lsd.log", std::ios::in | std::ios::out | std::ios::trunc); #endif + assert(lsd_multicast_address.is_multicast()); + rebind(listen_interface); } lsd::~lsd() {} +void lsd::rebind(address const& listen_interface) +{ + address_v4 local_ip = address_v4::any(); + if (listen_interface.is_v4() && listen_interface != address_v4::any()) + { + local_ip = listen_interface.to_v4(); + } + + try + { + // the local interface hasn't changed + if (m_socket.is_open() + && m_socket.local_endpoint().address() == local_ip) + return; + + m_socket.close(); + + using namespace asio::ip::multicast; + + m_socket.open(udp::v4()); + m_socket.set_option(datagram_socket::reuse_address(true)); + m_socket.bind(udp::endpoint(local_ip, 6771)); + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "local ip: " << local_ip << std::endl; +#endif + + m_socket.set_option(join_group(lsd_multicast_address)); + m_socket.set_option(outbound_interface(local_ip)); + m_socket.set_option(enable_loopback(true)); + m_socket.set_option(hops(255)); + } + catch (std::exception& e) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "socket multicast error " << e.what() + << ". disabling local service discovery" << std::endl; +#endif + m_disabled = true; + return; + } + m_disabled = false; + + setup_receive(); +} + void lsd::announce(sha1_hash const& ih, int listen_port) { if (m_disabled) return; @@ -83,7 +136,8 @@ void lsd::announce(sha1_hash const& ih, int listen_port) m_retry_count = 0; asio::error_code ec; - m_socket.send(msg.c_str(), int(msg.size()), ec); + m_socket.send_to(asio::buffer(msg.c_str(), msg.size() - 1) + , lsd_multicast_endpoint, 0, ec); if (ec) { m_disabled = true; @@ -103,8 +157,8 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try { if (e) return; - asio::error_code ec; - m_socket.send(msg.c_str(), int(msg.size()), ec); + m_socket.send_to(asio::buffer(msg, msg.size() - 1) + , lsd_multicast_endpoint); ++m_retry_count; if (m_retry_count >= 5) @@ -116,13 +170,14 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try catch (std::exception&) {} -void lsd::on_announce(udp::endpoint const& from, char* buffer +void lsd::on_announce(asio::error_code const& e , std::size_t bytes_transferred) { using namespace libtorrent::detail; + if (e) return; - char* p = buffer; - char* end = buffer + bytes_transferred; + char* p = m_receive_buffer; + char* end = m_receive_buffer + bytes_transferred; char* line = std::find(p, end, '\n'); for (char* i = p; i < line; ++i) *i = std::tolower(*i); #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) @@ -135,6 +190,7 @@ void lsd::on_announce(udp::endpoint const& from, char* buffer m_log << time_now_string() << " *** assumed 'bt-search', ignoring" << std::endl; #endif + setup_receive(); return; } p = line + 1; @@ -167,15 +223,25 @@ void lsd::on_announce(udp::endpoint const& from, char* buffer { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log << time_now_string() - << " *** incoming local announce " << from.address() + << " *** incoming local announce " << m_remote.address() << ":" << port << " ih: " << ih << std::endl; #endif // we got an announce, pass it on through the callback - try { m_callback(tcp::endpoint(from.address(), port), ih); } + try { m_callback(tcp::endpoint(m_remote.address(), port), ih); } catch (std::exception&) {} } + setup_receive(); } +void lsd::setup_receive() try +{ + assert(m_socket.is_open()); + m_socket.async_receive_from(asio::buffer(m_receive_buffer + , sizeof(m_receive_buffer)), m_remote, bind(&lsd::on_announce, this, _1, _2)); +} +catch (std::exception&) +{} + void lsd::close() { m_socket.close(); diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index 0623b156f..97635cdb9 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -523,7 +523,7 @@ namespace libtorrent { namespace if (num_blocks < 1) num_blocks = 1; assert(num_blocks <= 128); - int min_element = (std::numeric_limits::max)(); + int min_element = std::numeric_limits::max(); int best_index = 0; for (int i = 0; i < 256 - num_blocks + 1; ++i) { @@ -556,7 +556,7 @@ namespace libtorrent { namespace namespace libtorrent { - boost::shared_ptr create_metadata_plugin(torrent* t, void*) + boost::shared_ptr create_metadata_plugin(torrent* t) { return boost::shared_ptr(new metadata_plugin(*t)); } diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index bdcabce9a..0a5932a56 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -32,13 +32,11 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" +#include +#include #include #include -#include "libtorrent/natpmp.hpp" -#include "libtorrent/io.hpp" -#include "libtorrent/assert.hpp" - using boost::bind; using namespace libtorrent; diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index 981eca63d..e999473da 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -1,131 +1,132 @@ -/* - -Copyright (c) 2007, Un Shyam -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#ifndef TORRENT_DISABLE_ENCRYPTION - -#include - -#include -#include - -#include "libtorrent/pe_crypto.hpp" -#include "libtorrent/assert.hpp" - -namespace libtorrent { - - - // Set the prime P and the generator, generate local public key - DH_key_exchange::DH_key_exchange () - { - m_DH = DH_new (); - - m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); - m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); - m_DH->length = 160l; - - assert (sizeof(m_dh_prime) == DH_size(m_DH)); - - DH_generate_key (m_DH); // TODO Check != 0 - - assert (m_DH->pub_key); - - // DH can generate key sizes that are smaller than the size of - // P with exponentially decreasing probability, in which case - // the msb's of m_dh_local_key need to be zeroed - // appropriately. - int key_size = get_local_key_size(); - int len_dh = sizeof(m_dh_prime); // must equal DH_size(m_DH) - if (key_size != len_dh) - { - assert(key_size > 0 && key_size < len_dh); - - int pad_zero_size = len_dh - key_size; - std::fill(m_dh_local_key, m_dh_local_key + pad_zero_size, 0); - BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key + pad_zero_size); - } - else - BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key); // TODO Check return value - } - - DH_key_exchange::~DH_key_exchange () - { - assert (m_DH); - DH_free (m_DH); - } - - char const* DH_key_exchange::get_local_key () const - { - return m_dh_local_key; - } - - - // compute shared secret given remote public key - void DH_key_exchange::compute_secret (char const* remote_pubkey) - { - assert (remote_pubkey); - BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); - char dh_secret[96]; - - int secret_size = DH_compute_key ( (unsigned char*)dh_secret, - bn_remote_pubkey, m_DH); // TODO Check for errors - - if (secret_size != 96) - { - assert(secret_size < 96 && secret_size > 0); - std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); - } - std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); - - BN_free (bn_remote_pubkey); - } - - char const* DH_key_exchange::get_secret () const - { - return m_dh_secret; - } - - const unsigned char DH_key_exchange::m_dh_prime[96] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, - 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, - 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, - 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, - 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, - 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, - 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, - 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 - }; - - const unsigned char DH_key_exchange::m_dh_generator[1] = { 2 }; - -} // namespace libtorrent - -#endif // #ifndef TORRENT_DISABLE_ENCRYPTION +/* + +Copyright (c) 2007, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_ENCRYPTION + +#include +#include + +#include +#include + +#include "libtorrent/pe_crypto.hpp" + +namespace libtorrent { + + + // Set the prime P and the generator, generate local public key + DH_key_exchange::DH_key_exchange () + { + m_DH = DH_new (); + + m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); + m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); + m_DH->length = 160l; + + assert (sizeof(m_dh_prime) == DH_size(m_DH)); + + DH_generate_key (m_DH); // TODO Check != 0 + + assert (m_DH->pub_key); + + // DH can generate key sizes that are smaller than the size of + // P with exponentially decreasing probability, in which case + // the msb's of m_dh_local_key need to be zeroed + // appropriately. + int key_size = get_local_key_size(); + int len_dh = sizeof(m_dh_prime); // must equal DH_size(m_DH) + if (key_size != len_dh) + { + assert(key_size > 0 && key_size < len_dh); + + int pad_zero_size = len_dh - key_size; + std::fill(m_dh_local_key, m_dh_local_key + pad_zero_size, 0); + BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key + pad_zero_size); + } + else + BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key); // TODO Check return value + } + + DH_key_exchange::~DH_key_exchange () + { + assert (m_DH); + DH_free (m_DH); + } + + char const* DH_key_exchange::get_local_key () const + { + return m_dh_local_key; + } + + + // compute shared secret given remote public key + void DH_key_exchange::compute_secret (char const* remote_pubkey) + { + assert (remote_pubkey); + BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); + char dh_secret[96]; + + int secret_size = DH_compute_key ( (unsigned char*)dh_secret, + bn_remote_pubkey, m_DH); // TODO Check for errors + + if (secret_size != 96) + { + assert(secret_size < 96 && secret_size > 0); + std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); + } + std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); + + BN_free (bn_remote_pubkey); + } + + char const* DH_key_exchange::get_secret () const + { + return m_dh_secret; + } + + const unsigned char DH_key_exchange::m_dh_prime[96] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 + }; + + const unsigned char DH_key_exchange::m_dh_generator[1] = { 2 }; + +} // namespace libtorrent + +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION + diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 25bc0ba63..b73e32896 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -51,7 +51,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" -#include "libtorrent/assert.hpp" using boost::bind; using boost::shared_ptr; @@ -77,8 +76,6 @@ namespace libtorrent , m_timeout(m_ses.settings().peer_timeout) , m_last_piece(time_now()) , m_last_request(time_now()) - , m_last_incoming_request(min_time()) - , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) , m_current_send_buffer(0) @@ -96,7 +93,6 @@ namespace libtorrent , m_choked(true) , m_failed(false) , m_ignore_bandwidth_limits(false) - , m_have_all(false) , m_num_pieces(0) , m_desired_queue_size(2) , m_free_upload(0) @@ -112,8 +108,8 @@ namespace libtorrent , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_non_prioritized(false) - , m_upload_limit(bandwidth_limit::inf) - , m_download_limit(bandwidth_limit::inf) + , m_upload_limit(resource_request::inf) + , m_download_limit(resource_request::inf) , m_peer_info(peerinfo) , m_speed(slow) , m_connection_ticket(-1) @@ -157,8 +153,6 @@ namespace libtorrent , m_timeout(m_ses.settings().peer_timeout) , m_last_piece(time_now()) , m_last_request(time_now()) - , m_last_incoming_request(min_time()) - , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) , m_current_send_buffer(0) @@ -174,7 +168,6 @@ namespace libtorrent , m_choked(true) , m_failed(false) , m_ignore_bandwidth_limits(false) - , m_have_all(false) , m_num_pieces(0) , m_desired_queue_size(2) , m_free_upload(0) @@ -190,11 +183,10 @@ namespace libtorrent , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_non_prioritized(false) - , m_upload_limit(bandwidth_limit::inf) - , m_download_limit(bandwidth_limit::inf) + , m_upload_limit(resource_request::inf) + , m_download_limit(resource_request::inf) , m_peer_info(peerinfo) , m_speed(slow) - , m_connection_ticket(-1) , m_remote_bytes_dled(0) , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) @@ -259,67 +251,6 @@ namespace libtorrent } #endif - void peer_connection::send_allowed_set() - { - INVARIANT_CHECK; - - boost::shared_ptr t = m_torrent.lock(); - assert(t); - - int num_allowed_pieces = m_ses.settings().allowed_fast_set_size; - int num_pieces = t->torrent_file().num_pieces(); - - if (num_allowed_pieces >= num_pieces) - { - for (int i = 0; i < num_pieces; ++i) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " ==> ALLOWED_FAST [ " << i << " ]\n"; -#endif - write_allow_fast(i); - m_accept_fast.insert(i); - } - return; - } - - std::string x; - address const& addr = m_remote.address(); - if (addr.is_v4()) - { - address_v4::bytes_type bytes = addr.to_v4().to_bytes(); - x.assign((char*)&bytes[0], bytes.size()); - } - else - { - address_v6::bytes_type bytes = addr.to_v6().to_bytes(); - x.assign((char*)&bytes[0], bytes.size()); - } - x.append((char*)&t->torrent_file().info_hash()[0], 20); - - sha1_hash hash = hasher(&x[0], x.size()).final(); - for (;;) - { - char* p = (char*)&hash[0]; - for (int i = 0; i < 5; ++i) - { - int piece = detail::read_uint32(p) % num_pieces; - if (m_accept_fast.find(piece) == m_accept_fast.end()) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " ==> ALLOWED_FAST [ " << piece << " ]\n"; -#endif - write_allow_fast(piece); - m_accept_fast.insert(piece); - if (int(m_accept_fast.size()) >= num_allowed_pieces - || int(m_accept_fast.size()) == num_pieces) return; - } - } - hash = hasher((char*)&hash[0], 20).final(); - } - } - void peer_connection::init() { INVARIANT_CHECK; @@ -329,7 +260,7 @@ namespace libtorrent assert(t->valid_metadata()); assert(t->ready_for_connections()); - m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); + m_have_piece.resize(t->torrent_file().num_pieces(), false); // now that we have a piece_picker, // update it with this peers pieces @@ -343,7 +274,7 @@ namespace libtorrent // if this is a web seed. we don't have a peer_info struct if (m_peer_info) m_peer_info->seed = true; // if we're a seed too, disconnect - if (t->is_finished()) + if (t->is_seed()) { throw std::runtime_error("seed to seed connection redundant, disconnecting"); } @@ -400,12 +331,7 @@ namespace libtorrent { // dont announce during handshake if (in_handshake()) return; - - // remove suggested pieces that we have - std::vector::iterator i = std::find( - m_suggested_pieces.begin(), m_suggested_pieces.end(), index); - if (i != m_suggested_pieces.end()) m_suggested_pieces.erase(i); - + // optimization, don't send have messages // to peers that already have the piece if (!m_ses.settings().send_redundant_have @@ -452,6 +378,8 @@ namespace libtorrent void peer_connection::add_stat(size_type downloaded, size_type uploaded) { + INVARIANT_CHECK; + m_statistics.add_stat(downloaded, uploaded); } @@ -521,7 +449,6 @@ namespace libtorrent assert(t); assert(t->valid_metadata()); - torrent_info const& ti = t->torrent_file(); return p.piece >= 0 && p.piece < t->torrent_file().num_pieces() @@ -529,30 +456,35 @@ namespace libtorrent && p.start >= 0 && (p.length == t->block_size() || (p.length < t->block_size() - && p.piece == ti.num_pieces()-1 - && p.start + p.length == ti.piece_size(p.piece)) + && p.piece == t->torrent_file().num_pieces()-1 + && p.start + p.length == t->torrent_file().piece_size(p.piece)) || (m_request_large_blocks - && p.length <= ti.piece_length() * m_prefer_whole_pieces == 0 ? - 1 : m_prefer_whole_pieces)) - && p.piece * size_type(ti.piece_length()) + p.start + p.length - <= ti.total_size() + && p.length <= t->torrent_file().piece_size(p.piece))) + && p.start + p.length <= t->torrent_file().piece_size(p.piece) && (p.start % t->block_size() == 0); } - + + struct disconnect_torrent + { + disconnect_torrent(boost::weak_ptr& t): m_t(&t) {} + ~disconnect_torrent() { if (m_t) m_t->reset(); } + void cancel() { m_t = 0; } + private: + boost::weak_ptr* m_t; + }; + void peer_connection::attach_to_torrent(sha1_hash const& ih) { INVARIANT_CHECK; assert(!m_disconnecting); - assert(m_torrent.expired()); - boost::weak_ptr wpt = m_ses.find_torrent(ih); - boost::shared_ptr t = wpt.lock(); + m_torrent = m_ses.find_torrent(ih); + + boost::shared_ptr t = m_torrent.lock(); if (t && t->is_aborted()) { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " *** the torrent has been aborted\n"; -#endif + m_torrent.reset(); t.reset(); } @@ -560,18 +492,12 @@ namespace libtorrent { // we couldn't find the torrent! #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " *** couldn't find a torrent with the given info_hash: " << ih << "\n"; - (*m_logger) << " torrents:\n"; - session_impl::torrent_map const& torrents = m_ses.m_torrents; - for (session_impl::torrent_map::const_iterator i = torrents.begin() - , end(torrents.end()); i != end; ++i) - { - (*m_logger) << " " << i->second->torrent_file().info_hash() << "\n"; - } + (*m_logger) << " couldn't find a torrent with the given info_hash: " << ih << "\n"; #endif throw std::runtime_error("got info-hash that is not in our session"); } + disconnect_torrent disconnect(m_torrent); if (t->is_paused()) { // paused torrents will not accept @@ -582,27 +508,21 @@ namespace libtorrent throw std::runtime_error("connection rejected by paused torrent"); } - assert(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. t->attach_peer(this); - m_torrent = wpt; - - assert(!m_torrent.expired()); // if the torrent isn't ready to accept // connections yet, we'll have to wait with // our initialization if (t->ready_for_connections()) init(); - assert(!m_torrent.expired()); - // assume the other end has no pieces // if we don't have valid metadata yet, // leave the vector unallocated assert(m_num_pieces == 0); std::fill(m_have_piece.begin(), m_have_piece.end(), false); - assert(!m_torrent.expired()); + disconnect.cancel(); } // message handlers @@ -668,117 +588,6 @@ namespace libtorrent m_request_queue.clear(); } - bool match_request(peer_request const& r, piece_block const& b, int block_size) - { - if (b.piece_index != r.piece) return false; - if (b.block_index != r.start / block_size) return false; - if (r.start % block_size != 0) return false; - return true; - } - - // ----------------------------- - // -------- REJECT PIECE ------- - // ----------------------------- - - void peer_connection::incoming_reject_request(peer_request const& r) - { - INVARIANT_CHECK; - - boost::shared_ptr t = m_torrent.lock(); - assert(t); - -#ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { - if ((*i)->on_reject(r)) return; - } -#endif - - std::deque::iterator i = std::find_if( - m_download_queue.begin(), m_download_queue.end() - , bind(match_request, boost::cref(r), _1, t->block_size())); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " <== REJECT_PIECE [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; -#endif - - piece_block b(-1, 0); - if (i != m_download_queue.end()) - { - b = *i; - m_download_queue.erase(i); - } - else - { - i = std::find_if(m_request_queue.begin(), m_request_queue.end() - , bind(match_request, boost::cref(r), _1, t->block_size())); - - if (i != m_request_queue.end()) - { - b = *i; - m_request_queue.erase(i); - } - } - - if (b.piece_index != -1 && !t->is_seed()) - { - piece_picker& p = t->picker(); - p.abort_download(b); - } -#ifdef TORRENT_VERBOSE_LOGGING - else - { - (*m_logger) << time_now_string() - << " *** PIECE NOT IN REQUEST QUEUE\n"; - } -#endif - if (m_request_queue.empty()) - { - if (m_download_queue.size() < 2) - { - request_a_block(*t, *this); - } - send_block_requests(); - } - } - - // ----------------------------- - // -------- REJECT PIECE ------- - // ----------------------------- - - void peer_connection::incoming_suggest(int index) - { - INVARIANT_CHECK; - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " <== SUGGEST_PIECE [ piece: " << index << " ]\n"; -#endif - boost::shared_ptr t = m_torrent.lock(); - if (!t) return; - -#ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { - if ((*i)->on_suggest(index)) return; - } -#endif - - if (t->have_piece(index)) return; - - if (m_suggested_pieces.size() > 9) - m_suggested_pieces.erase(m_suggested_pieces.begin()); - m_suggested_pieces.push_back(index); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " ** SUGGEST_PIECE [ piece: " << index << " added to set: " << m_suggested_pieces.size() << " ]\n"; -#endif - } - // ----------------------------- // ---------- UNCHOKE ---------- // ----------------------------- @@ -933,7 +742,7 @@ namespace libtorrent { assert(m_peer_info); m_peer_info->seed = true; - if (t->is_finished()) + if (t->is_seed()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -989,12 +798,11 @@ namespace libtorrent { m_have_piece = bitfield; m_num_pieces = std::count(bitfield.begin(), bitfield.end(), true); - if (m_peer_info) m_peer_info->seed = (m_num_pieces == int(bitfield.size())); + + if (m_peer_info) m_peer_info->seed = true; return; } - assert(t->valid_metadata()); - int num_pieces = std::count(bitfield.begin(), bitfield.end(), true); if (num_pieces == int(m_have_piece.size())) { @@ -1004,7 +812,7 @@ namespace libtorrent // if this is a web seed. we don't have a peer_info struct if (m_peer_info) m_peer_info->seed = true; // if we're a seed too, disconnect - if (t->is_finished()) + if (t->is_seed()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -1098,7 +906,6 @@ namespace libtorrent "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif - write_reject_request(r); return; } @@ -1118,7 +925,6 @@ namespace libtorrent "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif - write_reject_request(r); return; } @@ -1141,20 +947,11 @@ namespace libtorrent #endif // if we have choked the client // ignore the request - if (m_choked && m_accept_fast.find(r.piece) == m_accept_fast.end()) - { - write_reject_request(r); -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " *** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]\n"; -#endif - } - else - { - m_requests.push_back(r); - m_last_incoming_request = time_now(); - fill_send_buffer(); - } + if (m_choked) + return; + + m_requests.push_back(r); + fill_send_buffer(); } else { @@ -1171,7 +968,6 @@ namespace libtorrent "block_limit: " << t->block_size() << " ]\n"; #endif - write_reject_request(r); ++m_num_invalid_requests; if (t->alerts().should_post(alert::debug)) @@ -1181,7 +977,7 @@ namespace libtorrent , t->get_handle() , m_remote , m_peer_id - , "peer sent an illegal piece request")); + , "peer sent an illegal piece request, ignoring")); } } } @@ -1335,8 +1131,11 @@ namespace libtorrent "request queue ***\n"; #endif t->received_redundant_data(p.length); - request_a_block(*t, *this); - send_block_requests(); + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); + } return; } @@ -1345,8 +1144,11 @@ namespace libtorrent { t->received_redundant_data(p.length); - request_a_block(*t, *this); - send_block_requests(); + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); + } return; } @@ -1403,11 +1205,15 @@ namespace libtorrent block_finished.block_index, block_finished.piece_index, "block finished")); } - if (!t->is_seed() && !m_torrent.expired()) + if (!has_peer_choked() && !t->is_seed() && !m_torrent.expired()) { // this is a free function defined in policy.cpp request_a_block(*t, *this); - send_block_requests(); + try + { + send_block_requests(); + } + catch (std::exception const&) {} } #ifndef NDEBUG @@ -1489,146 +1295,6 @@ namespace libtorrent #endif } - // ----------------------------- - // --------- HAVE ALL ---------- - // ----------------------------- - - void peer_connection::incoming_have_all() - { - INVARIANT_CHECK; - - boost::shared_ptr t = m_torrent.lock(); - assert(t); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() << " <== HAVE_ALL\n"; -#endif - -#ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { - if ((*i)->on_have_all()) return; - } -#endif - - m_have_all = true; - - if (m_peer_info) m_peer_info->seed = true; - - // if we don't have metadata yet - // just remember the bitmask - // don't update the piecepicker - // (since it doesn't exist yet) - if (!t->ready_for_connections()) - { - // TODO: this might need something more - // so that once we have the metadata - // we can construct a full bitfield - return; - } - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " *** THIS IS A SEED ***\n"; -#endif - - // if we're a seed too, disconnect - if (t->is_finished()) - throw protocol_error("seed to seed connection redundant, disconnecting"); - - assert(!m_have_piece.empty()); - std::fill(m_have_piece.begin(), m_have_piece.end(), true); - m_num_pieces = m_have_piece.size(); - - t->peer_has_all(); - if (!t->is_finished()) - t->get_policy().peer_is_interesting(*this); - } - - // ----------------------------- - // --------- HAVE NONE --------- - // ----------------------------- - - void peer_connection::incoming_have_none() - { - INVARIANT_CHECK; - - boost::shared_ptr t = m_torrent.lock(); - assert(t); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() << " <== HAVE_NONE\n"; -#endif - -#ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { - if ((*i)->on_have_none()) return; - } -#endif - - if (m_peer_info) m_peer_info->seed = false; - assert(!m_have_piece.empty() || !t->ready_for_connections()); - } - - // ----------------------------- - // ------- ALLOWED FAST -------- - // ----------------------------- - - void peer_connection::incoming_allowed_fast(int index) - { - INVARIANT_CHECK; - - boost::shared_ptr t = m_torrent.lock(); - assert(t); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() << " <== ALLOWED_FAST [ " << index << " ]\n"; -#endif - -#ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { - if ((*i)->on_allowed_fast(index)) return; - } -#endif - - // if we already have the piece, we can - // ignore this message - if (t->valid_metadata() - && t->have_piece(index)) - return; - - m_allowed_fast.push_back(index); - - // if the peer has the piece and we want - // to download it, request it - if (int(m_have_piece.size()) > index - && m_have_piece[index] - && t->has_picker() - && t->picker().piece_priority(index) > 0) - { - t->get_policy().peer_is_interesting(*this); - } - } - - std::vector const& peer_connection::allowed_fast() - { - INVARIANT_CHECK; - - boost::shared_ptr t = m_torrent.lock(); - assert(t); - - m_allowed_fast.erase(std::remove_if(m_allowed_fast.begin() - , m_allowed_fast.end(), bind(&torrent::have_piece, t, _1)) - , m_allowed_fast.end()); - - // TODO: sort the allowed fast set in priority order - return m_allowed_fast; - } - void peer_connection::add_request(piece_block const& block) { INVARIANT_CHECK; @@ -1642,11 +1308,10 @@ namespace libtorrent assert(block.block_index >= 0); assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); assert(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); - assert(!t->have_piece(block.piece_index)); piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); - char const* speedmsg = 0; + std::string speedmsg; if (speed == fast) { speedmsg = "fast"; @@ -1663,10 +1328,8 @@ namespace libtorrent state = piece_picker::slow; } - if (!t->picker().mark_as_downloading(block, peer_info_struct(), state)) - return; - - if (t->alerts().should_post(alert::info)) + t->picker().mark_as_downloading(block, peer_info_struct(), state); + if (t->alerts().should_post(alert::info)) { t->alerts().post_alert(block_downloading_alert(t->get_handle(), speedmsg, block.block_index, block.piece_index, "block downloading")); @@ -1717,7 +1380,7 @@ namespace libtorrent int block_offset = block.block_index * t->block_size(); int block_size - = (std::min)((int)t->torrent_file().piece_size(block.piece_index)-block_offset, + = std::min((int)t->torrent_file().piece_size(block.piece_index)-block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1740,8 +1403,6 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_peer_info || !m_peer_info->optimistically_unchoked); - if (m_choked) return; write_choke(); m_choked = true; @@ -1760,8 +1421,15 @@ namespace libtorrent { INVARIANT_CHECK; +#ifndef NDEBUG + // TODO: once the policy lowers the interval for optimistic + // unchoke, increase this value that interval + // this condition cannot be guaranteed since if peers disconnect + // a new one will be unchoked ignoring when it was last choked + //assert(time_now() - m_last_choke > seconds(9)); +#endif + if (!m_choked) return; - m_last_unchoke = time_now(); write_unchoke(); m_choked = false; @@ -1802,9 +1470,13 @@ namespace libtorrent { INVARIANT_CHECK; + if (has_peer_choked()) return; + boost::shared_ptr t = m_torrent.lock(); assert(t); + assert(!has_peer_choked()); + if ((int)m_download_queue.size() >= m_desired_queue_size) return; while (!m_request_queue.empty() @@ -1813,7 +1485,7 @@ namespace libtorrent piece_block block = m_request_queue.front(); int block_offset = block.block_index * t->block_size(); - int block_size = (std::min)((int)t->torrent_file().piece_size( + int block_size = std::min((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1837,29 +1509,23 @@ namespace libtorrent // blocks that are in the same piece into larger requests if (m_request_large_blocks) { - int blocks_per_piece = t->torrent_file().piece_length() / t->block_size(); - - while (!m_request_queue.empty()) + while (!m_request_queue.empty() + && m_request_queue.front().piece_index == r.piece + && m_request_queue.front().block_index == block.block_index + 1) { - // check to see if this block is connected to the previous one - // if it is, merge them, otherwise, break this merge loop - piece_block const& front = m_request_queue.front(); - if (front.piece_index * blocks_per_piece + front.block_index - != block.piece_index * blocks_per_piece + block.block_index + 1) - break; block = m_request_queue.front(); m_request_queue.pop_front(); m_download_queue.push_back(block); - +/* #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() - << " *** MERGING REQUEST ** [ " + << " *** REQUEST-QUEUE** [ " "piece: " << block.piece_index << " | " "block: " << block.block_index << " ]\n"; #endif - +*/ block_offset = block.block_index * t->block_size(); - block_size = (std::min)((int)t->torrent_file().piece_size( + block_size = std::min((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1952,6 +1618,7 @@ namespace libtorrent } t->remove_peer(this); + m_torrent.reset(); } @@ -1961,7 +1628,7 @@ namespace libtorrent void peer_connection::set_upload_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = (std::numeric_limits::max)(); + if (limit == -1) limit = resource_request::inf; if (limit < 10) limit = 10; m_upload_limit = limit; m_bandwidth_limit[upload_channel].throttle(m_upload_limit); @@ -1970,7 +1637,7 @@ namespace libtorrent void peer_connection::set_download_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = (std::numeric_limits::max)(); + if (limit == -1) limit = resource_request::inf; if (limit < 10) limit = 10; m_download_limit = limit; m_bandwidth_limit[download_channel].throttle(m_download_limit); @@ -1988,7 +1655,7 @@ namespace libtorrent // if we have an infinite ratio, just say we have downloaded // much more than we have uploaded. And we'll keep uploading. if (ratio == 0.f) - return (std::numeric_limits::max)(); + return std::numeric_limits::max(); return m_free_upload + static_cast(m_statistics.total_payload_download() * ratio) @@ -2036,9 +1703,8 @@ namespace libtorrent p.load_balancing = total_free_upload(); - p.download_queue_length = int(download_queue().size() + m_request_queue.size()); - p.target_dl_queue_length = int(desired_queue_size()); - p.upload_queue_length = int(upload_queue().size()); + p.download_queue_length = (int)download_queue().size(); + p.upload_queue_length = (int)upload_queue().size(); if (boost::optional ret = downloading_piece_progress()) { @@ -2058,7 +1724,7 @@ namespace libtorrent p.pieces = get_bitfield(); ptime now = time_now(); p.last_request = now - m_last_request; - p.last_active = now - (std::max)(m_last_sent, m_last_receive); + p.last_active = now - std::max(m_last_sent, m_last_receive); // this will set the flags so that we can update them later p.flags = 0; @@ -2071,7 +1737,6 @@ namespace libtorrent p.failcount = peer_info_struct()->failcount; p.num_hashfails = peer_info_struct()->hashfails; p.flags |= peer_info_struct()->on_parole ? peer_info::on_parole : 0; - p.flags |= peer_info_struct()->optimistically_unchoked ? peer_info::optimistic_unchoke : 0; p.remote_dl_rate = m_remote_dl_rate; } else @@ -2107,13 +1772,10 @@ namespace libtorrent if (m_packet_size >= m_recv_pos) m_recv_buffer.resize(m_packet_size); } - void peer_connection::second_tick(float tick_interval) throw() + void peer_connection::second_tick(float tick_interval) { INVARIANT_CHECK; - try - { - ptime now(time_now()); boost::shared_ptr t = m_torrent.lock(); @@ -2192,8 +1854,11 @@ namespace libtorrent m_assume_fifo = true; - request_a_block(*t, *this); - send_block_requests(); + if (!has_peer_choked()) + { + request_a_block(*t, *this); + send_block_requests(); + } } } @@ -2204,7 +1869,7 @@ namespace libtorrent // maintain the share ratio given by m_ratio // with all peers. - if (t->is_finished() || is_choked() || t->ratio() == 0.0f) + if (t->is_seed() || is_choked() || t->ratio() == 0.0f) { // if we have downloaded more than one piece more // than we have uploaded OR if we are a seed @@ -2226,14 +1891,14 @@ namespace libtorrent if (t->ratio() != 1.f) soon_downloaded = (size_type)(soon_downloaded*(double)t->ratio()); - double upload_speed_limit = (std::min)((soon_downloaded - have_uploaded + double upload_speed_limit = std::min((soon_downloaded - have_uploaded + bias) / break_even_time, double(m_upload_limit)); - upload_speed_limit = (std::min)(upload_speed_limit, - (double)(std::numeric_limits::max)()); + upload_speed_limit = std::min(upload_speed_limit, + (double)std::numeric_limits::max()); m_bandwidth_limit[upload_channel].throttle( - (std::min)((std::max)((int)upload_speed_limit, 20) + std::min(std::max((int)upload_speed_limit, 20) , m_upload_limit)); } @@ -2252,14 +1917,43 @@ namespace libtorrent } fill_send_buffer(); - } - catch (std::exception& e) +/* + size_type diff = share_diff(); + + enum { block_limit = 2 }; // how many blocks difference is considered unfair + + // if the peer has been choked, send the current piece + // as fast as possible + if (diff > block_limit*m_torrent->block_size() || m_torrent->is_seed() || is_choked()) { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "**ERROR**: " << e.what() << "\n"; -#endif - m_ses.connection_failed(m_socket, remote(), e.what()); + // if we have downloaded more than one piece more + // than we have uploaded OR if we are a seed + // have an unlimited upload rate + m_ul_bandwidth_quota.wanted = std::numeric_limits::max(); } + else + { + float ratio = m_torrent->ratio(); + // if we have downloaded too much, response with an + // upload rate of 10 kB/s more than we dowlload + // if we have uploaded too much, send with a rate of + // 10 kB/s less than we receive + int bias = 0; + if (diff > -block_limit*m_torrent->block_size()) + { + bias = static_cast(m_statistics.download_rate() * ratio) / 2; + if (bias < 10*1024) bias = 10*1024; + } + else + { + bias = -static_cast(m_statistics.download_rate() * ratio) / 2; + } + m_ul_bandwidth_quota.wanted = static_cast(m_statistics.download_rate()) + bias; + + // the maximum send_quota given our download rate from this peer + if (m_ul_bandwidth_quota.wanted < 256) m_ul_bandwidth_quota.wanted = 256; + } +*/ } void peer_connection::fill_send_buffer() @@ -2294,6 +1988,20 @@ namespace libtorrent m_reading_bytes += r.length; m_requests.erase(m_requests.begin()); +/* + if (m_requests.empty() + && m_num_invalid_requests > 0 + && is_peer_interested() + && !is_seed()) + { + // this will make the peer clear + // its download queue and re-request + // pieces. Hopefully it will not + // send invalid requests then + send_choke(); + send_unchoke(); + } +*/ } } @@ -2834,14 +2542,9 @@ namespace libtorrent void peer_connection::check_invariant() const { if (m_peer_info) - { assert(m_peer_info->connection == this || m_peer_info->connection == 0); - - if (m_peer_info->optimistically_unchoked) - assert(!is_choked()); - } - + boost::shared_ptr t = m_torrent.lock(); if (!t) { @@ -2855,8 +2558,6 @@ namespace libtorrent return; } - assert(t->connection_for(remote()) != 0 || m_in_constructor); - if (!m_in_constructor && t->connection_for(remote()) != this && !m_ses.settings().allow_multiple_connections_per_ip) { @@ -2916,6 +2617,11 @@ namespace libtorrent // TODO: the timeout should be called by an event INVARIANT_CHECK; +#ifndef NDEBUG + // allow step debugging without timing out + return false; +#endif + ptime now(time_now()); // if the socket is still connecting, don't @@ -2926,24 +2632,9 @@ namespace libtorrent // if the peer hasn't said a thing for a certain // time, it is considered to have timed out time_duration d; - d = now - m_last_receive; + d = time_now() - m_last_receive; if (d > seconds(m_timeout)) return true; - // if it takes more than 5 seconds to receive - // handshake, disconnect - if (in_handshake() && d > seconds(5)) return true; - - // disconnect peers that we unchoked, but - // they didn't send a request within 20 seconds. - // but only if we're a seed - boost::shared_ptr t = m_torrent.lock(); - d = now - (std::max)(m_last_unchoke, m_last_incoming_request); - if (m_requests.empty() - && !m_choked - && m_peer_interested - && t && t->is_finished() - && d > seconds(20)) return true; - // TODO: as long as we have less than 95% of the // global (or local) connection limit, connections should // never time out for another reason diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 398573d33..ddc2c2f5a 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -167,7 +167,6 @@ namespace libtorrent return; assert(sequenced_download_threshold > 0); - if (sequenced_download_threshold <= 0) return; int old_limit = m_sequenced_download_threshold; m_sequenced_download_threshold = sequenced_download_threshold; @@ -192,22 +191,22 @@ namespace libtorrent // the previous max availability was reached // we need to shuffle that bucket, if not, we // don't have to do anything - if (int(m_piece_info.size()) > old_limit * 2) + if (int(m_piece_info.size()) > old_limit) { - info_t& in = m_piece_info[old_limit * 2]; + info_t& in = m_piece_info[old_limit]; std::random_shuffle(in.begin(), in.end()); int c = 0; for (info_t::iterator i = in.begin() , end(in.end()); i != end; ++i) { m_piece_map[*i].index = c++; - assert(m_piece_map[*i].priority(old_limit) == old_limit * 2); + assert(m_piece_map[*i].priority(old_limit) == old_limit); } } } - else if (int(m_piece_info.size()) > sequenced_download_threshold * 2) + else if (int(m_piece_info.size()) > sequenced_download_threshold) { - info_t& in = m_piece_info[sequenced_download_threshold * 2]; + info_t& in = m_piece_info[sequenced_download_threshold]; std::sort(in.begin(), in.end()); int c = 0; for (info_t::iterator i = in.begin() @@ -215,7 +214,7 @@ namespace libtorrent { m_piece_map[*i].index = c++; assert(m_piece_map[*i].priority( - sequenced_download_threshold) == sequenced_download_threshold * 2); + sequenced_download_threshold) == sequenced_download_threshold); } } } @@ -263,23 +262,8 @@ namespace libtorrent } m_downloads.erase(i); } - #ifndef NDEBUG - void piece_picker::verify_pick(std::vector const& picked - , std::vector const& bitfield) const - { - assert(bitfield.size() == m_piece_map.size()); - for (std::vector::const_iterator i = picked.begin() - , end(picked.end()); i != end; ++i) - { - assert(i->piece_index >= 0); - assert(i->piece_index < int(bitfield.size())); - assert(bitfield[i->piece_index]); - assert(!m_piece_map[i->piece_index].have()); - } - } - void piece_picker::check_invariant(const torrent* t) const { assert(sizeof(piece_pos) == 4); @@ -410,7 +394,6 @@ namespace libtorrent assert(!t->have_piece(index)); int prio = i->priority(m_sequenced_download_threshold); - assert(prio < int(m_piece_info.size())); if (prio > 0) { const std::vector& vec = m_piece_info[prio]; @@ -464,7 +447,7 @@ namespace libtorrent if (i->have()) ++peer_count; if (min_availability > peer_count) { - min_availability = peer_count; + min_availability = i->peer_count; fraction_part += integer_part; integer_part = 1; } @@ -655,13 +638,12 @@ namespace libtorrent if (dp == m_downloads.begin()) return; int complete = dp->writing + dp->finished; for (std::vector::iterator i = dp, j(dp-1); - i != m_downloads.begin(); --i, --j) + i != m_downloads.begin() && j != m_downloads.begin(); --i, --j) { assert(j >= m_downloads.begin()); if (j->finished + j->writing >= complete) return; using std::swap; swap(*j, *i); - if (j == m_downloads.begin()) break; } } @@ -757,7 +739,6 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); - assert(prev_prio < int(m_piece_info.size())); ++i->peer_count; // if the assumption that the priority would // increase by 2 when increasing the availability @@ -847,8 +828,6 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); - assert(prev_prio < int(m_piece_info.size())); - assert(pushed_out_index < int(m_piece_info.size())); assert(i->peer_count > 0); --i->peer_count; // if the assumption that the priority would @@ -900,7 +879,6 @@ namespace libtorrent piece_pos& p = m_piece_map[i]; int index = p.index; int prev_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); assert(p.peer_count < piece_pos::max_peer_count); p.peer_count++; @@ -935,7 +913,6 @@ namespace libtorrent piece_pos& p = m_piece_map[i]; int prev_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); int index = p.index; assert(p.peer_count > 0); @@ -960,7 +937,6 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; int info_index = p.index; int priority = p.priority(m_sequenced_download_threshold); - assert(priority < int(m_piece_info.size())); assert(p.downloading == 1); assert(!p.have()); @@ -1004,7 +980,6 @@ namespace libtorrent if (new_piece_priority == int(p.piece_priority)) return false; int prev_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); bool ret = false; if (new_piece_priority == piece_pos::filter_priority @@ -1028,7 +1003,6 @@ namespace libtorrent p.piece_priority = new_piece_priority; int new_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); if (new_priority == prev_priority) return false; @@ -1094,9 +1068,8 @@ namespace libtorrent // or slow once they're started. void piece_picker::pick_pieces(const std::vector& pieces , std::vector& interesting_blocks - , int num_blocks, int prefer_whole_pieces - , void* peer, piece_state_t speed, bool rarest_first - , bool on_parole, std::vector const& suggested_pieces) const + , int num_blocks, bool prefer_whole_pieces + , void* peer, piece_state_t speed, bool rarest_first) const { TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(num_blocks > 0); @@ -1112,84 +1085,59 @@ namespace libtorrent // blocks belonging to a piece that others have // downloaded to std::vector backup_blocks; - // suggested pieces for each vector is put in this vector - std::vector suggested_bucket; - const std::vector empty_vector; // When prefer_whole_pieces is set (usually set when downloading from // fast peers) the partial pieces will not be prioritized, but actually // ignored as long as possible. All blocks found in downloading // pieces are regarded as backup blocks - - num_blocks = add_blocks_downloading(pieces - , interesting_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer, speed, on_parole); - - if (num_blocks <= 0) return; - - if (rarest_first) + bool ignore_downloading_pieces = false; + if (prefer_whole_pieces) { - // this loop will loop from pieces with priority 1 and up - // until we either reach the end of the piece list or - // has filled the interesting_blocks with num_blocks - // blocks. - - // +1 is to ignore pieces that no peer has. The bucket with index 0 contains - // pieces that 0 other peers have. bucket will point to a bucket with - // pieces with the same priority. It will be iterated in priority - // order (high priority/rare pices first). The content of each - // bucket is randomized - for (std::vector >::const_iterator bucket - = m_piece_info.begin() + 1; num_blocks > 0 && bucket != m_piece_info.end(); - ++bucket) + std::vector downloading_pieces; + downloading_pieces.reserve(m_downloads.size()); + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) { - if (bucket->empty()) continue; - if (!suggested_pieces.empty()) - { - int bucket_index = bucket - m_piece_info.begin(); - suggested_bucket.clear(); - for (std::vector::const_iterator i = suggested_pieces.begin() - , end(suggested_pieces.end()); i != end; ++i) - { - assert(*i >= 0); - assert(*i < int(m_piece_map.size())); - if (!can_pick(*i, pieces)) continue; - if (m_piece_map[*i].priority(m_sequenced_download_threshold) == bucket_index) - suggested_bucket.push_back(*i); - } - if (!suggested_bucket.empty()) - { - num_blocks = add_blocks(suggested_bucket, pieces - , interesting_blocks, num_blocks - , prefer_whole_pieces, peer, empty_vector); - if (num_blocks == 0) break; - } - } - num_blocks = add_blocks(*bucket, pieces - , interesting_blocks, num_blocks - , prefer_whole_pieces, peer, suggested_bucket); - assert(num_blocks >= 0); + downloading_pieces.push_back(i->index); } + add_interesting_blocks(downloading_pieces, pieces + , backup_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); + ignore_downloading_pieces = true; } - else + + // this loop will loop from pieces with priority 1 and up + // until we either reach the end of the piece list or + // has filled the interesting_blocks with num_blocks + // blocks. + + // +1 is to ignore pieces that no peer has. The bucket with index 0 contains + // pieces that 0 other peers have. bucket will point to a bucket with + // pieces with the same priority. It will be iterated in priority + // order (high priority/rare pices first). The content of each + // bucket is randomized + for (std::vector >::const_iterator bucket + = m_piece_info.begin() + 1; bucket != m_piece_info.end(); + ++bucket) { + if (bucket->empty()) continue; + num_blocks = add_interesting_blocks(*bucket, pieces + , interesting_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); + assert(num_blocks >= 0); + if (num_blocks == 0) return; + if (rarest_first) continue; + // we're not using rarest first (only for the first // bucket, since that's where the currently downloading // pieces are) - int start_piece = rand() % m_piece_map.size(); - - // if we have suggested pieces, try to find one of those instead - for (std::vector::const_iterator i = suggested_pieces.begin() - , end(suggested_pieces.end()); i != end; ++i) - { - if (!can_pick(*i, pieces)) continue; - start_piece = *i; - break; - } - int piece = start_piece; while (num_blocks > 0) { - while (!can_pick(piece, pieces)) + int start_piece = rand() % m_piece_map.size(); + int piece = start_piece; + while (!pieces[piece] + || m_piece_map[piece].index == piece_pos::we_have_index + || m_piece_map[piece].priority(m_sequenced_download_threshold) < 2) { ++piece; if (piece == int(m_piece_map.size())) piece = 0; @@ -1197,45 +1145,27 @@ namespace libtorrent if (piece == start_piece) return; } - int start, end; - boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces); - for (int k = start; k < end; ++k) - { - assert(m_piece_map[piece].downloading == false); - assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); - int num_blocks_in_piece = blocks_in_piece(k); - if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) - num_blocks_in_piece = num_blocks; - for (int j = 0; j < num_blocks_in_piece; ++j) - { - interesting_blocks.push_back(piece_block(k, j)); - --num_blocks; - } - } - piece = end; - if (piece == int(m_piece_map.size())) piece = 0; - // could not find any more pieces - if (piece == start_piece) return; + assert(m_piece_map[piece].downloading == false); + + int num_blocks_in_piece = blocks_in_piece(piece); + + if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) + interesting_blocks.push_back(piece_block(piece, j)); + num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); } - + if (num_blocks == 0) return; + break; } - if (num_blocks <= 0) return; + assert(num_blocks > 0); if (!backup_blocks.empty()) interesting_blocks.insert(interesting_blocks.end() , backup_blocks.begin(), backup_blocks.end()); } - bool piece_picker::can_pick(int piece, std::vector const& bitmask) const - { - assert(piece >= 0 && piece < int(m_piece_map.size())); - return bitmask[piece] - && !m_piece_map[piece].have() - && !m_piece_map[piece].downloading - && !m_piece_map[piece].filtered(); - } - void piece_picker::clear_peer(void* peer) { for (std::vector::iterator i = m_block_info.begin() @@ -1273,12 +1203,17 @@ namespace libtorrent } } - int piece_picker::add_blocks(std::vector const& piece_list + int piece_picker::add_interesting_blocks(std::vector const& piece_list , std::vector const& pieces , std::vector& interesting_blocks - , int num_blocks, int prefer_whole_pieces - , void* peer, std::vector const& ignore) const + , std::vector& backup_blocks + , int num_blocks, bool prefer_whole_pieces + , void* peer, piece_state_t speed + , bool ignore_downloading_pieces) const { + // if we have less than 1% of the pieces, ignore speed priorities and just try + // to finish any downloading piece + bool ignore_speed_categories = (m_num_have * 100 / m_piece_map.size()) < 1; for (std::vector::const_iterator i = piece_list.begin(); i != piece_list.end(); ++i) { @@ -1289,267 +1224,110 @@ namespace libtorrent // skip it if (!pieces[*i]) continue; - // ignore pieces found in the ignore list - if (std::find(ignore.begin(), ignore.end(), *i) != ignore.end()) continue; - - // skip the piece is the priority is 0 - assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); - int num_blocks_in_piece = blocks_in_piece(*i); - assert(m_piece_map[*i].downloading == 0); - assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); - - // pick a new piece - if (prefer_whole_pieces == 0) + if (m_piece_map[*i].downloading == 1) { - if (num_blocks_in_piece > num_blocks) - num_blocks_in_piece = num_blocks; - for (int j = 0; j < num_blocks_in_piece; ++j) - interesting_blocks.push_back(piece_block(*i, j)); - num_blocks -= num_blocks_in_piece; - } - else - { - int start, end; - boost::tie(start, end) = expand_piece(*i, prefer_whole_pieces, pieces); - for (int k = start; k < end; ++k) - { - assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); - num_blocks_in_piece = blocks_in_piece(k); - for (int j = 0; j < num_blocks_in_piece; ++j) - { - interesting_blocks.push_back(piece_block(k, j)); - --num_blocks; - } - } - } - if (num_blocks <= 0) - { -#ifndef NDEBUG - verify_pick(interesting_blocks, pieces); -#endif - return 0; - } - } -#ifndef NDEBUG - verify_pick(interesting_blocks, pieces); -#endif - return num_blocks; - } + if (ignore_downloading_pieces) continue; + std::vector::const_iterator p + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); + assert(p != m_downloads.end()); - int piece_picker::add_blocks_downloading(std::vector const& pieces - , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, int prefer_whole_pieces - , void* peer, piece_state_t speed, bool on_parole) const - { - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) - { - if (!pieces[i->index]) continue; - - int num_blocks_in_piece = blocks_in_piece(i->index); - - // is true if all the other pieces that are currently - // requested from this piece are from the same - // peer as 'peer'. - bool exclusive; - bool exclusive_active; - boost::tie(exclusive, exclusive_active) - = requested_from(*i, num_blocks_in_piece, peer); - - // peers on parole are only allowed to pick blocks from - // pieces that only they have downloaded/requested from - if (on_parole && !exclusive) continue; - - if (prefer_whole_pieces > 0 && !exclusive_active) continue; - - // don't pick too many back-up blocks - if (i->state != none - && i->state != speed - && !exclusive_active - && int(backup_blocks.size()) >= num_blocks) - continue; - - for (int j = 0; j < num_blocks_in_piece; ++j) - { - // ignore completed blocks and already requested blocks - block_info const& info = i->info[j]; - if (info.state != block_info::state_none) - continue; - - assert(i->info[j].state == block_info::state_none); - - // if the piece is fast and the peer is slow, or vice versa, - // add the block as a backup. - // override this behavior if all the other blocks - // have been requested from the same peer or - // if the state of the piece is none (the - // piece will in that case change state). - if (i->state != none && i->state != speed - && !exclusive_active) - { - backup_blocks.push_back(piece_block(i->index, j)); - continue; - } - - // this block is interesting (we don't have it - // yet). - interesting_blocks.push_back(piece_block(i->index, j)); - // we have found a block that's free to download - num_blocks--; - // if we prefer whole pieces, continue picking from this - // piece even though we have num_blocks - if (prefer_whole_pieces > 0) continue; - assert(num_blocks >= 0); - if (num_blocks <= 0) break; - } - if (num_blocks <= 0) break; - } - - assert(num_blocks >= 0 || prefer_whole_pieces > 0); - -#ifndef NDEBUG - verify_pick(interesting_blocks, pieces); - verify_pick(backup_blocks, pieces); -#endif - - if (num_blocks <= 0) return 0; - if (on_parole) return num_blocks; - - int to_copy; - if (prefer_whole_pieces == 0) - to_copy = (std::min)(int(backup_blocks.size()), num_blocks); - else - to_copy = int(backup_blocks.size()); - - interesting_blocks.insert(interesting_blocks.end() - , backup_blocks.begin(), backup_blocks.begin() + to_copy); - num_blocks -= to_copy; - backup_blocks.clear(); - - if (num_blocks <= 0) return 0; - - if (prefer_whole_pieces > 0) - { - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) - { - if (!pieces[i->index]) continue; - int num_blocks_in_piece = blocks_in_piece(i->index); + // is true if all the other pieces that are currently + // requested from this piece are from the same + // peer as 'peer'. bool exclusive; bool exclusive_active; boost::tie(exclusive, exclusive_active) - = requested_from(*i, num_blocks_in_piece, peer); + = requested_from(*p, num_blocks_in_piece, peer); + + // this means that this partial piece has + // been downloaded/requested partially from + // another peer that isn't us. And since + // we prefer whole pieces, add this piece's + // blocks to the backup list. If the prioritized + // blocks aren't enough, blocks from this list + // will be picked. + if (prefer_whole_pieces && !exclusive) + { + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = p->info[j]; + if (info.state == block_info::state_finished + || info.state == block_info::state_writing) + continue; + if (info.state == block_info::state_requested + && info.peer == peer) continue; + backup_blocks.push_back(piece_block(*i, j)); + } + continue; + } - if (exclusive_active) continue; - for (int j = 0; j < num_blocks_in_piece; ++j) { - block_info const& info = i->info[j]; - if (info.state != block_info::state_none) continue; - backup_blocks.push_back(piece_block(i->index, j)); + // ignore completed blocks + block_info const& info = p->info[j]; + if (info.state == block_info::state_finished + || info.state == block_info::state_writing) + continue; + // ignore blocks requested from this peer already + if (info.state == block_info::state_requested + && info.peer == peer) + continue; + // if the piece is fast and the peer is slow, or vice versa, + // add the block as a backup. + // override this behavior if all the other blocks + // have been requested from the same peer or + // if the state of the piece is none (the + // piece will in that case change state). + if (p->state != none && p->state != speed + && !exclusive_active + && !ignore_speed_categories) + { + backup_blocks.push_back(piece_block(*i, j)); + continue; + } + // this block is interesting (we don't have it + // yet). But it may already have been requested + // from another peer. We have to add it anyway + // to allow the requester to determine if the + // block should be requested from more than one + // peer. If it is being downloaded, we continue + // to look for blocks until we have num_blocks + // blocks that have not been requested from any + // other peer. + if (p->info[j].state == block_info::state_none) + { + interesting_blocks.push_back(piece_block(*i, j)); + // we have found a block that's free to download + num_blocks--; + // if we prefer whole pieces, continue picking from this + // piece even though we have num_blocks + if (prefer_whole_pieces) continue; + assert(num_blocks >= 0); + if (num_blocks == 0) return num_blocks; + } + else + { + backup_blocks.push_back(piece_block(*i, j)); + } } + assert(num_blocks >= 0 || prefer_whole_pieces); + if (num_blocks < 0) num_blocks = 0; } - } - - if (int(backup_blocks.size()) >= num_blocks) return num_blocks; - - -#ifndef NDEBUG -// make sure that we at this point has added requests to all unrequested blocks -// in all downloading pieces - - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) - { - if (!pieces[i->index]) continue; - - int num_blocks_in_piece = blocks_in_piece(i->index); - for (int j = 0; j < num_blocks_in_piece; ++j) + else { - block_info const& info = i->info[j]; - if (info.state != block_info::state_none) continue; - std::vector::iterator k = std::find( - interesting_blocks.begin(), interesting_blocks.end() - , piece_block(i->index, j)); - if (k != interesting_blocks.end()) continue; - - k = std::find(backup_blocks.begin() - , backup_blocks.end(), piece_block(i->index, j)); - if (k != backup_blocks.end()) continue; - - std::cerr << "interesting blocks:" << std::endl; - for (k = interesting_blocks.begin(); k != interesting_blocks.end(); ++k) - std::cerr << "(" << k->piece_index << ", " << k->block_index << ") "; - std::cerr << std::endl; - std::cerr << "backup blocks:" << std::endl; - for (k = backup_blocks.begin(); k != backup_blocks.end(); ++k) - std::cerr << "(" << k->piece_index << ", " << k->block_index << ") "; - std::cerr << std::endl; - std::cerr << "num_blocks: " << num_blocks << std::endl; - - for (std::vector::const_iterator l = m_downloads.begin() - , end(m_downloads.end()); l != end; ++l) - { - std::cerr << l->index << " : "; - int num_blocks_in_piece = blocks_in_piece(l->index); - for (int m = 0; m < num_blocks_in_piece; ++m) - std::cerr << l->info[m].state; - std::cerr << std::endl; - } - - assert(false); + if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) + interesting_blocks.push_back(piece_block(*i, j)); + num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); } + assert(num_blocks >= 0); + if (num_blocks == 0) return num_blocks; } -#endif - - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) - { - if (!pieces[i->index]) continue; - - int num_blocks_in_piece = blocks_in_piece(i->index); - - // fill in with blocks requested from other peers - // as backups - for (int j = 0; j < num_blocks_in_piece; ++j) - { - block_info const& info = i->info[j]; - if (info.state != block_info::state_requested - || info.peer == peer) - continue; - backup_blocks.push_back(piece_block(i->index, j)); - } - } -#ifndef NDEBUG - verify_pick(backup_blocks, pieces); -#endif return num_blocks; } - - std::pair piece_picker::expand_piece(int piece, int whole_pieces - , std::vector const& have) const - { - if (whole_pieces == 0) return std::make_pair(piece, piece + 1); - - int start = piece - 1; - int lower_limit = piece - whole_pieces; - if (lower_limit < -1) lower_limit = -1; - while (start > lower_limit - && can_pick(start, have)) - --start; - ++start; - assert(start >= 0); - int end = piece + 1; - int upper_limit = start + whole_pieces; - if (upper_limit > int(m_piece_map.size())) upper_limit = int(m_piece_map.size()); - while (end < upper_limit - && can_pick(end, have)) - ++end; - return std::make_pair(start, end); - } bool piece_picker::is_piece_finished(int index) const { @@ -1627,7 +1405,7 @@ namespace libtorrent } - bool piece_picker::mark_as_downloading(piece_block block + void piece_picker::mark_as_downloading(piece_block block , void* peer, piece_state_t state) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1636,14 +1414,11 @@ namespace libtorrent assert(block.block_index >= 0); assert(block.piece_index < (int)m_piece_map.size()); assert(block.block_index < blocks_in_piece(block.piece_index)); - assert(!m_piece_map[block.piece_index].have()); piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { int prio = p.priority(m_sequenced_download_threshold); - assert(prio < int(m_piece_info.size())); - assert(prio > 0); p.downloading = 1; move(prio, p.index); @@ -1662,9 +1437,6 @@ namespace libtorrent = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); assert(i != m_downloads.end()); block_info& info = i->info[block.block_index]; - if (info.state == block_info::state_writing - || info.state == block_info::state_finished) - return false; assert(info.state == block_info::state_none || (info.state == block_info::state_requested && (info.num_peers > 0))); @@ -1677,7 +1449,6 @@ namespace libtorrent ++info.num_peers; if (i->state == none) i->state = state; } - return true; } int piece_picker::num_peers(piece_block block) const @@ -1758,7 +1529,6 @@ namespace libtorrent assert(peer == 0); int prio = p.priority(m_sequenced_download_threshold); - assert(prio < int(m_piece_info.size())); p.downloading = 1; if (prio > 0) move(prio, p.index); else assert(p.priority(m_sequenced_download_threshold) == 0); @@ -1880,12 +1650,9 @@ namespace libtorrent { erase_download_piece(i); piece_pos& p = m_piece_map[block.piece_index]; - int prev_prio = p.priority(m_sequenced_download_threshold); - assert(prev_prio < int(m_piece_info.size())); - p.downloading = 0; int prio = p.priority(m_sequenced_download_threshold); - if (prev_prio == 0 && prio > 0) add(block.piece_index); - else if (prio > 0) move(prio, p.index); + p.downloading = 0; + if (prio > 0) move(prio, p.index); assert(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 4faed837e..572f48d35 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -83,7 +83,7 @@ namespace // (and we should not consider it free). If the share diff is // negative, there's no free download to get from this peer. size_type diff = i->second->share_diff(); - assert(diff < (std::numeric_limits::max)()); + assert(diff < std::numeric_limits::max()); if (i->second->is_peer_interested() || diff <= 0) continue; @@ -110,7 +110,7 @@ namespace for (torrent::peer_iterator i = start; i != end; ++i) { size_type d = i->second->share_diff(); - assert(d < (std::numeric_limits::max)()); + assert(d < std::numeric_limits::max()); total_diff += d; if (!i->second->is_peer_interested() || i->second->share_diff() >= 0) continue; ++num_peers; @@ -120,7 +120,7 @@ namespace size_type upload_share; if (total_diff >= 0) { - upload_share = (std::min)(free_upload, total_diff) / num_peers; + upload_share = std::min(free_upload, total_diff) / num_peers; } else { @@ -138,28 +138,28 @@ namespace return free_upload; } - struct match_peer_address + struct match_peer_ip { - match_peer_address(address const& addr) - : m_addr(addr) + match_peer_ip(address const& ip) + : m_ip(ip) {} bool operator()(policy::peer const& p) const - { return p.ip.address() == m_addr; } + { return p.ip.address() == m_ip; } - address const& m_addr; + address const& m_ip; }; - struct match_peer_endpoint + struct match_peer_id { - match_peer_endpoint(tcp::endpoint const& ep) - : m_ep(ep) + match_peer_id(peer_id const& id_) + : m_id(id_) {} bool operator()(policy::peer const& p) const - { return p.ip == m_ep; } + { return p.connection && p.connection->pid() == m_id; } - tcp::endpoint const& m_ep; + peer_id const& m_id; }; struct match_peer_connection @@ -187,19 +187,17 @@ namespace libtorrent // have only one piece that we don't have, and it's the // same piece for both peers. Then they might get into an // infinite loop, fighting to request the same blocks. - void request_a_block(torrent& t, peer_connection& c) + void request_a_block( + torrent& t + , peer_connection& c) { - if (t.is_seed()) return; - - assert(t.valid_metadata()); + assert(!t.is_seed()); + assert(!c.has_peer_choked()); assert(c.peer_info_struct() != 0 || !dynamic_cast(&c)); int num_requests = c.desired_queue_size() - (int)c.download_queue().size() - (int)c.request_queue().size(); -#ifdef TORRENT_VERBOSE_LOGGING - (*c.m_logger) << time_now_string() << " PIECE_PICKER [ req: " << num_requests << " ]\n"; -#endif assert(c.desired_queue_size() > 0); // if our request queue is already full, we // don't have to make any new requests yet @@ -209,15 +207,16 @@ namespace libtorrent std::vector interesting_pieces; interesting_pieces.reserve(100); - int prefer_whole_pieces = c.prefer_whole_pieces(); + bool prefer_whole_pieces = c.prefer_whole_pieces() + || (c.peer_info_struct() && c.peer_info_struct()->on_parole); bool rarest_first = t.num_pieces() >= t.settings().initial_picker_threshold; - if (prefer_whole_pieces == 0) + if (!prefer_whole_pieces) { prefer_whole_pieces = c.statistics().download_payload_rate() * t.settings().whole_pieces_threshold - > t.torrent_file().piece_length() ? 1 : 0; + > t.torrent_file().piece_length(); } // if we prefer whole pieces, the piece picker will pick at least @@ -232,6 +231,18 @@ namespace libtorrent else if (speed == peer_connection::medium) state = piece_picker::medium; else state = piece_picker::slow; + // picks the interesting pieces from this peer + // the integer is the number of pieces that + // should be guaranteed to be available for download + // (if num_requests is too big, too many pieces are + // picked and cpu-time is wasted) + // the last argument is if we should prefer whole pieces + // for this peer. If we're downloading one piece in 20 seconds + // then use this mode. + p.pick_pieces(c.get_bitfield(), interesting_pieces + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, rarest_first); + // this vector is filled with the interesting pieces // that some other peer is currently downloading // we should then compare this peer's download speed @@ -240,56 +251,14 @@ namespace libtorrent std::vector busy_pieces; busy_pieces.reserve(num_requests); - std::vector const& suggested = c.suggested_pieces(); - std::vector const& bitfield = c.get_bitfield(); - - if (c.has_peer_choked()) - { - // if we are choked we can only pick pieces from the - // allowed fast set. The allowed fast set is sorted - // in ascending priority order - std::vector const& allowed_fast = c.allowed_fast(); - - // build a bitmask with only the allowed pieces in it - std::vector mask(c.get_bitfield().size(), false); - for (std::vector::const_iterator i = allowed_fast.begin() - , end(allowed_fast.end()); i != end; ++i) - if (bitfield[*i]) mask[*i] = true; - - p.pick_pieces(mask, interesting_pieces - , num_requests, prefer_whole_pieces, c.peer_info_struct() - , state, rarest_first, c.on_parole(), suggested); - } - else - { - // picks the interesting pieces from this peer - // the integer is the number of pieces that - // should be guaranteed to be available for download - // (if num_requests is too big, too many pieces are - // picked and cpu-time is wasted) - // the last argument is if we should prefer whole pieces - // for this peer. If we're downloading one piece in 20 seconds - // then use this mode. - p.pick_pieces(bitfield, interesting_pieces - , num_requests, prefer_whole_pieces, c.peer_info_struct() - , state, rarest_first, c.on_parole(), suggested); - } - -#ifdef TORRENT_VERBOSE_LOGGING - (*c.m_logger) << time_now_string() << " PIECE_PICKER [ php: " << prefer_whole_pieces - << " picked: " << interesting_pieces.size() << " ]\n"; -#endif - std::deque const& dq = c.download_queue(); - std::deque const& rq = c.request_queue(); for (std::vector::iterator i = interesting_pieces.begin(); i != interesting_pieces.end(); ++i) { - if (prefer_whole_pieces == 0 && num_requests <= 0) break; - if (p.is_requested(*i)) { - if (num_requests <= 0) break; // don't request pieces we already have in our request queue + const std::deque& dq = c.download_queue(); + const std::deque& rq = c.request_queue(); if (std::find(dq.begin(), dq.end(), *i) != dq.end() || std::find(rq.begin(), rq.end(), *i) != rq.end()) continue; @@ -308,13 +277,13 @@ namespace libtorrent num_requests--; } - if (busy_pieces.empty() || num_requests <= 0) - { - // in this case, we could not find any blocks - // that was free. If we couldn't find any busy - // blocks as well, we cannot download anything - // more from this peer. + // in this case, we could not find any blocks + // that was free. If we couldn't find any busy + // blocks as well, we cannot download anything + // more from this peer. + if (busy_pieces.empty() || num_requests == 0) + { c.send_block_requests(); return; } @@ -339,8 +308,9 @@ namespace libtorrent policy::policy(torrent* t) : m_torrent(t) + , m_num_unchoked(0) , m_available_free_upload(0) -// , m_last_optimistic_disconnect(min_time()) + , m_last_optimistic_disconnect(min_time()) { assert(t); } // disconnects and removes all peers that are now filtered @@ -382,7 +352,7 @@ namespace libtorrent m_peers.erase(i++); } } -/* + // finds the peer that has the worst download rate // and returns it. May return 0 if all peers are // choked. @@ -391,7 +361,7 @@ namespace libtorrent INVARIANT_CHECK; iterator worst_peer = m_peers.end(); - size_type min_weight = (std::numeric_limits::min)(); + size_type min_weight = std::numeric_limits::min(); #ifndef NDEBUG int unchoked_counter = m_num_unchoked; @@ -464,13 +434,13 @@ namespace libtorrent } return unchoke_peer; } -*/ + policy::iterator policy::find_disconnect_candidate() { INVARIANT_CHECK; iterator disconnect_peer = m_peers.end(); - double slowest_transfer_rate = (std::numeric_limits::max)(); + double slowest_transfer_rate = std::numeric_limits::max(); ptime now = time_now(); @@ -513,8 +483,7 @@ namespace libtorrent policy::iterator policy::find_connect_candidate() { -// too expensive -// INVARIANT_CHECK; + INVARIANT_CHECK; ptime now = time_now(); ptime min_connect_time(now); @@ -522,7 +491,6 @@ namespace libtorrent int max_failcount = m_torrent->settings().max_failcount; int min_reconnect_time = m_torrent->settings().min_reconnect_time; - bool finished = m_torrent->is_finished(); aux::session_impl& ses = m_torrent->session(); @@ -531,7 +499,7 @@ namespace libtorrent if (i->connection) continue; if (i->banned) continue; if (i->type == peer::not_connectable) continue; - if (i->seed && finished) continue; + if (i->seed && m_torrent->is_seed()) continue; if (i->failcount >= max_failcount) continue; if (now - i->connected < seconds(i->failcount * min_reconnect_time)) continue; @@ -551,7 +519,7 @@ namespace libtorrent return candidate; } -/* + policy::iterator policy::find_seed_choke_candidate() { INVARIANT_CHECK; @@ -657,7 +625,7 @@ namespace libtorrent --m_num_unchoked; } } -*/ + void policy::pulse() { INVARIANT_CHECK; @@ -689,7 +657,7 @@ namespace libtorrent // ------------------------------------- // maintain the number of connections // ------------------------------------- -/* + // count the number of connected peers except for peers // that are currently in the process of disconnecting int num_connected_peers = 0; @@ -701,9 +669,10 @@ namespace libtorrent ++num_connected_peers; } - if (m_torrent->max_connections() != (std::numeric_limits::max)()) + if (m_torrent->m_connections_quota.given != std::numeric_limits::max()) { - int max_connections = m_torrent->max_connections(); + + int max_connections = m_torrent->m_connections_quota.given; if (num_connected_peers >= max_connections) { @@ -731,7 +700,7 @@ namespace libtorrent --num_connected_peers; } } -*/ + // ------------------------ // upload shift // ------------------------ @@ -762,7 +731,7 @@ namespace libtorrent , m_torrent->end() , m_available_free_upload); } -/* + // ------------------------ // seed choking policy // ------------------------ @@ -878,7 +847,6 @@ namespace libtorrent while (m_num_unchoked < m_torrent->m_uploads_quota.given && unchoke_one_peer()); } -*/ } int policy::count_choked() const @@ -911,8 +879,7 @@ namespace libtorrent // override at a time assert(c.remote() == c.get_socket()->remote_endpoint()); - if (m_torrent->num_peers() >= m_torrent->max_connections() - && m_torrent->session().num_connections() >= m_torrent->session().max_connections() + if (m_torrent->num_peers() >= m_torrent->m_connections_quota.given && c.remote().address() != m_torrent->current_tracker().address()) { throw protocol_error("too many connections, refusing incoming connection"); // cause a disconnect @@ -939,7 +906,7 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_address(c.remote().address())); + , match_peer_ip(c.remote().address())); } if (i != m_peers.end()) @@ -994,17 +961,16 @@ namespace libtorrent i->connection = &c; assert(i->connection); i->connected = time_now(); -// m_last_optimistic_disconnect = time_now(); + m_last_optimistic_disconnect = time_now(); } void policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int src, char flags) { -// too expensive -// INVARIANT_CHECK; + INVARIANT_CHECK; // just ignore the obviously invalid entries - if (remote.address() == address() || remote.port() == 0) + if(remote.address() == address() || remote.port() == 0) return; aux::session_impl& ses = m_torrent->session(); @@ -1029,14 +995,14 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_endpoint(remote)); + , match_peer_id(pid)); } else { i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_address(remote.address())); + , match_peer_ip(remote.address())); } if (i == m_peers.end()) @@ -1090,10 +1056,7 @@ namespace libtorrent if (i->failcount > 0 && src != peer_info::dht) --i->failcount; - // if we're connected to this peer - // we already know if it's a seed or not - // so we don't have to trust this source - if ((flags & 0x02) && !i->connection) i->seed = true; + if (flags & 0x02) i->seed = true; if (i->connection) { @@ -1183,38 +1146,14 @@ namespace libtorrent // In that case we don't care if people are leeching, they // can't pay for their downloads anyway. if (c.is_choked() - && m_torrent->session().num_uploads() < m_torrent->session().max_uploads() + && m_num_unchoked < m_torrent->m_uploads_quota.given && (m_torrent->ratio() == 0 || c.share_diff() >= -free_upload_amount - || m_torrent->is_finished())) + || m_torrent->is_seed())) { - m_torrent->session().unchoke_peer(c); + c.send_unchoke(); + ++m_num_unchoked; } -#if defined(TORRENT_VERBOSE_LOGGING) - else if (c.is_choked()) - { - std::string reason; - if (m_torrent->session().num_uploads() >= m_torrent->session().max_uploads()) - { - reason = "the number of uploads (" - + boost::lexical_cast(m_torrent->session().num_uploads()) - + ") is more than or equal to the limit (" - + boost::lexical_cast(m_torrent->session().max_uploads()) - + ")"; - } - else - { - reason = "the share ratio (" - + boost::lexical_cast(c.share_diff()) - + ") is <= free_upload_amount (" - + boost::lexical_cast(int(free_upload_amount)) - + ") and we are not seeding and the ratio (" - + boost::lexical_cast(m_torrent->ratio()) - + ")is non-zero"; - } - (*c.m_logger) << time_now_string() << " DID NOT UNCHOKE [ " << reason << " ]\n"; - } -#endif } // called when a peer is no longer interested in us @@ -1224,7 +1163,7 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { - assert(c.share_diff() < (std::numeric_limits::max)()); + assert(c.share_diff() < std::numeric_limits::max()); size_type diff = c.share_diff(); if (diff > 0 && c.is_seed()) { @@ -1246,7 +1185,7 @@ namespace libtorrent } */ } -/* + bool policy::unchoke_one_peer() { INVARIANT_CHECK; @@ -1275,7 +1214,7 @@ namespace libtorrent p->connection->send_choke(); --m_num_unchoked; } -*/ + bool policy::connect_one_peer() { INVARIANT_CHECK; @@ -1291,15 +1230,9 @@ namespace libtorrent try { - INVARIANT_CHECK; - p->connected = time_now(); + p->connected = m_last_optimistic_disconnect = time_now(); p->connection = m_torrent->connect_to_peer(&*p); - assert(p->connection == m_torrent->connection_for(p->ip)); - if (p->connection == 0) - { - ++p->failcount; - return false; - } + if (p->connection == 0) return false; p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); p->prev_amount_download = 0; p->prev_amount_upload = 0; @@ -1311,7 +1244,6 @@ namespace libtorrent (*m_torrent->session().m_logger) << "*** CONNECTION FAILED '" << e.what() << "'\n"; #endif - std::cerr << e.what() << std::endl; ++p->failcount; return false; } @@ -1331,33 +1263,33 @@ namespace libtorrent } // this is called whenever a peer connection is closed - void policy::connection_closed(const peer_connection& c) throw() + void policy::connection_closed(const peer_connection& c) try { -// too expensive -// INVARIANT_CHECK; + INVARIANT_CHECK; - peer* p = c.peer_info_struct(); +// assert(c.is_disconnecting()); + bool unchoked = false; - assert((std::find_if( + iterator i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_connection(c)) - != m_peers.end()) == (p != 0)); - + , match_peer_connection(c)); + // if we couldn't find the connection in our list, just ignore it. - if (p == 0) return; + if (i == m_peers.end()) return; + assert(i->connection == &c); + i->connection = 0; - assert(p->connection == &c); - - p->connection = 0; - p->optimistically_unchoked = false; - - p->connected = time_now(); + i->connected = time_now(); + if (!c.is_choked() && !m_torrent->is_aborted()) + { + unchoked = true; + } if (c.failed()) { - ++p->failcount; -// p->connected = time_now(); + ++i->failcount; +// i->connected = time_now(); } // if the share ratio is 0 (infinite), the @@ -1366,11 +1298,28 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { assert(c.associated_torrent().lock().get() == m_torrent); - assert(c.share_diff() < (std::numeric_limits::max)()); + assert(c.share_diff() < std::numeric_limits::max()); m_available_free_upload += c.share_diff(); } - p->prev_amount_download += c.statistics().total_payload_download(); - p->prev_amount_upload += c.statistics().total_payload_upload(); + i->prev_amount_download += c.statistics().total_payload_download(); + i->prev_amount_upload += c.statistics().total_payload_upload(); + + if (unchoked) + { + // if the peer that is diconnecting is unchoked + // then unchoke another peer in order to maintain + // the total number of unchoked peers + --m_num_unchoked; + if (m_torrent->is_seed()) seed_unchoke_one_peer(); + else unchoke_one_peer(); + } + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::string err = e.what(); +#endif + assert(false); } void policy::peer_is_interesting(peer_connection& c) @@ -1378,21 +1327,17 @@ namespace libtorrent INVARIANT_CHECK; c.send_interested(); - if (c.has_peer_choked() - && c.allowed_fast().empty()) - return; + if (c.has_peer_choked()) return; request_a_block(*m_torrent, c); } #ifndef NDEBUG bool policy::has_connection(const peer_connection* c) { -// too expensive -// INVARIANT_CHECK; + INVARIANT_CHECK; assert(c); - try { assert(c->remote() == c->get_socket()->remote_endpoint()); } - catch (std::exception&) {} + assert(c->remote() == c->get_socket()->remote_endpoint()); return std::find_if( m_peers.begin() @@ -1403,28 +1348,22 @@ namespace libtorrent void policy::check_invariant() const { if (m_torrent->is_aborted()) return; + int actual_unchoked = 0; int connected_peers = 0; int total_connections = 0; int nonempty_connections = 0; - + std::set
unique_test; - std::set unique_test2; for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { peer const& p = *i; if (!m_torrent->settings().allow_multiple_connections_per_ip) assert(unique_test.find(p.ip.address()) == unique_test.end()); - assert(unique_test2.find(p.ip) == unique_test2.end()); unique_test.insert(p.ip.address()); - unique_test2.insert(p.ip); ++total_connections; - if (!p.connection) - { -// assert(m_torrent->connection_for(p.ip) == 0); - continue; - } + if (!p.connection) continue; if (!m_torrent->settings().allow_multiple_connections_per_ip) { std::vector conns; @@ -1433,17 +1372,15 @@ namespace libtorrent , boost::bind(std::equal_to(), _1, p.connection)) != conns.end()); } - if (p.optimistically_unchoked) - { - assert(p.connection); - assert(!p.connection->is_choked()); - } assert(p.connection->peer_info_struct() == 0 || p.connection->peer_info_struct() == &p); ++nonempty_connections; if (!p.connection->is_disconnecting()) ++connected_peers; + if (!p.connection->is_choked()) ++actual_unchoked; } +// assert(actual_unchoked <= m_torrent->m_uploads_quota.given); + assert(actual_unchoked == m_num_unchoked); int num_torrent_peers = 0; for (torrent::const_peer_iterator i = m_torrent->begin(); @@ -1510,7 +1447,6 @@ namespace libtorrent , failcount(0) , hashfails(0) , seed(false) - , optimistically_unchoked(false) , last_optimistically_unchoked(min_time()) , connected(min_time()) , trust_points(0) diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 6298b3c2c..485c90d62 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -68,6 +68,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" +#include "libtorrent/allocate_resources.hpp" #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" @@ -132,7 +133,7 @@ namespace libtorrent m_impl->abort(); } - void session::add_extension(boost::function(torrent*, void*)> ext) + void session::add_extension(boost::function(torrent*)> ext) { m_impl->add_extension(ext); } @@ -179,27 +180,11 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data , bool compact_mode - , bool paused + , int block_size , storage_constructor_type sc) { - assert(!ti.m_half_metadata); - boost::intrusive_ptr tip(new torrent_info(ti)); - return m_impl->add_torrent(tip, save_path, resume_data - , compact_mode, sc, paused, 0); - } - - torrent_handle session::add_torrent( - boost::intrusive_ptr ti - , fs::path const& save_path - , entry const& resume_data - , bool compact_mode - , bool paused - , storage_constructor_type sc - , void* userdata) - { - assert(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data - , compact_mode, sc, paused, userdata); + , compact_mode, block_size, sc); } torrent_handle session::add_torrent( @@ -209,12 +194,11 @@ namespace libtorrent , fs::path const& save_path , entry const& e , bool compact_mode - , bool paused - , storage_constructor_type sc - , void* userdata) + , int block_size + , storage_constructor_type sc) { return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e - , compact_mode, sc, paused, userdata); + , compact_mode, block_size, sc); } void session::remove_torrent(const torrent_handle& h) @@ -353,11 +337,6 @@ namespace libtorrent m_impl->set_max_connections(limit); } - int session::max_half_open_connections() const - { - return m_impl->max_half_open_connections(); - } - void session::set_max_half_open_connections(int limit) { m_impl->set_max_half_open_connections(limit); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 6e1129b99..81f48a3ce 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -68,6 +68,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" +#include "libtorrent/allocate_resources.hpp" #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" @@ -222,12 +223,6 @@ namespace detail if (!m_ses.is_aborted()) { m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr)); - if (m_ses.m_alerts.should_post(alert::info)) - { - m_ses.m_alerts.post_alert(torrent_checked_alert( - processing->torrent_ptr->get_handle() - , "torrent finished checking")); - } if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) { m_ses.m_alerts.post_alert(torrent_finished_alert( @@ -350,12 +345,6 @@ namespace detail processing->torrent_ptr->files_checked(processing->unfinished_pieces); m_ses.m_torrents.insert(std::make_pair( processing->info_hash, processing->torrent_ptr)); - if (m_ses.m_alerts.should_post(alert::info)) - { - m_ses.m_alerts.post_alert(torrent_checked_alert( - processing->torrent_ptr->get_handle() - , "torrent finished checking")); - } if (processing->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) { @@ -516,12 +505,8 @@ namespace detail , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) , m_external_listen_port(0) , m_abort(false) - , m_max_uploads(8) - , m_max_connections(200) - , m_num_unchoked(0) - , m_unchoke_time_scaler(0) - , m_optimistic_unchoke_time_scaler(0) - , m_disconnect_time_scaler(0) + , m_max_uploads(-1) + , m_max_connections(-1) , m_incoming_connection(false) , m_last_tick(time_now()) #ifndef TORRENT_DISABLE_DHT @@ -532,11 +517,6 @@ namespace detail , m_next_connect_torrent(0) , m_checker_impl(*this) { -#ifdef WIN32 - // windows XP has a limit of 10 simultaneous connections - m_half_open.limit(8); -#endif - m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; m_bandwidth_manager[peer_connection::upload_channel] = &m_upload_channel; @@ -593,7 +573,7 @@ namespace detail #ifndef TORRENT_DISABLE_EXTENSIONS void session_impl::add_extension( - boost::function(torrent*, void*)> ext) + boost::function(torrent*)> ext) { m_extensions.push_back(ext); } @@ -629,9 +609,6 @@ namespace detail void session_impl::set_ip_filter(ip_filter const& f) { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - m_ip_filter = f; // Close connections whose endpoint is filtered @@ -644,9 +621,6 @@ namespace detail void session_impl::set_settings(session_settings const& s) { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - assert(s.connection_speed > 0); assert(s.file_pool_size > 0); @@ -666,25 +640,22 @@ namespace detail try { // create listener socket - m_listen_socket.reset(new socket_acceptor(m_io_service)); + m_listen_socket = boost::shared_ptr(new socket_acceptor(m_io_service)); for(;;) { try { m_listen_socket->open(m_listen_interface.protocol()); - m_listen_socket->set_option(socket_acceptor::reuse_address(true)); m_listen_socket->bind(m_listen_interface); m_listen_socket->listen(); - m_listen_interface = m_listen_socket->local_endpoint(); m_external_listen_port = m_listen_interface.port(); break; } catch (asio::system_error& e) { // TODO: make sure this is correct - if (e.code() == asio::error::host_not_found - || m_listen_interface.port() == 0) + if (e.code() == asio::error::host_not_found) { if (m_alerts.should_post(alert::fatal)) { @@ -729,18 +700,14 @@ namespace detail } } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (m_listen_socket) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << "listening on port: " << m_listen_interface.port() << " external port: " << m_external_listen_port << "\n"; -#endif - async_accept(); - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); } +#endif + if (m_listen_socket) async_accept(); } void session_impl::async_accept() @@ -766,6 +733,7 @@ namespace detail if (m_abort) return; + async_accept(); if (e) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -774,12 +742,8 @@ namespace detail (*m_logger) << msg << "\n"; #endif assert(m_listen_socket.unique()); - // try any random port - m_listen_interface.port(0); - open_listen_port(); return; } - async_accept(); // we got a connection request! m_incoming_connection = true; @@ -801,30 +765,8 @@ namespace detail return; } - // check if we have any active torrents - // if we don't reject the connection - if (m_torrents.empty()) - { - return; - } - else - { - bool has_active_torrent = false; - for (torrent_map::iterator i = m_torrents.begin() - , end(m_torrents.end()); i != end; ++i) - { - if (!i->second->is_paused()) - { - has_active_torrent = true; - break; - } - } - if (!has_active_torrent) - return; - } - boost::intrusive_ptr c( - new bt_peer_connection(*this, s, 0)); + new bt_peer_connection(*this, s, 0)); #ifndef NDEBUG c->m_in_constructor = false; #endif @@ -845,9 +787,6 @@ namespace detail #endif { mutex_t::scoped_lock l(m_mutex); - -// too expensive -// INVARIANT_CHECK; connection_map::iterator p = m_connections.find(s); @@ -879,16 +818,10 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); -// too expensive -// INVARIANT_CHECK; - assert(p->is_disconnecting()); connection_map::iterator i = m_connections.find(p->get_socket()); if (i != m_connections.end()) - { - if (!i->second->is_choked()) --m_num_unchoked; m_connections.erase(i); - } } void session_impl::set_peer_id(peer_id const& id) @@ -907,8 +840,6 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; - if (e) { #if defined(TORRENT_LOGGING) @@ -970,9 +901,7 @@ namespace detail // round robin fashion, so that every torrent is // equallt likely to connect to a peer - if (!m_torrents.empty() - && m_half_open.free_slots() - && num_connections() < m_max_connections) + if (!m_torrents.empty() && m_half_open.free_slots()) { // this is the maximum number of connections we will // attempt this tick @@ -989,13 +918,11 @@ namespace detail { torrent& t = *i->second; if (t.want_more_peers()) - { if (t.try_connect_peer()) { --max_connections; steps_since_last_connect = 0; } - } ++m_next_connect_torrent; ++steps_since_last_connect; ++i; @@ -1013,8 +940,6 @@ namespace detail // if we should not make any more connections // attempts this tick, abort if (max_connections == 0) break; - // maintain the global limit on number of connections - if (num_connections() >= m_max_connections) break; } } @@ -1051,7 +976,18 @@ namespace detail continue; } - c.keep_alive(); + try + { + c.keep_alive(); + } + catch (std::exception& exc) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*c.m_logger) << "**ERROR**: " << exc.what() << "\n"; +#endif + c.set_failed(); + c.disconnect(); + } } // check each torrent for tracker updates @@ -1083,183 +1019,30 @@ namespace detail } m_stat.second_tick(tick_interval); + // distribute the maximum upload rate among the torrents - // -------------------------------------------------------------- - // unchoke set and optimistic unchoke calculations - // -------------------------------------------------------------- - m_unchoke_time_scaler--; - if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) + assert(m_max_uploads >= -1); + assert(m_max_connections >= -1); + + allocate_resources(m_max_uploads == -1 + ? std::numeric_limits::max() + : m_max_uploads + , m_torrents + , &torrent::m_uploads_quota); + + allocate_resources(m_max_connections == -1 + ? std::numeric_limits::max() + : m_max_connections + , m_torrents + , &torrent::m_connections_quota); + + for (std::map >::iterator i + = m_torrents.begin(); i != m_torrents.end(); ++i) { - m_unchoke_time_scaler = settings().unchoke_interval; - - std::vector peers; - for (connection_map::iterator i = m_connections.begin() - , end(m_connections.end()); i != end; ++i) - { - peer_connection* p = i->second.get(); - torrent* t = p->associated_torrent().lock().get(); - if (!p->peer_info_struct() - || t == 0 - || !p->is_peer_interested() - || p->is_disconnecting() - || p->is_connecting() - || (p->share_diff() < -free_upload_amount - && !t->is_seed())) - { - if (!i->second->is_choked() && t) - { - policy::peer* pi = p->peer_info_struct(); - if (pi && pi->optimistically_unchoked) - { - pi->optimistically_unchoked = false; - // force a new optimistic unchoke - m_optimistic_unchoke_time_scaler = 0; - } - t->choke_peer(*i->second); - } - continue; - } - peers.push_back(i->second.get()); - } - - // sort the peers that are eligible for unchoke by download rate and secondary - // by total upload. The reason for this is, if all torrents are being seeded, - // the download rate will be 0, and the peers we have sent the least to should - // be unchoked - std::sort(peers.begin(), peers.end() - , bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _1)) - < bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _2))); - - std::stable_sort(peers.begin(), peers.end() - , bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _1)) - > bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _2))); - - // reserve one upload slot for optimistic unchokes - int unchoke_set_size = m_max_uploads - 1; - - m_num_unchoked = 0; - // go through all the peers and unchoke the first ones and choke - // all the other ones. - for (std::vector::iterator i = peers.begin() - , end(peers.end()); i != end; ++i) - { - peer_connection* p = *i; - assert(p); - torrent* t = p->associated_torrent().lock().get(); - assert(t); - if (unchoke_set_size > 0) - { - if (p->is_choked()) - { - if (!t->unchoke_peer(*p)) - continue; - } - - --unchoke_set_size; - ++m_num_unchoked; - - assert(p->peer_info_struct()); - if (p->peer_info_struct()->optimistically_unchoked) - { - // force a new optimistic unchoke - m_optimistic_unchoke_time_scaler = 0; - p->peer_info_struct()->optimistically_unchoked = false; - } - } - else - { - assert(p->peer_info_struct()); - if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) - t->choke_peer(*p); - if (!p->is_choked()) - ++m_num_unchoked; - } - } - - m_optimistic_unchoke_time_scaler--; - if (m_optimistic_unchoke_time_scaler <= 0) - { - m_optimistic_unchoke_time_scaler - = settings().optimistic_unchoke_multiplier; - - // find the peer that has been waiting the longest to be optimistically - // unchoked - connection_map::iterator current_optimistic_unchoke = m_connections.end(); - connection_map::iterator optimistic_unchoke_candidate = m_connections.end(); - ptime last_unchoke = max_time(); - - for (connection_map::iterator i = m_connections.begin() - , end(m_connections.end()); i != end; ++i) - { - peer_connection* p = i->second.get(); - assert(p); - policy::peer* pi = p->peer_info_struct(); - if (!pi) continue; - torrent* t = p->associated_torrent().lock().get(); - if (!t) continue; - - if (pi->optimistically_unchoked) - { - assert(!p->is_choked()); - assert(current_optimistic_unchoke == m_connections.end()); - current_optimistic_unchoke = i; - } - - if (pi->last_optimistically_unchoked < last_unchoke - && !p->is_connecting() - && !p->is_disconnecting() - && p->is_peer_interested() - && t->free_upload_slots() - && p->is_choked()) - { - last_unchoke = pi->last_optimistically_unchoked; - optimistic_unchoke_candidate = i; - } - } - - if (optimistic_unchoke_candidate != m_connections.end() - && optimistic_unchoke_candidate != current_optimistic_unchoke) - { - if (current_optimistic_unchoke != m_connections.end()) - { - torrent* t = current_optimistic_unchoke->second->associated_torrent().lock().get(); - assert(t); - current_optimistic_unchoke->second->peer_info_struct()->optimistically_unchoked = false; - t->choke_peer(*current_optimistic_unchoke->second); - } - else - { - ++m_num_unchoked; - } - - torrent* t = optimistic_unchoke_candidate->second->associated_torrent().lock().get(); - assert(t); - bool ret = t->unchoke_peer(*optimistic_unchoke_candidate->second); - assert(ret); - optimistic_unchoke_candidate->second->peer_info_struct()->optimistically_unchoked = true; - } - } - } - - // -------------------------------------------------------------- - // disconnect peers when we have too many - // -------------------------------------------------------------- - --m_disconnect_time_scaler; - if (m_disconnect_time_scaler <= 0) - { - m_disconnect_time_scaler = 60; - - // every 60 seconds, disconnect the worst peer - // if we have reached the connection limit - if (num_connections() >= max_connections() && !m_torrents.empty()) - { - torrent_map::iterator i = std::max_element(m_torrents.begin(), m_torrents.end() - , bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _1)) - < bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _2))); - - assert(i != m_torrents.end()); - i->second->get_policy().disconnect_one_peer(); - } +#ifndef NDEBUG + i->second->check_invariant(); +#endif + i->second->distribute_resources(tick_interval); } } catch (std::exception& exc) @@ -1269,7 +1052,29 @@ namespace detail assert(false); #endif }; // msvc 7.1 seems to require this +/* + void session_impl::connection_completed( + boost::intrusive_ptr const& p) try + { + mutex_t::scoped_lock l(m_mutex); + connection_map::iterator i = m_half_open.find(p->get_socket()); + m_connections.insert(std::make_pair(p->get_socket(), p)); + assert(i != m_half_open.end()); + if (i != m_half_open.end()) m_half_open.erase(i); + + if (m_abort) return; + + process_connection_queue(); + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::cerr << e.what() << std::endl; + assert(false); +#endif + }; +*/ void session_impl::operator()() { eh_initializer(); @@ -1278,6 +1083,10 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); open_listen_port(); + if (m_natpmp.get()) + m_natpmp->set_mappings(m_listen_interface.port(), 0); + if (m_upnp.get()) + m_upnp->set_mappings(m_listen_interface.port(), 0); } ptime timer = time_now(); @@ -1347,9 +1156,6 @@ namespace detail } } - // close listen socket - m_listen_socket.reset(); - ptime start(time_now()); l.unlock(); @@ -1469,37 +1275,47 @@ namespace detail } torrent_handle session_impl::add_torrent( - boost::intrusive_ptr ti + torrent_info const& ti , fs::path const& save_path , entry const& resume_data , bool compact_mode - , storage_constructor_type sc - , bool paused - , void* userdata) + , int block_size + , storage_constructor_type sc) { // if you get this assert, you haven't managed to // open a listen port. call listen_on() first. assert(m_external_listen_port > 0); + + // make sure the block_size is an even power of 2 +#ifndef NDEBUG + for (int i = 0; i < 32; ++i) + { + if (block_size & (1 << i)) + { + assert((block_size & ~(1 << i)) == 0); + break; + } + } +#endif + assert(!save_path.empty()); - if (ti->begin_files() == ti->end_files()) + if (ti.begin_files() == ti.end_files()) throw std::runtime_error("no files in torrent"); // lock the session and the checker thread (the order is important!) mutex_t::scoped_lock l(m_mutex); mutex::scoped_lock l2(m_checker_impl.m_mutex); - INVARIANT_CHECK; - if (is_aborted()) throw std::runtime_error("session is closing"); // is the torrent already active? - if (!find_torrent(ti->info_hash()).expired()) + if (!find_torrent(ti.info_hash()).expired()) throw duplicate_torrent(); // is the torrent currently being checked? - if (m_checker_impl.find_torrent(ti->info_hash())) + if (m_checker_impl.find_torrent(ti.info_hash())) throw duplicate_torrent(); // create the torrent and the data associated with @@ -1507,15 +1323,15 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, ti, save_path - , m_listen_interface, compact_mode, 16 * 1024 - , sc, paused)); + , m_listen_interface, compact_mode, block_size + , settings(), sc)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - boost::shared_ptr tp((*i)(torrent_ptr.get(), userdata)); + boost::shared_ptr tp((*i)(torrent_ptr.get())); if (tp) torrent_ptr->add_extension(tp); } #endif @@ -1524,13 +1340,13 @@ namespace detail new aux::piece_checker_data); d->torrent_ptr = torrent_ptr; d->save_path = save_path; - d->info_hash = ti->info_hash(); + d->info_hash = ti.info_hash(); d->resume_data = resume_data; #ifndef TORRENT_DISABLE_DHT if (m_dht) { - torrent_info::nodes_t const& nodes = ti->nodes(); + torrent_info::nodes_t const& nodes = ti.nodes(); std::for_each(nodes.begin(), nodes.end(), bind( (void(dht::dht_tracker::*)(std::pair const&)) &dht::dht_tracker::add_node @@ -1544,7 +1360,7 @@ namespace detail // job in its queue m_checker_impl.m_cond.notify_one(); - return torrent_handle(this, &m_checker_impl, ti->info_hash()); + return torrent_handle(this, &m_checker_impl, ti.info_hash()); } torrent_handle session_impl::add_torrent( @@ -1554,10 +1370,20 @@ namespace detail , fs::path const& save_path , entry const& , bool compact_mode - , storage_constructor_type sc - , bool paused - , void* userdata) + , int block_size + , storage_constructor_type sc) { + // make sure the block_size is an even power of 2 +#ifndef NDEBUG + for (int i = 0; i < 32; ++i) + { + if (block_size & (1 << i)) + { + assert((block_size & ~(1 << i)) == 0); + break; + } + } +#endif // TODO: support resume data in this case assert(!save_path.empty()); @@ -1573,8 +1399,6 @@ namespace detail // lock the session session_impl::mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; - // is the torrent already active? if (!find_torrent(info_hash).expired()) throw duplicate_torrent(); @@ -1587,15 +1411,15 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, tracker_url, info_hash, name - , save_path, m_listen_interface, compact_mode, 16 * 1024 - , sc, paused)); + , save_path, m_listen_interface, compact_mode, block_size + , settings(), sc)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - boost::shared_ptr tp((*i)(torrent_ptr.get(), userdata)); + boost::shared_ptr tp((*i)(torrent_ptr.get())); if (tp) torrent_ptr->add_extension(tp); } #endif @@ -1613,9 +1437,6 @@ namespace detail assert(h.m_ses != 0); mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - session_impl::torrent_map::iterator i = m_torrents.find(h.m_info_hash); if (i != m_torrents.end()) @@ -1678,8 +1499,6 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; - tcp::endpoint new_interface; if (net_interface && std::strlen(net_interface) > 0) new_interface = tcp::endpoint(address::from_string(net_interface), port_range.first); @@ -1703,6 +1522,21 @@ namespace detail bool new_listen_address = m_listen_interface.address() != new_interface.address(); + if (new_listen_address) + { + if (m_natpmp.get()) + m_natpmp->rebind(new_interface.address()); + if (m_upnp.get()) + m_upnp->rebind(new_interface.address()); + if (m_lsd.get()) + m_lsd->rebind(new_interface.address()); + } + + if (m_natpmp.get()) + m_natpmp->set_mappings(m_listen_interface.port(), 0); + if (m_upnp.get()) + m_upnp->set_mappings(m_listen_interface.port(), 0); + #ifndef TORRENT_DISABLE_DHT if ((new_listen_address || m_dht_same_port) && m_dht) { @@ -1744,8 +1578,6 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; - boost::shared_ptr t = find_torrent(ih).lock(); if (!t) return; // don't add peers from lsd to private torrents @@ -1800,9 +1632,6 @@ namespace detail session_status session_impl::status() const { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - session_status s; s.has_incoming_connections = m_incoming_connection; s.num_peers = (int)m_connections.size(); @@ -1844,9 +1673,6 @@ namespace detail void session_impl::start_dht(entry const& startup_state) { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - if (m_dht) { m_dht->stop(); @@ -2006,10 +1832,6 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - - if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_uploads = limit; } @@ -2017,10 +1839,6 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - - if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_connections = limit; } @@ -2028,10 +1846,7 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - - if (limit <= 0) limit = (std::numeric_limits::max)(); + m_half_open.limit(limit); } @@ -2039,10 +1854,7 @@ namespace detail { assert(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - - if (bytes_per_second <= 0) bytes_per_second = bandwidth_limit::inf; + if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; m_bandwidth_manager[peer_connection::download_channel]->throttle(bytes_per_second); } @@ -2050,20 +1862,31 @@ namespace detail { assert(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - - if (bytes_per_second <= 0) bytes_per_second = bandwidth_limit::inf; + if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; m_bandwidth_manager[peer_connection::upload_channel]->throttle(bytes_per_second); } + int session_impl::num_uploads() const + { + int uploads = 0; + mutex_t::scoped_lock l(m_mutex); + for (torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; i++) + { + uploads += i->second->get_policy().num_uploads(); + } + return uploads; + } + + int session_impl::num_connections() const + { + mutex_t::scoped_lock l(m_mutex); + return m_connections.size(); + } + std::auto_ptr session_impl::pop_alert() { mutex_t::scoped_lock l(m_mutex); - -// too expensive -// INVARIANT_CHECK; - if (m_alerts.pending()) return m_alerts.get(); return std::auto_ptr(0); @@ -2078,26 +1901,20 @@ namespace detail int session_impl::upload_rate_limit() const { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - int ret = m_bandwidth_manager[peer_connection::upload_channel]->throttle(); - return ret == (std::numeric_limits::max)() ? -1 : ret; + return ret == std::numeric_limits::max() ? -1 : ret; } int session_impl::download_rate_limit() const { mutex_t::scoped_lock l(m_mutex); int ret = m_bandwidth_manager[peer_connection::download_channel]->throttle(); - return ret == (std::numeric_limits::max)() ? -1 : ret; + return ret == std::numeric_limits::max() ? -1 : ret; } void session_impl::start_lsd() { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - m_lsd.reset(new lsd(m_io_service , m_listen_interface.address() , bind(&session_impl::on_lsd_peer, this, _1, _2))); @@ -2106,9 +1923,6 @@ namespace detail void session_impl::start_natpmp() { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - m_natpmp.reset(new natpmp(m_io_service , m_listen_interface.address() , bind(&session_impl::on_port_mapping @@ -2124,9 +1938,6 @@ namespace detail void session_impl::start_upnp() { mutex_t::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - m_upnp.reset(new upnp(m_io_service, m_half_open , m_listen_interface.address() , m_settings.user_agent @@ -2164,35 +1975,20 @@ namespace detail #ifndef NDEBUG - void session_impl::check_invariant() const + void session_impl::check_invariant(const char *place) { - assert(m_max_connections > 0); - assert(m_max_uploads > 0); - int unchokes = 0; - int num_optimistic = 0; - for (connection_map::const_iterator i = m_connections.begin(); + assert(place); + for (connection_map::iterator i = m_connections.begin(); i != m_connections.end(); ++i) { assert(i->second); boost::shared_ptr t = i->second->associated_torrent().lock(); - if (!i->second->is_choked()) ++unchokes; - if (i->second->peer_info_struct() - && i->second->peer_info_struct()->optimistically_unchoked) - { - ++num_optimistic; - assert(!i->second->is_choked()); - } - if (t && i->second->peer_info_struct()) + if (t) { assert(t->get_policy().has_connection(boost::get_pointer(i->second))); } } - assert(num_optimistic == 0 || num_optimistic == 1); - if (m_num_unchoked != unchokes) - { - assert(false); - } } #endif @@ -2305,7 +2101,7 @@ namespace detail const std::string& bitmask = (*i)["bitmask"].string(); - const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); + const int num_bitmask_bytes = std::max(num_blocks_per_piece / 8, 1); if ((int)bitmask.size() != num_bitmask_bytes) { error = "invalid size of bitmask (" + boost::lexical_cast(bitmask.size()) + ")"; @@ -2314,7 +2110,7 @@ namespace detail for (int j = 0; j < num_bitmask_bytes; ++j) { unsigned char bits = bitmask[j]; - int num_bits = (std::min)(num_blocks_per_piece - j*8, 8); + int num_bits = std::min(num_blocks_per_piece - j*8, 8); for (int k = 0; k < num_bits; ++k) { const int bit = j * 8 + k; diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp index a6b5544e4..b1679c4ac 100644 --- a/libtorrent/src/socks5_stream.cpp +++ b/libtorrent/src/socks5_stream.cpp @@ -33,7 +33,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include "libtorrent/socks5_stream.hpp" -#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index dbf6a9382..b23a2e858 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -88,12 +88,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif -#if defined(__FreeBSD__) -// for statfs() -#include -#include -#endif - #if defined(_WIN32) && defined(UNICODE) #include @@ -253,8 +247,8 @@ namespace libtorrent { p = complete(p); std::vector > sizes; - for (torrent_info::file_iterator i = t.begin_files(true); - i != t.end_files(true); ++i) + for (torrent_info::file_iterator i = t.begin_files(); + i != t.end_files(); ++i) { size_type size = 0; std::time_t time = 0; @@ -293,7 +287,7 @@ namespace libtorrent , bool compact_mode , std::string* error) { - if ((int)sizes.size() != t.num_files(true)) + if ((int)sizes.size() != t.num_files()) { if (error) *error = "mismatching number of files"; return false; @@ -302,8 +296,8 @@ namespace libtorrent std::vector >::const_iterator s = sizes.begin(); - for (torrent_info::file_iterator i = t.begin_files(true); - i != t.end_files(true); ++i, ++s) + for (torrent_info::file_iterator i = t.begin_files(); + i != t.end_files(); ++i, ++s) { size_type size = 0; std::time_t time = 0; @@ -348,14 +342,50 @@ namespace libtorrent return true; } - class storage : public storage_interface, boost::noncopyable + struct thread_safe_storage + { + thread_safe_storage(std::size_t n) + : slots(n, false) + {} + + boost::mutex mutex; + boost::condition condition; + std::vector slots; + }; + + struct slot_lock + { + slot_lock(thread_safe_storage& s, int slot_) + : storage_(s) + , slot(slot_) + { + assert(slot_>=0 && slot_ < (int)s.slots.size()); + boost::mutex::scoped_lock lock(storage_.mutex); + + while (storage_.slots[slot]) + storage_.condition.wait(lock); + storage_.slots[slot] = true; + } + + ~slot_lock() + { + storage_.slots[slot] = false; + storage_.condition.notify_all(); + } + + thread_safe_storage& storage_; + int slot; + }; + + class storage : public storage_interface, thread_safe_storage, boost::noncopyable { public: - storage(boost::intrusive_ptr info, fs::path const& path, file_pool& fp) - : m_info(info) + storage(torrent_info const& info, fs::path const& path, file_pool& fp) + : thread_safe_storage(info.num_pieces()) + , m_info(info) , m_files(fp) { - assert(info->begin_files(true) != info->end_files(true)); + assert(info.begin_files() != info.end_files()); m_save_path = fs::complete(path); assert(m_save_path.is_complete()); } @@ -375,9 +405,11 @@ namespace libtorrent size_type read_impl(char* buf, int slot, int offset, int size, bool fill_zero); ~storage() - { m_files.release(this); } + { + m_files.release(this); + } - boost::intrusive_ptr m_info; + torrent_info const& m_info; fs::path m_save_path; // the file pool is typically stored in // the session, to make all storage @@ -403,27 +435,21 @@ namespace libtorrent assert(ph.offset == 0 || partial_copy.final() == partial.final()); #endif int slot_size = piece_size - ph.offset; - if (slot_size > 0) - { - m_scratch_buffer.resize(slot_size); - read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); - ph.h.update(&m_scratch_buffer[0], slot_size); - } -#ifndef NDEBUG + if (slot_size == 0) return ph.h.final(); + m_scratch_buffer.resize(slot_size); + read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); + ph.h.update(&m_scratch_buffer[0], slot_size); sha1_hash ret = ph.h.final(); - assert(ret == whole.final()); + assert(whole.final() == ret); return ret; -#else - return ph.h.final(); -#endif } void storage::initialize(bool allocate_files) { // first, create all missing directories fs::path last_path; - for (torrent_info::file_iterator file_iter = m_info->begin_files(true), - end_iter = m_info->end_files(true); file_iter != end_iter; ++file_iter) + for (torrent_info::file_iterator file_iter = m_info.begin_files(), + end_iter = m_info.end_files(); file_iter != end_iter; ++file_iter) { fs::path dir = (m_save_path / file_iter->path).branch_path(); @@ -471,7 +497,7 @@ namespace libtorrent void storage::write_resume_data(entry& rd) const { std::vector > file_sizes - = get_filesizes(*m_info, m_save_path); + = get_filesizes(m_info, m_save_path); rd["file sizes"] = entry::list_type(); entry::list_type& fl = rd["file sizes"].list(); @@ -505,7 +531,7 @@ namespace libtorrent } entry::list_type& slots = rd["slots"].list(); - bool seed = int(slots.size()) == m_info->num_pieces() + bool seed = int(slots.size()) == m_info.num_pieces() && std::find_if(slots.begin(), slots.end() , boost::bind(std::less() , boost::bind((size_type const& (entry::*)() const) @@ -520,11 +546,11 @@ namespace libtorrent if (seed) { - if (m_info->num_files(true) != (int)file_sizes.size()) + if (m_info.num_files() != (int)file_sizes.size()) { error = "the number of files does not match the torrent (num: " + boost::lexical_cast(file_sizes.size()) + " actual: " - + boost::lexical_cast(m_info->num_files(true)) + ")"; + + boost::lexical_cast(m_info.num_files()) + ")"; return false; } @@ -532,8 +558,8 @@ namespace libtorrent fs = file_sizes.begin(); // the resume data says we have the entire torrent // make sure the file sizes are the right ones - for (torrent_info::file_iterator i = m_info->begin_files(true) - , end(m_info->end_files(true)); i != end; ++i, ++fs) + for (torrent_info::file_iterator i = m_info.begin_files() + , end(m_info.end_files()); i != end; ++i, ++fs) { if (i->size != fs->first) { @@ -546,7 +572,7 @@ namespace libtorrent return true; } - return match_filesizes(*m_info, m_save_path, file_sizes + return match_filesizes(m_info, m_save_path, file_sizes , !full_allocation_mode, &error); } @@ -585,11 +611,11 @@ namespace libtorrent m_files.release(this); #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 - old_path = safe_convert((m_save_path / m_info->name()).string()); - new_path = safe_convert((save_path / m_info->name()).string()); + old_path = safe_convert((m_save_path / m_info.name()).string()); + new_path = safe_convert((save_path / m_info.name()).string()); #else - old_path = m_save_path / m_info->name(); - new_path = save_path / m_info->name(); + old_path = m_save_path / m_info.name(); + new_path = save_path / m_info.name(); #endif try @@ -611,7 +637,7 @@ namespace libtorrent /* void storage::shuffle() { - int num_pieces = m_info->num_pieces(); + int num_pieces = m_info.num_pieces(); std::vector pieces(num_pieces); for (std::vector::iterator i = pieces.begin(); @@ -628,7 +654,7 @@ namespace libtorrent { const int slot_index = targets[i]; const int piece_index = pieces[i]; - const int slot_size =static_cast(m_info->piece_size(slot_index)); + const int slot_size =static_cast(m_info.piece_size(slot_index)); std::vector buf(slot_size); read(&buf[0], piece_index, 0, slot_size); write(&buf[0], slot_index, 0, slot_size); @@ -639,7 +665,7 @@ namespace libtorrent void storage::move_slot(int src_slot, int dst_slot) { - int piece_size = m_info->piece_size(dst_slot); + int piece_size = m_info.piece_size(dst_slot); m_scratch_buffer.resize(piece_size); read_impl(&m_scratch_buffer[0], src_slot, 0, piece_size, true); write(&m_scratch_buffer[0], dst_slot, 0, piece_size); @@ -648,9 +674,9 @@ namespace libtorrent void storage::swap_slots(int slot1, int slot2) { // the size of the target slot is the size of the piece - int piece_size = m_info->piece_length(); - int piece1_size = m_info->piece_size(slot2); - int piece2_size = m_info->piece_size(slot1); + int piece_size = m_info.piece_length(); + int piece1_size = m_info.piece_size(slot2); + int piece2_size = m_info.piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); @@ -661,10 +687,10 @@ namespace libtorrent void storage::swap_slots3(int slot1, int slot2, int slot3) { // the size of the target slot is the size of the piece - int piece_size = m_info->piece_length(); - int piece1_size = m_info->piece_size(slot2); - int piece2_size = m_info->piece_size(slot3); - int piece3_size = m_info->piece_size(slot1); + int piece_size = m_info.piece_length(); + int piece1_size = m_info.piece_size(slot2); + int piece2_size = m_info.piece_size(slot3); + int piece3_size = m_info.piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); @@ -691,25 +717,27 @@ namespace libtorrent , bool fill_zero) { assert(buf != 0); - assert(slot >= 0 && slot < m_info->num_pieces()); + assert(slot >= 0 && slot < m_info.num_pieces()); assert(offset >= 0); - assert(offset < m_info->piece_size(slot)); + assert(offset < m_info.piece_size(slot)); assert(size > 0); + slot_lock lock(*this, slot); + #ifndef NDEBUG std::vector slices - = m_info->map_block(slot, offset, size, true); + = m_info.map_block(slot, offset, size); assert(!slices.empty()); #endif - size_type start = slot * (size_type)m_info->piece_length() + offset; - assert(start + size <= m_info->total_size()); + size_type start = slot * (size_type)m_info.piece_length() + offset; + assert(start + size <= m_info.total_size()); // find the file iterator and file offset size_type file_offset = start; std::vector::const_iterator file_iter; - for (file_iter = m_info->begin_files(true);;) + for (file_iter = m_info.begin_files();;) { if (file_offset < file_iter->size) break; @@ -742,7 +770,7 @@ namespace libtorrent #endif int left_to_read = size; - int slot_size = static_cast(m_info->piece_size(slot)); + int slot_size = static_cast(m_info.piece_size(slot)); if (offset + left_to_read > slot_size) left_to_read = slot_size - offset; @@ -767,7 +795,7 @@ namespace libtorrent assert(int(slices.size()) > counter); size_type slice_size = slices[counter].size; assert(slice_size == read_bytes); - assert(m_info->file_at(slices[counter].file_index, true).path + assert(m_info.file_at(slices[counter].file_index).path == file_iter->path); #endif @@ -817,30 +845,32 @@ namespace libtorrent { assert(buf != 0); assert(slot >= 0); - assert(slot < m_info->num_pieces()); + assert(slot < m_info.num_pieces()); assert(offset >= 0); assert(size > 0); + slot_lock lock(*this, slot); + #ifndef NDEBUG std::vector slices - = m_info->map_block(slot, offset, size, true); + = m_info.map_block(slot, offset, size); assert(!slices.empty()); #endif - size_type start = slot * (size_type)m_info->piece_length() + offset; + size_type start = slot * (size_type)m_info.piece_length() + offset; // find the file iterator and file offset size_type file_offset = start; std::vector::const_iterator file_iter; - for (file_iter = m_info->begin_files(true);;) + for (file_iter = m_info.begin_files();;) { if (file_offset < file_iter->size) break; file_offset -= file_iter->size; ++file_iter; - assert(file_iter != m_info->end_files(true)); + assert(file_iter != m_info.end_files()); } fs::path p(m_save_path / file_iter->path); @@ -860,7 +890,7 @@ namespace libtorrent } int left_to_write = size; - int slot_size = static_cast(m_info->piece_size(slot)); + int slot_size = static_cast(m_info.piece_size(slot)); if (offset + left_to_write > slot_size) left_to_write = slot_size - offset; @@ -884,7 +914,7 @@ namespace libtorrent { assert(int(slices.size()) > counter); assert(slices[counter].size == write_bytes); - assert(m_info->file_at(slices[counter].file_index, true).path + assert(m_info.file_at(slices[counter].file_index).path == file_iter->path); assert(buf_pos >= 0); @@ -912,7 +942,7 @@ namespace libtorrent #endif ++file_iter; - assert(file_iter != m_info->end_files(true)); + assert(file_iter != m_info.end_files()); fs::path p = m_save_path / file_iter->path; file_offset = 0; out = m_files.open_file( @@ -923,7 +953,7 @@ namespace libtorrent } } - storage_interface* default_storage_constructor(boost::intrusive_ptr ti + storage_interface* default_storage_constructor(torrent_info const& ti , fs::path const& path, file_pool& fp) { return new storage(ti, path, fp); @@ -1002,6 +1032,9 @@ namespace libtorrent int err = statfs(query_path.native_directory_string().c_str(), &buf); if (err == 0) { +#ifndef NDEBUG + std::cerr << "buf.f_type " << std::hex << buf.f_type << std::endl; +#endif switch (buf.f_type) { case 0x5346544e: // NTFS @@ -1034,7 +1067,7 @@ namespace libtorrent piece_manager::piece_manager( boost::shared_ptr const& torrent - , boost::intrusive_ptr ti + , torrent_info const& ti , fs::path const& save_path , file_pool& fp , disk_io_thread& io @@ -1044,7 +1077,7 @@ namespace libtorrent , m_fill_mode(true) , m_info(ti) , m_save_path(complete(save_path)) - , m_storage_constructor(sc) + , m_allocating(false) , m_io_thread(io) , m_torrent(torrent) { @@ -1086,9 +1119,7 @@ namespace libtorrent void piece_manager::async_read( peer_request const& r - , boost::function const& handler - , char* buffer - , int priority) + , boost::function const& handler) { disk_io_job j; j.storage = this; @@ -1096,11 +1127,7 @@ namespace libtorrent j.piece = r.piece; j.offset = r.start; j.buffer_size = r.length; - j.buffer = buffer; - j.priority = priority; - // if a buffer is not specified, only one block can be read - // since that is the size of the pool allocator's buffers - assert(r.length <= 16 * 1024 || buffer != 0); + assert(r.length <= 16 * 1024); m_io_thread.add_job(j, handler); } @@ -1153,7 +1180,7 @@ namespace libtorrent int slot = m_piece_to_slot[piece]; assert(slot != has_no_slot); - return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece)); + return m_storage->hash_for_slot(slot, ph, m_info.piece_size(piece)); } void piece_manager::release_files_impl() @@ -1213,7 +1240,7 @@ namespace libtorrent int piece_manager::slot_for_piece(int piece_index) const { - assert(piece_index >= 0 && piece_index < m_info->num_pieces()); + assert(piece_index >= 0 && piece_index < m_info.num_pieces()); return m_piece_to_slot[piece_index]; } @@ -1224,13 +1251,13 @@ namespace libtorrent try { assert(slot_index >= 0); - assert(slot_index < m_info->num_pieces()); + assert(slot_index < m_info.num_pieces()); assert(block_size > 0); adler32_crc crc; std::vector buf(block_size); - int num_blocks = static_cast(m_info->piece_size(slot_index)) / block_size; - int last_block_size = static_cast(m_info->piece_size(slot_index)) % block_size; + int num_blocks = static_cast(m_info.piece_size(slot_index)) / block_size; + int last_block_size = static_cast(m_info.piece_size(slot_index)) % block_size; if (last_block_size == 0) last_block_size = block_size; for (int i = 0; i < num_blocks-1; ++i) @@ -1300,7 +1327,6 @@ namespace libtorrent if (i != m_piece_hasher.end()) { assert(i->second.offset > 0); - assert(offset >= i->second.offset); if (offset == i->second.offset) { i->second.offset += size; @@ -1324,11 +1350,11 @@ namespace libtorrent { // INVARIANT_CHECK; - assert((int)have_pieces.size() == m_info->num_pieces()); + assert((int)have_pieces.size() == m_info.num_pieces()); - const int piece_size = static_cast(m_info->piece_length()); - const int last_piece_size = static_cast(m_info->piece_size( - m_info->num_pieces() - 1)); + const int piece_size = static_cast(m_info.piece_length()); + const int last_piece_size = static_cast(m_info.piece_size( + m_info.num_pieces() - 1)); assert((int)piece_data.size() >= last_piece_size); @@ -1484,7 +1510,7 @@ namespace libtorrent INVARIANT_CHECK; - assert(m_info->piece_length() > 0); + assert(m_info.piece_length() > 0); m_compact_mode = compact_mode; @@ -1494,13 +1520,13 @@ namespace libtorrent // by check_pieces. // m_storage->shuffle(); - m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); - m_slot_to_piece.resize(m_info->num_pieces(), unallocated); + m_piece_to_slot.resize(m_info.num_pieces(), has_no_slot); + m_slot_to_piece.resize(m_info.num_pieces(), unallocated); m_free_slots.clear(); m_unallocated_slots.clear(); pieces.clear(); - pieces.resize(m_info->num_pieces(), false); + pieces.resize(m_info.num_pieces(), false); num_pieces = 0; // if we have fast-resume info @@ -1605,7 +1631,7 @@ namespace libtorrent return std::make_pair(false, 1.f); } - if (int(m_unallocated_slots.size()) == m_info->num_pieces() + if (int(m_unallocated_slots.size()) == m_info.num_pieces() && !m_fill_mode) { // if there is not a single file on disk, just @@ -1647,8 +1673,8 @@ namespace libtorrent assert(!m_fill_mode); std::vector().swap(m_unallocated_slots); std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); - m_free_slots.resize(m_info->num_pieces()); - for (int i = 0; i < m_info->num_pieces(); ++i) + m_free_slots.resize(m_info.num_pieces()); + for (int i = 0; i < m_info.num_pieces(); ++i) m_free_slots[i] = i; } @@ -1668,15 +1694,15 @@ namespace libtorrent if (m_hash_to_piece.empty()) { m_current_slot = 0; - for (int i = 0; i < m_info->num_pieces(); ++i) + for (int i = 0; i < m_info.num_pieces(); ++i) { - m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); + m_hash_to_piece.insert(std::make_pair(m_info.hash_for_piece(i), i)); } std::fill(pieces.begin(), pieces.end(), false); } - m_piece_data.resize(int(m_info->piece_length())); - int piece_size = int(m_info->piece_size(m_current_slot)); + m_piece_data.resize(int(m_info.piece_length())); + int piece_size = int(m_info.piece_size(m_current_slot)); int num_read = m_storage->read(&m_piece_data[0] , m_current_slot, 0, piece_size); @@ -1879,9 +1905,9 @@ namespace libtorrent { // find the file that failed, and skip all the blocks in that file size_type file_offset = 0; - size_type current_offset = m_current_slot * m_info->piece_length(); - for (torrent_info::file_iterator i = m_info->begin_files(true); - i != m_info->end_files(true); ++i) + size_type current_offset = m_current_slot * m_info.piece_length(); + for (torrent_info::file_iterator i = m_info.begin_files(); + i != m_info.end_files(); ++i) { file_offset += i->size; if (file_offset > current_offset) break; @@ -1889,8 +1915,8 @@ namespace libtorrent assert(file_offset > current_offset); int skip_blocks = static_cast( - (file_offset - current_offset + m_info->piece_length() - 1) - / m_info->piece_length()); + (file_offset - current_offset + m_info.piece_length() - 1) + / m_info.piece_length()); for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) { @@ -1903,9 +1929,9 @@ namespace libtorrent } ++m_current_slot; - if (m_current_slot >= m_info->num_pieces()) + if (m_current_slot >= m_info.num_pieces()) { - assert(m_current_slot == m_info->num_pieces()); + assert(m_current_slot == m_info.num_pieces()); // clear the memory we've been using std::vector().swap(m_piece_data); @@ -1917,7 +1943,7 @@ namespace libtorrent assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + return std::make_pair(false, (float)m_current_slot / m_info.num_pieces()); } int piece_manager::allocate_slot_for_piece(int piece_index) @@ -1959,7 +1985,7 @@ namespace libtorrent // special case to make sure we don't use the last slot // when we shouldn't, since it's smaller than ordinary slots - if (*iter == m_info->num_pieces() - 1 && piece_index != *iter) + if (*iter == m_info.num_pieces() - 1 && piece_index != *iter) { if (m_free_slots.size() == 1) allocate_slots(1); @@ -2064,13 +2090,13 @@ namespace libtorrent } else if (m_fill_mode) { - int piece_size = int(m_info->piece_size(pos)); + int piece_size = int(m_info.piece_size(pos)); int offset = 0; for (; piece_size > 0; piece_size -= stack_buffer_size , offset += stack_buffer_size) { m_storage->write(zeroes, pos, offset - , (std::min)(piece_size, stack_buffer_size)); + , std::min(piece_size, stack_buffer_size)); } written = true; } @@ -2090,8 +2116,8 @@ namespace libtorrent boost::recursive_mutex::scoped_lock lock(m_mutex); if (m_piece_to_slot.empty()) return; - assert((int)m_piece_to_slot.size() == m_info->num_pieces()); - assert((int)m_slot_to_piece.size() == m_info->num_pieces()); + assert((int)m_piece_to_slot.size() == m_info.num_pieces()); + assert((int)m_slot_to_piece.size() == m_info.num_pieces()); for (std::vector::const_iterator i = m_free_slots.begin(); i != m_free_slots.end(); ++i) @@ -2113,7 +2139,7 @@ namespace libtorrent == m_unallocated_slots.end()); } - for (int i = 0; i < m_info->num_pieces(); ++i) + for (int i = 0; i < m_info.num_pieces(); ++i) { // Check domain of piece_to_slot's elements if (m_piece_to_slot[i] != has_no_slot) @@ -2201,7 +2227,7 @@ namespace libtorrent s << "index\tslot\tpiece\n"; - for (int i = 0; i < m_info->num_pieces(); ++i) + for (int i = 0; i < m_info.num_pieces(); ++i) { s << i << "\t" << m_slot_to_piece[i] << "\t"; s << m_piece_to_slot[i] << "\n"; diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 252461bc6..13309c1e7 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -72,7 +72,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/instantiate_connection.hpp" -#include "libtorrent/assert.hpp" using namespace libtorrent; using boost::tuples::tuple; @@ -151,16 +150,16 @@ namespace libtorrent torrent::torrent( session_impl& ses , aux::checker_impl& checker - , boost::intrusive_ptr tf + , torrent_info const& tf , fs::path const& save_path , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , storage_constructor_type sc - , bool paused) + , session_settings const& s + , storage_constructor_type sc) : m_torrent_file(tf) , m_abort(false) - , m_paused(paused) + , m_paused(false) , m_just_paused(false) , m_event(tracker_request::started) , m_block_size(0) @@ -182,7 +181,7 @@ namespace libtorrent , m_ses(ses) , m_checker(checker) , m_picker(0) - , m_trackers(m_torrent_file->trackers()) + , m_trackers(m_torrent_file.trackers()) , m_last_working_tracker(-1) , m_currently_trying_tracker(0) , m_failed_trackers(0) @@ -198,15 +197,24 @@ namespace libtorrent , m_compact_mode(compact_mode) , m_default_block_size(block_size) , m_connections_initialized(true) - , m_settings(ses.settings()) + , m_settings(s) , m_storage_constructor(sc) - , m_max_uploads((std::numeric_limits::max)()) - , m_num_uploads(0) - , m_max_connections((std::numeric_limits::max)()) { +#ifndef NDEBUG + m_initial_done = 0; +#endif + + m_uploads_quota.min = 0; + m_connections_quota.min = 2; + // this will be corrected the next time the main session + // distributes resources, i.e. on average in 0.5 seconds + m_connections_quota.given = 100; + m_uploads_quota.max = std::numeric_limits::max(); + m_connections_quota.max = std::numeric_limits::max(); m_policy.reset(new policy(this)); } + torrent::torrent( session_impl& ses , aux::checker_impl& checker @@ -217,11 +225,11 @@ namespace libtorrent , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , storage_constructor_type sc - , bool paused) - : m_torrent_file(new torrent_info(info_hash)) + , session_settings const& s + , storage_constructor_type sc) + : m_torrent_file(info_hash) , m_abort(false) - , m_paused(paused) + , m_paused(false) , m_just_paused(false) , m_event(tracker_request::started) , m_block_size(0) @@ -258,20 +266,28 @@ namespace libtorrent , m_compact_mode(compact_mode) , m_default_block_size(block_size) , m_connections_initialized(false) - , m_settings(ses.settings()) + , m_settings(s) , m_storage_constructor(sc) - , m_max_uploads((std::numeric_limits::max)()) - , m_num_uploads(0) - , m_max_connections((std::numeric_limits::max)()) { +#ifndef NDEBUG + m_initial_done = 0; +#endif + INVARIANT_CHECK; if (name) m_name.reset(new std::string(name)); + m_uploads_quota.min = 0; + m_connections_quota.min = 2; + // this will be corrected the next time the main session + // distributes resources, i.e. on average in 0.5 seconds + m_connections_quota.given = 100; + m_uploads_quota.max = std::numeric_limits::max(); + m_connections_quota.max = std::numeric_limits::max(); if (tracker_url) { m_trackers.push_back(announce_entry(tracker_url)); - m_torrent_file->add_tracker(tracker_url); + m_torrent_file.add_tracker(tracker_url); } m_policy.reset(new policy(this)); @@ -280,7 +296,7 @@ namespace libtorrent void torrent::start() { boost::weak_ptr self(shared_from_this()); - if (m_torrent_file->is_valid()) init(); + if (m_torrent_file.is_valid()) init(); m_announce_timer.expires_from_now(seconds(1)); m_announce_timer.async_wait(m_ses.m_strand.wrap( bind(&torrent::on_announce_disp, self, _1))); @@ -290,7 +306,7 @@ namespace libtorrent bool torrent::should_announce_dht() const { // don't announce private torrents - if (m_torrent_file->is_valid() && m_torrent_file->priv()) return false; + if (m_torrent_file.is_valid() && m_torrent_file.priv()) return false; if (m_trackers.empty()) return true; @@ -313,14 +329,6 @@ namespace libtorrent INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) - for (peer_iterator i = m_connections.begin(); - i != m_connections.end(); ++i) - { - (*i->second->m_logger) << "*** DESTRUCTING TORRENT\n"; - } -#endif - assert(m_abort); if (!m_connections.empty()) disconnect_all(); @@ -328,7 +336,7 @@ namespace libtorrent std::string torrent::name() const { - if (valid_metadata()) return m_torrent_file->name(); + if (valid_metadata()) return m_torrent_file.name(); if (m_name) return *m_name; return ""; } @@ -344,22 +352,22 @@ namespace libtorrent // shared_from_this() void torrent::init() { - assert(m_torrent_file->is_valid()); - assert(m_torrent_file->num_files() > 0); - assert(m_torrent_file->total_size() >= 0); + assert(m_torrent_file.is_valid()); + assert(m_torrent_file.num_files() > 0); + assert(m_torrent_file.total_size() >= 0); - m_have_pieces.resize(m_torrent_file->num_pieces(), false); + m_have_pieces.resize(m_torrent_file.num_pieces(), false); // the shared_from_this() will create an intentional // cycle of ownership, se the hpp file for description. m_owning_storage = new piece_manager(shared_from_this(), m_torrent_file , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor); m_storage = m_owning_storage.get(); - m_block_size = calculate_block_size(*m_torrent_file, m_default_block_size); + m_block_size = calculate_block_size(m_torrent_file, m_default_block_size); m_picker.reset(new piece_picker( - static_cast(m_torrent_file->piece_length() / m_block_size) - , static_cast((m_torrent_file->total_size()+m_block_size-1)/m_block_size))); + static_cast(m_torrent_file.piece_length() / m_block_size) + , static_cast((m_torrent_file.total_size()+m_block_size-1)/m_block_size))); - std::vector const& url_seeds = m_torrent_file->url_seeds(); + std::vector const& url_seeds = m_torrent_file.url_seeds(); std::copy(url_seeds.begin(), url_seeds.end(), std::inserter(m_web_seeds , m_web_seeds.begin())); } @@ -387,7 +395,7 @@ namespace libtorrent { boost::weak_ptr self(shared_from_this()); - if (!m_torrent_file->priv()) + if (!m_torrent_file.priv()) { // announce on local network every 5 minutes m_announce_timer.expires_from_now(minutes(5)); @@ -395,7 +403,7 @@ namespace libtorrent bind(&torrent::on_announce_disp, self, _1))); // announce with the local discovery service - m_ses.announce_lsd(m_torrent_file->info_hash()); + m_ses.announce_lsd(m_torrent_file.info_hash()); } else { @@ -413,7 +421,7 @@ namespace libtorrent // TODO: There should be a way to abort an announce operation on the dht. // when the torrent is destructed assert(m_ses.m_external_listen_port > 0); - m_ses.m_dht->announce(m_torrent_file->info_hash() + m_ses.m_dht->announce(m_torrent_file.info_hash() , m_ses.m_external_listen_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } @@ -459,7 +467,7 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_torrent_file->trackers().empty()) return false; + if (m_torrent_file.trackers().empty()) return false; if (m_just_paused) { @@ -609,7 +617,7 @@ namespace libtorrent // if we don't have the metadata yet, we // cannot tell how big the torrent is. if (!valid_metadata()) return -1; - return m_torrent_file->total_size() + return m_torrent_file.total_size() - quantized_bytes_done(); } @@ -619,23 +627,23 @@ namespace libtorrent if (!valid_metadata()) return 0; - if (m_torrent_file->num_pieces() == 0) + if (m_torrent_file.num_pieces() == 0) return 0; - if (is_seed()) return m_torrent_file->total_size(); + if (is_seed()) return m_torrent_file.total_size(); - const int last_piece = m_torrent_file->num_pieces() - 1; + const int last_piece = m_torrent_file.num_pieces() - 1; size_type total_done - = m_num_pieces * m_torrent_file->piece_length(); + = m_num_pieces * m_torrent_file.piece_length(); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { - int corr = m_torrent_file->piece_size(last_piece) - - m_torrent_file->piece_length(); + int corr = m_torrent_file.piece_size(last_piece) + - m_torrent_file.piece_length(); total_done += corr; } return total_done; @@ -648,42 +656,42 @@ namespace libtorrent { INVARIANT_CHECK; - if (!valid_metadata() || m_torrent_file->num_pieces() == 0) + if (!valid_metadata() || m_torrent_file.num_pieces() == 0) return tuple(0,0); - const int last_piece = m_torrent_file->num_pieces() - 1; + const int last_piece = m_torrent_file.num_pieces() - 1; if (is_seed()) - return make_tuple(m_torrent_file->total_size() - , m_torrent_file->total_size()); + return make_tuple(m_torrent_file.total_size() + , m_torrent_file.total_size()); size_type wanted_done = (m_num_pieces - m_picker->num_have_filtered()) - * m_torrent_file->piece_length(); + * m_torrent_file.piece_length(); size_type total_done - = m_num_pieces * m_torrent_file->piece_length(); - assert(m_num_pieces < m_torrent_file->num_pieces()); + = m_num_pieces * m_torrent_file.piece_length(); + assert(m_num_pieces < m_torrent_file.num_pieces()); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { - int corr = m_torrent_file->piece_size(last_piece) - - m_torrent_file->piece_length(); + int corr = m_torrent_file.piece_size(last_piece) + - m_torrent_file.piece_length(); total_done += corr; if (m_picker->piece_priority(last_piece) != 0) wanted_done += corr; } - assert(total_done <= m_torrent_file->total_size()); - assert(wanted_done <= m_torrent_file->total_size()); + assert(total_done <= m_torrent_file.total_size()); + assert(wanted_done <= m_torrent_file.total_size()); const std::vector& dl_queue = m_picker->get_download_queue(); const int blocks_per_piece = static_cast( - m_torrent_file->piece_length() / m_block_size); + m_torrent_file.piece_length() / m_block_size); for (std::vector::const_iterator i = dl_queue.begin(); i != dl_queue.end(); ++i) @@ -716,15 +724,15 @@ namespace libtorrent == piece_picker::block_info::state_finished) { corr -= m_block_size; - corr += m_torrent_file->piece_size(last_piece) % m_block_size; + corr += m_torrent_file.piece_size(last_piece) % m_block_size; } total_done += corr; if (m_picker->piece_priority(index) != 0) wanted_done += corr; } - assert(total_done <= m_torrent_file->total_size()); - assert(wanted_done <= m_torrent_file->total_size()); + assert(total_done <= m_torrent_file.total_size()); + assert(wanted_done <= m_torrent_file.total_size()); std::map downloading_piece; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -754,10 +762,10 @@ namespace libtorrent } #ifndef NDEBUG assert(p->bytes_downloaded <= p->full_block_bytes); - int last_piece = m_torrent_file->num_pieces() - 1; + int last_piece = m_torrent_file.num_pieces() - 1; if (p->piece_index == last_piece - && p->block_index == m_torrent_file->piece_size(last_piece) / block_size()) - assert(p->full_block_bytes == m_torrent_file->piece_size(last_piece) % block_size()); + && p->block_index == m_torrent_file.piece_size(last_piece) / block_size()) + assert(p->full_block_bytes == m_torrent_file.piece_size(last_piece) % block_size()); else assert(p->full_block_bytes == block_size()); #endif @@ -773,7 +781,7 @@ namespace libtorrent #ifndef NDEBUG - if (total_done >= m_torrent_file->total_size()) + if (total_done >= m_torrent_file.total_size()) { std::copy(m_have_pieces.begin(), m_have_pieces.end() , std::ostream_iterator(std::cerr, " ")); @@ -804,8 +812,8 @@ namespace libtorrent } - assert(total_done <= m_torrent_file->total_size()); - assert(wanted_done <= m_torrent_file->total_size()); + assert(total_done <= m_torrent_file.total_size()); + assert(wanted_done <= m_torrent_file.total_size()); #endif @@ -902,14 +910,14 @@ namespace libtorrent // think that it has received all of it until this function // resets the download queue. So, we cannot do the // invariant check here since it assumes: - // (total_done == m_torrent_file->total_size()) => is_seed() + // (total_done == m_torrent_file.total_size()) => is_seed() // INVARIANT_CHECK; assert(m_storage); assert(m_storage->refcount() > 0); assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + assert(index < m_torrent_file.num_pieces()); if (m_ses.m_alerts.should_post(alert::info)) { @@ -918,7 +926,7 @@ namespace libtorrent m_ses.m_alerts.post_alert(hash_failed_alert(get_handle(), index, s.str())); } // increase the total amount of failed bytes - m_total_failed_bytes += m_torrent_file->piece_size(index); + m_total_failed_bytes += m_torrent_file.piece_size(index); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1012,15 +1020,6 @@ namespace libtorrent m_event = tracker_request::stopped; // disconnect all peers and close all // files belonging to the torrents - -#if defined(TORRENT_VERBOSE_LOGGING) - for (peer_iterator i = m_connections.begin(); - i != m_connections.end(); ++i) - { - (*i->second->m_logger) << "*** ABORTING TORRENT\n"; - } -#endif - disconnect_all(); if (m_owning_storage.get()) m_storage->async_release_files(); m_owning_storage = 0; @@ -1041,7 +1040,7 @@ namespace libtorrent // INVARIANT_CHECK; assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + assert(index < m_torrent_file.num_pieces()); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1084,7 +1083,7 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file->seed_free(); + m_torrent_file.seed_free(); } } @@ -1118,7 +1117,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + assert(index < m_torrent_file.num_pieces()); bool filter_updated = m_picker->set_piece_priority(index, priority); if (filter_updated) update_peer_interest(); @@ -1134,7 +1133,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + assert(index < m_torrent_file.num_pieces()); return m_picker->piece_priority(index); } @@ -1170,7 +1169,7 @@ namespace libtorrent if (is_seed()) { pieces.clear(); - pieces.resize(m_torrent_file->num_pieces(), 1); + pieces.resize(m_torrent_file.num_pieces(), 1); return; } @@ -1195,20 +1194,20 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert(int(files.size()) == m_torrent_file->num_files()); + assert(int(files.size()) == m_torrent_file.num_files()); size_type position = 0; - if (m_torrent_file->num_pieces() == 0) return; + if (m_torrent_file.num_pieces() == 0) return; - int piece_length = m_torrent_file->piece_length(); + int piece_length = m_torrent_file.piece_length(); // initialize the piece priorities to 0, then only allow // setting higher priorities - std::vector pieces(m_torrent_file->num_pieces(), 0); + std::vector pieces(m_torrent_file.num_pieces(), 0); for (int i = 0; i < int(files.size()); ++i) { size_type start = position; - size_type size = m_torrent_file->file_at(i).size; + size_type size = m_torrent_file.file_at(i).size; if (size == 0) continue; position += size; // mark all pieces of the file with this file's priority @@ -1244,7 +1243,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + assert(index < m_torrent_file.num_pieces()); m_picker->set_piece_priority(index, filter ? 1 : 0); update_peer_interest(); @@ -1281,7 +1280,7 @@ namespace libtorrent assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + assert(index < m_torrent_file.num_pieces()); return m_picker->piece_priority(index) == 0; } @@ -1295,7 +1294,7 @@ namespace libtorrent if (is_seed()) { bitmask.clear(); - bitmask.resize(m_torrent_file->num_pieces(), false); + bitmask.resize(m_torrent_file.num_pieces(), false); return; } @@ -1312,20 +1311,20 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert((int)bitmask.size() == m_torrent_file->num_files()); + assert((int)bitmask.size() == m_torrent_file.num_files()); size_type position = 0; - if (m_torrent_file->num_pieces()) + if (m_torrent_file.num_pieces()) { - int piece_length = m_torrent_file->piece_length(); + int piece_length = m_torrent_file.piece_length(); // mark all pieces as filtered, then clear the bits for files // that should be downloaded - std::vector piece_filter(m_torrent_file->num_pieces(), true); + std::vector piece_filter(m_torrent_file.num_pieces(), true); for (int i = 0; i < (int)bitmask.size(); ++i) { size_type start = position; - position += m_torrent_file->file_at(i).size; + position += m_torrent_file.file_at(i).size; // is the file selected for download? if (!bitmask[i]) { @@ -1360,7 +1359,7 @@ namespace libtorrent m_next_request = time_now() + seconds(tracker_retry_delay_max); tracker_request req; - req.info_hash = m_torrent_file->info_hash(); + req.info_hash = m_torrent_file.info_hash(); req.pid = m_ses.get_peer_id(); req.downloaded = m_stat.total_payload_download(); req.uploaded = m_stat.total_payload_upload(); @@ -1384,27 +1383,6 @@ namespace libtorrent return req; } - void torrent::choke_peer(peer_connection& c) - { - INVARIANT_CHECK; - - assert(!c.is_choked()); - assert(m_num_uploads > 0); - c.send_choke(); - --m_num_uploads; - } - - bool torrent::unchoke_peer(peer_connection& c) - { - INVARIANT_CHECK; - - assert(c.is_choked()); - if (m_num_uploads >= m_max_uploads) return false; - c.send_unchoke(); - ++m_num_uploads; - return true; - } - void torrent::cancel_block(piece_block block) { for (peer_iterator i = m_connections.begin() @@ -1416,7 +1394,7 @@ namespace libtorrent void torrent::remove_peer(peer_connection* p) try { -// INVARIANT_CHECK; + INVARIANT_CHECK; assert(p != 0); @@ -1455,9 +1433,6 @@ namespace libtorrent } } - if (!p->is_choked()) - --m_num_uploads; - m_policy->connection_closed(*p); p->set_peer_info(0); m_connections.erase(i); @@ -1653,7 +1628,6 @@ namespace libtorrent assert(m_connections.find(a) == m_connections.end()); // add the newly connected peer to this torrent's peer list - assert(m_connections.find(a) == m_connections.end()); m_connections.insert( std::make_pair(a, boost::get_pointer(c))); m_ses.m_connections.insert(std::make_pair(s, c)); @@ -1670,8 +1644,8 @@ namespace libtorrent #endif // TODO: post an error alert! -// std::map::iterator i = m_connections.find(a); -// if (i != m_connections.end()) m_connections.erase(i); + std::map::iterator i = m_connections.find(a); + if (i != m_connections.end()) m_connections.erase(i); m_ses.connection_failed(s, a, e.what()); c->disconnect(); } @@ -1833,7 +1807,6 @@ namespace libtorrent #endif assert(want_more_peers()); - assert(m_ses.num_connections() < m_ses.max_connections()); tcp::endpoint const& a(peerinfo->ip); assert((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); @@ -1858,8 +1831,6 @@ namespace libtorrent try { - assert(m_connections.find(a) == m_connections.end()); - // add the newly connected peer to this torrent's peer list m_connections.insert( std::make_pair(a, boost::get_pointer(c))); @@ -1872,7 +1843,6 @@ namespace libtorrent } catch (std::exception& e) { - assert(false); // TODO: post an error alert! std::map::iterator i = m_connections.find(a); if (i != m_connections.end()) m_connections.erase(i); @@ -1888,8 +1858,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_torrent_file->is_valid()); - m_torrent_file->parse_info_section(metadata); + assert(!m_torrent_file.is_valid()); + m_torrent_file.parse_info_section(metadata); init(); @@ -1899,12 +1869,12 @@ namespace libtorrent new aux::piece_checker_data); d->torrent_ptr = shared_from_this(); d->save_path = m_save_path; - d->info_hash = m_torrent_file->info_hash(); + d->info_hash = m_torrent_file.info_hash(); // add the torrent to the queue to be checked m_checker.m_torrents.push_back(d); typedef session_impl::torrent_map torrent_map; torrent_map::iterator i = m_ses.m_torrents.find( - m_torrent_file->info_hash()); + m_torrent_file.info_hash()); assert(i != m_ses.m_torrents.end()); m_ses.m_torrents.erase(i); // and notify the thread that it got another @@ -1920,7 +1890,7 @@ namespace libtorrent void torrent::attach_peer(peer_connection* p) { -// INVARIANT_CHECK; + INVARIANT_CHECK; assert(p != 0); assert(!p->is_local()); @@ -1929,7 +1899,6 @@ namespace libtorrent = m_connections.find(p->remote()); if (c != m_connections.end()) { - assert(p != c->second); // we already have a peer_connection to this ip. // It may currently be waiting for completing a // connection attempt that might fail. So, @@ -1953,7 +1922,6 @@ namespace libtorrent throw protocol_error("session is closing"); } - assert(m_connections.find(p->remote()) == m_connections.end()); peer_iterator ci = m_connections.insert( std::make_pair(p->remote(), p)).first; try @@ -1987,7 +1955,7 @@ namespace libtorrent bool torrent::want_more_peers() const { - return int(m_connections.size()) < m_max_connections + return int(m_connections.size()) < m_connections_quota.given && m_ses.m_half_open.free_slots() && !m_paused; } @@ -2026,9 +1994,7 @@ namespace libtorrent , boost::intrusive_ptr const& p , bool non_prioritized) { - assert(m_bandwidth_limit[channel].throttle() > 0); int block_size = m_bandwidth_limit[channel].throttle() / 10; - if (block_size <= 0) block_size = 1; if (m_bandwidth_limit[channel].max_assignable() > 0) { @@ -2156,7 +2122,7 @@ namespace libtorrent if ((unsigned)m_currently_trying_tracker >= m_trackers.size()) { int delay = tracker_retry_delay_min - + (std::min)(m_failed_trackers, (int)tracker_failed_max) + + std::min(m_failed_trackers, (int)tracker_failed_max) * (tracker_retry_delay_max - tracker_retry_delay_min) / tracker_failed_max; @@ -2215,12 +2181,14 @@ namespace libtorrent } pause(); } +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif return done; } std::pair torrent::check_files() { - assert(m_torrent_file->is_valid()); INVARIANT_CHECK; assert(m_owning_storage.get()); @@ -2248,6 +2216,9 @@ namespace libtorrent pause(); } +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif return progress; } @@ -2256,7 +2227,6 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - assert(m_torrent_file->is_valid()); INVARIANT_CHECK; if (!is_seed()) @@ -2287,7 +2257,7 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file->seed_free(); + m_torrent_file.seed_free(); } if (!m_connections_initialized) @@ -2316,6 +2286,9 @@ namespace libtorrent } } } +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif } alert_manager& torrent::alerts() const @@ -2367,7 +2340,7 @@ namespace libtorrent torrent_handle torrent::get_handle() const { - return torrent_handle(&m_ses, &m_checker, m_torrent_file->info_hash()); + return torrent_handle(&m_ses, &m_checker, m_torrent_file.info_hash()); } session_settings const& torrent::settings() const @@ -2378,7 +2351,9 @@ namespace libtorrent #ifndef NDEBUG void torrent::check_invariant() const { - int num_uploads = 0; +// size_type download = m_stat.total_payload_download(); +// size_type done = boost::get<0>(bytes_done()); +// assert(download >= done - m_initial_done); std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) { @@ -2389,12 +2364,10 @@ namespace libtorrent for (std::deque::const_iterator i = p.download_queue().begin() , end(p.download_queue().end()); i != end; ++i) ++num_requests[*i]; - if (!p.is_choked()) ++num_uploads; torrent* associated_torrent = p.associated_torrent().lock().get(); if (associated_torrent != this) assert(false); } - assert(num_uploads == m_num_uploads); if (has_picker()) { @@ -2407,26 +2380,20 @@ namespace libtorrent if (valid_metadata()) { - assert(m_abort || int(m_have_pieces.size()) == m_torrent_file->num_pieces()); + assert(m_abort || int(m_have_pieces.size()) == m_torrent_file.num_pieces()); } else { assert(m_abort || m_have_pieces.empty()); } -/* for (policy::const_iterator i = m_policy->begin_peer() - , end(m_policy->end_peer()); i != end; ++i) - { - assert(i->connection == const_cast(this)->connection_for(i->ip)); - } -*/ size_type total_done = quantized_bytes_done(); - if (m_torrent_file->is_valid()) + if (m_torrent_file.is_valid()) { if (is_seed()) - assert(total_done == m_torrent_file->total_size()); + assert(total_done == m_torrent_file.total_size()); else - assert(total_done != m_torrent_file->total_size()); + assert(total_done != m_torrent_file.total_size()); } else { @@ -2437,7 +2404,7 @@ namespace libtorrent assert(m_num_pieces == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); assert(!valid_metadata() || m_block_size > 0); - assert(!valid_metadata() || (m_torrent_file->piece_length() % m_block_size) == 0); + assert(!valid_metadata() || (m_torrent_file.piece_length() % m_block_size) == 0); // if (is_seed()) assert(m_picker.get() == 0); } #endif @@ -2458,15 +2425,15 @@ namespace libtorrent void torrent::set_max_uploads(int limit) { assert(limit >= -1); - if (limit <= 0) limit = (std::numeric_limits::max)(); - m_max_uploads = limit; + if (limit == -1) limit = std::numeric_limits::max(); + m_uploads_quota.max = std::max(m_uploads_quota.min, limit); } void torrent::set_max_connections(int limit) { assert(limit >= -1); - if (limit <= 0) limit = (std::numeric_limits::max)(); - m_max_connections = limit; + if (limit == -1) limit = std::numeric_limits::max(); + m_connections_quota.max = std::max(m_connections_quota.min, limit); } void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) @@ -2488,7 +2455,7 @@ namespace libtorrent void torrent::set_upload_limit(int limit) { assert(limit >= -1); - if (limit <= 0) limit = (std::numeric_limits::max)(); + if (limit == -1) limit = std::numeric_limits::max(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::upload_channel].throttle(limit); } @@ -2496,14 +2463,14 @@ namespace libtorrent int torrent::upload_limit() const { int limit = m_bandwidth_limit[peer_connection::upload_channel].throttle(); - if (limit == (std::numeric_limits::max)()) limit = -1; + if (limit == std::numeric_limits::max()) limit = -1; return limit; } void torrent::set_download_limit(int limit) { assert(limit >= -1); - if (limit <= 0) limit = (std::numeric_limits::max)(); + if (limit == -1) limit = std::numeric_limits::max(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::download_channel].throttle(limit); } @@ -2511,7 +2478,7 @@ namespace libtorrent int torrent::download_limit() const { int limit = m_bandwidth_limit[peer_connection::download_channel].throttle(); - if (limit == (std::numeric_limits::max)()) limit = -1; + if (limit == std::numeric_limits::max()) limit = -1; return limit; } @@ -2529,14 +2496,6 @@ namespace libtorrent } #endif -#if defined(TORRENT_VERBOSE_LOGGING) - for (peer_iterator i = m_connections.begin(); - i != m_connections.end(); ++i) - { - (*i->second->m_logger) << "*** PAUSING TORRENT\n"; - } -#endif - disconnect_all(); m_paused = true; // tell the tracker that we stopped @@ -2569,6 +2528,10 @@ namespace libtorrent #endif m_paused = false; + m_uploads_quota.min = 0; + m_connections_quota.min = 2; + m_uploads_quota.max = std::numeric_limits::max(); + m_connections_quota.max = std::numeric_limits::max(); // tell the tracker that we're back m_event = tracker_request::started; @@ -2582,6 +2545,10 @@ namespace libtorrent { INVARIANT_CHECK; + m_connections_quota.used = (int)m_connections.size(); + m_uploads_quota.used = m_policy->num_uploads(); + m_uploads_quota.max = (int)m_connections.size(); + #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2594,6 +2561,10 @@ namespace libtorrent { // let the stats fade out to 0 m_stat.second_tick(tick_interval); + m_connections_quota.min = 0; + m_connections_quota.max = 0; + m_uploads_quota.min = 0; + m_uploads_quota.max = 0; return; } @@ -2652,13 +2623,6 @@ namespace libtorrent } accumulator += m_stat; m_stat.second_tick(tick_interval); - - m_time_scaler--; - if (m_time_scaler <= 0) - { - m_time_scaler = 10; - m_policy->pulse(); - } } bool torrent::try_connect_peer() @@ -2667,6 +2631,18 @@ namespace libtorrent return m_policy->connect_one_peer(); } + void torrent::distribute_resources(float tick_interval) + { + INVARIANT_CHECK; + + m_time_scaler--; + if (m_time_scaler <= 0) + { + m_time_scaler = settings().unchoke_interval; + m_policy->pulse(); + } + } + void torrent::async_verify_piece(int piece_index, boost::function const& f) { INVARIANT_CHECK; @@ -2674,7 +2650,7 @@ namespace libtorrent assert(m_storage); assert(m_storage->refcount() > 0); assert(piece_index >= 0); - assert(piece_index < m_torrent_file->num_pieces()); + assert(piece_index < m_torrent_file.num_pieces()); assert(piece_index < (int)m_have_pieces.size()); m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified @@ -2686,7 +2662,7 @@ namespace libtorrent { sha1_hash h(j.str); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - f(m_torrent_file->hash_for_piece(j.piece) == h); + f(m_torrent_file.hash_for_piece(j.piece) == h); } const tcp::endpoint& torrent::current_tracker() const @@ -2702,12 +2678,12 @@ namespace libtorrent assert(valid_metadata()); fp.clear(); - fp.resize(m_torrent_file->num_files(), 0.f); + fp.resize(m_torrent_file.num_files(), 0.f); - for (int i = 0; i < m_torrent_file->num_files(); ++i) + for (int i = 0; i < m_torrent_file.num_files(); ++i) { - peer_request ret = m_torrent_file->map_file(i, 0, 0); - size_type size = m_torrent_file->file_at(i).size; + peer_request ret = m_torrent_file.map_file(i, 0, 0); + size_type size = m_torrent_file.file_at(i).size; // zero sized files are considered // 100% done all the time @@ -2720,7 +2696,7 @@ namespace libtorrent size_type done = 0; while (size > 0) { - size_type bytes_step = (std::min)(m_torrent_file->piece_size(ret.piece) + size_type bytes_step = std::min(m_torrent_file.piece_size(ret.piece) - ret.start, size); if (m_have_pieces[ret.piece]) done += bytes_step; ++ret.piece; @@ -2729,7 +2705,7 @@ namespace libtorrent } assert(size == 0); - fp[i] = static_cast(done) / m_torrent_file->file_at(i).size; + fp[i] = static_cast(done) / m_torrent_file.file_at(i).size; } } @@ -2788,10 +2764,10 @@ namespace libtorrent = m_trackers[m_last_working_tracker].url; } - st.num_uploads = m_num_uploads; - st.uploads_limit = m_max_uploads; - st.num_connections = int(m_connections.size()); - st.connections_limit = m_max_connections; + st.num_uploads = m_uploads_quota.used; + st.uploads_limit = m_uploads_quota.given; + st.num_connections = m_connections_quota.used; + st.connections_limit = m_connections_quota.given; // if we don't have any metadata, stop here if (!valid_metadata()) @@ -2804,7 +2780,7 @@ namespace libtorrent // TODO: add a progress member to the torrent that will be used in this case // and that may be set by a plugin // if (m_metadata_size == 0) st.progress = 0.f; -// else st.progress = (std::min)(1.f, m_metadata_progress / (float)m_metadata_size); +// else st.progress = std::min(1.f, m_metadata_progress / (float)m_metadata_size); st.progress = 0.f; st.block_size = 0; @@ -2816,21 +2792,21 @@ namespace libtorrent // fill in status that depends on metadata - st.total_wanted = m_torrent_file->total_size(); + st.total_wanted = m_torrent_file.total_size(); if (m_picker.get() && (m_picker->num_filtered() > 0 || m_picker->num_have_filtered() > 0)) { int filtered_pieces = m_picker->num_filtered() + m_picker->num_have_filtered(); - int last_piece_index = m_torrent_file->num_pieces() - 1; + int last_piece_index = m_torrent_file.num_pieces() - 1; if (m_picker->piece_priority(last_piece_index) == 0) { - st.total_wanted -= m_torrent_file->piece_size(last_piece_index); + st.total_wanted -= m_torrent_file.piece_size(last_piece_index); --filtered_pieces; } - st.total_wanted -= filtered_pieces * m_torrent_file->piece_length(); + st.total_wanted -= filtered_pieces * m_torrent_file.piece_length(); } assert(st.total_wanted >= st.total_wanted_done); @@ -2848,7 +2824,7 @@ namespace libtorrent } else if (is_seed()) { - assert(st.total_done == m_torrent_file->total_size()); + assert(st.total_done == m_torrent_file.total_size()); st.state = torrent_status::seeding; } else if (st.total_wanted_done == st.total_wanted) diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index ebef802a8..4538e66e8 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -89,16 +89,27 @@ namespace libtorrent throw invalid_handle(); } - boost::shared_ptr find_torrent( + template + Ret call_member( session_impl* ses , aux::checker_impl* chk - , sha1_hash const& hash) + , sha1_hash const& hash + , F f) { - aux::piece_checker_data* d = chk->find_torrent(hash); - if (d != 0) return d->torrent_ptr; + if (ses == 0) throw_invalid_handle(); - boost::shared_ptr t = ses->find_torrent(hash).lock(); - if (t) return t; + if (chk) + { + mutex::scoped_lock l(chk->m_mutex); + aux::piece_checker_data* d = chk->find_torrent(hash); + if (d != 0) return f(*d->torrent_ptr); + } + + { + session_impl::mutex_t::scoped_lock l(ses->m_mutex); + boost::shared_ptr t = ses->find_torrent(hash).lock(); + if (t) return f(*t); + } // throwing directly instead of calling // the throw_invalid_handle() function @@ -111,7 +122,7 @@ namespace libtorrent void torrent_handle::check_invariant() const { - assert((m_ses == 0 && m_chk == 0) || (m_ses != 0 && m_chk != 0)); + assert((m_ses == 0 && m_chk == 0) || (m_ses != 0)); } #endif @@ -120,40 +131,28 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - assert(max_uploads >= 2 || max_uploads == -1); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_max_uploads(max_uploads); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_max_uploads, _1, max_uploads)); } void torrent_handle::use_interface(const char* net_interface) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->use_interface(net_interface); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::use_interface, _1, net_interface)); } void torrent_handle::set_max_connections(int max_connections) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - assert(max_connections >= 2 || max_connections == -1); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_max_connections(max_connections); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_max_connections, _1, max_connections)); } void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const @@ -161,12 +160,8 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_peer_upload_limit(ip, limit); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_peer_upload_limit, _1, ip, limit)); } void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const @@ -174,64 +169,42 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_peer_download_limit(ip, limit); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_peer_download_limit, _1, ip, limit)); } void torrent_handle::set_upload_limit(int limit) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - assert(limit >= -1); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_upload_limit(limit); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_upload_limit, _1, limit)); } int torrent_handle::upload_limit() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->upload_limit(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::upload_limit, _1)); } void torrent_handle::set_download_limit(int limit) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - assert(limit >= -1); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_download_limit(limit); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_download_limit, _1, limit)); } int torrent_handle::download_limit() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->download_limit(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::download_limit, _1)); } void torrent_handle::move_storage( @@ -239,72 +212,48 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->move_storage(save_path); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::move_storage, _1, save_path)); } bool torrent_handle::has_metadata() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->valid_metadata(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::valid_metadata, _1)); } bool torrent_handle::is_seed() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->is_seed(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::is_seed, _1)); } bool torrent_handle::is_paused() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->is_paused(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::is_paused, _1)); } void torrent_handle::pause() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->pause(); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::pause, _1)); } void torrent_handle::resume() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->resume(); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::resume, _1)); } void torrent_handle::set_tracker_login(std::string const& name @@ -312,12 +261,8 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_tracker_login(name, password); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_tracker_login, _1, name, password)); } void torrent_handle::file_progress(std::vector& progress) @@ -325,27 +270,31 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) + if (m_chk) { - if (!d->processing) + mutex::scoped_lock l(m_chk->m_mutex); + + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) { - torrent_info const& info = d->torrent_ptr->torrent_file(); - progress.clear(); - progress.resize(info.num_files(), 0.f); + if (!d->processing) + { + torrent_info const& info = d->torrent_ptr->torrent_file(); + progress.clear(); + progress.resize(info.num_files(), 0.f); + return; + } + d->torrent_ptr->file_progress(progress); return; } - d->torrent_ptr->file_progress(progress); - return; } - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->file_progress(progress); + { + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->file_progress(progress); + } throw_invalid_handle(); } @@ -355,32 +304,36 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) + if (m_chk) { - torrent_status st; + mutex::scoped_lock l(m_chk->m_mutex); - if (d->processing) + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) { - if (d->torrent_ptr->is_allocating()) - st.state = torrent_status::allocating; + torrent_status st; + + if (d->processing) + { + if (d->torrent_ptr->is_allocating()) + st.state = torrent_status::allocating; + else + st.state = torrent_status::checking_files; + } else - st.state = torrent_status::checking_files; + st.state = torrent_status::queued_for_checking; + st.progress = d->progress; + st.paused = d->torrent_ptr->is_paused(); + return st; } - else - st.state = torrent_status::queued_for_checking; - st.progress = d->progress; - st.paused = d->torrent_ptr->is_paused(); - return st; } - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->status(); + { + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->status(); + } throw_invalid_handle(); return torrent_status(); @@ -389,25 +342,15 @@ namespace libtorrent void torrent_handle::set_sequenced_download_threshold(int threshold) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_sequenced_download_threshold(threshold); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_sequenced_download_threshold, _1, threshold)); } std::string torrent_handle::name() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->name(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::name, _1)); } @@ -415,61 +358,40 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->piece_availability(avail); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::piece_availability, _1, boost::ref(avail))); } void torrent_handle::piece_priority(int index, int priority) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_piece_priority(index, priority); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_piece_priority, _1, index, priority)); } int torrent_handle::piece_priority(int index) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->piece_priority(index); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::piece_priority, _1, index)); } void torrent_handle::prioritize_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->prioritize_pieces(pieces); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::prioritize_pieces, _1, boost::cref(pieces))); } std::vector torrent_handle::piece_priorities() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - std::vector ret; - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->piece_priorities(ret); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::piece_priorities, _1, boost::ref(ret))); return ret; } @@ -477,12 +399,8 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->prioritize_files(files); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::prioritize_files, _1, boost::cref(files))); } // ============ start deprecation =============== @@ -490,63 +408,38 @@ namespace libtorrent void torrent_handle::filter_piece(int index, bool filter) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filter_piece(index, filter); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filter_piece, _1, index, filter)); } void torrent_handle::filter_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filter_pieces(pieces); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filter_pieces, _1, boost::cref(pieces))); } bool torrent_handle::is_piece_filtered(int index) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->is_piece_filtered(index); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::is_piece_filtered, _1, index)); } std::vector torrent_handle::filtered_pieces() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - std::vector ret; - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filtered_pieces(ret); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filtered_pieces, _1, boost::ref(ret))); return ret; } void torrent_handle::filter_files(std::vector const& files) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filter_files(files); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::filter_files, _1, files)); } // ============ end deprecation =============== @@ -556,48 +449,16 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->trackers(); + return call_member const&>(m_ses + , m_chk, m_info_hash, bind(&torrent::trackers, _1)); } - void torrent_handle::add_url_seed(std::string const& url) const + void torrent_handle::add_url_seed(std::string const& url) { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->add_url_seed(url); - } - - void torrent_handle::remove_url_seed(std::string const& url) const - { - INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->remove_url_seed(url); - } - - std::set torrent_handle::url_seeds() const - { - INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->url_seeds(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::add_url_seed, _1, url)); } void torrent_handle::replace_trackers( @@ -605,26 +466,17 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->replace_trackers(urls); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::replace_trackers, _1, urls)); } torrent_info const& torrent_handle::get_torrent_info() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - boost::shared_ptr t = find_torrent(m_ses, m_chk, m_info_hash); - if (!t->valid_metadata()) throw_invalid_handle(); - return t->torrent_file(); + if (!has_metadata()) throw_invalid_handle(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::torrent_file, _1)); } bool torrent_handle::is_valid() const @@ -632,14 +484,16 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) return false; - assert(m_chk); - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) return true; + if (m_chk) + { + mutex::scoped_lock l(m_chk->m_mutex); + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) return true; + } { + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::weak_ptr t = m_ses->find_torrent(m_info_hash); if (!t.expired()) return true; } @@ -653,7 +507,6 @@ namespace libtorrent std::vector piece_index; if (m_ses == 0) return entry(); - assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -708,12 +561,12 @@ namespace libtorrent std::string bitmask; const int num_bitmask_bytes - = (std::max)(num_blocks_per_piece / 8, 1); + = std::max(num_blocks_per_piece / 8, 1); for (int j = 0; j < num_bitmask_bytes; ++j) { unsigned char v = 0; - int bits = (std::min)(num_blocks_per_piece - j*8, 8); + int bits = std::min(num_blocks_per_piece - j*8, 8); for (int k = 0; k < bits; ++k) v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) ? (1 << k) : 0; @@ -772,12 +625,8 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->save_path(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::save_path, _1)); } void torrent_handle::connect_peer(tcp::endpoint const& adr, int source) const @@ -785,7 +634,6 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -814,7 +662,6 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -829,7 +676,6 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -842,41 +688,28 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - assert(ratio >= 0.f); + if (ratio < 1.f && ratio > 0.f) ratio = 1.f; - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_ratio(ratio); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_ratio, _1, ratio)); } #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void torrent_handle::resolve_countries(bool r) { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->resolve_countries(r); + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::resolve_countries, _1, r)); } bool torrent_handle::resolve_countries() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->resolving_countries(); + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::resolving_countries, _1)); } #endif @@ -884,10 +717,8 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); - v.clear(); + if (m_ses == 0) throw_invalid_handle(); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); @@ -919,7 +750,6 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index d6dc27fa2..4ea09aefd 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -400,9 +400,7 @@ namespace libtorrent { if (i->first == "pieces" || i->first == "piece length" - || i->first == "length" -// || i->first == "files" - || i->first == "name") + || i->first == "length") continue; m_extra_info[i->first] = i->second; } @@ -676,7 +674,7 @@ namespace libtorrent { assert(m_piece_length > 0); - if (m_files.empty()) + if ((m_urls.empty() && m_nodes.empty()) || m_files.empty()) { // TODO: throw something here // throw @@ -826,33 +824,8 @@ namespace libtorrent m_nodes.push_back(node); } - bool torrent_info::remap_files(std::vector > const& map) - { - typedef std::vector > files_t; - - size_type offset = 0; - m_remapped_files.resize(map.size()); - - for (int i = 0; i < int(map.size()); ++i) - { - file_entry& fe = m_remapped_files[i]; - fe.path = map[i].first; - fe.offset = offset; - fe.size = map[i].second; - offset += fe.size; - } - if (offset != total_size()) - { - m_remapped_files.clear(); - return false; - } - - return true; - } - std::vector torrent_info::map_block(int piece, size_type offset - , int size, bool storage) const + , int size) const { assert(num_files() > 0); std::vector ret; @@ -866,9 +839,9 @@ namespace libtorrent std::vector::const_iterator file_iter; int counter = 0; - for (file_iter = begin_files(storage);; ++counter, ++file_iter) + for (file_iter = begin_files();; ++counter, ++file_iter) { - assert(file_iter != end_files(storage)); + assert(file_iter != end_files()); if (file_offset < file_iter->size) { file_slice f; @@ -889,11 +862,11 @@ namespace libtorrent } peer_request torrent_info::map_file(int file_index, size_type file_offset - , int size, bool storage) const + , int size) const { - assert(file_index < num_files(storage)); + assert(file_index < (int)m_files.size()); assert(file_index >= 0); - size_type offset = file_offset + file_at(file_index, storage).offset; + size_type offset = file_offset + m_files[file_index].offset; peer_request ret; ret.piece = offset / piece_length(); diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 981eb4caf..7bd511588 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -256,7 +256,7 @@ namespace libtorrent { // available input is 1,2 or 3 bytes // since we read 3 bytes at a time at most - int available_input = (std::min)(3, (int)std::distance(i, s.end())); + int available_input = std::min(3, (int)std::distance(i, s.end())); // clear input buffer std::fill(inbuf, inbuf+3, 0); @@ -305,7 +305,7 @@ namespace libtorrent m_start_time = time_now(); m_read_time = time_now(); - m_timeout.expires_at((std::min)( + m_timeout.expires_at(std::min( m_read_time + seconds(m_read_timeout) , m_start_time + seconds(m_completion_timeout))); m_timeout.async_wait(m_strand.wrap(bind( @@ -341,7 +341,7 @@ namespace libtorrent return; } - m_timeout.expires_at((std::min)( + m_timeout.expires_at(std::min( m_read_time + seconds(m_read_timeout) , m_start_time + seconds(m_completion_timeout))); m_timeout.async_wait(m_strand.wrap( @@ -365,22 +365,23 @@ namespace libtorrent , m_req(req) {} - boost::shared_ptr tracker_connection::requester() + request_callback& tracker_connection::requester() { - return m_requester.lock(); + boost::shared_ptr r = m_requester.lock(); + assert(r); + return *r; } void tracker_connection::fail(int code, char const* msg) { - boost::shared_ptr cb = requester(); - if (cb) cb->tracker_request_error(m_req, code, msg); + if (has_requester()) requester().tracker_request_error( + m_req, code, msg); close(); } void tracker_connection::fail_timeout() { - boost::shared_ptr cb = requester(); - if (cb) cb->tracker_request_timed_out(m_req); + if (has_requester()) requester().tracker_request_timed_out(m_req); close(); } @@ -547,8 +548,7 @@ namespace libtorrent m_connections.push_back(con); - boost::shared_ptr cb = con->requester(); - if (cb) cb->m_manager = this; + if (con->has_requester()) con->requester().m_manager = this; } catch (std::exception& e) { diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index cd500d98c..d08abd359 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -110,9 +110,8 @@ namespace libtorrent return; } - boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("udp tracker name lookup successful"); + if (has_requester()) requester().debug_log("udp tracker name lookup successful"); #endif restart_read_timeout(); @@ -127,11 +126,11 @@ namespace libtorrent if (target == end) { assert(target_address.address().is_v4() != bind_interface().is_v4()); - if (cb) + if (has_requester()) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - cb->tracker_warning("the tracker only resolves to an " + requester().tracker_warning("the tracker only resolves to an " + tracker_address_type + " address, and you're listening on an " + bind_address_type + " socket. This may prevent you from receiving incoming connections."); } @@ -141,7 +140,7 @@ namespace libtorrent target_address = *target; } - if (cb) cb->m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); + if (has_requester()) requester().m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); m_target = target_address; m_socket.reset(new datagram_socket(m_name_lookup.io_service())); m_socket->open(target_address.protocol()); @@ -164,10 +163,9 @@ namespace libtorrent void udp_tracker_connection::send_udp_connect() { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) + if (has_requester()) { - cb->debug_log("==> UDP_TRACKER_CONNECT [" + requester().debug_log("==> UDP_TRACKER_CONNECT [" + lexical_cast(tracker_req().info_hash) + "]"); } #endif @@ -261,10 +259,9 @@ namespace libtorrent m_connection_id = detail::read_int64(ptr); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) + if (has_requester()) { - cb->debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + requester().debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + lexical_cast(m_connection_id) + "]"); } #endif @@ -324,10 +321,9 @@ namespace libtorrent detail::write_uint16(0, out); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) + if (has_requester()) { - cb->debug_log("==> UDP_TRACKER_ANNOUNCE [" + requester().debug_log("==> UDP_TRACKER_ANNOUNCE [" + lexical_cast(req.info_hash) + "]"); } #endif @@ -435,15 +431,14 @@ namespace libtorrent return; } - boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) + if (has_requester()) { - cb->debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); + requester().debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); } #endif - if (!cb) + if (!has_requester()) { m_man.remove_request(this); return; @@ -464,7 +459,7 @@ namespace libtorrent peer_list.push_back(e); } - cb->tracker_response(tracker_req(), peer_list, interval + requester().tracker_response(tracker_req(), peer_list, interval , complete, incomplete); m_man.remove_request(this); @@ -539,15 +534,14 @@ namespace libtorrent /*int downloaded = */detail::read_int32(buf); int incomplete = detail::read_int32(buf); - boost::shared_ptr cb = requester(); - if (!cb) + if (!has_requester()) { m_man.remove_request(this); return; } std::vector peer_list; - cb->tracker_response(tracker_req(), peer_list, 0 + requester().tracker_response(tracker_req(), peer_list, 0 , complete, incomplete); m_man.remove_request(this); diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 87f950b48..aefff41b1 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -53,10 +53,38 @@ POSSIBILITY OF SUCH DAMAGE. using boost::bind; using namespace libtorrent; +address_v4 upnp::upnp_multicast_address; +udp::endpoint upnp::upnp_multicast_endpoint; + namespace libtorrent { - bool is_local(address const& a); - address_v4 guess_local_address(asio::io_service&); + bool is_local(address const& a) + { + if (a.is_v6()) return false; + address_v4 a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 + || (ip & 0xfff00000) == 0xac100000 + || (ip & 0xffff0000) == 0xc0a80000); + } + + address_v4 guess_local_address(asio::io_service& ios) + { + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + // ignore the loopback + if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; + // ignore addresses that are not on a local network + if (!is_local(i->endpoint().address())) continue; + // ignore non-IPv4 addresses + if (i->endpoint().address().is_v4()) break; + } + if (i == udp::resolver_iterator()) return address_v4::any(); + return i->endpoint().address().to_v4(); + } } upnp::upnp(io_service& ios, connection_queue& cc @@ -67,27 +95,89 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_user_agent(user_agent) , m_callback(cb) , m_retry_count(0) - , m_io_service(ios) - , m_strand(ios) - , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900) - , m_strand.wrap(bind(&upnp::on_reply, this, _1, _2, _3)), false) + , m_socket(ios) , m_broadcast_timer(ios) , m_refresh_timer(ios) + , m_strand(ios) , m_disabled(false) , m_closing(false) , m_cc(cc) { + // UPnP multicast address and port + upnp_multicast_address = address_v4::from_string("239.255.255.250"); + upnp_multicast_endpoint = udp::endpoint(upnp_multicast_address, 1900); + #ifdef TORRENT_UPNP_LOGGING m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - m_retry_count = 0; - discover_device(); + rebind(listen_interface); } upnp::~upnp() { } +void upnp::rebind(address const& listen_interface) try +{ + address_v4 bind_to = address_v4::any(); + if (listen_interface.is_v4() && listen_interface != address_v4::any()) + { + m_local_ip = listen_interface.to_v4(); + bind_to = listen_interface.to_v4(); + if (!is_local(m_local_ip)) + { + // the local address seems to be an external + // internet address. Assume it is not behind a NAT + throw std::runtime_error("local IP is not on a local network"); + } + } + else + { + m_local_ip = guess_local_address(m_socket.io_service()); + bind_to = address_v4::any(); + } + + if (!is_local(m_local_ip)) + { + throw std::runtime_error("local host is probably not on a NATed " + "network. disabling UPnP"); + } + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " local ip: " << m_local_ip.to_string() + << " bind to: " << bind_to.to_string() << std::endl; +#endif + + // the local interface hasn't changed + if (m_socket.is_open() + && m_socket.local_endpoint().address() == m_local_ip) + return; + + m_socket.close(); + + using namespace asio::ip::multicast; + + m_socket.open(udp::v4()); + m_socket.set_option(datagram_socket::reuse_address(true)); + m_socket.bind(udp::endpoint(bind_to, 0)); + + m_socket.set_option(join_group(upnp_multicast_address)); + m_socket.set_option(outbound_interface(bind_to)); + m_socket.set_option(hops(255)); + m_disabled = false; + + m_retry_count = 0; + discover_device(); +} +catch (std::exception& e) +{ + disable(); + std::stringstream msg; + msg << "UPnP portmapping disabled: " << e.what(); + m_callback(0, 0, msg.str()); +}; + void upnp::discover_device() try { const char msearch[] = @@ -98,20 +188,20 @@ void upnp::discover_device() try "MX:3\r\n" "\r\n\r\n"; + m_socket.async_receive_from(asio::buffer(m_receive_buffer + , sizeof(m_receive_buffer)), m_remote, m_strand.wrap(bind( + &upnp::on_reply, this, _1, _2))); + asio::error_code ec; #ifdef TORRENT_DEBUG_UPNP // simulate packet loss if (m_retry_count & 1) #endif - m_socket.send(msearch, sizeof(msearch) - 1, ec); + m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1) + , upnp_multicast_endpoint, 0, ec); if (ec) { -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> Broadcast FAILED: " << ec.message() << std::endl - << "aborting" << std::endl; -#endif disable(); return; } @@ -129,7 +219,7 @@ void upnp::discover_device() try catch (std::exception&) { disable(); -}; +} void upnp::set_mappings(int tcp, int udp) { @@ -202,7 +292,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_io_service + d.upnp_connection.reset(new http_connection(m_socket.io_service() , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -223,16 +313,17 @@ try catch (std::exception&) { assert(false); -}; +} #endif -void upnp::on_reply(udp::endpoint const& from, char* buffer +void upnp::on_reply(asio::error_code const& e , std::size_t bytes_transferred) #ifndef NDEBUG try #endif { using namespace libtorrent::detail; + if (e) return; // parse out the url for the device @@ -247,45 +338,29 @@ try EXT: Cache-Control:max-age=180 DATE: Fri, 02 Jan 1970 08:10:38 GMT - - a notification looks like this: - - NOTIFY * HTTP/1.1 - Host:239.255.255.250:1900 - NT:urn:schemas-upnp-org:device:MediaServer:1 - NTS:ssdp:alive - Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e - USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1 - Cache-Control:max-age=900 - Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0 - */ http_parser p; try { - p.incoming(buffer::const_interval(buffer - , buffer + bytes_transferred)); + p.incoming(buffer::const_interval(m_receive_buffer + , m_receive_buffer + bytes_transferred)); } catch (std::exception& e) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl; + << " <== Rootdevice responded with incorrect HTTP packet: " + << e.what() << ". Ignoring device" << std::endl; #endif return; } - if (p.status_code() != 200 && p.method() != "notify") + if (p.status_code() != 200) { #ifdef TORRENT_UPNP_LOGGING - if (p.method().empty()) - m_log << time_now_string() - << " <== Device responded with HTTP status: " << p.status_code() - << ". Ignoring device" << std::endl; - else - m_log << time_now_string() - << " <== Device with HTTP method: " << p.method() - << ". Ignoring device" << std::endl; + m_log << time_now_string() + << " <== Rootdevice responded with HTTP status: " << p.status_code() + << ". Ignoring device" << std::endl; #endif return; } @@ -356,8 +431,6 @@ try { d.mapping[0].need_update = true; d.mapping[0].local_port = m_tcp_local_port; - if (d.mapping[0].external_port == 0) - d.mapping[0].external_port = d.mapping[0].local_port; #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl; #endif @@ -366,8 +439,6 @@ try { d.mapping[1].need_update = true; d.mapping[1].local_port = m_udp_local_port; - if (d.mapping[1].external_port == 0) - d.mapping[1].external_port = d.mapping[1].local_port; #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl; #endif @@ -392,7 +463,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_io_service + d.upnp_connection.reset(new http_connection(m_socket.io_service() , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -417,7 +488,7 @@ catch (std::exception&) }; #endif -void upnp::post(upnp::rootdevice const& d, std::string const& soap +void upnp::post(rootdevice& d, std::stringstream const& soap , std::string const& soap_action) { std::stringstream header; @@ -425,40 +496,12 @@ void upnp::post(upnp::rootdevice const& d, std::string const& soap header << "POST " << d.control_url << " HTTP/1.1\r\n" "Host: " << d.hostname << ":" << d.port << "\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "Content-Length: " << soap.size() << "\r\n" - "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap; + "Content-Length: " << soap.str().size() << "\r\n" + "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str(); d.upnp_connection->sendbuffer = header.str(); - -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> sending: " << header.str() << std::endl; -#endif - -} - -void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) -{ - std::string soap_action = "AddPortMapping"; - - std::stringstream soap; - - soap << "\n" - "" - ""; - - soap << "" - "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" - "" << d.mapping[i].local_port << "" - "" << c.socket().local_endpoint().address().to_string() << "" - "1" - "" << m_user_agent << "" - "" << d.lease_duration << ""; - soap << ""; - - post(d, soap.str(), soap_action); + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); } void upnp::map_port(rootdevice& d, int i) @@ -479,21 +522,14 @@ void upnp::map_port(rootdevice& d, int i) assert(!d.upnp_connection); assert(d.service_namespace); - d.upnp_connection.reset(new http_connection(m_io_service + d.upnp_connection.reset(new http_connection(m_socket.io_service() , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2 - , boost::ref(d), i)), true - , bind(&upnp::create_port_mapping, this, _1, boost::ref(d), i))); + , boost::ref(d), i)))); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); -} + std::string soap_action = "AddPortMapping"; -void upnp::delete_port_mapping(rootdevice& d, int i) -{ std::stringstream soap; - std::string soap_action = "DeletePortMapping"; - soap << "\n" "" @@ -501,10 +537,20 @@ void upnp::delete_port_mapping(rootdevice& d, int i) soap << "" "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" + "" << d.mapping[i].local_port << "" + "" << m_local_ip.to_string() << "" + "1" + "" << m_user_agent << "" + "" << d.lease_duration << ""; soap << ""; + + post(d, soap, soap_action); +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> AddPortMapping: " << soap.str() << std::endl; +#endif - post(d, soap.str(), soap_action); } // requires the mutex to be locked @@ -522,13 +568,29 @@ void upnp::unmap_port(rootdevice& d, int i) } return; } - d.upnp_connection.reset(new http_connection(m_io_service + d.upnp_connection.reset(new http_connection(m_socket.io_service() , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2 - , boost::ref(d), i)), true - , bind(&upnp::delete_port_mapping, this, boost::ref(d), i))); + , boost::ref(d), i)))); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); + std::string soap_action = "DeletePortMapping"; + + std::stringstream soap; + + soap << "\n" + "" + ""; + + soap << "" + "" << d.mapping[i].external_port << "" + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; + soap << ""; + + post(d, soap, soap_action); +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> DeletePortMapping: " << soap.str() << std::endl; +#endif } namespace @@ -776,9 +838,16 @@ void upnp::on_upnp_map_response(asio::error_code const& e m_devices.erase(d); return; } - - // We don't want to ignore responses with return codes other than 200 - // since those might contain valid UPnP error codes + + if (p.status_code() != 200) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== error while adding portmap: " << p.message() << std::endl; +#endif + m_devices.erase(d); + return; + } error_code_parse_state s; xml_parse((char*)p.get_body().begin, (char*)p.get_body().end diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 18fe715ee..18cbf6c2f 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -67,6 +67,8 @@ namespace libtorrent { namespace if (!p.is_local()) return false; // don't send out peers that we haven't successfully connected to if (p.is_connecting()) return false; + // ut pex does not support IPv6 + if (!p.remote().address().is_v4()) return false; return true; } @@ -96,15 +98,9 @@ namespace libtorrent { namespace std::string& pla = pex["added"].string(); std::string& pld = pex["dropped"].string(); std::string& plf = pex["added.f"].string(); - std::string& pla6 = pex["added6"].string(); - std::string& pld6 = pex["dropped6"].string(); - std::string& plf6 = pex["added6.f"].string(); std::back_insert_iterator pla_out(pla); std::back_insert_iterator pld_out(pld); std::back_insert_iterator plf_out(plf); - std::back_insert_iterator pla6_out(pla6); - std::back_insert_iterator pld6_out(pld6); - std::back_insert_iterator plf6_out(plf6); std::set dropped; m_old_peers.swap(dropped); @@ -127,6 +123,8 @@ namespace libtorrent { namespace bt_peer_connection* p = dynamic_cast(i->second); if (!p) continue; + // i->first was added since the last time + detail::write_endpoint(i->first, pla_out); // no supported flags to set yet // 0x01 - peer supports encryption // 0x02 - peer is a seed @@ -134,17 +132,7 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif - // i->first was added since the last time - if (i->first.address().is_v4()) - { - detail::write_endpoint(i->first, pla_out); - detail::write_uint8(flags, plf_out); - } - else - { - detail::write_endpoint(i->first, pla6_out); - detail::write_uint8(flags, plf6_out); - } + detail::write_uint8(flags, plf_out); ++num_added; } else @@ -158,10 +146,8 @@ namespace libtorrent { namespace for (std::set::const_iterator i = dropped.begin() , end(dropped.end());i != end; ++i) { - if (i->address().is_v4()) - detail::write_endpoint(*i, pld_out); - else - detail::write_endpoint(*i, pld6_out); + if (!i->address().is_v4()) continue; + detail::write_endpoint(*i, pld_out); } m_ut_pex_msg.clear(); @@ -241,28 +227,6 @@ namespace libtorrent { namespace char flags = detail::read_uint8(fin); p.peer_from_tracker(adr, pid, peer_info::pex, flags); } - - if (entry const* p6 = pex_msg.find_key("added6")) - { - std::string const& peers6 = p6->string(); - std::string const& peer6_flags = pex_msg["added6.f"].string(); - - int num_peers = peers6.length() / 18; - char const* in = peers6.c_str(); - char const* fin = peer6_flags.c_str(); - - if (int(peer6_flags.size()) != num_peers) - return true; - - peer_id pid(0); - policy& p = m_torrent.get_policy(); - for (int i = 0; i < num_peers; ++i) - { - tcp::endpoint adr = detail::read_v6_endpoint(in); - char flags = detail::read_uint8(fin); - p.peer_from_tracker(adr, pid, peer_info::pex, flags); - } - } } catch (std::exception&) { @@ -315,13 +279,8 @@ namespace libtorrent { namespace pex["dropped"].string(); std::string& pla = pex["added"].string(); std::string& plf = pex["added.f"].string(); - pex["dropped6"].string(); - std::string& pla6 = pex["added6"].string(); - std::string& plf6 = pex["added6.f"].string(); std::back_insert_iterator pla_out(pla); std::back_insert_iterator plf_out(plf); - std::back_insert_iterator pla6_out(pla6); - std::back_insert_iterator plf6_out(plf6); int num_added = 0; for (torrent::peer_iterator i = m_torrent.begin() @@ -336,6 +295,8 @@ namespace libtorrent { namespace bt_peer_connection* p = dynamic_cast(i->second); if (!p) continue; + // i->first was added since the last time + detail::write_endpoint(i->first, pla_out); // no supported flags to set yet // 0x01 - peer supports encryption // 0x02 - peer is a seed @@ -343,17 +304,7 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif - // i->first was added since the last time - if (i->first.address().is_v4()) - { - detail::write_endpoint(i->first, pla_out); - detail::write_uint8(flags, plf_out); - } - else - { - detail::write_endpoint(i->first, pla6_out); - detail::write_uint8(flags, plf6_out); - } + detail::write_uint8(flags, plf_out); ++num_added; } std::vector pex_msg; @@ -396,7 +347,7 @@ namespace libtorrent { namespace namespace libtorrent { - boost::shared_ptr create_ut_pex_plugin(torrent* t, void*) + boost::shared_ptr create_ut_pex_plugin(torrent* t) { if (t->torrent_file().priv()) { diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index a307fc9cb..6c6745f30 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -69,6 +69,9 @@ namespace libtorrent { INVARIANT_CHECK; + // we always prefer downloading entire + // pieces from web seeds + prefer_whole_pieces(true); // we want large blocks as well, so // we can request more bytes at once request_large_blocks(true); @@ -77,10 +80,6 @@ namespace libtorrent shared_ptr tor = t.lock(); assert(tor); int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); - - // we always prefer downloading 1 MB chunks - // from web seeds - prefer_whole_pieces((1024 * 1024) / tor->torrent_file().piece_length()); // multiply with the blocks per piece since that many requests are // merged into one http request @@ -179,16 +178,13 @@ namespace libtorrent int size = r.length; const int block_size = t->block_size(); - const int piece_size = t->torrent_file().piece_length(); - peer_request pr; while (size > 0) { - int request_offset = r.start + r.length - size; - pr.start = request_offset % piece_size; - pr.length = (std::min)(block_size, size); - pr.piece = r.piece + request_offset / piece_size; + int request_size = std::min(block_size, size); + peer_request pr = {r.piece, r.start + r.length - size + , request_size}; m_requests.push_back(pr); - size -= pr.length; + size -= request_size; } proxy_settings const& ps = m_ses.web_seed_proxy(); @@ -481,11 +477,8 @@ namespace libtorrent peer_request front_request = m_requests.front(); - size_type rs = size_type(in_range.piece) * info.piece_length() + in_range.start; - size_type re = rs + in_range.length; - size_type fs = size_type(front_request.piece) * info.piece_length() + front_request.start; - size_type fe = fs + front_request.length; - if (fs < rs || fe > re) + if (in_range.piece != front_request.piece + || in_range.start > front_request.start + int(m_piece.size())) { throw std::runtime_error("invalid range in HTTP response"); } @@ -493,7 +486,7 @@ namespace libtorrent // skip the http header and the blocks we've already read. The // http_body.begin is now in sync with the request at the front // of the request queue -// assert(in_range.start - int(m_piece.size()) <= front_request.start); + assert(in_range.start - int(m_piece.size()) <= front_request.start); // the http response body consists of 3 parts // 1. the middle of a block or the ending of a block @@ -517,7 +510,7 @@ namespace libtorrent // m_piece as buffer. int piece_size = int(m_piece.size()); - int copy_size = (std::min)((std::min)(front_request.length - piece_size + int copy_size = std::min(std::min(front_request.length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); m_piece.resize(piece_size + copy_size); assert(copy_size > 0); @@ -575,7 +568,7 @@ namespace libtorrent && (m_received_body + recv_buffer.left() >= range_end - range_start)) { int piece_size = int(m_piece.size()); - int copy_size = (std::min)((std::min)(m_requests.front().length - piece_size + int copy_size = std::min(std::min(m_requests.front().length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); assert(copy_size >= 0); if (copy_size > 0) From adf5487962f7142d49fdbb4c2a27ff42c79cf8e5 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 00:55:58 +0000 Subject: [PATCH 0110/1009] libtorrent sync 1592 --- libtorrent/bindings/python/src/alert.cpp | 16 +- libtorrent/bindings/python/src/docstrings.cpp | 22 +- libtorrent/bindings/python/src/extensions.cpp | 2 +- libtorrent/bindings/python/src/session.cpp | 10 +- .../bindings/python/src/session_settings.cpp | 5 +- .../bindings/python/src/torrent_info.cpp | 32 +- libtorrent/include/asio/basic_socket.hpp | 6 + libtorrent/include/asio/buffer.hpp | 7 +- .../include/asio/completion_condition.hpp | 44 ++ .../include/asio/detail/epoll_reactor.hpp | 155 ++--- .../include/asio/detail/kqueue_reactor.hpp | 82 ++- libtorrent/include/asio/detail/null_event.hpp | 9 +- .../include/asio/detail/posix_event.hpp | 40 +- .../include/asio/detail/posix_mutex.hpp | 10 +- .../include/asio/detail/posix_thread.hpp | 3 +- .../include/asio/detail/posix_tss_ptr.hpp | 3 +- .../asio/detail/reactive_socket_service.hpp | 10 +- .../include/asio/detail/scoped_lock.hpp | 12 + .../include/asio/detail/select_reactor.hpp | 51 +- .../include/asio/detail/service_registry.hpp | 6 +- libtorrent/include/asio/detail/socket_ops.hpp | 38 +- .../include/asio/detail/socket_option.hpp | 13 +- .../include/asio/detail/socket_types.hpp | 7 + .../include/asio/detail/strand_service.hpp | 9 +- .../include/asio/detail/task_io_service.hpp | 99 ++- .../include/asio/detail/timer_queue.hpp | 52 +- .../include/asio/detail/timer_queue_base.hpp | 6 + libtorrent/include/asio/detail/win_event.hpp | 21 +- .../asio/detail/win_iocp_io_service.hpp | 14 +- .../asio/detail/win_iocp_io_service_fwd.hpp | 2 + .../asio/detail/win_iocp_socket_service.hpp | 166 +++-- libtorrent/include/asio/detail/win_mutex.hpp | 5 +- libtorrent/include/asio/detail/win_thread.hpp | 4 +- .../include/asio/detail/win_tss_ptr.hpp | 4 +- .../include/asio/detail/winsock_init.hpp | 3 +- .../include/asio/detail/wrapped_handler.hpp | 14 +- libtorrent/include/asio/error.hpp | 346 +++------- libtorrent/include/asio/error_code.hpp | 44 +- libtorrent/include/asio/impl/error_code.ipp | 6 +- libtorrent/include/asio/impl/io_service.ipp | 4 +- libtorrent/include/asio/impl/read_until.ipp | 18 +- libtorrent/include/asio/io_service.hpp | 2 +- libtorrent/include/asio/ip/basic_endpoint.hpp | 34 +- libtorrent/include/asio/ip/detail/CVS/Entries | 2 - .../include/asio/ip/detail/CVS/Repository | 1 - libtorrent/include/asio/ip/detail/CVS/Root | 1 - .../asio/ssl/detail/openssl_operation.hpp | 4 +- libtorrent/include/libtorrent/alert.hpp | 2 +- libtorrent/include/libtorrent/alert_types.hpp | 14 +- libtorrent/include/libtorrent/assert.hpp | 54 ++ .../include/libtorrent/aux_/session_impl.hpp | 58 +- .../include/libtorrent/bandwidth_manager.hpp | 55 +- libtorrent/include/libtorrent/bencode.hpp | 2 + .../include/libtorrent/broadcast_socket.hpp | 84 +++ .../include/libtorrent/bt_peer_connection.hpp | 30 +- libtorrent/include/libtorrent/buffer.hpp | 3 +- .../include/libtorrent/connection_queue.hpp | 5 + libtorrent/include/libtorrent/debug.hpp | 1 + .../include/libtorrent/disk_io_thread.hpp | 15 + libtorrent/include/libtorrent/entry.hpp | 2 +- libtorrent/include/libtorrent/enum_net.hpp | 44 ++ libtorrent/include/libtorrent/extensions.hpp | 15 + .../extensions/metadata_transfer.hpp | 2 +- .../include/libtorrent/extensions/ut_pex.hpp | 2 +- libtorrent/include/libtorrent/fingerprint.hpp | 2 + libtorrent/include/libtorrent/hasher.hpp | 2 +- .../include/libtorrent/http_connection.hpp | 12 +- .../libtorrent/http_tracker_connection.hpp | 4 + .../include/libtorrent/intrusive_ptr_base.hpp | 5 +- .../include/libtorrent/invariant_check.hpp | 2 +- libtorrent/include/libtorrent/ip_filter.hpp | 6 +- .../include/libtorrent/kademlia/node.hpp | 2 +- .../include/libtorrent/kademlia/node_id.hpp | 2 +- .../libtorrent/kademlia/routing_table.hpp | 1 + libtorrent/include/libtorrent/lsd.hpp | 18 +- libtorrent/include/libtorrent/pe_crypto.hpp | 5 +- .../include/libtorrent/peer_connection.hpp | 72 +- libtorrent/include/libtorrent/peer_id.hpp | 2 +- libtorrent/include/libtorrent/peer_info.hpp | 15 +- .../include/libtorrent/piece_picker.hpp | 50 +- libtorrent/include/libtorrent/policy.hpp | 31 +- libtorrent/include/libtorrent/session.hpp | 33 +- .../include/libtorrent/session_settings.hpp | 12 +- libtorrent/include/libtorrent/stat.hpp | 1 + libtorrent/include/libtorrent/storage.hpp | 30 +- libtorrent/include/libtorrent/time.hpp | 7 +- libtorrent/include/libtorrent/torrent.hpp | 57 +- .../include/libtorrent/torrent_handle.hpp | 6 +- .../include/libtorrent/torrent_info.hpp | 84 ++- .../include/libtorrent/tracker_manager.hpp | 3 +- libtorrent/include/libtorrent/upnp.hpp | 28 +- .../libtorrent/web_peer_connection.hpp | 3 +- libtorrent/src/assert.cpp | 73 +++ libtorrent/src/broadcast_socket.cpp | 186 ++++++ libtorrent/src/bt_peer_connection.cpp | 245 ++++++- libtorrent/src/connection_queue.cpp | 26 +- libtorrent/src/disk_io_thread.cpp | 83 ++- libtorrent/src/enum_net.cpp | 142 ++++ libtorrent/src/escape_string.cpp | 3 +- libtorrent/src/file.cpp | 19 +- libtorrent/src/http_connection.cpp | 7 +- libtorrent/src/http_tracker_connection.cpp | 101 +-- libtorrent/src/identify_client.cpp | 77 ++- libtorrent/src/kademlia/closest_nodes.cpp | 1 + libtorrent/src/kademlia/dht_tracker.cpp | 8 +- libtorrent/src/kademlia/node_id.cpp | 2 +- libtorrent/src/lsd.cpp | 88 +-- libtorrent/src/metadata_transfer.cpp | 4 +- libtorrent/src/natpmp.cpp | 6 +- libtorrent/src/pe_crypto.cpp | 263 ++++---- libtorrent/src/peer_connection.cpp | 615 +++++++++++++----- libtorrent/src/piece_picker.cpp | 563 +++++++++++----- libtorrent/src/policy.cpp | 306 +++++---- libtorrent/src/session.cpp | 35 +- libtorrent/src/session_impl.cpp | 518 ++++++++++----- libtorrent/src/socks5_stream.cpp | 1 + libtorrent/src/storage.cpp | 254 ++++---- libtorrent/src/torrent.cpp | 382 ++++++----- libtorrent/src/torrent_handle.cpp | 460 ++++++++----- libtorrent/src/torrent_info.cpp | 43 +- libtorrent/src/tracker_manager.cpp | 22 +- libtorrent/src/udp_tracker_connection.cpp | 38 +- libtorrent/src/upnp.cpp | 269 +++----- libtorrent/src/ut_pex.cpp | 71 +- libtorrent/src/web_peer_connection.cpp | 31 +- 125 files changed, 4836 insertions(+), 2433 deletions(-) delete mode 100644 libtorrent/include/asio/ip/detail/CVS/Entries delete mode 100644 libtorrent/include/asio/ip/detail/CVS/Repository delete mode 100644 libtorrent/include/asio/ip/detail/CVS/Root diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index f34cf4b5d..25c82a342 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -25,6 +25,8 @@ extern char const* peer_error_alert_doc; extern char const* invalid_request_alert_doc; extern char const* peer_request_doc; extern char const* torrent_finished_alert_doc; +extern char const* torrent_paused_alert_doc; +extern char const* storage_moved_alert_doc; extern char const* metadata_failed_alert_doc; extern char const* metadata_received_alert_doc; extern char const* fastresume_rejected_alert_doc; @@ -140,7 +142,18 @@ void bind_alert() ) .def_readonly("handle", &torrent_finished_alert::handle) ; - + + class_, noncopyable>( + "torrent_paused_alert", torrent_paused_alert_doc, no_init + ) + .def_readonly("handle", &torrent_paused_alert::handle) + ; + + class_, noncopyable>( + "storage_moved_alert", storage_moved_alert_doc, no_init + ) + .def_readonly("handle", &storage_moved_alert::handle) + ; class_, noncopyable>( "metadata_failed_alert", metadata_failed_alert_doc, no_init ) @@ -160,3 +173,4 @@ void bind_alert() ; } + diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index fbd0a157c..ae1e8e7ba 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -164,14 +164,14 @@ char const* session_set_severity_level_doc = ""; char const* session_pop_alert_doc = ""; -char const* session_start_upnp_doc = +char const* session_start_upnp_doc = ""; -char const* session_stop_upnp_doc = +char const* session_stop_upnp_doc = ""; - char const* session_start_natpmp_doc = +char const* session_start_natpmp_doc = + ""; +char const* session_stop_natpmp_doc = ""; -char const* session_stop_natpmp_doc = - ""; // -- alert ----------------------------------------------------------------- char const* alert_doc = @@ -257,6 +257,17 @@ char const* torrent_finished_alert_doc = "It contains a `torrent_handle` to the torrent in question. This alert\n" "is generated as severity level `alert.severity_levels.info`."; +char const* torrent_paused_alert_doc = + "This alert is generated when a torrent switches from being a\n" + "active to paused.\n" + "It contains a `torrent_handle` to the torrent in question. This alert\n" + "is generated as severity level `alert.severity_levels.warning`."; + +char const* storage_moved_alert_doc = + "This alert is generated when a torrent moves storage.\n" + "It contains a `torrent_handle` to the torrent in question. This alert\n" + "is generated as severity level `alert.severity_levels.warning`."; + char const* metadata_failed_alert_doc = "This alert is generated when the metadata has been completely\n" "received and the info-hash failed to match it. i.e. the\n" @@ -279,3 +290,4 @@ char const* fastresume_rejected_alert_doc = "fastresume file. The string explains the reason why the resume\n" "file was rejected. It is generated at severity level `alert.severity_levels.warning`."; + diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp index 10d18ff94..1951446ed 100755 --- a/libtorrent/bindings/python/src/extensions.cpp +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -142,8 +142,8 @@ void bind_extensions() // TODO move to it's own file class_("peer_connection", no_init); - class_ >("torrent_plugin", no_init); def("create_ut_pex_plugin", create_ut_pex_plugin); def("create_metadata_plugin", create_metadata_plugin); } + diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 4ea7a1711..58fe76b3e 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -46,7 +46,7 @@ extern char const* session_set_max_connections_doc; extern char const* session_set_max_half_open_connections_doc; extern char const* session_set_settings_doc; extern char const* session_set_pe_settings_doc; -extern char const* session_get_pe_settings_doc; +extern char const* session_get_pe_settings_doc; extern char const* session_set_severity_level_doc; extern char const* session_pop_alert_doc; extern char const* session_start_upnp_doc; @@ -86,11 +86,10 @@ namespace torrent_handle add_torrent(session& s, torrent_info const& ti , boost::filesystem::path const& save, entry const& resume - , bool compact, int block_size) + , bool compact, bool paused) { allow_threading_guard guard; - return s.add_torrent(ti, save, resume, compact, block_size - , default_storage_constructor); + return s.add_torrent(ti, save, resume, compact, paused, default_storage_constructor); } } // namespace unnamed @@ -175,7 +174,7 @@ void bind_session() "add_torrent", &add_torrent , ( arg("torrent_info"), "save_path", arg("resume_data") = entry() - , arg("compact_mode") = true, arg("block_size") = 16 * 1024 + , arg("compact_mode") = true, arg("paused") = false ) , session_add_torrent_doc ) @@ -236,3 +235,4 @@ void bind_session() register_ptr_to_python >(); } + diff --git a/libtorrent/bindings/python/src/session_settings.cpp b/libtorrent/bindings/python/src/session_settings.cpp index f584956b2..f893f560c 100755 --- a/libtorrent/bindings/python/src/session_settings.cpp +++ b/libtorrent/bindings/python/src/session_settings.cpp @@ -47,7 +47,7 @@ void bind_session_settings() .value("http", proxy_settings::http) .value("http_pw", proxy_settings::http_pw) ; - class_("proxy_settings") + class_("proxy_settings") .def_readwrite("hostname", &proxy_settings::hostname) .def_readwrite("port", &proxy_settings::port) .def_readwrite("password", &proxy_settings::password) @@ -64,7 +64,7 @@ void bind_session_settings() enum_("enc_level") .value("rc4", pe_settings::rc4) .value("plaintext", pe_settings::plaintext) - .value("both", pe_settings::both) + .value("both", pe_settings::both) ; class_("pe_settings") @@ -76,3 +76,4 @@ void bind_session_settings() } + diff --git a/libtorrent/bindings/python/src/torrent_info.cpp b/libtorrent/bindings/python/src/torrent_info.cpp index 301c4a5bf..359ab5449 100755 --- a/libtorrent/bindings/python/src/torrent_info.cpp +++ b/libtorrent/bindings/python/src/torrent_info.cpp @@ -16,7 +16,6 @@ namespace return i.trackers().begin(); } - std::vector::const_iterator end_trackers(torrent_info& i) { return i.trackers().end(); @@ -41,6 +40,29 @@ namespace return result; } + std::vector::const_iterator begin_files(torrent_info& i, bool storage) + { + return i.begin_files(storage); + } + + std::vector::const_iterator end_files(torrent_info& i, bool storage) + { + return i.end_files(storage); + } + + //list files(torrent_info const& ti, bool storage) { + list files(torrent_info const& ti, bool storage) { + list result; + + typedef std::vector list_type; + + for (list_type::const_iterator i = ti.begin_files(storage); i != ti.end_files(storage); ++i) + result.append(*i); + + return result; + } + + } // namespace unnamed void bind_torrent_info() @@ -71,9 +93,9 @@ void bind_torrent_info() .def("hash_for_piece", &torrent_info::hash_for_piece, copy) .def("piece_size", &torrent_info::piece_size) - .def("num_files", &torrent_info::num_files) + .def("num_files", &torrent_info::num_files, (arg("storage")=false)) .def("file_at", &torrent_info::file_at, return_internal_reference<>()) - .def("files", range(&torrent_info::begin_files, &torrent_info::end_files)) + .def("files", &files, (arg("storage")=false)) .def("priv", &torrent_info::priv) .def("set_priv", &torrent_info::set_priv) @@ -84,9 +106,8 @@ void bind_torrent_info() .def("add_node", &add_node) .def("nodes", &nodes) ; - class_("file_entry") - .add_property( + .add_property( "path" , make_getter( &file_entry::path, return_value_policy() @@ -102,3 +123,4 @@ void bind_torrent_info() ; } + diff --git a/libtorrent/include/asio/basic_socket.hpp b/libtorrent/include/asio/basic_socket.hpp index b0dc52e48..2b2521b69 100644 --- a/libtorrent/include/asio/basic_socket.hpp +++ b/libtorrent/include/asio/basic_socket.hpp @@ -238,6 +238,9 @@ public: * with the asio::error::operation_aborted error. * * @throws asio::system_error Thrown on failure. + * + * @note For portable behaviour with respect to graceful closure of a + * connected socket, call shutdown() before closing the socket. */ void close() { @@ -265,6 +268,9 @@ public: * // An error occurred. * } * @endcode + * + * @note For portable behaviour with respect to graceful closure of a + * connected socket, call shutdown() before closing the socket. */ asio::error_code close(asio::error_code& ec) { diff --git a/libtorrent/include/asio/buffer.hpp b/libtorrent/include/asio/buffer.hpp index 7e5dc76c8..9fe76178c 100644 --- a/libtorrent/include/asio/buffer.hpp +++ b/libtorrent/include/asio/buffer.hpp @@ -542,9 +542,10 @@ inline const_buffers_1 buffer(const PodType (&data)[N], ? N * sizeof(PodType) : max_size_in_bytes)); } -#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) \ + || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) -// Borland C++ thinks the overloads: +// Borland C++ and Sun Studio think the overloads: // // unspecified buffer(boost::array& array ...); // @@ -610,6 +611,7 @@ buffer(boost::array& data, std::size_t max_size_in_bytes) } #else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + // || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) /// Create a new modifiable buffer that represents the given POD array. template @@ -650,6 +652,7 @@ inline const_buffers_1 buffer(boost::array& data, } #endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + // || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x590)) /// Create a new non-modifiable buffer that represents the given POD array. template diff --git a/libtorrent/include/asio/completion_condition.hpp b/libtorrent/include/asio/completion_condition.hpp index 42696d599..939a4a8f3 100644 --- a/libtorrent/include/asio/completion_condition.hpp +++ b/libtorrent/include/asio/completion_condition.hpp @@ -71,6 +71,28 @@ private: /// Return a completion condition function object that indicates that a read or /// write operation should continue until all of the data has been transferred, /// or until an error occurs. +/** + * This function is used to create an object, of unspecified type, that meets + * CompletionCondition requirements. + * + * @par Example + * Reading until a buffer is full: + * @code + * boost::array buf; + * asio::error_code ec; + * std::size_t n = asio::read( + * sock, asio::buffer(buf), + * asio::transfer_all(), ec); + * if (ec) + * { + * // An error occurred. + * } + * else + * { + * // n == 128 + * } + * @endcode + */ #if defined(GENERATING_DOCUMENTATION) unspecified transfer_all(); #else @@ -83,6 +105,28 @@ inline detail::transfer_all_t transfer_all() /// Return a completion condition function object that indicates that a read or /// write operation should continue until a minimum number of bytes has been /// transferred, or until an error occurs. +/** + * This function is used to create an object, of unspecified type, that meets + * CompletionCondition requirements. + * + * @par Example + * Reading until a buffer is full or contains at least 64 bytes: + * @code + * boost::array buf; + * asio::error_code ec; + * std::size_t n = asio::read( + * sock, asio::buffer(buf), + * asio::transfer_at_least(64), ec); + * if (ec) + * { + * // An error occurred. + * } + * else + * { + * // n >= 64 && n <= 128 + * } + * @endcode + */ #if defined(GENERATING_DOCUMENTATION) unspecified transfer_at_least(std::size_t minimum); #else diff --git a/libtorrent/include/asio/detail/epoll_reactor.hpp b/libtorrent/include/asio/detail/epoll_reactor.hpp index d55e86454..e260c5194 100644 --- a/libtorrent/include/asio/detail/epoll_reactor.hpp +++ b/libtorrent/include/asio/detail/epoll_reactor.hpp @@ -157,7 +157,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -190,7 +191,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -219,7 +221,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -250,7 +253,8 @@ public: int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); if (result != 0) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); except_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -331,7 +335,10 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - return timer_queue.cancel_timer(token); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; } private: @@ -347,16 +354,13 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -365,12 +369,7 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -398,59 +397,45 @@ private: } else { - if (events[i].events & (EPOLLERR | EPOLLHUP)) + bool more_reads = false; + bool more_writes = false; + bool more_except = false; + asio::error_code ec; + + // Exception operations must be processed first to ensure that any + // out-of-band data is read before normal data. + if (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP)) + more_except = except_op_queue_.dispatch_operation(descriptor, ec); + else + more_except = except_op_queue_.has_operation(descriptor); + + if (events[i].events & (EPOLLIN | EPOLLERR | EPOLLHUP)) + more_reads = read_op_queue_.dispatch_operation(descriptor, ec); + else + more_reads = read_op_queue_.has_operation(descriptor); + + if (events[i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) + more_writes = write_op_queue_.dispatch_operation(descriptor, ec); + else + more_writes = write_op_queue_.has_operation(descriptor); + + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLERR | EPOLLHUP; + if (more_reads) + ev.events |= EPOLLIN; + if (more_writes) + ev.events |= EPOLLOUT; + if (more_except) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0) { - asio::error_code ec; - except_op_queue_.dispatch_all_operations(descriptor, ec); + ec = asio::error_code(errno, + asio::error::system_category); read_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); - - epoll_event ev = { 0, { 0 } }; - ev.events = 0; - ev.data.fd = descriptor; - epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); - } - else - { - bool more_reads = false; - bool more_writes = false; - bool more_except = false; - asio::error_code ec; - - // Exception operations must be processed first to ensure that any - // out-of-band data is read before normal data. - if (events[i].events & EPOLLPRI) - more_except = except_op_queue_.dispatch_operation(descriptor, ec); - else - more_except = except_op_queue_.has_operation(descriptor); - - if (events[i].events & EPOLLIN) - more_reads = read_op_queue_.dispatch_operation(descriptor, ec); - else - more_reads = read_op_queue_.has_operation(descriptor); - - if (events[i].events & EPOLLOUT) - more_writes = write_op_queue_.dispatch_operation(descriptor, ec); - else - more_writes = write_op_queue_.has_operation(descriptor); - - epoll_event ev = { 0, { 0 } }; - ev.events = EPOLLERR | EPOLLHUP; - if (more_reads) - ev.events |= EPOLLIN; - if (more_writes) - ev.events |= EPOLLOUT; - if (more_except) - ev.events |= EPOLLPRI; - ev.data.fd = descriptor; - int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); - if (result != 0) - { - ec = asio::error_code(errno, asio::native_ecat); - read_op_queue_.dispatch_all_operations(descriptor, ec); - write_op_queue_.dispatch_all_operations(descriptor, ec); - except_op_queue_.dispatch_all_operations(descriptor, ec); - } + except_op_queue_.dispatch_all_operations(descriptor, ec); } } } @@ -458,19 +443,17 @@ private: write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } // Issue any pending cancellations. for (size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); } // Run the select loop in the thread. @@ -507,8 +490,10 @@ private: int fd = epoll_create(epoll_size); if (fd == -1) { - boost::throw_exception(asio::system_error( - asio::error_code(errno, asio::native_ecat), + boost::throw_exception( + asio::system_error( + asio::error_code(errno, + asio::error::system_category), "epoll")); } return fd; @@ -566,6 +551,22 @@ private: interrupter_.interrupt(); } + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -590,6 +591,10 @@ private: // The timer queues. std::vector timer_queues_; + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/kqueue_reactor.hpp b/libtorrent/include/asio/detail/kqueue_reactor.hpp index 6628803af..5fffc6788 100644 --- a/libtorrent/include/asio/detail/kqueue_reactor.hpp +++ b/libtorrent/include/asio/detail/kqueue_reactor.hpp @@ -150,7 +150,8 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -176,7 +177,8 @@ public: EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -201,7 +203,8 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -224,7 +227,8 @@ public: EV_SET(&event, descriptor, EVFILT_WRITE, EV_ADD, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -238,7 +242,8 @@ public: EV_SET(&event, descriptor, EVFILT_READ, EV_ADD, EV_OOBAND, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code ec(errno, asio::native_ecat); + asio::error_code ec(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -321,7 +326,10 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - return timer_queue.cancel_timer(token); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; } private: @@ -337,16 +345,13 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -355,12 +360,7 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -397,7 +397,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::native_ecat); + events[i].data, asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -427,7 +427,8 @@ private: EV_SET(&event, descriptor, EVFILT_READ, EV_DELETE, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code error(errno, asio::native_ecat); + asio::error_code error(errno, + asio::error::system_category); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -439,7 +440,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::native_ecat); + events[i].data, asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, error); } else @@ -456,7 +457,8 @@ private: EV_SET(&event, descriptor, EVFILT_WRITE, EV_DELETE, 0, 0, 0); if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { - asio::error_code error(errno, asio::native_ecat); + asio::error_code error(errno, + asio::error::system_category); write_op_queue_.dispatch_all_operations(descriptor, error); } } @@ -466,19 +468,17 @@ private: write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } // Issue any pending cancellations. for (std::size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); } // Run the select loop in the thread. @@ -512,8 +512,10 @@ private: int fd = kqueue(); if (fd == -1) { - boost::throw_exception(asio::system_error( - asio::error_code(errno, asio::native_ecat), + boost::throw_exception( + asio::system_error( + asio::error_code(errno, + asio::error::system_category), "kqueue")); } return fd; @@ -573,6 +575,22 @@ private: interrupter_.interrupt(); } + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -597,6 +615,10 @@ private: // The timer queues. std::vector timer_queues_; + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/null_event.hpp b/libtorrent/include/asio/detail/null_event.hpp index df522ce0f..99bcbc6a4 100644 --- a/libtorrent/include/asio/detail/null_event.hpp +++ b/libtorrent/include/asio/detail/null_event.hpp @@ -43,17 +43,20 @@ public: } // Signal the event. - void signal() + template + void signal(Lock&) { } // Reset the event. - void clear() + template + void clear(Lock&) { } // Wait for the event to become signalled. - void wait() + template + void wait(Lock&) { } }; diff --git a/libtorrent/include/asio/detail/posix_event.hpp b/libtorrent/include/asio/detail/posix_event.hpp index 408c23bb9..b48586f15 100644 --- a/libtorrent/include/asio/detail/posix_event.hpp +++ b/libtorrent/include/asio/detail/posix_event.hpp @@ -24,10 +24,12 @@ #if defined(BOOST_HAS_PTHREADS) #include "asio/detail/push_options.hpp" +#include #include #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -42,21 +44,11 @@ public: posix_event() : signalled_(false) { - int error = ::pthread_mutex_init(&mutex_, 0); + int error = ::pthread_cond_init(&cond_, 0); if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), - "event"); - boost::throw_exception(e); - } - - error = ::pthread_cond_init(&cond_, 0); - if (error != 0) - { - ::pthread_mutex_destroy(&mutex_); - asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "event"); boost::throw_exception(e); } @@ -66,37 +58,37 @@ public: ~posix_event() { ::pthread_cond_destroy(&cond_); - ::pthread_mutex_destroy(&mutex_); } // Signal the event. - void signal() + template + void signal(Lock& lock) { - ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + BOOST_ASSERT(lock.locked()); + (void)lock; signalled_ = true; ::pthread_cond_signal(&cond_); // Ignore EINVAL. - ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } // Reset the event. - void clear() + template + void clear(Lock& lock) { - ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + BOOST_ASSERT(lock.locked()); + (void)lock; signalled_ = false; - ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. } // Wait for the event to become signalled. - void wait() + template + void wait(Lock& lock) { - ::pthread_mutex_lock(&mutex_); // Ignore EINVAL and EDEADLK. + BOOST_ASSERT(lock.locked()); while (!signalled_) - ::pthread_cond_wait(&cond_, &mutex_); // Ignore EINVAL. - ::pthread_mutex_unlock(&mutex_); // Ignore EINVAL and EPERM. + ::pthread_cond_wait(&cond_, &lock.mutex().mutex_); // Ignore EINVAL. } private: - ::pthread_mutex_t mutex_; ::pthread_cond_t cond_; bool signalled_; }; diff --git a/libtorrent/include/asio/detail/posix_mutex.hpp b/libtorrent/include/asio/detail/posix_mutex.hpp index 154089f3c..6067880fb 100644 --- a/libtorrent/include/asio/detail/posix_mutex.hpp +++ b/libtorrent/include/asio/detail/posix_mutex.hpp @@ -28,6 +28,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/scoped_lock.hpp" @@ -35,6 +36,8 @@ namespace asio { namespace detail { +class posix_event; + class posix_mutex : private noncopyable { @@ -48,7 +51,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } @@ -67,7 +70,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } @@ -80,13 +83,14 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } } private: + friend class posix_event; ::pthread_mutex_t mutex_; }; diff --git a/libtorrent/include/asio/detail/posix_thread.hpp b/libtorrent/include/asio/detail/posix_thread.hpp index f01b40428..6e5815426 100644 --- a/libtorrent/include/asio/detail/posix_thread.hpp +++ b/libtorrent/include/asio/detail/posix_thread.hpp @@ -29,6 +29,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -52,7 +53,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/posix_tss_ptr.hpp b/libtorrent/include/asio/detail/posix_tss_ptr.hpp index 93fce3479..dda329c40 100644 --- a/libtorrent/include/asio/detail/posix_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/posix_tss_ptr.hpp @@ -28,6 +28,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" @@ -46,7 +47,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/reactive_socket_service.hpp b/libtorrent/include/asio/detail/reactive_socket_service.hpp index d5b8e4fc8..9c0075821 100644 --- a/libtorrent/include/asio/detail/reactive_socket_service.hpp +++ b/libtorrent/include/asio/detail/reactive_socket_service.hpp @@ -86,7 +86,7 @@ public: }; // The maximum number of buffers to support in a single operation. - enum { max_buffers = 16 }; + enum { max_buffers = 64 < max_iov_len ? 64 : max_iov_len }; // Constructor. reactive_socket_service(asio::io_service& io_service) @@ -157,7 +157,7 @@ public: if (int err = reactor_.register_descriptor(sock.get())) { - ec = asio::error_code(err, asio::native_ecat); + ec = asio::error_code(err, asio::error::system_category); return ec; } @@ -181,7 +181,7 @@ public: if (int err = reactor_.register_descriptor(native_socket)) { - ec = asio::error_code(err, asio::native_ecat); + ec = asio::error_code(err, asio::error::system_category); return ec; } @@ -1124,7 +1124,7 @@ public: bool operator()(const asio::error_code& result) { // Check whether the operation was successful. - if (result != 0) + if (result) { io_service_.post(bind_handler(handler_, result, 0)); return true; @@ -1489,7 +1489,7 @@ public: if (connect_error) { ec = asio::error_code(connect_error, - asio::native_ecat); + asio::error::system_category); io_service_.post(bind_handler(handler_, ec)); return true; } diff --git a/libtorrent/include/asio/detail/scoped_lock.hpp b/libtorrent/include/asio/detail/scoped_lock.hpp index 64c77cbab..57f8cb8f6 100644 --- a/libtorrent/include/asio/detail/scoped_lock.hpp +++ b/libtorrent/include/asio/detail/scoped_lock.hpp @@ -63,6 +63,18 @@ public: } } + // Test whether the lock is held. + bool locked() const + { + return locked_; + } + + // Get the underlying mutex. + Mutex& mutex() + { + return mutex_; + } + private: // The underlying mutex. Mutex& mutex_; diff --git a/libtorrent/include/asio/detail/select_reactor.hpp b/libtorrent/include/asio/detail/select_reactor.hpp index 83f093ae6..079ec2de8 100644 --- a/libtorrent/include/asio/detail/select_reactor.hpp +++ b/libtorrent/include/asio/detail/select_reactor.hpp @@ -229,7 +229,10 @@ public: std::size_t cancel_timer(timer_queue& timer_queue, void* token) { asio::detail::mutex::scoped_lock lock(mutex_); - return timer_queue.cancel_timer(token); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; } private: @@ -245,16 +248,13 @@ private: read_op_queue_.dispatch_cancellations(); write_op_queue_.dispatch_cancellations(); except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); // Check if the thread is supposed to stop. if (stop_thread_) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -263,12 +263,7 @@ private: if (!block && read_op_queue_.empty() && write_op_queue_.empty() && except_op_queue_.empty() && all_timer_queues_are_empty()) { - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); return; } @@ -321,19 +316,17 @@ private: write_op_queue_.dispatch_cancellations(); } for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } // Issue any pending cancellations. for (size_t i = 0; i < pending_cancellations_.size(); ++i) cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); - // Clean up operations. We must not hold the lock since the operations may - // make calls back into this reactor. - lock.unlock(); - read_op_queue_.cleanup_operations(); - write_op_queue_.cleanup_operations(); - except_op_queue_.cleanup_operations(); + cleanup_operations_and_timers(lock); } // Run the select loop in the thread. @@ -414,6 +407,22 @@ private: interrupter_.interrupt(); } + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + // Mutex to protect access to internal data. asio::detail::mutex mutex_; @@ -435,6 +444,10 @@ private: // The timer queues. std::vector timer_queues_; + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + // The descriptors that are pending cancellation. std::vector pending_cancellations_; diff --git a/libtorrent/include/asio/detail/service_registry.hpp b/libtorrent/include/asio/detail/service_registry.hpp index bd1c3ea5b..3a9e9f620 100644 --- a/libtorrent/include/asio/detail/service_registry.hpp +++ b/libtorrent/include/asio/detail/service_registry.hpp @@ -166,7 +166,8 @@ private: } // Check if a service matches the given id. - bool service_id_matches(const asio::io_service::service& service, + static bool service_id_matches( + const asio::io_service::service& service, const asio::io_service::id& id) { return service.id_ == &id; @@ -174,7 +175,8 @@ private: // Check if a service matches the given id. template - bool service_id_matches(const asio::io_service::service& service, + static bool service_id_matches( + const asio::io_service::service& service, const asio::detail::service_id& /*id*/) { return service.type_info_ != 0 && *service.type_info_ == typeid(Service); diff --git a/libtorrent/include/asio/detail/socket_ops.hpp b/libtorrent/include/asio/detail/socket_ops.hpp index 4b38c6ee8..98f3b0f64 100644 --- a/libtorrent/include/asio/detail/socket_ops.hpp +++ b/libtorrent/include/asio/detail/socket_ops.hpp @@ -52,9 +52,10 @@ inline ReturnType error_wrapper(ReturnType return_value, asio::error_code& ec) { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) - ec = asio::error_code(WSAGetLastError(), asio::native_ecat); + ec = asio::error_code(WSAGetLastError(), + asio::error::system_category); #else - ec = asio::error_code(errno, asio::native_ecat); + ec = asio::error_code(errno, asio::error::system_category); #endif return return_value; } @@ -923,6 +924,13 @@ inline void gai_free(void* p) ::operator delete(p); } +inline void gai_strcpy(char* target, const char* source, std::size_t max_size) +{ + using namespace std; + *target = 0; + strncat(target, source, max_size); +} + enum { gai_clone_flag = 1 << 30 }; inline int gai_aistruct(addrinfo_type*** next, const addrinfo_type* hints, @@ -1292,14 +1300,15 @@ inline int getaddrinfo_emulation(const char* host, const char* service, if (host != 0 && host[0] != '\0' && hptr->h_name && hptr->h_name[0] && (hints.ai_flags & AI_CANONNAME) && canon == 0) { - canon = gai_alloc(strlen(hptr->h_name) + 1); + std::size_t canon_len = strlen(hptr->h_name) + 1; + canon = gai_alloc(canon_len); if (canon == 0) { freeaddrinfo_emulation(aihead); socket_ops::freehostent(hptr); return EAI_MEMORY; } - strcpy(canon, hptr->h_name); + gai_strcpy(canon, hptr->h_name, canon_len); } // Create an addrinfo structure for each returned address. @@ -1335,13 +1344,14 @@ inline int getaddrinfo_emulation(const char* host, const char* service, } else { - aihead->ai_canonname = gai_alloc(strlen(search[0].host) + 1); + std::size_t canonname_len = strlen(search[0].host) + 1; + aihead->ai_canonname = gai_alloc(canonname_len); if (aihead->ai_canonname == 0) { freeaddrinfo_emulation(aihead); return EAI_MEMORY; } - strcpy(aihead->ai_canonname, search[0].host); + gai_strcpy(aihead->ai_canonname, search[0].host, canonname_len); } } gai_free(canon); @@ -1424,8 +1434,7 @@ inline asio::error_code getnameinfo_emulation( *dot = 0; } } - *host = '\0'; - strncat(host, hptr->h_name, hostlen); + gai_strcpy(host, hptr->h_name, hostlen); socket_ops::freehostent(hptr); } else @@ -1463,8 +1472,7 @@ inline asio::error_code getnameinfo_emulation( servent* sptr = ::getservbyport(port, (flags & NI_DGRAM) ? "udp" : 0); if (sptr && sptr->s_name && sptr->s_name[0] != '\0') { - *serv = '\0'; - strncat(serv, sptr->s_name, servlen); + gai_strcpy(serv, sptr->s_name, servlen); } else { @@ -1504,6 +1512,12 @@ inline asio::error_code translate_addrinfo_error(int error) case EAI_MEMORY: return asio::error::no_memory; case EAI_NONAME: +#if defined(EAI_ADDRFAMILY) + case EAI_ADDRFAMILY: +#endif +#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) + case EAI_NODATA: +#endif return asio::error::host_not_found; case EAI_SERVICE: return asio::error::service_not_found; @@ -1512,10 +1526,10 @@ inline asio::error_code translate_addrinfo_error(int error) default: // Possibly the non-portable EAI_SYSTEM. #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) return asio::error_code( - WSAGetLastError(), asio::native_ecat); + WSAGetLastError(), asio::error::system_category); #else return asio::error_code( - errno, asio::native_ecat); + errno, asio::error::system_category); #endif } } diff --git a/libtorrent/include/asio/detail/socket_option.hpp b/libtorrent/include/asio/detail/socket_option.hpp index ee867e6b2..1a03936ab 100644 --- a/libtorrent/include/asio/detail/socket_option.hpp +++ b/libtorrent/include/asio/detail/socket_option.hpp @@ -110,8 +110,19 @@ public: template void resize(const Protocol&, std::size_t s) { - if (s != sizeof(value_)) + // On some platforms (e.g. Windows Vista), the getsockopt function will + // return the size of a boolean socket option as one byte, even though a + // four byte integer was passed in. + switch (s) + { + case sizeof(char): + value_ = *reinterpret_cast(&value_) ? 1 : 0; + break; + case sizeof(value_): + break; + default: throw std::length_error("boolean socket option resize"); + } } private: diff --git a/libtorrent/include/asio/detail/socket_types.hpp b/libtorrent/include/asio/detail/socket_types.hpp index 49d1c7fc2..02c3a78d5 100644 --- a/libtorrent/include/asio/detail/socket_types.hpp +++ b/libtorrent/include/asio/detail/socket_types.hpp @@ -98,6 +98,7 @@ # include # include # include +# include # if defined(__sun) # include # include @@ -141,6 +142,11 @@ const int shutdown_both = SD_BOTH; const int message_peek = MSG_PEEK; const int message_out_of_band = MSG_OOB; const int message_do_not_route = MSG_DONTROUTE; +# if defined (_WIN32_WINNT) +const int max_iov_len = 64; +# else +const int max_iov_len = 16; +# endif #else typedef int socket_type; const int invalid_socket = -1; @@ -166,6 +172,7 @@ const int shutdown_both = SHUT_RDWR; const int message_peek = MSG_PEEK; const int message_out_of_band = MSG_OOB; const int message_do_not_route = MSG_DONTROUTE; +const int max_iov_len = IOV_MAX; #endif const int custom_socket_option_level = 0xA5100000; const int enable_connection_aborted_option = 1; diff --git a/libtorrent/include/asio/detail/strand_service.hpp b/libtorrent/include/asio/detail/strand_service.hpp index f10289090..d987cb98d 100644 --- a/libtorrent/include/asio/detail/strand_service.hpp +++ b/libtorrent/include/asio/detail/strand_service.hpp @@ -239,6 +239,7 @@ public: #else BOOST_ASSERT(size <= strand_impl::handler_storage_type::size); #endif + (void)size; return impl_->handler_storage_.address(); } @@ -415,14 +416,14 @@ public: } else { - asio::detail::mutex::scoped_lock lock(impl->mutex_); - // Allocate and construct an object to wrap the handler. typedef handler_wrapper value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, handler); + asio::detail::mutex::scoped_lock lock(impl->mutex_); + if (impl->current_handler_ == 0) { // This handler now has the lock, so can be dispatched immediately. @@ -455,14 +456,14 @@ public: template void post(implementation_type& impl, Handler handler) { - asio::detail::mutex::scoped_lock lock(impl->mutex_); - // Allocate and construct an object to wrap the handler. typedef handler_wrapper value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, handler); + asio::detail::mutex::scoped_lock lock(impl->mutex_); + if (impl->current_handler_ == 0) { // This handler now has the lock, so can be dispatched immediately. diff --git a/libtorrent/include/asio/detail/task_io_service.hpp b/libtorrent/include/asio/detail/task_io_service.hpp index 07df1c18a..802d7ea95 100644 --- a/libtorrent/include/asio/detail/task_io_service.hpp +++ b/libtorrent/include/asio/detail/task_io_service.hpp @@ -40,6 +40,7 @@ public: : asio::detail::service_base >(io_service), mutex_(), task_(use_service(io_service)), + task_interrupted_(true), outstanding_work_(0), handler_queue_(&task_handler_), handler_queue_end_(&task_handler_), @@ -80,8 +81,7 @@ public: typename call_stack::context ctx(this); idle_thread_info this_idle_thread; - this_idle_thread.prev = &this_idle_thread; - this_idle_thread.next = &this_idle_thread; + this_idle_thread.next = 0; asio::detail::mutex::scoped_lock lock(mutex_); @@ -98,8 +98,7 @@ public: typename call_stack::context ctx(this); idle_thread_info this_idle_thread; - this_idle_thread.prev = &this_idle_thread; - this_idle_thread.next = &this_idle_thread; + this_idle_thread.next = 0; asio::detail::mutex::scoped_lock lock(mutex_); @@ -134,7 +133,7 @@ public: void stop() { asio::detail::mutex::scoped_lock lock(mutex_); - stop_all_threads(); + stop_all_threads(lock); } // Reset in preparation for a subsequent run invocation. @@ -156,7 +155,7 @@ public: { asio::detail::mutex::scoped_lock lock(mutex_); if (--outstanding_work_ == 0) - stop_all_threads(); + stop_all_threads(lock); } // Request invocation of the given handler. @@ -201,9 +200,14 @@ public: ++outstanding_work_; // Wake up a thread to execute the handler. - if (!interrupt_one_idle_thread()) - if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) + if (!interrupt_one_idle_thread(lock)) + { + if (!task_interrupted_) + { + task_interrupted_ = true; task_.interrupt(); + } + } } private: @@ -214,7 +218,7 @@ private: { if (outstanding_work_ == 0 && !stopped_) { - stop_all_threads(); + stop_all_threads(lock); ec = asio::error_code(); return 0; } @@ -230,11 +234,14 @@ private: handler_queue_ = h->next_; if (handler_queue_ == 0) handler_queue_end_ = 0; - bool more_handlers = (handler_queue_ != 0); - lock.unlock(); + h->next_ = 0; if (h == &task_handler_) { + bool more_handlers = (handler_queue_ != 0); + task_interrupted_ = more_handlers || polling; + lock.unlock(); + // If the task has already run and we're polling then we're done. if (task_has_run && polling) { @@ -252,6 +259,7 @@ private: } else { + lock.unlock(); handler_cleanup c(lock, *this); // Invoke the handler. May throw an exception. @@ -264,31 +272,10 @@ private: else if (this_idle_thread) { // Nothing to run right now, so just wait for work to do. - if (first_idle_thread_) - { - this_idle_thread->next = first_idle_thread_; - this_idle_thread->prev = first_idle_thread_->prev; - first_idle_thread_->prev->next = this_idle_thread; - first_idle_thread_->prev = this_idle_thread; - } + this_idle_thread->next = first_idle_thread_; first_idle_thread_ = this_idle_thread; - this_idle_thread->wakeup_event.clear(); - lock.unlock(); - this_idle_thread->wakeup_event.wait(); - lock.lock(); - if (this_idle_thread->next == this_idle_thread) - { - first_idle_thread_ = 0; - } - else - { - if (first_idle_thread_ == this_idle_thread) - first_idle_thread_ = this_idle_thread->next; - this_idle_thread->next->prev = this_idle_thread->prev; - this_idle_thread->prev->next = this_idle_thread->next; - this_idle_thread->next = this_idle_thread; - this_idle_thread->prev = this_idle_thread; - } + this_idle_thread->wakeup_event.clear(lock); + this_idle_thread->wakeup_event.wait(lock); } else { @@ -302,39 +289,44 @@ private: } // Stop the task and all idle threads. - void stop_all_threads() + void stop_all_threads( + asio::detail::mutex::scoped_lock& lock) { stopped_ = true; - interrupt_all_idle_threads(); - if (task_handler_.next_ == 0 && handler_queue_end_ != &task_handler_) + interrupt_all_idle_threads(lock); + if (!task_interrupted_) + { + task_interrupted_ = true; task_.interrupt(); + } } // Interrupt a single idle thread. Returns true if a thread was interrupted, // false if no running thread could be found to interrupt. - bool interrupt_one_idle_thread() + bool interrupt_one_idle_thread( + asio::detail::mutex::scoped_lock& lock) { if (first_idle_thread_) { - first_idle_thread_->wakeup_event.signal(); - first_idle_thread_ = first_idle_thread_->next; + idle_thread_info* idle_thread = first_idle_thread_; + first_idle_thread_ = idle_thread->next; + idle_thread->next = 0; + idle_thread->wakeup_event.signal(lock); return true; } return false; } // Interrupt all idle threads. - void interrupt_all_idle_threads() + void interrupt_all_idle_threads( + asio::detail::mutex::scoped_lock& lock) { - if (first_idle_thread_) + while (first_idle_thread_) { - first_idle_thread_->wakeup_event.signal(); - idle_thread_info* current_idle_thread = first_idle_thread_->next; - while (current_idle_thread != first_idle_thread_) - { - current_idle_thread->wakeup_event.signal(); - current_idle_thread = current_idle_thread->next; - } + idle_thread_info* idle_thread = first_idle_thread_; + first_idle_thread_ = idle_thread->next; + idle_thread->next = 0; + idle_thread->wakeup_event.signal(lock); } } @@ -440,6 +432,7 @@ private: { // Reinsert the task at the end of the handler queue. lock_.lock(); + task_io_service_.task_interrupted_ = true; task_io_service_.task_handler_.next_ = 0; if (task_io_service_.handler_queue_end_) { @@ -478,7 +471,7 @@ private: { lock_.lock(); if (--task_io_service_.outstanding_work_ == 0) - task_io_service_.stop_all_threads(); + task_io_service_.stop_all_threads(lock_); } private: @@ -503,6 +496,9 @@ private: } } task_handler_; + // Whether the task has been interrupted. + bool task_interrupted_; + // The count of unfinished work. int outstanding_work_; @@ -522,7 +518,6 @@ private: struct idle_thread_info { event wakeup_event; - idle_thread_info* prev; idle_thread_info* next; }; diff --git a/libtorrent/include/asio/detail/timer_queue.hpp b/libtorrent/include/asio/detail/timer_queue.hpp index af1e36bd5..7735e87cf 100644 --- a/libtorrent/include/asio/detail/timer_queue.hpp +++ b/libtorrent/include/asio/detail/timer_queue.hpp @@ -48,7 +48,9 @@ public: // Constructor. timer_queue() : timers_(), - heap_() + heap_(), + cancelled_timers_(0), + cleanup_timers_(0) { } @@ -111,12 +113,17 @@ public: { timer_base* t = heap_[0]; remove_timer(t); + t->prev_ = 0; + t->next_ = cleanup_timers_; + cleanup_timers_ = t; t->invoke(asio::error_code()); } } - // Cancel the timer with the given token. The handler will be invoked - // immediately with the result operation_aborted. + // Cancel the timers with the given token. Any timers pending for the token + // will be notified that they have been cancelled next time + // dispatch_cancellations is called. Returns the number of timers that were + // cancelled. std::size_t cancel_timer(void* timer_token) { std::size_t num_cancelled = 0; @@ -129,7 +136,9 @@ public: { timer_base* next = t->next_; remove_timer(t); - t->invoke(asio::error::operation_aborted); + t->prev_ = 0; + t->next_ = cancelled_timers_; + cancelled_timers_ = t; t = next; ++num_cancelled; } @@ -137,6 +146,31 @@ public: return num_cancelled; } + // Dispatch any pending cancels for timers. + virtual void dispatch_cancellations() + { + while (cancelled_timers_) + { + timer_base* this_timer = cancelled_timers_; + cancelled_timers_ = this_timer->next_; + this_timer->next_ = cleanup_timers_; + cleanup_timers_ = this_timer; + this_timer->invoke(asio::error::operation_aborted); + } + } + + // Destroy timers that are waiting to be cleaned up. + virtual void cleanup_timers() + { + while (cleanup_timers_) + { + timer_base* next_timer = cleanup_timers_->next_; + cleanup_timers_->next_ = 0; + cleanup_timers_->destroy(); + cleanup_timers_ = next_timer; + } + } + // Destroy all timers. virtual void destroy_timers() { @@ -151,6 +185,7 @@ public: } heap_.clear(); timers_.clear(); + cleanup_timers(); } private: @@ -238,8 +273,7 @@ private: static void invoke_handler(timer_base* base, const asio::error_code& result) { - std::auto_ptr > t(static_cast*>(base)); - t->handler_(result); + static_cast*>(base)->handler_(result); } // Destroy the handler. @@ -338,6 +372,12 @@ private: // The heap of timers, with the earliest timer at the front. std::vector heap_; + + // The list of timers to be cancelled. + timer_base* cancelled_timers_; + + // The list of timers to be destroyed. + timer_base* cleanup_timers_; }; } // namespace detail diff --git a/libtorrent/include/asio/detail/timer_queue_base.hpp b/libtorrent/include/asio/detail/timer_queue_base.hpp index c8be49748..6cf25747a 100644 --- a/libtorrent/include/asio/detail/timer_queue_base.hpp +++ b/libtorrent/include/asio/detail/timer_queue_base.hpp @@ -44,6 +44,12 @@ public: // Dispatch all ready timers. virtual void dispatch_timers() = 0; + // Dispatch any pending cancels for timers. + virtual void dispatch_cancellations() = 0; + + // Destroy timers that are waiting to be cleaned up. + virtual void cleanup_timers() = 0; + // Destroy all timers. virtual void destroy_timers() = 0; }; diff --git a/libtorrent/include/asio/detail/win_event.hpp b/libtorrent/include/asio/detail/win_event.hpp index 8de9383da..c73ed56ea 100644 --- a/libtorrent/include/asio/detail/win_event.hpp +++ b/libtorrent/include/asio/detail/win_event.hpp @@ -23,11 +23,13 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" #include "asio/detail/push_options.hpp" +#include #include #include "asio/detail/pop_options.hpp" @@ -46,7 +48,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "event"); boost::throw_exception(e); } @@ -59,21 +62,31 @@ public: } // Signal the event. - void signal() + template + void signal(Lock& lock) { + BOOST_ASSERT(lock.locked()); + (void)lock; ::SetEvent(event_); } // Reset the event. - void clear() + template + void clear(Lock& lock) { + BOOST_ASSERT(lock.locked()); + (void)lock; ::ResetEvent(event_); } // Wait for the event to become signalled. - void wait() + template + void wait(Lock& lock) { + BOOST_ASSERT(lock.locked()); + lock.unlock(); ::WaitForSingleObject(event_, INFINITE); + lock.lock(); } private: diff --git a/libtorrent/include/asio/detail/win_iocp_io_service.hpp b/libtorrent/include/asio/detail/win_iocp_io_service.hpp index 4957fb01a..61eeb1745 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service.hpp @@ -63,7 +63,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "iocp"); boost::throw_exception(e); } @@ -173,7 +174,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "pqcs"); boost::throw_exception(e); } @@ -228,7 +230,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "pqcs"); boost::throw_exception(e); } @@ -247,7 +250,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "pqcs"); boost::throw_exception(e); } @@ -312,7 +316,7 @@ private: { DWORD last_error = ::GetLastError(); ec = asio::error_code(last_error, - asio::native_ecat); + asio::error::system_category); return 0; } diff --git a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp index 184fdfa18..26eacae2a 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp @@ -21,6 +21,8 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/detail/socket_types.hpp" + // This service is only supported on Win32 (NT4 and later). #if !defined(ASIO_DISABLE_IOCP) #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) diff --git a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp index 007286e8d..17d1d5887 100644 --- a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp @@ -137,7 +137,7 @@ public: enum { enable_connection_aborted = 1, // User wants connection_aborted errors. - user_set_linger = 2, // The user set the linger option. + close_might_block = 2, // User set linger option for blocking close. user_set_non_blocking = 4 // The user wants a non-blocking socket. }; @@ -170,7 +170,7 @@ public: typedef detail::select_reactor reactor_type; // The maximum number of buffers to support in a single operation. - enum { max_buffers = 16 }; + enum { max_buffers = 64 < max_iov_len ? 64 : max_iov_len }; // Constructor. win_iocp_socket_service(asio::io_service& io_service) @@ -192,7 +192,7 @@ public: while (impl) { asio::error_code ignored_ec; - close(*impl, ignored_ec); + close_for_destruction(*impl); impl = impl->next_; } } @@ -217,34 +217,7 @@ public: // Destroy a socket implementation. void destroy(implementation_type& impl) { - if (impl.socket_ != invalid_socket) - { - // Check if the reactor was created, in which case we need to close the - // socket on the reactor as well to cancel any operations that might be - // running there. - reactor_type* reactor = static_cast( - interlocked_compare_exchange_pointer( - reinterpret_cast(&reactor_), 0, 0)); - if (reactor) - reactor->close_descriptor(impl.socket_); - - if (impl.flags_ & implementation_type::user_set_linger) - { - ::linger opt; - opt.l_onoff = 0; - opt.l_linger = 0; - asio::error_code ignored_ec; - socket_ops::setsockopt(impl.socket_, - SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); - } - - asio::error_code ignored_ec; - socket_ops::close(impl.socket_, ignored_ec); - impl.socket_ = invalid_socket; - impl.flags_ = 0; - impl.cancel_token_.reset(); - impl.safe_cancellation_thread_id_ = 0; - } + close_for_destruction(impl); // Remove implementation from linked list of all implementations. asio::detail::mutex::scoped_lock lock(mutex_); @@ -353,6 +326,25 @@ public: { ec = asio::error::bad_descriptor; } + else if (FARPROC cancel_io_ex_ptr = ::GetProcAddress( + ::GetModuleHandle("KERNEL32"), "CancelIoEx")) + { + // The version of Windows supports cancellation from any thread. + typedef BOOL (WINAPI* cancel_io_ex_t)(HANDLE, LPOVERLAPPED); + cancel_io_ex_t cancel_io_ex = (cancel_io_ex_t)cancel_io_ex_ptr; + socket_type sock = impl.socket_; + HANDLE sock_as_handle = reinterpret_cast(sock); + if (!cancel_io_ex(sock_as_handle, 0)) + { + DWORD last_error = ::GetLastError(); + ec = asio::error_code(last_error, + asio::error::system_category); + } + else + { + ec = asio::error_code(); + } + } else if (impl.safe_cancellation_thread_id_ == 0) { // No operations have been started, so there's nothing to cancel. @@ -367,7 +359,8 @@ public: if (!::CancelIo(sock_as_handle)) { DWORD last_error = ::GetLastError(); - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); } else { @@ -475,7 +468,12 @@ public: if (option.level(impl.protocol_) == SOL_SOCKET && option.name(impl.protocol_) == SO_LINGER) { - impl.flags_ |= implementation_type::user_set_linger; + const ::linger* linger_option = + reinterpret_cast(option.data(impl.protocol_)); + if (linger_option->l_onoff != 0 && linger_option->l_linger != 0) + impl.flags_ |= implementation_type::close_might_block; + else + impl.flags_ &= ~implementation_type::close_might_block; } socket_ops::setsockopt(impl.socket_, @@ -668,7 +666,8 @@ public: last_error = WSAECONNRESET; else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } @@ -719,7 +718,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -821,7 +821,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -865,7 +866,8 @@ public: DWORD last_error = ::WSAGetLastError(); if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } @@ -914,7 +916,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -997,7 +1000,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1051,7 +1055,8 @@ public: last_error = WSAECONNRESET; else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } if (bytes_transferred == 0) @@ -1109,7 +1114,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -1216,7 +1222,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1262,7 +1269,8 @@ public: DWORD last_error = ::WSAGetLastError(); if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; - ec = asio::error_code(last_error, asio::native_ecat); + ec = asio::error_code(last_error, + asio::error::system_category); return 0; } if (bytes_transferred == 0) @@ -1328,7 +1336,8 @@ public: #endif // defined(ASIO_ENABLE_BUFFER_DEBUGGING) // Map non-portable errors to their portable counterparts. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -1422,7 +1431,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1659,7 +1669,8 @@ public: ptr.reset(); // Call the handler. - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); asio_handler_invoke_helpers::invoke( detail::bind_handler(handler, ec), &handler); } @@ -1759,7 +1770,8 @@ public: { asio::io_service::work work(this->io_service()); ptr.reset(); - asio::error_code ec(last_error, asio::native_ecat); + asio::error_code ec(last_error, + asio::error::system_category); iocp_service_.post(bind_handler(handler, ec)); } } @@ -1835,8 +1847,8 @@ public: // If connection failed then post the handler with the error code. if (connect_error) { - ec = asio::error_code( - connect_error, asio::native_ecat); + ec = asio::error_code(connect_error, + asio::error::system_category); io_service_.post(bind_handler(handler_, ec)); return true; } @@ -1950,26 +1962,66 @@ public: } private: - // Helper function to provide InterlockedCompareExchangePointer functionality - // on very old Platform SDKs. + // Helper function to close a socket when the associated object is being + // destroyed. + void close_for_destruction(implementation_type& impl) + { + if (is_open(impl)) + { + // Check if the reactor was created, in which case we need to close the + // socket on the reactor as well to cancel any operations that might be + // running there. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (reactor) + reactor->close_descriptor(impl.socket_); + + // The socket destructor must not block. If the user has changed the + // linger option to block in the foreground, we will change it back to the + // default so that the closure is performed in the background. + if (impl.flags_ & implementation_type::close_might_block) + { + ::linger opt; + opt.l_onoff = 0; + opt.l_linger = 0; + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, + SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); + } + + asio::error_code ignored_ec; + socket_ops::close(impl.socket_, ignored_ec); + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + } + } + + // Helper function to emulate InterlockedCompareExchangePointer functionality + // for: + // - very old Platform SDKs; and + // - platform SDKs where MSVC's /Wp64 option causes spurious warnings. void* interlocked_compare_exchange_pointer(void** dest, void* exch, void* cmp) { -#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) +#if defined(_M_IX86) return reinterpret_cast(InterlockedCompareExchange( - reinterpret_cast(dest), reinterpret_cast(exch), + reinterpret_cast(dest), reinterpret_cast(exch), reinterpret_cast(cmp))); #else return InterlockedCompareExchangePointer(dest, exch, cmp); #endif } - // Helper function to provide InterlockedExchangePointer functionality on very - // old Platform SDKs. + // Helper function to emulate InterlockedExchangePointer functionality for: + // - very old Platform SDKs; and + // - platform SDKs where MSVC's /Wp64 option causes spurious warnings. void* interlocked_exchange_pointer(void** dest, void* val) { -#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x400) && (_M_IX86) +#if defined(_M_IX86) return reinterpret_cast(InterlockedExchange( - reinterpret_cast(dest), reinterpret_cast(val))); + reinterpret_cast(dest), reinterpret_cast(val))); #else return InterlockedExchangePointer(dest, val); #endif diff --git a/libtorrent/include/asio/detail/win_mutex.hpp b/libtorrent/include/asio/detail/win_mutex.hpp index 82659831f..4d1bc20c2 100644 --- a/libtorrent/include/asio/detail/win_mutex.hpp +++ b/libtorrent/include/asio/detail/win_mutex.hpp @@ -23,6 +23,7 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -48,7 +49,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } @@ -67,7 +68,7 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::native_ecat), + asio::error_code(error, asio::error::system_category), "mutex"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_thread.hpp b/libtorrent/include/asio/detail/win_thread.hpp index a6c9b15d2..c6bd61af5 100644 --- a/libtorrent/include/asio/detail/win_thread.hpp +++ b/libtorrent/include/asio/detail/win_thread.hpp @@ -23,6 +23,7 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -54,7 +55,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_tss_ptr.hpp b/libtorrent/include/asio/detail/win_tss_ptr.hpp index d3e2f8161..d84810d41 100644 --- a/libtorrent/include/asio/detail/win_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/win_tss_ptr.hpp @@ -23,6 +23,7 @@ #if defined(BOOST_WINDOWS) +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -47,7 +48,8 @@ public: { DWORD last_error = ::GetLastError(); asio::system_error e( - asio::error_code(last_error, asio::native_ecat), + asio::error_code(last_error, + asio::error::system_category), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/winsock_init.hpp b/libtorrent/include/asio/detail/winsock_init.hpp index 67c69e8ce..874d2b77b 100644 --- a/libtorrent/include/asio/detail/winsock_init.hpp +++ b/libtorrent/include/asio/detail/winsock_init.hpp @@ -85,7 +85,8 @@ public: if (this != &instance_ && ref_->result() != 0) { asio::system_error e( - asio::error_code(ref_->result(), asio::native_ecat), + asio::error_code(ref_->result(), + asio::error::system_category), "winsock"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/wrapped_handler.hpp b/libtorrent/include/asio/detail/wrapped_handler.hpp index f757fd3dc..913a795dc 100644 --- a/libtorrent/include/asio/detail/wrapped_handler.hpp +++ b/libtorrent/include/asio/detail/wrapped_handler.hpp @@ -17,6 +17,10 @@ #include "asio/detail/push_options.hpp" +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + #include "asio/detail/bind_handler.hpp" #include "asio/detail/handler_alloc_helpers.hpp" #include "asio/detail/handler_invoke_helpers.hpp" @@ -30,7 +34,9 @@ class wrapped_handler public: typedef void result_type; - wrapped_handler(Dispatcher& dispatcher, Handler handler) + wrapped_handler( + typename boost::add_reference::type dispatcher, + Handler handler) : dispatcher_(dispatcher), handler_(handler) { @@ -117,7 +123,7 @@ public: } //private: - Dispatcher& dispatcher_; + Dispatcher dispatcher_; Handler handler_; }; @@ -171,9 +177,9 @@ inline void asio_handler_invoke(const Function& function, function, this_handler->handler_)); } -template +template inline void asio_handler_invoke(const Function& function, - rewrapped_handler* this_handler) + rewrapped_handler* this_handler) { asio_handler_invoke_helpers::invoke( function, &this_handler->context_); diff --git a/libtorrent/include/asio/error.hpp b/libtorrent/include/asio/error.hpp index 935cc6796..a8316be2c 100644 --- a/libtorrent/include/asio/error.hpp +++ b/libtorrent/include/asio/error.hpp @@ -37,327 +37,195 @@ /// INTERNAL ONLY. # define ASIO_WIN_OR_POSIX(e_win, e_posix) implementation_defined #elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) -# define ASIO_NATIVE_ERROR(e) \ - asio::error_code(e, \ - asio::native_ecat) -# define ASIO_SOCKET_ERROR(e) \ - asio::error_code(WSA ## e, \ - asio::native_ecat) -# define ASIO_NETDB_ERROR(e) \ - asio::error_code(WSA ## e, \ - asio::native_ecat) -# define ASIO_GETADDRINFO_ERROR(e) \ - asio::error_code(WSA ## e, \ - asio::native_ecat) -# define ASIO_MISC_ERROR(e) \ - asio::error_code(e, \ - asio::misc_ecat) +# define ASIO_NATIVE_ERROR(e) e +# define ASIO_SOCKET_ERROR(e) WSA ## e +# define ASIO_NETDB_ERROR(e) WSA ## e +# define ASIO_GETADDRINFO_ERROR(e) WSA ## e # define ASIO_WIN_OR_POSIX(e_win, e_posix) e_win #else -# define ASIO_NATIVE_ERROR(e) \ - asio::error_code(e, \ - asio::native_ecat) -# define ASIO_SOCKET_ERROR(e) \ - asio::error_code(e, \ - asio::native_ecat) -# define ASIO_NETDB_ERROR(e) \ - asio::error_code(e, \ - asio::netdb_ecat) -# define ASIO_GETADDRINFO_ERROR(e) \ - asio::error_code(e, \ - asio::addrinfo_ecat) -# define ASIO_MISC_ERROR(e) \ - asio::error_code(e, \ - asio::misc_ecat) +# define ASIO_NATIVE_ERROR(e) e +# define ASIO_SOCKET_ERROR(e) e +# define ASIO_NETDB_ERROR(e) e +# define ASIO_GETADDRINFO_ERROR(e) e # define ASIO_WIN_OR_POSIX(e_win, e_posix) e_posix #endif namespace asio { +namespace error { -namespace detail { - -/// Hack to keep asio library header-file-only. -template -class error_base +enum basic_errors { -public: - // boostify: error category declarations go here. - /// Permission denied. - static const asio::error_code access_denied; + access_denied = ASIO_SOCKET_ERROR(EACCES), /// Address family not supported by protocol. - static const asio::error_code address_family_not_supported; + address_family_not_supported = ASIO_SOCKET_ERROR(EAFNOSUPPORT), /// Address already in use. - static const asio::error_code address_in_use; + address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE), /// Transport endpoint is already connected. - static const asio::error_code already_connected; - - /// Already open. - static const asio::error_code already_open; + already_connected = ASIO_SOCKET_ERROR(EISCONN), /// Operation already in progress. - static const asio::error_code already_started; + already_started = ASIO_SOCKET_ERROR(EALREADY), /// A connection has been aborted. - static const asio::error_code connection_aborted; + connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED), /// Connection refused. - static const asio::error_code connection_refused; + connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED), /// Connection reset by peer. - static const asio::error_code connection_reset; + connection_reset = ASIO_SOCKET_ERROR(ECONNRESET), /// Bad file descriptor. - static const asio::error_code bad_descriptor; - - /// End of file or stream. - static const asio::error_code eof; + bad_descriptor = ASIO_SOCKET_ERROR(EBADF), /// Bad address. - static const asio::error_code fault; - - /// Host not found (authoritative). - static const asio::error_code host_not_found; - - /// Host not found (non-authoritative). - static const asio::error_code host_not_found_try_again; + fault = ASIO_SOCKET_ERROR(EFAULT), /// No route to host. - static const asio::error_code host_unreachable; + host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH), /// Operation now in progress. - static const asio::error_code in_progress; + in_progress = ASIO_SOCKET_ERROR(EINPROGRESS), /// Interrupted system call. - static const asio::error_code interrupted; + interrupted = ASIO_SOCKET_ERROR(EINTR), /// Invalid argument. - static const asio::error_code invalid_argument; + invalid_argument = ASIO_SOCKET_ERROR(EINVAL), /// Message too long. - static const asio::error_code message_size; + message_size = ASIO_SOCKET_ERROR(EMSGSIZE), /// Network is down. - static const asio::error_code network_down; + network_down = ASIO_SOCKET_ERROR(ENETDOWN), /// Network dropped connection on reset. - static const asio::error_code network_reset; + network_reset = ASIO_SOCKET_ERROR(ENETRESET), /// Network is unreachable. - static const asio::error_code network_unreachable; + network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH), /// Too many open files. - static const asio::error_code no_descriptors; + no_descriptors = ASIO_SOCKET_ERROR(EMFILE), /// No buffer space available. - static const asio::error_code no_buffer_space; - - /// The query is valid but does not have associated address data. - static const asio::error_code no_data; + no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS), /// Cannot allocate memory. - static const asio::error_code no_memory; + no_memory = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), + ASIO_NATIVE_ERROR(ENOMEM)), /// Operation not permitted. - static const asio::error_code no_permission; + no_permission = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), + ASIO_NATIVE_ERROR(EPERM)), /// Protocol not available. - static const asio::error_code no_protocol_option; - - /// A non-recoverable error occurred. - static const asio::error_code no_recovery; + no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT), /// Transport endpoint is not connected. - static const asio::error_code not_connected; - - /// Element not found. - static const asio::error_code not_found; + not_connected = ASIO_SOCKET_ERROR(ENOTCONN), /// Socket operation on non-socket. - static const asio::error_code not_socket; + not_socket = ASIO_SOCKET_ERROR(ENOTSOCK), /// Operation cancelled. - static const asio::error_code operation_aborted; + operation_aborted = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), + ASIO_NATIVE_ERROR(ECANCELED)), /// Operation not supported. - static const asio::error_code operation_not_supported; - - /// The service is not supported for the given socket type. - static const asio::error_code service_not_found; - - /// The socket type is not supported. - static const asio::error_code socket_type_not_supported; + operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP), /// Cannot send after transport endpoint shutdown. - static const asio::error_code shut_down; + shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN), /// Connection timed out. - static const asio::error_code timed_out; + timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT), /// Resource temporarily unavailable. - static const asio::error_code try_again; + try_again = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_RETRY), + ASIO_NATIVE_ERROR(EAGAIN)), /// The socket is marked non-blocking and the requested operation would block. - static const asio::error_code would_block; + would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK) +}; -private: - error_base(); +enum netdb_errors +{ + /// Host not found (authoritative). + host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND), + + /// Host not found (non-authoritative). + host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN), + + /// The query is valid but does not have associated address data. + no_data = ASIO_NETDB_ERROR(NO_DATA), + + /// A non-recoverable error occurred. + no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY) +}; + +enum addrinfo_errors +{ + /// The service is not supported for the given socket type. + service_not_found = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), + ASIO_GETADDRINFO_ERROR(EAI_SERVICE)), + + /// The socket type is not supported. + socket_type_not_supported = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), + ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)) +}; + +enum misc_errors +{ + /// Already open. + already_open = 1, + + /// End of file or stream. + eof, + + /// Element not found. + not_found }; // boostify: error category definitions go here. -template const asio::error_code -error_base::access_denied = ASIO_SOCKET_ERROR(EACCES); - -template const asio::error_code -error_base::address_family_not_supported = ASIO_SOCKET_ERROR( - EAFNOSUPPORT); - -template const asio::error_code -error_base::address_in_use = ASIO_SOCKET_ERROR(EADDRINUSE); - -template const asio::error_code -error_base::already_connected = ASIO_SOCKET_ERROR(EISCONN); - -template const asio::error_code -error_base::already_open = ASIO_MISC_ERROR(1); - -template const asio::error_code -error_base::already_started = ASIO_SOCKET_ERROR(EALREADY); - -template const asio::error_code -error_base::connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED); - -template const asio::error_code -error_base::connection_refused = ASIO_SOCKET_ERROR(ECONNREFUSED); - -template const asio::error_code -error_base::connection_reset = ASIO_SOCKET_ERROR(ECONNRESET); - -template const asio::error_code -error_base::bad_descriptor = ASIO_SOCKET_ERROR(EBADF); - -template const asio::error_code -error_base::eof = ASIO_MISC_ERROR(2); - -template const asio::error_code -error_base::fault = ASIO_SOCKET_ERROR(EFAULT); - -template const asio::error_code -error_base::host_not_found = ASIO_NETDB_ERROR(HOST_NOT_FOUND); - -template const asio::error_code -error_base::host_not_found_try_again = ASIO_NETDB_ERROR(TRY_AGAIN); - -template const asio::error_code -error_base::host_unreachable = ASIO_SOCKET_ERROR(EHOSTUNREACH); - -template const asio::error_code -error_base::in_progress = ASIO_SOCKET_ERROR(EINPROGRESS); - -template const asio::error_code -error_base::interrupted = ASIO_SOCKET_ERROR(EINTR); - -template const asio::error_code -error_base::invalid_argument = ASIO_SOCKET_ERROR(EINVAL); - -template const asio::error_code -error_base::message_size = ASIO_SOCKET_ERROR(EMSGSIZE); - -template const asio::error_code -error_base::network_down = ASIO_SOCKET_ERROR(ENETDOWN); - -template const asio::error_code -error_base::network_reset = ASIO_SOCKET_ERROR(ENETRESET); - -template const asio::error_code -error_base::network_unreachable = ASIO_SOCKET_ERROR(ENETUNREACH); - -template const asio::error_code -error_base::no_descriptors = ASIO_SOCKET_ERROR(EMFILE); - -template const asio::error_code -error_base::no_buffer_space = ASIO_SOCKET_ERROR(ENOBUFS); - -template const asio::error_code -error_base::no_data = ASIO_NETDB_ERROR(NO_DATA); - -template const asio::error_code -error_base::no_memory = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_OUTOFMEMORY), - ASIO_NATIVE_ERROR(ENOMEM)); - -template const asio::error_code -error_base::no_permission = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_ACCESS_DENIED), - ASIO_NATIVE_ERROR(EPERM)); - -template const asio::error_code -error_base::no_protocol_option = ASIO_SOCKET_ERROR(ENOPROTOOPT); - -template const asio::error_code -error_base::no_recovery = ASIO_NETDB_ERROR(NO_RECOVERY); - -template const asio::error_code -error_base::not_connected = ASIO_SOCKET_ERROR(ENOTCONN); - -template const asio::error_code -error_base::not_found = ASIO_MISC_ERROR(3); - -template const asio::error_code -error_base::not_socket = ASIO_SOCKET_ERROR(ENOTSOCK); - -template const asio::error_code -error_base::operation_aborted = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_OPERATION_ABORTED), - ASIO_NATIVE_ERROR(ECANCELED)); - -template const asio::error_code -error_base::operation_not_supported = ASIO_SOCKET_ERROR(EOPNOTSUPP); - -template const asio::error_code -error_base::service_not_found = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(WSATYPE_NOT_FOUND), - ASIO_GETADDRINFO_ERROR(EAI_SERVICE)); - -template const asio::error_code -error_base::socket_type_not_supported = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(WSAESOCKTNOSUPPORT), - ASIO_GETADDRINFO_ERROR(EAI_SOCKTYPE)); - -template const asio::error_code -error_base::shut_down = ASIO_SOCKET_ERROR(ESHUTDOWN); - -template const asio::error_code -error_base::timed_out = ASIO_SOCKET_ERROR(ETIMEDOUT); - -template const asio::error_code -error_base::try_again = ASIO_WIN_OR_POSIX( - ASIO_NATIVE_ERROR(ERROR_RETRY), - ASIO_NATIVE_ERROR(EAGAIN)); - -template const asio::error_code -error_base::would_block = ASIO_SOCKET_ERROR(EWOULDBLOCK); - -} // namespace detail - -/// Contains error constants. -class error : public asio::detail::error_base +inline asio::error_code make_error_code(basic_errors e) { -private: - error(); -}; + return asio::error_code(static_cast(e), system_category); +} +inline asio::error_code make_error_code(netdb_errors e) +{ + return asio::error_code(static_cast(e), netdb_category); +} + +inline asio::error_code make_error_code(addrinfo_errors e) +{ + return asio::error_code(static_cast(e), addrinfo_category); +} + +inline asio::error_code make_error_code(misc_errors e) +{ + return asio::error_code(static_cast(e), misc_category); +} + +} // namespace error } // namespace asio #undef ASIO_NATIVE_ERROR #undef ASIO_SOCKET_ERROR #undef ASIO_NETDB_ERROR #undef ASIO_GETADDRINFO_ERROR -#undef ASIO_MISC_ERROR #undef ASIO_WIN_OR_POSIX #include "asio/impl/error_code.ipp" diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp index 0614490e2..0941a8c00 100644 --- a/libtorrent/include/asio/error_code.hpp +++ b/libtorrent/include/asio/error_code.hpp @@ -32,24 +32,27 @@ namespace asio { -/// Available error code categories. -enum error_category +namespace error { - /// Native error codes. - native_ecat = ASIO_WIN_OR_POSIX(0, 0), + /// Available error code categories. + enum error_category + { + /// System error codes. + system_category = ASIO_WIN_OR_POSIX(0, 0), - /// Error codes from NetDB functions. - netdb_ecat = ASIO_WIN_OR_POSIX(native_ecat, 1), + /// Error codes from NetDB functions. + netdb_category = ASIO_WIN_OR_POSIX(system_category, 1), - /// Error codes from getaddrinfo. - addrinfo_ecat = ASIO_WIN_OR_POSIX(native_ecat, 2), + /// Error codes from getaddrinfo. + addrinfo_category = ASIO_WIN_OR_POSIX(system_category, 2), - /// Miscellaneous error codes. - misc_ecat = ASIO_WIN_OR_POSIX(3, 3), + /// Miscellaneous error codes. + misc_category = ASIO_WIN_OR_POSIX(3, 3), - /// SSL error codes. - ssl_ecat = ASIO_WIN_OR_POSIX(4, 4) -}; + /// SSL error codes. + ssl_category = ASIO_WIN_OR_POSIX(4, 4) + }; +} // namespace error /// Class to represent an error code value. class error_code @@ -61,17 +64,24 @@ public: /// Default constructor. error_code() : value_(0), - category_(native_ecat) + category_(error::system_category) { } /// Construct with specific error code and category. - error_code(value_type v, error_category c) + error_code(value_type v, error::error_category c) : value_(v), category_(c) { } + /// Construct from an error code enum. + template + error_code(ErrorEnum e) + { + *this = make_error_code(e); + } + /// Get the error value. value_type value() const { @@ -79,7 +89,7 @@ public: } /// Get the error category. - error_category category() const + error::error_category category() const { return category_; } @@ -125,7 +135,7 @@ private: value_type value_; // The category associated with the error code. - error_category category_; + error::error_category category_; }; } // namespace asio diff --git a/libtorrent/include/asio/impl/error_code.ipp b/libtorrent/include/asio/impl/error_code.ipp index da2f98833..f66b6fd94 100644 --- a/libtorrent/include/asio/impl/error_code.ipp +++ b/libtorrent/include/asio/impl/error_code.ipp @@ -35,10 +35,12 @@ inline std::string error_code::message() const return "Already open."; if (*this == error::not_found) return "Not found."; - if (category_ == ssl_ecat) + if (category_ == error::ssl_category) return "SSL error."; #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) value_type value = value_; + if (category() != error::system_category && *this != error::eof) + return "asio error"; if (*this == error::eof) value = ERROR_HANDLE_EOF; char* msg = 0; @@ -76,6 +78,8 @@ inline std::string error_code::message() const return "Service not found."; if (*this == error::socket_type_not_supported) return "Socket type not supported."; + if (category() != error::system_category) + return "asio error"; #if defined(__sun) || defined(__QNX__) return strerror(value_); #elif defined(__MACH__) && defined(__APPLE__) \ diff --git a/libtorrent/include/asio/impl/io_service.ipp b/libtorrent/include/asio/impl/io_service.ipp index e973619d1..f51d3697d 100644 --- a/libtorrent/include/asio/impl/io_service.ipp +++ b/libtorrent/include/asio/impl/io_service.ipp @@ -128,11 +128,11 @@ template #if defined(GENERATING_DOCUMENTATION) unspecified #else -inline detail::wrapped_handler +inline detail::wrapped_handler #endif io_service::wrap(Handler handler) { - return detail::wrapped_handler(*this, handler); + return detail::wrapped_handler(*this, handler); } inline io_service::work::work(asio::io_service& io_service) diff --git a/libtorrent/include/asio/impl/read_until.ipp b/libtorrent/include/asio/impl/read_until.ipp index 64c15ec7d..8b69a11c6 100644 --- a/libtorrent/include/asio/impl/read_until.ipp +++ b/libtorrent/include/asio/impl/read_until.ipp @@ -311,7 +311,8 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - handler_(error::not_found, bytes); + asio::error_code ec(error::not_found); + handler_(ec, bytes); return; } @@ -388,7 +389,8 @@ void async_read_until(AsyncReadStream& s, // No match. Check if buffer is full. if (b.size() == b.max_size()) { - s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + asio::error_code ec(error::not_found); + s.io_service().post(detail::bind_handler(handler, ec, 0)); return; } @@ -469,7 +471,8 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - handler_(error::not_found, bytes); + asio::error_code ec(error::not_found); + handler_(ec, bytes); return; } @@ -559,7 +562,8 @@ void async_read_until(AsyncReadStream& s, // Check if buffer is full. if (b.size() == b.max_size()) { - s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + asio::error_code ec(error::not_found); + s.io_service().post(detail::bind_handler(handler, ec, 0)); return; } @@ -641,7 +645,8 @@ namespace detail if (streambuf_.size() == streambuf_.max_size()) { std::size_t bytes = 0; - handler_(error::not_found, bytes); + asio::error_code ec(error::not_found); + handler_(ec, bytes); return; } @@ -731,7 +736,8 @@ void async_read_until(AsyncReadStream& s, // Check if buffer is full. if (b.size() == b.max_size()) { - s.io_service().post(detail::bind_handler(handler, error::not_found, 0)); + asio::error_code ec(error::not_found); + s.io_service().post(detail::bind_handler(handler, ec, 0)); return; } diff --git a/libtorrent/include/asio/io_service.hpp b/libtorrent/include/asio/io_service.hpp index b694545db..2101e56c4 100644 --- a/libtorrent/include/asio/io_service.hpp +++ b/libtorrent/include/asio/io_service.hpp @@ -320,7 +320,7 @@ public: #if defined(GENERATING_DOCUMENTATION) unspecified #else - detail::wrapped_handler + detail::wrapped_handler #endif wrap(Handler handler); diff --git a/libtorrent/include/asio/ip/basic_endpoint.hpp b/libtorrent/include/asio/ip/basic_endpoint.hpp index 3ca91dc03..3d1316e22 100644 --- a/libtorrent/include/asio/ip/basic_endpoint.hpp +++ b/libtorrent/include/asio/ip/basic_endpoint.hpp @@ -172,7 +172,7 @@ public: /// The protocol associated with the endpoint. protocol_type protocol() const { - if (is_v4()) + if (is_v4(data_)) return InternetProtocol::v4(); return InternetProtocol::v6(); } @@ -192,7 +192,7 @@ public: /// Get the underlying size of the endpoint in the native type. size_type size() const { - if (is_v4()) + if (is_v4(data_)) return sizeof(asio::detail::sockaddr_in4_type); else return sizeof(asio::detail::sockaddr_in6_type); @@ -218,7 +218,7 @@ public: /// the host's byte order. unsigned short port() const { - if (is_v4()) + if (is_v4(data_)) { return asio::detail::socket_ops::network_to_host_short( reinterpret_cast( @@ -236,7 +236,7 @@ public: /// the host's byte order. void port(unsigned short port_num) { - if (is_v4()) + if (is_v4(data_)) { reinterpret_cast(data_).sin_port = asio::detail::socket_ops::host_to_network_short(port_num); @@ -252,7 +252,7 @@ public: asio::ip::address address() const { using namespace std; // For memcpy. - if (is_v4()) + if (is_v4(data_)) { const asio::detail::sockaddr_in4_type& data = reinterpret_cast( @@ -306,15 +306,27 @@ public: private: // Helper function to determine whether the endpoint is IPv4. - bool is_v4() const - { #if defined(_AIX) - return data_.__ss_family == AF_INET; -#else - return data_.ss_family == AF_INET; -#endif + template struct is_v4_helper {}; + + template + static bool is_v4(const T& ss, is_v4_helper* = 0) + { + return ss.ss_family == AF_INET; } + template + static bool is_v4(const T& ss, is_v4_helper* = 0) + { + return ss.__ss_family == AF_INET; + } +#else + static bool is_v4(const asio::detail::sockaddr_storage_type& ss) + { + return ss.ss_family == AF_INET; + } +#endif + // The underlying IP socket address. asio::detail::sockaddr_storage_type data_; }; diff --git a/libtorrent/include/asio/ip/detail/CVS/Entries b/libtorrent/include/asio/ip/detail/CVS/Entries deleted file mode 100644 index adc3dfb58..000000000 --- a/libtorrent/include/asio/ip/detail/CVS/Entries +++ /dev/null @@ -1,2 +0,0 @@ -/socket_option.hpp/1.9/Sun May 6 10:51:53 2007// -D diff --git a/libtorrent/include/asio/ip/detail/CVS/Repository b/libtorrent/include/asio/ip/detail/CVS/Repository deleted file mode 100644 index b37c59644..000000000 --- a/libtorrent/include/asio/ip/detail/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -asio/include/asio/ip/detail diff --git a/libtorrent/include/asio/ip/detail/CVS/Root b/libtorrent/include/asio/ip/detail/CVS/Root deleted file mode 100644 index a7505d52a..000000000 --- a/libtorrent/include/asio/ip/detail/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -:pserver:anonymous@asio.cvs.sourceforge.net:/cvsroot/asio diff --git a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp index b7a564464..5fd3ebba4 100755 --- a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp @@ -174,12 +174,12 @@ public: if (error_code == SSL_ERROR_SYSCALL) { return handler_(asio::error_code( - sys_error_code, asio::native_ecat), rc); + sys_error_code, asio::error::system_category), rc); } else { return handler_(asio::error_code( - error_code, asio::ssl_ecat), rc); + error_code, asio::error::ssl_category), rc); } } diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp index b6b6711dc..954e39ef5 100755 --- a/libtorrent/include/libtorrent/alert.hpp +++ b/libtorrent/include/libtorrent/alert.hpp @@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #ifdef _MSC_VER @@ -56,6 +55,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/time.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" #ifndef TORRENT_MAX_ALERT_TYPES #define TORRENT_MAX_ALERT_TYPES 15 diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 48491bca4..36c13c5ab 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_connection.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -223,7 +224,7 @@ namespace libtorrent { block_downloading_alert( const torrent_handle& h - , std::string& speedmsg + , char const* speedmsg , int block_num , int piece_num , const std::string& msg) @@ -261,6 +262,17 @@ namespace libtorrent { return std::auto_ptr(new torrent_paused_alert(*this)); } }; + struct TORRENT_EXPORT torrent_checked_alert: torrent_alert + { + torrent_checked_alert(torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::info, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new torrent_checked_alert(*this)); } + }; + + struct TORRENT_EXPORT url_seed_alert: torrent_alert { url_seed_alert( diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp index e69de29bb..6577acc46 100644 --- a/libtorrent/include/libtorrent/assert.hpp +++ b/libtorrent/include/libtorrent/assert.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#ifndef NDEBUG +#if (defined __linux__ || defined __MACH__) && defined __GNUC__ +#ifdef assert +#undef assert +#endif + +#include "libtorrent/config.hpp" + +TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file, char const* function); + +#define assert(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) + +#endif + +#else +#ifndef assert +#define assert(x) (void) +#endif +#endif + diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 207016898..9300a1ce3 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -83,6 +83,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type.hpp" #include "libtorrent/connection_queue.hpp" #include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -184,7 +185,7 @@ namespace libtorrent ~session_impl(); #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*)> ext); + void add_extension(boost::function(torrent*, void*)> ext); #endif void operator()(); @@ -240,12 +241,13 @@ namespace libtorrent bool is_listening() const; torrent_handle add_torrent( - torrent_info const& ti + boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused + , void* userdata); torrent_handle add_torrent( char const* tracker_url @@ -254,8 +256,9 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused + , void* userdata); void remove_torrent(torrent_handle const& h); @@ -273,8 +276,21 @@ namespace libtorrent void set_max_connections(int limit); void set_max_uploads(int limit); - int num_uploads() const; - int num_connections() const; + int max_connections() const { return m_max_connections; } + int max_uploads() const { return m_max_uploads; } + int max_half_open_connections() const { return m_half_open.limit(); } + + int num_uploads() const { return m_num_unchoked; } + int num_connections() const + { return m_connections.size(); } + + void unchoke_peer(peer_connection& c) + { + torrent* t = c.associated_torrent().lock().get(); + assert(t); + if (t->unchoke_peer(c)) + ++m_num_unchoked; + } session_status status() const; void set_peer_id(peer_id const& id); @@ -417,6 +433,28 @@ namespace libtorrent int m_max_uploads; int m_max_connections; + // the number of unchoked peers + int m_num_unchoked; + + // this is initialized to the unchoke_interval + // session_setting and decreased every second. + // when it reaches zero, it is reset to the + // unchoke_interval and the unchoke set is + // recomputed. + int m_unchoke_time_scaler; + + // works like unchoke_time_scaler but it + // is only decresed when the unchoke set + // is recomputed, and when it reaches zero, + // the optimistic unchoke is moved to another peer. + int m_optimistic_unchoke_time_scaler; + + // works like unchoke_time_scaler. Each time + // it reaches 0, and all the connections are + // used, the worst connection will be disconnected + // from the torrent with the most peers + int m_disconnect_time_scaler; + // statistics gathered from all torrents. stat m_stat; @@ -459,7 +497,7 @@ namespace libtorrent // This implements a round robin. int m_next_connect_torrent; #ifndef NDEBUG - void check_invariant(const char *place = 0); + void check_invariant() const; #endif #ifdef TORRENT_STATS @@ -483,7 +521,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list(torrent*)> > extension_list_t; + torrent_plugin>(torrent*, void*)> > extension_list_t; extension_list_t m_extensions; #endif diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 75e1f1d4e..38aa67f43 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -33,9 +33,6 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED #define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED -#include "libtorrent/socket.hpp" -#include "libtorrent/invariant_check.hpp" - #include #include #include @@ -44,11 +41,17 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include "libtorrent/socket.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" + using boost::weak_ptr; using boost::shared_ptr; using boost::intrusive_ptr; using boost::bind; +//#define TORRENT_VERBOSE_BANDWIDTH_LIMIT + namespace libtorrent { // the maximum block of bandwidth quota to @@ -177,6 +180,7 @@ struct bandwidth_manager , m_limit(bandwidth_limit::inf) , m_current_quota(0) , m_channel(channel) + , m_in_hand_out_bandwidth(false) {} void throttle(int limit) throw() @@ -237,8 +241,10 @@ struct bandwidth_manager i = j; } } - - if (m_queue.size() == 1) hand_out_bandwidth(); +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::cerr << " req_bandwidht. m_queue.size() = " << m_queue.size() << std::endl; +#endif + if (!m_queue.empty()) hand_out_bandwidth(); } #ifndef NDEBUG @@ -323,6 +329,10 @@ private: void hand_out_bandwidth() throw() { + // if we're already handing out bandwidth, just return back + // to the loop further down on the callstack + if (m_in_hand_out_bandwidth) return; + m_in_hand_out_bandwidth = true; #ifndef NDEBUG try { #endif @@ -337,10 +347,18 @@ private: // available bandwidth to hand out int amount = limit - m_current_quota; +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::cerr << " hand_out_bandwidht. m_queue.size() = " << m_queue.size() + << " amount = " << amount + << " limit = " << limit + << " m_current_quota = " << m_current_quota << std::endl; +#endif + while (!m_queue.empty() && amount > 0) { assert(amount == limit - m_current_quota); bw_queue_entry qe = m_queue.front(); + assert(qe.max_block_size > 0); m_queue.pop_front(); shared_ptr t = qe.peer->associated_torrent().lock(); @@ -348,6 +366,7 @@ private: if (qe.peer->is_disconnecting()) { t->expire_bandwidth(m_channel, qe.max_block_size); + assert(amount == limit - m_current_quota); continue; } @@ -361,6 +380,7 @@ private: if (max_assignable == 0) { t->expire_bandwidth(m_channel, qe.max_block_size); + assert(amount == limit - m_current_quota); continue; } @@ -374,17 +394,16 @@ private: // block size must be smaller for lower rates. This is because // the history window is one second, and the block will be forgotten // after one second. - int block_size = (std::min)(qe.max_block_size - , (std::min)(qe.peer->bandwidth_throttle(m_channel) - , m_limit / 10)); + int block_size = (std::min)(qe.peer->bandwidth_throttle(m_channel) + , limit / 10); if (block_size < min_bandwidth_block_size) { - block_size = min_bandwidth_block_size; + block_size = (std::min)(int(min_bandwidth_block_size), limit); } else if (block_size > max_bandwidth_block_size) { - if (m_limit == bandwidth_limit::inf) + if (limit == bandwidth_limit::inf) { block_size = max_bandwidth_block_size; } @@ -395,11 +414,15 @@ private: // as possible // TODO: move this calculcation to where the limit // is changed - block_size = m_limit - / (m_limit / max_bandwidth_block_size); + block_size = limit + / (limit / max_bandwidth_block_size); } } + if (block_size > qe.max_block_size) block_size = qe.max_block_size; +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; +#endif if (amount < block_size / 2) { m_queue.push_front(qe); @@ -412,18 +435,21 @@ private: int hand_out_amount = (std::min)((std::min)(block_size, max_assignable) , amount); assert(hand_out_amount > 0); + assert(amount == limit - m_current_quota); amount -= hand_out_amount; assert(hand_out_amount <= qe.max_block_size); t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); qe.peer->assign_bandwidth(m_channel, hand_out_amount); add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); + assert(amount == limit - m_current_quota); } #ifndef NDEBUG } catch (std::exception& e) { assert(false); }; #endif + m_in_hand_out_bandwidth = false; } @@ -456,6 +482,11 @@ private: // this is the channel within the consumers // that bandwidth is assigned to (upload or download) int m_channel; + + // this is true while we're in the hand_out_bandwidth loop + // to prevent recursive invocations to interfere + bool m_in_hand_out_bandwidth; + }; } diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index a142b5864..9e670c10b 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -79,6 +79,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/entry.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + #if defined(_MSC_VER) namespace std { diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index e69de29bb..23be67b0d 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BROADCAST_SOCKET_HPP_INCLUDED +#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include +#include +#include + +namespace libtorrent +{ + + bool is_local(address const& a); + bool is_loopback(address const& addr); + bool is_multicast(address const& addr); + + address_v4 guess_local_address(asio::io_service&); + + typedef boost::function receive_handler_t; + + class broadcast_socket + { + public: + broadcast_socket(asio::io_service& ios, udp::endpoint const& multicast_endpoint + , receive_handler_t const& handler, bool loopback = true); + ~broadcast_socket() { close(); } + + void send(char const* buffer, int size, asio::error_code& ec); + void close(); + + private: + + struct socket_entry + { + socket_entry(boost::shared_ptr const& s): socket(s) {} + boost::shared_ptr socket; + char buffer[1024]; + udp::endpoint remote; + }; + + void on_receive(socket_entry* s, asio::error_code const& ec + , std::size_t bytes_transferred); + + std::list m_sockets; + udp::endpoint m_multicast_endpoint; + receive_handler_t m_on_receive; + + }; +} + +#endif + diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index beec94979..0fcba89a8 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -65,7 +65,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -122,8 +121,16 @@ namespace libtorrent msg_request, msg_piece, msg_cancel, + // DHT extension msg_dht_port, - // extension protocol message + // FAST extension + msg_suggest_piece = 0xd, + msg_have_all, + msg_have_none, + msg_reject_request, + msg_allowed_fast, + + // extension protocol message msg_extended = 20, num_supported_messages @@ -174,8 +181,17 @@ namespace libtorrent void on_request(int received); void on_piece(int received); void on_cancel(int received); + + // DHT extension void on_dht_port(int received); + // FAST extension + void on_suggest_piece(int received); + void on_have_all(int received); + void on_have_none(int received); + void on_reject_request(int received); + void on_allowed_fast(int received); + void on_extended(int received); void on_extended_handshake(); @@ -201,7 +217,16 @@ namespace libtorrent void write_metadata(std::pair req); void write_metadata_request(std::pair req); void write_keepalive(); + + // DHT extension void write_dht_port(int listen_port); + + // FAST extension + void write_have_all(); + void write_have_none(); + void write_reject_request(peer_request const&); + void write_allow_fast(int piece); + void on_connected(); void on_metadata(); @@ -325,6 +350,7 @@ namespace libtorrent bool m_supports_extensions; #endif bool m_supports_dht_port; + bool m_supports_fast; #ifndef TORRENT_DISABLE_ENCRYPTION // this is set to true after the encryption method has been diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index 0cb44225a..0f37edcbd 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -34,8 +34,9 @@ POSSIBILITY OF SUCH DAMAGE. //#define TORRENT_BUFFER_DEBUG -#include "libtorrent/invariant_check.hpp" #include +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp index 17be248bf..b3b7cde86 100644 --- a/libtorrent/include/libtorrent/connection_queue.hpp +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "libtorrent/socket.hpp" #include "libtorrent/time.hpp" @@ -88,6 +89,10 @@ private: int m_half_open_limit; deadline_timer m_timer; + + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + #ifndef NDEBUG bool m_in_timeout_function; #endif diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp index 436b695f6..1bb645a8e 100755 --- a/libtorrent/include/libtorrent/debug.hpp +++ b/libtorrent/include/libtorrent/debug.hpp @@ -80,3 +80,4 @@ namespace libtorrent } #endif // TORRENT_DEBUG_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index 16ee0bca4..61ca9bc53 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -30,6 +30,10 @@ POSSIBILITY OF SUCH DAMAGE. */ +#ifdef TORRENT_DISK_STATS +#include +#endif + #include "libtorrent/storage.hpp" #include #include @@ -50,6 +54,7 @@ namespace libtorrent , buffer_size(0) , piece(0) , offset(0) + , priority(0) {} enum action_t @@ -72,6 +77,12 @@ namespace libtorrent // to the error message std::string str; + // priority decides whether or not this + // job will skip entries in the queue or + // not. It always skips in front of entries + // with lower priority + int priority; + // this is called when operation completes boost::function callback; }; @@ -115,6 +126,10 @@ namespace libtorrent int m_block_size; #endif +#ifdef TORRENT_DISK_STATS + std::ofstream m_log; +#endif + // thread for performing blocking disk io operations boost::thread m_disk_io_thread; }; diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp index 59e29803d..7fd6c8c53 100755 --- a/libtorrent/include/libtorrent/entry.hpp +++ b/libtorrent/include/libtorrent/entry.hpp @@ -64,10 +64,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include "libtorrent/size_type.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp index e69de29bb..0c6063a2b 100644 --- a/libtorrent/include/libtorrent/enum_net.hpp +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" + +namespace libtorrent +{ + std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); +} + +#endif + diff --git a/libtorrent/include/libtorrent/extensions.hpp b/libtorrent/include/libtorrent/extensions.hpp index 5f8172649..fd48588e1 100644 --- a/libtorrent/include/libtorrent/extensions.hpp +++ b/libtorrent/include/libtorrent/extensions.hpp @@ -131,6 +131,15 @@ namespace libtorrent virtual bool on_bitfield(std::vector const& bitfield) { return false; } + virtual bool on_have_all() + { return false; } + + virtual bool on_have_none() + { return false; } + + virtual bool on_allowed_fast(int index) + { return false; } + virtual bool on_request(peer_request const& req) { return false; } @@ -140,6 +149,12 @@ namespace libtorrent virtual bool on_cancel(peer_request const& req) { return false; } + virtual bool on_reject(peer_request const& req) + { return false; } + + virtual bool on_suggest(int index) + { return false; } + // called when an extended message is received. If returning true, // the message is not processed by any other plugin and if false // is returned the next plugin in the chain will receive it to diff --git a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp index 210642161..c42136d70 100644 --- a/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp +++ b/libtorrent/include/libtorrent/extensions/metadata_transfer.hpp @@ -48,7 +48,7 @@ namespace libtorrent { struct torrent_plugin; class torrent; - TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*); + TORRENT_EXPORT boost::shared_ptr create_metadata_plugin(torrent*, void*); } #endif // TORRENT_METADATA_TRANSFER_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/extensions/ut_pex.hpp b/libtorrent/include/libtorrent/extensions/ut_pex.hpp index efd9ab4f6..ebf6aa834 100644 --- a/libtorrent/include/libtorrent/extensions/ut_pex.hpp +++ b/libtorrent/include/libtorrent/extensions/ut_pex.hpp @@ -48,7 +48,7 @@ namespace libtorrent { struct torrent_plugin; class torrent; - TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*); + TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent*, void*); } #endif // TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/fingerprint.hpp b/libtorrent/include/libtorrent/fingerprint.hpp index d7e5a5fc6..712be6979 100755 --- a/libtorrent/include/libtorrent/fingerprint.hpp +++ b/libtorrent/include/libtorrent/fingerprint.hpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -91,3 +92,4 @@ namespace libtorrent } #endif // TORRENT_FINGERPRINT_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/hasher.hpp b/libtorrent/include/libtorrent/hasher.hpp index 932f2b100..71b7f9ede 100755 --- a/libtorrent/include/libtorrent/hasher.hpp +++ b/libtorrent/include/libtorrent/hasher.hpp @@ -33,11 +33,11 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_HASHER_HPP_INCLUDED #define TORRENT_HASHER_HPP_INCLUDED -#include #include #include "libtorrent/peer_id.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" #include "zlib.h" #ifdef TORRENT_USE_OPENSSL diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index 409213857..ccc145413 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -44,13 +44,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/http_tracker_connection.hpp" #include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { +struct http_connection; + typedef boost::function http_handler; +typedef boost::function http_connect_handler; + // TODO: add bind interface // when bottled, the last two arguments to the handler @@ -58,11 +63,13 @@ typedef boost::function, boost::noncopyable { http_connection(asio::io_service& ios, connection_queue& cc - , http_handler handler, bool bottled = true) + , http_handler const& handler, bool bottled = true + , http_connect_handler const& ch = http_connect_handler()) : m_sock(ios) , m_read_pos(0) , m_resolver(ios) , m_handler(handler) + , m_connect_handler(ch) , m_timer(ios) , m_last_receive(time_now()) , m_bottled(bottled) @@ -92,6 +99,8 @@ struct http_connection : boost::enable_shared_from_this, boost: , time_duration timeout, bool handle_redirect = true); void close(); + tcp::socket const& socket() const { return m_sock; } + private: void on_resolve(asio::error_code const& e @@ -112,6 +121,7 @@ private: tcp::resolver m_resolver; http_parser m_parser; http_handler m_handler; + http_connect_handler m_connect_handler; deadline_timer m_timer; time_duration m_timeout; ptime m_last_receive; diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index 35d529504..76c3aac98 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -73,6 +73,8 @@ namespace libtorrent T header(char const* key) const; std::string const& protocol() const { return m_protocol; } int status_code() const { return m_status_code; } + std::string const& method() const { return m_method; } + std::string const& path() const { return m_path; } std::string message() const { return m_server_message; } buffer::const_interval get_body() const; bool header_finished() const { return m_state == read_body; } @@ -85,6 +87,8 @@ namespace libtorrent private: int m_recv_pos; int m_status_code; + std::string m_method; + std::string m_path; std::string m_protocol; std::string m_server_message; diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index a432bc350..d2c35ffe3 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -34,14 +34,17 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_INTRUSIVE_PTR_BASE #include -#include #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { template struct intrusive_ptr_base { + intrusive_ptr_base(intrusive_ptr_base const&) + : m_refs(0) {} + friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) { assert(s->m_refs >= 0); diff --git a/libtorrent/include/libtorrent/invariant_check.hpp b/libtorrent/include/libtorrent/invariant_check.hpp index c6eacf338..3eaacf34c 100755 --- a/libtorrent/include/libtorrent/invariant_check.hpp +++ b/libtorrent/include/libtorrent/invariant_check.hpp @@ -5,7 +5,7 @@ #ifndef TORRENT_INVARIANT_ACCESS_HPP_INCLUDED #define TORRENT_INVARIANT_ACCESS_HPP_INCLUDED -#include +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/ip_filter.hpp b/libtorrent/include/libtorrent/ip_filter.hpp index 8b1793c3a..7b8cc0e17 100644 --- a/libtorrent/include/libtorrent/ip_filter.hpp +++ b/libtorrent/include/libtorrent/ip_filter.hpp @@ -33,6 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_IP_FILTER_HPP #define TORRENT_IP_FILTER_HPP +#include +#include + #ifdef _MSC_VER #pragma warning(push, 1) #endif @@ -48,8 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/socket.hpp" -#include -#include +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/kademlia/node.hpp b/libtorrent/include/libtorrent/kademlia/node.hpp index 850333043..ee75e7f0a 100644 --- a/libtorrent/include/libtorrent/kademlia/node.hpp +++ b/libtorrent/include/libtorrent/kademlia/node.hpp @@ -34,7 +34,6 @@ POSSIBILITY OF SUCH DAMAGE. #define NODE_HPP #include -#include #include #include @@ -45,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include diff --git a/libtorrent/include/libtorrent/kademlia/node_id.hpp b/libtorrent/include/libtorrent/kademlia/node_id.hpp index eb4d6c539..5e732acac 100644 --- a/libtorrent/include/libtorrent/kademlia/node_id.hpp +++ b/libtorrent/include/libtorrent/kademlia/node_id.hpp @@ -33,10 +33,10 @@ POSSIBILITY OF SUCH DAMAGE. #define NODE_ID_HPP #include -#include #include #include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { namespace dht { diff --git a/libtorrent/include/libtorrent/kademlia/routing_table.hpp b/libtorrent/include/libtorrent/kademlia/routing_table.hpp index 45a7dd762..9e10a3483 100644 --- a/libtorrent/include/libtorrent/kademlia/routing_table.hpp +++ b/libtorrent/include/libtorrent/kademlia/routing_table.hpp @@ -50,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace libtorrent { namespace dht { diff --git a/libtorrent/include/libtorrent/lsd.hpp b/libtorrent/include/libtorrent/lsd.hpp index 9ffbcdfc3..e8eaf0df1 100644 --- a/libtorrent/include/libtorrent/lsd.hpp +++ b/libtorrent/include/libtorrent/lsd.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_id.hpp" +#include "libtorrent/broadcast_socket.hpp" #include #include @@ -58,35 +59,26 @@ public: , peer_callback_t const& cb); ~lsd(); - void rebind(address const& listen_interface); +// void rebind(address const& listen_interface); void announce(sha1_hash const& ih, int listen_port); void close(); private: - static address_v4 lsd_multicast_address; - static udp::endpoint lsd_multicast_endpoint; - void resend_announce(asio::error_code const& e, std::string msg); - void on_announce(asio::error_code const& e + void on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); - void setup_receive(); +// void setup_receive(); peer_callback_t m_callback; // current retry count int m_retry_count; - // used to receive responses in - char m_receive_buffer[1024]; - - // the endpoint we received the message from - udp::endpoint m_remote; - // the udp socket used to send and receive // multicast messages on - datagram_socket m_socket; + broadcast_socket m_socket; // used to resend udp packets in case // they time out diff --git a/libtorrent/include/libtorrent/pe_crypto.hpp b/libtorrent/include/libtorrent/pe_crypto.hpp index 91616c42d..e2276dee6 100644 --- a/libtorrent/include/libtorrent/pe_crypto.hpp +++ b/libtorrent/include/libtorrent/pe_crypto.hpp @@ -35,13 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED #define TORRENT_PE_CRYPTO_HPP_INCLUDED -#include - #include #include #include -#include "peer_id.hpp" // For sha1_hash +#include "libtorrent/peer_id.hpp" // For sha1_hash +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 31bcde94a..ea16a8d0a 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -64,7 +64,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -73,6 +72,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" #include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -131,6 +131,8 @@ namespace libtorrent enum peer_speed_t { slow, medium, fast }; peer_speed_t peer_speed(); + void send_allowed_set(); + #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::shared_ptr); #endif @@ -151,11 +153,17 @@ namespace libtorrent int upload_limit() const { return m_upload_limit; } int download_limit() const { return m_download_limit; } - bool prefer_whole_pieces() const - { return m_prefer_whole_pieces; } + int prefer_whole_pieces() const + { + if (on_parole()) return 1; + return m_prefer_whole_pieces; + } - void prefer_whole_pieces(bool b) - { m_prefer_whole_pieces = b; } + bool on_parole() const + { return peer_info_struct() && peer_info_struct()->on_parole; } + + void prefer_whole_pieces(int num) + { m_prefer_whole_pieces = num; } bool request_large_blocks() const { return m_request_large_blocks; } @@ -186,9 +194,9 @@ namespace libtorrent void set_pid(const peer_id& pid) { m_peer_id = pid; } bool has_piece(int i) const; - const std::deque& download_queue() const; - const std::deque& request_queue() const; - const std::deque& upload_queue() const; + std::deque const& download_queue() const; + std::deque const& request_queue() const; + std::deque const& upload_queue() const; bool is_interesting() const { return m_interesting; } bool is_choked() const { return m_choked; } @@ -211,12 +219,14 @@ namespace libtorrent void add_stat(size_type downloaded, size_type uploaded); // is called once every second by the main loop - void second_tick(float tick_interval); + void second_tick(float tick_interval) throw(); boost::shared_ptr get_socket() const { return m_socket; } tcp::endpoint const& remote() const { return m_remote; } std::vector const& get_bitfield() const; + std::vector const& allowed_fast(); + std::vector const& suggested_pieces() const { return m_suggested_pieces; } void timed_out(); // this will cause this peer_connection to be disconnected. @@ -294,7 +304,14 @@ namespace libtorrent void incoming_piece(peer_request const& p, char const* data); void incoming_piece_fragment(); void incoming_cancel(peer_request const& r); + void incoming_dht_port(int listen_port); + + void incoming_reject_request(peer_request const& r); + void incoming_have_all(); + void incoming_have_none(); + void incoming_allowed_fast(int index); + void incoming_suggest(int index); // the following functions appends messages // to the send buffer @@ -373,6 +390,9 @@ namespace libtorrent virtual void write_keepalive() = 0; virtual void write_piece(peer_request const& r, char const* buffer) = 0; + virtual void write_reject_request(peer_request const& r) = 0; + virtual void write_allow_fast(int piece) = 0; + virtual void on_connected() = 0; virtual void on_tick() {} @@ -482,6 +502,11 @@ namespace libtorrent // the time we sent a request to // this peer the last time ptime m_last_request; + // the time we received the last + // piece request from the peer + ptime m_last_incoming_request; + // the time when we unchoked this peer + ptime m_last_unchoke; int m_packet_size; int m_recv_pos; @@ -529,7 +554,7 @@ namespace libtorrent // set to the torrent it belongs to. boost::weak_ptr m_torrent; // is true if it was we that connected to the peer - // and false if we got an incomming connection + // and false if we got an incoming connection // could be considered: true = local, false = remote bool m_active; @@ -563,6 +588,10 @@ namespace libtorrent // the pieces the other end have std::vector m_have_piece; + // this is set to true when a have_all + // message is received. This information + // is used to fill the bitmask in init() + bool m_have_all; // the number of pieces this peer // has. Must be the same as @@ -575,7 +604,7 @@ namespace libtorrent std::deque m_requests; // the blocks we have reserved in the piece - // picker and will send to this peer. + // picker and will request from this peer. std::deque m_request_queue; // the queue of blocks we have requested @@ -643,12 +672,13 @@ namespace libtorrent bool m_writing; bool m_reading; - // if set to true, this peer will always prefer - // to request entire pieces, rather than blocks. - // if it is false, the download rate limit setting + // if set to non-zero, this peer will always prefer + // to request entire n pieces, rather than blocks. + // where n is the value of this variable. + // if it is 0, the download rate limit setting // will be used to determine if whole pieces // are preferred. - bool m_prefer_whole_pieces; + int m_prefer_whole_pieces; // if this is true, the blocks picked by the piece // picker will be merged before passed to the @@ -695,6 +725,18 @@ namespace libtorrent // was last updated ptime m_remote_dl_update; + // the pieces we will send to the peer + // if requested (regardless of choke state) + std::set m_accept_fast; + + // the pieces the peer will send us if + // requested (regardless of choke state) + std::vector m_allowed_fast; + + // pieces that has been suggested to be + // downloaded from this peer + std::vector m_suggested_pieces; + // the number of bytes send to the disk-io // thread that hasn't yet been completely written. int m_outstanding_writing_bytes; diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp index b66c1d4bc..57303e2fd 100755 --- a/libtorrent/include/libtorrent/peer_id.hpp +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include #include #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index 15ad34a7a..b07acffd4 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -56,10 +56,11 @@ namespace libtorrent connecting = 0x80, queued = 0x100, on_parole = 0x200, - seed = 0x400 + seed = 0x400, + optimistic_unchoke = 0x800 #ifndef TORRENT_DISABLE_ENCRYPTION - , rc4_encrypted = 0x800, - plaintext_encrypted = 0x1000 + , rc4_encrypted = 0x100000, + plaintext_encrypted = 0x200000 #endif }; @@ -71,7 +72,8 @@ namespace libtorrent dht = 0x2, pex = 0x4, lsd = 0x8, - resume_data = 0x10 + resume_data = 0x10, + incoming = 0x20 }; int source; @@ -116,6 +118,11 @@ namespace libtorrent // for yet int download_queue_length; + // the number of requests that is + // tried to be maintained (this is + // typically a function of download speed) + int target_dl_queue_length; + // this is the number of requests // the peer has sent to us // that we haven't sent yet diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index 54df003ef..64f6203d5 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -35,7 +35,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #ifdef _MSC_VER @@ -52,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -191,11 +191,33 @@ namespace libtorrent // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! // The last argument is the policy::peer pointer for the peer that // we'll download from. - void pick_pieces(const std::vector& pieces + void pick_pieces(std::vector const& pieces , std::vector& interesting_blocks - , int num_pieces, bool prefer_whole_pieces + , int num_pieces, int prefer_whole_pieces , void* peer, piece_state_t speed - , bool rarest_first) const; + , bool rarest_first, bool on_parole + , std::vector const& suggested_pieces) const; + + // picks blocks from each of the pieces in the piece_list + // vector that is also in the piece bitmask. The blocks + // are added to interesting_blocks, and busy blocks are + // added to backup_blocks. num blocks is the number of + // blocks to be picked. Blocks are not picked from pieces + // that are being downloaded + int add_blocks(std::vector const& piece_list + , const std::vector& pieces + , std::vector& interesting_blocks + , int num_blocks, int prefer_whole_pieces + , void* peer, std::vector const& ignore) const; + + // picks blocks only from downloading pieces + int add_blocks_downloading( + std::vector const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, int prefer_whole_pieces + , void* peer, piece_state_t speed + , bool on_parole) const; // clears the peer pointer in all downloading pieces with this // peer pointer @@ -211,7 +233,7 @@ namespace libtorrent bool is_finished(piece_block block) const; // marks this piece-block as queued for downloading - void mark_as_downloading(piece_block block, void* peer + bool mark_as_downloading(piece_block block, void* peer , piece_state_t s); void mark_as_writing(piece_block block, void* peer); void mark_as_finished(piece_block block, void* peer); @@ -253,6 +275,8 @@ namespace libtorrent #ifndef NDEBUG // used in debug mode void check_invariant(const torrent* t = 0) const; + void verify_pick(std::vector const& picked + , std::vector const& bitfield) const; #endif // functor that compares indices on downloading_pieces @@ -271,6 +295,10 @@ namespace libtorrent private: + bool can_pick(int piece, std::vector const& bitmask) const; + std::pair expand_piece(int piece, int whole_pieces + , std::vector const& have) const; + struct piece_pos { piece_pos() {} @@ -320,9 +348,9 @@ namespace libtorrent int priority(int limit) const { - if (filtered() || have()) return 0; + if (downloading || filtered() || have()) return 0; // pieces we are currently downloading have high priority - int prio = downloading ? (std::min)(1, int(peer_count)) : peer_count * 2; + int prio = peer_count * 2; // if the peer_count is 0 or 1, the priority cannot be higher if (prio <= 1) return prio; if (prio >= limit * 2) prio = limit * 2; @@ -358,14 +386,6 @@ namespace libtorrent void move(int vec_index, int elem_index); void sort_piece(std::vector::iterator dp); - int add_interesting_blocks(const std::vector& piece_list - , const std::vector& pieces - , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, bool prefer_whole_pieces - , void* peer, piece_state_t speed - , bool ignore_downloading_pieces) const; - downloading_piece& add_download_piece(); void erase_download_piece(std::vector::iterator i); diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 6c976d047..7a789ec8c 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -89,7 +89,7 @@ namespace libtorrent void new_connection(peer_connection& c); // the given connection was just closed - void connection_closed(const peer_connection& c); + void connection_closed(const peer_connection& c) throw(); // the peer has got at least one interesting piece void peer_is_interesting(peer_connection& c); @@ -155,6 +155,13 @@ namespace libtorrent // this is true if the peer is a seed bool seed; + // true if this peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another peer, this peer will be choked + // if this is true + bool optimistically_unchoked; + // the time when this peer was optimistically unchoked // the last time. libtorrent::ptime last_optimistically_unchoked; @@ -203,25 +210,18 @@ namespace libtorrent peer_connection* connection; }; - int num_peers() const - { - return m_peers.size(); - } + int num_peers() const { return m_peers.size(); } - int num_uploads() const - { - return m_num_unchoked; - } - typedef std::list::iterator iterator; typedef std::list::const_iterator const_iterator; iterator begin_peer() { return m_peers.begin(); } iterator end_peer() { return m_peers.end(); } bool connect_one_peer(); + bool disconnect_one_peer(); private: - +/* bool unchoke_one_peer(); void choke_one_peer(); iterator find_choke_candidate(); @@ -233,8 +233,7 @@ namespace libtorrent void seed_choke_one_peer(); iterator find_seed_choke_candidate(); iterator find_seed_unchoke_candidate(); - - bool disconnect_one_peer(); +*/ iterator find_disconnect_candidate(); iterator find_connect_candidate(); @@ -242,10 +241,6 @@ namespace libtorrent torrent* m_torrent; - // the number of unchoked peers - // at any given time - int m_num_unchoked; - // free download we have got that hasn't // been distributed yet. size_type m_available_free_upload; @@ -253,7 +248,7 @@ namespace libtorrent // if there is a connection limit, // we disconnect one peer every minute in hope of // establishing a connection with a better peer - ptime m_last_optimistic_disconnect; +// ptime m_last_optimistic_disconnect; }; } diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 38206f32c..3a9eb563b 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -53,6 +53,7 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif +#include "libtorrent/config.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/alert.hpp" @@ -60,7 +61,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/fingerprint.hpp" -#include "libtorrent/resource_request.hpp" #include "libtorrent/storage.hpp" #ifdef _MSC_VER @@ -141,22 +141,17 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , int block_size = 16 * 1024 - , storage_constructor_type sc = default_storage_constructor); + , bool paused = false + , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED; - // ==== deprecated, this is for backwards compatibility only - // instead, use one of the other add_torrent overloads torrent_handle add_torrent( - entry const& e + boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , int block_size = 16 * 1024 - , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED - { - return add_torrent(torrent_info(e), save_path, resume_data - , compact_mode, block_size, sc); - } + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); torrent_handle add_torrent( char const* tracker_url @@ -165,8 +160,9 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data = entry() , bool compact_mode = true - , int block_size = 16 * 1024 - , storage_constructor_type sc = default_storage_constructor); + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); session_proxy abort() { return session_proxy(m_impl); } @@ -187,7 +183,7 @@ namespace libtorrent #endif #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*)> ext); + void add_extension(boost::function(torrent*, void*)> ext); #endif void set_ip_filter(ip_filter const& f); @@ -243,6 +239,7 @@ namespace libtorrent int upload_rate_limit() const; int download_rate_limit() const; + int max_half_open_connections() const; void set_upload_rate_limit(int bytes_per_second); void set_download_rate_limit(int bytes_per_second); @@ -265,12 +262,6 @@ namespace libtorrent void stop_natpmp(); void stop_upnp(); - // Resource management used for global limits. - resource_request m_ul_bandwidth_quota; - resource_request m_dl_bandwidth_quota; - resource_request m_uploads_quota; - resource_request m_connections_quota; - private: // just a way to initialize boost.filesystem diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index ebc30eae3..3a145c687 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -105,9 +105,11 @@ namespace libtorrent , send_redundant_have(false) , lazy_bitfields(true) , inactivity_timeout(600) - , unchoke_interval(20) + , unchoke_interval(15) + , optimistic_unchoke_multiplier(4) , num_want(200) , initial_picker_threshold(4) + , allowed_fast_set_size(10) , max_outstanding_disk_bytes_per_connection(64 * 1024) #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) @@ -241,6 +243,10 @@ namespace libtorrent // the number of seconds between chokes/unchokes int unchoke_interval; + // the number of unchoke intervals between + // optimistic unchokes + int optimistic_unchoke_multiplier; + // if this is set, this IP will be reported do the // tracker in the ip= parameter. address announce_ip; @@ -252,6 +258,10 @@ namespace libtorrent // random pieces instead of rarest first. int initial_picker_threshold; + // the number of allowed pieces to send to peers + // that supports the fast extensions + int allowed_fast_set_size; + // the maximum number of bytes a connection may have // pending in the disk write queue before its download // rate is being throttled. This prevents fast downloads diff --git a/libtorrent/include/libtorrent/stat.hpp b/libtorrent/include/libtorrent/stat.hpp index 2424d5d6c..24e477a37 100755 --- a/libtorrent/include/libtorrent/stat.hpp +++ b/libtorrent/include/libtorrent/stat.hpp @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/size_type.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 8a10c7148..9db79ea3d 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #ifdef _MSC_VER @@ -147,10 +148,11 @@ namespace libtorrent }; typedef storage_interface* (&storage_constructor_type)( - torrent_info const&, fs::path const& + boost::intrusive_ptr, fs::path const& , file_pool&); - TORRENT_EXPORT storage_interface* default_storage_constructor(torrent_info const& ti + TORRENT_EXPORT storage_interface* default_storage_constructor( + boost::intrusive_ptr ti , fs::path const& path, file_pool& fp); // returns true if the filesystem the path relies on supports @@ -169,7 +171,7 @@ namespace libtorrent piece_manager( boost::shared_ptr const& torrent - , torrent_info const& ti + , boost::intrusive_ptr ti , fs::path const& path , file_pool& fp , disk_io_thread& io @@ -199,7 +201,9 @@ namespace libtorrent void async_read( peer_request const& r - , boost::function const& handler); + , boost::function const& handler + , char* buffer = 0 + , int priority = 0); void async_write( peer_request const& r @@ -227,7 +231,7 @@ namespace libtorrent { return m_compact_mode; } #ifndef NDEBUG - std::string name() const { return m_info.name(); } + std::string name() const { return m_info->name(); } #endif private: @@ -283,7 +287,7 @@ namespace libtorrent // a bitmask representing the pieces we have std::vector m_have_piece; - torrent_info const& m_info; + boost::intrusive_ptr m_info; // slots that haven't had any file storage allocated std::vector m_unallocated_slots; @@ -313,12 +317,6 @@ namespace libtorrent mutable boost::recursive_mutex m_mutex; - bool m_allocating; - boost::mutex m_allocating_monitor; - boost::condition m_allocating_condition; - - // these states are used while checking/allocating the torrent - enum { // the default initial state state_none, @@ -333,6 +331,11 @@ namespace libtorrent } m_state; int m_current_slot; + // this is saved in case we need to instantiate a new + // storage (osed when remapping files) + storage_constructor_type m_storage_constructor; + + // temporary buffer used while checking std::vector m_piece_data; // this maps a piece hash to piece index. It will be @@ -340,6 +343,9 @@ namespace libtorrent // isn't needed) std::multimap m_hash_to_piece; + // this map contains partial hashes for downloading + // pieces. This is only accessed from within the + // disk-io thread. std::map m_piece_hasher; disk_io_thread& m_io_thread; diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index 2227fc932..27d61af9d 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -55,6 +55,7 @@ namespace libtorrent || _POSIX_MONOTONIC_CLOCK < 0)) || defined (TORRENT_USE_BOOST_DATE_TIME) #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -85,6 +86,7 @@ namespace libtorrent #include #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -170,6 +172,7 @@ namespace asio #include #include +#include "libtorrent/assert.hpp" // high precision timer for darwin intel and ppc @@ -249,6 +252,7 @@ namespace libtorrent #define WIN32_LEAN_AND_MEAN #endif #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -335,6 +339,7 @@ namespace libtorrent #elif defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 #include +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -385,4 +390,4 @@ namespace libtorrent #endif #endif - + diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 2eef2656b..bcc54899f 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -62,13 +62,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/tracker_manager.hpp" #include "libtorrent/stat.hpp" #include "libtorrent/alert.hpp" -#include "libtorrent/resource_request.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/config.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/bandwidth_manager.hpp" #include "libtorrent/storage.hpp" #include "libtorrent/hasher.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -98,13 +98,13 @@ namespace libtorrent torrent( aux::session_impl& ses , aux::checker_impl& checker - , torrent_info const& tf + , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused); // used with metadata-less torrents // (the metadata is downloaded from the peers) @@ -118,8 +118,8 @@ namespace libtorrent , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc); + , storage_constructor_type sc + , bool paused); ~torrent(); @@ -154,10 +154,6 @@ namespace libtorrent bool verify_resume_data(entry& rd, std::string& error) { assert(m_storage); return m_storage->verify_resume_data(rd, error); } - // is called every second by session. This will - // caclulate the upload/download and number - // of connections this torrent needs. And prepare - // it for being used by allocate_resources. void second_tick(stat& accumulator, float tick_interval); // debug purpose only @@ -254,6 +250,15 @@ namespace libtorrent void remove_url_seed(std::string const& url) { m_web_seeds.erase(url); } + std::set url_seeds() const + { return m_web_seeds; } + + bool free_upload_slots() const + { return m_num_uploads < m_max_uploads; } + + void choke_peer(peer_connection& c); + bool unchoke_peer(peer_connection& c); + // used by peer_connection to attach itself to a torrent // since incoming connections don't know what torrent // they're a part of until they have received an info_hash. @@ -465,14 +470,14 @@ namespace libtorrent bool is_seed() const { return valid_metadata() - && m_num_pieces == m_torrent_file.num_pieces(); + && m_num_pieces == m_torrent_file->num_pieces(); } // this is true if we have all the pieces that we want bool is_finished() const { if (is_seed()) return true; - return valid_metadata() && m_torrent_file.num_pieces() + return valid_metadata() && m_torrent_file->num_pieces() - m_num_pieces - m_picker->num_filtered() == 0; } @@ -494,7 +499,7 @@ namespace libtorrent } piece_manager& filesystem(); torrent_info const& torrent_file() const - { return m_torrent_file; } + { return *m_torrent_file; } std::vector const& trackers() const { return m_trackers; } @@ -516,11 +521,6 @@ namespace libtorrent // -------------------------------------------- // RESOURCE MANAGEMENT - void distribute_resources(float tick_interval); - - resource_request m_uploads_quota; - resource_request m_connections_quota; - void set_peer_upload_limit(tcp::endpoint ip, int limit); void set_peer_download_limit(tcp::endpoint ip, int limit); @@ -530,7 +530,9 @@ namespace libtorrent int download_limit() const; void set_max_uploads(int limit); + int max_uploads() const { return m_max_uploads; } void set_max_connections(int limit); + int max_connections() const { return m_max_connections; } void move_storage(fs::path const& save_path); // unless this returns true, new connections must wait @@ -538,7 +540,7 @@ namespace libtorrent bool ready_for_connections() const { return m_connections_initialized; } bool valid_metadata() const - { return m_torrent_file.is_valid(); } + { return m_torrent_file->is_valid(); } // parses the info section from the given // bencoded tree and moves the torrent @@ -562,7 +564,7 @@ namespace libtorrent void update_peer_interest(); - torrent_info m_torrent_file; + boost::intrusive_ptr m_torrent_file; // is set to true when the torrent has // been aborted. @@ -705,9 +707,9 @@ namespace libtorrent // determine the timeout until next try. int m_failed_trackers; - // this is a counter that is increased every - // second, and when it reaches 10, the policy::pulse() - // is called and the time scaler is reset to 0. + // this is a counter that is decreased every + // second, and when it reaches 0, the policy::pulse() + // is called and the time scaler is reset to 10. int m_time_scaler; // the bitmask that says which pieces we have @@ -774,6 +776,15 @@ namespace libtorrent session_settings const& m_settings; storage_constructor_type m_storage_constructor; + + // the maximum number of uploads for this torrent + int m_max_uploads; + + // the number of unchoked peers in this torrent + int m_num_uploads; + + // the maximum number of connections for this torrent + int m_max_connections; #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list > extension_list_t; diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 3f7ae5bcc..287c57305 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_TORRENT_HANDLE_HPP_INCLUDED #include +#include #ifdef _MSC_VER #pragma warning(push, 1) @@ -273,7 +274,9 @@ namespace libtorrent std::vector const& trackers() const; void replace_trackers(std::vector const&) const; - void add_url_seed(std::string const& url); + void add_url_seed(std::string const& url) const; + void remove_url_seed(std::string const& url) const; + std::set url_seeds() const; bool has_metadata() const; const torrent_info& get_torrent_info() const; @@ -396,6 +399,7 @@ namespace libtorrent , m_info_hash(h) { assert(m_ses != 0); + assert(m_chk != 0); } #ifndef NDEBUG diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index a2d6c4ef9..492fda48d 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -57,6 +57,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_request.hpp" #include "libtorrent/config.hpp" #include "libtorrent/time.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { @@ -71,7 +73,7 @@ namespace libtorrent size_type offset; // the offset of this file inside the torrent size_type size; // the size of this file // if the path was incorrectly encoded, this is - // the origianal corrupt encoded string. It is + // the original corrupt encoded string. It is // preserved in order to be able to reproduce // the correct info-hash boost::shared_ptr orig_path; @@ -96,7 +98,7 @@ namespace libtorrent virtual const char* what() const throw() { return "invalid torrent file"; } }; - class TORRENT_EXPORT torrent_info + class TORRENT_EXPORT torrent_info : public intrusive_ptr_base { public: @@ -115,8 +117,12 @@ namespace libtorrent void add_file(fs::path file, size_type size); void add_url_seed(std::string const& url); - std::vector map_block(int piece, size_type offset, int size) const; - peer_request map_file(int file, size_type offset, int size) const; + bool remap_files(std::vector > const& map); + + std::vector map_block(int piece, size_type offset + , int size, bool storage = false) const; + peer_request map_file(int file, size_type offset, int size + , bool storage = false) const; std::vector const& url_seeds() const { @@ -128,15 +134,60 @@ namespace libtorrent typedef std::vector::const_reverse_iterator reverse_file_iterator; // list the files in the torrent file - file_iterator begin_files() const { return m_files.begin(); } - file_iterator end_files() const { return m_files.end(); } - reverse_file_iterator rbegin_files() const { return m_files.rbegin(); } - reverse_file_iterator rend_files() const { return m_files.rend(); } + file_iterator begin_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.begin(); + else + return m_remapped_files.begin(); + } - int num_files() const - { assert(m_piece_length > 0); return (int)m_files.size(); } - const file_entry& file_at(int index) const - { assert(index >= 0 && index < (int)m_files.size()); return m_files[index]; } + file_iterator end_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.end(); + else + return m_remapped_files.end(); + } + + reverse_file_iterator rbegin_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.rbegin(); + else + return m_remapped_files.rbegin(); + } + + reverse_file_iterator rend_files(bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + return m_files.rend(); + else + return m_remapped_files.rend(); + } + + int num_files(bool storage = false) const + { + assert(m_piece_length > 0); + if (!storage || m_remapped_files.empty()) + return (int)m_files.size(); + else + return (int)m_remapped_files.size(); + } + + const file_entry& file_at(int index, bool storage = false) const + { + if (!storage || m_remapped_files.empty()) + { + assert(index >= 0 && index < (int)m_files.size()); + return m_files[index]; + } + else + { + assert(index >= 0 && index < (int)m_remapped_files.size()); + return m_remapped_files[index]; + } + } const std::vector& trackers() const { return m_urls; } @@ -218,6 +269,13 @@ namespace libtorrent // the list of files that this torrent consists of std::vector m_files; + // this vector is typically empty. If it is not + // empty, it means the user has re-mapped the + // files in this torrent to diffefrent names + // on disk. This is only used when reading and + // writing the disk. + std::vector m_remapped_files; + nodes_t m_nodes; // the sum of all filesizes @@ -264,8 +322,10 @@ namespace libtorrent entry m_extra_info; #ifndef NDEBUG + public: // this is set to true when seed_free() is called bool m_half_metadata; + private: #endif }; diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index 1435ceda6..57f7bd851 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -194,11 +194,10 @@ namespace libtorrent , address bind_interface , boost::weak_ptr r); - request_callback& requester(); + boost::shared_ptr requester(); virtual ~tracker_connection() {} tracker_request const& tracker_req() const { return m_req; } - bool has_requester() const { return !m_requester.expired(); } void fail(int code, char const* msg); void fail_timeout(); diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index d4b701aad..fc0650631 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_UPNP_HPP #include "libtorrent/socket.hpp" +#include "libtorrent/broadcast_socket.hpp" #include "libtorrent/http_connection.hpp" #include "libtorrent/connection_queue.hpp" @@ -56,9 +57,6 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -bool is_local(address const& a); -address_v4 guess_local_address(asio::io_service&); - // int: external tcp port // int: external udp port // std::string: error message @@ -72,8 +70,6 @@ public: , portmap_callback_t const& cb); ~upnp(); - void rebind(address const& listen_interface); - // maps the ports, if a port is set to 0 // it will not be mapped void set_mappings(int tcp, int udp); @@ -90,7 +86,7 @@ private: void update_mapping(int i, int port); void resend_request(asio::error_code const& e); - void on_reply(asio::error_code const& e + void on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); void discover_device(); @@ -106,12 +102,15 @@ private: , int mapping); void on_expire(asio::error_code const& e); - void post(rootdevice& d, std::stringstream const& s - , std::string const& soap_action); void map_port(rootdevice& d, int i); void unmap_port(rootdevice& d, int i); void disable(); + void delete_port_mapping(rootdevice& d, int i); + void create_port_mapping(http_connection& c, rootdevice& d, int i); + void post(upnp::rootdevice const& d, std::string const& soap + , std::string const& soap_action); + struct mapping_t { mapping_t() @@ -198,18 +197,13 @@ private: // current retry count int m_retry_count; - // used to receive responses in - char m_receive_buffer[1024]; + asio::io_service& m_io_service; - // the endpoint we received the message from - udp::endpoint m_remote; + asio::strand m_strand; - // the local address we're listening on - address_v4 m_local_ip; - // the udp socket used to send and receive // multicast messages on the network - datagram_socket m_socket; + broadcast_socket m_socket; // used to resend udp packets in case // they time out @@ -217,8 +211,6 @@ private: // timer used to refresh mappings deadline_timer m_refresh_timer; - - asio::strand m_strand; bool m_disabled; bool m_closing; diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index ba7450c0a..1290f14a1 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -65,7 +65,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" @@ -126,6 +125,8 @@ namespace libtorrent void write_piece(peer_request const& r, char const* buffer) { assert(false); } void write_keepalive() {} void on_connected(); + void write_reject_request(peer_request const&) {} + void write_allow_fast(int) {} #ifndef NDEBUG void check_invariant() const; diff --git a/libtorrent/src/assert.cpp b/libtorrent/src/assert.cpp index e69de29bb..b4f011978 100644 --- a/libtorrent/src/assert.cpp +++ b/libtorrent/src/assert.cpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NDEBUG + +#include +#include +#include +#if defined __linux__ && defined __GNUC__ +#include +#endif + +void assert_fail(char const* expr, int line, char const* file, char const* function) +{ + + fprintf(stderr, "assertion failed. Please file a bugreport at " + "http://code.rasterbar.com/libtorrent/newticket\n" + "Please include the following information:\n\n" + "file: '%s'\n" + "line: %d\n" + "function: %s\n" + "expression: %s\n" + "stack:\n", file, line, function, expr); + +#if defined __linux__ && defined __GNUC__ + void* stack[50]; + int size = backtrace(stack, 50); + char** symbols = backtrace_symbols(stack, size); + + for (int i = 0; i < size; ++i) + { + fprintf(stderr, "%d: %s\n", i, symbols[i]); + } + + free(symbols); +#endif + // send SIGINT to the current process + // to break into the debugger + raise(SIGINT); + abort(); +} + +#endif + diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index e69de29bb..3aaadcc81 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + bool is_local(address const& a) + { + if (a.is_v6()) return a.to_v6().is_link_local(); + address_v4 a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 + || (ip & 0xfff00000) == 0xac100000 + || (ip & 0xffff0000) == 0xc0a80000); + } + + address_v4 guess_local_address(asio::io_service& ios) + { + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + address const& a = i->endpoint().address(); + // ignore non-IPv4 addresses + if (!a.is_v4()) break; + // ignore the loopback + if (a.to_v4() == address_v4::loopback()) continue; + } + if (i == udp::resolver_iterator()) return address_v4::any(); + return i->endpoint().address().to_v4(); + } + + bool is_loopback(address const& addr) + { + if (addr.is_v4()) + return addr.to_v4() == address_v4::loopback(); + else + return addr.to_v6() == address_v6::loopback(); + } + + bool is_multicast(address const& addr) + { + if (addr.is_v4()) + return addr.to_v4().is_multicast(); + else + return addr.to_v6().is_multicast(); + } + + broadcast_socket::broadcast_socket(asio::io_service& ios + , udp::endpoint const& multicast_endpoint + , receive_handler_t const& handler + , bool loopback) + : m_multicast_endpoint(multicast_endpoint) + , m_on_receive(handler) + { + assert(is_multicast(m_multicast_endpoint.address())); + + using namespace asio::ip::multicast; + + asio::error_code ec; + std::vector
interfaces = enum_net_interfaces(ios, ec); + + for (std::vector
::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + // only broadcast to IPv4 addresses that are not local + if (!is_local(*i)) continue; + // only multicast on compatible networks + if (i->is_v4() != multicast_endpoint.address().is_v4()) continue; + // ignore any loopback interface + if (is_loopback(*i)) continue; + + boost::shared_ptr s(new datagram_socket(ios)); + if (i->is_v4()) + { + s->open(udp::v4(), ec); + if (ec) continue; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) continue; + s->bind(udp::endpoint(address_v4::any(), multicast_endpoint.port()), ec); + if (ec) continue; + s->set_option(join_group(multicast_endpoint.address()), ec); + if (ec) continue; + s->set_option(outbound_interface(i->to_v4()), ec); + if (ec) continue; + } + else + { + s->open(udp::v6(), ec); + if (ec) continue; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) continue; + s->bind(udp::endpoint(address_v6::any(), multicast_endpoint.port()), ec); + if (ec) continue; + s->set_option(join_group(multicast_endpoint.address()), ec); + if (ec) continue; +// s->set_option(outbound_interface(i->to_v6()), ec); +// if (ec) continue; + } + s->set_option(hops(255), ec); + if (ec) continue; + s->set_option(enable_loopback(loopback), ec); + if (ec) continue; + m_sockets.push_back(socket_entry(s)); + socket_entry& se = m_sockets.back(); + s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); +#ifndef NDEBUG +// std::cerr << "broadcast socket [ if: " << i->to_v4().to_string() +// << " group: " << multicast_endpoint.address() << " ]" << std::endl; +#endif + } + } + + void broadcast_socket::send(char const* buffer, int size, asio::error_code& ec) + { + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + asio::error_code e; + i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); +#ifndef NDEBUG +// std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; +#endif + if (e) ec = e; + } + } + + void broadcast_socket::on_receive(socket_entry* s, asio::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec || bytes_transferred == 0) return; + m_on_receive(s->remote, s->buffer, bytes_transferred); + s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer)) + , s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2)); + } + + void broadcast_socket::close() + { + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + i->socket->close(); + } + } +} + + diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 5b63f26cd..ab61d98f9 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -50,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION #include "libtorrent/pe_crypto.hpp" @@ -75,7 +76,14 @@ namespace libtorrent &bt_peer_connection::on_piece, &bt_peer_connection::on_cancel, &bt_peer_connection::on_dht_port, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + // FAST extension messages + &bt_peer_connection::on_suggest_piece, + &bt_peer_connection::on_have_all, + &bt_peer_connection::on_have_none, + &bt_peer_connection::on_reject_request, + &bt_peer_connection::on_allowed_fast, + 0, 0, &bt_peer_connection::on_extended }; @@ -93,6 +101,7 @@ namespace libtorrent , m_supports_extensions(false) #endif , m_supports_dht_port(false) + , m_supports_fast(false) #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) @@ -124,6 +133,7 @@ namespace libtorrent , m_supports_extensions(false) #endif , m_supports_dht_port(false) + , m_supports_fast(false) #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) @@ -226,6 +236,10 @@ namespace libtorrent boost::shared_ptr t = associated_torrent().lock(); assert(t); write_bitfield(t->pieces()); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.get_dht_settings().service_port); +#endif } void bt_peer_connection::write_dht_port(int listen_port) @@ -246,6 +260,75 @@ namespace libtorrent setup_send(); } + void bt_peer_connection::write_have_all() + { + INVARIANT_CHECK; + assert(m_sent_handshake && !m_sent_bitfield); +#ifndef NDEBUG + m_sent_bitfield = true; +#endif +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> HAVE_ALL\n"; +#endif + char buf[] = {0,0,0,1, msg_have_all}; + send_buffer(buf, buf + sizeof(buf)); + } + + void bt_peer_connection::write_have_none() + { + INVARIANT_CHECK; + assert(m_sent_handshake && !m_sent_bitfield); +#ifndef NDEBUG + m_sent_bitfield = true; +#endif +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> HAVE_NONE\n"; +#endif + char buf[] = {0,0,0,1, msg_have_none}; + send_buffer(buf, buf + sizeof(buf)); + } + + void bt_peer_connection::write_reject_request(peer_request const& r) + { + INVARIANT_CHECK; + + assert(m_sent_handshake && m_sent_bitfield); + assert(associated_torrent().lock()->valid_metadata()); + + char buf[] = {0,0,0,13, msg_reject_request}; + + buffer::interval i = allocate_send_buffer(17); + + std::copy(buf, buf + 5, i.begin); + i.begin += 5; + + // index + detail::write_int32(r.piece, i.begin); + // begin + detail::write_int32(r.start, i.begin); + // length + detail::write_int32(r.length, i.begin); + assert(i.begin == i.end); + + setup_send(); + } + + void bt_peer_connection::write_allow_fast(int piece) + { + INVARIANT_CHECK; + + assert(m_sent_handshake && m_sent_bitfield); + assert(associated_torrent().lock()->valid_metadata()); + + char buf[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; + + char* ptr = buf + 5; + detail::write_int32(piece, ptr); + send_buffer(buf, buf + sizeof(buf)); + } + void bt_peer_connection::get_specific_peer_info(peer_info& p) const { assert(!associated_torrent().expired()); @@ -628,14 +711,17 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT // indicate that we support the DHT messages - *(i.begin + 7) = 0x01; + *(i.begin + 7) |= 0x01; #endif #ifndef TORRENT_DISABLE_EXTENSIONS // we support extensions - *(i.begin + 5) = 0x10; + *(i.begin + 5) |= 0x10; #endif + // we support FAST extension + *(i.begin + 7) |= 0x04; + i.begin += 8; // info hash @@ -721,6 +807,20 @@ namespace libtorrent if (!packet_finished()) return; incoming_choke(); + if (!m_supports_fast) + { + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + while (!request_queue().empty()) + { + piece_block const& b = request_queue().front(); + peer_request r; + r.piece = b.piece_index; + r.start = b.block_index * t->block_size(); + r.length = t->block_size(); + incoming_reject_request(r); + } + } } // ----------------------------- @@ -939,6 +1039,9 @@ namespace libtorrent { INVARIANT_CHECK; + if (!m_supports_dht_port) + throw protocol_error("got 'dht_port' message from peer that doesn't support it"); + assert(received > 0); if (packet_size() != 3) throw protocol_error("'dht_port' message size != 3"); @@ -953,6 +1056,80 @@ namespace libtorrent incoming_dht_port(listen_port); } + void bt_peer_connection::on_suggest_piece(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'suggest_piece' without FAST extension support"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + const char* ptr = recv_buffer.begin + 1; + int piece = detail::read_uint32(ptr); + incoming_suggest(piece); + } + + void bt_peer_connection::on_have_all(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'have_all' without FAST extension support"); + m_statistics.received_bytes(0, received); + incoming_have_all(); + } + + void bt_peer_connection::on_have_none(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'have_none' without FAST extension support"); + m_statistics.received_bytes(0, received); + incoming_have_none(); + } + + void bt_peer_connection::on_reject_request(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'reject_request' without FAST extension support"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + + buffer::const_interval recv_buffer = receive_buffer(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_reject_request(r); + } + + void bt_peer_connection::on_allowed_fast(int received) + { + INVARIANT_CHECK; + + if (!m_supports_fast) + throw protocol_error("got 'allowed_fast' without FAST extension support"); + + m_statistics.received_bytes(0, received); + if (!packet_finished()) return; + buffer::const_interval recv_buffer = receive_buffer(); + const char* ptr = recv_buffer.begin + 1; + int index = detail::read_int32(ptr); + + incoming_allowed_fast(index); + } + // ----------------------------- // --------- EXTENDED ---------- // ----------------------------- @@ -1045,7 +1222,7 @@ namespace libtorrent { tcp::endpoint adr(remote().address() , (unsigned short)listen_port->integer()); - t->get_policy().peer_from_tracker(adr, pid(), 0, 0); + t->get_policy().peer_from_tracker(adr, pid(), peer_info::incoming, 0); } } // there should be a version too @@ -1175,6 +1352,22 @@ namespace libtorrent assert(m_sent_handshake && !m_sent_bitfield); assert(t->valid_metadata()); + // in this case, have_all or have_none should be sent instead + assert(!m_supports_fast || !t->is_seed() || t->num_pieces() != 0); + + if (m_supports_fast && t->is_seed()) + { + write_have_all(); + send_allowed_set(); + return; + } + else if (m_supports_fast && t->num_pieces() == 0) + { + write_have_none(); + send_allowed_set(); + return; + } + int num_pieces = bitfield.size(); int lazy_pieces[50]; int num_lazy_pieces = 0; @@ -1183,7 +1376,7 @@ namespace libtorrent assert(t->is_seed() == (std::count(bitfield.begin(), bitfield.end(), true) == num_pieces)); if (t->is_seed() && m_ses.settings().lazy_bitfields) { - num_lazy_pieces = std::min(50, num_pieces / 10); + num_lazy_pieces = (std::min)(50, num_pieces / 10); if (num_lazy_pieces < 1) num_lazy_pieces = 1; for (int i = 0; i < num_pieces; ++i) { @@ -1251,6 +1444,9 @@ namespace libtorrent #endif } } + + if (m_supports_fast) + send_allowed_set(); } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1279,6 +1475,19 @@ namespace libtorrent detail::write_address(remote().address(), out); handshake["yourip"] = remote_address; handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; + asio::error_code ec; + std::vector
const& interfaces = enum_net_interfaces(get_socket()->io_service(), ec); + for (std::vector
::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + // TODO: only use global IPv6 addresses + if (!i->is_v6() || i->to_v6().is_link_local()) continue; + std::string ipv6_address; + std::back_insert_iterator out(ipv6_address); + detail::write_address(*i, out); + handshake["ipv6"] = ipv6_address; + break; + } // loop backwards, to make the first extension be the last // to fill in the handshake (i.e. give the first extensions priority) @@ -1546,7 +1755,7 @@ namespace libtorrent if (m_sync_bytes_read >= 512) throw protocol_error("sync hash not found within 532 bytes"); - cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+20) - m_sync_bytes_read)); + cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+20) - m_sync_bytes_read)); assert(!packet_finished()); return; @@ -1684,7 +1893,7 @@ namespace libtorrent if (m_sync_bytes_read >= 512) throw protocol_error("sync verification constant not found within 520 bytes"); - cut_receive_buffer(bytes_processed, std::min(packet_size(), (512+8) - m_sync_bytes_read)); + cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+8) - m_sync_bytes_read)); assert(!packet_finished()); return; @@ -2016,6 +2225,9 @@ namespace libtorrent if (recv_buffer[7] & 0x01) m_supports_dht_port = true; + if (recv_buffer[7] & 0x04) + m_supports_fast = true; + // ok, now we have got enough of the handshake. Is this connection // attached to a torrent? if (!t) @@ -2049,10 +2261,10 @@ namespace libtorrent assert(t); // if this is a local connection, we have already - // send the handshake + // sent the handshake if (!is_local()) write_handshake(); - if (t->valid_metadata()) - write_bitfield(t->pieces()); +// if (t->valid_metadata()) +// write_bitfield(t->pieces()); assert(t->get_policy().has_connection(this)); @@ -2125,11 +2337,6 @@ namespace libtorrent throw protocol_error("closing connection to ourself"); } -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.get_dht_settings().service_port); -#endif - m_client_version = identify_client(pid); boost::optional f = client_fingerprint(pid); if (f && std::equal(f->name, f->name + 2, "BC")) @@ -2181,6 +2388,14 @@ namespace libtorrent m_state = read_packet_size; reset_recv_buffer(4); + if (t->valid_metadata()) + { + write_bitfield(t->pieces()); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.get_dht_settings().service_port); +#endif + } assert(!packet_finished()); return; diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index 859205ed0..0b3f5ff54 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -54,6 +54,8 @@ namespace libtorrent , boost::function const& on_timeout , time_duration timeout) { + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; m_queue.push_back(entry()); @@ -68,6 +70,8 @@ namespace libtorrent void connection_queue::done(int ticket) { + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; std::list::iterator i = std::find_if(m_queue.begin() @@ -148,6 +152,8 @@ namespace libtorrent void connection_queue::on_timeout(asio::error_code const& e) { + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; #ifndef NDEBUG function_guard guard_(m_in_timeout_function); @@ -158,21 +164,35 @@ namespace libtorrent ptime next_expire = max_time(); ptime now = time_now(); + std::list timed_out; for (std::list::iterator i = m_queue.begin(); !m_queue.empty() && i != m_queue.end();) { if (i->connecting && i->expires < now) { - boost::function on_timeout = i->on_timeout; - m_queue.erase(i++); + std::list::iterator j = i; + ++i; + timed_out.splice(timed_out.end(), m_queue, j, i); --m_num_connecting; - try { on_timeout(); } catch (std::exception&) {} continue; } if (i->expires < next_expire) next_expire = i->expires; ++i; } + + // we don't want to call the timeout callback while we're locked + // since that is a recepie for dead-locks + l.unlock(); + + for (std::list::iterator i = timed_out.begin() + , end(timed_out.end()); i != end; ++i) + { + try { i->on_timeout(); } catch (std::exception&) {} + } + + l.lock(); + if (next_expire < max_time()) { m_timer.expires_at(next_expire); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 6bc357506..4fb2cfb3a 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -34,6 +34,24 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/disk_io_thread.hpp" +#ifdef TORRENT_DISK_STATS + +#include "libtorrent/time.hpp" +#include + +namespace +{ + std::string log_time() + { + using namespace libtorrent; + static ptime start = time_now(); + return boost::lexical_cast( + total_milliseconds(time_now() - start)); + } +} + +#endif + namespace libtorrent { @@ -45,7 +63,12 @@ namespace libtorrent , m_block_size(block_size) #endif , m_disk_io_thread(boost::ref(*this)) - {} + { + +#ifdef TORRENT_DISK_STATS + m_log.open("disk_io_thread.log", std::ios::trunc); +#endif + } disk_io_thread::~disk_io_thread() { @@ -89,8 +112,15 @@ namespace libtorrent namespace { + // The semantic of this operator is: + // shouls lhs come before rhs in the job queue bool operator<(disk_io_job const& lhs, disk_io_job const& rhs) { + // NOTE: comparison inverted to make higher priority + // skip _in_front_of_ lower priority + if (lhs.priority > rhs.priority) return true; + if (lhs.priority < rhs.priority) return false; + if (lhs.storage.get() < rhs.storage.get()) return true; if (lhs.storage.get() > rhs.storage.get()) return false; if (lhs.piece < rhs.piece) return true; @@ -165,6 +195,9 @@ namespace libtorrent { for (;;) { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " idle" << std::endl; +#endif boost::mutex::scoped_lock l(m_mutex); while (m_jobs.empty() && !m_abort) m_signal.wait(l); @@ -179,31 +212,46 @@ namespace libtorrent int ret = 0; + bool free_buffer = true; try { +#ifdef TORRENT_DISK_STATS + ptime start = time_now(); +#endif // std::cerr << "DISK THREAD: executing job: " << j.action << std::endl; switch (j.action) { case disk_io_job::read: - l.lock(); - j.buffer = (char*)m_pool.ordered_malloc(); - l.unlock(); +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " read " << j.buffer_size << std::endl; +#endif if (j.buffer == 0) { - ret = -1; - j.str = "out of memory"; + l.lock(); + j.buffer = (char*)m_pool.ordered_malloc(); + l.unlock(); + assert(j.buffer_size <= m_block_size); + if (j.buffer == 0) + { + ret = -1; + j.str = "out of memory"; + break; + } } else { - assert(j.buffer_size <= m_block_size); - ret = j.storage->read_impl(j.buffer, j.piece, j.offset - , j.buffer_size); - - // simulates slow drives - // usleep(300); + free_buffer = false; } + ret = j.storage->read_impl(j.buffer, j.piece, j.offset + , j.buffer_size); + + // simulates slow drives + // usleep(300); break; case disk_io_job::write: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " write " << j.buffer_size << std::endl; +#endif assert(j.buffer); assert(j.buffer_size <= m_block_size); j.storage->write_impl(j.buffer, j.piece, j.offset @@ -214,16 +262,25 @@ namespace libtorrent break; case disk_io_job::hash: { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " hash" << std::endl; +#endif sha1_hash h = j.storage->hash_for_piece_impl(j.piece); j.str.resize(20); std::memcpy(&j.str[0], &h[0], 20); } break; case disk_io_job::move_storage: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " move" << std::endl; +#endif ret = j.storage->move_storage_impl(j.str) ? 1 : 0; j.str = j.storage->save_path().string(); break; case disk_io_job::release_files: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " release" << std::endl; +#endif j.storage->release_files_impl(); break; } @@ -240,7 +297,7 @@ namespace libtorrent try { if (handler) handler(ret, j); } catch (std::exception&) {} - if (j.buffer) + if (j.buffer && free_buffer) { l.lock(); m_pool.ordered_free(j.buffer); diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index e69de29bb..172719793 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#if defined __linux__ || defined __MACH__ +#include +#include +#include +#endif + +#include "libtorrent/enum_net.hpp" + +namespace libtorrent +{ + std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) + { + static std::vector
ret; + if (!ret.empty()) return ret; + +#if defined __linux__ || defined __MACH__ || defined(__FreeBSD__) + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = asio::error::fault; + return ret; + } + ifconf ifc; + char buf[1024]; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) + { + close(s); + ec = asio::error::fault; + return ret; + } + close(s); + + char *ifr = (char*)ifc.ifc_req; + int remaining = ifc.ifc_len; + + while (remaining) + { + ifreq const& item = *reinterpret_cast(ifr); + if (item.ifr_addr.sa_family == AF_INET) + { + typedef asio::ip::address_v4::bytes_type bytes_t; + bytes_t b; + memcpy(&b[0], &((sockaddr_in const*)&item.ifr_addr)->sin_addr, b.size()); + ret.push_back(address_v4(b)); + } + else if (item.ifr_addr.sa_family == AF_INET6) + { + typedef asio::ip::address_v6::bytes_type bytes_t; + bytes_t b; + memcpy(&b[0], &((sockaddr_in6 const*)&item.ifr_addr)->sin6_addr, b.size()); + ret.push_back(address_v6(b)); + } + +#if defined __MACH__ || defined(__FreeBSD__) + int current_size = item.ifr_addr.sa_len + IFNAMSIZ; +#elif defined __linux__ + int current_size = sizeof(ifreq); +#endif + ifr += current_size; + remaining -= current_size; + } + +#elif defined WIN32 + + SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == SOCKET_ERROR) + { + ec = asio::error::fault; + return ret; + } + + INTERFACE_INFO buffer[30]; + DWORD size; + + if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, + sizeof(buffer), &size, 0, 0) != 0) + { + closesocket(s); + ec = asio::error::fault; + return ret; + } + closesocket(s); + + int n = size / sizeof(INTERFACE_INFO); + + for (int i = 0; i < n; ++i) + { + sockaddr_in *sockaddr = (sockaddr_in*)&buffer[i].iiAddress; + address a(address::from_string(inet_ntoa(sockaddr->sin_addr))); + if (a == address_v4::any()) continue; + ret.push_back(a); + } + +#else + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + ret.push_back(i->endpoint().address()); + } +#endif + return ret; + } + +} + + diff --git a/libtorrent/src/escape_string.cpp b/libtorrent/src/escape_string.cpp index 02a4fa085..faff3de95 100755 --- a/libtorrent/src/escape_string.cpp +++ b/libtorrent/src/escape_string.cpp @@ -33,13 +33,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include -#include #include #include #include #include #include +#include "libtorrent/assert.hpp" + namespace libtorrent { std::string unescape_string(std::string const& s) diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 3d568d1f7..72876d528 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -247,19 +247,16 @@ namespace libtorrent void set_size(size_type s) { - size_type pos = tell(); - // Only set size if current file size not equals s. - // 2 as "m" argument is to be sure seek() sets SEEK_END on - // all compilers. - if(s != seek(0, 2)) +#ifdef _WIN32 +#error file.cpp is for posix systems only. use file_win.cpp on windows +#else + if (ftruncate(m_fd, s) < 0) { - seek(s - 1); - char dummy = 0; - read(&dummy, 1); - seek(s - 1); - write(&dummy, 1); + std::stringstream msg; + msg << "ftruncate failed: '" << strerror(errno); + throw file_error(msg.str()); } - seek(pos); +#endif } size_type seek(size_type offset, int m = 1) diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 53798cae1..2b306ca6d 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -42,6 +42,8 @@ using boost::bind; namespace libtorrent { + enum { max_bottled_buffer = 1024 * 1024 }; + void http_connection::get(std::string const& url, time_duration timeout , bool handle_redirect) { @@ -165,6 +167,7 @@ void http_connection::on_connect(asio::error_code const& e if (!e) { m_last_receive = time_now(); + if (m_connect_handler) m_connect_handler(*this); asio::async_write(m_sock, asio::buffer(sendbuffer) , bind(&http_connection::on_write, shared_from_this(), _1)); } @@ -310,8 +313,8 @@ void http_connection::on_read(asio::error_code const& e } if (int(m_recvbuffer.size()) == m_read_pos) - m_recvbuffer.resize((std::min)(m_read_pos + 2048, 1024*500)); - if (m_read_pos == 1024 * 500) + m_recvbuffer.resize((std::min)(m_read_pos + 2048, int(max_bottled_buffer))); + if (m_read_pos == max_bottled_buffer) { close(); if (m_bottled && m_called) return; diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 936f8292a..0a0e59c48 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -148,13 +148,19 @@ namespace libtorrent pos = newline; line >> m_protocol; - if (m_protocol.substr(0, 5) != "HTTP/") + if (m_protocol.substr(0, 5) == "HTTP/") { - throw std::runtime_error("unknown protocol in HTTP response: " - + m_protocol + " line: " + std::string(pos, newline)); + line >> m_status_code; + std::getline(line, m_server_message); + } + else + { + m_method = m_protocol; + std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower); + m_protocol.clear(); + line >> m_path >> m_protocol; + m_status_code = 0; } - line >> m_status_code; - std::getline(line, m_server_message); m_state = read_header; } @@ -250,7 +256,7 @@ namespace libtorrent assert(m_state == read_body); if (m_content_length >= 0) return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos - , m_recv_buffer.begin + std::min(m_recv_pos + , m_recv_buffer.begin + (std::min)(m_recv_pos , m_body_start_pos + m_content_length)); else return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos @@ -408,7 +414,7 @@ namespace libtorrent { m_send_buffer += "numwant="; m_send_buffer += boost::lexical_cast( - std::min(req.num_want, 999)); + (std::min)(req.num_want, 999)); m_send_buffer += '&'; } if (m_settings.announce_ip != address() && !url_has_argument(request, "ip")) @@ -459,14 +465,16 @@ namespace libtorrent m_send_buffer += "\r\n\r\n"; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); + cb->debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); std::stringstream info_hash_str; info_hash_str << req.info_hash; - requester().debug_log("info_hash: " + cb->debug_log("info_hash: " + boost::lexical_cast(req.info_hash)); - requester().debug_log("name lookup: " + hostname); + cb->debug_log("name lookup: " + hostname); } #endif @@ -491,8 +499,9 @@ namespace libtorrent void http_tracker_connection::name_lookup(asio::error_code const& error , tcp::resolver::iterator i) try { + boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker name lookup handler called"); + if (cb) cb->debug_log("tracker name lookup handler called"); #endif if (error == asio::error::operation_aborted) return; if (m_timed_out) return; @@ -504,7 +513,7 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker name lookup successful"); + if (cb) cb->debug_log("tracker name lookup successful"); #endif restart_read_timeout(); @@ -519,11 +528,11 @@ namespace libtorrent if (target == end) { assert(target_address.address().is_v4() != bind_interface().is_v4()); - if (has_requester()) + if (cb) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - requester().tracker_warning("the tracker only resolves to an " + cb->tracker_warning("the tracker only resolves to an " + tracker_address_type + " address, and you're listening on an " + bind_address_type + " socket. This may prevent you from receiving incoming connections."); } @@ -533,7 +542,7 @@ namespace libtorrent target_address = *target; } - if (has_requester()) requester().m_tracker_address = target_address; + if (cb) cb->m_tracker_address = target_address; m_socket = instantiate_connection(m_name_lookup.io_service(), m_proxy); if (m_proxy.type == proxy_settings::http @@ -574,7 +583,8 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker connection successful"); + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("tracker connection successful"); #endif restart_read_timeout(); @@ -598,7 +608,8 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker send data completed"); + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("tracker send data completed"); #endif restart_read_timeout(); assert(m_buffer.size() - m_recv_pos > 0); @@ -634,7 +645,8 @@ namespace libtorrent restart_read_timeout(); assert(bytes_transferred > 0); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker connection reading " + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("tracker connection reading " + boost::lexical_cast(bytes_transferred)); #endif @@ -700,6 +712,8 @@ namespace libtorrent } std::string location = m_parser.header("location"); + + boost::shared_ptr cb = requester(); if (m_parser.status_code() >= 300 && m_parser.status_code() < 400) { @@ -720,9 +734,9 @@ namespace libtorrent } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("Redirecting to \"" + location + "\""); + if (cb) cb->debug_log("Redirecting to \"" + location + "\""); #endif - if (has_requester()) requester().tracker_warning("Redirecting to \"" + location + "\""); + if (cb) cb->tracker_warning("Redirecting to \"" + location + "\""); tracker_request req = tracker_req(); req.url = location; @@ -745,20 +759,18 @@ namespace libtorrent std::string content_encoding = m_parser.header("content-encoding"); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("content-encoding: \"" + content_encoding + "\""); + if (cb) cb->debug_log("content-encoding: \"" + content_encoding + "\""); #endif if (content_encoding == "gzip" || content_encoding == "x-gzip") { - boost::shared_ptr r = m_requester.lock(); - - if (!r) + if (!cb) { close(); return; } m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_parser.body_start()); - if (inflate_gzip(m_buffer, tracker_request(), r.get(), + if (inflate_gzip(m_buffer, tracker_request(), cb.get(), m_settings.tracker_maximum_response_length)) { close(); @@ -835,7 +847,8 @@ namespace libtorrent void http_tracker_connection::parse(entry const& e) { - if (!has_requester()) return; + boost::shared_ptr cb = requester(); + if (!cb) return; try { @@ -852,8 +865,7 @@ namespace libtorrent try { entry const& warning = e["warning message"]; - if (has_requester()) - requester().tracker_warning(warning.string()); + cb->tracker_warning(warning.string()); } catch(type_error const&) {} @@ -867,7 +879,7 @@ namespace libtorrent entry scrape_data = e["files"][ih]; int complete = scrape_data["complete"].integer(); int incomplete = scrape_data["incomplete"].integer(); - requester().tracker_response(tracker_request(), peer_list, 0, complete + cb->tracker_response(tracker_request(), peer_list, 0, complete , incomplete); return; } @@ -884,12 +896,7 @@ namespace libtorrent peer_entry p; p.pid.clear(); - std::stringstream ip_str; - ip_str << (int)detail::read_uint8(i) << "."; - ip_str << (int)detail::read_uint8(i) << "."; - ip_str << (int)detail::read_uint8(i) << "."; - ip_str << (int)detail::read_uint8(i); - p.ip = ip_str.str(); + p.ip = detail::read_v4_address(i).to_string(); p.port = detail::read_uint16(i); peer_list.push_back(p); } @@ -904,6 +911,22 @@ namespace libtorrent } } + if (entry const* ipv6_peers = e.find_key("peers6")) + { + std::string const& peers = ipv6_peers->string(); + for (std::string::const_iterator i = peers.begin(); + i != peers.end();) + { + if (std::distance(i, peers.end()) < 18) break; + + peer_entry p; + p.pid.clear(); + p.ip = detail::read_v6_address(i).to_string(); + p.port = detail::read_uint16(i); + peer_list.push_back(p); + } + } + // look for optional scrape info int complete = -1; int incomplete = -1; @@ -914,16 +937,16 @@ namespace libtorrent try { incomplete = e["incomplete"].integer(); } catch(type_error&) {} - requester().tracker_response(tracker_request(), peer_list, interval, complete + cb->tracker_response(tracker_request(), peer_list, interval, complete , incomplete); } catch(type_error& e) { - requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); } catch(std::runtime_error& e) { - requester().tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); } } diff --git a/libtorrent/src/identify_client.cpp b/libtorrent/src/identify_client.cpp index 26ddb51dc..7fa808f20 100755 --- a/libtorrent/src/identify_client.cpp +++ b/libtorrent/src/identify_client.cpp @@ -184,6 +184,7 @@ namespace , {"SB", "Swiftbit"} , {"SN", "ShareNet"} , {"SS", "SwarmScope"} + , {"ST", "SymTorrent"} , {"SZ", "Shareaza"} , {"S~", "Shareaza (beta)"} , {"T", "BitTornado"} @@ -194,12 +195,57 @@ namespace , {"U", "UPnP"} , {"UL", "uLeecher"} , {"UT", "uTorrent"} + , {"XL", "Xunlei"} , {"XT", "XanTorrent"} , {"XX", "Xtorrent"} , {"ZT", "ZipTorrent"} , {"lt", "rTorrent"} , {"pX", "pHoeniX"} , {"qB", "qBittorrent"} + , {"st", "SharkTorrent"} + }; + + struct generic_map_entry + { + int offset; + char const* id; + char const* name; + }; + // non-standard names + generic_map_entry generic_mappings[] = + { + {0, "Deadman Walking-", "Deadman"} + , {5, "Azureus", "Azureus 2.0.3.2"} + , {0, "DansClient", "XanTorrent"} + , {4, "btfans", "SimpleBT"} + , {0, "PRC.P---", "Bittorrent Plus! II"} + , {0, "P87.P---", "Bittorrent Plus!"} + , {0, "S587Plus", "Bittorrent Plus!"} + , {0, "martini", "Martini Man"} + , {0, "Plus---", "Bittorrent Plus"} + , {0, "turbobt", "TurboBT"} + , {0, "a00---0", "Swarmy"} + , {0, "a02---0", "Swarmy"} + , {0, "T00---0", "Teeweety"} + , {0, "BTDWV-", "Deadman Walking"} + , {2, "BS", "BitSpirit"} + , {0, "Pando-", "Pando"} + , {0, "LIME", "LimeWire"} + , {0, "btuga", "BTugaXP"} + , {0, "oernu", "BTugaXP"} + , {0, "Mbrst", "Burst!"} + , {0, "PEERAPP", "PeerApp"} + , {0, "Plus", "Plus!"} + , {0, "-Qt-", "Qt"} + , {0, "exbc", "BitComet"} + , {0, "DNA", "BitTorrent DNA"} + , {0, "-G3", "G3 Torrent"} + , {0, "-FG", "FlashGet"} + , {0, "-ML", "MLdonkey"} + , {0, "XBT", "XBT"} + , {0, "OP", "Opera"} + , {2, "RS", "Rufus"} + , {0, "AZ2500BT", "BitTyrant"} }; bool compare_id(map_entry const& lhs, map_entry const& rhs) @@ -281,30 +327,13 @@ namespace libtorrent // non standard encodings // ---------------------- - if (find_string(PID, "Deadman Walking-")) return "Deadman"; - if (find_string(PID + 5, "Azureus")) return "Azureus 2.0.3.2"; - if (find_string(PID, "DansClient")) return "XanTorrent"; - if (find_string(PID + 4, "btfans")) return "SimpleBT"; - if (find_string(PID, "PRC.P---")) return "Bittorrent Plus! II"; - if (find_string(PID, "P87.P---")) return "Bittorrent Plus!"; - if (find_string(PID, "S587Plus")) return "Bittorrent Plus!"; - if (find_string(PID, "martini")) return "Martini Man"; - if (find_string(PID, "Plus---")) return "Bittorrent Plus"; - if (find_string(PID, "turbobt")) return "TurboBT"; - if (find_string(PID, "a00---0")) return "Swarmy"; - if (find_string(PID, "a02---0")) return "Swarmy"; - if (find_string(PID, "T00---0")) return "Teeweety"; - if (find_string(PID, "BTDWV-")) return "Deadman Walking"; - if (find_string(PID + 2, "BS")) return "BitSpirit"; - if (find_string(PID, "btuga")) return "BTugaXP"; - if (find_string(PID, "oernu")) return "BTugaXP"; - if (find_string(PID, "Mbrst")) return "Burst!"; - if (find_string(PID, "Plus")) return "Plus!"; - if (find_string(PID, "-Qt-")) return "Qt"; - if (find_string(PID, "exbc")) return "BitComet"; - if (find_string(PID, "-G3")) return "G3 Torrent"; - if (find_string(PID, "XBT")) return "XBT"; - if (find_string(PID, "OP")) return "Opera"; + int num_generic_mappings = sizeof(generic_mappings) / sizeof(generic_mappings[0]); + + for (int i = 0; i < num_generic_mappings; ++i) + { + generic_map_entry const& e = generic_mappings[i]; + if (find_string(PID + e.offset, e.id)) return e.name; + } if (find_string(PID, "-BOW") && PID[7] == '-') return "Bits on Wheels " + std::string(PID + 4, PID + 7); diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp index 0c7d9d276..a3849ed69 100644 --- a/libtorrent/src/kademlia/closest_nodes.cpp +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include "libtorrent/assert.hpp" namespace libtorrent { namespace dht { diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index eda6cd864..c9908a163 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -237,6 +237,7 @@ namespace libtorrent { namespace dht try { if (e) return; + if (!m_socket.is_open()) return; time_duration d = m_dht.connection_timeout(); m_connection_timer.expires_from_now(d); m_connection_timer.async_wait(m_strand.wrap(bind(&dht_tracker::connection_timeout, self(), _1))); @@ -254,6 +255,7 @@ namespace libtorrent { namespace dht try { if (e) return; + if (!m_socket.is_open()) return; time_duration d = m_dht.refresh_timeout(); m_refresh_timer.expires_from_now(d); m_refresh_timer.async_wait(m_strand.wrap( @@ -276,8 +278,9 @@ namespace libtorrent { namespace dht try { if (e) return; + if (!m_socket.is_open()) return; m_timer.expires_from_now(minutes(tick_period)); - m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, this, _1))); + m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, self(), _1))); ptime now = time_now(); if (now - m_last_new_key > minutes(key_refresh)) @@ -388,6 +391,7 @@ namespace libtorrent { namespace dht try { if (error == asio::error::operation_aborted) return; + if (!m_socket.is_open()) return; int current_buffer = m_buffer; m_buffer = (m_buffer + 1) & 1; @@ -716,6 +720,7 @@ namespace libtorrent { namespace dht , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; + if (!m_socket.is_open()) return; add_node(host->endpoint()); } catch (std::exception&) @@ -734,6 +739,7 @@ namespace libtorrent { namespace dht , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; + if (!m_socket.is_open()) return; m_dht.add_router_node(host->endpoint()); } catch (std::exception&) diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp index 4ed413714..ad06c515d 100644 --- a/libtorrent/src/kademlia/node_id.cpp +++ b/libtorrent/src/kademlia/node_id.cpp @@ -34,10 +34,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/assert.hpp" using boost::bind; diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index 76f25548d..d7590ec47 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/io.hpp" #include "libtorrent/http_tracker_connection.hpp" + #include #include #include @@ -52,76 +53,22 @@ namespace libtorrent address_v4 guess_local_address(asio::io_service&); } -address_v4 lsd::lsd_multicast_address; -udp::endpoint lsd::lsd_multicast_endpoint; - lsd::lsd(io_service& ios, address const& listen_interface , peer_callback_t const& cb) : m_callback(cb) , m_retry_count(0) - , m_socket(ios) + , m_socket(ios, udp::endpoint(address_v4::from_string("239.192.152.143"), 6771) + , bind(&lsd::on_announce, this, _1, _2, _3)) , m_broadcast_timer(ios) , m_disabled(false) { - // Bittorrent Local discovery multicast address and port - lsd_multicast_address = address_v4::from_string("239.192.152.143"); - lsd_multicast_endpoint = udp::endpoint(lsd_multicast_address, 6771); - #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log.open("lsd.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - assert(lsd_multicast_address.is_multicast()); - rebind(listen_interface); } lsd::~lsd() {} -void lsd::rebind(address const& listen_interface) -{ - address_v4 local_ip = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) - { - local_ip = listen_interface.to_v4(); - } - - try - { - // the local interface hasn't changed - if (m_socket.is_open() - && m_socket.local_endpoint().address() == local_ip) - return; - - m_socket.close(); - - using namespace asio::ip::multicast; - - m_socket.open(udp::v4()); - m_socket.set_option(datagram_socket::reuse_address(true)); - m_socket.bind(udp::endpoint(local_ip, 6771)); - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "local ip: " << local_ip << std::endl; -#endif - - m_socket.set_option(join_group(lsd_multicast_address)); - m_socket.set_option(outbound_interface(local_ip)); - m_socket.set_option(enable_loopback(true)); - m_socket.set_option(hops(255)); - } - catch (std::exception& e) - { -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "socket multicast error " << e.what() - << ". disabling local service discovery" << std::endl; -#endif - m_disabled = true; - return; - } - m_disabled = false; - - setup_receive(); -} - void lsd::announce(sha1_hash const& ih, int listen_port) { if (m_disabled) return; @@ -136,8 +83,7 @@ void lsd::announce(sha1_hash const& ih, int listen_port) m_retry_count = 0; asio::error_code ec; - m_socket.send_to(asio::buffer(msg.c_str(), msg.size() - 1) - , lsd_multicast_endpoint, 0, ec); + m_socket.send(msg.c_str(), int(msg.size()), ec); if (ec) { m_disabled = true; @@ -157,8 +103,8 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try { if (e) return; - m_socket.send_to(asio::buffer(msg, msg.size() - 1) - , lsd_multicast_endpoint); + asio::error_code ec; + m_socket.send(msg.c_str(), int(msg.size()), ec); ++m_retry_count; if (m_retry_count >= 5) @@ -170,14 +116,13 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try catch (std::exception&) {} -void lsd::on_announce(asio::error_code const& e +void lsd::on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) { using namespace libtorrent::detail; - if (e) return; - char* p = m_receive_buffer; - char* end = m_receive_buffer + bytes_transferred; + char* p = buffer; + char* end = buffer + bytes_transferred; char* line = std::find(p, end, '\n'); for (char* i = p; i < line; ++i) *i = std::tolower(*i); #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) @@ -190,7 +135,6 @@ void lsd::on_announce(asio::error_code const& e m_log << time_now_string() << " *** assumed 'bt-search', ignoring" << std::endl; #endif - setup_receive(); return; } p = line + 1; @@ -223,25 +167,15 @@ void lsd::on_announce(asio::error_code const& e { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log << time_now_string() - << " *** incoming local announce " << m_remote.address() + << " *** incoming local announce " << from.address() << ":" << port << " ih: " << ih << std::endl; #endif // we got an announce, pass it on through the callback - try { m_callback(tcp::endpoint(m_remote.address(), port), ih); } + try { m_callback(tcp::endpoint(from.address(), port), ih); } catch (std::exception&) {} } - setup_receive(); } -void lsd::setup_receive() try -{ - assert(m_socket.is_open()); - m_socket.async_receive_from(asio::buffer(m_receive_buffer - , sizeof(m_receive_buffer)), m_remote, bind(&lsd::on_announce, this, _1, _2)); -} -catch (std::exception&) -{} - void lsd::close() { m_socket.close(); diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index 97635cdb9..0623b156f 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -523,7 +523,7 @@ namespace libtorrent { namespace if (num_blocks < 1) num_blocks = 1; assert(num_blocks <= 128); - int min_element = std::numeric_limits::max(); + int min_element = (std::numeric_limits::max)(); int best_index = 0; for (int i = 0; i < 256 - num_blocks + 1; ++i) { @@ -556,7 +556,7 @@ namespace libtorrent { namespace namespace libtorrent { - boost::shared_ptr create_metadata_plugin(torrent* t) + boost::shared_ptr create_metadata_plugin(torrent* t, void*) { return boost::shared_ptr(new metadata_plugin(*t)); } diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index 0a5932a56..bdcabce9a 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -32,11 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" -#include -#include #include #include +#include "libtorrent/natpmp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" + using boost::bind; using namespace libtorrent; diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index e999473da..981eca63d 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -1,132 +1,131 @@ -/* - -Copyright (c) 2007, Un Shyam -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - * Neither the name of the author nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -#ifndef TORRENT_DISABLE_ENCRYPTION - -#include -#include - -#include -#include - -#include "libtorrent/pe_crypto.hpp" - -namespace libtorrent { - - - // Set the prime P and the generator, generate local public key - DH_key_exchange::DH_key_exchange () - { - m_DH = DH_new (); - - m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); - m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); - m_DH->length = 160l; - - assert (sizeof(m_dh_prime) == DH_size(m_DH)); - - DH_generate_key (m_DH); // TODO Check != 0 - - assert (m_DH->pub_key); - - // DH can generate key sizes that are smaller than the size of - // P with exponentially decreasing probability, in which case - // the msb's of m_dh_local_key need to be zeroed - // appropriately. - int key_size = get_local_key_size(); - int len_dh = sizeof(m_dh_prime); // must equal DH_size(m_DH) - if (key_size != len_dh) - { - assert(key_size > 0 && key_size < len_dh); - - int pad_zero_size = len_dh - key_size; - std::fill(m_dh_local_key, m_dh_local_key + pad_zero_size, 0); - BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key + pad_zero_size); - } - else - BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key); // TODO Check return value - } - - DH_key_exchange::~DH_key_exchange () - { - assert (m_DH); - DH_free (m_DH); - } - - char const* DH_key_exchange::get_local_key () const - { - return m_dh_local_key; - } - - - // compute shared secret given remote public key - void DH_key_exchange::compute_secret (char const* remote_pubkey) - { - assert (remote_pubkey); - BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); - char dh_secret[96]; - - int secret_size = DH_compute_key ( (unsigned char*)dh_secret, - bn_remote_pubkey, m_DH); // TODO Check for errors - - if (secret_size != 96) - { - assert(secret_size < 96 && secret_size > 0); - std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); - } - std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); - - BN_free (bn_remote_pubkey); - } - - char const* DH_key_exchange::get_secret () const - { - return m_dh_secret; - } - - const unsigned char DH_key_exchange::m_dh_prime[96] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, - 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, - 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, - 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, - 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, - 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, - 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, - 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 - }; - - const unsigned char DH_key_exchange::m_dh_generator[1] = { 2 }; - -} // namespace libtorrent - -#endif // #ifndef TORRENT_DISABLE_ENCRYPTION - +/* + +Copyright (c) 2007, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_ENCRYPTION + +#include + +#include +#include + +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + + // Set the prime P and the generator, generate local public key + DH_key_exchange::DH_key_exchange () + { + m_DH = DH_new (); + + m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); + m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); + m_DH->length = 160l; + + assert (sizeof(m_dh_prime) == DH_size(m_DH)); + + DH_generate_key (m_DH); // TODO Check != 0 + + assert (m_DH->pub_key); + + // DH can generate key sizes that are smaller than the size of + // P with exponentially decreasing probability, in which case + // the msb's of m_dh_local_key need to be zeroed + // appropriately. + int key_size = get_local_key_size(); + int len_dh = sizeof(m_dh_prime); // must equal DH_size(m_DH) + if (key_size != len_dh) + { + assert(key_size > 0 && key_size < len_dh); + + int pad_zero_size = len_dh - key_size; + std::fill(m_dh_local_key, m_dh_local_key + pad_zero_size, 0); + BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key + pad_zero_size); + } + else + BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key); // TODO Check return value + } + + DH_key_exchange::~DH_key_exchange () + { + assert (m_DH); + DH_free (m_DH); + } + + char const* DH_key_exchange::get_local_key () const + { + return m_dh_local_key; + } + + + // compute shared secret given remote public key + void DH_key_exchange::compute_secret (char const* remote_pubkey) + { + assert (remote_pubkey); + BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); + char dh_secret[96]; + + int secret_size = DH_compute_key ( (unsigned char*)dh_secret, + bn_remote_pubkey, m_DH); // TODO Check for errors + + if (secret_size != 96) + { + assert(secret_size < 96 && secret_size > 0); + std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); + } + std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); + + BN_free (bn_remote_pubkey); + } + + char const* DH_key_exchange::get_secret () const + { + return m_dh_secret; + } + + const unsigned char DH_key_exchange::m_dh_prime[96] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 + }; + + const unsigned char DH_key_exchange::m_dh_generator[1] = { 2 }; + +} // namespace libtorrent + +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index b73e32896..25bc0ba63 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" +#include "libtorrent/assert.hpp" using boost::bind; using boost::shared_ptr; @@ -76,6 +77,8 @@ namespace libtorrent , m_timeout(m_ses.settings().peer_timeout) , m_last_piece(time_now()) , m_last_request(time_now()) + , m_last_incoming_request(min_time()) + , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) , m_current_send_buffer(0) @@ -93,6 +96,7 @@ namespace libtorrent , m_choked(true) , m_failed(false) , m_ignore_bandwidth_limits(false) + , m_have_all(false) , m_num_pieces(0) , m_desired_queue_size(2) , m_free_upload(0) @@ -108,8 +112,8 @@ namespace libtorrent , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_non_prioritized(false) - , m_upload_limit(resource_request::inf) - , m_download_limit(resource_request::inf) + , m_upload_limit(bandwidth_limit::inf) + , m_download_limit(bandwidth_limit::inf) , m_peer_info(peerinfo) , m_speed(slow) , m_connection_ticket(-1) @@ -153,6 +157,8 @@ namespace libtorrent , m_timeout(m_ses.settings().peer_timeout) , m_last_piece(time_now()) , m_last_request(time_now()) + , m_last_incoming_request(min_time()) + , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) , m_current_send_buffer(0) @@ -168,6 +174,7 @@ namespace libtorrent , m_choked(true) , m_failed(false) , m_ignore_bandwidth_limits(false) + , m_have_all(false) , m_num_pieces(0) , m_desired_queue_size(2) , m_free_upload(0) @@ -183,10 +190,11 @@ namespace libtorrent , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_non_prioritized(false) - , m_upload_limit(resource_request::inf) - , m_download_limit(resource_request::inf) + , m_upload_limit(bandwidth_limit::inf) + , m_download_limit(bandwidth_limit::inf) , m_peer_info(peerinfo) , m_speed(slow) + , m_connection_ticket(-1) , m_remote_bytes_dled(0) , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) @@ -251,6 +259,67 @@ namespace libtorrent } #endif + void peer_connection::send_allowed_set() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + int num_allowed_pieces = m_ses.settings().allowed_fast_set_size; + int num_pieces = t->torrent_file().num_pieces(); + + if (num_allowed_pieces >= num_pieces) + { + for (int i = 0; i < num_pieces; ++i) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> ALLOWED_FAST [ " << i << " ]\n"; +#endif + write_allow_fast(i); + m_accept_fast.insert(i); + } + return; + } + + std::string x; + address const& addr = m_remote.address(); + if (addr.is_v4()) + { + address_v4::bytes_type bytes = addr.to_v4().to_bytes(); + x.assign((char*)&bytes[0], bytes.size()); + } + else + { + address_v6::bytes_type bytes = addr.to_v6().to_bytes(); + x.assign((char*)&bytes[0], bytes.size()); + } + x.append((char*)&t->torrent_file().info_hash()[0], 20); + + sha1_hash hash = hasher(&x[0], x.size()).final(); + for (;;) + { + char* p = (char*)&hash[0]; + for (int i = 0; i < 5; ++i) + { + int piece = detail::read_uint32(p) % num_pieces; + if (m_accept_fast.find(piece) == m_accept_fast.end()) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> ALLOWED_FAST [ " << piece << " ]\n"; +#endif + write_allow_fast(piece); + m_accept_fast.insert(piece); + if (int(m_accept_fast.size()) >= num_allowed_pieces + || int(m_accept_fast.size()) == num_pieces) return; + } + } + hash = hasher((char*)&hash[0], 20).final(); + } + } + void peer_connection::init() { INVARIANT_CHECK; @@ -260,7 +329,7 @@ namespace libtorrent assert(t->valid_metadata()); assert(t->ready_for_connections()); - m_have_piece.resize(t->torrent_file().num_pieces(), false); + m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); // now that we have a piece_picker, // update it with this peers pieces @@ -274,7 +343,7 @@ namespace libtorrent // if this is a web seed. we don't have a peer_info struct if (m_peer_info) m_peer_info->seed = true; // if we're a seed too, disconnect - if (t->is_seed()) + if (t->is_finished()) { throw std::runtime_error("seed to seed connection redundant, disconnecting"); } @@ -331,7 +400,12 @@ namespace libtorrent { // dont announce during handshake if (in_handshake()) return; - + + // remove suggested pieces that we have + std::vector::iterator i = std::find( + m_suggested_pieces.begin(), m_suggested_pieces.end(), index); + if (i != m_suggested_pieces.end()) m_suggested_pieces.erase(i); + // optimization, don't send have messages // to peers that already have the piece if (!m_ses.settings().send_redundant_have @@ -378,8 +452,6 @@ namespace libtorrent void peer_connection::add_stat(size_type downloaded, size_type uploaded) { - INVARIANT_CHECK; - m_statistics.add_stat(downloaded, uploaded); } @@ -449,6 +521,7 @@ namespace libtorrent assert(t); assert(t->valid_metadata()); + torrent_info const& ti = t->torrent_file(); return p.piece >= 0 && p.piece < t->torrent_file().num_pieces() @@ -456,35 +529,30 @@ namespace libtorrent && p.start >= 0 && (p.length == t->block_size() || (p.length < t->block_size() - && p.piece == t->torrent_file().num_pieces()-1 - && p.start + p.length == t->torrent_file().piece_size(p.piece)) + && p.piece == ti.num_pieces()-1 + && p.start + p.length == ti.piece_size(p.piece)) || (m_request_large_blocks - && p.length <= t->torrent_file().piece_size(p.piece))) - && p.start + p.length <= t->torrent_file().piece_size(p.piece) + && p.length <= ti.piece_length() * m_prefer_whole_pieces == 0 ? + 1 : m_prefer_whole_pieces)) + && p.piece * size_type(ti.piece_length()) + p.start + p.length + <= ti.total_size() && (p.start % t->block_size() == 0); } - - struct disconnect_torrent - { - disconnect_torrent(boost::weak_ptr& t): m_t(&t) {} - ~disconnect_torrent() { if (m_t) m_t->reset(); } - void cancel() { m_t = 0; } - private: - boost::weak_ptr* m_t; - }; - + void peer_connection::attach_to_torrent(sha1_hash const& ih) { INVARIANT_CHECK; assert(!m_disconnecting); - m_torrent = m_ses.find_torrent(ih); - - boost::shared_ptr t = m_torrent.lock(); + assert(m_torrent.expired()); + boost::weak_ptr wpt = m_ses.find_torrent(ih); + boost::shared_ptr t = wpt.lock(); if (t && t->is_aborted()) { - m_torrent.reset(); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** the torrent has been aborted\n"; +#endif t.reset(); } @@ -492,12 +560,18 @@ namespace libtorrent { // we couldn't find the torrent! #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " couldn't find a torrent with the given info_hash: " << ih << "\n"; + (*m_logger) << " *** couldn't find a torrent with the given info_hash: " << ih << "\n"; + (*m_logger) << " torrents:\n"; + session_impl::torrent_map const& torrents = m_ses.m_torrents; + for (session_impl::torrent_map::const_iterator i = torrents.begin() + , end(torrents.end()); i != end; ++i) + { + (*m_logger) << " " << i->second->torrent_file().info_hash() << "\n"; + } #endif throw std::runtime_error("got info-hash that is not in our session"); } - disconnect_torrent disconnect(m_torrent); if (t->is_paused()) { // paused torrents will not accept @@ -508,21 +582,27 @@ namespace libtorrent throw std::runtime_error("connection rejected by paused torrent"); } + assert(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. t->attach_peer(this); + m_torrent = wpt; + + assert(!m_torrent.expired()); // if the torrent isn't ready to accept // connections yet, we'll have to wait with // our initialization if (t->ready_for_connections()) init(); + assert(!m_torrent.expired()); + // assume the other end has no pieces // if we don't have valid metadata yet, // leave the vector unallocated assert(m_num_pieces == 0); std::fill(m_have_piece.begin(), m_have_piece.end(), false); - disconnect.cancel(); + assert(!m_torrent.expired()); } // message handlers @@ -588,6 +668,117 @@ namespace libtorrent m_request_queue.clear(); } + bool match_request(peer_request const& r, piece_block const& b, int block_size) + { + if (b.piece_index != r.piece) return false; + if (b.block_index != r.start / block_size) return false; + if (r.start % block_size != 0) return false; + return true; + } + + // ----------------------------- + // -------- REJECT PIECE ------- + // ----------------------------- + + void peer_connection::incoming_reject_request(peer_request const& r) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_reject(r)) return; + } +#endif + + std::deque::iterator i = std::find_if( + m_download_queue.begin(), m_download_queue.end() + , bind(match_request, boost::cref(r), _1, t->block_size())); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== REJECT_PIECE [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; +#endif + + piece_block b(-1, 0); + if (i != m_download_queue.end()) + { + b = *i; + m_download_queue.erase(i); + } + else + { + i = std::find_if(m_request_queue.begin(), m_request_queue.end() + , bind(match_request, boost::cref(r), _1, t->block_size())); + + if (i != m_request_queue.end()) + { + b = *i; + m_request_queue.erase(i); + } + } + + if (b.piece_index != -1 && !t->is_seed()) + { + piece_picker& p = t->picker(); + p.abort_download(b); + } +#ifdef TORRENT_VERBOSE_LOGGING + else + { + (*m_logger) << time_now_string() + << " *** PIECE NOT IN REQUEST QUEUE\n"; + } +#endif + if (m_request_queue.empty()) + { + if (m_download_queue.size() < 2) + { + request_a_block(*t, *this); + } + send_block_requests(); + } + } + + // ----------------------------- + // -------- REJECT PIECE ------- + // ----------------------------- + + void peer_connection::incoming_suggest(int index) + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " <== SUGGEST_PIECE [ piece: " << index << " ]\n"; +#endif + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_suggest(index)) return; + } +#endif + + if (t->have_piece(index)) return; + + if (m_suggested_pieces.size() > 9) + m_suggested_pieces.erase(m_suggested_pieces.begin()); + m_suggested_pieces.push_back(index); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ** SUGGEST_PIECE [ piece: " << index << " added to set: " << m_suggested_pieces.size() << " ]\n"; +#endif + } + // ----------------------------- // ---------- UNCHOKE ---------- // ----------------------------- @@ -742,7 +933,7 @@ namespace libtorrent { assert(m_peer_info); m_peer_info->seed = true; - if (t->is_seed()) + if (t->is_finished()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -798,11 +989,12 @@ namespace libtorrent { m_have_piece = bitfield; m_num_pieces = std::count(bitfield.begin(), bitfield.end(), true); - - if (m_peer_info) m_peer_info->seed = true; + if (m_peer_info) m_peer_info->seed = (m_num_pieces == int(bitfield.size())); return; } + assert(t->valid_metadata()); + int num_pieces = std::count(bitfield.begin(), bitfield.end(), true); if (num_pieces == int(m_have_piece.size())) { @@ -812,7 +1004,7 @@ namespace libtorrent // if this is a web seed. we don't have a peer_info struct if (m_peer_info) m_peer_info->seed = true; // if we're a seed too, disconnect - if (t->is_seed()) + if (t->is_finished()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -906,6 +1098,7 @@ namespace libtorrent "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif + write_reject_request(r); return; } @@ -925,6 +1118,7 @@ namespace libtorrent "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif + write_reject_request(r); return; } @@ -947,11 +1141,20 @@ namespace libtorrent #endif // if we have choked the client // ignore the request - if (m_choked) - return; - - m_requests.push_back(r); - fill_send_buffer(); + if (m_choked && m_accept_fast.find(r.piece) == m_accept_fast.end()) + { + write_reject_request(r); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " *** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]\n"; +#endif + } + else + { + m_requests.push_back(r); + m_last_incoming_request = time_now(); + fill_send_buffer(); + } } else { @@ -968,6 +1171,7 @@ namespace libtorrent "block_limit: " << t->block_size() << " ]\n"; #endif + write_reject_request(r); ++m_num_invalid_requests; if (t->alerts().should_post(alert::debug)) @@ -977,7 +1181,7 @@ namespace libtorrent , t->get_handle() , m_remote , m_peer_id - , "peer sent an illegal piece request, ignoring")); + , "peer sent an illegal piece request")); } } } @@ -1131,11 +1335,8 @@ namespace libtorrent "request queue ***\n"; #endif t->received_redundant_data(p.length); - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } + request_a_block(*t, *this); + send_block_requests(); return; } @@ -1144,11 +1345,8 @@ namespace libtorrent { t->received_redundant_data(p.length); - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } + request_a_block(*t, *this); + send_block_requests(); return; } @@ -1205,15 +1403,11 @@ namespace libtorrent block_finished.block_index, block_finished.piece_index, "block finished")); } - if (!has_peer_choked() && !t->is_seed() && !m_torrent.expired()) + if (!t->is_seed() && !m_torrent.expired()) { // this is a free function defined in policy.cpp request_a_block(*t, *this); - try - { - send_block_requests(); - } - catch (std::exception const&) {} + send_block_requests(); } #ifndef NDEBUG @@ -1295,6 +1489,146 @@ namespace libtorrent #endif } + // ----------------------------- + // --------- HAVE ALL ---------- + // ----------------------------- + + void peer_connection::incoming_have_all() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== HAVE_ALL\n"; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_have_all()) return; + } +#endif + + m_have_all = true; + + if (m_peer_info) m_peer_info->seed = true; + + // if we don't have metadata yet + // just remember the bitmask + // don't update the piecepicker + // (since it doesn't exist yet) + if (!t->ready_for_connections()) + { + // TODO: this might need something more + // so that once we have the metadata + // we can construct a full bitfield + return; + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " *** THIS IS A SEED ***\n"; +#endif + + // if we're a seed too, disconnect + if (t->is_finished()) + throw protocol_error("seed to seed connection redundant, disconnecting"); + + assert(!m_have_piece.empty()); + std::fill(m_have_piece.begin(), m_have_piece.end(), true); + m_num_pieces = m_have_piece.size(); + + t->peer_has_all(); + if (!t->is_finished()) + t->get_policy().peer_is_interesting(*this); + } + + // ----------------------------- + // --------- HAVE NONE --------- + // ----------------------------- + + void peer_connection::incoming_have_none() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== HAVE_NONE\n"; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_have_none()) return; + } +#endif + + if (m_peer_info) m_peer_info->seed = false; + assert(!m_have_piece.empty() || !t->ready_for_connections()); + } + + // ----------------------------- + // ------- ALLOWED FAST -------- + // ----------------------------- + + void peer_connection::incoming_allowed_fast(int index) + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== ALLOWED_FAST [ " << index << " ]\n"; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_allowed_fast(index)) return; + } +#endif + + // if we already have the piece, we can + // ignore this message + if (t->valid_metadata() + && t->have_piece(index)) + return; + + m_allowed_fast.push_back(index); + + // if the peer has the piece and we want + // to download it, request it + if (int(m_have_piece.size()) > index + && m_have_piece[index] + && t->has_picker() + && t->picker().piece_priority(index) > 0) + { + t->get_policy().peer_is_interesting(*this); + } + } + + std::vector const& peer_connection::allowed_fast() + { + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + m_allowed_fast.erase(std::remove_if(m_allowed_fast.begin() + , m_allowed_fast.end(), bind(&torrent::have_piece, t, _1)) + , m_allowed_fast.end()); + + // TODO: sort the allowed fast set in priority order + return m_allowed_fast; + } + void peer_connection::add_request(piece_block const& block) { INVARIANT_CHECK; @@ -1308,10 +1642,11 @@ namespace libtorrent assert(block.block_index >= 0); assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); assert(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); + assert(!t->have_piece(block.piece_index)); piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); - std::string speedmsg; + char const* speedmsg = 0; if (speed == fast) { speedmsg = "fast"; @@ -1328,8 +1663,10 @@ namespace libtorrent state = piece_picker::slow; } - t->picker().mark_as_downloading(block, peer_info_struct(), state); - if (t->alerts().should_post(alert::info)) + if (!t->picker().mark_as_downloading(block, peer_info_struct(), state)) + return; + + if (t->alerts().should_post(alert::info)) { t->alerts().post_alert(block_downloading_alert(t->get_handle(), speedmsg, block.block_index, block.piece_index, "block downloading")); @@ -1380,7 +1717,7 @@ namespace libtorrent int block_offset = block.block_index * t->block_size(); int block_size - = std::min((int)t->torrent_file().piece_size(block.piece_index)-block_offset, + = (std::min)((int)t->torrent_file().piece_size(block.piece_index)-block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1403,6 +1740,8 @@ namespace libtorrent { INVARIANT_CHECK; + assert(!m_peer_info || !m_peer_info->optimistically_unchoked); + if (m_choked) return; write_choke(); m_choked = true; @@ -1421,15 +1760,8 @@ namespace libtorrent { INVARIANT_CHECK; -#ifndef NDEBUG - // TODO: once the policy lowers the interval for optimistic - // unchoke, increase this value that interval - // this condition cannot be guaranteed since if peers disconnect - // a new one will be unchoked ignoring when it was last choked - //assert(time_now() - m_last_choke > seconds(9)); -#endif - if (!m_choked) return; + m_last_unchoke = time_now(); write_unchoke(); m_choked = false; @@ -1470,13 +1802,9 @@ namespace libtorrent { INVARIANT_CHECK; - if (has_peer_choked()) return; - boost::shared_ptr t = m_torrent.lock(); assert(t); - assert(!has_peer_choked()); - if ((int)m_download_queue.size() >= m_desired_queue_size) return; while (!m_request_queue.empty() @@ -1485,7 +1813,7 @@ namespace libtorrent piece_block block = m_request_queue.front(); int block_offset = block.block_index * t->block_size(); - int block_size = std::min((int)t->torrent_file().piece_size( + int block_size = (std::min)((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1509,23 +1837,29 @@ namespace libtorrent // blocks that are in the same piece into larger requests if (m_request_large_blocks) { - while (!m_request_queue.empty() - && m_request_queue.front().piece_index == r.piece - && m_request_queue.front().block_index == block.block_index + 1) + int blocks_per_piece = t->torrent_file().piece_length() / t->block_size(); + + while (!m_request_queue.empty()) { + // check to see if this block is connected to the previous one + // if it is, merge them, otherwise, break this merge loop + piece_block const& front = m_request_queue.front(); + if (front.piece_index * blocks_per_piece + front.block_index + != block.piece_index * blocks_per_piece + block.block_index + 1) + break; block = m_request_queue.front(); m_request_queue.pop_front(); m_download_queue.push_back(block); -/* + #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() - << " *** REQUEST-QUEUE** [ " + << " *** MERGING REQUEST ** [ " "piece: " << block.piece_index << " | " "block: " << block.block_index << " ]\n"; #endif -*/ + block_offset = block.block_index * t->block_size(); - block_size = std::min((int)t->torrent_file().piece_size( + block_size = (std::min)((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); assert(block_size > 0); assert(block_size <= t->block_size()); @@ -1618,7 +1952,6 @@ namespace libtorrent } t->remove_peer(this); - m_torrent.reset(); } @@ -1628,7 +1961,7 @@ namespace libtorrent void peer_connection::set_upload_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = resource_request::inf; + if (limit == -1) limit = (std::numeric_limits::max)(); if (limit < 10) limit = 10; m_upload_limit = limit; m_bandwidth_limit[upload_channel].throttle(m_upload_limit); @@ -1637,7 +1970,7 @@ namespace libtorrent void peer_connection::set_download_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = resource_request::inf; + if (limit == -1) limit = (std::numeric_limits::max)(); if (limit < 10) limit = 10; m_download_limit = limit; m_bandwidth_limit[download_channel].throttle(m_download_limit); @@ -1655,7 +1988,7 @@ namespace libtorrent // if we have an infinite ratio, just say we have downloaded // much more than we have uploaded. And we'll keep uploading. if (ratio == 0.f) - return std::numeric_limits::max(); + return (std::numeric_limits::max)(); return m_free_upload + static_cast(m_statistics.total_payload_download() * ratio) @@ -1703,8 +2036,9 @@ namespace libtorrent p.load_balancing = total_free_upload(); - p.download_queue_length = (int)download_queue().size(); - p.upload_queue_length = (int)upload_queue().size(); + p.download_queue_length = int(download_queue().size() + m_request_queue.size()); + p.target_dl_queue_length = int(desired_queue_size()); + p.upload_queue_length = int(upload_queue().size()); if (boost::optional ret = downloading_piece_progress()) { @@ -1724,7 +2058,7 @@ namespace libtorrent p.pieces = get_bitfield(); ptime now = time_now(); p.last_request = now - m_last_request; - p.last_active = now - std::max(m_last_sent, m_last_receive); + p.last_active = now - (std::max)(m_last_sent, m_last_receive); // this will set the flags so that we can update them later p.flags = 0; @@ -1737,6 +2071,7 @@ namespace libtorrent p.failcount = peer_info_struct()->failcount; p.num_hashfails = peer_info_struct()->hashfails; p.flags |= peer_info_struct()->on_parole ? peer_info::on_parole : 0; + p.flags |= peer_info_struct()->optimistically_unchoked ? peer_info::optimistic_unchoke : 0; p.remote_dl_rate = m_remote_dl_rate; } else @@ -1772,10 +2107,13 @@ namespace libtorrent if (m_packet_size >= m_recv_pos) m_recv_buffer.resize(m_packet_size); } - void peer_connection::second_tick(float tick_interval) + void peer_connection::second_tick(float tick_interval) throw() { INVARIANT_CHECK; + try + { + ptime now(time_now()); boost::shared_ptr t = m_torrent.lock(); @@ -1854,11 +2192,8 @@ namespace libtorrent m_assume_fifo = true; - if (!has_peer_choked()) - { - request_a_block(*t, *this); - send_block_requests(); - } + request_a_block(*t, *this); + send_block_requests(); } } @@ -1869,7 +2204,7 @@ namespace libtorrent // maintain the share ratio given by m_ratio // with all peers. - if (t->is_seed() || is_choked() || t->ratio() == 0.0f) + if (t->is_finished() || is_choked() || t->ratio() == 0.0f) { // if we have downloaded more than one piece more // than we have uploaded OR if we are a seed @@ -1891,14 +2226,14 @@ namespace libtorrent if (t->ratio() != 1.f) soon_downloaded = (size_type)(soon_downloaded*(double)t->ratio()); - double upload_speed_limit = std::min((soon_downloaded - have_uploaded + double upload_speed_limit = (std::min)((soon_downloaded - have_uploaded + bias) / break_even_time, double(m_upload_limit)); - upload_speed_limit = std::min(upload_speed_limit, - (double)std::numeric_limits::max()); + upload_speed_limit = (std::min)(upload_speed_limit, + (double)(std::numeric_limits::max)()); m_bandwidth_limit[upload_channel].throttle( - std::min(std::max((int)upload_speed_limit, 20) + (std::min)((std::max)((int)upload_speed_limit, 20) , m_upload_limit)); } @@ -1917,43 +2252,14 @@ namespace libtorrent } fill_send_buffer(); -/* - size_type diff = share_diff(); - - enum { block_limit = 2 }; // how many blocks difference is considered unfair - - // if the peer has been choked, send the current piece - // as fast as possible - if (diff > block_limit*m_torrent->block_size() || m_torrent->is_seed() || is_choked()) - { - // if we have downloaded more than one piece more - // than we have uploaded OR if we are a seed - // have an unlimited upload rate - m_ul_bandwidth_quota.wanted = std::numeric_limits::max(); } - else + catch (std::exception& e) { - float ratio = m_torrent->ratio(); - // if we have downloaded too much, response with an - // upload rate of 10 kB/s more than we dowlload - // if we have uploaded too much, send with a rate of - // 10 kB/s less than we receive - int bias = 0; - if (diff > -block_limit*m_torrent->block_size()) - { - bias = static_cast(m_statistics.download_rate() * ratio) / 2; - if (bias < 10*1024) bias = 10*1024; - } - else - { - bias = -static_cast(m_statistics.download_rate() * ratio) / 2; - } - m_ul_bandwidth_quota.wanted = static_cast(m_statistics.download_rate()) + bias; - - // the maximum send_quota given our download rate from this peer - if (m_ul_bandwidth_quota.wanted < 256) m_ul_bandwidth_quota.wanted = 256; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "**ERROR**: " << e.what() << "\n"; +#endif + m_ses.connection_failed(m_socket, remote(), e.what()); } -*/ } void peer_connection::fill_send_buffer() @@ -1988,20 +2294,6 @@ namespace libtorrent m_reading_bytes += r.length; m_requests.erase(m_requests.begin()); -/* - if (m_requests.empty() - && m_num_invalid_requests > 0 - && is_peer_interested() - && !is_seed()) - { - // this will make the peer clear - // its download queue and re-request - // pieces. Hopefully it will not - // send invalid requests then - send_choke(); - send_unchoke(); - } -*/ } } @@ -2542,9 +2834,14 @@ namespace libtorrent void peer_connection::check_invariant() const { if (m_peer_info) + { assert(m_peer_info->connection == this || m_peer_info->connection == 0); - + + if (m_peer_info->optimistically_unchoked) + assert(!is_choked()); + } + boost::shared_ptr t = m_torrent.lock(); if (!t) { @@ -2558,6 +2855,8 @@ namespace libtorrent return; } + assert(t->connection_for(remote()) != 0 || m_in_constructor); + if (!m_in_constructor && t->connection_for(remote()) != this && !m_ses.settings().allow_multiple_connections_per_ip) { @@ -2617,11 +2916,6 @@ namespace libtorrent // TODO: the timeout should be called by an event INVARIANT_CHECK; -#ifndef NDEBUG - // allow step debugging without timing out - return false; -#endif - ptime now(time_now()); // if the socket is still connecting, don't @@ -2632,9 +2926,24 @@ namespace libtorrent // if the peer hasn't said a thing for a certain // time, it is considered to have timed out time_duration d; - d = time_now() - m_last_receive; + d = now - m_last_receive; if (d > seconds(m_timeout)) return true; + // if it takes more than 5 seconds to receive + // handshake, disconnect + if (in_handshake() && d > seconds(5)) return true; + + // disconnect peers that we unchoked, but + // they didn't send a request within 20 seconds. + // but only if we're a seed + boost::shared_ptr t = m_torrent.lock(); + d = now - (std::max)(m_last_unchoke, m_last_incoming_request); + if (m_requests.empty() + && !m_choked + && m_peer_interested + && t && t->is_finished() + && d > seconds(20)) return true; + // TODO: as long as we have less than 95% of the // global (or local) connection limit, connections should // never time out for another reason diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index ddc2c2f5a..398573d33 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -167,6 +167,7 @@ namespace libtorrent return; assert(sequenced_download_threshold > 0); + if (sequenced_download_threshold <= 0) return; int old_limit = m_sequenced_download_threshold; m_sequenced_download_threshold = sequenced_download_threshold; @@ -191,22 +192,22 @@ namespace libtorrent // the previous max availability was reached // we need to shuffle that bucket, if not, we // don't have to do anything - if (int(m_piece_info.size()) > old_limit) + if (int(m_piece_info.size()) > old_limit * 2) { - info_t& in = m_piece_info[old_limit]; + info_t& in = m_piece_info[old_limit * 2]; std::random_shuffle(in.begin(), in.end()); int c = 0; for (info_t::iterator i = in.begin() , end(in.end()); i != end; ++i) { m_piece_map[*i].index = c++; - assert(m_piece_map[*i].priority(old_limit) == old_limit); + assert(m_piece_map[*i].priority(old_limit) == old_limit * 2); } } } - else if (int(m_piece_info.size()) > sequenced_download_threshold) + else if (int(m_piece_info.size()) > sequenced_download_threshold * 2) { - info_t& in = m_piece_info[sequenced_download_threshold]; + info_t& in = m_piece_info[sequenced_download_threshold * 2]; std::sort(in.begin(), in.end()); int c = 0; for (info_t::iterator i = in.begin() @@ -214,7 +215,7 @@ namespace libtorrent { m_piece_map[*i].index = c++; assert(m_piece_map[*i].priority( - sequenced_download_threshold) == sequenced_download_threshold); + sequenced_download_threshold) == sequenced_download_threshold * 2); } } } @@ -262,8 +263,23 @@ namespace libtorrent } m_downloads.erase(i); } + #ifndef NDEBUG + void piece_picker::verify_pick(std::vector const& picked + , std::vector const& bitfield) const + { + assert(bitfield.size() == m_piece_map.size()); + for (std::vector::const_iterator i = picked.begin() + , end(picked.end()); i != end; ++i) + { + assert(i->piece_index >= 0); + assert(i->piece_index < int(bitfield.size())); + assert(bitfield[i->piece_index]); + assert(!m_piece_map[i->piece_index].have()); + } + } + void piece_picker::check_invariant(const torrent* t) const { assert(sizeof(piece_pos) == 4); @@ -394,6 +410,7 @@ namespace libtorrent assert(!t->have_piece(index)); int prio = i->priority(m_sequenced_download_threshold); + assert(prio < int(m_piece_info.size())); if (prio > 0) { const std::vector& vec = m_piece_info[prio]; @@ -447,7 +464,7 @@ namespace libtorrent if (i->have()) ++peer_count; if (min_availability > peer_count) { - min_availability = i->peer_count; + min_availability = peer_count; fraction_part += integer_part; integer_part = 1; } @@ -638,12 +655,13 @@ namespace libtorrent if (dp == m_downloads.begin()) return; int complete = dp->writing + dp->finished; for (std::vector::iterator i = dp, j(dp-1); - i != m_downloads.begin() && j != m_downloads.begin(); --i, --j) + i != m_downloads.begin(); --i, --j) { assert(j >= m_downloads.begin()); if (j->finished + j->writing >= complete) return; using std::swap; swap(*j, *i); + if (j == m_downloads.begin()) break; } } @@ -739,6 +757,7 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); + assert(prev_prio < int(m_piece_info.size())); ++i->peer_count; // if the assumption that the priority would // increase by 2 when increasing the availability @@ -828,6 +847,8 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); + assert(prev_prio < int(m_piece_info.size())); + assert(pushed_out_index < int(m_piece_info.size())); assert(i->peer_count > 0); --i->peer_count; // if the assumption that the priority would @@ -879,6 +900,7 @@ namespace libtorrent piece_pos& p = m_piece_map[i]; int index = p.index; int prev_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); assert(p.peer_count < piece_pos::max_peer_count); p.peer_count++; @@ -913,6 +935,7 @@ namespace libtorrent piece_pos& p = m_piece_map[i]; int prev_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); int index = p.index; assert(p.peer_count > 0); @@ -937,6 +960,7 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; int info_index = p.index; int priority = p.priority(m_sequenced_download_threshold); + assert(priority < int(m_piece_info.size())); assert(p.downloading == 1); assert(!p.have()); @@ -980,6 +1004,7 @@ namespace libtorrent if (new_piece_priority == int(p.piece_priority)) return false; int prev_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); bool ret = false; if (new_piece_priority == piece_pos::filter_priority @@ -1003,6 +1028,7 @@ namespace libtorrent p.piece_priority = new_piece_priority; int new_priority = p.priority(m_sequenced_download_threshold); + assert(prev_priority < int(m_piece_info.size())); if (new_priority == prev_priority) return false; @@ -1068,8 +1094,9 @@ namespace libtorrent // or slow once they're started. void piece_picker::pick_pieces(const std::vector& pieces , std::vector& interesting_blocks - , int num_blocks, bool prefer_whole_pieces - , void* peer, piece_state_t speed, bool rarest_first) const + , int num_blocks, int prefer_whole_pieces + , void* peer, piece_state_t speed, bool rarest_first + , bool on_parole, std::vector const& suggested_pieces) const { TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(num_blocks > 0); @@ -1085,59 +1112,84 @@ namespace libtorrent // blocks belonging to a piece that others have // downloaded to std::vector backup_blocks; + // suggested pieces for each vector is put in this vector + std::vector suggested_bucket; + const std::vector empty_vector; // When prefer_whole_pieces is set (usually set when downloading from // fast peers) the partial pieces will not be prioritized, but actually // ignored as long as possible. All blocks found in downloading // pieces are regarded as backup blocks - bool ignore_downloading_pieces = false; - if (prefer_whole_pieces) + + num_blocks = add_blocks_downloading(pieces + , interesting_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer, speed, on_parole); + + if (num_blocks <= 0) return; + + if (rarest_first) { - std::vector downloading_pieces; - downloading_pieces.reserve(m_downloads.size()); - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) + // this loop will loop from pieces with priority 1 and up + // until we either reach the end of the piece list or + // has filled the interesting_blocks with num_blocks + // blocks. + + // +1 is to ignore pieces that no peer has. The bucket with index 0 contains + // pieces that 0 other peers have. bucket will point to a bucket with + // pieces with the same priority. It will be iterated in priority + // order (high priority/rare pices first). The content of each + // bucket is randomized + for (std::vector >::const_iterator bucket + = m_piece_info.begin() + 1; num_blocks > 0 && bucket != m_piece_info.end(); + ++bucket) { - downloading_pieces.push_back(i->index); + if (bucket->empty()) continue; + if (!suggested_pieces.empty()) + { + int bucket_index = bucket - m_piece_info.begin(); + suggested_bucket.clear(); + for (std::vector::const_iterator i = suggested_pieces.begin() + , end(suggested_pieces.end()); i != end; ++i) + { + assert(*i >= 0); + assert(*i < int(m_piece_map.size())); + if (!can_pick(*i, pieces)) continue; + if (m_piece_map[*i].priority(m_sequenced_download_threshold) == bucket_index) + suggested_bucket.push_back(*i); + } + if (!suggested_bucket.empty()) + { + num_blocks = add_blocks(suggested_bucket, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, empty_vector); + if (num_blocks == 0) break; + } + } + num_blocks = add_blocks(*bucket, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, suggested_bucket); + assert(num_blocks >= 0); } - add_interesting_blocks(downloading_pieces, pieces - , backup_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); - ignore_downloading_pieces = true; } - - // this loop will loop from pieces with priority 1 and up - // until we either reach the end of the piece list or - // has filled the interesting_blocks with num_blocks - // blocks. - - // +1 is to ignore pieces that no peer has. The bucket with index 0 contains - // pieces that 0 other peers have. bucket will point to a bucket with - // pieces with the same priority. It will be iterated in priority - // order (high priority/rare pices first). The content of each - // bucket is randomized - for (std::vector >::const_iterator bucket - = m_piece_info.begin() + 1; bucket != m_piece_info.end(); - ++bucket) + else { - if (bucket->empty()) continue; - num_blocks = add_interesting_blocks(*bucket, pieces - , interesting_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer, speed, ignore_downloading_pieces); - assert(num_blocks >= 0); - if (num_blocks == 0) return; - if (rarest_first) continue; - // we're not using rarest first (only for the first // bucket, since that's where the currently downloading // pieces are) + int start_piece = rand() % m_piece_map.size(); + + // if we have suggested pieces, try to find one of those instead + for (std::vector::const_iterator i = suggested_pieces.begin() + , end(suggested_pieces.end()); i != end; ++i) + { + if (!can_pick(*i, pieces)) continue; + start_piece = *i; + break; + } + int piece = start_piece; while (num_blocks > 0) { - int start_piece = rand() % m_piece_map.size(); - int piece = start_piece; - while (!pieces[piece] - || m_piece_map[piece].index == piece_pos::we_have_index - || m_piece_map[piece].priority(m_sequenced_download_threshold) < 2) + while (!can_pick(piece, pieces)) { ++piece; if (piece == int(m_piece_map.size())) piece = 0; @@ -1145,27 +1197,45 @@ namespace libtorrent if (piece == start_piece) return; } - assert(m_piece_map[piece].downloading == false); - - int num_blocks_in_piece = blocks_in_piece(piece); - - if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) - num_blocks_in_piece = num_blocks; - for (int j = 0; j < num_blocks_in_piece; ++j) - interesting_blocks.push_back(piece_block(piece, j)); - num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); + int start, end; + boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces); + for (int k = start; k < end; ++k) + { + assert(m_piece_map[piece].downloading == false); + assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + int num_blocks_in_piece = blocks_in_piece(k); + if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.push_back(piece_block(k, j)); + --num_blocks; + } + } + piece = end; + if (piece == int(m_piece_map.size())) piece = 0; + // could not find any more pieces + if (piece == start_piece) return; } - if (num_blocks == 0) return; - break; + } - assert(num_blocks > 0); + if (num_blocks <= 0) return; if (!backup_blocks.empty()) interesting_blocks.insert(interesting_blocks.end() , backup_blocks.begin(), backup_blocks.end()); } + bool piece_picker::can_pick(int piece, std::vector const& bitmask) const + { + assert(piece >= 0 && piece < int(m_piece_map.size())); + return bitmask[piece] + && !m_piece_map[piece].have() + && !m_piece_map[piece].downloading + && !m_piece_map[piece].filtered(); + } + void piece_picker::clear_peer(void* peer) { for (std::vector::iterator i = m_block_info.begin() @@ -1203,17 +1273,12 @@ namespace libtorrent } } - int piece_picker::add_interesting_blocks(std::vector const& piece_list + int piece_picker::add_blocks(std::vector const& piece_list , std::vector const& pieces , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, bool prefer_whole_pieces - , void* peer, piece_state_t speed - , bool ignore_downloading_pieces) const + , int num_blocks, int prefer_whole_pieces + , void* peer, std::vector const& ignore) const { - // if we have less than 1% of the pieces, ignore speed priorities and just try - // to finish any downloading piece - bool ignore_speed_categories = (m_num_have * 100 / m_piece_map.size()) < 1; for (std::vector::const_iterator i = piece_list.begin(); i != piece_list.end(); ++i) { @@ -1224,111 +1289,268 @@ namespace libtorrent // skip it if (!pieces[*i]) continue; + // ignore pieces found in the ignore list + if (std::find(ignore.begin(), ignore.end(), *i) != ignore.end()) continue; + + // skip the piece is the priority is 0 + assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + int num_blocks_in_piece = blocks_in_piece(*i); - if (m_piece_map[*i].downloading == 1) + assert(m_piece_map[*i].downloading == 0); + assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + + // pick a new piece + if (prefer_whole_pieces == 0) { - if (ignore_downloading_pieces) continue; - std::vector::const_iterator p - = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); - assert(p != m_downloads.end()); - - // is true if all the other pieces that are currently - // requested from this piece are from the same - // peer as 'peer'. - bool exclusive; - bool exclusive_active; - boost::tie(exclusive, exclusive_active) - = requested_from(*p, num_blocks_in_piece, peer); - - // this means that this partial piece has - // been downloaded/requested partially from - // another peer that isn't us. And since - // we prefer whole pieces, add this piece's - // blocks to the backup list. If the prioritized - // blocks aren't enough, blocks from this list - // will be picked. - if (prefer_whole_pieces && !exclusive) - { - for (int j = 0; j < num_blocks_in_piece; ++j) - { - block_info const& info = p->info[j]; - if (info.state == block_info::state_finished - || info.state == block_info::state_writing) - continue; - if (info.state == block_info::state_requested - && info.peer == peer) continue; - backup_blocks.push_back(piece_block(*i, j)); - } - continue; - } - - for (int j = 0; j < num_blocks_in_piece; ++j) - { - // ignore completed blocks - block_info const& info = p->info[j]; - if (info.state == block_info::state_finished - || info.state == block_info::state_writing) - continue; - // ignore blocks requested from this peer already - if (info.state == block_info::state_requested - && info.peer == peer) - continue; - // if the piece is fast and the peer is slow, or vice versa, - // add the block as a backup. - // override this behavior if all the other blocks - // have been requested from the same peer or - // if the state of the piece is none (the - // piece will in that case change state). - if (p->state != none && p->state != speed - && !exclusive_active - && !ignore_speed_categories) - { - backup_blocks.push_back(piece_block(*i, j)); - continue; - } - // this block is interesting (we don't have it - // yet). But it may already have been requested - // from another peer. We have to add it anyway - // to allow the requester to determine if the - // block should be requested from more than one - // peer. If it is being downloaded, we continue - // to look for blocks until we have num_blocks - // blocks that have not been requested from any - // other peer. - if (p->info[j].state == block_info::state_none) - { - interesting_blocks.push_back(piece_block(*i, j)); - // we have found a block that's free to download - num_blocks--; - // if we prefer whole pieces, continue picking from this - // piece even though we have num_blocks - if (prefer_whole_pieces) continue; - assert(num_blocks >= 0); - if (num_blocks == 0) return num_blocks; - } - else - { - backup_blocks.push_back(piece_block(*i, j)); - } - } - assert(num_blocks >= 0 || prefer_whole_pieces); - if (num_blocks < 0) num_blocks = 0; - } - else - { - if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + if (num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; for (int j = 0; j < num_blocks_in_piece; ++j) interesting_blocks.push_back(piece_block(*i, j)); - num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); + num_blocks -= num_blocks_in_piece; + } + else + { + int start, end; + boost::tie(start, end) = expand_piece(*i, prefer_whole_pieces, pieces); + for (int k = start; k < end; ++k) + { + assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + num_blocks_in_piece = blocks_in_piece(k); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.push_back(piece_block(k, j)); + --num_blocks; + } + } + } + if (num_blocks <= 0) + { +#ifndef NDEBUG + verify_pick(interesting_blocks, pieces); +#endif + return 0; } - assert(num_blocks >= 0); - if (num_blocks == 0) return num_blocks; } +#ifndef NDEBUG + verify_pick(interesting_blocks, pieces); +#endif return num_blocks; } + int piece_picker::add_blocks_downloading(std::vector const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, int prefer_whole_pieces + , void* peer, piece_state_t speed, bool on_parole) const + { + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + + int num_blocks_in_piece = blocks_in_piece(i->index); + + // is true if all the other pieces that are currently + // requested from this piece are from the same + // peer as 'peer'. + bool exclusive; + bool exclusive_active; + boost::tie(exclusive, exclusive_active) + = requested_from(*i, num_blocks_in_piece, peer); + + // peers on parole are only allowed to pick blocks from + // pieces that only they have downloaded/requested from + if (on_parole && !exclusive) continue; + + if (prefer_whole_pieces > 0 && !exclusive_active) continue; + + // don't pick too many back-up blocks + if (i->state != none + && i->state != speed + && !exclusive_active + && int(backup_blocks.size()) >= num_blocks) + continue; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + // ignore completed blocks and already requested blocks + block_info const& info = i->info[j]; + if (info.state != block_info::state_none) + continue; + + assert(i->info[j].state == block_info::state_none); + + // if the piece is fast and the peer is slow, or vice versa, + // add the block as a backup. + // override this behavior if all the other blocks + // have been requested from the same peer or + // if the state of the piece is none (the + // piece will in that case change state). + if (i->state != none && i->state != speed + && !exclusive_active) + { + backup_blocks.push_back(piece_block(i->index, j)); + continue; + } + + // this block is interesting (we don't have it + // yet). + interesting_blocks.push_back(piece_block(i->index, j)); + // we have found a block that's free to download + num_blocks--; + // if we prefer whole pieces, continue picking from this + // piece even though we have num_blocks + if (prefer_whole_pieces > 0) continue; + assert(num_blocks >= 0); + if (num_blocks <= 0) break; + } + if (num_blocks <= 0) break; + } + + assert(num_blocks >= 0 || prefer_whole_pieces > 0); + +#ifndef NDEBUG + verify_pick(interesting_blocks, pieces); + verify_pick(backup_blocks, pieces); +#endif + + if (num_blocks <= 0) return 0; + if (on_parole) return num_blocks; + + int to_copy; + if (prefer_whole_pieces == 0) + to_copy = (std::min)(int(backup_blocks.size()), num_blocks); + else + to_copy = int(backup_blocks.size()); + + interesting_blocks.insert(interesting_blocks.end() + , backup_blocks.begin(), backup_blocks.begin() + to_copy); + num_blocks -= to_copy; + backup_blocks.clear(); + + if (num_blocks <= 0) return 0; + + if (prefer_whole_pieces > 0) + { + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + int num_blocks_in_piece = blocks_in_piece(i->index); + bool exclusive; + bool exclusive_active; + boost::tie(exclusive, exclusive_active) + = requested_from(*i, num_blocks_in_piece, peer); + + if (exclusive_active) continue; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = i->info[j]; + if (info.state != block_info::state_none) continue; + backup_blocks.push_back(piece_block(i->index, j)); + } + } + } + + if (int(backup_blocks.size()) >= num_blocks) return num_blocks; + + +#ifndef NDEBUG +// make sure that we at this point has added requests to all unrequested blocks +// in all downloading pieces + + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + + int num_blocks_in_piece = blocks_in_piece(i->index); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = i->info[j]; + if (info.state != block_info::state_none) continue; + std::vector::iterator k = std::find( + interesting_blocks.begin(), interesting_blocks.end() + , piece_block(i->index, j)); + if (k != interesting_blocks.end()) continue; + + k = std::find(backup_blocks.begin() + , backup_blocks.end(), piece_block(i->index, j)); + if (k != backup_blocks.end()) continue; + + std::cerr << "interesting blocks:" << std::endl; + for (k = interesting_blocks.begin(); k != interesting_blocks.end(); ++k) + std::cerr << "(" << k->piece_index << ", " << k->block_index << ") "; + std::cerr << std::endl; + std::cerr << "backup blocks:" << std::endl; + for (k = backup_blocks.begin(); k != backup_blocks.end(); ++k) + std::cerr << "(" << k->piece_index << ", " << k->block_index << ") "; + std::cerr << std::endl; + std::cerr << "num_blocks: " << num_blocks << std::endl; + + for (std::vector::const_iterator l = m_downloads.begin() + , end(m_downloads.end()); l != end; ++l) + { + std::cerr << l->index << " : "; + int num_blocks_in_piece = blocks_in_piece(l->index); + for (int m = 0; m < num_blocks_in_piece; ++m) + std::cerr << l->info[m].state; + std::cerr << std::endl; + } + + assert(false); + } + } +#endif + + for (std::vector::const_iterator i = m_downloads.begin() + , end(m_downloads.end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + + int num_blocks_in_piece = blocks_in_piece(i->index); + + // fill in with blocks requested from other peers + // as backups + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = i->info[j]; + if (info.state != block_info::state_requested + || info.peer == peer) + continue; + backup_blocks.push_back(piece_block(i->index, j)); + } + } +#ifndef NDEBUG + verify_pick(backup_blocks, pieces); +#endif + return num_blocks; + } + + std::pair piece_picker::expand_piece(int piece, int whole_pieces + , std::vector const& have) const + { + if (whole_pieces == 0) return std::make_pair(piece, piece + 1); + + int start = piece - 1; + int lower_limit = piece - whole_pieces; + if (lower_limit < -1) lower_limit = -1; + while (start > lower_limit + && can_pick(start, have)) + --start; + ++start; + assert(start >= 0); + int end = piece + 1; + int upper_limit = start + whole_pieces; + if (upper_limit > int(m_piece_map.size())) upper_limit = int(m_piece_map.size()); + while (end < upper_limit + && can_pick(end, have)) + ++end; + return std::make_pair(start, end); + } + bool piece_picker::is_piece_finished(int index) const { assert(index < (int)m_piece_map.size()); @@ -1405,7 +1627,7 @@ namespace libtorrent } - void piece_picker::mark_as_downloading(piece_block block + bool piece_picker::mark_as_downloading(piece_block block , void* peer, piece_state_t state) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1414,11 +1636,14 @@ namespace libtorrent assert(block.block_index >= 0); assert(block.piece_index < (int)m_piece_map.size()); assert(block.block_index < blocks_in_piece(block.piece_index)); + assert(!m_piece_map[block.piece_index].have()); piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { int prio = p.priority(m_sequenced_download_threshold); + assert(prio < int(m_piece_info.size())); + assert(prio > 0); p.downloading = 1; move(prio, p.index); @@ -1437,6 +1662,9 @@ namespace libtorrent = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); assert(i != m_downloads.end()); block_info& info = i->info[block.block_index]; + if (info.state == block_info::state_writing + || info.state == block_info::state_finished) + return false; assert(info.state == block_info::state_none || (info.state == block_info::state_requested && (info.num_peers > 0))); @@ -1449,6 +1677,7 @@ namespace libtorrent ++info.num_peers; if (i->state == none) i->state = state; } + return true; } int piece_picker::num_peers(piece_block block) const @@ -1529,6 +1758,7 @@ namespace libtorrent assert(peer == 0); int prio = p.priority(m_sequenced_download_threshold); + assert(prio < int(m_piece_info.size())); p.downloading = 1; if (prio > 0) move(prio, p.index); else assert(p.priority(m_sequenced_download_threshold) == 0); @@ -1650,9 +1880,12 @@ namespace libtorrent { erase_download_piece(i); piece_pos& p = m_piece_map[block.piece_index]; - int prio = p.priority(m_sequenced_download_threshold); + int prev_prio = p.priority(m_sequenced_download_threshold); + assert(prev_prio < int(m_piece_info.size())); p.downloading = 0; - if (prio > 0) move(prio, p.index); + int prio = p.priority(m_sequenced_download_threshold); + if (prev_prio == 0 && prio > 0) add(block.piece_index); + else if (prio > 0) move(prio, p.index); assert(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 572f48d35..4faed837e 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -83,7 +83,7 @@ namespace // (and we should not consider it free). If the share diff is // negative, there's no free download to get from this peer. size_type diff = i->second->share_diff(); - assert(diff < std::numeric_limits::max()); + assert(diff < (std::numeric_limits::max)()); if (i->second->is_peer_interested() || diff <= 0) continue; @@ -110,7 +110,7 @@ namespace for (torrent::peer_iterator i = start; i != end; ++i) { size_type d = i->second->share_diff(); - assert(d < std::numeric_limits::max()); + assert(d < (std::numeric_limits::max)()); total_diff += d; if (!i->second->is_peer_interested() || i->second->share_diff() >= 0) continue; ++num_peers; @@ -120,7 +120,7 @@ namespace size_type upload_share; if (total_diff >= 0) { - upload_share = std::min(free_upload, total_diff) / num_peers; + upload_share = (std::min)(free_upload, total_diff) / num_peers; } else { @@ -138,28 +138,28 @@ namespace return free_upload; } - struct match_peer_ip + struct match_peer_address { - match_peer_ip(address const& ip) - : m_ip(ip) + match_peer_address(address const& addr) + : m_addr(addr) {} bool operator()(policy::peer const& p) const - { return p.ip.address() == m_ip; } + { return p.ip.address() == m_addr; } - address const& m_ip; + address const& m_addr; }; - struct match_peer_id + struct match_peer_endpoint { - match_peer_id(peer_id const& id_) - : m_id(id_) + match_peer_endpoint(tcp::endpoint const& ep) + : m_ep(ep) {} bool operator()(policy::peer const& p) const - { return p.connection && p.connection->pid() == m_id; } + { return p.ip == m_ep; } - peer_id const& m_id; + tcp::endpoint const& m_ep; }; struct match_peer_connection @@ -187,17 +187,19 @@ namespace libtorrent // have only one piece that we don't have, and it's the // same piece for both peers. Then they might get into an // infinite loop, fighting to request the same blocks. - void request_a_block( - torrent& t - , peer_connection& c) + void request_a_block(torrent& t, peer_connection& c) { - assert(!t.is_seed()); - assert(!c.has_peer_choked()); + if (t.is_seed()) return; + + assert(t.valid_metadata()); assert(c.peer_info_struct() != 0 || !dynamic_cast(&c)); int num_requests = c.desired_queue_size() - (int)c.download_queue().size() - (int)c.request_queue().size(); +#ifdef TORRENT_VERBOSE_LOGGING + (*c.m_logger) << time_now_string() << " PIECE_PICKER [ req: " << num_requests << " ]\n"; +#endif assert(c.desired_queue_size() > 0); // if our request queue is already full, we // don't have to make any new requests yet @@ -207,16 +209,15 @@ namespace libtorrent std::vector interesting_pieces; interesting_pieces.reserve(100); - bool prefer_whole_pieces = c.prefer_whole_pieces() - || (c.peer_info_struct() && c.peer_info_struct()->on_parole); + int prefer_whole_pieces = c.prefer_whole_pieces(); bool rarest_first = t.num_pieces() >= t.settings().initial_picker_threshold; - if (!prefer_whole_pieces) + if (prefer_whole_pieces == 0) { prefer_whole_pieces = c.statistics().download_payload_rate() * t.settings().whole_pieces_threshold - > t.torrent_file().piece_length(); + > t.torrent_file().piece_length() ? 1 : 0; } // if we prefer whole pieces, the piece picker will pick at least @@ -231,18 +232,6 @@ namespace libtorrent else if (speed == peer_connection::medium) state = piece_picker::medium; else state = piece_picker::slow; - // picks the interesting pieces from this peer - // the integer is the number of pieces that - // should be guaranteed to be available for download - // (if num_requests is too big, too many pieces are - // picked and cpu-time is wasted) - // the last argument is if we should prefer whole pieces - // for this peer. If we're downloading one piece in 20 seconds - // then use this mode. - p.pick_pieces(c.get_bitfield(), interesting_pieces - , num_requests, prefer_whole_pieces, c.peer_info_struct() - , state, rarest_first); - // this vector is filled with the interesting pieces // that some other peer is currently downloading // we should then compare this peer's download speed @@ -251,14 +240,56 @@ namespace libtorrent std::vector busy_pieces; busy_pieces.reserve(num_requests); + std::vector const& suggested = c.suggested_pieces(); + std::vector const& bitfield = c.get_bitfield(); + + if (c.has_peer_choked()) + { + // if we are choked we can only pick pieces from the + // allowed fast set. The allowed fast set is sorted + // in ascending priority order + std::vector const& allowed_fast = c.allowed_fast(); + + // build a bitmask with only the allowed pieces in it + std::vector mask(c.get_bitfield().size(), false); + for (std::vector::const_iterator i = allowed_fast.begin() + , end(allowed_fast.end()); i != end; ++i) + if (bitfield[*i]) mask[*i] = true; + + p.pick_pieces(mask, interesting_pieces + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, rarest_first, c.on_parole(), suggested); + } + else + { + // picks the interesting pieces from this peer + // the integer is the number of pieces that + // should be guaranteed to be available for download + // (if num_requests is too big, too many pieces are + // picked and cpu-time is wasted) + // the last argument is if we should prefer whole pieces + // for this peer. If we're downloading one piece in 20 seconds + // then use this mode. + p.pick_pieces(bitfield, interesting_pieces + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, rarest_first, c.on_parole(), suggested); + } + +#ifdef TORRENT_VERBOSE_LOGGING + (*c.m_logger) << time_now_string() << " PIECE_PICKER [ php: " << prefer_whole_pieces + << " picked: " << interesting_pieces.size() << " ]\n"; +#endif + std::deque const& dq = c.download_queue(); + std::deque const& rq = c.request_queue(); for (std::vector::iterator i = interesting_pieces.begin(); i != interesting_pieces.end(); ++i) { + if (prefer_whole_pieces == 0 && num_requests <= 0) break; + if (p.is_requested(*i)) { + if (num_requests <= 0) break; // don't request pieces we already have in our request queue - const std::deque& dq = c.download_queue(); - const std::deque& rq = c.request_queue(); if (std::find(dq.begin(), dq.end(), *i) != dq.end() || std::find(rq.begin(), rq.end(), *i) != rq.end()) continue; @@ -277,13 +308,13 @@ namespace libtorrent num_requests--; } - // in this case, we could not find any blocks - // that was free. If we couldn't find any busy - // blocks as well, we cannot download anything - // more from this peer. - - if (busy_pieces.empty() || num_requests == 0) + if (busy_pieces.empty() || num_requests <= 0) { + // in this case, we could not find any blocks + // that was free. If we couldn't find any busy + // blocks as well, we cannot download anything + // more from this peer. + c.send_block_requests(); return; } @@ -308,9 +339,8 @@ namespace libtorrent policy::policy(torrent* t) : m_torrent(t) - , m_num_unchoked(0) , m_available_free_upload(0) - , m_last_optimistic_disconnect(min_time()) +// , m_last_optimistic_disconnect(min_time()) { assert(t); } // disconnects and removes all peers that are now filtered @@ -352,7 +382,7 @@ namespace libtorrent m_peers.erase(i++); } } - +/* // finds the peer that has the worst download rate // and returns it. May return 0 if all peers are // choked. @@ -361,7 +391,7 @@ namespace libtorrent INVARIANT_CHECK; iterator worst_peer = m_peers.end(); - size_type min_weight = std::numeric_limits::min(); + size_type min_weight = (std::numeric_limits::min)(); #ifndef NDEBUG int unchoked_counter = m_num_unchoked; @@ -434,13 +464,13 @@ namespace libtorrent } return unchoke_peer; } - +*/ policy::iterator policy::find_disconnect_candidate() { INVARIANT_CHECK; iterator disconnect_peer = m_peers.end(); - double slowest_transfer_rate = std::numeric_limits::max(); + double slowest_transfer_rate = (std::numeric_limits::max)(); ptime now = time_now(); @@ -483,7 +513,8 @@ namespace libtorrent policy::iterator policy::find_connect_candidate() { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; ptime now = time_now(); ptime min_connect_time(now); @@ -491,6 +522,7 @@ namespace libtorrent int max_failcount = m_torrent->settings().max_failcount; int min_reconnect_time = m_torrent->settings().min_reconnect_time; + bool finished = m_torrent->is_finished(); aux::session_impl& ses = m_torrent->session(); @@ -499,7 +531,7 @@ namespace libtorrent if (i->connection) continue; if (i->banned) continue; if (i->type == peer::not_connectable) continue; - if (i->seed && m_torrent->is_seed()) continue; + if (i->seed && finished) continue; if (i->failcount >= max_failcount) continue; if (now - i->connected < seconds(i->failcount * min_reconnect_time)) continue; @@ -519,7 +551,7 @@ namespace libtorrent return candidate; } - +/* policy::iterator policy::find_seed_choke_candidate() { INVARIANT_CHECK; @@ -625,7 +657,7 @@ namespace libtorrent --m_num_unchoked; } } - +*/ void policy::pulse() { INVARIANT_CHECK; @@ -657,7 +689,7 @@ namespace libtorrent // ------------------------------------- // maintain the number of connections // ------------------------------------- - +/* // count the number of connected peers except for peers // that are currently in the process of disconnecting int num_connected_peers = 0; @@ -669,10 +701,9 @@ namespace libtorrent ++num_connected_peers; } - if (m_torrent->m_connections_quota.given != std::numeric_limits::max()) + if (m_torrent->max_connections() != (std::numeric_limits::max)()) { - - int max_connections = m_torrent->m_connections_quota.given; + int max_connections = m_torrent->max_connections(); if (num_connected_peers >= max_connections) { @@ -700,7 +731,7 @@ namespace libtorrent --num_connected_peers; } } - +*/ // ------------------------ // upload shift // ------------------------ @@ -731,7 +762,7 @@ namespace libtorrent , m_torrent->end() , m_available_free_upload); } - +/* // ------------------------ // seed choking policy // ------------------------ @@ -847,6 +878,7 @@ namespace libtorrent while (m_num_unchoked < m_torrent->m_uploads_quota.given && unchoke_one_peer()); } +*/ } int policy::count_choked() const @@ -879,7 +911,8 @@ namespace libtorrent // override at a time assert(c.remote() == c.get_socket()->remote_endpoint()); - if (m_torrent->num_peers() >= m_torrent->m_connections_quota.given + if (m_torrent->num_peers() >= m_torrent->max_connections() + && m_torrent->session().num_connections() >= m_torrent->session().max_connections() && c.remote().address() != m_torrent->current_tracker().address()) { throw protocol_error("too many connections, refusing incoming connection"); // cause a disconnect @@ -906,7 +939,7 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_ip(c.remote().address())); + , match_peer_address(c.remote().address())); } if (i != m_peers.end()) @@ -961,16 +994,17 @@ namespace libtorrent i->connection = &c; assert(i->connection); i->connected = time_now(); - m_last_optimistic_disconnect = time_now(); +// m_last_optimistic_disconnect = time_now(); } void policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int src, char flags) { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; // just ignore the obviously invalid entries - if(remote.address() == address() || remote.port() == 0) + if (remote.address() == address() || remote.port() == 0) return; aux::session_impl& ses = m_torrent->session(); @@ -995,14 +1029,14 @@ namespace libtorrent i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_id(pid)); + , match_peer_endpoint(remote)); } else { i = std::find_if( m_peers.begin() , m_peers.end() - , match_peer_ip(remote.address())); + , match_peer_address(remote.address())); } if (i == m_peers.end()) @@ -1056,7 +1090,10 @@ namespace libtorrent if (i->failcount > 0 && src != peer_info::dht) --i->failcount; - if (flags & 0x02) i->seed = true; + // if we're connected to this peer + // we already know if it's a seed or not + // so we don't have to trust this source + if ((flags & 0x02) && !i->connection) i->seed = true; if (i->connection) { @@ -1146,14 +1183,38 @@ namespace libtorrent // In that case we don't care if people are leeching, they // can't pay for their downloads anyway. if (c.is_choked() - && m_num_unchoked < m_torrent->m_uploads_quota.given + && m_torrent->session().num_uploads() < m_torrent->session().max_uploads() && (m_torrent->ratio() == 0 || c.share_diff() >= -free_upload_amount - || m_torrent->is_seed())) + || m_torrent->is_finished())) { - c.send_unchoke(); - ++m_num_unchoked; + m_torrent->session().unchoke_peer(c); } +#if defined(TORRENT_VERBOSE_LOGGING) + else if (c.is_choked()) + { + std::string reason; + if (m_torrent->session().num_uploads() >= m_torrent->session().max_uploads()) + { + reason = "the number of uploads (" + + boost::lexical_cast(m_torrent->session().num_uploads()) + + ") is more than or equal to the limit (" + + boost::lexical_cast(m_torrent->session().max_uploads()) + + ")"; + } + else + { + reason = "the share ratio (" + + boost::lexical_cast(c.share_diff()) + + ") is <= free_upload_amount (" + + boost::lexical_cast(int(free_upload_amount)) + + ") and we are not seeding and the ratio (" + + boost::lexical_cast(m_torrent->ratio()) + + ")is non-zero"; + } + (*c.m_logger) << time_now_string() << " DID NOT UNCHOKE [ " << reason << " ]\n"; + } +#endif } // called when a peer is no longer interested in us @@ -1163,7 +1224,7 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { - assert(c.share_diff() < std::numeric_limits::max()); + assert(c.share_diff() < (std::numeric_limits::max)()); size_type diff = c.share_diff(); if (diff > 0 && c.is_seed()) { @@ -1185,7 +1246,7 @@ namespace libtorrent } */ } - +/* bool policy::unchoke_one_peer() { INVARIANT_CHECK; @@ -1214,7 +1275,7 @@ namespace libtorrent p->connection->send_choke(); --m_num_unchoked; } - +*/ bool policy::connect_one_peer() { INVARIANT_CHECK; @@ -1230,9 +1291,15 @@ namespace libtorrent try { - p->connected = m_last_optimistic_disconnect = time_now(); + INVARIANT_CHECK; + p->connected = time_now(); p->connection = m_torrent->connect_to_peer(&*p); - if (p->connection == 0) return false; + assert(p->connection == m_torrent->connection_for(p->ip)); + if (p->connection == 0) + { + ++p->failcount; + return false; + } p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); p->prev_amount_download = 0; p->prev_amount_upload = 0; @@ -1244,6 +1311,7 @@ namespace libtorrent (*m_torrent->session().m_logger) << "*** CONNECTION FAILED '" << e.what() << "'\n"; #endif + std::cerr << e.what() << std::endl; ++p->failcount; return false; } @@ -1263,33 +1331,33 @@ namespace libtorrent } // this is called whenever a peer connection is closed - void policy::connection_closed(const peer_connection& c) try + void policy::connection_closed(const peer_connection& c) throw() { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; -// assert(c.is_disconnecting()); - bool unchoked = false; + peer* p = c.peer_info_struct(); - iterator i = std::find_if( + assert((std::find_if( m_peers.begin() , m_peers.end() - , match_peer_connection(c)); - + , match_peer_connection(c)) + != m_peers.end()) == (p != 0)); + // if we couldn't find the connection in our list, just ignore it. - if (i == m_peers.end()) return; - assert(i->connection == &c); - i->connection = 0; + if (p == 0) return; - i->connected = time_now(); - if (!c.is_choked() && !m_torrent->is_aborted()) - { - unchoked = true; - } + assert(p->connection == &c); + + p->connection = 0; + p->optimistically_unchoked = false; + + p->connected = time_now(); if (c.failed()) { - ++i->failcount; -// i->connected = time_now(); + ++p->failcount; +// p->connected = time_now(); } // if the share ratio is 0 (infinite), the @@ -1298,28 +1366,11 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { assert(c.associated_torrent().lock().get() == m_torrent); - assert(c.share_diff() < std::numeric_limits::max()); + assert(c.share_diff() < (std::numeric_limits::max)()); m_available_free_upload += c.share_diff(); } - i->prev_amount_download += c.statistics().total_payload_download(); - i->prev_amount_upload += c.statistics().total_payload_upload(); - - if (unchoked) - { - // if the peer that is diconnecting is unchoked - // then unchoke another peer in order to maintain - // the total number of unchoked peers - --m_num_unchoked; - if (m_torrent->is_seed()) seed_unchoke_one_peer(); - else unchoke_one_peer(); - } - } - catch (std::exception& e) - { -#ifndef NDEBUG - std::string err = e.what(); -#endif - assert(false); + p->prev_amount_download += c.statistics().total_payload_download(); + p->prev_amount_upload += c.statistics().total_payload_upload(); } void policy::peer_is_interesting(peer_connection& c) @@ -1327,17 +1378,21 @@ namespace libtorrent INVARIANT_CHECK; c.send_interested(); - if (c.has_peer_choked()) return; + if (c.has_peer_choked() + && c.allowed_fast().empty()) + return; request_a_block(*m_torrent, c); } #ifndef NDEBUG bool policy::has_connection(const peer_connection* c) { - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; assert(c); - assert(c->remote() == c->get_socket()->remote_endpoint()); + try { assert(c->remote() == c->get_socket()->remote_endpoint()); } + catch (std::exception&) {} return std::find_if( m_peers.begin() @@ -1348,22 +1403,28 @@ namespace libtorrent void policy::check_invariant() const { if (m_torrent->is_aborted()) return; - int actual_unchoked = 0; int connected_peers = 0; int total_connections = 0; int nonempty_connections = 0; - + std::set
unique_test; + std::set unique_test2; for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { peer const& p = *i; if (!m_torrent->settings().allow_multiple_connections_per_ip) assert(unique_test.find(p.ip.address()) == unique_test.end()); + assert(unique_test2.find(p.ip) == unique_test2.end()); unique_test.insert(p.ip.address()); + unique_test2.insert(p.ip); ++total_connections; - if (!p.connection) continue; + if (!p.connection) + { +// assert(m_torrent->connection_for(p.ip) == 0); + continue; + } if (!m_torrent->settings().allow_multiple_connections_per_ip) { std::vector conns; @@ -1372,15 +1433,17 @@ namespace libtorrent , boost::bind(std::equal_to(), _1, p.connection)) != conns.end()); } + if (p.optimistically_unchoked) + { + assert(p.connection); + assert(!p.connection->is_choked()); + } assert(p.connection->peer_info_struct() == 0 || p.connection->peer_info_struct() == &p); ++nonempty_connections; if (!p.connection->is_disconnecting()) ++connected_peers; - if (!p.connection->is_choked()) ++actual_unchoked; } -// assert(actual_unchoked <= m_torrent->m_uploads_quota.given); - assert(actual_unchoked == m_num_unchoked); int num_torrent_peers = 0; for (torrent::const_peer_iterator i = m_torrent->begin(); @@ -1447,6 +1510,7 @@ namespace libtorrent , failcount(0) , hashfails(0) , seed(false) + , optimistically_unchoked(false) , last_optimistically_unchoked(min_time()) , connected(min_time()) , trust_points(0) diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 485c90d62..6298b3c2c 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -68,7 +68,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" @@ -133,7 +132,7 @@ namespace libtorrent m_impl->abort(); } - void session::add_extension(boost::function(torrent*)> ext) + void session::add_extension(boost::function(torrent*, void*)> ext) { m_impl->add_extension(ext); } @@ -180,11 +179,27 @@ namespace libtorrent , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size + , bool paused , storage_constructor_type sc) { + assert(!ti.m_half_metadata); + boost::intrusive_ptr tip(new torrent_info(ti)); + return m_impl->add_torrent(tip, save_path, resume_data + , compact_mode, sc, paused, 0); + } + + torrent_handle session::add_torrent( + boost::intrusive_ptr ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + assert(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data - , compact_mode, block_size, sc); + , compact_mode, sc, paused, userdata); } torrent_handle session::add_torrent( @@ -194,11 +209,12 @@ namespace libtorrent , fs::path const& save_path , entry const& e , bool compact_mode - , int block_size - , storage_constructor_type sc) + , bool paused + , storage_constructor_type sc + , void* userdata) { return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e - , compact_mode, block_size, sc); + , compact_mode, sc, paused, userdata); } void session::remove_torrent(const torrent_handle& h) @@ -337,6 +353,11 @@ namespace libtorrent m_impl->set_max_connections(limit); } + int session::max_half_open_connections() const + { + return m_impl->max_half_open_connections(); + } + void session::set_max_half_open_connections(int limit) { m_impl->set_max_half_open_connections(limit); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 81f48a3ce..6e1129b99 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -68,7 +68,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" -#include "libtorrent/allocate_resources.hpp" #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" @@ -223,6 +222,12 @@ namespace detail if (!m_ses.is_aborted()) { m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr)); + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(torrent_checked_alert( + processing->torrent_ptr->get_handle() + , "torrent finished checking")); + } if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) { m_ses.m_alerts.post_alert(torrent_finished_alert( @@ -345,6 +350,12 @@ namespace detail processing->torrent_ptr->files_checked(processing->unfinished_pieces); m_ses.m_torrents.insert(std::make_pair( processing->info_hash, processing->torrent_ptr)); + if (m_ses.m_alerts.should_post(alert::info)) + { + m_ses.m_alerts.post_alert(torrent_checked_alert( + processing->torrent_ptr->get_handle() + , "torrent finished checking")); + } if (processing->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) { @@ -505,8 +516,12 @@ namespace detail , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) , m_external_listen_port(0) , m_abort(false) - , m_max_uploads(-1) - , m_max_connections(-1) + , m_max_uploads(8) + , m_max_connections(200) + , m_num_unchoked(0) + , m_unchoke_time_scaler(0) + , m_optimistic_unchoke_time_scaler(0) + , m_disconnect_time_scaler(0) , m_incoming_connection(false) , m_last_tick(time_now()) #ifndef TORRENT_DISABLE_DHT @@ -517,6 +532,11 @@ namespace detail , m_next_connect_torrent(0) , m_checker_impl(*this) { +#ifdef WIN32 + // windows XP has a limit of 10 simultaneous connections + m_half_open.limit(8); +#endif + m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; m_bandwidth_manager[peer_connection::upload_channel] = &m_upload_channel; @@ -573,7 +593,7 @@ namespace detail #ifndef TORRENT_DISABLE_EXTENSIONS void session_impl::add_extension( - boost::function(torrent*)> ext) + boost::function(torrent*, void*)> ext) { m_extensions.push_back(ext); } @@ -609,6 +629,9 @@ namespace detail void session_impl::set_ip_filter(ip_filter const& f) { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_ip_filter = f; // Close connections whose endpoint is filtered @@ -621,6 +644,9 @@ namespace detail void session_impl::set_settings(session_settings const& s) { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + assert(s.connection_speed > 0); assert(s.file_pool_size > 0); @@ -640,22 +666,25 @@ namespace detail try { // create listener socket - m_listen_socket = boost::shared_ptr(new socket_acceptor(m_io_service)); + m_listen_socket.reset(new socket_acceptor(m_io_service)); for(;;) { try { m_listen_socket->open(m_listen_interface.protocol()); + m_listen_socket->set_option(socket_acceptor::reuse_address(true)); m_listen_socket->bind(m_listen_interface); m_listen_socket->listen(); + m_listen_interface = m_listen_socket->local_endpoint(); m_external_listen_port = m_listen_interface.port(); break; } catch (asio::system_error& e) { // TODO: make sure this is correct - if (e.code() == asio::error::host_not_found) + if (e.code() == asio::error::host_not_found + || m_listen_interface.port() == 0) { if (m_alerts.should_post(alert::fatal)) { @@ -700,14 +729,18 @@ namespace detail } } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (m_listen_socket) { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << "listening on port: " << m_listen_interface.port() << " external port: " << m_external_listen_port << "\n"; - } #endif - if (m_listen_socket) async_accept(); + async_accept(); + if (m_natpmp.get()) + m_natpmp->set_mappings(m_listen_interface.port(), 0); + if (m_upnp.get()) + m_upnp->set_mappings(m_listen_interface.port(), 0); + } } void session_impl::async_accept() @@ -733,7 +766,6 @@ namespace detail if (m_abort) return; - async_accept(); if (e) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -742,8 +774,12 @@ namespace detail (*m_logger) << msg << "\n"; #endif assert(m_listen_socket.unique()); + // try any random port + m_listen_interface.port(0); + open_listen_port(); return; } + async_accept(); // we got a connection request! m_incoming_connection = true; @@ -765,8 +801,30 @@ namespace detail return; } + // check if we have any active torrents + // if we don't reject the connection + if (m_torrents.empty()) + { + return; + } + else + { + bool has_active_torrent = false; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + if (!i->second->is_paused()) + { + has_active_torrent = true; + break; + } + } + if (!has_active_torrent) + return; + } + boost::intrusive_ptr c( - new bt_peer_connection(*this, s, 0)); + new bt_peer_connection(*this, s, 0)); #ifndef NDEBUG c->m_in_constructor = false; #endif @@ -787,6 +845,9 @@ namespace detail #endif { mutex_t::scoped_lock l(m_mutex); + +// too expensive +// INVARIANT_CHECK; connection_map::iterator p = m_connections.find(s); @@ -818,10 +879,16 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); +// too expensive +// INVARIANT_CHECK; + assert(p->is_disconnecting()); connection_map::iterator i = m_connections.find(p->get_socket()); if (i != m_connections.end()) + { + if (!i->second->is_choked()) --m_num_unchoked; m_connections.erase(i); + } } void session_impl::set_peer_id(peer_id const& id) @@ -840,6 +907,8 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + if (e) { #if defined(TORRENT_LOGGING) @@ -901,7 +970,9 @@ namespace detail // round robin fashion, so that every torrent is // equallt likely to connect to a peer - if (!m_torrents.empty() && m_half_open.free_slots()) + if (!m_torrents.empty() + && m_half_open.free_slots() + && num_connections() < m_max_connections) { // this is the maximum number of connections we will // attempt this tick @@ -918,11 +989,13 @@ namespace detail { torrent& t = *i->second; if (t.want_more_peers()) + { if (t.try_connect_peer()) { --max_connections; steps_since_last_connect = 0; } + } ++m_next_connect_torrent; ++steps_since_last_connect; ++i; @@ -940,6 +1013,8 @@ namespace detail // if we should not make any more connections // attempts this tick, abort if (max_connections == 0) break; + // maintain the global limit on number of connections + if (num_connections() >= m_max_connections) break; } } @@ -976,18 +1051,7 @@ namespace detail continue; } - try - { - c.keep_alive(); - } - catch (std::exception& exc) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*c.m_logger) << "**ERROR**: " << exc.what() << "\n"; -#endif - c.set_failed(); - c.disconnect(); - } + c.keep_alive(); } // check each torrent for tracker updates @@ -1019,30 +1083,183 @@ namespace detail } m_stat.second_tick(tick_interval); - // distribute the maximum upload rate among the torrents - assert(m_max_uploads >= -1); - assert(m_max_connections >= -1); - - allocate_resources(m_max_uploads == -1 - ? std::numeric_limits::max() - : m_max_uploads - , m_torrents - , &torrent::m_uploads_quota); - - allocate_resources(m_max_connections == -1 - ? std::numeric_limits::max() - : m_max_connections - , m_torrents - , &torrent::m_connections_quota); - - for (std::map >::iterator i - = m_torrents.begin(); i != m_torrents.end(); ++i) + // -------------------------------------------------------------- + // unchoke set and optimistic unchoke calculations + // -------------------------------------------------------------- + m_unchoke_time_scaler--; + if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) { -#ifndef NDEBUG - i->second->check_invariant(); -#endif - i->second->distribute_resources(tick_interval); + m_unchoke_time_scaler = settings().unchoke_interval; + + std::vector peers; + for (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = i->second.get(); + torrent* t = p->associated_torrent().lock().get(); + if (!p->peer_info_struct() + || t == 0 + || !p->is_peer_interested() + || p->is_disconnecting() + || p->is_connecting() + || (p->share_diff() < -free_upload_amount + && !t->is_seed())) + { + if (!i->second->is_choked() && t) + { + policy::peer* pi = p->peer_info_struct(); + if (pi && pi->optimistically_unchoked) + { + pi->optimistically_unchoked = false; + // force a new optimistic unchoke + m_optimistic_unchoke_time_scaler = 0; + } + t->choke_peer(*i->second); + } + continue; + } + peers.push_back(i->second.get()); + } + + // sort the peers that are eligible for unchoke by download rate and secondary + // by total upload. The reason for this is, if all torrents are being seeded, + // the download rate will be 0, and the peers we have sent the least to should + // be unchoked + std::sort(peers.begin(), peers.end() + , bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _1)) + < bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _2))); + + std::stable_sort(peers.begin(), peers.end() + , bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _1)) + > bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _2))); + + // reserve one upload slot for optimistic unchokes + int unchoke_set_size = m_max_uploads - 1; + + m_num_unchoked = 0; + // go through all the peers and unchoke the first ones and choke + // all the other ones. + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection* p = *i; + assert(p); + torrent* t = p->associated_torrent().lock().get(); + assert(t); + if (unchoke_set_size > 0) + { + if (p->is_choked()) + { + if (!t->unchoke_peer(*p)) + continue; + } + + --unchoke_set_size; + ++m_num_unchoked; + + assert(p->peer_info_struct()); + if (p->peer_info_struct()->optimistically_unchoked) + { + // force a new optimistic unchoke + m_optimistic_unchoke_time_scaler = 0; + p->peer_info_struct()->optimistically_unchoked = false; + } + } + else + { + assert(p->peer_info_struct()); + if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) + t->choke_peer(*p); + if (!p->is_choked()) + ++m_num_unchoked; + } + } + + m_optimistic_unchoke_time_scaler--; + if (m_optimistic_unchoke_time_scaler <= 0) + { + m_optimistic_unchoke_time_scaler + = settings().optimistic_unchoke_multiplier; + + // find the peer that has been waiting the longest to be optimistically + // unchoked + connection_map::iterator current_optimistic_unchoke = m_connections.end(); + connection_map::iterator optimistic_unchoke_candidate = m_connections.end(); + ptime last_unchoke = max_time(); + + for (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = i->second.get(); + assert(p); + policy::peer* pi = p->peer_info_struct(); + if (!pi) continue; + torrent* t = p->associated_torrent().lock().get(); + if (!t) continue; + + if (pi->optimistically_unchoked) + { + assert(!p->is_choked()); + assert(current_optimistic_unchoke == m_connections.end()); + current_optimistic_unchoke = i; + } + + if (pi->last_optimistically_unchoked < last_unchoke + && !p->is_connecting() + && !p->is_disconnecting() + && p->is_peer_interested() + && t->free_upload_slots() + && p->is_choked()) + { + last_unchoke = pi->last_optimistically_unchoked; + optimistic_unchoke_candidate = i; + } + } + + if (optimistic_unchoke_candidate != m_connections.end() + && optimistic_unchoke_candidate != current_optimistic_unchoke) + { + if (current_optimistic_unchoke != m_connections.end()) + { + torrent* t = current_optimistic_unchoke->second->associated_torrent().lock().get(); + assert(t); + current_optimistic_unchoke->second->peer_info_struct()->optimistically_unchoked = false; + t->choke_peer(*current_optimistic_unchoke->second); + } + else + { + ++m_num_unchoked; + } + + torrent* t = optimistic_unchoke_candidate->second->associated_torrent().lock().get(); + assert(t); + bool ret = t->unchoke_peer(*optimistic_unchoke_candidate->second); + assert(ret); + optimistic_unchoke_candidate->second->peer_info_struct()->optimistically_unchoked = true; + } + } + } + + // -------------------------------------------------------------- + // disconnect peers when we have too many + // -------------------------------------------------------------- + --m_disconnect_time_scaler; + if (m_disconnect_time_scaler <= 0) + { + m_disconnect_time_scaler = 60; + + // every 60 seconds, disconnect the worst peer + // if we have reached the connection limit + if (num_connections() >= max_connections() && !m_torrents.empty()) + { + torrent_map::iterator i = std::max_element(m_torrents.begin(), m_torrents.end() + , bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _1)) + < bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _2))); + + assert(i != m_torrents.end()); + i->second->get_policy().disconnect_one_peer(); + } } } catch (std::exception& exc) @@ -1052,29 +1269,7 @@ namespace detail assert(false); #endif }; // msvc 7.1 seems to require this -/* - void session_impl::connection_completed( - boost::intrusive_ptr const& p) try - { - mutex_t::scoped_lock l(m_mutex); - connection_map::iterator i = m_half_open.find(p->get_socket()); - m_connections.insert(std::make_pair(p->get_socket(), p)); - assert(i != m_half_open.end()); - if (i != m_half_open.end()) m_half_open.erase(i); - - if (m_abort) return; - - process_connection_queue(); - } - catch (std::exception& e) - { -#ifndef NDEBUG - std::cerr << e.what() << std::endl; - assert(false); -#endif - }; -*/ void session_impl::operator()() { eh_initializer(); @@ -1083,10 +1278,6 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); open_listen_port(); - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); } ptime timer = time_now(); @@ -1156,6 +1347,9 @@ namespace detail } } + // close listen socket + m_listen_socket.reset(); + ptime start(time_now()); l.unlock(); @@ -1275,47 +1469,37 @@ namespace detail } torrent_handle session_impl::add_torrent( - torrent_info const& ti + boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data , bool compact_mode - , int block_size - , storage_constructor_type sc) + , storage_constructor_type sc + , bool paused + , void* userdata) { // if you get this assert, you haven't managed to // open a listen port. call listen_on() first. assert(m_external_listen_port > 0); - - // make sure the block_size is an even power of 2 -#ifndef NDEBUG - for (int i = 0; i < 32; ++i) - { - if (block_size & (1 << i)) - { - assert((block_size & ~(1 << i)) == 0); - break; - } - } -#endif - assert(!save_path.empty()); - if (ti.begin_files() == ti.end_files()) + if (ti->begin_files() == ti->end_files()) throw std::runtime_error("no files in torrent"); // lock the session and the checker thread (the order is important!) mutex_t::scoped_lock l(m_mutex); mutex::scoped_lock l2(m_checker_impl.m_mutex); + INVARIANT_CHECK; + if (is_aborted()) throw std::runtime_error("session is closing"); // is the torrent already active? - if (!find_torrent(ti.info_hash()).expired()) + if (!find_torrent(ti->info_hash()).expired()) throw duplicate_torrent(); // is the torrent currently being checked? - if (m_checker_impl.find_torrent(ti.info_hash())) + if (m_checker_impl.find_torrent(ti->info_hash())) throw duplicate_torrent(); // create the torrent and the data associated with @@ -1323,15 +1507,15 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, ti, save_path - , m_listen_interface, compact_mode, block_size - , settings(), sc)); + , m_listen_interface, compact_mode, 16 * 1024 + , sc, paused)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - boost::shared_ptr tp((*i)(torrent_ptr.get())); + boost::shared_ptr tp((*i)(torrent_ptr.get(), userdata)); if (tp) torrent_ptr->add_extension(tp); } #endif @@ -1340,13 +1524,13 @@ namespace detail new aux::piece_checker_data); d->torrent_ptr = torrent_ptr; d->save_path = save_path; - d->info_hash = ti.info_hash(); + d->info_hash = ti->info_hash(); d->resume_data = resume_data; #ifndef TORRENT_DISABLE_DHT if (m_dht) { - torrent_info::nodes_t const& nodes = ti.nodes(); + torrent_info::nodes_t const& nodes = ti->nodes(); std::for_each(nodes.begin(), nodes.end(), bind( (void(dht::dht_tracker::*)(std::pair const&)) &dht::dht_tracker::add_node @@ -1360,7 +1544,7 @@ namespace detail // job in its queue m_checker_impl.m_cond.notify_one(); - return torrent_handle(this, &m_checker_impl, ti.info_hash()); + return torrent_handle(this, &m_checker_impl, ti->info_hash()); } torrent_handle session_impl::add_torrent( @@ -1370,20 +1554,10 @@ namespace detail , fs::path const& save_path , entry const& , bool compact_mode - , int block_size - , storage_constructor_type sc) + , storage_constructor_type sc + , bool paused + , void* userdata) { - // make sure the block_size is an even power of 2 -#ifndef NDEBUG - for (int i = 0; i < 32; ++i) - { - if (block_size & (1 << i)) - { - assert((block_size & ~(1 << i)) == 0); - break; - } - } -#endif // TODO: support resume data in this case assert(!save_path.empty()); @@ -1399,6 +1573,8 @@ namespace detail // lock the session session_impl::mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + // is the torrent already active? if (!find_torrent(info_hash).expired()) throw duplicate_torrent(); @@ -1411,15 +1587,15 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, tracker_url, info_hash, name - , save_path, m_listen_interface, compact_mode, block_size - , settings(), sc)); + , save_path, m_listen_interface, compact_mode, 16 * 1024 + , sc, paused)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - boost::shared_ptr tp((*i)(torrent_ptr.get())); + boost::shared_ptr tp((*i)(torrent_ptr.get(), userdata)); if (tp) torrent_ptr->add_extension(tp); } #endif @@ -1437,6 +1613,9 @@ namespace detail assert(h.m_ses != 0); mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + session_impl::torrent_map::iterator i = m_torrents.find(h.m_info_hash); if (i != m_torrents.end()) @@ -1499,6 +1678,8 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + tcp::endpoint new_interface; if (net_interface && std::strlen(net_interface) > 0) new_interface = tcp::endpoint(address::from_string(net_interface), port_range.first); @@ -1522,21 +1703,6 @@ namespace detail bool new_listen_address = m_listen_interface.address() != new_interface.address(); - if (new_listen_address) - { - if (m_natpmp.get()) - m_natpmp->rebind(new_interface.address()); - if (m_upnp.get()) - m_upnp->rebind(new_interface.address()); - if (m_lsd.get()) - m_lsd->rebind(new_interface.address()); - } - - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); - #ifndef TORRENT_DISABLE_DHT if ((new_listen_address || m_dht_same_port) && m_dht) { @@ -1578,6 +1744,8 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + boost::shared_ptr t = find_torrent(ih).lock(); if (!t) return; // don't add peers from lsd to private torrents @@ -1632,6 +1800,9 @@ namespace detail session_status session_impl::status() const { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + session_status s; s.has_incoming_connections = m_incoming_connection; s.num_peers = (int)m_connections.size(); @@ -1673,6 +1844,9 @@ namespace detail void session_impl::start_dht(entry const& startup_state) { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + if (m_dht) { m_dht->stop(); @@ -1832,6 +2006,10 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_uploads = limit; } @@ -1839,6 +2017,10 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_connections = limit; } @@ -1846,7 +2028,10 @@ namespace detail { assert(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); - + + INVARIANT_CHECK; + + if (limit <= 0) limit = (std::numeric_limits::max)(); m_half_open.limit(limit); } @@ -1854,7 +2039,10 @@ namespace detail { assert(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); - if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; + + INVARIANT_CHECK; + + if (bytes_per_second <= 0) bytes_per_second = bandwidth_limit::inf; m_bandwidth_manager[peer_connection::download_channel]->throttle(bytes_per_second); } @@ -1862,31 +2050,20 @@ namespace detail { assert(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); - if (bytes_per_second == -1) bytes_per_second = bandwidth_limit::inf; + + INVARIANT_CHECK; + + if (bytes_per_second <= 0) bytes_per_second = bandwidth_limit::inf; m_bandwidth_manager[peer_connection::upload_channel]->throttle(bytes_per_second); } - int session_impl::num_uploads() const - { - int uploads = 0; - mutex_t::scoped_lock l(m_mutex); - for (torrent_map::const_iterator i = m_torrents.begin() - , end(m_torrents.end()); i != end; i++) - { - uploads += i->second->get_policy().num_uploads(); - } - return uploads; - } - - int session_impl::num_connections() const - { - mutex_t::scoped_lock l(m_mutex); - return m_connections.size(); - } - std::auto_ptr session_impl::pop_alert() { mutex_t::scoped_lock l(m_mutex); + +// too expensive +// INVARIANT_CHECK; + if (m_alerts.pending()) return m_alerts.get(); return std::auto_ptr(0); @@ -1901,20 +2078,26 @@ namespace detail int session_impl::upload_rate_limit() const { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + int ret = m_bandwidth_manager[peer_connection::upload_channel]->throttle(); - return ret == std::numeric_limits::max() ? -1 : ret; + return ret == (std::numeric_limits::max)() ? -1 : ret; } int session_impl::download_rate_limit() const { mutex_t::scoped_lock l(m_mutex); int ret = m_bandwidth_manager[peer_connection::download_channel]->throttle(); - return ret == std::numeric_limits::max() ? -1 : ret; + return ret == (std::numeric_limits::max)() ? -1 : ret; } void session_impl::start_lsd() { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_lsd.reset(new lsd(m_io_service , m_listen_interface.address() , bind(&session_impl::on_lsd_peer, this, _1, _2))); @@ -1923,6 +2106,9 @@ namespace detail void session_impl::start_natpmp() { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_natpmp.reset(new natpmp(m_io_service , m_listen_interface.address() , bind(&session_impl::on_port_mapping @@ -1938,6 +2124,9 @@ namespace detail void session_impl::start_upnp() { mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + m_upnp.reset(new upnp(m_io_service, m_half_open , m_listen_interface.address() , m_settings.user_agent @@ -1975,20 +2164,35 @@ namespace detail #ifndef NDEBUG - void session_impl::check_invariant(const char *place) + void session_impl::check_invariant() const { - assert(place); - for (connection_map::iterator i = m_connections.begin(); + assert(m_max_connections > 0); + assert(m_max_uploads > 0); + int unchokes = 0; + int num_optimistic = 0; + for (connection_map::const_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { assert(i->second); boost::shared_ptr t = i->second->associated_torrent().lock(); - if (t) + if (!i->second->is_choked()) ++unchokes; + if (i->second->peer_info_struct() + && i->second->peer_info_struct()->optimistically_unchoked) + { + ++num_optimistic; + assert(!i->second->is_choked()); + } + if (t && i->second->peer_info_struct()) { assert(t->get_policy().has_connection(boost::get_pointer(i->second))); } } + assert(num_optimistic == 0 || num_optimistic == 1); + if (m_num_unchoked != unchokes) + { + assert(false); + } } #endif @@ -2101,7 +2305,7 @@ namespace detail const std::string& bitmask = (*i)["bitmask"].string(); - const int num_bitmask_bytes = std::max(num_blocks_per_piece / 8, 1); + const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); if ((int)bitmask.size() != num_bitmask_bytes) { error = "invalid size of bitmask (" + boost::lexical_cast(bitmask.size()) + ")"; @@ -2110,7 +2314,7 @@ namespace detail for (int j = 0; j < num_bitmask_bytes; ++j) { unsigned char bits = bitmask[j]; - int num_bits = std::min(num_blocks_per_piece - j*8, 8); + int num_bits = (std::min)(num_blocks_per_piece - j*8, 8); for (int k = 0; k < num_bits; ++k) { const int bit = j * 8 + k; diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp index b1679c4ac..a6b5544e4 100644 --- a/libtorrent/src/socks5_stream.cpp +++ b/libtorrent/src/socks5_stream.cpp @@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include "libtorrent/socks5_stream.hpp" +#include "libtorrent/assert.hpp" namespace libtorrent { diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index b23a2e858..dbf6a9382 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -88,6 +88,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif +#if defined(__FreeBSD__) +// for statfs() +#include +#include +#endif + #if defined(_WIN32) && defined(UNICODE) #include @@ -247,8 +253,8 @@ namespace libtorrent { p = complete(p); std::vector > sizes; - for (torrent_info::file_iterator i = t.begin_files(); - i != t.end_files(); ++i) + for (torrent_info::file_iterator i = t.begin_files(true); + i != t.end_files(true); ++i) { size_type size = 0; std::time_t time = 0; @@ -287,7 +293,7 @@ namespace libtorrent , bool compact_mode , std::string* error) { - if ((int)sizes.size() != t.num_files()) + if ((int)sizes.size() != t.num_files(true)) { if (error) *error = "mismatching number of files"; return false; @@ -296,8 +302,8 @@ namespace libtorrent std::vector >::const_iterator s = sizes.begin(); - for (torrent_info::file_iterator i = t.begin_files(); - i != t.end_files(); ++i, ++s) + for (torrent_info::file_iterator i = t.begin_files(true); + i != t.end_files(true); ++i, ++s) { size_type size = 0; std::time_t time = 0; @@ -342,50 +348,14 @@ namespace libtorrent return true; } - struct thread_safe_storage - { - thread_safe_storage(std::size_t n) - : slots(n, false) - {} - - boost::mutex mutex; - boost::condition condition; - std::vector slots; - }; - - struct slot_lock - { - slot_lock(thread_safe_storage& s, int slot_) - : storage_(s) - , slot(slot_) - { - assert(slot_>=0 && slot_ < (int)s.slots.size()); - boost::mutex::scoped_lock lock(storage_.mutex); - - while (storage_.slots[slot]) - storage_.condition.wait(lock); - storage_.slots[slot] = true; - } - - ~slot_lock() - { - storage_.slots[slot] = false; - storage_.condition.notify_all(); - } - - thread_safe_storage& storage_; - int slot; - }; - - class storage : public storage_interface, thread_safe_storage, boost::noncopyable + class storage : public storage_interface, boost::noncopyable { public: - storage(torrent_info const& info, fs::path const& path, file_pool& fp) - : thread_safe_storage(info.num_pieces()) - , m_info(info) + storage(boost::intrusive_ptr info, fs::path const& path, file_pool& fp) + : m_info(info) , m_files(fp) { - assert(info.begin_files() != info.end_files()); + assert(info->begin_files(true) != info->end_files(true)); m_save_path = fs::complete(path); assert(m_save_path.is_complete()); } @@ -405,11 +375,9 @@ namespace libtorrent size_type read_impl(char* buf, int slot, int offset, int size, bool fill_zero); ~storage() - { - m_files.release(this); - } + { m_files.release(this); } - torrent_info const& m_info; + boost::intrusive_ptr m_info; fs::path m_save_path; // the file pool is typically stored in // the session, to make all storage @@ -435,21 +403,27 @@ namespace libtorrent assert(ph.offset == 0 || partial_copy.final() == partial.final()); #endif int slot_size = piece_size - ph.offset; - if (slot_size == 0) return ph.h.final(); - m_scratch_buffer.resize(slot_size); - read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); - ph.h.update(&m_scratch_buffer[0], slot_size); + if (slot_size > 0) + { + m_scratch_buffer.resize(slot_size); + read_impl(&m_scratch_buffer[0], slot, ph.offset, slot_size, true); + ph.h.update(&m_scratch_buffer[0], slot_size); + } +#ifndef NDEBUG sha1_hash ret = ph.h.final(); - assert(whole.final() == ret); + assert(ret == whole.final()); return ret; +#else + return ph.h.final(); +#endif } void storage::initialize(bool allocate_files) { // first, create all missing directories fs::path last_path; - for (torrent_info::file_iterator file_iter = m_info.begin_files(), - end_iter = m_info.end_files(); file_iter != end_iter; ++file_iter) + for (torrent_info::file_iterator file_iter = m_info->begin_files(true), + end_iter = m_info->end_files(true); file_iter != end_iter; ++file_iter) { fs::path dir = (m_save_path / file_iter->path).branch_path(); @@ -497,7 +471,7 @@ namespace libtorrent void storage::write_resume_data(entry& rd) const { std::vector > file_sizes - = get_filesizes(m_info, m_save_path); + = get_filesizes(*m_info, m_save_path); rd["file sizes"] = entry::list_type(); entry::list_type& fl = rd["file sizes"].list(); @@ -531,7 +505,7 @@ namespace libtorrent } entry::list_type& slots = rd["slots"].list(); - bool seed = int(slots.size()) == m_info.num_pieces() + bool seed = int(slots.size()) == m_info->num_pieces() && std::find_if(slots.begin(), slots.end() , boost::bind(std::less() , boost::bind((size_type const& (entry::*)() const) @@ -546,11 +520,11 @@ namespace libtorrent if (seed) { - if (m_info.num_files() != (int)file_sizes.size()) + if (m_info->num_files(true) != (int)file_sizes.size()) { error = "the number of files does not match the torrent (num: " + boost::lexical_cast(file_sizes.size()) + " actual: " - + boost::lexical_cast(m_info.num_files()) + ")"; + + boost::lexical_cast(m_info->num_files(true)) + ")"; return false; } @@ -558,8 +532,8 @@ namespace libtorrent fs = file_sizes.begin(); // the resume data says we have the entire torrent // make sure the file sizes are the right ones - for (torrent_info::file_iterator i = m_info.begin_files() - , end(m_info.end_files()); i != end; ++i, ++fs) + for (torrent_info::file_iterator i = m_info->begin_files(true) + , end(m_info->end_files(true)); i != end; ++i, ++fs) { if (i->size != fs->first) { @@ -572,7 +546,7 @@ namespace libtorrent return true; } - return match_filesizes(m_info, m_save_path, file_sizes + return match_filesizes(*m_info, m_save_path, file_sizes , !full_allocation_mode, &error); } @@ -611,11 +585,11 @@ namespace libtorrent m_files.release(this); #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 - old_path = safe_convert((m_save_path / m_info.name()).string()); - new_path = safe_convert((save_path / m_info.name()).string()); + old_path = safe_convert((m_save_path / m_info->name()).string()); + new_path = safe_convert((save_path / m_info->name()).string()); #else - old_path = m_save_path / m_info.name(); - new_path = save_path / m_info.name(); + old_path = m_save_path / m_info->name(); + new_path = save_path / m_info->name(); #endif try @@ -637,7 +611,7 @@ namespace libtorrent /* void storage::shuffle() { - int num_pieces = m_info.num_pieces(); + int num_pieces = m_info->num_pieces(); std::vector pieces(num_pieces); for (std::vector::iterator i = pieces.begin(); @@ -654,7 +628,7 @@ namespace libtorrent { const int slot_index = targets[i]; const int piece_index = pieces[i]; - const int slot_size =static_cast(m_info.piece_size(slot_index)); + const int slot_size =static_cast(m_info->piece_size(slot_index)); std::vector buf(slot_size); read(&buf[0], piece_index, 0, slot_size); write(&buf[0], slot_index, 0, slot_size); @@ -665,7 +639,7 @@ namespace libtorrent void storage::move_slot(int src_slot, int dst_slot) { - int piece_size = m_info.piece_size(dst_slot); + int piece_size = m_info->piece_size(dst_slot); m_scratch_buffer.resize(piece_size); read_impl(&m_scratch_buffer[0], src_slot, 0, piece_size, true); write(&m_scratch_buffer[0], dst_slot, 0, piece_size); @@ -674,9 +648,9 @@ namespace libtorrent void storage::swap_slots(int slot1, int slot2) { // the size of the target slot is the size of the piece - int piece_size = m_info.piece_length(); - int piece1_size = m_info.piece_size(slot2); - int piece2_size = m_info.piece_size(slot1); + int piece_size = m_info->piece_length(); + int piece1_size = m_info->piece_size(slot2); + int piece2_size = m_info->piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); @@ -687,10 +661,10 @@ namespace libtorrent void storage::swap_slots3(int slot1, int slot2, int slot3) { // the size of the target slot is the size of the piece - int piece_size = m_info.piece_length(); - int piece1_size = m_info.piece_size(slot2); - int piece2_size = m_info.piece_size(slot3); - int piece3_size = m_info.piece_size(slot1); + int piece_size = m_info->piece_length(); + int piece1_size = m_info->piece_size(slot2); + int piece2_size = m_info->piece_size(slot3); + int piece3_size = m_info->piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); @@ -717,27 +691,25 @@ namespace libtorrent , bool fill_zero) { assert(buf != 0); - assert(slot >= 0 && slot < m_info.num_pieces()); + assert(slot >= 0 && slot < m_info->num_pieces()); assert(offset >= 0); - assert(offset < m_info.piece_size(slot)); + assert(offset < m_info->piece_size(slot)); assert(size > 0); - slot_lock lock(*this, slot); - #ifndef NDEBUG std::vector slices - = m_info.map_block(slot, offset, size); + = m_info->map_block(slot, offset, size, true); assert(!slices.empty()); #endif - size_type start = slot * (size_type)m_info.piece_length() + offset; - assert(start + size <= m_info.total_size()); + size_type start = slot * (size_type)m_info->piece_length() + offset; + assert(start + size <= m_info->total_size()); // find the file iterator and file offset size_type file_offset = start; std::vector::const_iterator file_iter; - for (file_iter = m_info.begin_files();;) + for (file_iter = m_info->begin_files(true);;) { if (file_offset < file_iter->size) break; @@ -770,7 +742,7 @@ namespace libtorrent #endif int left_to_read = size; - int slot_size = static_cast(m_info.piece_size(slot)); + int slot_size = static_cast(m_info->piece_size(slot)); if (offset + left_to_read > slot_size) left_to_read = slot_size - offset; @@ -795,7 +767,7 @@ namespace libtorrent assert(int(slices.size()) > counter); size_type slice_size = slices[counter].size; assert(slice_size == read_bytes); - assert(m_info.file_at(slices[counter].file_index).path + assert(m_info->file_at(slices[counter].file_index, true).path == file_iter->path); #endif @@ -845,32 +817,30 @@ namespace libtorrent { assert(buf != 0); assert(slot >= 0); - assert(slot < m_info.num_pieces()); + assert(slot < m_info->num_pieces()); assert(offset >= 0); assert(size > 0); - slot_lock lock(*this, slot); - #ifndef NDEBUG std::vector slices - = m_info.map_block(slot, offset, size); + = m_info->map_block(slot, offset, size, true); assert(!slices.empty()); #endif - size_type start = slot * (size_type)m_info.piece_length() + offset; + size_type start = slot * (size_type)m_info->piece_length() + offset; // find the file iterator and file offset size_type file_offset = start; std::vector::const_iterator file_iter; - for (file_iter = m_info.begin_files();;) + for (file_iter = m_info->begin_files(true);;) { if (file_offset < file_iter->size) break; file_offset -= file_iter->size; ++file_iter; - assert(file_iter != m_info.end_files()); + assert(file_iter != m_info->end_files(true)); } fs::path p(m_save_path / file_iter->path); @@ -890,7 +860,7 @@ namespace libtorrent } int left_to_write = size; - int slot_size = static_cast(m_info.piece_size(slot)); + int slot_size = static_cast(m_info->piece_size(slot)); if (offset + left_to_write > slot_size) left_to_write = slot_size - offset; @@ -914,7 +884,7 @@ namespace libtorrent { assert(int(slices.size()) > counter); assert(slices[counter].size == write_bytes); - assert(m_info.file_at(slices[counter].file_index).path + assert(m_info->file_at(slices[counter].file_index, true).path == file_iter->path); assert(buf_pos >= 0); @@ -942,7 +912,7 @@ namespace libtorrent #endif ++file_iter; - assert(file_iter != m_info.end_files()); + assert(file_iter != m_info->end_files(true)); fs::path p = m_save_path / file_iter->path; file_offset = 0; out = m_files.open_file( @@ -953,7 +923,7 @@ namespace libtorrent } } - storage_interface* default_storage_constructor(torrent_info const& ti + storage_interface* default_storage_constructor(boost::intrusive_ptr ti , fs::path const& path, file_pool& fp) { return new storage(ti, path, fp); @@ -1032,9 +1002,6 @@ namespace libtorrent int err = statfs(query_path.native_directory_string().c_str(), &buf); if (err == 0) { -#ifndef NDEBUG - std::cerr << "buf.f_type " << std::hex << buf.f_type << std::endl; -#endif switch (buf.f_type) { case 0x5346544e: // NTFS @@ -1067,7 +1034,7 @@ namespace libtorrent piece_manager::piece_manager( boost::shared_ptr const& torrent - , torrent_info const& ti + , boost::intrusive_ptr ti , fs::path const& save_path , file_pool& fp , disk_io_thread& io @@ -1077,7 +1044,7 @@ namespace libtorrent , m_fill_mode(true) , m_info(ti) , m_save_path(complete(save_path)) - , m_allocating(false) + , m_storage_constructor(sc) , m_io_thread(io) , m_torrent(torrent) { @@ -1119,7 +1086,9 @@ namespace libtorrent void piece_manager::async_read( peer_request const& r - , boost::function const& handler) + , boost::function const& handler + , char* buffer + , int priority) { disk_io_job j; j.storage = this; @@ -1127,7 +1096,11 @@ namespace libtorrent j.piece = r.piece; j.offset = r.start; j.buffer_size = r.length; - assert(r.length <= 16 * 1024); + j.buffer = buffer; + j.priority = priority; + // if a buffer is not specified, only one block can be read + // since that is the size of the pool allocator's buffers + assert(r.length <= 16 * 1024 || buffer != 0); m_io_thread.add_job(j, handler); } @@ -1180,7 +1153,7 @@ namespace libtorrent int slot = m_piece_to_slot[piece]; assert(slot != has_no_slot); - return m_storage->hash_for_slot(slot, ph, m_info.piece_size(piece)); + return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece)); } void piece_manager::release_files_impl() @@ -1240,7 +1213,7 @@ namespace libtorrent int piece_manager::slot_for_piece(int piece_index) const { - assert(piece_index >= 0 && piece_index < m_info.num_pieces()); + assert(piece_index >= 0 && piece_index < m_info->num_pieces()); return m_piece_to_slot[piece_index]; } @@ -1251,13 +1224,13 @@ namespace libtorrent try { assert(slot_index >= 0); - assert(slot_index < m_info.num_pieces()); + assert(slot_index < m_info->num_pieces()); assert(block_size > 0); adler32_crc crc; std::vector buf(block_size); - int num_blocks = static_cast(m_info.piece_size(slot_index)) / block_size; - int last_block_size = static_cast(m_info.piece_size(slot_index)) % block_size; + int num_blocks = static_cast(m_info->piece_size(slot_index)) / block_size; + int last_block_size = static_cast(m_info->piece_size(slot_index)) % block_size; if (last_block_size == 0) last_block_size = block_size; for (int i = 0; i < num_blocks-1; ++i) @@ -1327,6 +1300,7 @@ namespace libtorrent if (i != m_piece_hasher.end()) { assert(i->second.offset > 0); + assert(offset >= i->second.offset); if (offset == i->second.offset) { i->second.offset += size; @@ -1350,11 +1324,11 @@ namespace libtorrent { // INVARIANT_CHECK; - assert((int)have_pieces.size() == m_info.num_pieces()); + assert((int)have_pieces.size() == m_info->num_pieces()); - const int piece_size = static_cast(m_info.piece_length()); - const int last_piece_size = static_cast(m_info.piece_size( - m_info.num_pieces() - 1)); + const int piece_size = static_cast(m_info->piece_length()); + const int last_piece_size = static_cast(m_info->piece_size( + m_info->num_pieces() - 1)); assert((int)piece_data.size() >= last_piece_size); @@ -1510,7 +1484,7 @@ namespace libtorrent INVARIANT_CHECK; - assert(m_info.piece_length() > 0); + assert(m_info->piece_length() > 0); m_compact_mode = compact_mode; @@ -1520,13 +1494,13 @@ namespace libtorrent // by check_pieces. // m_storage->shuffle(); - m_piece_to_slot.resize(m_info.num_pieces(), has_no_slot); - m_slot_to_piece.resize(m_info.num_pieces(), unallocated); + m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); + m_slot_to_piece.resize(m_info->num_pieces(), unallocated); m_free_slots.clear(); m_unallocated_slots.clear(); pieces.clear(); - pieces.resize(m_info.num_pieces(), false); + pieces.resize(m_info->num_pieces(), false); num_pieces = 0; // if we have fast-resume info @@ -1631,7 +1605,7 @@ namespace libtorrent return std::make_pair(false, 1.f); } - if (int(m_unallocated_slots.size()) == m_info.num_pieces() + if (int(m_unallocated_slots.size()) == m_info->num_pieces() && !m_fill_mode) { // if there is not a single file on disk, just @@ -1673,8 +1647,8 @@ namespace libtorrent assert(!m_fill_mode); std::vector().swap(m_unallocated_slots); std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); - m_free_slots.resize(m_info.num_pieces()); - for (int i = 0; i < m_info.num_pieces(); ++i) + m_free_slots.resize(m_info->num_pieces()); + for (int i = 0; i < m_info->num_pieces(); ++i) m_free_slots[i] = i; } @@ -1694,15 +1668,15 @@ namespace libtorrent if (m_hash_to_piece.empty()) { m_current_slot = 0; - for (int i = 0; i < m_info.num_pieces(); ++i) + for (int i = 0; i < m_info->num_pieces(); ++i) { - m_hash_to_piece.insert(std::make_pair(m_info.hash_for_piece(i), i)); + m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); } std::fill(pieces.begin(), pieces.end(), false); } - m_piece_data.resize(int(m_info.piece_length())); - int piece_size = int(m_info.piece_size(m_current_slot)); + m_piece_data.resize(int(m_info->piece_length())); + int piece_size = int(m_info->piece_size(m_current_slot)); int num_read = m_storage->read(&m_piece_data[0] , m_current_slot, 0, piece_size); @@ -1905,9 +1879,9 @@ namespace libtorrent { // find the file that failed, and skip all the blocks in that file size_type file_offset = 0; - size_type current_offset = m_current_slot * m_info.piece_length(); - for (torrent_info::file_iterator i = m_info.begin_files(); - i != m_info.end_files(); ++i) + size_type current_offset = m_current_slot * m_info->piece_length(); + for (torrent_info::file_iterator i = m_info->begin_files(true); + i != m_info->end_files(true); ++i) { file_offset += i->size; if (file_offset > current_offset) break; @@ -1915,8 +1889,8 @@ namespace libtorrent assert(file_offset > current_offset); int skip_blocks = static_cast( - (file_offset - current_offset + m_info.piece_length() - 1) - / m_info.piece_length()); + (file_offset - current_offset + m_info->piece_length() - 1) + / m_info->piece_length()); for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) { @@ -1929,9 +1903,9 @@ namespace libtorrent } ++m_current_slot; - if (m_current_slot >= m_info.num_pieces()) + if (m_current_slot >= m_info->num_pieces()) { - assert(m_current_slot == m_info.num_pieces()); + assert(m_current_slot == m_info->num_pieces()); // clear the memory we've been using std::vector().swap(m_piece_data); @@ -1943,7 +1917,7 @@ namespace libtorrent assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - return std::make_pair(false, (float)m_current_slot / m_info.num_pieces()); + return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); } int piece_manager::allocate_slot_for_piece(int piece_index) @@ -1985,7 +1959,7 @@ namespace libtorrent // special case to make sure we don't use the last slot // when we shouldn't, since it's smaller than ordinary slots - if (*iter == m_info.num_pieces() - 1 && piece_index != *iter) + if (*iter == m_info->num_pieces() - 1 && piece_index != *iter) { if (m_free_slots.size() == 1) allocate_slots(1); @@ -2090,13 +2064,13 @@ namespace libtorrent } else if (m_fill_mode) { - int piece_size = int(m_info.piece_size(pos)); + int piece_size = int(m_info->piece_size(pos)); int offset = 0; for (; piece_size > 0; piece_size -= stack_buffer_size , offset += stack_buffer_size) { m_storage->write(zeroes, pos, offset - , std::min(piece_size, stack_buffer_size)); + , (std::min)(piece_size, stack_buffer_size)); } written = true; } @@ -2116,8 +2090,8 @@ namespace libtorrent boost::recursive_mutex::scoped_lock lock(m_mutex); if (m_piece_to_slot.empty()) return; - assert((int)m_piece_to_slot.size() == m_info.num_pieces()); - assert((int)m_slot_to_piece.size() == m_info.num_pieces()); + assert((int)m_piece_to_slot.size() == m_info->num_pieces()); + assert((int)m_slot_to_piece.size() == m_info->num_pieces()); for (std::vector::const_iterator i = m_free_slots.begin(); i != m_free_slots.end(); ++i) @@ -2139,7 +2113,7 @@ namespace libtorrent == m_unallocated_slots.end()); } - for (int i = 0; i < m_info.num_pieces(); ++i) + for (int i = 0; i < m_info->num_pieces(); ++i) { // Check domain of piece_to_slot's elements if (m_piece_to_slot[i] != has_no_slot) @@ -2227,7 +2201,7 @@ namespace libtorrent s << "index\tslot\tpiece\n"; - for (int i = 0; i < m_info.num_pieces(); ++i) + for (int i = 0; i < m_info->num_pieces(); ++i) { s << i << "\t" << m_slot_to_piece[i] << "\t"; s << m_piece_to_slot[i] << "\n"; diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 13309c1e7..252461bc6 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -72,6 +72,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/assert.hpp" using namespace libtorrent; using boost::tuples::tuple; @@ -150,16 +151,16 @@ namespace libtorrent torrent::torrent( session_impl& ses , aux::checker_impl& checker - , torrent_info const& tf + , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc) + , storage_constructor_type sc + , bool paused) : m_torrent_file(tf) , m_abort(false) - , m_paused(false) + , m_paused(paused) , m_just_paused(false) , m_event(tracker_request::started) , m_block_size(0) @@ -181,7 +182,7 @@ namespace libtorrent , m_ses(ses) , m_checker(checker) , m_picker(0) - , m_trackers(m_torrent_file.trackers()) + , m_trackers(m_torrent_file->trackers()) , m_last_working_tracker(-1) , m_currently_trying_tracker(0) , m_failed_trackers(0) @@ -197,24 +198,15 @@ namespace libtorrent , m_compact_mode(compact_mode) , m_default_block_size(block_size) , m_connections_initialized(true) - , m_settings(s) + , m_settings(ses.settings()) , m_storage_constructor(sc) + , m_max_uploads((std::numeric_limits::max)()) + , m_num_uploads(0) + , m_max_connections((std::numeric_limits::max)()) { -#ifndef NDEBUG - m_initial_done = 0; -#endif - - m_uploads_quota.min = 0; - m_connections_quota.min = 2; - // this will be corrected the next time the main session - // distributes resources, i.e. on average in 0.5 seconds - m_connections_quota.given = 100; - m_uploads_quota.max = std::numeric_limits::max(); - m_connections_quota.max = std::numeric_limits::max(); m_policy.reset(new policy(this)); } - torrent::torrent( session_impl& ses , aux::checker_impl& checker @@ -225,11 +217,11 @@ namespace libtorrent , tcp::endpoint const& net_interface , bool compact_mode , int block_size - , session_settings const& s - , storage_constructor_type sc) - : m_torrent_file(info_hash) + , storage_constructor_type sc + , bool paused) + : m_torrent_file(new torrent_info(info_hash)) , m_abort(false) - , m_paused(false) + , m_paused(paused) , m_just_paused(false) , m_event(tracker_request::started) , m_block_size(0) @@ -266,28 +258,20 @@ namespace libtorrent , m_compact_mode(compact_mode) , m_default_block_size(block_size) , m_connections_initialized(false) - , m_settings(s) + , m_settings(ses.settings()) , m_storage_constructor(sc) + , m_max_uploads((std::numeric_limits::max)()) + , m_num_uploads(0) + , m_max_connections((std::numeric_limits::max)()) { -#ifndef NDEBUG - m_initial_done = 0; -#endif - INVARIANT_CHECK; if (name) m_name.reset(new std::string(name)); - m_uploads_quota.min = 0; - m_connections_quota.min = 2; - // this will be corrected the next time the main session - // distributes resources, i.e. on average in 0.5 seconds - m_connections_quota.given = 100; - m_uploads_quota.max = std::numeric_limits::max(); - m_connections_quota.max = std::numeric_limits::max(); if (tracker_url) { m_trackers.push_back(announce_entry(tracker_url)); - m_torrent_file.add_tracker(tracker_url); + m_torrent_file->add_tracker(tracker_url); } m_policy.reset(new policy(this)); @@ -296,7 +280,7 @@ namespace libtorrent void torrent::start() { boost::weak_ptr self(shared_from_this()); - if (m_torrent_file.is_valid()) init(); + if (m_torrent_file->is_valid()) init(); m_announce_timer.expires_from_now(seconds(1)); m_announce_timer.async_wait(m_ses.m_strand.wrap( bind(&torrent::on_announce_disp, self, _1))); @@ -306,7 +290,7 @@ namespace libtorrent bool torrent::should_announce_dht() const { // don't announce private torrents - if (m_torrent_file.is_valid() && m_torrent_file.priv()) return false; + if (m_torrent_file->is_valid() && m_torrent_file->priv()) return false; if (m_trackers.empty()) return true; @@ -329,6 +313,14 @@ namespace libtorrent INVARIANT_CHECK; +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** DESTRUCTING TORRENT\n"; + } +#endif + assert(m_abort); if (!m_connections.empty()) disconnect_all(); @@ -336,7 +328,7 @@ namespace libtorrent std::string torrent::name() const { - if (valid_metadata()) return m_torrent_file.name(); + if (valid_metadata()) return m_torrent_file->name(); if (m_name) return *m_name; return ""; } @@ -352,22 +344,22 @@ namespace libtorrent // shared_from_this() void torrent::init() { - assert(m_torrent_file.is_valid()); - assert(m_torrent_file.num_files() > 0); - assert(m_torrent_file.total_size() >= 0); + assert(m_torrent_file->is_valid()); + assert(m_torrent_file->num_files() > 0); + assert(m_torrent_file->total_size() >= 0); - m_have_pieces.resize(m_torrent_file.num_pieces(), false); + m_have_pieces.resize(m_torrent_file->num_pieces(), false); // the shared_from_this() will create an intentional // cycle of ownership, se the hpp file for description. m_owning_storage = new piece_manager(shared_from_this(), m_torrent_file , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor); m_storage = m_owning_storage.get(); - m_block_size = calculate_block_size(m_torrent_file, m_default_block_size); + m_block_size = calculate_block_size(*m_torrent_file, m_default_block_size); m_picker.reset(new piece_picker( - static_cast(m_torrent_file.piece_length() / m_block_size) - , static_cast((m_torrent_file.total_size()+m_block_size-1)/m_block_size))); + static_cast(m_torrent_file->piece_length() / m_block_size) + , static_cast((m_torrent_file->total_size()+m_block_size-1)/m_block_size))); - std::vector const& url_seeds = m_torrent_file.url_seeds(); + std::vector const& url_seeds = m_torrent_file->url_seeds(); std::copy(url_seeds.begin(), url_seeds.end(), std::inserter(m_web_seeds , m_web_seeds.begin())); } @@ -395,7 +387,7 @@ namespace libtorrent { boost::weak_ptr self(shared_from_this()); - if (!m_torrent_file.priv()) + if (!m_torrent_file->priv()) { // announce on local network every 5 minutes m_announce_timer.expires_from_now(minutes(5)); @@ -403,7 +395,7 @@ namespace libtorrent bind(&torrent::on_announce_disp, self, _1))); // announce with the local discovery service - m_ses.announce_lsd(m_torrent_file.info_hash()); + m_ses.announce_lsd(m_torrent_file->info_hash()); } else { @@ -421,7 +413,7 @@ namespace libtorrent // TODO: There should be a way to abort an announce operation on the dht. // when the torrent is destructed assert(m_ses.m_external_listen_port > 0); - m_ses.m_dht->announce(m_torrent_file.info_hash() + m_ses.m_dht->announce(m_torrent_file->info_hash() , m_ses.m_external_listen_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } @@ -467,7 +459,7 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_torrent_file.trackers().empty()) return false; + if (m_torrent_file->trackers().empty()) return false; if (m_just_paused) { @@ -617,7 +609,7 @@ namespace libtorrent // if we don't have the metadata yet, we // cannot tell how big the torrent is. if (!valid_metadata()) return -1; - return m_torrent_file.total_size() + return m_torrent_file->total_size() - quantized_bytes_done(); } @@ -627,23 +619,23 @@ namespace libtorrent if (!valid_metadata()) return 0; - if (m_torrent_file.num_pieces() == 0) + if (m_torrent_file->num_pieces() == 0) return 0; - if (is_seed()) return m_torrent_file.total_size(); + if (is_seed()) return m_torrent_file->total_size(); - const int last_piece = m_torrent_file.num_pieces() - 1; + const int last_piece = m_torrent_file->num_pieces() - 1; size_type total_done - = m_num_pieces * m_torrent_file.piece_length(); + = m_num_pieces * m_torrent_file->piece_length(); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { - int corr = m_torrent_file.piece_size(last_piece) - - m_torrent_file.piece_length(); + int corr = m_torrent_file->piece_size(last_piece) + - m_torrent_file->piece_length(); total_done += corr; } return total_done; @@ -656,42 +648,42 @@ namespace libtorrent { INVARIANT_CHECK; - if (!valid_metadata() || m_torrent_file.num_pieces() == 0) + if (!valid_metadata() || m_torrent_file->num_pieces() == 0) return tuple(0,0); - const int last_piece = m_torrent_file.num_pieces() - 1; + const int last_piece = m_torrent_file->num_pieces() - 1; if (is_seed()) - return make_tuple(m_torrent_file.total_size() - , m_torrent_file.total_size()); + return make_tuple(m_torrent_file->total_size() + , m_torrent_file->total_size()); size_type wanted_done = (m_num_pieces - m_picker->num_have_filtered()) - * m_torrent_file.piece_length(); + * m_torrent_file->piece_length(); size_type total_done - = m_num_pieces * m_torrent_file.piece_length(); - assert(m_num_pieces < m_torrent_file.num_pieces()); + = m_num_pieces * m_torrent_file->piece_length(); + assert(m_num_pieces < m_torrent_file->num_pieces()); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { - int corr = m_torrent_file.piece_size(last_piece) - - m_torrent_file.piece_length(); + int corr = m_torrent_file->piece_size(last_piece) + - m_torrent_file->piece_length(); total_done += corr; if (m_picker->piece_priority(last_piece) != 0) wanted_done += corr; } - assert(total_done <= m_torrent_file.total_size()); - assert(wanted_done <= m_torrent_file.total_size()); + assert(total_done <= m_torrent_file->total_size()); + assert(wanted_done <= m_torrent_file->total_size()); const std::vector& dl_queue = m_picker->get_download_queue(); const int blocks_per_piece = static_cast( - m_torrent_file.piece_length() / m_block_size); + m_torrent_file->piece_length() / m_block_size); for (std::vector::const_iterator i = dl_queue.begin(); i != dl_queue.end(); ++i) @@ -724,15 +716,15 @@ namespace libtorrent == piece_picker::block_info::state_finished) { corr -= m_block_size; - corr += m_torrent_file.piece_size(last_piece) % m_block_size; + corr += m_torrent_file->piece_size(last_piece) % m_block_size; } total_done += corr; if (m_picker->piece_priority(index) != 0) wanted_done += corr; } - assert(total_done <= m_torrent_file.total_size()); - assert(wanted_done <= m_torrent_file.total_size()); + assert(total_done <= m_torrent_file->total_size()); + assert(wanted_done <= m_torrent_file->total_size()); std::map downloading_piece; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -762,10 +754,10 @@ namespace libtorrent } #ifndef NDEBUG assert(p->bytes_downloaded <= p->full_block_bytes); - int last_piece = m_torrent_file.num_pieces() - 1; + int last_piece = m_torrent_file->num_pieces() - 1; if (p->piece_index == last_piece - && p->block_index == m_torrent_file.piece_size(last_piece) / block_size()) - assert(p->full_block_bytes == m_torrent_file.piece_size(last_piece) % block_size()); + && p->block_index == m_torrent_file->piece_size(last_piece) / block_size()) + assert(p->full_block_bytes == m_torrent_file->piece_size(last_piece) % block_size()); else assert(p->full_block_bytes == block_size()); #endif @@ -781,7 +773,7 @@ namespace libtorrent #ifndef NDEBUG - if (total_done >= m_torrent_file.total_size()) + if (total_done >= m_torrent_file->total_size()) { std::copy(m_have_pieces.begin(), m_have_pieces.end() , std::ostream_iterator(std::cerr, " ")); @@ -812,8 +804,8 @@ namespace libtorrent } - assert(total_done <= m_torrent_file.total_size()); - assert(wanted_done <= m_torrent_file.total_size()); + assert(total_done <= m_torrent_file->total_size()); + assert(wanted_done <= m_torrent_file->total_size()); #endif @@ -910,14 +902,14 @@ namespace libtorrent // think that it has received all of it until this function // resets the download queue. So, we cannot do the // invariant check here since it assumes: - // (total_done == m_torrent_file.total_size()) => is_seed() + // (total_done == m_torrent_file->total_size()) => is_seed() // INVARIANT_CHECK; assert(m_storage); assert(m_storage->refcount() > 0); assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); if (m_ses.m_alerts.should_post(alert::info)) { @@ -926,7 +918,7 @@ namespace libtorrent m_ses.m_alerts.post_alert(hash_failed_alert(get_handle(), index, s.str())); } // increase the total amount of failed bytes - m_total_failed_bytes += m_torrent_file.piece_size(index); + m_total_failed_bytes += m_torrent_file->piece_size(index); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1020,6 +1012,15 @@ namespace libtorrent m_event = tracker_request::stopped; // disconnect all peers and close all // files belonging to the torrents + +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** ABORTING TORRENT\n"; + } +#endif + disconnect_all(); if (m_owning_storage.get()) m_storage->async_release_files(); m_owning_storage = 0; @@ -1040,7 +1041,7 @@ namespace libtorrent // INVARIANT_CHECK; assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1083,7 +1084,7 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file.seed_free(); + m_torrent_file->seed_free(); } } @@ -1117,7 +1118,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); bool filter_updated = m_picker->set_piece_priority(index, priority); if (filter_updated) update_peer_interest(); @@ -1133,7 +1134,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); return m_picker->piece_priority(index); } @@ -1169,7 +1170,7 @@ namespace libtorrent if (is_seed()) { pieces.clear(); - pieces.resize(m_torrent_file.num_pieces(), 1); + pieces.resize(m_torrent_file->num_pieces(), 1); return; } @@ -1194,20 +1195,20 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert(int(files.size()) == m_torrent_file.num_files()); + assert(int(files.size()) == m_torrent_file->num_files()); size_type position = 0; - if (m_torrent_file.num_pieces() == 0) return; + if (m_torrent_file->num_pieces() == 0) return; - int piece_length = m_torrent_file.piece_length(); + int piece_length = m_torrent_file->piece_length(); // initialize the piece priorities to 0, then only allow // setting higher priorities - std::vector pieces(m_torrent_file.num_pieces(), 0); + std::vector pieces(m_torrent_file->num_pieces(), 0); for (int i = 0; i < int(files.size()); ++i) { size_type start = position; - size_type size = m_torrent_file.file_at(i).size; + size_type size = m_torrent_file->file_at(i).size; if (size == 0) continue; position += size; // mark all pieces of the file with this file's priority @@ -1243,7 +1244,7 @@ namespace libtorrent // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); m_picker->set_piece_priority(index, filter ? 1 : 0); update_peer_interest(); @@ -1280,7 +1281,7 @@ namespace libtorrent assert(m_picker.get()); assert(index >= 0); - assert(index < m_torrent_file.num_pieces()); + assert(index < m_torrent_file->num_pieces()); return m_picker->piece_priority(index) == 0; } @@ -1294,7 +1295,7 @@ namespace libtorrent if (is_seed()) { bitmask.clear(); - bitmask.resize(m_torrent_file.num_pieces(), false); + bitmask.resize(m_torrent_file->num_pieces(), false); return; } @@ -1311,20 +1312,20 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert((int)bitmask.size() == m_torrent_file.num_files()); + assert((int)bitmask.size() == m_torrent_file->num_files()); size_type position = 0; - if (m_torrent_file.num_pieces()) + if (m_torrent_file->num_pieces()) { - int piece_length = m_torrent_file.piece_length(); + int piece_length = m_torrent_file->piece_length(); // mark all pieces as filtered, then clear the bits for files // that should be downloaded - std::vector piece_filter(m_torrent_file.num_pieces(), true); + std::vector piece_filter(m_torrent_file->num_pieces(), true); for (int i = 0; i < (int)bitmask.size(); ++i) { size_type start = position; - position += m_torrent_file.file_at(i).size; + position += m_torrent_file->file_at(i).size; // is the file selected for download? if (!bitmask[i]) { @@ -1359,7 +1360,7 @@ namespace libtorrent m_next_request = time_now() + seconds(tracker_retry_delay_max); tracker_request req; - req.info_hash = m_torrent_file.info_hash(); + req.info_hash = m_torrent_file->info_hash(); req.pid = m_ses.get_peer_id(); req.downloaded = m_stat.total_payload_download(); req.uploaded = m_stat.total_payload_upload(); @@ -1383,6 +1384,27 @@ namespace libtorrent return req; } + void torrent::choke_peer(peer_connection& c) + { + INVARIANT_CHECK; + + assert(!c.is_choked()); + assert(m_num_uploads > 0); + c.send_choke(); + --m_num_uploads; + } + + bool torrent::unchoke_peer(peer_connection& c) + { + INVARIANT_CHECK; + + assert(c.is_choked()); + if (m_num_uploads >= m_max_uploads) return false; + c.send_unchoke(); + ++m_num_uploads; + return true; + } + void torrent::cancel_block(piece_block block) { for (peer_iterator i = m_connections.begin() @@ -1394,7 +1416,7 @@ namespace libtorrent void torrent::remove_peer(peer_connection* p) try { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(p != 0); @@ -1433,6 +1455,9 @@ namespace libtorrent } } + if (!p->is_choked()) + --m_num_uploads; + m_policy->connection_closed(*p); p->set_peer_info(0); m_connections.erase(i); @@ -1628,6 +1653,7 @@ namespace libtorrent assert(m_connections.find(a) == m_connections.end()); // add the newly connected peer to this torrent's peer list + assert(m_connections.find(a) == m_connections.end()); m_connections.insert( std::make_pair(a, boost::get_pointer(c))); m_ses.m_connections.insert(std::make_pair(s, c)); @@ -1644,8 +1670,8 @@ namespace libtorrent #endif // TODO: post an error alert! - std::map::iterator i = m_connections.find(a); - if (i != m_connections.end()) m_connections.erase(i); +// std::map::iterator i = m_connections.find(a); +// if (i != m_connections.end()) m_connections.erase(i); m_ses.connection_failed(s, a, e.what()); c->disconnect(); } @@ -1807,6 +1833,7 @@ namespace libtorrent #endif assert(want_more_peers()); + assert(m_ses.num_connections() < m_ses.max_connections()); tcp::endpoint const& a(peerinfo->ip); assert((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); @@ -1831,6 +1858,8 @@ namespace libtorrent try { + assert(m_connections.find(a) == m_connections.end()); + // add the newly connected peer to this torrent's peer list m_connections.insert( std::make_pair(a, boost::get_pointer(c))); @@ -1843,6 +1872,7 @@ namespace libtorrent } catch (std::exception& e) { + assert(false); // TODO: post an error alert! std::map::iterator i = m_connections.find(a); if (i != m_connections.end()) m_connections.erase(i); @@ -1858,8 +1888,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_torrent_file.is_valid()); - m_torrent_file.parse_info_section(metadata); + assert(!m_torrent_file->is_valid()); + m_torrent_file->parse_info_section(metadata); init(); @@ -1869,12 +1899,12 @@ namespace libtorrent new aux::piece_checker_data); d->torrent_ptr = shared_from_this(); d->save_path = m_save_path; - d->info_hash = m_torrent_file.info_hash(); + d->info_hash = m_torrent_file->info_hash(); // add the torrent to the queue to be checked m_checker.m_torrents.push_back(d); typedef session_impl::torrent_map torrent_map; torrent_map::iterator i = m_ses.m_torrents.find( - m_torrent_file.info_hash()); + m_torrent_file->info_hash()); assert(i != m_ses.m_torrents.end()); m_ses.m_torrents.erase(i); // and notify the thread that it got another @@ -1890,7 +1920,7 @@ namespace libtorrent void torrent::attach_peer(peer_connection* p) { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(p != 0); assert(!p->is_local()); @@ -1899,6 +1929,7 @@ namespace libtorrent = m_connections.find(p->remote()); if (c != m_connections.end()) { + assert(p != c->second); // we already have a peer_connection to this ip. // It may currently be waiting for completing a // connection attempt that might fail. So, @@ -1922,6 +1953,7 @@ namespace libtorrent throw protocol_error("session is closing"); } + assert(m_connections.find(p->remote()) == m_connections.end()); peer_iterator ci = m_connections.insert( std::make_pair(p->remote(), p)).first; try @@ -1955,7 +1987,7 @@ namespace libtorrent bool torrent::want_more_peers() const { - return int(m_connections.size()) < m_connections_quota.given + return int(m_connections.size()) < m_max_connections && m_ses.m_half_open.free_slots() && !m_paused; } @@ -1994,7 +2026,9 @@ namespace libtorrent , boost::intrusive_ptr const& p , bool non_prioritized) { + assert(m_bandwidth_limit[channel].throttle() > 0); int block_size = m_bandwidth_limit[channel].throttle() / 10; + if (block_size <= 0) block_size = 1; if (m_bandwidth_limit[channel].max_assignable() > 0) { @@ -2122,7 +2156,7 @@ namespace libtorrent if ((unsigned)m_currently_trying_tracker >= m_trackers.size()) { int delay = tracker_retry_delay_min - + std::min(m_failed_trackers, (int)tracker_failed_max) + + (std::min)(m_failed_trackers, (int)tracker_failed_max) * (tracker_retry_delay_max - tracker_retry_delay_min) / tracker_failed_max; @@ -2181,14 +2215,12 @@ namespace libtorrent } pause(); } -#ifndef NDEBUG - m_initial_done = boost::get<0>(bytes_done()); -#endif return done; } std::pair torrent::check_files() { + assert(m_torrent_file->is_valid()); INVARIANT_CHECK; assert(m_owning_storage.get()); @@ -2216,9 +2248,6 @@ namespace libtorrent pause(); } -#ifndef NDEBUG - m_initial_done = boost::get<0>(bytes_done()); -#endif return progress; } @@ -2227,6 +2256,7 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + assert(m_torrent_file->is_valid()); INVARIANT_CHECK; if (!is_seed()) @@ -2257,7 +2287,7 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file.seed_free(); + m_torrent_file->seed_free(); } if (!m_connections_initialized) @@ -2286,9 +2316,6 @@ namespace libtorrent } } } -#ifndef NDEBUG - m_initial_done = boost::get<0>(bytes_done()); -#endif } alert_manager& torrent::alerts() const @@ -2340,7 +2367,7 @@ namespace libtorrent torrent_handle torrent::get_handle() const { - return torrent_handle(&m_ses, &m_checker, m_torrent_file.info_hash()); + return torrent_handle(&m_ses, &m_checker, m_torrent_file->info_hash()); } session_settings const& torrent::settings() const @@ -2351,9 +2378,7 @@ namespace libtorrent #ifndef NDEBUG void torrent::check_invariant() const { -// size_type download = m_stat.total_payload_download(); -// size_type done = boost::get<0>(bytes_done()); -// assert(download >= done - m_initial_done); + int num_uploads = 0; std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) { @@ -2364,10 +2389,12 @@ namespace libtorrent for (std::deque::const_iterator i = p.download_queue().begin() , end(p.download_queue().end()); i != end; ++i) ++num_requests[*i]; + if (!p.is_choked()) ++num_uploads; torrent* associated_torrent = p.associated_torrent().lock().get(); if (associated_torrent != this) assert(false); } + assert(num_uploads == m_num_uploads); if (has_picker()) { @@ -2380,20 +2407,26 @@ namespace libtorrent if (valid_metadata()) { - assert(m_abort || int(m_have_pieces.size()) == m_torrent_file.num_pieces()); + assert(m_abort || int(m_have_pieces.size()) == m_torrent_file->num_pieces()); } else { assert(m_abort || m_have_pieces.empty()); } +/* for (policy::const_iterator i = m_policy->begin_peer() + , end(m_policy->end_peer()); i != end; ++i) + { + assert(i->connection == const_cast(this)->connection_for(i->ip)); + } +*/ size_type total_done = quantized_bytes_done(); - if (m_torrent_file.is_valid()) + if (m_torrent_file->is_valid()) { if (is_seed()) - assert(total_done == m_torrent_file.total_size()); + assert(total_done == m_torrent_file->total_size()); else - assert(total_done != m_torrent_file.total_size()); + assert(total_done != m_torrent_file->total_size()); } else { @@ -2404,7 +2437,7 @@ namespace libtorrent assert(m_num_pieces == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); assert(!valid_metadata() || m_block_size > 0); - assert(!valid_metadata() || (m_torrent_file.piece_length() % m_block_size) == 0); + assert(!valid_metadata() || (m_torrent_file->piece_length() % m_block_size) == 0); // if (is_seed()) assert(m_picker.get() == 0); } #endif @@ -2425,15 +2458,15 @@ namespace libtorrent void torrent::set_max_uploads(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); - m_uploads_quota.max = std::max(m_uploads_quota.min, limit); + if (limit <= 0) limit = (std::numeric_limits::max)(); + m_max_uploads = limit; } void torrent::set_max_connections(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); - m_connections_quota.max = std::max(m_connections_quota.min, limit); + if (limit <= 0) limit = (std::numeric_limits::max)(); + m_max_connections = limit; } void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) @@ -2455,7 +2488,7 @@ namespace libtorrent void torrent::set_upload_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); + if (limit <= 0) limit = (std::numeric_limits::max)(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::upload_channel].throttle(limit); } @@ -2463,14 +2496,14 @@ namespace libtorrent int torrent::upload_limit() const { int limit = m_bandwidth_limit[peer_connection::upload_channel].throttle(); - if (limit == std::numeric_limits::max()) limit = -1; + if (limit == (std::numeric_limits::max)()) limit = -1; return limit; } void torrent::set_download_limit(int limit) { assert(limit >= -1); - if (limit == -1) limit = std::numeric_limits::max(); + if (limit <= 0) limit = (std::numeric_limits::max)(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::download_channel].throttle(limit); } @@ -2478,7 +2511,7 @@ namespace libtorrent int torrent::download_limit() const { int limit = m_bandwidth_limit[peer_connection::download_channel].throttle(); - if (limit == std::numeric_limits::max()) limit = -1; + if (limit == (std::numeric_limits::max)()) limit = -1; return limit; } @@ -2496,6 +2529,14 @@ namespace libtorrent } #endif +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** PAUSING TORRENT\n"; + } +#endif + disconnect_all(); m_paused = true; // tell the tracker that we stopped @@ -2528,10 +2569,6 @@ namespace libtorrent #endif m_paused = false; - m_uploads_quota.min = 0; - m_connections_quota.min = 2; - m_uploads_quota.max = std::numeric_limits::max(); - m_connections_quota.max = std::numeric_limits::max(); // tell the tracker that we're back m_event = tracker_request::started; @@ -2545,10 +2582,6 @@ namespace libtorrent { INVARIANT_CHECK; - m_connections_quota.used = (int)m_connections.size(); - m_uploads_quota.used = m_policy->num_uploads(); - m_uploads_quota.max = (int)m_connections.size(); - #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2561,10 +2594,6 @@ namespace libtorrent { // let the stats fade out to 0 m_stat.second_tick(tick_interval); - m_connections_quota.min = 0; - m_connections_quota.max = 0; - m_uploads_quota.min = 0; - m_uploads_quota.max = 0; return; } @@ -2623,6 +2652,13 @@ namespace libtorrent } accumulator += m_stat; m_stat.second_tick(tick_interval); + + m_time_scaler--; + if (m_time_scaler <= 0) + { + m_time_scaler = 10; + m_policy->pulse(); + } } bool torrent::try_connect_peer() @@ -2631,18 +2667,6 @@ namespace libtorrent return m_policy->connect_one_peer(); } - void torrent::distribute_resources(float tick_interval) - { - INVARIANT_CHECK; - - m_time_scaler--; - if (m_time_scaler <= 0) - { - m_time_scaler = settings().unchoke_interval; - m_policy->pulse(); - } - } - void torrent::async_verify_piece(int piece_index, boost::function const& f) { INVARIANT_CHECK; @@ -2650,7 +2674,7 @@ namespace libtorrent assert(m_storage); assert(m_storage->refcount() > 0); assert(piece_index >= 0); - assert(piece_index < m_torrent_file.num_pieces()); + assert(piece_index < m_torrent_file->num_pieces()); assert(piece_index < (int)m_have_pieces.size()); m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified @@ -2662,7 +2686,7 @@ namespace libtorrent { sha1_hash h(j.str); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - f(m_torrent_file.hash_for_piece(j.piece) == h); + f(m_torrent_file->hash_for_piece(j.piece) == h); } const tcp::endpoint& torrent::current_tracker() const @@ -2678,12 +2702,12 @@ namespace libtorrent assert(valid_metadata()); fp.clear(); - fp.resize(m_torrent_file.num_files(), 0.f); + fp.resize(m_torrent_file->num_files(), 0.f); - for (int i = 0; i < m_torrent_file.num_files(); ++i) + for (int i = 0; i < m_torrent_file->num_files(); ++i) { - peer_request ret = m_torrent_file.map_file(i, 0, 0); - size_type size = m_torrent_file.file_at(i).size; + peer_request ret = m_torrent_file->map_file(i, 0, 0); + size_type size = m_torrent_file->file_at(i).size; // zero sized files are considered // 100% done all the time @@ -2696,7 +2720,7 @@ namespace libtorrent size_type done = 0; while (size > 0) { - size_type bytes_step = std::min(m_torrent_file.piece_size(ret.piece) + size_type bytes_step = (std::min)(m_torrent_file->piece_size(ret.piece) - ret.start, size); if (m_have_pieces[ret.piece]) done += bytes_step; ++ret.piece; @@ -2705,7 +2729,7 @@ namespace libtorrent } assert(size == 0); - fp[i] = static_cast(done) / m_torrent_file.file_at(i).size; + fp[i] = static_cast(done) / m_torrent_file->file_at(i).size; } } @@ -2764,10 +2788,10 @@ namespace libtorrent = m_trackers[m_last_working_tracker].url; } - st.num_uploads = m_uploads_quota.used; - st.uploads_limit = m_uploads_quota.given; - st.num_connections = m_connections_quota.used; - st.connections_limit = m_connections_quota.given; + st.num_uploads = m_num_uploads; + st.uploads_limit = m_max_uploads; + st.num_connections = int(m_connections.size()); + st.connections_limit = m_max_connections; // if we don't have any metadata, stop here if (!valid_metadata()) @@ -2780,7 +2804,7 @@ namespace libtorrent // TODO: add a progress member to the torrent that will be used in this case // and that may be set by a plugin // if (m_metadata_size == 0) st.progress = 0.f; -// else st.progress = std::min(1.f, m_metadata_progress / (float)m_metadata_size); +// else st.progress = (std::min)(1.f, m_metadata_progress / (float)m_metadata_size); st.progress = 0.f; st.block_size = 0; @@ -2792,21 +2816,21 @@ namespace libtorrent // fill in status that depends on metadata - st.total_wanted = m_torrent_file.total_size(); + st.total_wanted = m_torrent_file->total_size(); if (m_picker.get() && (m_picker->num_filtered() > 0 || m_picker->num_have_filtered() > 0)) { int filtered_pieces = m_picker->num_filtered() + m_picker->num_have_filtered(); - int last_piece_index = m_torrent_file.num_pieces() - 1; + int last_piece_index = m_torrent_file->num_pieces() - 1; if (m_picker->piece_priority(last_piece_index) == 0) { - st.total_wanted -= m_torrent_file.piece_size(last_piece_index); + st.total_wanted -= m_torrent_file->piece_size(last_piece_index); --filtered_pieces; } - st.total_wanted -= filtered_pieces * m_torrent_file.piece_length(); + st.total_wanted -= filtered_pieces * m_torrent_file->piece_length(); } assert(st.total_wanted >= st.total_wanted_done); @@ -2824,7 +2848,7 @@ namespace libtorrent } else if (is_seed()) { - assert(st.total_done == m_torrent_file.total_size()); + assert(st.total_done == m_torrent_file->total_size()); st.state = torrent_status::seeding; } else if (st.total_wanted_done == st.total_wanted) diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 4538e66e8..ebef802a8 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -89,27 +89,16 @@ namespace libtorrent throw invalid_handle(); } - template - Ret call_member( + boost::shared_ptr find_torrent( session_impl* ses , aux::checker_impl* chk - , sha1_hash const& hash - , F f) + , sha1_hash const& hash) { - if (ses == 0) throw_invalid_handle(); + aux::piece_checker_data* d = chk->find_torrent(hash); + if (d != 0) return d->torrent_ptr; - if (chk) - { - mutex::scoped_lock l(chk->m_mutex); - aux::piece_checker_data* d = chk->find_torrent(hash); - if (d != 0) return f(*d->torrent_ptr); - } - - { - session_impl::mutex_t::scoped_lock l(ses->m_mutex); - boost::shared_ptr t = ses->find_torrent(hash).lock(); - if (t) return f(*t); - } + boost::shared_ptr t = ses->find_torrent(hash).lock(); + if (t) return t; // throwing directly instead of calling // the throw_invalid_handle() function @@ -122,7 +111,7 @@ namespace libtorrent void torrent_handle::check_invariant() const { - assert((m_ses == 0 && m_chk == 0) || (m_ses != 0)); + assert((m_ses == 0 && m_chk == 0) || (m_ses != 0 && m_chk != 0)); } #endif @@ -131,28 +120,40 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(max_uploads >= 2 || max_uploads == -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_max_uploads, _1, max_uploads)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_max_uploads(max_uploads); } void torrent_handle::use_interface(const char* net_interface) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::use_interface, _1, net_interface)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->use_interface(net_interface); } void torrent_handle::set_max_connections(int max_connections) const { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(max_connections >= 2 || max_connections == -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_max_connections, _1, max_connections)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_max_connections(max_connections); } void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const @@ -160,8 +161,12 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_peer_upload_limit, _1, ip, limit)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_peer_upload_limit(ip, limit); } void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const @@ -169,42 +174,64 @@ namespace libtorrent INVARIANT_CHECK; assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_peer_download_limit, _1, ip, limit)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_peer_download_limit(ip, limit); } void torrent_handle::set_upload_limit(int limit) const { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_upload_limit, _1, limit)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_upload_limit(limit); } int torrent_handle::upload_limit() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::upload_limit, _1)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->upload_limit(); } void torrent_handle::set_download_limit(int limit) const { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(limit >= -1); - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_download_limit, _1, limit)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_download_limit(limit); } int torrent_handle::download_limit() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::download_limit, _1)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->download_limit(); } void torrent_handle::move_storage( @@ -212,48 +239,72 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::move_storage, _1, save_path)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->move_storage(save_path); } bool torrent_handle::has_metadata() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::valid_metadata, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->valid_metadata(); } bool torrent_handle::is_seed() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::is_seed, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->is_seed(); } bool torrent_handle::is_paused() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::is_paused, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->is_paused(); } void torrent_handle::pause() const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::pause, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->pause(); } void torrent_handle::resume() const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::resume, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->resume(); } void torrent_handle::set_tracker_login(std::string const& name @@ -261,8 +312,12 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_tracker_login, _1, name, password)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_tracker_login(name, password); } void torrent_handle::file_progress(std::vector& progress) @@ -270,31 +325,27 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); - if (m_chk) + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) { - mutex::scoped_lock l(m_chk->m_mutex); - - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) + if (!d->processing) { - if (!d->processing) - { - torrent_info const& info = d->torrent_ptr->torrent_file(); - progress.clear(); - progress.resize(info.num_files(), 0.f); - return; - } - d->torrent_ptr->file_progress(progress); + torrent_info const& info = d->torrent_ptr->torrent_file(); + progress.clear(); + progress.resize(info.num_files(), 0.f); return; } + d->torrent_ptr->file_progress(progress); + return; } - { - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->file_progress(progress); - } + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->file_progress(progress); throw_invalid_handle(); } @@ -304,36 +355,32 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); - if (m_chk) + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) { - mutex::scoped_lock l(m_chk->m_mutex); + torrent_status st; - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) + if (d->processing) { - torrent_status st; - - if (d->processing) - { - if (d->torrent_ptr->is_allocating()) - st.state = torrent_status::allocating; - else - st.state = torrent_status::checking_files; - } + if (d->torrent_ptr->is_allocating()) + st.state = torrent_status::allocating; else - st.state = torrent_status::queued_for_checking; - st.progress = d->progress; - st.paused = d->torrent_ptr->is_paused(); - return st; + st.state = torrent_status::checking_files; } + else + st.state = torrent_status::queued_for_checking; + st.progress = d->progress; + st.paused = d->torrent_ptr->is_paused(); + return st; } - { - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->status(); - } + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->status(); throw_invalid_handle(); return torrent_status(); @@ -342,15 +389,25 @@ namespace libtorrent void torrent_handle::set_sequenced_download_threshold(int threshold) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_sequenced_download_threshold, _1, threshold)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_sequenced_download_threshold(threshold); } std::string torrent_handle::name() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::name, _1)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->name(); } @@ -358,40 +415,61 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::piece_availability, _1, boost::ref(avail))); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->piece_availability(avail); } void torrent_handle::piece_priority(int index, int priority) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_piece_priority, _1, index, priority)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_piece_priority(index, priority); } int torrent_handle::piece_priority(int index) const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::piece_priority, _1, index)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->piece_priority(index); } void torrent_handle::prioritize_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::prioritize_pieces, _1, boost::cref(pieces))); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->prioritize_pieces(pieces); } std::vector torrent_handle::piece_priorities() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + std::vector ret; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::piece_priorities, _1, boost::ref(ret))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->piece_priorities(ret); return ret; } @@ -399,8 +477,12 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::prioritize_files, _1, boost::cref(files))); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->prioritize_files(files); } // ============ start deprecation =============== @@ -408,38 +490,63 @@ namespace libtorrent void torrent_handle::filter_piece(int index, bool filter) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_piece, _1, index, filter)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filter_piece(index, filter); } void torrent_handle::filter_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_pieces, _1, boost::cref(pieces))); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filter_pieces(pieces); } bool torrent_handle::is_piece_filtered(int index) const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::is_piece_filtered, _1, index)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->is_piece_filtered(index); } std::vector torrent_handle::filtered_pieces() const { INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + std::vector ret; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filtered_pieces, _1, boost::ref(ret))); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filtered_pieces(ret); return ret; } void torrent_handle::filter_files(std::vector const& files) const { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_files, _1, files)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->filter_files(files); } // ============ end deprecation =============== @@ -449,16 +556,48 @@ namespace libtorrent { INVARIANT_CHECK; - return call_member const&>(m_ses - , m_chk, m_info_hash, bind(&torrent::trackers, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->trackers(); } - void torrent_handle::add_url_seed(std::string const& url) + void torrent_handle::add_url_seed(std::string const& url) const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::add_url_seed, _1, url)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->add_url_seed(url); + } + + void torrent_handle::remove_url_seed(std::string const& url) const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->remove_url_seed(url); + } + + std::set torrent_handle::url_seeds() const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->url_seeds(); } void torrent_handle::replace_trackers( @@ -466,17 +605,26 @@ namespace libtorrent { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::replace_trackers, _1, urls)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->replace_trackers(urls); } torrent_info const& torrent_handle::get_torrent_info() const { INVARIANT_CHECK; - if (!has_metadata()) throw_invalid_handle(); - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::torrent_file, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + boost::shared_ptr t = find_torrent(m_ses, m_chk, m_info_hash); + if (!t->valid_metadata()) throw_invalid_handle(); + return t->torrent_file(); } bool torrent_handle::is_valid() const @@ -484,16 +632,14 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) return false; + assert(m_chk); - if (m_chk) - { - mutex::scoped_lock l(m_chk->m_mutex); - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) return true; - } + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); + if (d != 0) return true; { - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::weak_ptr t = m_ses->find_torrent(m_info_hash); if (!t.expired()) return true; } @@ -507,6 +653,7 @@ namespace libtorrent std::vector piece_index; if (m_ses == 0) return entry(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -561,12 +708,12 @@ namespace libtorrent std::string bitmask; const int num_bitmask_bytes - = std::max(num_blocks_per_piece / 8, 1); + = (std::max)(num_blocks_per_piece / 8, 1); for (int j = 0; j < num_bitmask_bytes; ++j) { unsigned char v = 0; - int bits = std::min(num_blocks_per_piece - j*8, 8); + int bits = (std::min)(num_blocks_per_piece - j*8, 8); for (int k = 0; k < bits; ++k) v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) ? (1 << k) : 0; @@ -625,8 +772,12 @@ namespace libtorrent { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::save_path, _1)); + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->save_path(); } void torrent_handle::connect_peer(tcp::endpoint const& adr, int source) const @@ -634,6 +785,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -662,6 +814,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -676,6 +829,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -688,28 +842,41 @@ namespace libtorrent { INVARIANT_CHECK; + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + assert(ratio >= 0.f); - if (ratio < 1.f && ratio > 0.f) ratio = 1.f; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::set_ratio, _1, ratio)); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->set_ratio(ratio); } #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void torrent_handle::resolve_countries(bool r) { INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::resolve_countries, _1, r)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + find_torrent(m_ses, m_chk, m_info_hash)->resolve_countries(r); } bool torrent_handle::resolve_countries() const { INVARIANT_CHECK; - return call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::resolving_countries, _1)); + + if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->resolving_countries(); } #endif @@ -717,8 +884,10 @@ namespace libtorrent { INVARIANT_CHECK; - v.clear(); if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); + + v.clear(); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); @@ -750,6 +919,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); + assert(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index 4ea09aefd..d6dc27fa2 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -400,7 +400,9 @@ namespace libtorrent { if (i->first == "pieces" || i->first == "piece length" - || i->first == "length") + || i->first == "length" +// || i->first == "files" + || i->first == "name") continue; m_extra_info[i->first] = i->second; } @@ -674,7 +676,7 @@ namespace libtorrent { assert(m_piece_length > 0); - if ((m_urls.empty() && m_nodes.empty()) || m_files.empty()) + if (m_files.empty()) { // TODO: throw something here // throw @@ -824,8 +826,33 @@ namespace libtorrent m_nodes.push_back(node); } + bool torrent_info::remap_files(std::vector > const& map) + { + typedef std::vector > files_t; + + size_type offset = 0; + m_remapped_files.resize(map.size()); + + for (int i = 0; i < int(map.size()); ++i) + { + file_entry& fe = m_remapped_files[i]; + fe.path = map[i].first; + fe.offset = offset; + fe.size = map[i].second; + offset += fe.size; + } + if (offset != total_size()) + { + m_remapped_files.clear(); + return false; + } + + return true; + } + std::vector torrent_info::map_block(int piece, size_type offset - , int size) const + , int size, bool storage) const { assert(num_files() > 0); std::vector ret; @@ -839,9 +866,9 @@ namespace libtorrent std::vector::const_iterator file_iter; int counter = 0; - for (file_iter = begin_files();; ++counter, ++file_iter) + for (file_iter = begin_files(storage);; ++counter, ++file_iter) { - assert(file_iter != end_files()); + assert(file_iter != end_files(storage)); if (file_offset < file_iter->size) { file_slice f; @@ -862,11 +889,11 @@ namespace libtorrent } peer_request torrent_info::map_file(int file_index, size_type file_offset - , int size) const + , int size, bool storage) const { - assert(file_index < (int)m_files.size()); + assert(file_index < num_files(storage)); assert(file_index >= 0); - size_type offset = file_offset + m_files[file_index].offset; + size_type offset = file_offset + file_at(file_index, storage).offset; peer_request ret; ret.piece = offset / piece_length(); diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 7bd511588..981eb4caf 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -256,7 +256,7 @@ namespace libtorrent { // available input is 1,2 or 3 bytes // since we read 3 bytes at a time at most - int available_input = std::min(3, (int)std::distance(i, s.end())); + int available_input = (std::min)(3, (int)std::distance(i, s.end())); // clear input buffer std::fill(inbuf, inbuf+3, 0); @@ -305,7 +305,7 @@ namespace libtorrent m_start_time = time_now(); m_read_time = time_now(); - m_timeout.expires_at(std::min( + m_timeout.expires_at((std::min)( m_read_time + seconds(m_read_timeout) , m_start_time + seconds(m_completion_timeout))); m_timeout.async_wait(m_strand.wrap(bind( @@ -341,7 +341,7 @@ namespace libtorrent return; } - m_timeout.expires_at(std::min( + m_timeout.expires_at((std::min)( m_read_time + seconds(m_read_timeout) , m_start_time + seconds(m_completion_timeout))); m_timeout.async_wait(m_strand.wrap( @@ -365,23 +365,22 @@ namespace libtorrent , m_req(req) {} - request_callback& tracker_connection::requester() + boost::shared_ptr tracker_connection::requester() { - boost::shared_ptr r = m_requester.lock(); - assert(r); - return *r; + return m_requester.lock(); } void tracker_connection::fail(int code, char const* msg) { - if (has_requester()) requester().tracker_request_error( - m_req, code, msg); + boost::shared_ptr cb = requester(); + if (cb) cb->tracker_request_error(m_req, code, msg); close(); } void tracker_connection::fail_timeout() { - if (has_requester()) requester().tracker_request_timed_out(m_req); + boost::shared_ptr cb = requester(); + if (cb) cb->tracker_request_timed_out(m_req); close(); } @@ -548,7 +547,8 @@ namespace libtorrent m_connections.push_back(con); - if (con->has_requester()) con->requester().m_manager = this; + boost::shared_ptr cb = con->requester(); + if (cb) cb->m_manager = this; } catch (std::exception& e) { diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index d08abd359..cd500d98c 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -110,8 +110,9 @@ namespace libtorrent return; } + boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("udp tracker name lookup successful"); + if (cb) cb->debug_log("udp tracker name lookup successful"); #endif restart_read_timeout(); @@ -126,11 +127,11 @@ namespace libtorrent if (target == end) { assert(target_address.address().is_v4() != bind_interface().is_v4()); - if (has_requester()) + if (cb) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - requester().tracker_warning("the tracker only resolves to an " + cb->tracker_warning("the tracker only resolves to an " + tracker_address_type + " address, and you're listening on an " + bind_address_type + " socket. This may prevent you from receiving incoming connections."); } @@ -140,7 +141,7 @@ namespace libtorrent target_address = *target; } - if (has_requester()) requester().m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); + if (cb) cb->m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); m_target = target_address; m_socket.reset(new datagram_socket(m_name_lookup.io_service())); m_socket->open(target_address.protocol()); @@ -163,9 +164,10 @@ namespace libtorrent void udp_tracker_connection::send_udp_connect() { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("==> UDP_TRACKER_CONNECT [" + cb->debug_log("==> UDP_TRACKER_CONNECT [" + lexical_cast(tracker_req().info_hash) + "]"); } #endif @@ -259,9 +261,10 @@ namespace libtorrent m_connection_id = detail::read_int64(ptr); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + cb->debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + lexical_cast(m_connection_id) + "]"); } #endif @@ -321,9 +324,10 @@ namespace libtorrent detail::write_uint16(0, out); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + boost::shared_ptr cb = requester(); + if (cb) { - requester().debug_log("==> UDP_TRACKER_ANNOUNCE [" + cb->debug_log("==> UDP_TRACKER_ANNOUNCE [" + lexical_cast(req.info_hash) + "]"); } #endif @@ -431,14 +435,15 @@ namespace libtorrent return; } + boost::shared_ptr cb = requester(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) + if (cb) { - requester().debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); + cb->debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); } #endif - if (!has_requester()) + if (!cb) { m_man.remove_request(this); return; @@ -459,7 +464,7 @@ namespace libtorrent peer_list.push_back(e); } - requester().tracker_response(tracker_req(), peer_list, interval + cb->tracker_response(tracker_req(), peer_list, interval , complete, incomplete); m_man.remove_request(this); @@ -534,14 +539,15 @@ namespace libtorrent /*int downloaded = */detail::read_int32(buf); int incomplete = detail::read_int32(buf); - if (!has_requester()) + boost::shared_ptr cb = requester(); + if (!cb) { m_man.remove_request(this); return; } std::vector peer_list; - requester().tracker_response(tracker_req(), peer_list, 0 + cb->tracker_response(tracker_req(), peer_list, 0 , complete, incomplete); m_man.remove_request(this); diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index aefff41b1..87f950b48 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -53,38 +53,10 @@ POSSIBILITY OF SUCH DAMAGE. using boost::bind; using namespace libtorrent; -address_v4 upnp::upnp_multicast_address; -udp::endpoint upnp::upnp_multicast_endpoint; - namespace libtorrent { - bool is_local(address const& a) - { - if (a.is_v6()) return false; - address_v4 a4 = a.to_v4(); - unsigned long ip = a4.to_ulong(); - return ((ip & 0xff000000) == 0x0a000000 - || (ip & 0xfff00000) == 0xac100000 - || (ip & 0xffff0000) == 0xc0a80000); - } - - address_v4 guess_local_address(asio::io_service& ios) - { - // make a best guess of the interface we're using and its IP - udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); - for (;i != udp::resolver_iterator(); ++i) - { - // ignore the loopback - if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; - // ignore addresses that are not on a local network - if (!is_local(i->endpoint().address())) continue; - // ignore non-IPv4 addresses - if (i->endpoint().address().is_v4()) break; - } - if (i == udp::resolver_iterator()) return address_v4::any(); - return i->endpoint().address().to_v4(); - } + bool is_local(address const& a); + address_v4 guess_local_address(asio::io_service&); } upnp::upnp(io_service& ios, connection_queue& cc @@ -95,89 +67,27 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_user_agent(user_agent) , m_callback(cb) , m_retry_count(0) - , m_socket(ios) + , m_io_service(ios) + , m_strand(ios) + , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900) + , m_strand.wrap(bind(&upnp::on_reply, this, _1, _2, _3)), false) , m_broadcast_timer(ios) , m_refresh_timer(ios) - , m_strand(ios) , m_disabled(false) , m_closing(false) , m_cc(cc) { - // UPnP multicast address and port - upnp_multicast_address = address_v4::from_string("239.255.255.250"); - upnp_multicast_endpoint = udp::endpoint(upnp_multicast_address, 1900); - #ifdef TORRENT_UPNP_LOGGING m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - rebind(listen_interface); + m_retry_count = 0; + discover_device(); } upnp::~upnp() { } -void upnp::rebind(address const& listen_interface) try -{ - address_v4 bind_to = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) - { - m_local_ip = listen_interface.to_v4(); - bind_to = listen_interface.to_v4(); - if (!is_local(m_local_ip)) - { - // the local address seems to be an external - // internet address. Assume it is not behind a NAT - throw std::runtime_error("local IP is not on a local network"); - } - } - else - { - m_local_ip = guess_local_address(m_socket.io_service()); - bind_to = address_v4::any(); - } - - if (!is_local(m_local_ip)) - { - throw std::runtime_error("local host is probably not on a NATed " - "network. disabling UPnP"); - } - -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " local ip: " << m_local_ip.to_string() - << " bind to: " << bind_to.to_string() << std::endl; -#endif - - // the local interface hasn't changed - if (m_socket.is_open() - && m_socket.local_endpoint().address() == m_local_ip) - return; - - m_socket.close(); - - using namespace asio::ip::multicast; - - m_socket.open(udp::v4()); - m_socket.set_option(datagram_socket::reuse_address(true)); - m_socket.bind(udp::endpoint(bind_to, 0)); - - m_socket.set_option(join_group(upnp_multicast_address)); - m_socket.set_option(outbound_interface(bind_to)); - m_socket.set_option(hops(255)); - m_disabled = false; - - m_retry_count = 0; - discover_device(); -} -catch (std::exception& e) -{ - disable(); - std::stringstream msg; - msg << "UPnP portmapping disabled: " << e.what(); - m_callback(0, 0, msg.str()); -}; - void upnp::discover_device() try { const char msearch[] = @@ -188,20 +98,20 @@ void upnp::discover_device() try "MX:3\r\n" "\r\n\r\n"; - m_socket.async_receive_from(asio::buffer(m_receive_buffer - , sizeof(m_receive_buffer)), m_remote, m_strand.wrap(bind( - &upnp::on_reply, this, _1, _2))); - asio::error_code ec; #ifdef TORRENT_DEBUG_UPNP // simulate packet loss if (m_retry_count & 1) #endif - m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1) - , upnp_multicast_endpoint, 0, ec); + m_socket.send(msearch, sizeof(msearch) - 1, ec); if (ec) { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> Broadcast FAILED: " << ec.message() << std::endl + << "aborting" << std::endl; +#endif disable(); return; } @@ -219,7 +129,7 @@ void upnp::discover_device() try catch (std::exception&) { disable(); -} +}; void upnp::set_mappings(int tcp, int udp) { @@ -292,7 +202,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -313,17 +223,16 @@ try catch (std::exception&) { assert(false); -} +}; #endif -void upnp::on_reply(asio::error_code const& e +void upnp::on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) #ifndef NDEBUG try #endif { using namespace libtorrent::detail; - if (e) return; // parse out the url for the device @@ -338,29 +247,45 @@ try EXT: Cache-Control:max-age=180 DATE: Fri, 02 Jan 1970 08:10:38 GMT + + a notification looks like this: + + NOTIFY * HTTP/1.1 + Host:239.255.255.250:1900 + NT:urn:schemas-upnp-org:device:MediaServer:1 + NTS:ssdp:alive + Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e + USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1 + Cache-Control:max-age=900 + Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0 + */ http_parser p; try { - p.incoming(buffer::const_interval(m_receive_buffer - , m_receive_buffer + bytes_transferred)); + p.incoming(buffer::const_interval(buffer + , buffer + bytes_transferred)); } catch (std::exception& e) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with incorrect HTTP packet: " - << e.what() << ". Ignoring device" << std::endl; + << " <== Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl; #endif return; } - if (p.status_code() != 200) + if (p.status_code() != 200 && p.method() != "notify") { #ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " <== Rootdevice responded with HTTP status: " << p.status_code() - << ". Ignoring device" << std::endl; + if (p.method().empty()) + m_log << time_now_string() + << " <== Device responded with HTTP status: " << p.status_code() + << ". Ignoring device" << std::endl; + else + m_log << time_now_string() + << " <== Device with HTTP method: " << p.method() + << ". Ignoring device" << std::endl; #endif return; } @@ -431,6 +356,8 @@ try { d.mapping[0].need_update = true; d.mapping[0].local_port = m_tcp_local_port; + if (d.mapping[0].external_port == 0) + d.mapping[0].external_port = d.mapping[0].local_port; #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl; #endif @@ -439,6 +366,8 @@ try { d.mapping[1].need_update = true; d.mapping[1].local_port = m_udp_local_port; + if (d.mapping[1].external_port == 0) + d.mapping[1].external_port = d.mapping[1].local_port; #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl; #endif @@ -463,7 +392,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -488,7 +417,7 @@ catch (std::exception&) }; #endif -void upnp::post(rootdevice& d, std::stringstream const& soap +void upnp::post(upnp::rootdevice const& d, std::string const& soap , std::string const& soap_action) { std::stringstream header; @@ -496,12 +425,40 @@ void upnp::post(rootdevice& d, std::stringstream const& soap header << "POST " << d.control_url << " HTTP/1.1\r\n" "Host: " << d.hostname << ":" << d.port << "\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "Content-Length: " << soap.str().size() << "\r\n" - "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str(); + "Content-Length: " << soap.size() << "\r\n" + "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap; d.upnp_connection->sendbuffer = header.str(); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> sending: " << header.str() << std::endl; +#endif + +} + +void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) +{ + std::string soap_action = "AddPortMapping"; + + std::stringstream soap; + + soap << "\n" + "" + ""; + + soap << "" + "" << d.mapping[i].external_port << "" + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" + "" << d.mapping[i].local_port << "" + "" << c.socket().local_endpoint().address().to_string() << "" + "1" + "" << m_user_agent << "" + "" << d.lease_duration << ""; + soap << ""; + + post(d, soap.str(), soap_action); } void upnp::map_port(rootdevice& d, int i) @@ -522,14 +479,21 @@ void upnp::map_port(rootdevice& d, int i) assert(!d.upnp_connection); assert(d.service_namespace); - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2 - , boost::ref(d), i)))); + , boost::ref(d), i)), true + , bind(&upnp::create_port_mapping, this, _1, boost::ref(d), i))); - std::string soap_action = "AddPortMapping"; + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); +} +void upnp::delete_port_mapping(rootdevice& d, int i) +{ std::stringstream soap; + std::string soap_action = "DeletePortMapping"; + soap << "\n" "" @@ -537,20 +501,10 @@ void upnp::map_port(rootdevice& d, int i) soap << "" "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" - "" << d.mapping[i].local_port << "" - "" << m_local_ip.to_string() << "" - "1" - "" << m_user_agent << "" - "" << d.lease_duration << ""; + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; soap << ""; - - post(d, soap, soap_action); -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> AddPortMapping: " << soap.str() << std::endl; -#endif + post(d, soap.str(), soap_action); } // requires the mutex to be locked @@ -568,29 +522,13 @@ void upnp::unmap_port(rootdevice& d, int i) } return; } - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2 - , boost::ref(d), i)))); + , boost::ref(d), i)), true + , bind(&upnp::delete_port_mapping, this, boost::ref(d), i))); - std::string soap_action = "DeletePortMapping"; - - std::stringstream soap; - - soap << "\n" - "" - ""; - - soap << "" - "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; - soap << ""; - - post(d, soap, soap_action); -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> DeletePortMapping: " << soap.str() << std::endl; -#endif + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); } namespace @@ -838,16 +776,9 @@ void upnp::on_upnp_map_response(asio::error_code const& e m_devices.erase(d); return; } - - if (p.status_code() != 200) - { -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " <== error while adding portmap: " << p.message() << std::endl; -#endif - m_devices.erase(d); - return; - } + + // We don't want to ignore responses with return codes other than 200 + // since those might contain valid UPnP error codes error_code_parse_state s; xml_parse((char*)p.get_body().begin, (char*)p.get_body().end diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 18cbf6c2f..18fe715ee 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -67,8 +67,6 @@ namespace libtorrent { namespace if (!p.is_local()) return false; // don't send out peers that we haven't successfully connected to if (p.is_connecting()) return false; - // ut pex does not support IPv6 - if (!p.remote().address().is_v4()) return false; return true; } @@ -98,9 +96,15 @@ namespace libtorrent { namespace std::string& pla = pex["added"].string(); std::string& pld = pex["dropped"].string(); std::string& plf = pex["added.f"].string(); + std::string& pla6 = pex["added6"].string(); + std::string& pld6 = pex["dropped6"].string(); + std::string& plf6 = pex["added6.f"].string(); std::back_insert_iterator pla_out(pla); std::back_insert_iterator pld_out(pld); std::back_insert_iterator plf_out(plf); + std::back_insert_iterator pla6_out(pla6); + std::back_insert_iterator pld6_out(pld6); + std::back_insert_iterator plf6_out(plf6); std::set dropped; m_old_peers.swap(dropped); @@ -123,8 +127,6 @@ namespace libtorrent { namespace bt_peer_connection* p = dynamic_cast(i->second); if (!p) continue; - // i->first was added since the last time - detail::write_endpoint(i->first, pla_out); // no supported flags to set yet // 0x01 - peer supports encryption // 0x02 - peer is a seed @@ -132,7 +134,17 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif - detail::write_uint8(flags, plf_out); + // i->first was added since the last time + if (i->first.address().is_v4()) + { + detail::write_endpoint(i->first, pla_out); + detail::write_uint8(flags, plf_out); + } + else + { + detail::write_endpoint(i->first, pla6_out); + detail::write_uint8(flags, plf6_out); + } ++num_added; } else @@ -146,8 +158,10 @@ namespace libtorrent { namespace for (std::set::const_iterator i = dropped.begin() , end(dropped.end());i != end; ++i) { - if (!i->address().is_v4()) continue; - detail::write_endpoint(*i, pld_out); + if (i->address().is_v4()) + detail::write_endpoint(*i, pld_out); + else + detail::write_endpoint(*i, pld6_out); } m_ut_pex_msg.clear(); @@ -227,6 +241,28 @@ namespace libtorrent { namespace char flags = detail::read_uint8(fin); p.peer_from_tracker(adr, pid, peer_info::pex, flags); } + + if (entry const* p6 = pex_msg.find_key("added6")) + { + std::string const& peers6 = p6->string(); + std::string const& peer6_flags = pex_msg["added6.f"].string(); + + int num_peers = peers6.length() / 18; + char const* in = peers6.c_str(); + char const* fin = peer6_flags.c_str(); + + if (int(peer6_flags.size()) != num_peers) + return true; + + peer_id pid(0); + policy& p = m_torrent.get_policy(); + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v6_endpoint(in); + char flags = detail::read_uint8(fin); + p.peer_from_tracker(adr, pid, peer_info::pex, flags); + } + } } catch (std::exception&) { @@ -279,8 +315,13 @@ namespace libtorrent { namespace pex["dropped"].string(); std::string& pla = pex["added"].string(); std::string& plf = pex["added.f"].string(); + pex["dropped6"].string(); + std::string& pla6 = pex["added6"].string(); + std::string& plf6 = pex["added6.f"].string(); std::back_insert_iterator pla_out(pla); std::back_insert_iterator plf_out(plf); + std::back_insert_iterator pla6_out(pla6); + std::back_insert_iterator plf6_out(plf6); int num_added = 0; for (torrent::peer_iterator i = m_torrent.begin() @@ -295,8 +336,6 @@ namespace libtorrent { namespace bt_peer_connection* p = dynamic_cast(i->second); if (!p) continue; - // i->first was added since the last time - detail::write_endpoint(i->first, pla_out); // no supported flags to set yet // 0x01 - peer supports encryption // 0x02 - peer is a seed @@ -304,7 +343,17 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif - detail::write_uint8(flags, plf_out); + // i->first was added since the last time + if (i->first.address().is_v4()) + { + detail::write_endpoint(i->first, pla_out); + detail::write_uint8(flags, plf_out); + } + else + { + detail::write_endpoint(i->first, pla6_out); + detail::write_uint8(flags, plf6_out); + } ++num_added; } std::vector pex_msg; @@ -347,7 +396,7 @@ namespace libtorrent { namespace namespace libtorrent { - boost::shared_ptr create_ut_pex_plugin(torrent* t) + boost::shared_ptr create_ut_pex_plugin(torrent* t, void*) { if (t->torrent_file().priv()) { diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index 6c6745f30..a307fc9cb 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -69,9 +69,6 @@ namespace libtorrent { INVARIANT_CHECK; - // we always prefer downloading entire - // pieces from web seeds - prefer_whole_pieces(true); // we want large blocks as well, so // we can request more bytes at once request_large_blocks(true); @@ -80,6 +77,10 @@ namespace libtorrent shared_ptr tor = t.lock(); assert(tor); int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); + + // we always prefer downloading 1 MB chunks + // from web seeds + prefer_whole_pieces((1024 * 1024) / tor->torrent_file().piece_length()); // multiply with the blocks per piece since that many requests are // merged into one http request @@ -178,13 +179,16 @@ namespace libtorrent int size = r.length; const int block_size = t->block_size(); + const int piece_size = t->torrent_file().piece_length(); + peer_request pr; while (size > 0) { - int request_size = std::min(block_size, size); - peer_request pr = {r.piece, r.start + r.length - size - , request_size}; + int request_offset = r.start + r.length - size; + pr.start = request_offset % piece_size; + pr.length = (std::min)(block_size, size); + pr.piece = r.piece + request_offset / piece_size; m_requests.push_back(pr); - size -= request_size; + size -= pr.length; } proxy_settings const& ps = m_ses.web_seed_proxy(); @@ -477,8 +481,11 @@ namespace libtorrent peer_request front_request = m_requests.front(); - if (in_range.piece != front_request.piece - || in_range.start > front_request.start + int(m_piece.size())) + size_type rs = size_type(in_range.piece) * info.piece_length() + in_range.start; + size_type re = rs + in_range.length; + size_type fs = size_type(front_request.piece) * info.piece_length() + front_request.start; + size_type fe = fs + front_request.length; + if (fs < rs || fe > re) { throw std::runtime_error("invalid range in HTTP response"); } @@ -486,7 +493,7 @@ namespace libtorrent // skip the http header and the blocks we've already read. The // http_body.begin is now in sync with the request at the front // of the request queue - assert(in_range.start - int(m_piece.size()) <= front_request.start); +// assert(in_range.start - int(m_piece.size()) <= front_request.start); // the http response body consists of 3 parts // 1. the middle of a block or the ending of a block @@ -510,7 +517,7 @@ namespace libtorrent // m_piece as buffer. int piece_size = int(m_piece.size()); - int copy_size = std::min(std::min(front_request.length - piece_size + int copy_size = (std::min)((std::min)(front_request.length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); m_piece.resize(piece_size + copy_size); assert(copy_size > 0); @@ -568,7 +575,7 @@ namespace libtorrent && (m_received_body + recv_buffer.left() >= range_end - range_start)) { int piece_size = int(m_piece.size()); - int copy_size = std::min(std::min(m_requests.front().length - piece_size + int copy_size = (std::min)((std::min)(m_requests.front().length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); assert(copy_size >= 0); if (copy_size > 0) From 8cea7e34ed20bbea86bbb85ac61e14d0fbfed265 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 00:58:28 +0000 Subject: [PATCH 0111/1009] Fix python bindings. --- libtorrent/bindings/python/src/session.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 58fe76b3e..cf939d693 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -69,7 +69,7 @@ namespace : cb(callback) {} - boost::shared_ptr operator()(torrent* t) + boost::shared_ptr operator()(torrent* t, void*) { lock_gil lock; return extract >(cb(ptr(t)))(); From 7224fa71966cd6c534f9aaaa451bae7557cbece8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 01:18:32 +0000 Subject: [PATCH 0112/1009] Fix extensions. --- libtorrent/bindings/python/src/extensions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp index 1951446ed..648adbbd4 100755 --- a/libtorrent/bindings/python/src/extensions.cpp +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -142,6 +142,7 @@ void bind_extensions() // TODO move to it's own file class_("peer_connection", no_init); + class_ >("torrent_plugin", no_init); def("create_ut_pex_plugin", create_ut_pex_plugin); def("create_metadata_plugin", create_metadata_plugin); } From bf0c8ba820db3f1ca1582e6964279608258e1c90 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 02:52:52 +0000 Subject: [PATCH 0113/1009] Fix extensions. --- libtorrent/bindings/python/src/extensions.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libtorrent/bindings/python/src/extensions.cpp b/libtorrent/bindings/python/src/extensions.cpp index 648adbbd4..acb8f2457 100755 --- a/libtorrent/bindings/python/src/extensions.cpp +++ b/libtorrent/bindings/python/src/extensions.cpp @@ -109,6 +109,15 @@ namespace } // namespace unnamed + +boost::shared_ptr create_metadata_plugin_wrapper(torrent* t) { + return create_metadata_plugin(t, NULL); +} + +boost::shared_ptr create_ut_pex_plugin_wrapper(torrent* t) { + return create_ut_pex_plugin(t, NULL); +} + void bind_extensions() { class_< @@ -143,8 +152,8 @@ void bind_extensions() class_("peer_connection", no_init); class_ >("torrent_plugin", no_init); - def("create_ut_pex_plugin", create_ut_pex_plugin); - def("create_metadata_plugin", create_metadata_plugin); + def("create_ut_pex_plugin", create_ut_pex_plugin_wrapper); + def("create_metadata_plugin", create_metadata_plugin_wrapper); } From b9de236a0026b41950a60b3fc78d71fe7cef4c60 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 03:09:17 +0000 Subject: [PATCH 0114/1009] Update TODO. --- TODO | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO b/TODO index c94243c77..1f3a98e05 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* Status icons for the torrentview * Have the ui better handle not being able to connect to the daemon. * Mainwindow state saving.. Size, location, vpane position, etc.. * System tray icon From 537bc72190d56ddb36b1c518341517edabdbf990 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 04:02:07 +0000 Subject: [PATCH 0115/1009] peer num reminder --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 1f3a98e05..a2f8ff007 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,4 @@ +* Have peer numbers show what we receive from tracker_reply_alert * Have the ui better handle not being able to connect to the daemon. * Mainwindow state saving.. Size, location, vpane position, etc.. * System tray icon From c69a5661df122e12581d156b45e19a0759fac357 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 04:08:55 +0000 Subject: [PATCH 0116/1009] i18n fixes --- deluge/ui/gtkui/torrentview.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index d628c6117..fa69d2662 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -83,36 +83,36 @@ class TorrentView(listview.ListView): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) - self.add_texticon_column("Name", status_field=["state", "name"], + self.add_texticon_column(_("Name"), status_field=["state", "name"], function=cell_data_statusicon) - self.add_func_column("Size", + self.add_func_column(_("Size"), listview.cell_data_size, [long], status_field=["total_size"]) - self.add_progress_column("Progress", status_field=["progress", "state"]) - self.add_func_column("Seeders", + self.add_progress_column(_("Progress"), status_field=["progress", "state"]) + self.add_func_column(_("Seeders"), listview.cell_data_peer, [int, int], status_field=["num_seeds", "total_seeds"]) - self.add_func_column("Peers", + self.add_func_column(_("Peers"), listview.cell_data_peer, [int, int], status_field=["num_peers", "total_peers"]) - self.add_func_column("Down Speed", + self.add_func_column(_("Down Speed"), listview.cell_data_speed, [float], status_field=["download_payload_rate"]) - self.add_func_column("Up Speed", + self.add_func_column(_("Up Speed"), listview.cell_data_speed, [float], status_field=["upload_payload_rate"]) - self.add_func_column("ETA", + self.add_func_column(_("ETA"), listview.cell_data_time, [int], status_field=["eta"]) - self.add_func_column("Ratio", + self.add_func_column(_("Ratio"), listview.cell_data_ratio, [float], status_field=["ratio"]) From 7b91bf83cc4de948b2b7946b2a75a46e6c25f4ce Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 04:24:26 +0000 Subject: [PATCH 0117/1009] Fix get_pe_settings() in bindings. More debug output for encryption settings. Fix preferences glade file so that encryption settings are in correct order. --- deluge/core/core.py | 9 +- .../ui/gtkui/glade/preferences_dialog.glade | 263 ++++++++++-------- libtorrent/bindings/python/src/session.cpp | 2 +- 3 files changed, 156 insertions(+), 118 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 3f0d9d862..0ae34d3d6 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -373,9 +373,16 @@ class Core(dbus.service.Object): pe_settings.out_enc_policy = \ lt.enc_policy(self.config["enc_out_policy"]) pe_settings.in_enc_policy = lt.enc_policy(self.config["enc_in_policy"]) - pe_settings.allow_enc_level = lt.enc_level(self.config["enc_level"]) + pe_settings.allowed_enc_level = lt.enc_level(self.config["enc_level"]) pe_settings.prefer_rc4 = self.config["enc_prefer_rc4"] self.session.set_pe_settings(pe_settings) + set = self.session.get_pe_settings() + log.debug("encryption settings:\n\t\t\tout_policy: %s\n\t\t\ + in_policy: %s\n\t\t\tlevel: %s\n\t\t\tprefer_rc4: %s", + set.out_enc_policy, + set.in_enc_policy, + set.allowed_enc_level, + set.prefer_rc4) def on_set_max_connections_global(self, key, value): log.debug("max_connections_global set to %s..", value) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 4c7aa0c8c..ba9bf74be 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -38,6 +38,7 @@ False + True @@ -106,6 +107,7 @@ True Ask where to save each download True + 0 True @@ -121,6 +123,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Store all downloads in: Store all downloads in: + 0 True True radio_ask_save @@ -193,6 +196,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Full allocation preallocates all of the space that is needed for the torrent and prevents disk fragmentation Use Full Allocation + 0 True True @@ -208,6 +212,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Compact allocation only allocates space as needed Use Compact Allocation + 0 True radio_full_allocation @@ -260,6 +265,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Enable selecting files for torrents before loading Enable selecting files for torrents before loading + 0 True @@ -273,6 +279,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Prioritize first and last pieces of files in torrent Prioritize first and last pieces of files in torrent + 0 True @@ -436,6 +443,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Test Active Port + 0 False @@ -460,6 +468,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Deluge will automatically choose a different port to use every time. Use Random Ports + 0 True @@ -553,6 +562,7 @@ Distributed hash table may improve the amount of active connections. Enable Mainline DHT True + 0 True @@ -599,6 +609,7 @@ Universal Plug and Play UPnP True + 0 True True @@ -614,6 +625,7 @@ NAT Port Mapping Protocol NAT-PMP True + 0 True True @@ -630,6 +642,7 @@ µTorrent Peer-Exchange µTorrent-PeX True + 0 True True @@ -691,9 +704,9 @@ True - Disabled + Forced Enabled -Forced +Disabled False @@ -715,9 +728,9 @@ Forced True - Disabled + Forced Enabled -Forced +Disabled False @@ -744,8 +757,8 @@ Forced True Handshake -Either -Full Stream +Full Stream +Either False @@ -764,6 +777,7 @@ Full Stream True Prefer to encrypt the entire stream True + 0 True @@ -868,40 +882,71 @@ Full Stream 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -926,74 +971,43 @@ Full Stream - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1037,24 +1051,29 @@ Full Stream 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1072,24 +1091,19 @@ Full Stream - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1193,6 +1207,7 @@ Full Stream True Enable system tray icon True + 0 True @@ -1207,6 +1222,7 @@ Full Stream False Minimize to tray on close True + 0 True @@ -1225,6 +1241,7 @@ Full Stream False Start in tray True + 0 True @@ -1245,6 +1262,7 @@ Full Stream True Password protect system tray True + 0 True @@ -1337,31 +1355,15 @@ Full Stream 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1391,20 +1393,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + @@ -1455,6 +1475,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Deluge will check our servers and will tell you if a newer version has been released Be alerted about new releases + 0 True @@ -1528,6 +1549,7 @@ Thunar True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Yes, please send anonymous statistics + 0 True @@ -1630,6 +1652,7 @@ Thunar False + True @@ -1644,6 +1667,7 @@ Thunar False + True @@ -1668,6 +1692,10 @@ Thunar + + True + True + @@ -1687,6 +1715,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True + 0 @@ -1698,6 +1727,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply True + 0 @@ -1712,6 +1742,7 @@ Thunar GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-ok True + 0 diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index cf939d693..851d604ad 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -211,7 +211,7 @@ void bind_session() ) .def("set_settings", allow_threads(&session::set_settings), session_set_settings_doc) .def("set_pe_settings", allow_threads(&session::set_pe_settings), session_set_pe_settings_doc) - .def_readonly("get_pe_settings", allow_threads(&session::get_pe_settings), session_get_pe_settings_doc) + .def("get_pe_settings", allow_threads(&session::get_pe_settings), return_value_policy()) .def( "set_severity_level", allow_threads(&session::set_severity_level) , session_set_severity_level_doc From c454ea05fe88c089da78c4d53f1667d69a86395d Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 21:20:19 +0000 Subject: [PATCH 0118/1009] fix resizing --- deluge/ui/gtkui/glade/main_window.glade | 906 ++++++++++++------------ 1 file changed, 453 insertions(+), 453 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 04a171142..2bdea3b0f 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -316,7 +316,7 @@ - False + True False @@ -348,376 +348,6 @@ 1 2 10 - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - True @@ -738,54 +368,33 @@ 2 2 - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - + True 0 1 2 - 2 - 3 + 5 + 6 - + True 0 1 2 - 1 - 2 + 4 + 5 - + True 0 True @@ -794,55 +403,49 @@ 1 2 + 3 + 4 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True 0 + 0 1 - <b>Total Size:</b> + <b>Name:</b> True - 1 - 2 GTK_FILL - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - + 0 + 1 + <b>Next Announce:</b> + True - 3 - 4 + 5 + 6 GTK_FILL @@ -875,48 +478,56 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + True 0 1 - <b>Next Announce:</b> + <b>Total Size:</b> True - 5 - 6 + 1 + 2 GTK_FILL - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - + True 0 True @@ -925,37 +536,56 @@ 1 2 - 3 - 4 - + True 0 1 2 - 4 - 5 + 1 + 2 - + True 0 1 2 - 5 - 6 + 2 + 3 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 2 + 3 + GTK_FILL + + @@ -978,6 +608,376 @@ GTK_FILL + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + From fb20bcd1a2ba6e3b7632f2a4fb9cf1a155603cd3 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 22:25:54 +0000 Subject: [PATCH 0119/1009] add avail column --- deluge/ui/gtkui/torrentview.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index fa69d2662..78b42a203 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -116,7 +116,10 @@ class TorrentView(listview.ListView): listview.cell_data_ratio, [float], status_field=["ratio"]) - + self.add_func_column(_("Avail"), + listview.cell_data_ratio, + [float], + status_field=["distributed_copies"]) ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the From 0c012133cd401ef15c85e5594f07c0cfa7455630 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Sep 2007 22:37:17 +0000 Subject: [PATCH 0120/1009] fix icon size --- deluge/ui/gtkui/glade/torrent_menu.glade | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index b371e68d3..22766def1 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -14,6 +14,7 @@ gtk-media-pause + 1 @@ -31,6 +32,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-media-play + 1 From f7b537ad81c59fc9e88eb1945231c36e1c0667d4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 22:54:46 +0000 Subject: [PATCH 0121/1009] Display progress bar in torrentview properly. --- deluge/ui/gtkui/listview.py | 16 +++++++++++----- deluge/ui/gtkui/torrentview.py | 19 ++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 681cbf70b..ec6cb0aa8 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -287,10 +287,14 @@ class ListView: self.columns[header].column_indices[0]) elif column_type == "progress": column.pack_start(render) - column.add_attribute(render, "text", - self.columns[header].column_indices[text]) - column.add_attribute(render, "value", - self.columns[header].column_indices[value]) + if function is None: + column.add_attribute(render, "text", + self.columns[header].column_indices[text]) + column.add_attribute(render, "value", + self.columns[header].column_indices[value]) + else: + column.set_cell_data_func(render, function, + tuple(self.columns[header].column_indices)) elif column_type == "texticon": column.pack_start(render[pixbuf]) if function is not None: @@ -354,12 +358,14 @@ class ListView: hidden=False, position=None, status_field=None, + function=None, column_type="progress"): """Add a progress column to the listview.""" render = gtk.CellRendererProgress() self.add_column(header, render, col_types, hidden, position, - status_field, sortid, column_type=column_type, + status_field, sortid, function=function, + column_type=column_type, value=0, text=1) return True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 78b42a203..9337fc9fe 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -65,6 +65,20 @@ def cell_data_statusicon(column, cell, model, row, data): icon = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap(fname)) cell.set_property("pixbuf", icon) + +def cell_data_progress(column, cell, model, row, data): + """Display progress bar with text""" + column1, column2 = data + value = model.get_value(row, column1) + text = model.get_value(row, column2) + cell.set_property("value", value) + textstr = "%s" % _(deluge.common.TORRENT_STATE[text]) + if deluge.common.TORRENT_STATE[text] == "Downloading" or\ + deluge.common.TORRENT_STATE[text] == "Downloading Metadata" or\ + deluge.common.TORRENT_STATE[text] == "Checking" or\ + deluge.common.TORRENT_STATE[text] == "Allocating": + textstr = textstr + " %.2f%%" % value + cell.set_property("text", textstr) class TorrentView(listview.ListView): """TorrentView handles the listing of torrents.""" @@ -89,7 +103,10 @@ class TorrentView(listview.ListView): listview.cell_data_size, [long], status_field=["total_size"]) - self.add_progress_column(_("Progress"), status_field=["progress", "state"]) + self.add_progress_column(_("Progress"), + status_field=["progress", "state"], + col_types=[float, int], + function=cell_data_progress) self.add_func_column(_("Seeders"), listview.cell_data_peer, [int, int], From 007f8c8dc3b5db7ffa00f8acedb84a6a799f0626 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 23:30:03 +0000 Subject: [PATCH 0122/1009] Show paused state correctly in TorrentView. --- deluge/common.py | 3 ++- deluge/core/torrent.py | 9 ++++++++- deluge/ui/gtkui/torrentview.py | 7 +++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 1be89d5f9..0f432dab1 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -47,7 +47,8 @@ TORRENT_STATE = [ "Downloading", "Finished", "Seeding", - "Allocating" + "Allocating", + "Paused" ] def get_version(): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 0058c1285..b582e6e03 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -33,6 +33,8 @@ """Internal Torrent class""" +import deluge.common + class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ @@ -101,6 +103,11 @@ class Torrent: total_peers = status.num_peers - status.num_seeds else: total_peers = status.num_incomplete + + # Set the state to 'Paused' if the torrent is paused. + state = status.state + if status.paused: + state = deluge.common.TORRENT_STATE.index("Paused") full_status = { "name": self.handle.torrent_info().name(), @@ -111,7 +118,7 @@ class Torrent: "distributed_copies": status.distributed_copies, "total_done": status.total_done, "total_uploaded": self.total_uploaded + status.total_payload_upload, - "state": int(status.state), + "state": int(state), "paused": status.paused, "progress": progress, "next_announce": status.next_announce.seconds, diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 9337fc9fe..fe30b532a 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -54,6 +54,8 @@ def cell_data_statusicon(column, cell, model, row, data): fname = "downloading16.png" if state == deluge.common.TORRENT_STATE.index("Queued"): fname = "inactive16.png" + if state == deluge.common.TORRENT_STATE.index("Paused"): + fname = "inactive16.png" if state == deluge.common.TORRENT_STATE.index("Checking"): fname = "downloading16.png" if state == deluge.common.TORRENT_STATE.index("Allocating"): @@ -72,11 +74,12 @@ def cell_data_progress(column, cell, model, row, data): value = model.get_value(row, column1) text = model.get_value(row, column2) cell.set_property("value", value) - textstr = "%s" % _(deluge.common.TORRENT_STATE[text]) + textstr = "%s" % deluge.common.TORRENT_STATE[text] if deluge.common.TORRENT_STATE[text] == "Downloading" or\ deluge.common.TORRENT_STATE[text] == "Downloading Metadata" or\ deluge.common.TORRENT_STATE[text] == "Checking" or\ - deluge.common.TORRENT_STATE[text] == "Allocating": + deluge.common.TORRENT_STATE[text] == "Allocating" or\ + (deluge.common.TORRENT_STATE[text] == "Paused" and value < 100): textstr = textstr + " %.2f%%" % value cell.set_property("text", textstr) From e2422ab64ee1ee4ef001fc8c38f3ced0dae3ef70 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 21 Sep 2007 23:56:59 +0000 Subject: [PATCH 0123/1009] Add translated torrent state list. --- deluge/ui/gtkui/torrentview.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index fe30b532a..b5eea0639 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -70,16 +70,28 @@ def cell_data_statusicon(column, cell, model, row, data): def cell_data_progress(column, cell, model, row, data): """Display progress bar with text""" + # Translated state strings + TORRENT_STATE = [ + _("Queued"), + _("Checking"), + _("Connecting"), + _("Downloading Metadata"), + _("Downloading"), + _("Finished"), + _("Seeding"), + _("Allocating"), + _("Paused") + ] column1, column2 = data value = model.get_value(row, column1) text = model.get_value(row, column2) cell.set_property("value", value) - textstr = "%s" % deluge.common.TORRENT_STATE[text] - if deluge.common.TORRENT_STATE[text] == "Downloading" or\ - deluge.common.TORRENT_STATE[text] == "Downloading Metadata" or\ - deluge.common.TORRENT_STATE[text] == "Checking" or\ - deluge.common.TORRENT_STATE[text] == "Allocating" or\ - (deluge.common.TORRENT_STATE[text] == "Paused" and value < 100): + textstr = "%s" % TORRENT_STATE[text] + if TORRENT_STATE[text] == "Downloading" or\ + TORRENT_STATE[text] == "Downloading Metadata" or\ + TORRENT_STATE[text] == "Checking" or\ + TORRENT_STATE[text] == "Allocating" or\ + (TORRENT_STATE[text] == "Paused" and value < 100): textstr = textstr + " %.2f%%" % value cell.set_property("text", textstr) From 61216e06b0fb4a276b6d0d3c237d0251dcf48164 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 22 Sep 2007 00:21:24 +0000 Subject: [PATCH 0124/1009] have either, not full, set as default --- deluge/core/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 0ae34d3d6..8af996343 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -61,7 +61,7 @@ DEFAULT_PREFS = { "utpex": False, "enc_in_policy": 1, "enc_out_policy": 1, - "enc_level": 1, + "enc_level": 2, "enc_prefer_rc4": True, "max_connections_global": -1, "max_upload_speed": -1.0, From 0ed8ac1cdcdb9074c5bf1c9dfc998ac53f264913 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 02:53:06 +0000 Subject: [PATCH 0125/1009] MainWindow state saving. --- deluge/config.py | 5 +--- deluge/ui/gtkui/gtkui.py | 7 ++++- deluge/ui/gtkui/mainwindow.py | 49 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index 8db92a86c..cdb3a29d9 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -115,17 +115,14 @@ class Config: log.debug("Setting '%s' to %s", key, value) if self.config[key] != value: self.config[key] = value - # Whenever something is set, we should save - self.save() # Run the set_function for this key if any try: self.set_functions[key](key, value) except KeyError: pass else: - log.debug("Not set as value is same.") + log.debug("Not set because value is same.") - def get(self, key): """Get the value of 'key'. If it is an invalid key then get() will return None.""" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 0b37a1348..12b861c58 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -58,7 +58,12 @@ DEFAULT_PREFS = { "open_folder_location": "", "check_new_releases": False, "send_info": False, - "default_load_path": None + "default_load_path": None, + "window_maximized": False, + "window_x_pos": -1, + "window_y_pos": -1, + "window_width": -1, + "window_height": -1 } class GtkUI: diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 9945ae8c1..54f18c966 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -37,6 +37,7 @@ import gtk, gtk.glade import gobject import pkg_resources +from deluge.configmanager import ConfigManager from menubar import MenuBar from toolbar import ToolBar from torrentview import TorrentView @@ -48,6 +49,7 @@ from deluge.log import LOG as log class MainWindow: def __init__(self): + self.config = ConfigManager("gtkui.conf") # Get the glade file for the main window self.main_glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", @@ -55,6 +57,16 @@ class MainWindow: self.window = self.main_glade.get_widget("main_window") self.window.set_icon(deluge.common.get_logo(32)) + # Load the window state + self.load_window_geometry() + + # Keep track of window's minimization state so that we don't update the + # UI when it is minimized. + self.is_minimized = False + + # Connect events + self.window.connect("window-state-event", self.window_state_event) + self.window.connect("configure-event", self.window_configure_event) # Initialize various components of the gtkui self.menubar = MenuBar(self) @@ -66,6 +78,9 @@ class MainWindow: gobject.timeout_add(1000, self.update) def update(self): + # Don't update the UI if the the window is minimized. + if self.is_minimized == True: + return True self.torrentview.update() self.torrentdetails.update() return True @@ -79,3 +94,37 @@ class MainWindow: def quit(self): self.hide() gtk.main_quit() + + def load_window_geometry(self): + x = self.config["window_x_pos"] + y = self.config["window_y_pos"] + w = self.config["window_width"] + h = self.config["window_height"] + self.window.move(x, y) + self.window.resize(w, h) + if self.config["window_maximized"] == True: + self.window.maximize() + + def window_configure_event(self, widget, event): + if self.config["window_maximized"] == False: + self.config.set("window_x_pos", self.window.get_position()[0]) + self.config.set("window_y_pos", self.window.get_position()[1]) + self.config.set("window_width", event.width) + self.config.set("window_height", event.height) + + def window_state_event(self, widget, event): + if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED: + if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED: + self.config.set("window_maximized", True) + else: + self.config.set("window_maximized", False) + if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: + if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: + log.debug("MainWindow is minimized..") + self.is_minimized = True + else: + log.debug("MainWindow is not minimized..") + self.is_minimized = False + # Force UI update as we don't update it while minimized + self.update() + return False From 1f6798c98a0c3ead625eb3fa27347b9664f6c572 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 03:13:32 +0000 Subject: [PATCH 0126/1009] Make sure gtkui.conf is written on quit. --- deluge/ui/gtkui/gtkui.py | 3 +++ deluge/ui/gtkui/mainwindow.py | 6 ++++++ deluge/ui/gtkui/preferences.py | 3 +++ deluge/ui/gtkui/signals.py | 1 - 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 12b861c58..20cfa05d5 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -101,8 +101,11 @@ class GtkUI: # Start the gtk main loop gtk.main() + log.debug("gtkui shutting down..") # Clean-up del self.mainwindow del self.signal_receiver del self.plugins + # Make sure the config file is closed and saved to disk. + deluge.configmanager.close("gtkui.conf") del deluge.configmanager diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 54f18c966..47913791d 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -92,6 +92,12 @@ class MainWindow: self.window.hide() def quit(self): + del self.menubar + del self.toolbar + del self.torrentview + del self.torrentdetails + del self.preferences + del self.config self.hide() gtk.main_quit() diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 1625b8b49..fafd81a3a 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -78,6 +78,9 @@ class Preferences: "on_toggle": self.on_toggle }) + def __del__(self): + del self.gtkui_config + def add_page(self, name, widget): """Add a another page to the notebook""" index = self.notebook.append_page(widget) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 10970775a..ddcc0d551 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -32,7 +32,6 @@ # statement from all source files in the program, then also delete it here. import deluge.ui.functions as functions -from deluge.config import Config from deluge.log import LOG as log class Signals: From cc155d94732e0b16356e2f5a5566385a11200503 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 03:14:23 +0000 Subject: [PATCH 0127/1009] ConfigManager now tries to save configs every 5 minutes. Added close() to ConfigManager to explicitly close and save a config. --- deluge/config.py | 4 +--- deluge/configmanager.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index cdb3a29d9..187b2ffe1 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -112,16 +112,14 @@ class Config: def set(self, key, value): """Set the 'key' with 'value'.""" # Sets the "key" with "value" in the config dict - log.debug("Setting '%s' to %s", key, value) if self.config[key] != value: + log.debug("Setting '%s' to %s", key, value) self.config[key] = value # Run the set_function for this key if any try: self.set_functions[key](key, value) except KeyError: pass - else: - log.debug("Not set because value is same.") def get(self, key): """Get the value of 'key'. If it is an invalid key then get() will diff --git a/deluge/configmanager.py b/deluge/configmanager.py index 543492f36..aaf87fa00 100644 --- a/deluge/configmanager.py +++ b/deluge/configmanager.py @@ -31,6 +31,8 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import gobject + from deluge.log import LOG as log from deluge.config import Config @@ -38,11 +40,25 @@ class _ConfigManager: def __init__(self): log.debug("ConfigManager started..") self.config_files = {} + # Set a 5 minute timer to call save() + gobject.timeout_add(300000, self.save) def __del__(self): log.debug("ConfigManager stopping..") del self.config_files + def close(self, config): + """Closes a config file.""" + try: + del self.config_files[config] + except KeyError: + pass + + def save(self): + """Saves all the configs to disk.""" + for key in self.config_files.keys(): + self.config_files[key].save() + def get_config(self, config_file, defaults=None): """Get a reference to the Config object for this filename""" # Create the config object if not already created @@ -56,3 +72,6 @@ _configmanager = _ConfigManager() def ConfigManager(config, defaults=None): return _configmanager.get_config(config, defaults) + +def close(config): + return _configmanager.close(config) From 30ed1f2d86c2beda0b32b3c397ed6c8d2cefddc1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 03:30:42 +0000 Subject: [PATCH 0128/1009] Fix saving config on exit of UI. --- deluge/configmanager.py | 3 ++- deluge/ui/gtkui/addtorrentdialog.py | 1 + deluge/ui/gtkui/gtkui.py | 20 +++++++++++--------- deluge/ui/gtkui/preferences.py | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/deluge/configmanager.py b/deluge/configmanager.py index aaf87fa00..cdb8081f7 100644 --- a/deluge/configmanager.py +++ b/deluge/configmanager.py @@ -61,10 +61,11 @@ class _ConfigManager: def get_config(self, config_file, defaults=None): """Get a reference to the Config object for this filename""" + log.debug("Getting config '%s'", config_file) # Create the config object if not already created if config_file not in self.config_files.keys(): self.config_files[config_file] = Config(config_file, defaults) - + return self.config_files[config_file] # Singleton functions diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 9966e58da..db1de8e54 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -83,4 +83,5 @@ class AddTorrentDialog: result = None self.chooser.destroy() + del self.config return result diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 20cfa05d5..d1783d1d7 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -60,10 +60,10 @@ DEFAULT_PREFS = { "send_info": False, "default_load_path": None, "window_maximized": False, - "window_x_pos": -1, - "window_y_pos": -1, - "window_width": -1, - "window_height": -1 + "window_x_pos": 0, + "window_y_pos": 0, + "window_width": 640, + "window_height": 480 } class GtkUI: @@ -84,8 +84,7 @@ class GtkUI: # Make sure gtkui.conf has at least the defaults set config = ConfigManager("gtkui.conf", DEFAULT_PREFS) - del config - + # Initialize the main window self.mainwindow = MainWindow() @@ -100,12 +99,15 @@ class GtkUI: # Start the gtk main loop gtk.main() - + log.debug("gtkui shutting down..") + + # Make sure the config is saved. + config.save() + del config + # Clean-up del self.mainwindow del self.signal_receiver del self.plugins - # Make sure the config file is closed and saved to disk. - deluge.configmanager.close("gtkui.conf") del deluge.configmanager diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index fafd81a3a..d15fd1cc5 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -52,6 +52,7 @@ class Preferences: self.treeview = self.glade.get_widget("treeview") self.notebook = self.glade.get_widget("notebook") self.core = functions.get_core() + self.gtkui_config = ConfigManager("gtkui.conf") # Setup the liststore for the categories (tab pages) self.liststore = gtk.ListStore(int, str) self.treeview.set_model(self.liststore) @@ -88,7 +89,6 @@ class Preferences: def show(self): self.core_config = functions.get_config(self.core) - self.gtkui_config = ConfigManager("gtkui.conf") # Update the preferences dialog to reflect current config settings ## Downloads tab ## From 82873f812959c08ae3983eb786196d8c56bdd083 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 22 Sep 2007 14:31:48 +0000 Subject: [PATCH 0129/1009] translate torrentview --- deluge/i18n/POTFILES.in | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/i18n/POTFILES.in b/deluge/i18n/POTFILES.in index 0eff8a995..68d3ea661 100644 --- a/deluge/i18n/POTFILES.in +++ b/deluge/i18n/POTFILES.in @@ -1,3 +1,4 @@ deluge/ui/gtkui/glade/main_window.glade deluge/ui/gtkui/glade/preferences_dialog.glade deluge/plugins/queue/queue/gtkui.py +deluge/ui/gtkui/torrentview.py From d91bcbe55cfe9041338e48cd3b71267cfd78bbce Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 19:27:29 +0000 Subject: [PATCH 0130/1009] Update TorrentDetails when a different torrent is selected. --- deluge/ui/gtkui/torrentview.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index b5eea0639..d22a75ccf 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -294,4 +294,6 @@ class TorrentView(listview.ListView): def on_selection_changed(self, treeselection): """This callback is know when the selection has changed.""" log.debug("on_selection_changed") + self.window.torrentdetails.update() + From 731c51815e25b09f77b93223d08c4828110ef595 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 22 Sep 2007 19:44:11 +0000 Subject: [PATCH 0131/1009] update tracker aka force reannounce --- deluge/core/core.py | 6 ++++++ deluge/core/torrentmanager.py | 10 ++++++++++ deluge/ui/functions.py | 6 ++++++ deluge/ui/gtkui/menubar.py | 2 ++ 4 files changed, 24 insertions(+) diff --git a/deluge/core/core.py b/deluge/core/core.py index 8af996343..1201ff276 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -212,6 +212,12 @@ class Core(dbus.service.Object): # Emit the torrent_removed signal self.torrent_removed(torrent_id) + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", out_signature="") + def force_reannounce(self, torrent_id): + log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id) + self.torrents.force_reannounce(torrent_id) + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def pause_torrent(self, torrent_id): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index baf6e7c55..824176d0c 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -197,6 +197,16 @@ class TorrentManager: return True + def force_reannounce(self, torrent_id): + """Resume a torrent""" + try: + self.torrents[torrent_id].handle.force_reannounce() + except: + return False + + return True + + def load_state(self): """Load the state of the TorrentManager from the torrents.state file""" state = TorrentManagerState() diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 304f729d0..b05f29208 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -122,6 +122,12 @@ def resume_torrent(torrent_ids): for torrent_id in torrent_ids: core.resume_torrent(torrent_id) +def force_reannounce(torrent_ids): + """Reannounce to trackers""" + core = get_core() + for torrent_id in torrent_ids: + core.force_reannounce(torrent_id) + def get_torrent_status(core, torrent_id, keys): """Builds the status dictionary and returns it""" status = core.get_torrent_status(torrent_id, keys) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 85e17d09c..345af06af 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -137,6 +137,8 @@ class MenuBar: def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") + functions.force_reannounce( + self.window.torrentview.get_selected_torrents()) def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") From 28447f7564a758f3ecec11712825734028a2a4d3 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 22 Sep 2007 19:52:22 +0000 Subject: [PATCH 0132/1009] oops, fix docstring --- deluge/core/torrentmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 824176d0c..9e7d0fb69 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -198,7 +198,7 @@ class TorrentManager: return True def force_reannounce(self, torrent_id): - """Resume a torrent""" + """Force a tracker reannounce""" try: self.torrents[torrent_id].handle.force_reannounce() except: From 8f62df07323636eb2b75e9bfbf87eda769001216 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 23:02:58 +0000 Subject: [PATCH 0133/1009] Inital import of SystemTray --- deluge/configmanager.py | 2 + deluge/core/core.py | 41 ++++++++++- deluge/core/torrentmanager.py | 25 ++++++- deluge/ui/functions.py | 8 +++ deluge/ui/gtkui/glade/dgtkpopups.glade | 97 +++----------------------- deluge/ui/gtkui/glade/tray_menu.glade | 92 ++++++++++-------------- deluge/ui/gtkui/gtkui.py | 4 +- deluge/ui/gtkui/mainwindow.py | 15 +++- deluge/ui/gtkui/signals.py | 19 ++++- 9 files changed, 158 insertions(+), 145 deletions(-) diff --git a/deluge/configmanager.py b/deluge/configmanager.py index cdb8081f7..163fcdc1f 100644 --- a/deluge/configmanager.py +++ b/deluge/configmanager.py @@ -58,6 +58,8 @@ class _ConfigManager: """Saves all the configs to disk.""" for key in self.config_files.keys(): self.config_files[key].save() + # We need to return True to keep the timer active + return True def get_config(self, config_file, defaults=None): """Get a reference to the Config object for this filename""" diff --git a/deluge/core/core.py b/deluge/core/core.py index 1201ff276..468379e29 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -224,7 +224,21 @@ class Core(dbus.service.Object): log.debug("Pausing torrent %s", torrent_id) if self.torrents.pause(torrent_id): self.torrent_paused(torrent_id) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") + def pause_all_torrents(self): + """Pause all torrents in the session""" + if self.torrents.pause_all(): + # Emit 'torrent_all_paused' signal + self.torrent_all_paused() + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") + def resume_all_torrents(self): + """Resume all torrents in the session""" + if self.torrents.resume_all(): + # Emit the 'torrent_all_resumed' signal + self.torrent_all_resumed() + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") def resume_torrent(self, torrent_id): @@ -282,6 +296,19 @@ class Core(dbus.service.Object): config = pickle.dumps(config) return config + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s", + out_signature="ay") + def get_config_value(self, key): + """Get the config value for key""" + try: + value = self.config[key] + except KeyError: + return None + + value = pickle.dumps(value) + return value + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="ay") def set_config(self, config): @@ -317,10 +344,22 @@ class Core(dbus.service.Object): def torrent_paused(self, torrent_id): """Emitted when a torrent is paused""" log.debug("torrent_paused signal emitted") - + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", + signature="s") def torrent_resumed(self, torrent_id): """Emitted when a torrent is resumed""" log.debug("torrent_resumed signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge") + def torrent_all_paused(self): + """Emitted when all torrents have been paused""" + log.debug("torrent_all_paused signal emitted") + + @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge") + def torrent_all_resumed(self): + """Emitted when all torrents have been resumed""" + log.debug("torrent_all_resumed signal emitted") # Config set functions def on_set_listen_ports(self, key, value): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 9e7d0fb69..7a763f64e 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -187,6 +187,18 @@ class TorrentManager: return False return True + + def pause_all(self): + """Pauses all torrents.. Returns a list of torrents paused.""" + torrent_was_paused = False + for key in self.torrents.keys(): + try: + self.torrents[key].handle.pause() + torrent_was_paused = True + except: + log.warning("Unable to pause torrent %s", key) + + return torrent_was_paused def resume(self, torrent_id): """Resume a torrent""" @@ -197,6 +209,18 @@ class TorrentManager: return True + def resume_all(self): + """Resumes all torrents.. Returns a list of torrents resumed""" + torrent_was_resumed = False + for key in self.torrents.keys(): + try: + self.torrents[key].handle.resume() + torrent_was_resumed = True + except: + log.warning("Unable to resume torrent %s", key) + + return torrent_was_resumed + def force_reannounce(self, torrent_id): """Force a tracker reannounce""" try: @@ -206,7 +230,6 @@ class TorrentManager: return True - def load_state(self): """Load the state of the TorrentManager from the torrents.state file""" state = TorrentManagerState() diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index b05f29208..65f26b2e0 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -156,6 +156,14 @@ def get_config(core=None): config = pickle.loads(config) return config +def get_config_value(key, core=None): + if core is None: + core = get_core() + config = core.get_config_value(key) + config = "".join(chr(b) for b in config) + config = pickle.loads(config) + return config + def set_config(config, core=None): if config == {}: return diff --git a/deluge/ui/gtkui/glade/dgtkpopups.glade b/deluge/ui/gtkui/glade/dgtkpopups.glade index 63279d9a9..cdb19036d 100644 --- a/deluge/ui/gtkui/glade/dgtkpopups.glade +++ b/deluge/ui/gtkui/glade/dgtkpopups.glade @@ -1,86 +1,11 @@ - + - - True - - - True - Size - True - True - - - - - - True - Status - True - True - - - - - - True - Seeders - True - True - - - - - - True - Peers - True - True - - - - - - True - Download Speed - True - True - - - - - - True - Upload Speed - True - True - - - - - - True - Time Remaining - True - True - - - - - - True - Share Ratio - True - True - - - - Remove Torrent - True - GDK_WINDOW_TYPE_HINT_DIALOG + GTK_WIN_POS_CENTER + GDK_WINDOW_TYPE_HINT_NORMAL True True False @@ -309,14 +234,15 @@ - + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - GTK_WIN_POS_MOUSE - True - GDK_WINDOW_TYPE_HINT_DIALOG + Speed + GTK_WIN_POS_CENTER + GDK_WINDOW_TYPE_HINT_NORMAL True - False + True False @@ -329,17 +255,16 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Rate: False - + True True True diff --git a/deluge/ui/gtkui/glade/tray_menu.glade b/deluge/ui/gtkui/glade/tray_menu.glade index 22411c91d..6caae77ed 100644 --- a/deluge/ui/gtkui/glade/tray_menu.glade +++ b/deluge/ui/gtkui/glade/tray_menu.glade @@ -6,12 +6,12 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Show Deluge True - + @@ -21,12 +21,12 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Add Torrent True - + True @@ -38,17 +38,40 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Clear Finished + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Pause All True - + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-clear + gtk-media-pause + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Resume All + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-media-play 1 @@ -61,17 +84,11 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Download Limit + _Download Speed Limit True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - True @@ -83,10 +100,10 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Upload Limit + _Upload Speed Limit True @@ -105,45 +122,12 @@ - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-preferences - True - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Plu_gins - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-disconnect - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Quit True - + True diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index d1783d1d7..bed11ca51 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -63,7 +63,9 @@ DEFAULT_PREFS = { "window_x_pos": 0, "window_y_pos": 0, "window_width": 640, - "window_height": 480 + "window_height": 480, + "tray_download_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], + "tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0] } class GtkUI: diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 47913791d..1d1d4c931 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -43,6 +43,7 @@ from toolbar import ToolBar from torrentview import TorrentView from torrentdetails import TorrentDetails from preferences import Preferences +from systemtray import SystemTray import deluge.common from deluge.log import LOG as log @@ -74,6 +75,7 @@ class MainWindow: self.torrentview = TorrentView(self) self.torrentdetails = TorrentDetails(self) self.preferences = Preferences(self) + self.systemtray = SystemTray(self) gobject.timeout_add(1000, self.update) @@ -86,11 +88,22 @@ class MainWindow: return True def show(self): - self.window.show_all() + self.window.show() def hide(self): self.window.hide() + + def present(self): + self.window.present() + + def active(self): + """Returns True if the window is active, False if not.""" + return self.window.is_active() + def visible(self): + """Returns True if window is visible, False if not.""" + return self.window.get_property("visible") + def quit(self): del self.menubar del self.toolbar diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index ddcc0d551..3ae091c0c 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -42,7 +42,12 @@ class Signals: self.core.connect_to_signal("torrent_removed", self.torrent_removed_signal) self.core.connect_to_signal("torrent_paused", self.torrent_paused) - + self.core.connect_to_signal("torrent_resumed", self.torrent_resumed) + self.core.connect_to_signal("torrent_all_paused", + self.torrent_all_paused) + self.core.connect_to_signal("torrent_all_resumed", + self.torrent_all_resumed) + def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) @@ -58,3 +63,15 @@ class Signals: def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") self.ui.mainwindow.torrentview.update() + + def torrent_resumed(self, torrent_id): + log.debug("torrent_resumed signal received..") + self.ui.mainwindow.torrentview.update() + + def torrent_all_paused(self): + log.debug("torrent_all_paused signal received..") + self.ui.mainwindow.torrentview.update() + + def torrent_all_resumed(self): + log.debug("torrent_all_resumed signal received..") + self.ui.mainwindow.torrentview.update() From 56e78f0cc6386c2124780d54dd147ec1879fad5b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Sep 2007 23:04:05 +0000 Subject: [PATCH 0134/1009] Add file. --- deluge/ui/gtkui/systemtray.py | 292 ++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 deluge/ui/gtkui/systemtray.py diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py new file mode 100644 index 000000000..8a380aad5 --- /dev/null +++ b/deluge/ui/gtkui/systemtray.py @@ -0,0 +1,292 @@ +# +# systemtray.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gtk +import pkg_resources + +import deluge.ui.functions as functions +import deluge.common +from deluge.configmanager import ConfigManager +from deluge.log import LOG as log + +class SystemTray: + def __init__(self, window): + self.window = window + self.config = ConfigManager("gtkui.conf") + self.config.register_set_function("enable_system_tray", + self.on_enable_system_tray_set) + if self.config["enable_system_tray"]: + self.enable() + + def enable(self): + """Enables the system tray icon.""" + log.debug("Enabling the system tray icon..") + self.core = functions.get_core() + self.tray = gtk.status_icon_new_from_pixbuf(deluge.common.get_logo(48)) + self.tray.connect("activate", self.on_tray_clicked) + self.tray.connect("popup-menu", self.on_tray_popup) + + self.tray_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/tray_menu.glade")) + + self.tray_glade.signal_autoconnect({ + "on_menuitem_show_deluge_activate": \ + self.on_menuitem_show_deluge_activate, + "on_menuitem_add_torrent_activate": \ + self.on_menuitem_add_torrent_activate, + "on_menuitem_pause_all_activate": \ + self.on_menuitem_pause_all_activate, + "on_menuitem_resume_all_activate": \ + self.on_menuitem_resume_all_activate, + "on_menuitem_quit_activate": self.on_menuitem_quit_activate + }) + + self.tray_menu = self.tray_glade.get_widget("tray_menu") + + self.tray_glade.get_widget("download-limit-image").set_from_file( + deluge.common.get_pixmap("downloading16.png")) + self.tray_glade.get_widget("upload-limit-image").set_from_file( + deluge.common.get_pixmap("seeding16.png")) + + # Build the bandwidth speed limit menus + self.build_tray_bwsetsubmenu() + + def build_tray_bwsetsubmenu(self): + # Create the Download speed list sub-menu + submenu_bwdownset = self.build_menu_radio_list( + self.config["tray_download_speed_list"], self.tray_setbwdown, + functions.get_config_value("max_download_speed", + core=self.core), _("KiB/s"), show_notset=True, + show_other=True) + + # Create the Upload speed list sub-menu + submenu_bwupset = self.build_menu_radio_list( + self.config["tray_upload_speed_list"], self.tray_setbwup, + functions.get_config_value("max_upload_speed", core=self.core), + _("KiB/s"), show_notset=True, show_other=True) + + # Add the sub-menus to the tray menu + self.tray_glade.get_widget("menuitem_download_limit").set_submenu( + submenu_bwdownset) + self.tray_glade.get_widget("menuitem_upload_limit").set_submenu( + submenu_bwupset) + + # Show the sub-menus for all to see + submenu_bwdownset.show_all() + submenu_bwupset.show_all() + + def disable(self): + """Disables the system tray icon.""" + log.debug("Disabling the system tray icon..") + self.tray.set_visible(False) + del self.tray + del self.tray_glade + del self.tray_menu + + def on_enable_system_tray_set(self, key, value): + """Called whenever the 'enable_system_tray' config key is modified""" + if value: + self.enable() + else: + self.disable() + + def on_tray_clicked(self, icon): + """Called when the tray icon is left clicked.""" + if self.window.visible(): + if self.window.active(): + self.window.hide() + else: + self.window.present() + # Force UI update as we don't update it while minimized + self.window.update() + else: + if self.config["lock_tray"] == True: + log.debug("Implement tray locking please!") + else: + self.window.load_window_geometry() + self.window.show() + # Force UI update as we don't update it while in tray + self.window.update() + + def on_tray_popup(self, status_icon, button, activate_time): + """Called when the tray icon is right clicked.""" + if self.window.visible(): + self.tray_glade.get_widget("menuitem_show_deluge").set_active(True) + else: + self.tray_glade.get_widget("menuitem_show_deluge").set_active(False) + + self.tray_menu.popup(None, None, gtk.status_icon_position_menu, + button, activate_time, status_icon) + + def on_menuitem_show_deluge_activate(self, menuitem): + log.debug("on_menuitem_show_deluge_activate") + if menuitem.get_active() and not self.window.visible(): + if self.config["lock_tray"] == True: + self.unlock_tray() + else: + self.window.show() + elif not menuitem.get_active() and self.window.visible(): + self.window.hide() + + def on_menuitem_add_torrent_activate(self, menuitem): + log.debug("on_menuitem_add_torrent_activate") + from addtorrentdialog import AddTorrentDialog + functions.add_torrent_file(AddTorrentDialog().run()) + + def on_menuitem_pause_all_activate(self, menuitem): + log.debug("on_menuitem_pause_all_activate") + self.core.pause_all_torrents() + + def on_menuitem_resume_all_activate(self, menuitem): + log.debug("on_menuitem_resume_all_activate") + self.core.resume_all_torrents() + + def on_menuitem_quit_activate(self, menuitem): + log.debug("on_menuitem_quit_activate") + self.window.quit() + + def build_menu_radio_list(self, value_list, callback, pref_value=None, + suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, + show_other=False, show_activated=False, activated_label=None): + # Build a menu with radio menu items from a list and connect them to + # the callback. The pref_value is what you would like to test for the + # default active radio item. + if notset_label is None: + notset_label = _("Unlimited") + + if activated_label is None: + activated_label = _("Activated") + + menu = gtk.Menu() + group = None + if show_activated is False: + for value in sorted(value_list): + if suffix != None: + menuitem = gtk.RadioMenuItem(group, str(value) + " " + \ + suffix) + else: + menuitem = gtk.RadioMenuItem(group, str(value)) + + group = menuitem + + if value == pref_value and pref_value != None: + menuitem.set_active(True) + + if callback != None: + menuitem.connect("toggled", callback) + + menu.append(menuitem) + + if show_activated is True: + for value in sorted(value_list): + menuitem = gtk.RadioMenuItem(group, str(activated_label)) + + group = menuitem + + if value == pref_value and pref_value != None: + menuitem.set_active(True) + + if callback != None: + menuitem.connect("toggled", callback) + + menu.append(menuitem) + + if show_notset: + menuitem = gtk.RadioMenuItem(group, notset_label) + if pref_value < notset_lessthan and pref_value != None: + menuitem.set_active(True) + if show_activated and pref_value == 1: + menuitem.set_active(True) + menuitem.connect("toggled", callback) + menu.append(menuitem) + + # Add the Other... menuitem + if show_other is True: + menuitem = gtk.SeparatorMenuItem() + menu.append(menuitem) + menuitem = gtk.MenuItem(_("Other...")) + menuitem.connect("activate", callback) + menu.append(menuitem) + + return menu + + def tray_setbwdown(self, widget, data=None): + self.setbwlimit(widget, _("Download"), "max_download_speed", + "tray_download_speed_list") + + def tray_setbwup(self, widget, data=None): + self.setbwlimit(widget, _("Upload"), "max_upload_speed", + "tray_upload_speed_list") + + def setbwlimit(self, widget, string, core_key, ui_key): + """Sets the bandwidth limit based on the user selection.""" + value = widget.get_children()[0].get_text().rstrip(" " + + _("KiB/s")) + if value == _("Unlimited"): + value = -1 + + if value == _("Other..."): + dialog_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/dgtkpopups.glade")) + speed_dialog = dialog_glade.get_widget("speed_dialog") + spin_title = dialog_glade.get_widget("spin_title") + spin_title.set_text(_("%s Speed (KiB/s):" % string)) + spin_speed = dialog_glade.get_widget("spin_speed") + spin_speed.set_value( + functions.get_config_value(core_key, core=self.core)) + spin_speed.select_region(0, -1) + response = speed_dialog.run() + if response == 1: # OK Response + value = spin_speed.get_value() + else: + speed_dialog.destroy() + return + speed_dialog.destroy() + + # Set the config in the core + value = float(value) + config_to_set = {core_key: value} + functions.set_config(config_to_set, core=self.core) + + # Update the tray speed limit list + if value not in self.config[ui_key] and value >= 0: + # We prepend this value and remove the last value in the list + self.config[ui_key].insert(0, value) + self.config[ui_key].pop() + # Re-build the menu + self.build_tray_bwsetsubmenu() + + def unlock_tray(self): + log.debug("Tray locking needs implementation..!") From d80debfbea2ab2d46f64219df2bdcd7498395493 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 23 Sep 2007 00:50:42 +0000 Subject: [PATCH 0135/1009] Save and restore the main window's vpane position. --- deluge/ui/gtkui/gtkui.py | 1 + deluge/ui/gtkui/mainwindow.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index bed11ca51..f3f4c3250 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -64,6 +64,7 @@ DEFAULT_PREFS = { "window_y_pos": 0, "window_width": 640, "window_height": 480, + "window_pane_position": -1, "tray_download_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], "tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0] } diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 1d1d4c931..5865a865b 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -58,6 +58,7 @@ class MainWindow: self.window = self.main_glade.get_widget("main_window") self.window.set_icon(deluge.common.get_logo(32)) + self.vpaned = self.main_glade.get_widget("vpaned") # Load the window state self.load_window_geometry() @@ -68,6 +69,7 @@ class MainWindow: # Connect events self.window.connect("window-state-event", self.window_state_event) self.window.connect("configure-event", self.window_configure_event) + self.vpaned.connect("notify::position", self.vpaned_position_event) # Initialize various components of the gtkui self.menubar = MenuBar(self) @@ -123,7 +125,9 @@ class MainWindow: self.window.resize(w, h) if self.config["window_maximized"] == True: self.window.maximize() - + self.vpaned.set_position( + self.config["window_height"] - self.config["window_pane_position"]) + def window_configure_event(self, widget, event): if self.config["window_maximized"] == False: self.config.set("window_x_pos", self.window.get_position()[0]) @@ -147,3 +151,8 @@ class MainWindow: # Force UI update as we don't update it while minimized self.update() return False + + def vpaned_position_event(self, obj, param): + self.config.set("window_pane_position", + self.config["window_height"] - self.vpaned.get_position()) + From 3270f2374774d6b637b39e8c8148463d664174db Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 23 Sep 2007 02:24:18 +0000 Subject: [PATCH 0136/1009] Initial import of StatusBar --- deluge/core/core.py | 22 +++- deluge/ui/gtkui/mainwindow.py | 3 + deluge/ui/gtkui/statusbar.py | 107 ++++++++++++++++++ libtorrent/bindings/python/src/docstrings.cpp | 3 +- libtorrent/bindings/python/src/session.cpp | 5 + 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 deluge/ui/gtkui/statusbar.py diff --git a/deluge/core/core.py b/deluge/core/core.py index 468379e29..6c91eb030 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -325,7 +325,27 @@ class Core(dbus.service.Object): def get_listen_port(self): """Returns the active listen port""" return self.session.listen_port() - + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + out_signature="i") + def get_num_connections(self): + """Returns the current number of connections""" + return self.session.num_connections() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + out_signature="d") + def get_download_rate(self): + """Returns the payload download rate""" + # print self.session.status().payload_download_rate + return self.session.status().payload_download_rate +# return 0.0 + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + out_signature="d") + def get_upload_rate(self): + """Returns the payload upload rate""" + return self.session.status().payload_upload_rate + # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 5865a865b..6e36f4209 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -44,6 +44,7 @@ from torrentview import TorrentView from torrentdetails import TorrentDetails from preferences import Preferences from systemtray import SystemTray +from statusbar import StatusBar import deluge.common from deluge.log import LOG as log @@ -78,6 +79,7 @@ class MainWindow: self.torrentdetails = TorrentDetails(self) self.preferences = Preferences(self) self.systemtray = SystemTray(self) + self.statusbar = StatusBar(self) gobject.timeout_add(1000, self.update) @@ -87,6 +89,7 @@ class MainWindow: return True self.torrentview.update() self.torrentdetails.update() + self.statusbar.update() return True def show(self): diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py new file mode 100644 index 000000000..d9c79f5ff --- /dev/null +++ b/deluge/ui/gtkui/statusbar.py @@ -0,0 +1,107 @@ +# +# statusbar.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gtk + +import deluge.common +import deluge.ui.functions as functions + +class StatusBar: + def __init__(self, window): + self.window = window + self.statusbar = self.window.main_glade.get_widget("statusbar") + self.core = functions.get_core() + + # Add a HBox to the statusbar after removing the initial label widget + self.hbox = gtk.HBox() + self.hbox.set_spacing(5) + frame = self.statusbar.get_children()[0] + frame.remove(frame.get_children()[0]) + frame.add(self.hbox) + + # Add in images and labels + image = gtk.Image() + image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) + self.hbox.pack_start(image, expand=False, fill=False) + self.label_connections = gtk.Label() + self.hbox.pack_start(self.label_connections, expand=False, fill=False) + image = gtk.Image() + image.set_from_file(deluge.common.get_pixmap("downloading16.png")) + self.hbox.pack_start(image, expand=False, fill=False) + self.label_download_speed = gtk.Label() + self.hbox.pack_start(self.label_download_speed, + expand=False, fill=False) + image = gtk.Image() + image.set_from_file(deluge.common.get_pixmap("seeding16.png")) + self.hbox.pack_start(image, expand=False, fill=False) + self.label_upload_speed = gtk.Label() + self.hbox.pack_start(self.label_upload_speed, + expand=False, fill=False) + + # Update once before showing + self.update() + self.statusbar.show_all() + + def update(self): + # Set the max connections label + max_connections = functions.get_config_value("max_connections_global", + core=self.core) + if max_connections < 0: + max_connections = _("Unlimited") + + self.label_connections.set_text("%s (%s)" % ( + self.core.get_num_connections(), max_connections)) + + # Set the download speed label + max_download_speed = functions.get_config_value("max_download_speed", + core=self.core) + if max_download_speed < 0: + max_download_speed = _("Unlimited") + else: + max_download_speed = "%s %s" % (max_download_speed, _("KiB/s")) + + self.label_download_speed.set_text("%s/s (%s)" % ( + deluge.common.fsize(self.core.get_download_rate()), + max_download_speed)) + + # Set the upload speed label + max_upload_speed = functions.get_config_value("max_upload_speed", + core=self.core) + if max_upload_speed < 0: + max_upload_speed = _("Unlimited") + else: + max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s")) + + self.label_upload_speed.set_text("%s/s (%s)" % ( + deluge.common.fsize(self.core.get_upload_rate()), + max_upload_speed)) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index ae1e8e7ba..30be6e029 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -153,7 +153,8 @@ char const* session_set_max_half_open_connections_doc = "-1 as the limit, means to have no limit. When limiting the number of\n" "simultaneous connection attempts, peers will be put in a queue waiting\n" "for their turn to get connected."; - +char const* session_num_connections_doc = + ""; char const* session_set_settings_doc = ""; char const* session_set_pe_settings_doc = diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 851d604ad..3717ebadd 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -44,6 +44,7 @@ extern char const* session_upload_rate_limit_doc; extern char const* session_set_max_uploads_doc; extern char const* session_set_max_connections_doc; extern char const* session_set_max_half_open_connections_doc; +extern char const* session_num_connections_doc; extern char const* session_set_settings_doc; extern char const* session_set_pe_settings_doc; extern char const* session_get_pe_settings_doc; @@ -209,6 +210,10 @@ void bind_session() "set_max_half_open_connections", allow_threads(&session::set_max_half_open_connections) , session_set_max_half_open_connections_doc ) + .def( + "num_connections", allow_threads(&session::num_connections) + , session_num_connections_doc + ) .def("set_settings", allow_threads(&session::set_settings), session_set_settings_doc) .def("set_pe_settings", allow_threads(&session::set_pe_settings), session_set_pe_settings_doc) .def("get_pe_settings", allow_threads(&session::get_pe_settings), return_value_policy()) From 9cb26544e1b3a0d6cd96965538709c31ee985dc9 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 23 Sep 2007 08:06:44 +0000 Subject: [PATCH 0137/1009] fix icons to use theme properly --- deluge/data/share/applications/deluge.desktop | 3 +-- deluge/ui/gtkui/systemtray.py | 2 +- setup.py | 24 +++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/deluge/data/share/applications/deluge.desktop b/deluge/data/share/applications/deluge.desktop index 570cd9ed8..85cd5ea1b 100644 --- a/deluge/data/share/applications/deluge.desktop +++ b/deluge/data/share/applications/deluge.desktop @@ -4,10 +4,9 @@ Encoding=UTF-8 Name=Deluge BitTorrent Client Comment=Bittorrent client written in PyGTK Exec=deluge -Icon=deluge.xpm +Icon=deluge.png Terminal=false Type=Application Categories=Application;Network StartupNotify=true MimeType=application/x-bittorrent; -GenericName= diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 8a380aad5..fc4fcb560 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -52,7 +52,7 @@ class SystemTray: """Enables the system tray icon.""" log.debug("Enabling the system tray icon..") self.core = functions.get_core() - self.tray = gtk.status_icon_new_from_pixbuf(deluge.common.get_logo(48)) + self.tray = gtk.status_icon_new_from_icon_name('deluge') self.tray.connect("activate", self.on_tray_clicked) self.tray.connect("popup-menu", self.on_tray_popup) diff --git a/setup.py b/setup.py index 1f098d887..20f575a37 100644 --- a/setup.py +++ b/setup.py @@ -162,29 +162,29 @@ setup( "i18n/*/LC_MESSAGES/*.mo"]}, data_files = [('/usr/share/deluge/icons/scalable/apps', [ 'deluge/data/icons/scalable/apps/deluge.svg']), - ('/usr/share/deluge/icons/hicolor/128x128/apps', [ + ('/usr/share/icons/hicolor/128x128/apps', [ 'deluge/data/icons/hicolor/128x128/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/16x16/apps', [ + ('/usr/share/icons/hicolor/16x16/apps', [ 'deluge/data/icons/hicolor/16x16/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/192x192/apps', [ + ('/usr/share/icons/hicolor/192x192/apps', [ 'deluge/data/icons/hicolor/192x192/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/22x22/apps', [ + ('/usr/share/icons/hicolor/22x22/apps', [ 'deluge/data/icons/hicolor/22x22/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/24x24/apps', [ + ('/usr/share/icons/hicolor/24x24/apps', [ 'deluge/data/icons/hicolor/24x24/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/256x256/apps', [ + ('/usr/share/icons/hicolor/256x256/apps', [ 'deluge/data/icons/hicolor/256x256/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/32x32/apps', [ + ('/usr/share/icons/hicolor/32x32/apps', [ 'deluge/data/icons/hicolor/32x32/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/36x36/apps', [ + ('/usr/share/icons/hicolor/36x36/apps', [ 'deluge/data/icons/hicolor/36x36/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/48x48/apps', [ + ('/usr/share/icons/hicolor/48x48/apps', [ 'deluge/data/icons/hicolor/48x48/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/64x64/apps', [ + ('/usr/share/icons/hicolor/64x64/apps', [ 'deluge/data/icons/hicolor/64x64/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/72x72/apps', [ + ('/usr/share/icons/hicolor/72x72/apps', [ 'deluge/data/icons/hicolor/72x72/apps/deluge.png']), - ('/usr/share/deluge/icons/hicolor/96x96/apps', [ + ('/usr/share/icons/hicolor/96x96/apps', [ 'deluge/data/icons/hicolor/96x96/apps/deluge.png']), ('/usr/share/applications', [ 'deluge/data/share/applications/deluge.desktop'])], From f6640db58ea1e9193c4ef3a377ba91aead790965 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 23 Sep 2007 08:53:42 +0000 Subject: [PATCH 0138/1009] clear torrentdetails when a torrent is removed --- deluge/ui/gtkui/torrentdetails.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 5c31edab5..c99605639 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -84,6 +84,7 @@ class TorrentDetails: selected = selected[0] else: # No torrent is selected in the torrentview + self.clear() return # Get the torrent status @@ -127,4 +128,26 @@ class TorrentDetails: self.tracker.set_text(status["tracker"]) self.next_announce.set_text( deluge.common.ftime(status["next_announce"])) - + + def clear(self): + # Only update if this page is showing + if self.notebook.page_num(self.details_tab) is \ + self.notebook.get_current_page(): + self.name.set_text("") + self.total_size.set_text("") + self.num_files.set_text("") + self.pieces.set_text("") + self.availability.set_text("") + self.total_downloaded.set_text("") + self.total_uploaded.set_text("") + self.download_speed.set_text("") + self.upload_speed.set_text("") + self.seeders.set_text("") + self.peers.set_text("") + self.progress_bar.set_fraction(0.0) + self.progress_bar.set_text("") + self.share_ratio.set_text("") + self.tracker.set_text("") + self.tracker_status.set_text("") + self.next_announce.set_text("") + self.eta.set_text("") From e47e7920cc6b3e3d1eeda958d0ba5d5f90f64e7b Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 23 Sep 2007 09:41:53 +0000 Subject: [PATCH 0139/1009] change where clearing of torrentdetails is done --- deluge/ui/gtkui/signals.py | 1 + deluge/ui/gtkui/torrentdetails.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 3ae091c0c..1698af094 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -59,6 +59,7 @@ class Signals: log.debug("torrent id: %s", torrent_id) # Remove the torrent from the treeview self.ui.mainwindow.torrentview.remove_row(torrent_id) + self.ui.mainwindow.torrentdetails.clear() def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index c99605639..f7fd2e0c2 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -84,7 +84,6 @@ class TorrentDetails: selected = selected[0] else: # No torrent is selected in the torrentview - self.clear() return # Get the torrent status From 10632476ce230d63e15cf37d0e9932ae579b7bc7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 23 Sep 2007 23:30:46 +0000 Subject: [PATCH 0140/1009] Have core save the config file on shutdown. --- deluge/core/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/core/core.py b/deluge/core/core.py index 6c91eb030..c6b15dba4 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -156,6 +156,8 @@ class Core(dbus.service.Object): del self.torrents self.plugins.shutdown() del self.plugins + # Make sure the config file has been saved + self.config.save() del self.config del deluge.configmanager del self.session From c86a17b42b21cbe8d8b57fa0e546fa041424a5bd Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 23 Sep 2007 23:38:54 +0000 Subject: [PATCH 0141/1009] Some interactivty tweaks. --- deluge/ui/gtkui/preferences.py | 6 ++++++ deluge/ui/gtkui/systemtray.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index d15fd1cc5..af8f8b173 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -285,6 +285,12 @@ class Preferences: # Update the configuration self.core_config.update(config_to_set) + # Re-show the dialog to make sure everything has been updated + self.show() + + # Update the UI + self.window.update() + def hide(self): self.pref_dialog.hide() diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index fc4fcb560..5a6d2a418 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -287,6 +287,8 @@ class SystemTray: self.config[ui_key].pop() # Re-build the menu self.build_tray_bwsetsubmenu() + # Update the UI + self.window.update() def unlock_tray(self): log.debug("Tray locking needs implementation..!") From 1f0579d5906523b9f0b7a2c0d86fe11eb25fcf2c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 23 Sep 2007 23:51:29 +0000 Subject: [PATCH 0142/1009] Update TODO. --- TODO | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index a2f8ff007..fdb76310f 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,6 @@ * Have peer numbers show what we receive from tracker_reply_alert * Have the ui better handle not being able to connect to the daemon. -* Mainwindow state saving.. Size, location, vpane position, etc.. -* System tray icon +* Tray locking and other tray options (close to tray, start in tray..) * Add state saving to listview.. this includes saving column size and position. * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. @@ -17,3 +16,7 @@ * Add the tracker responses to the torrent details * Fast resume saving * Restart daemon function +* Sync the details pane to current trunk +* Automatically save torrent state every ~5 minutes, much like how + configmanager does it. +* Docstrings! From 5043c8bf29be8a6b82dbc5d4079a912696feb4ba Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 24 Sep 2007 03:54:35 +0000 Subject: [PATCH 0143/1009] activate test active port button --- .../ui/gtkui/glade/preferences_dialog.glade | 225 +++++++++--------- deluge/ui/gtkui/preferences.py | 10 +- 2 files changed, 121 insertions(+), 114 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index ba9bf74be..7592595c2 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -444,6 +444,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Test Active Port 0 + False @@ -882,71 +883,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -971,43 +941,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1051,29 +1052,24 @@ Either 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1091,19 +1087,24 @@ Either - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1355,15 +1356,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1393,38 +1412,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index af8f8b173..d1966ade8 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -76,12 +76,13 @@ class Preferences: "on_button_ok_clicked": self.on_button_ok_clicked, "on_button_apply_clicked": self.on_button_apply_clicked, "on_button_cancel_clicked": self.on_button_cancel_clicked, - "on_toggle": self.on_toggle + "on_toggle": self.on_toggle, + "on_test_port_clicked": self.on_test_port_clicked }) def __del__(self): del self.gtkui_config - + def add_page(self, name, widget): """Add a another page to the notebook""" index = self.notebook.append_page(widget) @@ -354,3 +355,8 @@ class Preferences: (model, row) = treeselection.get_selected() self.notebook.set_current_page(model.get_value(row, 0)) + def on_test_port_clicked(self, data): + functions.open_url_in_browser('\ + http://www.deluge-torrent.org/test-port.php?port=%s' % \ + functions.get_listen_port(self.core)) + From dd3ec750aa87df504432e890d289df7bff33f52a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 24 Sep 2007 06:22:31 +0000 Subject: [PATCH 0144/1009] Prevent update() thread to start until all plugins are loaded and we are ready to show the main window. --- deluge/ui/gtkui/gtkui.py | 4 ++-- deluge/ui/gtkui/mainwindow.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index f3f4c3250..8669c4f5a 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -97,8 +97,8 @@ class GtkUI: # Initalize the plugins self.plugins = PluginManager(self) - # Show the main window - self.mainwindow.show() + # Start the mainwindow and show it + self.mainwindow.start() # Start the gtk main loop gtk.main() diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 6e36f4209..545c65f1c 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -81,8 +81,11 @@ class MainWindow: self.systemtray = SystemTray(self) self.statusbar = StatusBar(self) + def start(self): + """Start the update thread and show the window""" gobject.timeout_add(1000, self.update) - + self.show() + def update(self): # Don't update the UI if the the window is minimized. if self.is_minimized == True: From 8b6a14403f79e8f42dddf7ac338d481d0115dcef Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 24 Sep 2007 06:41:38 +0000 Subject: [PATCH 0145/1009] fix icon path --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 20f575a37..0c04904b7 100644 --- a/setup.py +++ b/setup.py @@ -160,7 +160,7 @@ setup( "plugins/*.egg", "i18n/*.pot", "i18n/*/LC_MESSAGES/*.mo"]}, - data_files = [('/usr/share/deluge/icons/scalable/apps', [ + data_files = [('/usr/share/icons/scalable/apps', [ 'deluge/data/icons/scalable/apps/deluge.svg']), ('/usr/share/icons/hicolor/128x128/apps', [ 'deluge/data/icons/hicolor/128x128/apps/deluge.png']), From 67347e67b6c885866444742b7bb77167cd70c178 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 24 Sep 2007 06:44:20 +0000 Subject: [PATCH 0146/1009] Stop update timer before shutting down. --- deluge/ui/gtkui/mainwindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 545c65f1c..671708529 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -83,7 +83,7 @@ class MainWindow: def start(self): """Start the update thread and show the window""" - gobject.timeout_add(1000, self.update) + self.update_timer = gobject.timeout_add(1000, self.update) self.show() def update(self): @@ -113,6 +113,8 @@ class MainWindow: return self.window.get_property("visible") def quit(self): + # Stop the update timer from running + gobject.source_remove(self.update_timer) del self.menubar del self.toolbar del self.torrentview From cf116cf3a13f9017e9392bef103ae2b9cecfe8e7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 24 Sep 2007 08:22:02 +0000 Subject: [PATCH 0147/1009] Make shutdown() an async method. --- deluge/core/core.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index c6b15dba4..644970296 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -147,10 +147,8 @@ class Core(dbus.service.Object): self.loop = gobject.MainLoop() self.loop.run() - # Exported Methods - @dbus.service.method("org.deluge_torrent.Deluge") - def shutdown(self): - """Shutdown the core""" + def _shutdown(self): + """This is called by a thread from shutdown()""" log.info("Shutting down core..") self.loop.quit() del self.torrents @@ -161,6 +159,14 @@ class Core(dbus.service.Object): del self.config del deluge.configmanager del self.session + + # Exported Methods + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="", out_signature="") + def shutdown(self): + """Shutdown the core""" + # Make shutdown an async call + gobject.idle_add(self._shutdown) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="say", out_signature="b") @@ -338,9 +344,8 @@ class Core(dbus.service.Object): out_signature="d") def get_download_rate(self): """Returns the payload download rate""" - # print self.session.status().payload_download_rate return self.session.status().payload_download_rate -# return 0.0 + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", out_signature="d") From 280eed95b61542a82f5ceb3fef097e450bc643ea Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 24 Sep 2007 23:07:45 +0000 Subject: [PATCH 0148/1009] libtorrent sync 1605 --- libtorrent/include/libtorrent/alert_types.hpp | 21 +- .../include/libtorrent/aux_/session_impl.hpp | 52 +- .../include/libtorrent/broadcast_socket.hpp | 1 + libtorrent/include/libtorrent/buffer.hpp | 526 +++++------------- libtorrent/include/libtorrent/enum_net.hpp | 2 +- .../include/libtorrent/peer_connection.hpp | 4 +- libtorrent/include/libtorrent/socket.hpp | 15 + .../include/libtorrent/tracker_manager.hpp | 1 + .../include/libtorrent/variant_stream.hpp | 38 +- libtorrent/src/alert.cpp | 2 +- libtorrent/src/broadcast_socket.cpp | 8 + libtorrent/src/bt_peer_connection.cpp | 13 +- libtorrent/src/enum_net.cpp | 5 +- libtorrent/src/http_tracker_connection.cpp | 7 + libtorrent/src/peer_connection.cpp | 11 +- libtorrent/src/policy.cpp | 27 +- libtorrent/src/session_impl.cpp | 355 +++++++----- libtorrent/src/torrent.cpp | 7 +- libtorrent/src/torrent_info.cpp | 4 +- 19 files changed, 513 insertions(+), 586 deletions(-) diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 36c13c5ab..bfbfa8ae4 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -328,14 +328,33 @@ namespace libtorrent struct TORRENT_EXPORT listen_failed_alert: alert { listen_failed_alert( - const std::string& msg) + tcp::endpoint const& ep + , std::string const& msg) : alert(alert::fatal, msg) + , endpoint(ep) {} + tcp::endpoint endpoint; + virtual std::auto_ptr clone() const { return std::auto_ptr(new listen_failed_alert(*this)); } }; + struct TORRENT_EXPORT listen_succeeded_alert: alert + { + listen_succeeded_alert( + tcp::endpoint const& ep + , std::string const& msg) + : alert(alert::fatal, msg) + , endpoint(ep) + {} + + tcp::endpoint endpoint; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new listen_succeeded_alert(*this)); } + }; + struct TORRENT_EXPORT portmap_error_alert: alert { portmap_error_alert(const std::string& msg) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 9300a1ce3..afb358afe 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -189,11 +189,16 @@ namespace libtorrent #endif void operator()(); - void open_listen_port(); + void open_listen_port() throw(); - void async_accept(); + // if we are listening on an IPv6 interface + // this will return one of the IPv6 addresses on this + // machine, otherwise just an empty endpoint + tcp::endpoint get_ipv6_interface() const; + + void async_accept(boost::shared_ptr const& listener); void on_incoming_connection(boost::shared_ptr const& s - , boost::weak_ptr const& as, asio::error_code const& e); + , boost::weak_ptr listener, asio::error_code const& e); // must be locked to access the data // in this struct @@ -393,8 +398,10 @@ namespace libtorrent // at startup int m_key; - // the range of ports we try to listen on - std::pair m_listen_port_range; + // the number of retries we make when binding the + // listen socket. For each retry the port number + // is incremented by one + int m_listen_port_retries; // the ip-address of the interface // we are supposed to listen on. @@ -402,17 +409,32 @@ namespace libtorrent // that we should let the os decide which // interface to listen on tcp::endpoint m_listen_interface; - - // this is typically set to the same as the local - // listen port. In case a NAT port forward was - // successfully opened, this will be set to the - // port that is open on the external (NAT) interface - // on the NAT box itself. This is the port that has - // to be published to peers, since this is the port - // the client is reachable through. - int m_external_listen_port; - boost::shared_ptr m_listen_socket; + // if we're listening on an IPv6 interface + // this is one of the non local IPv6 interfaces + // on this machine + tcp::endpoint m_ipv6_interface; + + struct listen_socket_t + { + listen_socket_t(): external_port(0) {} + // this is typically set to the same as the local + // listen port. In case a NAT port forward was + // successfully opened, this will be set to the + // port that is open on the external (NAT) interface + // on the NAT box itself. This is the port that has + // to be published to peers, since this is the port + // the client is reachable through. + int external_port; + + // the actual socket + boost::shared_ptr sock; + }; + // since we might be listening on multiple interfaces + // we might need more than one listen socket + std::list m_listen_sockets; + + listen_socket_t setup_listener(tcp::endpoint ep, int retries); // the settings for the client session_settings m_settings; diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index 23be67b0d..485d1bd1f 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -44,6 +44,7 @@ namespace libtorrent bool is_local(address const& a); bool is_loopback(address const& addr); bool is_multicast(address const& addr); + bool is_any(address const& addr); address_v4 guess_local_address(asio::io_service&); diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index 0f37edcbd..6af8817e2 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2003 - 2005, Arvid Norberg, Daniel Wallin +Copyright (c) 2007, Arvid Norberg All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,9 +32,8 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef LIBTORRENT_BUFFER_HPP #define LIBTORRENT_BUFFER_HPP -//#define TORRENT_BUFFER_DEBUG - #include +#include #include "libtorrent/invariant_check.hpp" #include "libtorrent/assert.hpp" @@ -43,410 +42,157 @@ namespace libtorrent { class buffer { public: - struct interval - { - interval(char* begin, char* end) - : begin(begin) - , end(end) - {} + struct interval + { + interval(char* begin, char* end) + : begin(begin) + , end(end) + {} - char operator[](int index) const - { - assert(begin + index < end); - return begin[index]; - } + char operator[](int index) const + { + assert(begin + index < end); + return begin[index]; + } - int left() const { assert(end >= begin); return end - begin; } + int left() const { assert(end >= begin); return end - begin; } - char* begin; - char* end; - }; + char* begin; + char* end; + }; - struct const_interval - { - const_interval(char const* begin, char const* end) - : begin(begin) - , end(end) - {} + struct const_interval + { + const_interval(char const* begin, char const* end) + : begin(begin) + , end(end) + {} - char operator[](int index) const - { - assert(begin + index < end); - return begin[index]; - } + char operator[](int index) const + { + assert(begin + index < end); + return begin[index]; + } - bool operator==(const const_interval& p_interval) - { - return (begin == p_interval.begin - && end == p_interval.end); - } + bool operator==(const const_interval& p_interval) + { + return (begin == p_interval.begin + && end == p_interval.end); + } - int left() const { assert(end >= begin); return end - begin; } + int left() const { assert(end >= begin); return end - begin; } - char const* begin; - char const* end; - }; + char const* begin; + char const* end; + }; - typedef std::pair interval_type; + buffer(std::size_t n = 0) + : m_begin(0) + , m_end(0) + , m_last(0) + { + if (n) resize(n); + } - buffer(std::size_t n = 0); - ~buffer(); + buffer(buffer const& b) + : m_begin(0) + , m_end(0) + , m_last(0) + { + if (b.size() == 0) return; + resize(b.size()); + std::memcpy(m_begin, b.begin(), b.size()); + } - interval allocate(std::size_t n); - void insert(char const* first, char const* last); - void erase(std::size_t n); - std::size_t size() const; - std::size_t capacity() const; - void reserve(std::size_t n); - interval_type data() const; - bool empty() const; + buffer& operator=(buffer const& b) + { + resize(b.size()); + std::memcpy(m_begin, b.begin(), b.size()); + return *this; + } - std::size_t space_left() const; + ~buffer() + { + ::operator delete (m_begin); + } - char const* raw_data() const - { - return m_first; - } + buffer::interval data() { return interval(m_begin, m_end); } + buffer::const_interval data() const { return const_interval(m_begin, m_end); } + + void resize(std::size_t n) + { + reserve(n); + m_end = m_begin + n; + } -#ifndef NDEBUG - void check_invariant() const; -#endif - + void insert(char* point, char const* first, char const* last) + { + std::size_t p = point - m_begin; + if (point == m_end) + { + resize(size() + last - first); + std::memcpy(m_begin + p, first, last - first); + return; + } + + resize(size() + last - first); + std::memmove(m_begin + p + (last - first), m_begin + p, last - first); + std::memcpy(m_begin + p, first, last - first); + } + + void erase(char* begin, char* end) + { + assert(end <= m_end); + assert(begin >= m_begin); + assert(begin <= end); + if (end == m_end) + { + resize(begin - m_begin); + return; + } + std::memmove(begin, end, m_end - end); + m_end = begin + (m_end - end); + } + + void clear() { m_end = m_begin; } + std::size_t size() const { return m_end - m_begin; } + std::size_t capacity() const { return m_last - m_begin; } + void reserve(std::size_t n) + { + if (n <= capacity()) return; + assert(n > 0); + + char* buf = (char*)::operator new(n); + std::size_t s = size(); + std::memcpy(buf, m_begin, s); + ::operator delete (m_begin); + m_begin = buf; + m_end = buf + s; + m_last = m_begin + n; + } + + bool empty() const { return m_begin == m_end; } + char& operator[](std::size_t i) { assert(i >= 0 && i < size()); return m_begin[i]; } + char const& operator[](std::size_t i) const { assert(i >= 0 && i < size()); return m_begin[i]; } + + char* begin() { return m_begin; } + char const* begin() const { return m_begin; } + char* end() { return m_end; } + char const* end() const { return m_end; } + + void swap(buffer& b) + { + using std::swap; + swap(m_begin, b.m_begin); + swap(m_end, b.m_end); + swap(m_last, b.m_last); + } private: - char* m_first; - char* m_last; - char* m_write_cursor; - char* m_read_cursor; - char* m_read_end; - bool m_empty; -#ifdef TORRENT_BUFFER_DEBUG - mutable std::vector m_debug; - mutable int m_pending_copy; -#endif + char* m_begin; // first + char* m_end; // one passed end of size + char* m_last; // one passed end of allocation }; -inline buffer::buffer(std::size_t n) - : m_first((char*)::operator new(n)) - , m_last(m_first + n) - , m_write_cursor(m_first) - , m_read_cursor(m_first) - , m_read_end(m_last) - , m_empty(true) -{ -#ifdef TORRENT_BUFFER_DEBUG - m_pending_copy = 0; -#endif -} - -inline buffer::~buffer() -{ - ::operator delete (m_first); -} - -inline buffer::interval buffer::allocate(std::size_t n) -{ - assert(m_read_cursor <= m_read_end || m_empty); - - INVARIANT_CHECK; - -#ifdef TORRENT_BUFFER_DEBUG - if (m_pending_copy) - { - std::copy(m_write_cursor - m_pending_copy, m_write_cursor - , m_debug.end() - m_pending_copy); - m_pending_copy = 0; - } - m_debug.resize(m_debug.size() + n); - m_pending_copy = n; -#endif - if (m_read_cursor < m_write_cursor || m_empty) - { - // ..R***W.. - if (m_last - m_write_cursor >= (std::ptrdiff_t)n) - { - interval ret(m_write_cursor, m_write_cursor + n); - m_write_cursor += n; - m_read_end = m_write_cursor; - assert(m_read_cursor <= m_read_end); - if (n) m_empty = false; - return ret; - } - - if (m_read_cursor - m_first >= (std::ptrdiff_t)n) - { - m_read_end = m_write_cursor; - interval ret(m_first, m_first + n); - m_write_cursor = m_first + n; - assert(m_read_cursor <= m_read_end); - if (n) m_empty = false; - return ret; - } - - reserve(capacity() + n - (m_last - m_write_cursor)); - assert(m_last - m_write_cursor >= (std::ptrdiff_t)n); - interval ret(m_write_cursor, m_write_cursor + n); - m_write_cursor += n; - m_read_end = m_write_cursor; - if (n) m_empty = false; - assert(m_read_cursor <= m_read_end); - return ret; - - } - //**W...R** - if (m_read_cursor - m_write_cursor >= (std::ptrdiff_t)n) - { - interval ret(m_write_cursor, m_write_cursor + n); - m_write_cursor += n; - if (n) m_empty = false; - return ret; - } - reserve(capacity() + n - (m_read_cursor - m_write_cursor)); - assert(m_read_cursor - m_write_cursor >= (std::ptrdiff_t)n); - interval ret(m_write_cursor, m_write_cursor + n); - m_write_cursor += n; - if (n) m_empty = false; - return ret; -} - -inline void buffer::insert(char const* first, char const* last) -{ - INVARIANT_CHECK; - - std::size_t n = last - first; - -#ifdef TORRENT_BUFFER_DEBUG - if (m_pending_copy) - { - std::copy(m_write_cursor - m_pending_copy, m_write_cursor - , m_debug.end() - m_pending_copy); - m_pending_copy = 0; - } - m_debug.insert(m_debug.end(), first, last); -#endif - - if (space_left() < n) - { - reserve(capacity() + n); - } - - m_empty = false; - - char const* end = (m_last - m_write_cursor) < (std::ptrdiff_t)n ? - m_last : m_write_cursor + n; - - std::size_t copied = end - m_write_cursor; - std::memcpy(m_write_cursor, first, copied); - - m_write_cursor += copied; - if (m_write_cursor > m_read_end) m_read_end = m_write_cursor; - first += copied; - n -= copied; - - if (n == 0) return; - - assert(m_write_cursor == m_last); - m_write_cursor = m_first; - - memcpy(m_write_cursor, first, n); - m_write_cursor += n; -} - -inline void buffer::erase(std::size_t n) -{ - INVARIANT_CHECK; - - if (n == 0) return; - assert(!m_empty); - -#ifndef NDEBUG - int prev_size = size(); -#endif - assert(m_read_cursor <= m_read_end); - m_read_cursor += n; - if (m_read_cursor > m_read_end) - { - m_read_cursor = m_first + (m_read_cursor - m_read_end); - assert(m_read_cursor <= m_write_cursor); - } - - m_empty = m_read_cursor == m_write_cursor; - - assert(prev_size - n == size()); - -#ifdef TORRENT_BUFFER_DEBUG - m_debug.erase(m_debug.begin(), m_debug.begin() + n); -#endif -} - -inline std::size_t buffer::size() const -{ - // ...R***W. - if (m_read_cursor < m_write_cursor) - { - return m_write_cursor - m_read_cursor; - } - // ***W..R* - else - { - if (m_empty) return 0; - return (m_write_cursor - m_first) + (m_read_end - m_read_cursor); - } -} - -inline std::size_t buffer::capacity() const -{ - return m_last - m_first; -} - -inline void buffer::reserve(std::size_t size) -{ - std::size_t n = (std::size_t)(capacity() * 1.f); - if (n < size) n = size; - - char* buf = (char*)::operator new(n); - char* old = m_first; - - if (m_read_cursor < m_write_cursor) - { - // ...R***W.<>. - std::memcpy( - buf + (m_read_cursor - m_first) - , m_read_cursor - , m_write_cursor - m_read_cursor - ); - - m_write_cursor = buf + (m_write_cursor - m_first); - m_read_cursor = buf + (m_read_cursor - m_first); - m_read_end = m_write_cursor; - m_first = buf; - m_last = buf + n; - } - else - { - // **W..<>.R** - std::size_t skip = n - (m_last - m_first); - - std::memcpy(buf, m_first, m_write_cursor - m_first); - std::memcpy( - buf + (m_read_cursor - m_first) + skip - , m_read_cursor - , m_last - m_read_cursor - ); - - m_write_cursor = buf + (m_write_cursor - m_first); - - if (!m_empty) - { - m_read_cursor = buf + (m_read_cursor - m_first) + skip; - m_read_end = buf + (m_read_end - m_first) + skip; - } - else - { - m_read_cursor = m_write_cursor; - m_read_end = m_write_cursor; - } - - m_first = buf; - m_last = buf + n; - } - - ::operator delete (old); -} - -#ifndef NDEBUG -inline void buffer::check_invariant() const -{ - assert(m_read_end >= m_read_cursor); - assert(m_last >= m_read_cursor); - assert(m_last >= m_write_cursor); - assert(m_last >= m_first); - assert(m_first <= m_read_cursor); - assert(m_first <= m_write_cursor); -#ifdef TORRENT_BUFFER_DEBUG - int a = m_debug.size(); - int b = size(); - (void)a; - (void)b; - assert(m_debug.size() == size()); -#endif -} -#endif - -inline buffer::interval_type buffer::data() const -{ - INVARIANT_CHECK; - -#ifdef TORRENT_BUFFER_DEBUG - if (m_pending_copy) - { - std::copy(m_write_cursor - m_pending_copy, m_write_cursor - , m_debug.end() - m_pending_copy); - m_pending_copy = 0; - } -#endif - - // ...R***W. - if (m_read_cursor < m_write_cursor) - { -#ifdef TORRENT_BUFFER_DEBUG - assert(m_debug.size() == size()); - assert(std::equal(m_debug.begin(), m_debug.end(), m_read_cursor)); -#endif - return interval_type( - const_interval(m_read_cursor, m_write_cursor) - , const_interval(m_last, m_last) - ); - } - // **W...R** - else - { - if (m_read_cursor == m_read_end) - { -#ifdef TORRENT_BUFFER_DEBUG - assert(m_debug.size() == size()); - assert(std::equal(m_debug.begin(), m_debug.end(), m_first)); -#endif - - return interval_type( - const_interval(m_first, m_write_cursor) - , const_interval(m_last, m_last)); - } -#ifdef TORRENT_BUFFER_DEBUG - assert(m_debug.size() == size()); - assert(std::equal(m_debug.begin(), m_debug.begin() + (m_read_end - - m_read_cursor), m_read_cursor)); - assert(std::equal(m_debug.begin() + (m_read_end - m_read_cursor), m_debug.end() - , m_first)); -#endif - - assert(m_read_cursor <= m_read_end || m_empty); - return interval_type( - const_interval(m_read_cursor, m_read_end) - , const_interval(m_first, m_write_cursor) - ); - } -} - -inline bool buffer::empty() const -{ - return m_empty; -} - -inline std::size_t buffer::space_left() const -{ - if (m_empty) return m_last - m_first; - - // ...R***W. - if (m_read_cursor < m_write_cursor) - { - return (m_last - m_write_cursor) + (m_read_cursor - m_first); - } - // ***W..R* - else - { - return m_read_cursor - m_write_cursor; - } -} } diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp index 0c6063a2b..0da76ff36 100644 --- a/libtorrent/include/libtorrent/enum_net.hpp +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -37,7 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); + std::vector
enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); } #endif diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index ea16a8d0a..ac9aa0322 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -510,7 +510,7 @@ namespace libtorrent int m_packet_size; int m_recv_pos; - std::vector m_recv_buffer; + buffer m_recv_buffer; // this is the buffer where data that is // to be sent is stored until it gets @@ -521,7 +521,7 @@ namespace libtorrent // waiting for a async_write operation on one // buffer, the other is used to write data to // be queued up. - std::vector m_send_buffer[2]; + buffer m_send_buffer[2]; // the current send buffer is the one to write to. // (m_current_send_buffer + 1) % 2 is the // buffer we're currently waiting for. diff --git a/libtorrent/include/libtorrent/socket.hpp b/libtorrent/include/libtorrent/socket.hpp index c478a92ef..514be256c 100755 --- a/libtorrent/include/libtorrent/socket.hpp +++ b/libtorrent/include/libtorrent/socket.hpp @@ -98,6 +98,21 @@ namespace libtorrent typedef asio::basic_deadline_timer deadline_timer; + inline std::ostream& print_endpoint(std::ostream& os, tcp::endpoint const& ep) + { + address const& addr = ep.address(); + asio::error_code ec; + std::string a = addr.to_string(ec); + if (ec) return os; + + if (addr.is_v6()) + os << "[" << a << "]:"; + else + os << a << ":"; + os << ep.port(); + return os; + } + namespace detail { template diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index 57f7bd851..b1b4d87ac 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -113,6 +113,7 @@ namespace libtorrent std::string url; int key; int num_want; + std::string ipv6; }; struct TORRENT_EXPORT request_callback diff --git a/libtorrent/include/libtorrent/variant_stream.hpp b/libtorrent/include/libtorrent/variant_stream.hpp index 8f9888519..4b32c9349 100644 --- a/libtorrent/include/libtorrent/variant_stream.hpp +++ b/libtorrent/include/libtorrent/variant_stream.hpp @@ -232,18 +232,18 @@ namespace aux // -------------- remote_endpoint ----------- - template - struct remote_endpoint_visitor + template + struct remote_endpoint_visitor_ec : boost::static_visitor { - remote_endpoint_visitor(Error_Handler const& error_handler) - : error_handler(error_handler) + remote_endpoint_visitor_ec(asio::error_code& ec) + : error_code(ec) {} template EndpointType operator()(T* p) const { - return p->remote_endpoint(error_handler); + return p->remote_endpoint(error_code); } EndpointType operator()(boost::blank) const @@ -251,11 +251,11 @@ namespace aux return EndpointType(); } - Error_Handler const& error_handler; + asio::error_code& error_code; }; template - struct remote_endpoint_visitor + struct remote_endpoint_visitor : boost::static_visitor { template @@ -272,18 +272,18 @@ namespace aux // -------------- local_endpoint ----------- - template - struct local_endpoint_visitor + template + struct local_endpoint_visitor_ec : boost::static_visitor { - local_endpoint_visitor(Error_Handler const& error_handler) - : error_handler(error_handler) + local_endpoint_visitor_ec(asio::error_code& ec) + : error_code(ec) {} template EndpointType operator()(T* p) const { - return p->local_endpoint(error_handler); + return p->local_endpoint(error_code); } EndpointType operator()(boost::blank) const @@ -291,11 +291,11 @@ namespace aux return EndpointType(); } - Error_Handler const& error_handler; + asio::error_code& error_code; }; template - struct local_endpoint_visitor + struct local_endpoint_visitor : boost::static_visitor { template @@ -665,12 +665,11 @@ public: return boost::apply_visitor(aux::remote_endpoint_visitor(), m_variant); } - template - endpoint_type remote_endpoint(Error_Handler const& error_handler) + endpoint_type remote_endpoint(asio::error_code& ec) { assert(instantiated()); return boost::apply_visitor( - aux::remote_endpoint_visitor(error_handler), m_variant + aux::remote_endpoint_visitor_ec(ec), m_variant ); } @@ -680,12 +679,11 @@ public: return boost::apply_visitor(aux::local_endpoint_visitor(), m_variant); } - template - endpoint_type local_endpoint(Error_Handler const& error_handler) + endpoint_type local_endpoint(asio::error_code& ec) { assert(instantiated()); return boost::apply_visitor( - aux::local_endpoint_visitor(error_handler), m_variant + aux::local_endpoint_visitor_ec(ec), m_variant ); } diff --git a/libtorrent/src/alert.cpp b/libtorrent/src/alert.cpp index b990db7e1..80ae6ead8 100755 --- a/libtorrent/src/alert.cpp +++ b/libtorrent/src/alert.cpp @@ -65,7 +65,7 @@ namespace libtorrent { alert_manager::alert_manager() - : m_severity(alert::none) + : m_severity(alert::fatal) {} alert_manager::~alert_manager() diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 3aaadcc81..359c0e0d2 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -84,6 +84,14 @@ namespace libtorrent return addr.to_v6().is_multicast(); } + bool is_any(address const& addr) + { + if (addr.is_v4()) + return addr.to_v4() == address_v4::any(); + else + return addr.to_v6() == address_v6::any(); + } + broadcast_socket::broadcast_socket(asio::io_service& ios , udp::endpoint const& multicast_endpoint , receive_handler_t const& handler diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index ab61d98f9..e27a7f4c3 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -50,7 +50,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" -#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION #include "libtorrent/pe_crypto.hpp" @@ -1475,18 +1474,14 @@ namespace libtorrent detail::write_address(remote().address(), out); handshake["yourip"] = remote_address; handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; - asio::error_code ec; - std::vector
const& interfaces = enum_net_interfaces(get_socket()->io_service(), ec); - for (std::vector
::const_iterator i = interfaces.begin() - , end(interfaces.end()); i != end; ++i) + + tcp::endpoint ep = m_ses.get_ipv6_interface(); + if (ep != tcp::endpoint()) { - // TODO: only use global IPv6 addresses - if (!i->is_v6() || i->to_v6().is_link_local()) continue; std::string ipv6_address; std::back_insert_iterator out(ipv6_address); - detail::write_address(*i, out); + detail::write_address(ep.address(), out); handshake["ipv6"] = ipv6_address; - break; } // loop backwards, to make the first extension be the last diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index 172719793..94c74e855 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -40,10 +40,9 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) + std::vector
enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) { - static std::vector
ret; - if (!ret.empty()) return ret; + std::vector
ret; #if defined __linux__ || defined __MACH__ || defined(__FreeBSD__) int s = socket(AF_INET, SOCK_DGRAM, 0); diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 0a0e59c48..64dea2e2e 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -428,6 +428,13 @@ namespace libtorrent m_send_buffer += "supportcrypto=1&"; #endif + if (!url_has_argument(request, "ipv6") && !req.ipv6.empty()) + { + m_send_buffer += "ipv6="; + m_send_buffer += req.ipv6; + m_send_buffer += '&'; + } + // extension that tells the tracker that // we don't need any peer_id's in the response if (!url_has_argument(request, "no_peer_id")) diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 25bc0ba63..ad5087325 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -2082,7 +2082,8 @@ namespace libtorrent p.remote_dl_rate = 0; } - p.send_buffer_size = send_buffer_size(); + p.send_buffer_size = int(m_send_buffer[0].capacity() + + m_send_buffer[1].capacity()); } void peer_connection::cut_receive_buffer(int size, int packet_size) @@ -2512,7 +2513,7 @@ namespace libtorrent void peer_connection::send_buffer(char const* begin, char const* end) { - std::vector& buf = m_send_buffer[m_current_send_buffer]; + buffer& buf = m_send_buffer[m_current_send_buffer]; buf.insert(buf.end(), begin, end); setup_send(); } @@ -2521,7 +2522,7 @@ namespace libtorrent // return value is destructed buffer::interval peer_connection::allocate_send_buffer(int size) { - std::vector& buf = m_send_buffer[m_current_send_buffer]; + buffer& buf = m_send_buffer[m_current_send_buffer]; buf.resize(buf.size() + size); buffer::interval ret(&buf[0] + buf.size() - size, &buf[0] + buf.size()); return ret; @@ -2588,7 +2589,7 @@ namespace libtorrent && m_recv_pos == 0 && (m_recv_buffer.capacity() - m_packet_size) > 128) { - std::vector(m_packet_size).swap(m_recv_buffer); + buffer(m_packet_size).swap(m_recv_buffer); } int max_receive = m_packet_size - m_recv_pos; @@ -2807,7 +2808,7 @@ namespace libtorrent if (int(m_send_buffer[i].size()) < 64 && int(m_send_buffer[i].capacity()) > 128) { - std::vector tmp(m_send_buffer[i]); + buffer tmp(m_send_buffer[i]); tmp.swap(m_send_buffer[i]); assert(m_send_buffer[i].capacity() == m_send_buffer[i].size()); } diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 4faed837e..49671c5db 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -162,6 +162,18 @@ namespace tcp::endpoint const& m_ep; }; + struct match_peer_id + { + match_peer_id(peer_id const& id_) + : m_id(id_) + {} + + bool operator()(policy::peer const& p) const + { return p.connection && p.connection->pid() == m_id; } + + peer_id const& m_id; + }; + struct match_peer_connection { match_peer_connection(peer_connection const& c) @@ -1030,6 +1042,12 @@ namespace libtorrent m_peers.begin() , m_peers.end() , match_peer_endpoint(remote)); + + if (i == m_peers.end()) + i = std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_id(pid)); } else { @@ -1156,7 +1174,7 @@ namespace libtorrent // data from now on void policy::unchoked(peer_connection& c) { - INVARIANT_CHECK; +// INVARIANT_CHECK; if (c.is_interesting()) { request_a_block(*m_torrent, c); @@ -1166,7 +1184,7 @@ namespace libtorrent // called when a peer is interested in us void policy::interested(peer_connection& c) { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(std::find_if(m_peers.begin(), m_peers.end() , boost::bind(std::equal_to(), bind(&peer::connection, _1) @@ -1278,7 +1296,7 @@ namespace libtorrent */ bool policy::connect_one_peer() { - INVARIANT_CHECK; +// INVARIANT_CHECK; assert(m_torrent->want_more_peers()); @@ -1291,7 +1309,6 @@ namespace libtorrent try { - INVARIANT_CHECK; p->connected = time_now(); p->connection = m_torrent->connect_to_peer(&*p); assert(p->connection == m_torrent->connection_for(p->ip)); @@ -1375,7 +1392,7 @@ namespace libtorrent void policy::peer_is_interesting(peer_connection& c) { - INVARIANT_CHECK; +// INVARIANT_CHECK; c.send_interested(); if (c.has_peer_choked() diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 6e1129b99..a58906156 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -73,6 +73,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION @@ -512,9 +513,8 @@ namespace detail , m_download_channel(m_io_service, peer_connection::download_channel) , m_upload_channel(m_io_service, peer_connection::upload_channel) , m_tracker_manager(m_settings, m_tracker_proxy) - , m_listen_port_range(listen_port_range) + , m_listen_port_retries(listen_port_range.second - listen_port_range.first) , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) - , m_external_listen_port(0) , m_abort(false) , m_max_uploads(8) , m_max_connections(200) @@ -661,129 +661,227 @@ namespace detail *i = ' '; } - void session_impl::open_listen_port() + tcp::endpoint session_impl::get_ipv6_interface() const { - try - { - // create listener socket - m_listen_socket.reset(new socket_acceptor(m_io_service)); + return m_ipv6_interface; + } - for(;;) - { - try - { - m_listen_socket->open(m_listen_interface.protocol()); - m_listen_socket->set_option(socket_acceptor::reuse_address(true)); - m_listen_socket->bind(m_listen_interface); - m_listen_socket->listen(); - m_listen_interface = m_listen_socket->local_endpoint(); - m_external_listen_port = m_listen_interface.port(); - break; - } - catch (asio::system_error& e) - { - // TODO: make sure this is correct - if (e.code() == asio::error::host_not_found - || m_listen_interface.port() == 0) - { - if (m_alerts.should_post(alert::fatal)) - { - std::string msg = "cannot listen on the given interface '" - + m_listen_interface.address().to_string() + "'"; - m_alerts.post_alert(listen_failed_alert(msg)); - } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - std::string msg = "cannot listen on the given interface '" - + m_listen_interface.address().to_string() + "'"; - (*m_logger) << msg << "\n"; -#endif - assert(m_listen_socket.unique()); - m_listen_socket.reset(); - break; - } - m_listen_socket->close(); - m_listen_interface.port(m_listen_interface.port() + 1); - if (m_listen_interface.port() > m_listen_port_range.second) - { - std::stringstream msg; - msg << "none of the ports in the range [" - << m_listen_port_range.first - << ", " << m_listen_port_range.second - << "] could be opened for listening"; - m_alerts.post_alert(listen_failed_alert(msg.str())); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << msg.str() << "\n"; -#endif - m_listen_socket.reset(); - break; - } - } - } + session_impl::listen_socket_t session_impl::setup_listener(tcp::endpoint ep, int retries) + { + asio::error_code ec; + listen_socket_t s; + s.sock.reset(new socket_acceptor(m_io_service)); + s.sock->open(ep.protocol(), ec); + s.sock->set_option(socket_acceptor::reuse_address(true), ec); + s.sock->bind(ep, ec); + while (ec && retries > 0) + { + ec = asio::error_code(); + assert(!ec); + --retries; + ep.port(ep.port() + 1); + s.sock->bind(ep, ec); } - catch (asio::system_error& e) + if (ec) + { + // instead of giving up, try + // let the OS pick a port + ep.port(0); + ec = asio::error_code(); + s.sock->bind(ep, ec); + } + if (ec) + { + // not even that worked, give up + if (m_alerts.should_post(alert::fatal)) + { + std::stringstream msg; + msg << "cannot bind to interface '"; + print_endpoint(msg, ep) << "' " << ec.message(); + m_alerts.post_alert(listen_failed_alert(ep, msg.str())); + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::stringstream msg; + msg << "cannot bind to interface '"; + print_endpoint(msg, ep) << "' " << ec.message(); + (*m_logger) << msg.str() << "\n"; +#endif + return listen_socket_t(); + } + s.external_port = s.sock->local_endpoint(ec).port(); + s.sock->listen(0, ec); + if (ec) { if (m_alerts.should_post(alert::fatal)) { - m_alerts.post_alert(listen_failed_alert( - std::string("failed to open listen port: ") + e.what())); + std::stringstream msg; + msg << "cannot listen on interface '"; + print_endpoint(msg, ep) << "' " << ec.message(); + m_alerts.post_alert(listen_failed_alert(ep, msg.str())); + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::stringstream msg; + msg << "cannot listen on interface '"; + print_endpoint(msg, ep) << "' " << ec.message(); + (*m_logger) << msg.str() << "\n"; +#endif + return listen_socket_t(); + } + + if (m_alerts.should_post(alert::fatal)) + { + std::string msg = "listening on interface " + + boost::lexical_cast(ep); + m_alerts.post_alert(listen_succeeded_alert(ep, msg)); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << "listening on: " << ep + << " external port: " << s.external_port << "\n"; +#endif + return s; + } + + void session_impl::open_listen_port() throw() + { + // close the open listen sockets + m_listen_sockets.clear(); + m_incoming_connection = false; + + if (is_any(m_listen_interface.address())) + { + // this means we should open two listen sockets + // one for IPv4 and one for IPv6 + + listen_socket_t s = setup_listener( + tcp::endpoint(address_v4::any(), m_listen_interface.port()) + , m_listen_port_retries); + + if (s.sock) + { + m_listen_sockets.push_back(s); + async_accept(s.sock); + } + + s = setup_listener( + tcp::endpoint(address_v6::any(), m_listen_interface.port()) + , m_listen_port_retries); + + if (s.sock) + { + m_listen_sockets.push_back(s); + async_accept(s.sock); + } + } + else + { + // we should only open a single listen socket, that + // binds to the given interface + + listen_socket_t s = setup_listener( + m_listen_interface, m_listen_port_retries); + + if (s.sock) + { + m_listen_sockets.push_back(s); + async_accept(s.sock); } } - if (m_listen_socket) + m_ipv6_interface = tcp::endpoint(); + + for (std::list::const_iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << "listening on port: " << m_listen_interface.port() - << " external port: " << m_external_listen_port << "\n"; -#endif - async_accept(); - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); + asio::error_code ec; + tcp::endpoint ep = i->sock->local_endpoint(ec); + if (ec || ep.address().is_v4()) continue; + + if (ep.address().to_v6() != address_v6::any()) + { + // if we're listening on a specific address + // pick it + m_ipv6_interface = ep; + } + else + { + // if we're listening on any IPv6 address, enumerate them and + // pick the first non-local address + std::vector
const& ifs = enum_net_interfaces(m_io_service, ec); + for (std::vector
::const_iterator i = ifs.begin() + , end(ifs.end()); i != end; ++i) + { + if (i->is_v4() || i->to_v6().is_link_local() || i->to_v6().is_loopback()) continue; + m_ipv6_interface = tcp::endpoint(*i, ep.port()); + break; + } + break; + } + } + + if (!m_listen_sockets.empty()) + { + asio::error_code ec; + tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec); + if (!ec) + { + if (m_natpmp.get()) m_natpmp->set_mappings(local.port(), 0); + if (m_upnp.get()) m_upnp->set_mappings(local.port(), 0); + } } } - void session_impl::async_accept() + void session_impl::async_accept(boost::shared_ptr const& listener) { shared_ptr c(new socket_type(m_io_service)); c->instantiate(); - m_listen_socket->async_accept(c->get() + listener->async_accept(c->get() , bind(&session_impl::on_incoming_connection, this, c - , weak_ptr(m_listen_socket), _1)); + , boost::weak_ptr(listener), _1)); } void session_impl::on_incoming_connection(shared_ptr const& s - , weak_ptr const& listen_socket, asio::error_code const& e) try + , weak_ptr listen_socket, asio::error_code const& e) try { - if (listen_socket.expired()) - return; + boost::shared_ptr listener = listen_socket.lock(); + if (!listener) return; - if (e == asio::error::operation_aborted) - return; + if (e == asio::error::operation_aborted) return; mutex_t::scoped_lock l(m_mutex); - assert(listen_socket.lock() == m_listen_socket); - if (m_abort) return; + asio::error_code ec; if (e) { + tcp::endpoint ep = listener->local_endpoint(ec); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) std::string msg = "error accepting connection on '" - + m_listen_interface.address().to_string() + "'"; + + boost::lexical_cast(ep) + "' " + e.message(); (*m_logger) << msg << "\n"; #endif - assert(m_listen_socket.unique()); - // try any random port - m_listen_interface.port(0); - open_listen_port(); + if (m_alerts.should_post(alert::fatal)) + { + std::string msg = "error accepting connection on '" + + boost::lexical_cast(ep) + "' " + ec.message(); + m_alerts.post_alert(listen_failed_alert(ep, msg)); + } return; } - async_accept(); + async_accept(listener); // we got a connection request! m_incoming_connection = true; - tcp::endpoint endp = s->remote_endpoint(); + tcp::endpoint endp = s->remote_endpoint(ec); + + if (ec) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << endp << " <== INCOMING CONNECTION FAILED, could " + "not retrieve remote endpoint " << ec.message() << "\n"; +#endif + return; + } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << endp << " <== INCOMING CONNECTION\n"; @@ -803,28 +901,22 @@ namespace detail // check if we have any active torrents // if we don't reject the connection - if (m_torrents.empty()) + if (m_torrents.empty()) return; + + bool has_active_torrent = false; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) { - return; - } - else - { - bool has_active_torrent = false; - for (torrent_map::iterator i = m_torrents.begin() - , end(m_torrents.end()); i != end; ++i) + if (!i->second->is_paused()) { - if (!i->second->is_paused()) - { - has_active_torrent = true; - break; - } + has_active_torrent = true; + break; } - if (!has_active_torrent) - return; } + if (!has_active_torrent) return; boost::intrusive_ptr c( - new bt_peer_connection(*this, s, 0)); + new bt_peer_connection(*this, s, 0)); #ifndef NDEBUG c->m_in_constructor = false; #endif @@ -1064,7 +1156,9 @@ namespace detail if (t.should_request()) { tracker_request req = t.generate_tracker_request(); - req.listen_port = m_external_listen_port; + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; req.key = m_key; m_tracker_manager.queue_request(m_strand, m_half_open, req , t.tracker_login(), m_listen_interface.address(), i->second); @@ -1274,10 +1368,9 @@ namespace detail { eh_initializer(); - if (m_listen_port_range.first != 0 && m_listen_port_range.second != 0) { session_impl::mutex_t::scoped_lock l(m_mutex); - open_listen_port(); + if (m_listen_interface.port() != 0) open_listen_port(); } ptime timer = time_now(); @@ -1331,8 +1424,10 @@ namespace detail && !i->second->trackers().empty()) { tracker_request req = i->second->generate_tracker_request(); - assert(m_external_listen_port > 0); - req.listen_port = m_external_listen_port; + assert(!m_listen_sockets.empty()); + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; req.key = m_key; std::string login = i->second->tracker_login(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1347,8 +1442,8 @@ namespace detail } } - // close listen socket - m_listen_socket.reset(); + // close the listen sockets + m_listen_sockets.clear(); ptime start(time_now()); l.unlock(); @@ -1477,10 +1572,12 @@ namespace detail , bool paused , void* userdata) { + assert(!save_path.empty()); + // if you get this assert, you haven't managed to // open a listen port. call listen_on() first. - assert(m_external_listen_port > 0); - assert(!save_path.empty()); + if (m_listen_sockets.empty()) + throw std::runtime_error("no listen socket opened"); if (ti->begin_files() == ti->end_files()) throw std::runtime_error("no files in torrent"); @@ -1489,7 +1586,7 @@ namespace detail mutex_t::scoped_lock l(m_mutex); mutex::scoped_lock l2(m_checker_impl.m_mutex); - INVARIANT_CHECK; +// INVARIANT_CHECK; if (is_aborted()) throw std::runtime_error("session is closing"); @@ -1573,7 +1670,7 @@ namespace detail // lock the session session_impl::mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; +// INVARIANT_CHECK; // is the torrent already active? if (!find_torrent(info_hash).expired()) @@ -1628,8 +1725,10 @@ namespace detail { tracker_request req = t.generate_tracker_request(); assert(req.event == tracker_request::stopped); - assert(m_external_listen_port > 0); - req.listen_port = m_external_listen_port; + assert(!m_listen_sockets.empty()); + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; req.key = m_key; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1684,19 +1783,15 @@ namespace detail if (net_interface && std::strlen(net_interface) > 0) new_interface = tcp::endpoint(address::from_string(net_interface), port_range.first); else - new_interface = tcp::endpoint(address(), port_range.first); + new_interface = tcp::endpoint(address_v4::any(), port_range.first); - m_listen_port_range = port_range; + m_listen_port_retries = port_range.second - port_range.first; // if the interface is the same and the socket is open // don't do anything if (new_interface == m_listen_interface - && m_listen_socket) return true; + && !m_listen_sockets.empty()) return true; - if (m_listen_socket) - m_listen_socket.reset(); - - m_incoming_connection = false; m_listen_interface = new_interface; open_listen_port(); @@ -1723,13 +1818,14 @@ namespace detail (*m_logger) << time_now_string() << "\n"; #endif - return m_listen_socket; + return !m_listen_sockets.empty(); } unsigned short session_impl::listen_port() const { mutex_t::scoped_lock l(m_mutex); - return m_external_listen_port; + if (m_listen_sockets.empty()) return 0; + return m_listen_sockets.front().external_port;; } void session_impl::announce_lsd(sha1_hash const& ih) @@ -1777,7 +1873,8 @@ namespace detail if (tcp_port != 0) { - m_external_listen_port = tcp_port; + if (!m_listen_sockets.empty()) + m_listen_sockets.front().external_port = tcp_port; if (m_alerts.should_post(alert::info)) { std::stringstream msg; @@ -1801,7 +1898,7 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; +// INVARIANT_CHECK; session_status s; s.has_incoming_connections = m_incoming_connection; @@ -1945,7 +2042,7 @@ namespace detail bool session_impl::is_listening() const { mutex_t::scoped_lock l(m_mutex); - return m_listen_socket; + return !m_listen_sockets.empty(); } session_impl::~session_impl() diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 252461bc6..6e941e9ec 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -412,9 +412,9 @@ namespace libtorrent m_last_dht_announce = now; // TODO: There should be a way to abort an announce operation on the dht. // when the torrent is destructed - assert(m_ses.m_external_listen_port > 0); + if (m_ses.m_listen_sockets.empty()) return; m_ses.m_dht->announce(m_torrent_file->info_hash() - , m_ses.m_external_listen_port + , m_ses.m_listen_sockets.front().external_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } #endif @@ -1367,6 +1367,9 @@ namespace libtorrent req.left = bytes_left(); if (req.left == -1) req.left = 16*1024; req.event = m_event; + tcp::endpoint ep = m_ses.get_ipv6_interface(); + if (ep != tcp::endpoint()) + req.ipv6 = ep.address().to_string(); if (m_event != tracker_request::stopped) m_event = tracker_request::none; diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index d6dc27fa2..76c10e572 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -400,9 +400,7 @@ namespace libtorrent { if (i->first == "pieces" || i->first == "piece length" - || i->first == "length" -// || i->first == "files" - || i->first == "name") + || i->first == "length") continue; m_extra_info[i->first] = i->second; } From d64ed7e2e4a90e663a42b2aec9cc8ad211af4648 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 24 Sep 2007 23:13:18 +0000 Subject: [PATCH 0149/1009] Make sure the queue state is saved whenever there is a change. --- deluge/plugins/queue/queue/torrentqueue.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py index 983639d10..2677240fe 100644 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -76,16 +76,19 @@ class TorrentQueue: """Append torrent_id to the bottom of the queue""" log.debug("Append torrent %s to queue..", torrent_id) self.queue.append(torrent_id) + self.save_state() def prepend(self, torrent_id): """Prepend torrent_id to the top of the queue""" log.debug("Prepend torrent %s to queue..", torrent_id) self.queue.insert(0, torrent_id) + self.save_state() def remove(self, torrent_id): """Removes torrent_id from the list""" log.debug("Remove torrent %s from queue..", torrent_id) self.queue.remove(torrent_id) + self.save_state() def up(self, torrent_id): """Move torrent_id up one in the queue""" @@ -103,7 +106,9 @@ class TorrentQueue: # Pop and insert the torrent_id at index - 1 self.queue.insert(index - 1, self.queue.pop(index)) - + + self.save_state() + return True def top(self, torrent_id): @@ -141,7 +146,9 @@ class TorrentQueue: # Pop and insert the torrent_id at index + 1 self.queue.insert(index + 1, self.queue.pop(index)) - + + self.save_state() + return True def bottom(self, torrent_id): From b72a79332d34706413561a7b7986ba2c38e4b384 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 24 Sep 2007 23:14:25 +0000 Subject: [PATCH 0150/1009] Update mainwindow glade --- deluge/ui/gtkui/glade/main_window.glade | 932 ++++++++++++------------ 1 file changed, 466 insertions(+), 466 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 2bdea3b0f..80b78d6b8 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -151,7 +151,7 @@ True - Columns + _Columns True @@ -348,6 +348,376 @@ 1 2 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + True @@ -368,33 +738,54 @@ 2 2 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + True 0 1 2 - 5 - 6 + 2 + 3 - + True 0 1 2 - 4 - 5 + 1 + 2 - + True 0 True @@ -403,77 +794,27 @@ 1 2 - 3 - 4 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True 0 - 0 1 - <b>Name:</b> + <b>Total Size:</b> True - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - - - 4 - 5 + 1 + 2 GTK_FILL @@ -506,28 +847,76 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + True 0 1 - <b>Total Size:</b> + <b>Next Announce:</b> True - 1 - 2 + 5 + 6 GTK_FILL - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + True 0 True @@ -536,56 +925,37 @@ 1 2 + 3 + 4 - + True 0 1 2 - 1 - 2 + 4 + 5 - + True 0 1 2 - 2 - 3 + 5 + 6 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 2 - 3 - GTK_FILL - - @@ -608,376 +978,6 @@ GTK_FILL - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - From 20f76b4eaa57363a385fb7d567aca6312d5b5ee9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 25 Sep 2007 07:15:35 +0000 Subject: [PATCH 0151/1009] Update the alerts bindings. --- libtorrent/bindings/python/src/alert.cpp | 159 +++++++++++------- libtorrent/bindings/python/src/docstrings.cpp | 79 ++++++--- 2 files changed, 149 insertions(+), 89 deletions(-) diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index 25c82a342..c212ed04d 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -12,24 +12,33 @@ using namespace libtorrent; extern char const* alert_doc; extern char const* alert_msg_doc; extern char const* alert_severity_doc; -extern char const* listen_failed_alert_doc; -extern char const* file_error_alert_doc; -extern char const* tracker_announce_alert_doc; +extern char const* torrent_alert_doc; extern char const* tracker_alert_doc; -extern char const* tracker_reply_alert_doc; extern char const* tracker_warning_alert_doc; -extern char const* url_seed_alert_doc; +extern char const* tracker_reply_alert_doc; +extern char const* tracker_announce_alert_doc; extern char const* hash_failed_alert_doc; extern char const* peer_ban_alert_doc; extern char const* peer_error_alert_doc; extern char const* invalid_request_alert_doc; extern char const* peer_request_doc; extern char const* torrent_finished_alert_doc; -extern char const* torrent_paused_alert_doc; +extern char const* piece_finished_alert_doc; +extern char const* block_finished_alert_doc; +extern char const* block_downloading_alert_doc; extern char const* storage_moved_alert_doc; +extern char const* torrent_paused_alert_doc; +extern char const* torrent_checked_alert_doc; +extern char const* url_seed_alert_doc; +extern char const* file_error_alert_doc; extern char const* metadata_failed_alert_doc; extern char const* metadata_received_alert_doc; +extern char const* listen_failed_alert_doc; +extern char const* listen_succeeded_alert_doc; +extern char const* portmap_error_alert_doc; +extern char const* portmap_alert_doc; extern char const* fastresume_rejected_alert_doc; +extern char const* peer_blocked_alert_doc; void bind_alert() { @@ -58,60 +67,41 @@ void bind_alert() ; } - class_, noncopyable>( - "listen_failed_alert", listen_failed_alert_doc, no_init - ); - - class_, noncopyable>( - "file_error_alert", file_error_alert_doc, no_init + class_, noncopyable>( + "torrent_alert", torrent_alert_doc, no_init ) - .def_readonly("handle", &file_error_alert::handle) + .def_readonly("handle", &torrent_alert::handle) ; - class_, noncopyable>( - "tracker_announce_alert", tracker_announce_alert_doc, no_init - ) - .def_readonly("handle", &tracker_announce_alert::handle) - ; - - class_, noncopyable>( + class_, noncopyable>( "tracker_alert", tracker_alert_doc, no_init ) - .def_readonly("handle", &tracker_alert::handle) .def_readonly("times_in_row", &tracker_alert::times_in_row) .def_readonly("status_code", &tracker_alert::status_code) ; - class_, noncopyable>( - "tracker_reply_alert", tracker_reply_alert_doc, no_init - ) - .def_readonly("handle", &tracker_reply_alert::handle) - ; - - class_, noncopyable>( + class_, noncopyable>( "tracker_warning_alert", tracker_warning_alert_doc, no_init - ) - .def_readonly("handle", &tracker_warning_alert::handle) - ; + ); - class_, noncopyable>( - "url_seed_alert", url_seed_alert_doc, no_init - ) - .def_readonly("url", &url_seed_alert::url) - ; + class_, noncopyable>( + "tracker_reply_alert", tracker_reply_alert_doc, no_init + ); - class_, noncopyable>( + class_, noncopyable>( + "tracker_announce_alert", tracker_announce_alert_doc, no_init + ); + + class_, noncopyable>( "hash_failed_alert", hash_failed_alert_doc, no_init ) - .def_readonly("handle", &hash_failed_alert::handle) .def_readonly("piece_index", &hash_failed_alert::piece_index) ; - class_, noncopyable>( + class_, noncopyable>( "peer_ban_alert", peer_ban_alert_doc, no_init ) .def_readonly("ip", &peer_ban_alert::ip) - .def_readonly("handle", &peer_ban_alert::handle) ; class_, noncopyable>( @@ -121,10 +111,9 @@ void bind_alert() .def_readonly("pid", &peer_error_alert::pid) ; - class_, noncopyable>( + class_, noncopyable>( "invalid_request_alert", invalid_request_alert_doc, no_init ) - .def_readonly("handle", &invalid_request_alert::handle) .def_readonly("ip", &invalid_request_alert::ip) .def_readonly("request", &invalid_request_alert::request) .def_readonly("pid", &invalid_request_alert::pid) @@ -137,40 +126,86 @@ void bind_alert() .def(self == self) ; - class_, noncopyable>( + class_, noncopyable>( "torrent_finished_alert", torrent_finished_alert_doc, no_init + ); + + class_, noncopyable>( + "piece_finished_alert", piece_finished_alert_doc, no_init ) - .def_readonly("handle", &torrent_finished_alert::handle) + .def_readonly("piece_index", &piece_finished_alert::piece_index) ; - class_, noncopyable>( - "torrent_paused_alert", torrent_paused_alert_doc, no_init + class_, noncopyable>( + "block_finished_alert", block_finished_alert_doc, no_init ) - .def_readonly("handle", &torrent_paused_alert::handle) + .def_readonly("block_index", &block_finished_alert::block_index) + .def_readonly("piece_index", &block_finished_alert::piece_index) ; - class_, noncopyable>( + class_, noncopyable>( + "block_downloading_alert", block_downloading_alert_doc, no_init + ) + .def_readonly("peer_speedmsg", &block_downloading_alert::peer_speedmsg) + .def_readonly("block_index", &block_downloading_alert::block_index) + .def_readonly("piece_index", &block_downloading_alert::piece_index) + ; + + class_, noncopyable>( "storage_moved_alert", storage_moved_alert_doc, no_init + ); + + class_, noncopyable>( + "torrent_paused_alert", torrent_paused_alert_doc, no_init + ); + + class_, noncopyable>( + "torrent_checked_alert", torrent_checked_alert_doc, no_init + ); + + class_, noncopyable>( + "url_seed_alert", url_seed_alert_doc, no_init ) - .def_readonly("handle", &storage_moved_alert::handle) + .def_readonly("url", &url_seed_alert::url) ; - class_, noncopyable>( + + class_, noncopyable>( + "file_error_alert", file_error_alert_doc, no_init + ); + + class_, noncopyable>( "metadata_failed_alert", metadata_failed_alert_doc, no_init - ) - .def_readonly("handle", &metadata_failed_alert::handle) - ; + ); - class_, noncopyable>( + class_, noncopyable>( "metadata_received_alert", metadata_received_alert_doc, no_init - ) - .def_readonly("handle", &metadata_received_alert::handle) - ; + ); - class_, noncopyable>( - "fastresume_rejected_alert", fastresume_rejected_alert_doc, no_init + class_, noncopyable>( + "listen_failed_alert", listen_failed_alert_doc, no_init + ); + + class_, noncopyable>( + "listen_succeeded_alert", listen_succeeded_alert_doc, no_init ) - .def_readonly("handle", &fastresume_rejected_alert::handle) + .def_readonly("endpoint", &listen_succeeded_alert::endpoint) + ; + + class_, noncopyable>( + "portmap_error_alert", portmap_error_alert_doc, no_init + ); + + class_, noncopyable>( + "portmap_alert", portmap_alert_doc, no_init + ); + + class_, noncopyable>( + "fastresume_rejected_alert", fastresume_rejected_alert_doc, no_init + ); + + class_, noncopyable>( + "peer_blocked_alert", peer_blocked_alert_doc, no_init + ) + .def_readonly("ip", &peer_blocked_alert::ip) ; } - - diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index 30be6e029..6c8682d9c 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -184,19 +184,8 @@ char const* alert_msg_doc = char const* alert_severity_doc = "Returns the severity level for this alert, one of `alert.severity_levels`."; -char const* listen_failed_alert_doc = - "This alert is generated when none of the ports, given in the\n" - "port range, to `session` can be opened for listening. This alert\n" - "is generated as severity level `alert.severity_levels.fatal`."; - -char const* file_error_alert_doc = - "If the storage fails to read or write files that it needs access\n" - "to, this alert is generated and the torrent is paused. It is\n" - "generated as severity level `alert.severity_levels.fatal`."; - -char const* tracker_announce_alert_doc = - "This alert is generated each time a tracker announce is sent\n" - "(or attempted to be sent). It is generated at severity level `alert.severity_levels.info`."; +char const* torrent_alert_doc = + ""; char const* tracker_alert_doc = "This alert is generated on tracker time outs, premature\n" @@ -205,21 +194,21 @@ char const* tracker_alert_doc = "the tracker belongs to. This alert is generated as severity level\n" "`alert.severity_levels.warning`."; -char const* tracker_reply_alert_doc = - "This alert is only for informational purpose. It is generated when\n" - "a tracker announce succeeds. It is generated with severity level\n" - "`alert.severity_levels.info`."; - char const* tracker_warning_alert_doc = "This alert is triggered if the tracker reply contains a warning\n" "field. Usually this means that the tracker announce was successful\n" ", but the tracker has a message to the client. The message string in\n" "the alert will contain the warning message from the tracker. It is\n" "generated with severity level `alert.severity_levels.warning`."; + +char const* tracker_reply_alert_doc = + "This alert is only for informational purpose. It is generated when\n" + "a tracker announce succeeds. It is generated with severity level\n" + "`alert.severity_levels.info`."; -char const* url_seed_alert_doc = - "This alert is generated when a HTTP seed name lookup fails. This\n" - "alert is generated as severity level `alert.severity_levels.warning`."; +char const* tracker_announce_alert_doc = + "This alert is generated each time a tracker announce is sent\n" + "(or attempted to be sent). It is generated at severity level `alert.severity_levels.info`."; char const* hash_failed_alert_doc = "This alert is generated when a finished piece fails its hash check.\n" @@ -258,17 +247,38 @@ char const* torrent_finished_alert_doc = "It contains a `torrent_handle` to the torrent in question. This alert\n" "is generated as severity level `alert.severity_levels.info`."; -char const* torrent_paused_alert_doc = - "This alert is generated when a torrent switches from being a\n" - "active to paused.\n" - "It contains a `torrent_handle` to the torrent in question. This alert\n" - "is generated as severity level `alert.severity_levels.warning`."; +char const* piece_finished_alert_doc = + ""; + +char const* block_finished_alert_doc = + ""; + +char const* block_downloading_alert_doc = + ""; char const* storage_moved_alert_doc = "This alert is generated when a torrent moves storage.\n" "It contains a `torrent_handle` to the torrent in question. This alert\n" "is generated as severity level `alert.severity_levels.warning`."; +char const* torrent_paused_alert_doc = + "This alert is generated when a torrent switches from being a\n" + "active to paused.\n" + "It contains a `torrent_handle` to the torrent in question. This alert\n" + "is generated as severity level `alert.severity_levels.warning`."; + +char const* torrent_checked_alert_doc = + ""; + +char const* url_seed_alert_doc = + "This alert is generated when a HTTP seed name lookup fails. This\n" + "alert is generated as severity level `alert.severity_levels.warning`."; + +char const* file_error_alert_doc = + "If the storage fails to read or write files that it needs access\n" + "to, this alert is generated and the torrent is paused. It is\n" + "generated as severity level `alert.severity_levels.fatal`."; + char const* metadata_failed_alert_doc = "This alert is generated when the metadata has been completely\n" "received and the info-hash failed to match it. i.e. the\n" @@ -285,10 +295,25 @@ char const* metadata_received_alert_doc = "needs to download it from peers (when utilizing the libtorrent\n" "extension). It is generated at severity level `alert.severity_levels.info`."; +char const* listen_failed_alert_doc = + "This alert is generated when none of the ports, given in the\n" + "port range, to `session` can be opened for listening. This alert\n" + "is generated as severity level `alert.severity_levels.fatal`."; + +char const* listen_succeeded_alert_doc = + ""; + +char const* portmap_error_alert_doc = + ""; + +char const* portmap_alert_doc = + ""; + char const* fastresume_rejected_alert_doc = "This alert is generated when a fastresume file has been passed\n" "to `session.add_torrent` but the files on disk did not match the\n" "fastresume file. The string explains the reason why the resume\n" "file was rejected. It is generated at severity level `alert.severity_levels.warning`."; - +char const* peer_blocked_alert_doc = + ""; From 0e73700f1511876385411a712d6afdbfdbcb57fe Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 26 Sep 2007 01:14:55 +0000 Subject: [PATCH 0152/1009] Fix add_torrent() deprecation and fix tracker_reply_alert to have the number of peers. --- libtorrent/bindings/python/src/alert.cpp | 4 +++- libtorrent/bindings/python/src/session.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index c212ed04d..722cee461 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -86,7 +86,9 @@ void bind_alert() class_, noncopyable>( "tracker_reply_alert", tracker_reply_alert_doc, no_init - ); + ) + .def_readonly("num_peers", &tracker_reply_alert::num_peers) + ; class_, noncopyable>( "tracker_announce_alert", tracker_announce_alert_doc, no_init diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 3717ebadd..6d4855c10 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -174,8 +174,8 @@ void bind_session() .def( "add_torrent", &add_torrent , ( - arg("torrent_info"), "save_path", arg("resume_data") = entry() - , arg("compact_mode") = true, arg("paused") = false + arg("resume_data") = entry(), arg("compact_mode") = true + , arg("paused") = false ) , session_add_torrent_doc ) From 78244649b8674286720ec7b0fc9b0adef165a655 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 26 Sep 2007 01:16:14 +0000 Subject: [PATCH 0153/1009] Add support for saving .fastresume files. They are not loaded on add yet. --- deluge/core/core.py | 35 +++++++++++++++++---- deluge/core/torrent.py | 2 +- deluge/core/torrentmanager.py | 58 +++++++++++++++++++++++++++-------- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 644970296..25357d6ae 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -110,6 +110,12 @@ class Core(dbus.service.Object): # Start the AlertManager self.alerts = AlertManager(self.session) + # Register alert functions + self.alerts.register_handler("torrent_finished_alert", + self.on_alert_torrent_finished) + self.alerts.register_handler("torrent_paused_alert", + self.on_alert_torrent_paused) + # Register set functions in the Config self.config.register_set_function("listen_ports", self.on_set_listen_ports) @@ -159,7 +165,7 @@ class Core(dbus.service.Object): del self.config del deluge.configmanager del self.session - + # Exported Methods @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="", out_signature="") @@ -230,15 +236,14 @@ class Core(dbus.service.Object): in_signature="s", out_signature="") def pause_torrent(self, torrent_id): log.debug("Pausing torrent %s", torrent_id) - if self.torrents.pause(torrent_id): - self.torrent_paused(torrent_id) + if not self.torrents.pause(torrent_id): + log.warning("Error pausing torrent %s", torrent_id) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") def pause_all_torrents(self): """Pause all torrents in the session""" - if self.torrents.pause_all(): - # Emit 'torrent_all_paused' signal - self.torrent_all_paused() + if not self.torrents.pause_all(): + log.warning("Error pausing all torrents..") @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") def resume_all_torrents(self): @@ -481,3 +486,21 @@ class Core(dbus.service.Object): def on_set_max_upload_slots_per_torrent(self, key, value): log.debug("max_upload_slots_per_torrent set to %s..", value) self.torrents.set_max_uploads(value) + + ## Alert handlers ## + def on_alert_torrent_finished(self, alert): + log.debug("on_alert_torrent_finished") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + log.debug("%s is finished..", torrent_id) + # Write the fastresume file + self.torrents.write_fastresume(torrent_id) + + def on_alert_torrent_paused(self, alert): + log.debug("on_alert_torrent_paused") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + # Write the fastresume file + self.torrents.write_fastresume(torrent_id) + # Emit torrent_paused signal + self.torrent_paused(torrent_id) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index b582e6e03..0b58bf029 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -49,7 +49,7 @@ class Torrent: self.total_uploaded = 0 # Set the allocation mode self.compact = compact - + def get_state(self): """Returns the state of this torrent for saving to the session state""" return (self.torrent_id, self.filename, self.compact) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 7a763f64e..fd53f0c54 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -63,6 +63,8 @@ class TorrentManager: log.debug("TorrentManager init..") # Set the libtorrent session self.session = session + # Get the core config + self.config = ConfigManager("core.conf") # Per torrent connection limit and upload slot limit self.max_connections = -1 self.max_uploads = -1 @@ -87,8 +89,6 @@ class TorrentManager: def add(self, filename, filedump=None, compact=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) - # Get the core config - config = ConfigManager("core.conf") # Make sure 'filename' is a python string filename = str(filename) @@ -103,7 +103,8 @@ class TorrentManager: # Get the data from the file try: log.debug("Attempting to open %s for add.", filename) - filedump = open(os.path.join(config["torrentfiles_location"], + filedump = open( + os.path.join(self.config["torrentfiles_location"], filename), "rb").read() except IOError: log.warning("Unable to open %s", filename) @@ -115,12 +116,12 @@ class TorrentManager: # Make sure we are adding it with the correct allocation method. if compact is None: - compact = config["compact_allocation"] + compact = self.config["compact_allocation"] try: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), - config["download_location"], + self.config["download_location"], compact) except RuntimeError: log.warning("Error adding torrent") @@ -135,17 +136,18 @@ class TorrentManager: log.debug("Attemping to save torrent file: %s", filename) # Test if the torrentfiles_location is accessible - if os.access(os.path.join(config["torrentfiles_location"]), os.F_OK) \ + if os.access( + os.path.join(self.config["torrentfiles_location"]), os.F_OK) \ is False: # The directory probably doesn't exist, so lets create it try: - os.makedirs(os.path.join(config["torrentfiles_location"])) + os.makedirs(os.path.join(self.config["torrentfiles_location"])) except IOError: log.warning("Unable to create torrent files directory..") # Write the .torrent file to the torrent directory try: - save_file = open(os.path.join(config["torrentfiles_location"], + save_file = open(os.path.join(self.config["torrentfiles_location"], filename), "wb") save_file.write(filedump) @@ -207,16 +209,21 @@ class TorrentManager: except: return False + status = self.torrents[torrent_id].get_status( + ["total_done", "total_wanted"]) + + # Only delete the .fastresume file if we're still downloading stuff + if status["total_done"] < status["total_wanted"]: + self.delete_fastresume(torrent_id) return True def resume_all(self): """Resumes all torrents.. Returns a list of torrents resumed""" torrent_was_resumed = False for key in self.torrents.keys(): - try: - self.torrents[key].handle.resume() + if self.resume(key): torrent_was_resumed = True - except: + else: log.warning("Unable to resume torrent %s", key) return torrent_was_resumed @@ -264,7 +271,34 @@ class TorrentManager: state_file.close() except IOError: log.warning("Unable to save state file.") - + + def delete_fastresume(self, torrent_id): + """Deletes the .fastresume file""" + torrent = self.torrents[torrent_id] + path = "%s/%s.fastresume" % ( + self.config["torrentfiles_location"], + torrent.filename) + log.debug("Deleting fastresume file: %s", path) + try: + os.remove(path) + except IOError: + log.warning("Unable to delete the fastresume file: %s", path) + + def write_fastresume(self, torrent_id): + """Writes the .fastresume file for the torrent""" + torrent = self.torrents[torrent_id] + resume_data = lt.bencode(torrent.handle.write_resume_data()) + path = "%s/%s.fastresume" % ( + self.config["torrentfiles_location"], + torrent.filename) + log.debug("Saving fastresume file: %s", path) + try: + fastresume = open(path,"wb") + fastresume.write(resume_data) + fastresume.close() + except IOError: + log.warning("Error trying to save fastresume file") + def set_max_connections(self, value): """Sets the per-torrent connection limit""" self.max_connections = value From 4401c8f238299c84422911df286d461b56c9a280 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 26 Sep 2007 01:27:38 +0000 Subject: [PATCH 0154/1009] Save .fastresume files on shutdown. --- deluge/core/core.py | 2 +- deluge/core/torrentmanager.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 25357d6ae..5f668c2e5 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -157,9 +157,9 @@ class Core(dbus.service.Object): """This is called by a thread from shutdown()""" log.info("Shutting down core..") self.loop.quit() - del self.torrents self.plugins.shutdown() del self.plugins + del self.torrents # Make sure the config file has been saved self.config.save() del self.config diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index fd53f0c54..8a810fb66 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -77,7 +77,11 @@ class TorrentManager: log.debug("TorrentManager shutting down..") # Save state on shutdown self.save_state() - + # Pause all torrents and save the .fastresume files + self.pause_all() + for key in self.torrents.keys(): + self.write_fastresume(key) + def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" return self.torrents[torrent_id] From 936df09c60dba34355d3ba61f845b0e7aca1d05d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 27 Sep 2007 13:29:20 +0000 Subject: [PATCH 0155/1009] Fix issue where port was not opened before attempting to add torrents. All config 'set functions' will apply automatically when they are registered unless otherwise specified. --- deluge/config.py | 5 +++- deluge/core/core.py | 45 ++++++++++++----------------------- deluge/core/torrentmanager.py | 31 ++++++++++++++++-------- 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index 187b2ffe1..bbb9dd6ed 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -138,10 +138,13 @@ class Config: """Returns the entire configuration as a dictionary.""" return self.config - def register_set_function(self, key, function): + def register_set_function(self, key, function, apply_now=True): """Register a function to be run when a config value changes.""" log.debug("Registering function for %s key..", key) self.set_functions[key] = function + # Run the function now if apply_now is set + if apply_now: + self.set_functions[key](key, self.config[key]) return def apply_all(self): diff --git a/deluge/core/core.py b/deluge/core/core.py index 5f668c2e5..714a4edd5 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -101,21 +101,6 @@ class Core(dbus.service.Object): # Load metadata extension self.session.add_extension(lt.create_metadata_plugin) - # Start the TorrentManager - self.torrents = TorrentManager(self.session) - - # Load plugins - self.plugins = PluginManager() - - # Start the AlertManager - self.alerts = AlertManager(self.session) - - # Register alert functions - self.alerts.register_handler("torrent_finished_alert", - self.on_alert_torrent_finished) - self.alerts.register_handler("torrent_paused_alert", - self.on_alert_torrent_paused) - # Register set functions in the Config self.config.register_set_function("listen_ports", self.on_set_listen_ports) @@ -141,14 +126,22 @@ class Core(dbus.service.Object): self.on_set_max_download_speed) self.config.register_set_function("max_upload_slots_global", self.on_set_max_upload_slots_global) - self.config.register_set_function("max_connections_per_torrent", - self.on_set_max_connections_per_torrent) - self.config.register_set_function("max_upload_slots_per_torrent", - self.on_set_max_upload_slots_per_torrent) - - # Run all the set functions now to set the config for the session - self.config.apply_all() + + # Start the TorrentManager + self.torrents = TorrentManager(self.session) + # Load plugins + self.plugins = PluginManager() + + # Start the AlertManager + self.alerts = AlertManager(self.session) + + # Register alert functions + self.alerts.register_handler("torrent_finished_alert", + self.on_alert_torrent_finished) + self.alerts.register_handler("torrent_paused_alert", + self.on_alert_torrent_paused) + log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() @@ -479,14 +472,6 @@ class Core(dbus.service.Object): log.debug("max_upload_slots_global set to %s..", value) self.session.set_max_uploads(value) - def on_set_max_connections_per_torrent(self, key, value): - log.debug("max_connections_per_torrent set to %s..", value) - self.torrents.set_max_connections(value) - - def on_set_max_upload_slots_per_torrent(self, key, value): - log.debug("max_upload_slots_per_torrent set to %s..", value) - self.torrents.set_max_uploads(value) - ## Alert handlers ## def on_alert_torrent_finished(self, alert): log.debug("on_alert_torrent_finished") diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 8a810fb66..ab8478dd8 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -73,6 +73,12 @@ class TorrentManager: # Try to load the state from file self.load_state() + # Register set functions + self.config.register_set_function("max_connections_per_torrent", + self.on_set_max_connections_per_torrent) + self.config.register_set_function("max_upload_slots_per_torrent", + self.on_set_max_upload_slots_per_torrent) + def __del__(self): log.debug("TorrentManager shutting down..") # Save state on shutdown @@ -107,17 +113,19 @@ class TorrentManager: # Get the data from the file try: log.debug("Attempting to open %s for add.", filename) - filedump = open( - os.path.join(self.config["torrentfiles_location"], - filename), "rb").read() + _file = open( + os.path.join( + self.config["torrentfiles_location"], filename), "rb") + filedump = _file.read() + _file.close() except IOError: log.warning("Unable to open %s", filename) return None - + # Bdecode the filedata torrent_filedump = lt.bdecode(filedump) handle = None - + # Make sure we are adding it with the correct allocation method. if compact is None: compact = self.config["compact_allocation"] @@ -154,7 +162,7 @@ class TorrentManager: save_file = open(os.path.join(self.config["torrentfiles_location"], filename), "wb") - save_file.write(filedump) + save_file.write(lt.bencode(torrent_filedump)) save_file.close() except IOError: log.warning("Unable to save torrent file: %s", filename) @@ -302,15 +310,18 @@ class TorrentManager: fastresume.close() except IOError: log.warning("Error trying to save fastresume file") - - def set_max_connections(self, value): + + def on_set_max_connections_per_torrent(self, key, value): """Sets the per-torrent connection limit""" + log.debug("max_connections_per_torrent set to %s..", value) self.max_connections = value for key in self.torrents.keys(): self.torrents[key].handle.set_max_connections(value) - - def set_max_uploads(self, value): + + def on_set_max_upload_slots_per_torrent(self, key, value): """Sets the per-torrent upload slot limit""" + log.debug("max_upload_slots_per_torrent set to %s..", value) self.max_uploads = value for key in self.torrents.keys(): self.torrents[key].handle.set_max_uploads(value) + From 9551aab1c1b12a625584e6e54ddfcb17d899911d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 27 Sep 2007 13:41:19 +0000 Subject: [PATCH 0156/1009] Properly show the Columns menu. --- deluge/ui/gtkui/listview.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index ec6cb0aa8..e8cfdc701 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -164,7 +164,7 @@ class ListView: def create_checklist_menu(self): """Creates a menu used for toggling the display of columns.""" - self.menu = gtk.Menu() + menu = gtk.Menu() # Iterate through the column_index list to preserve order for name in self.column_index: column = self.columns[name] @@ -180,13 +180,14 @@ class ListView: # Connect to the 'toggled' event menuitem.connect("toggled", self.on_menuitem_toggled) # Add the new checkmenuitem to the menu - self.menu.append(menuitem) + menu.append(menuitem) # Attach this new menu to all the checklist_menus - for menu in self.checklist_menus: - menu.set_submenu(self.menu) + for _menu in self.checklist_menus: + _menu.set_submenu(menu) + _menu.show_all() - return self.menu + return menu def create_new_liststore(self): """Creates a new GtkListStore based on the liststore_columns list""" From e0d82901e3eb1e154f052ffa266fdd0bc375dadc Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Sep 2007 06:29:04 +0000 Subject: [PATCH 0157/1009] Remove silly line causing exception in get_torrent_status() --- deluge/core/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 714a4edd5..af3478a19 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -266,7 +266,6 @@ class Core(dbus.service.Object): except KeyError: # The torrent_id is not found in the torrentmanager, so return None status = None - status.pickle.dumps(status) return status # Get the leftover fields and ask the plugin manager to fill them From b6456307731730991cb9fb27c953c383368ff9eb Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 29 Sep 2007 05:32:42 +0000 Subject: [PATCH 0158/1009] Only enable the system tray once. --- deluge/ui/gtkui/mainwindow.py | 1 + deluge/ui/gtkui/systemtray.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 671708529..5be1eb048 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -115,6 +115,7 @@ class MainWindow: def quit(self): # Stop the update timer from running gobject.source_remove(self.update_timer) + del self.systemtray del self.menubar del self.toolbar del self.torrentview diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 5a6d2a418..ec7b013e7 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -45,8 +45,8 @@ class SystemTray: self.config = ConfigManager("gtkui.conf") self.config.register_set_function("enable_system_tray", self.on_enable_system_tray_set) - if self.config["enable_system_tray"]: - self.enable() + #if self.config["enable_system_tray"]: + # self.enable() def enable(self): """Enables the system tray icon.""" From 234e6827e8223c4c0236350e42cc625697939fe7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 29 Sep 2007 10:37:46 +0000 Subject: [PATCH 0159/1009] Load .fastresume files on add. Fix opening browser function. --- deluge/core/core.py | 3 +-- deluge/core/torrentmanager.py | 18 ++++++++++++++++-- deluge/ui/functions.py | 19 +++++++++---------- deluge/ui/gtkui/preferences.py | 7 ++++--- deluge/ui/gtkui/systemtray.py | 2 -- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index af3478a19..3ae8b74e0 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -265,8 +265,7 @@ class Core(dbus.service.Object): status = self.torrents[torrent_id].get_status(nkeys) except KeyError: # The torrent_id is not found in the torrentmanager, so return None - status = None - return status + return None # Get the leftover fields and ask the plugin manager to fill them leftover_fields = list(set(nkeys) - set(status.keys())) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index ab8478dd8..b8985665a 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -121,7 +121,20 @@ class TorrentManager: except IOError: log.warning("Unable to open %s", filename) return None - + + # Attempt to load fastresume data + try: + _file = open( + os.path.join( + self.config["torrentfiles_location"], + filename + ".fastresume"), + "rb") + fastresume = lt.bdecode(_file.read()) + _file.close() + except IOError: + log.debug("Unable to load .fastresume..") + fastresume = None + # Bdecode the filedata torrent_filedump = lt.bdecode(filedump) handle = None @@ -134,7 +147,8 @@ class TorrentManager: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), self.config["download_location"], - compact) + resume_data=fastresume, + compact_mode=compact) except RuntimeError: log.warning("Error adding torrent") diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 65f26b2e0..3c894f67e 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -177,14 +177,13 @@ def get_listen_port(core=None): core = get_core() return int(core.get_listen_port()) -def open_url_in_browser(link): +def open_url_in_browser(url): """Opens link in the desktop's default browser""" - import threading - import webbrowser - class BrowserThread(threading.Thread): - def __init__(self, link): - threading.Thread.__init__(self) - self.url = link - def run(self): - webbrowser.open(self.url) - BrowserThread(link).start() + def start_browser(): + import webbrowser + log.debug("Opening webbrowser with url: %s", url) + webbrowser.open(url) + return False + + import gobject + gobject.idle_add(start_browser) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index d1966ade8..38f18db7e 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -356,7 +356,8 @@ class Preferences: self.notebook.set_current_page(model.get_value(row, 0)) def on_test_port_clicked(self, data): - functions.open_url_in_browser('\ - http://www.deluge-torrent.org/test-port.php?port=%s' % \ - functions.get_listen_port(self.core)) + log.debug("on_test_port_clicked") + url = "http://deluge-torrent.org/test-port.php?port=%s" % \ + functions.get_listen_port(self.core) + functions.open_url_in_browser(url) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index ec7b013e7..115dce987 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -45,8 +45,6 @@ class SystemTray: self.config = ConfigManager("gtkui.conf") self.config.register_set_function("enable_system_tray", self.on_enable_system_tray_set) - #if self.config["enable_system_tray"]: - # self.enable() def enable(self): """Enables the system tray icon.""" From 466292d38e34b589c6b1bcddfba4e32c3c904d70 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 03:49:30 +0000 Subject: [PATCH 0160/1009] clean up adding via url and make enter submit it instead of having to click ok --- deluge/ui/gtkui/addtorrenturl.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/deluge/ui/gtkui/addtorrenturl.py b/deluge/ui/gtkui/addtorrenturl.py index 5f11c1a4d..4b07fc8cc 100644 --- a/deluge/ui/gtkui/addtorrenturl.py +++ b/deluge/ui/gtkui/addtorrenturl.py @@ -42,16 +42,26 @@ import pkg_resources class AddTorrentUrl: def __init__(self, parent=None): """Set up url dialog""" - self.dlg = gtk.Dialog(title=_("Add torrent from URL"), parent=None, - buttons=(gtk.STOCK_CANCEL, 0, gtk.STOCK_OK, 1)) + self.dlg = gtk.Dialog(_("Add torrent from URL"), None, 0, \ + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OK, gtk.RESPONSE_OK)) + self.dlg.set_default_response(gtk.RESPONSE_OK) self.dlg.set_icon(deluge.common.get_logo(32)) self.dlg.set_default_response(1) label = gtk.Label(_("Enter the URL of the .torrent to download")) self.entry = gtk.Entry() + self.entry.connect("activate", lambda w : self.dlg.response\ + (gtk.RESPONSE_OK)) self.dlg.vbox.pack_start(label) self.dlg.vbox.pack_start(self.entry) - clip = gtk.clipboard_get(selection='PRIMARY') - text = clip.wait_for_text() + if deluge.common.windows_check(): + import win32clipboard as clip + import win32con + clip.OpenClipboard() + text = clip.GetClipboardData(win32con.CF_UNICODETEXT) + clip.CloseClipboard() + else: + clip = gtk.clipboard_get(selection='PRIMARY') + text = clip.wait_for_text() if text: text = text.strip() if deluge.common.is_url(text): @@ -61,9 +71,10 @@ class AddTorrentUrl: """Show url dialog and add torrent""" self.dlg.show_all() self.response = self.dlg.run() - url = self.entry.get_text() - self.dlg.destroy() - if self.response == 1: + if self.response == gtk.RESPONSE_OK: + url = self.entry.get_text().decode("utf_8") + self.dlg.destroy() return url else: + self.dlg.destroy() return None From f2f73b85393e55cb7ce0194a2877b5e382d35d1e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 30 Sep 2007 05:33:53 +0000 Subject: [PATCH 0161/1009] Fix exception when removing multiple torrents at once. Updated TODO. --- TODO | 2 -- deluge/core/core.py | 9 +++++---- deluge/core/torrent.py | 6 +++--- deluge/core/torrentmanager.py | 4 ++-- deluge/ui/gtkui/torrentdetails.py | 6 +++++- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/TODO b/TODO index fdb76310f..08d7e3bad 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* Have peer numbers show what we receive from tracker_reply_alert * Have the ui better handle not being able to connect to the daemon. * Tray locking and other tray options (close to tray, start in tray..) * Add state saving to listview.. this includes saving column size and position. @@ -14,7 +13,6 @@ * Change the menubar.py gtkui component to menus.py and add support for plugins to add menuitems to the torrentmenu in an easy way. * Add the tracker responses to the torrent details -* Fast resume saving * Restart daemon function * Sync the details pane to current trunk * Automatically save torrent state every ~5 minutes, much like how diff --git a/deluge/core/core.py b/deluge/core/core.py index 3ae8b74e0..e4f99b602 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -151,8 +151,7 @@ class Core(dbus.service.Object): log.info("Shutting down core..") self.loop.quit() self.plugins.shutdown() - del self.plugins - del self.torrents + self.torrents.shutdown() # Make sure the config file has been saved self.config.save() del self.config @@ -265,7 +264,9 @@ class Core(dbus.service.Object): status = self.torrents[torrent_id].get_status(nkeys) except KeyError: # The torrent_id is not found in the torrentmanager, so return None - return None + status = None + status = pickle.dumps(status) + return status # Get the leftover fields and ask the plugin manager to fill them leftover_fields = list(set(nkeys) - set(status.keys())) @@ -486,4 +487,4 @@ class Core(dbus.service.Object): # Write the fastresume file self.torrents.write_fastresume(torrent_id) # Emit torrent_paused signal - self.torrent_paused(torrent_id) + self.torrent_paused(torrent_id) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 0b58bf029..cbe017236 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -49,7 +49,7 @@ class Torrent: self.total_uploaded = 0 # Set the allocation mode self.compact = compact - + def get_state(self): """Returns the state of this torrent for saving to the session state""" return (self.torrent_id, self.filename, self.compact) @@ -94,12 +94,12 @@ class Torrent: progress = status.progress*100 # Get the total number of seeds and peers - if status.num_complete is -1: + if status.num_complete == -1: total_seeds = status.num_seeds else: total_seeds = status.num_complete - if status.num_incomplete is -1: + if status.num_incomplete == -1: total_peers = status.num_peers - status.num_seeds else: total_peers = status.num_incomplete diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index b8985665a..c02e332c0 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -78,8 +78,8 @@ class TorrentManager: self.on_set_max_connections_per_torrent) self.config.register_set_function("max_upload_slots_per_torrent", self.on_set_max_upload_slots_per_torrent) - - def __del__(self): + + def shutdown(self): log.debug("TorrentManager shutting down..") # Save state on shutdown self.save_state() diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index f7fd2e0c2..7eaffde3d 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -96,7 +96,11 @@ class TorrentDetails: status = functions.get_torrent_status(self.core, selected, status_keys) - + + # Check to see if we got valid data from the core + if status is None: + return + # We need to adjust the value core gives us for progress progress = status["progress"]/100 self.progress_bar.set_fraction(progress) From 1ce3eb709977e3d2e98a6b21b8c62298d7b5aa72 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 06:07:55 +0000 Subject: [PATCH 0162/1009] add quit & shutdown daemon to tray --- deluge/ui/gtkui/glade/tray_menu.glade | 9 +++++++++ deluge/ui/gtkui/systemtray.py | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/glade/tray_menu.glade b/deluge/ui/gtkui/glade/tray_menu.glade index 6caae77ed..8c0155fdb 100644 --- a/deluge/ui/gtkui/glade/tray_menu.glade +++ b/deluge/ui/gtkui/glade/tray_menu.glade @@ -121,6 +121,15 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Quit & Shutdown Daemon + True + + + True diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 115dce987..2f44d6168 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -67,7 +67,8 @@ class SystemTray: self.on_menuitem_pause_all_activate, "on_menuitem_resume_all_activate": \ self.on_menuitem_resume_all_activate, - "on_menuitem_quit_activate": self.on_menuitem_quit_activate + "on_menuitem_quit_activate": self.on_menuitem_quit_activate, + "on_menuitem_quit_daemon": self.on_menuitem_quitdaemon_activate }) self.tray_menu = self.tray_glade.get_widget("tray_menu") @@ -173,6 +174,11 @@ class SystemTray: def on_menuitem_quit_activate(self, menuitem): log.debug("on_menuitem_quit_activate") self.window.quit() + + def on_menuitem_quitdaemon_activate(self, menuitem): + log.debug("on_menuitem_quitdaemon_activate") + functions.shutdown() + self.window.quit() def build_menu_radio_list(self, value_list, callback, pref_value=None, suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, From fa73b6d125b13c68a096e42f09bea56a0956c981 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 06:10:28 +0000 Subject: [PATCH 0163/1009] add icon to quit daemon --- deluge/ui/gtkui/glade/tray_menu.glade | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/glade/tray_menu.glade b/deluge/ui/gtkui/glade/tray_menu.glade index 8c0155fdb..947861466 100644 --- a/deluge/ui/gtkui/glade/tray_menu.glade +++ b/deluge/ui/gtkui/glade/tray_menu.glade @@ -122,12 +122,19 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Quit & Shutdown Daemon True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-quit + + From 78f01e1397678f17b0eb103a8d24fd921dc487c5 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 06:14:03 +0000 Subject: [PATCH 0164/1009] set icon size and fix typo --- deluge/ui/gtkui/glade/tray_menu.glade | 1 + deluge/ui/gtkui/systemtray.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/glade/tray_menu.glade b/deluge/ui/gtkui/glade/tray_menu.glade index 947861466..d54b82ca2 100644 --- a/deluge/ui/gtkui/glade/tray_menu.glade +++ b/deluge/ui/gtkui/glade/tray_menu.glade @@ -133,6 +133,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-quit + 1 diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 2f44d6168..5f06eb2c7 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -68,7 +68,8 @@ class SystemTray: "on_menuitem_resume_all_activate": \ self.on_menuitem_resume_all_activate, "on_menuitem_quit_activate": self.on_menuitem_quit_activate, - "on_menuitem_quit_daemon": self.on_menuitem_quitdaemon_activate + "on_menuitem_quitdaemon_activate": \ + self.on_menuitem_quitdaemon_activate }) self.tray_menu = self.tray_glade.get_widget("tray_menu") From fd03c62ccb1386a42d7e8d54980a3cb2b8df7f7c Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 07:28:06 +0000 Subject: [PATCH 0165/1009] add tray locking --- deluge/ui/gtkui/menubar.py | 29 +++++++++++++++-- deluge/ui/gtkui/systemtray.py | 59 ++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 345af06af..e0962b513 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -112,12 +112,35 @@ class MenuBar: def on_menuitem_quitdaemon_activate(self, data=None): log.debug("on_menuitem_quitdaemon_activate") # Tell the core to shutdown - functions.shutdown() - self.window.quit() + if self.window.visible(): + self.window.hide() + self.window.quit() + functions.shutdown() + else: + from deluge.configmanager import ConfigManager + if self.config.get("lock_tray") == True: + from deluge.ui.gtk.systemtray import SystemTray + SystemTray.unlock_tray("quitdaemon") + else: + self.window.hide() + self.window.quit() + functions.shutdown() def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") - self.window.quit() + if self.window.visible(): + self.window.hide() + self.window.quit() + else: + from deluge.configmanager import ConfigManager + self.config = ConfigManager("gtkui.conf") + if self.config.get("lock_tray") == True: + log.debug("trying to import") + from deluge.ui.gtk.systemtray import SystemTray + SystemTray.unlock_tray("quitui") + else: + self.window.hide() + self.window.quit() ## Edit Menu ## def on_menuitem_preferences_activate(self, data=None): diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 5f06eb2c7..037019852 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -132,7 +132,7 @@ class SystemTray: self.window.update() else: if self.config["lock_tray"] == True: - log.debug("Implement tray locking please!") + self.unlock_tray("mainwinshow") else: self.window.load_window_geometry() self.window.show() @@ -153,7 +153,7 @@ class SystemTray: log.debug("on_menuitem_show_deluge_activate") if menuitem.get_active() and not self.window.visible(): if self.config["lock_tray"] == True: - self.unlock_tray() + self.unlock_tray("mainwinshow") else: self.window.show() elif not menuitem.get_active() and self.window.visible(): @@ -174,12 +174,18 @@ class SystemTray: def on_menuitem_quit_activate(self, menuitem): log.debug("on_menuitem_quit_activate") - self.window.quit() + if self.window.visible(): + self.window.quit() + else: + self.unlock_tray("quitui") def on_menuitem_quitdaemon_activate(self, menuitem): log.debug("on_menuitem_quitdaemon_activate") - functions.shutdown() - self.window.quit() + if self.window.visible(): + self.window.quit() + functions.shutdown() + else: + self.unlock_tray("quitdaemon") def build_menu_radio_list(self, value_list, callback, pref_value=None, suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, @@ -295,5 +301,44 @@ class SystemTray: # Update the UI self.window.update() - def unlock_tray(self): - log.debug("Tray locking needs implementation..!") + def unlock_tray(self, comingnext, is_showing_dlg=[False]): + log.debug("Show tray lock dialog") + if is_showing_dlg[0]: + return + is_showing_dlg[0] = True + + entered_pass = gtk.Entry(25) + entered_pass.set_activates_default(True) + entered_pass.set_width_chars(25) + entered_pass.set_visibility(False) + entered_pass.show() + tray_lock = gtk.Dialog(title=_("Deluge is locked"), parent=None, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, + gtk.RESPONSE_ACCEPT)) + label = gtk.Label(_("Deluge is password protected.\nTo show the Deluge \ +window, please enter your password")) + label.set_line_wrap(True) + label.set_justify(gtk.JUSTIFY_CENTER) + tray_lock.set_position(gtk.WIN_POS_CENTER_ALWAYS) + tray_lock.set_size_request(400, 200) + tray_lock.set_default_response(gtk.RESPONSE_ACCEPT) + tray_lock.vbox.pack_start(label) + tray_lock.vbox.pack_start(entered_pass) + tray_lock.show_all() + if tray_lock.run() == gtk.RESPONSE_ACCEPT: + if self.config["tray_password"] == entered_pass.get_text(): + if comingnext == "mainwinshow": + log.debug("Showing main window via tray") + self.window.show() + elif comingnext == "quitdaemon": + functions.shutdown() + self.window.hide() + self.window.quit() + elif comingnext == "quitui": + log.debug("Quiting UI via tray") + self.window.hide() + self.window.quit() + tray_lock.destroy() + is_showing_dlg[0] = False + return True + From ca43940c1f01707773a925a608e5b538a98a1b29 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 07:52:48 +0000 Subject: [PATCH 0166/1009] sha1 hash password for tray lock --- deluge/ui/gtkui/preferences.py | 10 ++++------ deluge/ui/gtkui/systemtray.py | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 38f18db7e..824709bbd 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -162,9 +162,6 @@ class Preferences: self.gtkui_config["start_in_tray"]) self.glade.get_widget("chk_lock_tray").set_active( self.gtkui_config["lock_tray"]) - self.glade.get_widget("txt_tray_password").set_text( - self.gtkui_config["tray_password"]) - self.glade.get_widget("combo_file_manager").set_active( self.gtkui_config["stock_file_manager"]) self.glade.get_widget("txt_open_folder_location").set_text( @@ -185,6 +182,7 @@ class Preferences: def set_config(self): """Sets all altered config values in the core""" + import sha # Get the values from the dialog new_core_config = {} new_gtkui_config = {} @@ -251,9 +249,9 @@ class Preferences: self.glade.get_widget("chk_start_in_tray").get_active() new_gtkui_config["lock_tray"] = \ self.glade.get_widget("chk_lock_tray").get_active() - new_gtkui_config["tray_password"] = \ - self.glade.get_widget("txt_tray_password").get_text() - + password = sha.new(self.glade.get_widget("txt_tray_password").\ + get_text()).hexdigest() + new_gtkui_config["tray_password"] = password new_gtkui_config["stock_file_manager"] = \ self.glade.get_widget("combo_file_manager").get_active() new_gtkui_config["open_folder_location"] = \ diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 037019852..4e78b3df1 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -302,6 +302,7 @@ class SystemTray: self.window.update() def unlock_tray(self, comingnext, is_showing_dlg=[False]): + import sha log.debug("Show tray lock dialog") if is_showing_dlg[0]: return @@ -326,7 +327,8 @@ window, please enter your password")) tray_lock.vbox.pack_start(entered_pass) tray_lock.show_all() if tray_lock.run() == gtk.RESPONSE_ACCEPT: - if self.config["tray_password"] == entered_pass.get_text(): + if self.config["tray_password"] == sha.new(entered_pass.get_text())\ + .hexdigest(): if comingnext == "mainwinshow": log.debug("Showing main window via tray") self.window.show() From 4a858a70bcb90629c7c8435434649a10c9100d06 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 09:12:38 +0000 Subject: [PATCH 0167/1009] fix tray password resetting itself sometimes --- deluge/ui/gtkui/preferences.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 824709bbd..7e071fc1b 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -162,6 +162,8 @@ class Preferences: self.gtkui_config["start_in_tray"]) self.glade.get_widget("chk_lock_tray").set_active( self.gtkui_config["lock_tray"]) + self.glade.get_widget("txt_tray_password").set_text( + self.gtkui_config["tray_password"]) self.glade.get_widget("combo_file_manager").set_active( self.gtkui_config["stock_file_manager"]) self.glade.get_widget("txt_open_folder_location").set_text( @@ -249,8 +251,11 @@ class Preferences: self.glade.get_widget("chk_start_in_tray").get_active() new_gtkui_config["lock_tray"] = \ self.glade.get_widget("chk_lock_tray").get_active() - password = sha.new(self.glade.get_widget("txt_tray_password").\ - get_text()).hexdigest() + if len(self.glade.get_widget("txt_tray_password").get_text()) == 40: + password = self.glade.get_widget("txt_tray_password").get_text() + else: + password = sha.new(self.glade.get_widget("txt_tray_password").\ + get_text()).hexdigest() new_gtkui_config["tray_password"] = password new_gtkui_config["stock_file_manager"] = \ self.glade.get_widget("combo_file_manager").get_active() From c6c649485e9fa4426eb823ad78b3968b8529038c Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 30 Sep 2007 09:26:05 +0000 Subject: [PATCH 0168/1009] enable start in tray --- deluge/ui/gtkui/mainwindow.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 5be1eb048..ec7c7118b 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -84,7 +84,11 @@ class MainWindow: def start(self): """Start the update thread and show the window""" self.update_timer = gobject.timeout_add(1000, self.update) - self.show() + if not(self.config["start_in_tray"] and \ + self.config["enable_system_tray"]) and not \ + self.window.get_property("visible"): + log.debug("Showing window") + self.show() def update(self): # Don't update the UI if the the window is minimized. From 4d4b2de7c3a70dae140a1221c9e8f596fd1136a8 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 1 Oct 2007 01:52:25 +0000 Subject: [PATCH 0169/1009] remove misplaced and unnecessary code --- deluge/ui/gtkui/menubar.py | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index e0962b513..e7e3d8c0e 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -112,35 +112,12 @@ class MenuBar: def on_menuitem_quitdaemon_activate(self, data=None): log.debug("on_menuitem_quitdaemon_activate") # Tell the core to shutdown - if self.window.visible(): - self.window.hide() - self.window.quit() - functions.shutdown() - else: - from deluge.configmanager import ConfigManager - if self.config.get("lock_tray") == True: - from deluge.ui.gtk.systemtray import SystemTray - SystemTray.unlock_tray("quitdaemon") - else: - self.window.hide() - self.window.quit() - functions.shutdown() + self.window.quit() + functions.shutdown() def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") - if self.window.visible(): - self.window.hide() - self.window.quit() - else: - from deluge.configmanager import ConfigManager - self.config = ConfigManager("gtkui.conf") - if self.config.get("lock_tray") == True: - log.debug("trying to import") - from deluge.ui.gtk.systemtray import SystemTray - SystemTray.unlock_tray("quitui") - else: - self.window.hide() - self.window.quit() + self.window.quit() ## Edit Menu ## def on_menuitem_preferences_activate(self, data=None): From 9a66035f0db1f9ad4898be10663b29f8d5473f7d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 1 Oct 2007 22:29:05 +0000 Subject: [PATCH 0170/1009] Add 'paused' to TorrentState. Move some alert handlers around. --- deluge/common.py | 16 ++++++++++++++ deluge/core/core.py | 32 +++++++++------------------- deluge/core/torrent.py | 9 +++++++- deluge/core/torrentmanager.py | 40 +++++++++++++++++++++++++++++------ 4 files changed, 67 insertions(+), 30 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 0f432dab1..b044da775 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -174,3 +174,19 @@ def fetch_url(url): else: log.debug("URL doesn't appear to be a valid torrent file: %s", url) return None + +def pythonize(var): + """Translates DBUS types back to basic Python types.""" + if isinstance(var, list): + return [pythonize(value) for value in var] + if isinstance(var, tuple): + return tuple([pythonize(value) for value in var]) + if isinstance(var, dict): + return dict( + [(pythonize(key), pythonize(value)) for key, value in var.iteritems()] + ) + + for klass in [unicode, str, bool, int, float, long]: + if isinstance(var,klass): + return klass(var) + return var diff --git a/deluge/core/core.py b/deluge/core/core.py index e4f99b602..3734bad34 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -126,22 +126,20 @@ class Core(dbus.service.Object): self.on_set_max_download_speed) self.config.register_set_function("max_upload_slots_global", self.on_set_max_upload_slots_global) - + + # Start the AlertManager + self.alerts = AlertManager(self.session) + # Start the TorrentManager - self.torrents = TorrentManager(self.session) + self.torrents = TorrentManager(self.session, self.alerts) # Load plugins self.plugins = PluginManager() - - # Start the AlertManager - self.alerts = AlertManager(self.session) - - # Register alert functions - self.alerts.register_handler("torrent_finished_alert", - self.on_alert_torrent_finished) + + # Register alert handlers self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused) - + log.debug("Starting main loop..") self.loop = gobject.MainLoop() self.loop.run() @@ -470,21 +468,11 @@ class Core(dbus.service.Object): def on_set_max_upload_slots_global(self, key, value): log.debug("max_upload_slots_global set to %s..", value) self.session.set_max_uploads(value) - + ## Alert handlers ## - def on_alert_torrent_finished(self, alert): - log.debug("on_alert_torrent_finished") - # Get the torrent_id - torrent_id = str(alert.handle.info_hash()) - log.debug("%s is finished..", torrent_id) - # Write the fastresume file - self.torrents.write_fastresume(torrent_id) - def on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) - # Write the fastresume file - self.torrents.write_fastresume(torrent_id) # Emit torrent_paused signal - self.torrent_paused(torrent_id) + self.torrent_paused(torrent_id) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index cbe017236..c95a27b5f 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -49,10 +49,17 @@ class Torrent: self.total_uploaded = 0 # Set the allocation mode self.compact = compact + # The reply from the tracker + self.tracker_reply = "" + def set_tracker_reply(self, reply): + """Sets the tracker reply message""" + self.tracker_reply = reply + def get_state(self): """Returns the state of this torrent for saving to the session state""" - return (self.torrent_id, self.filename, self.compact) + status = self.handle.status() + return (self.torrent_id, self.filename, self.compact, status.paused) def get_eta(self): """Returns the ETA in seconds for this torrent""" diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c02e332c0..bc4ddd0a0 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -45,10 +45,11 @@ from deluge.core.torrent import Torrent from deluge.log import LOG as log class TorrentState: - def __init__(self, torrent_id, filename, compact): + def __init__(self, torrent_id, filename, compact, paused): self.torrent_id = torrent_id self.filename = filename self.compact = compact + self.paused = paused class TorrentManagerState: def __init__(self): @@ -59,10 +60,12 @@ class TorrentManager: session. This object is also responsible for saving the state of the session for use on restart.""" - def __init__(self, session): + def __init__(self, session, alerts): log.debug("TorrentManager init..") # Set the libtorrent session self.session = session + # Set the alertmanager + self.alerts = alerts # Get the core config self.config = ConfigManager("core.conf") # Per torrent connection limit and upload slot limit @@ -78,7 +81,13 @@ class TorrentManager: self.on_set_max_connections_per_torrent) self.config.register_set_function("max_upload_slots_per_torrent", self.on_set_max_upload_slots_per_torrent) - + + # Register alert functions + self.alerts.register_handler("torrent_finished_alert", + self.on_alert_torrent_finished) + self.alerts.register_handler("torrent_paused_alert", + self.on_alert_torrent_paused) + def shutdown(self): log.debug("TorrentManager shutting down..") # Save state on shutdown @@ -96,7 +105,7 @@ class TorrentManager: """Returns a list of torrent_ids""" return self.torrents.keys() - def add(self, filename, filedump=None, compact=None): + def add(self, filename, filedump=None, compact=None, paused=False): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) @@ -148,7 +157,8 @@ class TorrentManager: lt.torrent_info(torrent_filedump), self.config["download_location"], resume_data=fastresume, - compact_mode=compact) + compact_mode=compact, + paused=paused) except RuntimeError: log.warning("Error adding torrent") @@ -278,7 +288,8 @@ class TorrentManager: # Try to add the torrents in the state to the session for torrent_state in state.torrents: - self.add(torrent_state.filename, compact=torrent_state.compact) + self.add(torrent_state.filename, compact=torrent_state.compact, + paused=torrent_state.paused) def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" @@ -338,4 +349,19 @@ class TorrentManager: self.max_uploads = value for key in self.torrents.keys(): self.torrents[key].handle.set_max_uploads(value) - + + ## Alert handlers ## + def on_alert_torrent_finished(self, alert): + log.debug("on_alert_torrent_finished") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + log.debug("%s is finished..", torrent_id) + # Write the fastresume file + self.write_fastresume(torrent_id) + + def on_alert_torrent_paused(self, alert): + log.debug("on_alert_torrent_paused") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + # Write the fastresume file + self.write_fastresume(torrent_id) From 4ad4ac058cddf8fa9ec0aa42ccb1a1e1a201fa4f Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 1 Oct 2007 22:32:33 +0000 Subject: [PATCH 0171/1009] remove lt example files --- libtorrent/bindings/python/Jamfile | 29 -- libtorrent/bindings/python/client.py | 355 -------------------- libtorrent/bindings/python/simple_client.py | 24 -- 3 files changed, 408 deletions(-) delete mode 100755 libtorrent/bindings/python/Jamfile delete mode 100755 libtorrent/bindings/python/client.py delete mode 100755 libtorrent/bindings/python/simple_client.py diff --git a/libtorrent/bindings/python/Jamfile b/libtorrent/bindings/python/Jamfile deleted file mode 100755 index afad3a1a7..000000000 --- a/libtorrent/bindings/python/Jamfile +++ /dev/null @@ -1,29 +0,0 @@ -import python ; - -use-project /torrent : ../.. ; - -python-extension libtorrent - : src/module.cpp - src/big_number.cpp - src/fingerprint.cpp - src/utility.cpp - src/session.cpp - src/entry.cpp - src/torrent_info.cpp - src/filesystem.cpp - src/torrent_handle.cpp - src/torrent_status.cpp - src/session_settings.cpp - src/version.cpp - src/alert.cpp - src/datetime.cpp - src/extensions.cpp - src/peer_plugin.cpp - src/docstrings.cpp - src/torrent.cpp - src/peer_info.cpp - /torrent//torrent - /boost/python//boost_python - : src - ; - diff --git a/libtorrent/bindings/python/client.py b/libtorrent/bindings/python/client.py deleted file mode 100755 index 1e04a9a08..000000000 --- a/libtorrent/bindings/python/client.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/python - -# Copyright Daniel Wallin 2006. Use, modification and distribution is -# subject to the Boost Software License, Version 1.0. (See accompanying -# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -import sys -import libtorrent as lt -import time -import os.path -import sys - -class WindowsConsole: - def __init__(self): - self.console = Console.getconsole() - - def clear(self): - self.console.page() - - def write(self, str): - self.console.write(str) - - def sleep_and_input(self, seconds): - time.sleep(seconds) - if msvcrt.kbhit(): - return msvcrt.getch() - return None - -class UnixConsole: - def __init__(self): - self.fd = sys.stdin - self.old = termios.tcgetattr(self.fd.fileno()) - new = termios.tcgetattr(self.fd.fileno()) - new[3] = new[3] & ~termios.ICANON - new[6][termios.VTIME] = 0 - new[6][termios.VMIN] = 1 - termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, new) - - sys.exitfunc = self._onexit - - def _onexit(self): - termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, self.old) - - def clear(self): - sys.stdout.write('\033[2J\033[0;0H') - sys.stdout.flush() - - def write(self, str): - sys.stdout.write(str) - sys.stdout.flush() - - def sleep_and_input(self, seconds): - read,_,_ = select.select([self.fd.fileno()], [], [], seconds) - if len(read) > 0: - return self.fd.read(1) - return None - -if os.name == 'nt': - import Console - import msvcrt -else: - import termios - import select - -class PythonExtension(lt.torrent_plugin): - def __init__(self, alerts): - lt.torrent_plugin.__init__(self) - self.alerts = alerts - self.alerts.append('PythonExtension') - self.count = 0 - - def on_piece_pass(self, index): - self.alerts.append('got piece %d' % index) - - def on_piece_failed(self, index): - self.alerts.append('failed piece %d' % index) - - def tick(self): - self.count += 1 - if self.count >= 10: - self.count = 0 - self.alerts.append('PythonExtension tick') - -def write_line(console, line): - console.write(line) - -def add_suffix(val): - prefix = ['B', 'kB', 'MB', 'GB', 'TB'] - for i in range(len(prefix)): - if abs(val) < 1000: - if i == 0: - return '%5.3g%s' % (val, prefix[i]) - else: - return '%4.3g%s' % (val, prefix[i]) - val /= 1000 - - return '%6.3gPB' % val - -def progress_bar(progress, width): - progress_chars = int(progress * width + 0.5) - return progress_chars * '#' + (width - progress_chars) * '-' - -def print_peer_info(console, peers): - - out = ' down (total ) up (total ) q r flags block progress client\n' - - for p in peers: - - out += '%s/s ' % add_suffix(p.down_speed) - out += '(%s) ' % add_suffix(p.total_download) - out += '%s/s ' % add_suffix(p.up_speed) - out += '(%s) ' % add_suffix(p.total_upload) - out += '%2d ' % p.download_queue_length - out += '%2d ' % p.upload_queue_length - - if p.flags & lt.peer_info.interesting: out += 'I' - else: out += '.' - if p.flags & lt.peer_info.choked: out += 'C' - else: out += '.' - if p.flags & lt.peer_info.remote_interested: out += 'i' - else: out += '.' - if p.flags & lt.peer_info.remote_choked: out += 'c' - else: out += '.' - if p.flags & lt.peer_info.supports_extensions: out += 'e' - else: out += '.' - if p.flags & lt.peer_info.local_connection: out += 'l' - else: out += 'r' - out += ' ' - - if p.downloading_piece_index >= 0: - out += progress_bar(float(p.downloading_progress) / p.downloading_total, 15) - else: - out += progress_bar(0, 15) - out += ' ' - - if p.flags & lt.peer_info.handshake: - id = 'waiting for handshake' - elif p.flags & lt.peer_info.connecting: - id = 'connecting to peer' - elif p.flags & lt.peer_info.queued: - id = 'queued' - else: - id = p.client - - out += '%s\n' % id[:10] - - write_line(console, out) - - -def print_download_queue(console, download_queue): - - out = "" - - for e in download_queue: - out += '%4d: [' % e['piece_index']; - for b in e['blocks']: - s = b['state'] - if s == 3: - out += '#' - elif s == 2: - out += '=' - elif s == 1: - out += '-' - else: - out += ' ' - out += ']\n' - - write_line(console, out) - -def main(): - from optparse import OptionParser - - parser = OptionParser() - - parser.add_option('-p', '--port', - type='int', help='set listening port') - - parser.add_option('-r', '--ratio', - type='float', help='set the preferred upload/download ratio. 0 means infinite. Values smaller than 1 are clamped to 1') - - parser.add_option('-d', '--max-download-rate', - type='float', help='the maximum download rate given in kB/s. 0 means infinite.') - - parser.add_option('-u', '--max-upload-rate', - type='float', help='the maximum upload rate given in kB/s. 0 means infinite.') - - parser.add_option('-s', '--save-path', - type='string', help='the path where the downloaded file/folder should be placed.') - - parser.add_option('-a', '--allocation-mode', - type='string', help='sets mode used for allocating the downloaded files on disk. Possible options are [full | compact]') - - parser.set_defaults( - port=6881 - , ratio=0 - , max_download_rate=0 - , max_upload_rate=0 - , save_path='./' - , allocation_mode='compact' - ) - - (options, args) = parser.parse_args() - - if options.port < 0 or options.port > 65525: - options.port = 6881 - - options.max_upload_rate *= 1000 - options.max_download_rate *= 1000 - - if options.max_upload_rate <= 0: - options.max_upload_rate = -1 - if options.max_download_rate <= 0: - options.max_download_rate = -1 - - compact_allocation = options.allocation_mode == 'compact' - - 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_severity_level(lt.alert.severity_levels.info) -# ses.add_extension(lt.create_ut_pex_plugin); -# ses.add_extension(lt.create_metadata_plugin); - - handles = [] - alerts = [] - - # Extensions - # ses.add_extension(lambda x: PythonExtension(alerts)) - - for f in args: - e = lt.bdecode(open(f, 'rb').read()) - info = lt.torrent_info(e) - print 'Adding \'%s\'...' % info.name() - - try: - resume_data = lt.bdecode(open( - os.path.join(options.save_path, info.name() + '.fastresume'), 'rb').read()) - except: - resume_data = None - - h = ses.add_torrent(info, options.save_path, - resume_data=resume_data, compact_mode=compact_allocation) - - handles.append(h) - - h.set_max_connections(60) - h.set_max_uploads(-1) - h.set_ratio(options.ratio) - h.set_sequenced_download_threshold(15) - - if os.name == 'nt': - console = WindowsConsole() - else: - console = UnixConsole() - - alive = True - while alive: - console.clear() - - out = '' - - for h in handles: - if h.has_metadata(): - name = h.torrent_info().name()[:40] - else: - name = '-' - out += 'name: %-40s\n' % name - - s = h.status() - - if s.state != lt.torrent_status.seeding: - state_str = ['queued', 'checking', 'connecting', 'downloading metadata', \ - 'downloading', 'finished', 'seeding', 'allocating'] - out += state_str[s.state] + ' ' - - out += '%5.4f%% ' % (s.progress*100) - out += progress_bar(s.progress, 49) - out += '\n' - - out += 'total downloaded: %d Bytes\n' % s.total_done - out += 'peers: %d seeds: %d distributed copies: %d\n' % \ - (s.num_peers, s.num_seeds, s.distributed_copies) - out += '\n' - - out += 'download: %s/s (%s) ' \ - % (add_suffix(s.download_rate), add_suffix(s.total_download)) - out += 'upload: %s/s (%s) ' \ - % (add_suffix(s.upload_rate), add_suffix(s.total_upload)) - out += 'ratio: %s\n' % '0' - - if s.state != lt.torrent_status.seeding: - out += 'info-hash: %s\n' % h.info_hash() - out += 'next announce: %s\n' % s.next_announce - out += 'tracker: %s\n' % s.current_tracker - - write_line(console, out) - - print_peer_info(console, h.get_peer_info()) - print_download_queue(console, h.get_download_queue()) - - if True and s.state != lt.torrent_status.seeding: - out = '\n' - fp = h.file_progress() - ti = h.torrent_info() - for f,p in zip(ti.files(), fp): - out += progress_bar(p, 20) - out += ' ' + f.path + '\n' - write_line(console, out) - - write_line(console, 76 * '-' + '\n') - 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] - - for a in alerts: - if type(a) == str: - write_line(console, a + '\n') - else: - write_line(console, a.msg() + '\n') - - c = console.sleep_and_input(0.5) - - if not c: - continue - - if c == 'r': - for h in handles: h.force_reannounce() - elif c == 'q': - alive = False - elif c == 'p': - for h in handles: h.pause() - elif c == 'u': - for h in handles: h.resume() - - for h in handles: - if not h.is_valid() or not h.has_metadata(): - continue - h.pause() - data = lt.bencode(h.write_resume_data()) - open(os.path.join(options.save_path, h.torrent_info().name() + '.fastresume'), 'wb').write(data) - -main() - diff --git a/libtorrent/bindings/python/simple_client.py b/libtorrent/bindings/python/simple_client.py deleted file mode 100755 index b06aba6bc..000000000 --- a/libtorrent/bindings/python/simple_client.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/python - -import libtorrent as lt -import time - -ses = lt.session() -ses.listen_on(6881, 6891) - -e = lt.bdecode(open("test.torrent", 'rb').read()) -info = lt.torrent_info(e) - -h = ses.add_torrent(info, "./", compact_mode = True) - -while (not h.is_seed()): - s = h.status() - - state_str = ['queued', 'checking', 'connecting', 'downloading metadata', \ - 'downloading', 'finished', 'seeding', 'allocating'] - print '%.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % \ - (s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, \ - s.num_peers, state_str[s.state]) - - time.sleep(1) - From 935aa72584dbd0b01a3df953519c0a5cae57ab00 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 2 Oct 2007 01:22:30 +0000 Subject: [PATCH 0172/1009] Add tracker responses to TorrentDetails. --- TODO | 1 - deluge/core/torrent.py | 13 +- deluge/core/torrentmanager.py | 42 +++++- deluge/ui/gtkui/torrentdetails.py | 4 +- libtorrent/include/Makefile.am | 232 ------------------------------ 5 files changed, 51 insertions(+), 241 deletions(-) delete mode 100644 libtorrent/include/Makefile.am diff --git a/TODO b/TODO index 08d7e3bad..305832816 100644 --- a/TODO +++ b/TODO @@ -12,7 +12,6 @@ * Figure out easy way for user-made plugins to add i18n support. * Change the menubar.py gtkui component to menus.py and add support for plugins to add menuitems to the torrentmenu in an easy way. -* Add the tracker responses to the torrent details * Restart daemon function * Sync the details pane to current trunk * Automatically save torrent state every ~5 minutes, much like how diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index c95a27b5f..2c790b8b9 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -49,12 +49,12 @@ class Torrent: self.total_uploaded = 0 # Set the allocation mode self.compact = compact - # The reply from the tracker - self.tracker_reply = "" + # The tracker status + self.tracker_status = "" - def set_tracker_reply(self, reply): - """Sets the tracker reply message""" - self.tracker_reply = reply + def set_tracker_status(self, status): + """Sets the tracker status""" + self.tracker_status = status def get_state(self): """Returns the state of this torrent for saving to the session state""" @@ -140,7 +140,8 @@ class Torrent: "total_wanted": status.total_wanted, "eta": self.get_eta(), "ratio": self.get_ratio(), - "tracker": status.current_tracker + "tracker": status.current_tracker, + "tracker_status": self.tracker_status } # Create the desired status dictionary and return it diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index bc4ddd0a0..9fc66ea97 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -87,6 +87,13 @@ class TorrentManager: self.on_alert_torrent_finished) self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused) + self.alerts.register_handler("tracker_reply_alert", + self.on_alert_tracker_reply) + self.alerts.register_handler("tracker_announce_alert", + self.on_alert_tracker_announce) + self.alerts.register_handler("tracker_alert", self.on_alert_tracker) + self.alerts.register_handler("tracker_warning_alert", + self.on_alert_tracker_warning) def shutdown(self): log.debug("TorrentManager shutting down..") @@ -364,4 +371,37 @@ class TorrentManager: # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Write the fastresume file - self.write_fastresume(torrent_id) + self.write_fastresume(torrent_id) + + def on_alert_tracker_reply(self, alert): + log.debug("on_alert_tracker_reply") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + # Set the tracker status for the torrent + self.torrents[torrent_id].set_tracker_status("Announce OK") + + def on_alert_tracker_announce(self, alert): + log.debug("on_alert_tracker_announce") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + # Set the tracker status for the torrent + self.torrents[torrent_id].set_tracker_status("Announce Sent") + + def on_alert_tracker(self, alert): + log.debug("on_alert_tracker") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + tracker_status = "%s: %s (%s=%s, %s=%s)" % \ + ("Alert", str(alert.msg()), + "HTTP code", alert.status_code, + "times in a row", alert.times_in_row) + # Set the tracker status for the torrent + self.torrents[torrent_id].set_tracker_status(tracker_status) + + def on_alert_tracker_warning(self, alert): + log.debug("on_alert_tracker_warning") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + tracker_status = '%s: %s' % ("Warning", str(alert.msg())) + # Set the tracker status for the torrent + self.torrents[torrent_id].set_tracker_status(tracker_status) diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 7eaffde3d..507998830 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -92,7 +92,8 @@ class TorrentDetails: "total_done", "total_payload_download", "total_uploaded", "total_payload_upload", "download_payload_rate", "upload_payload_rate", "num_peers", "num_seeds", "total_peers", - "total_seeds", "eta", "ratio", "tracker", "next_announce"] + "total_seeds", "eta", "ratio", "tracker", "next_announce", + "tracker_status"] status = functions.get_torrent_status(self.core, selected, status_keys) @@ -129,6 +130,7 @@ class TorrentDetails: self.eta.set_text(deluge.common.ftime(status["eta"])) self.share_ratio.set_text("%.3f" % status["ratio"]) self.tracker.set_text(status["tracker"]) + self.tracker_status.set_text(status["tracker_status"]) self.next_announce.set_text( deluge.common.ftime(status["next_announce"])) diff --git a/libtorrent/include/Makefile.am b/libtorrent/include/Makefile.am deleted file mode 100644 index 010a2b181..000000000 --- a/libtorrent/include/Makefile.am +++ /dev/null @@ -1,232 +0,0 @@ -nobase_include_HEADERS = libtorrent/alert.hpp \ -libtorrent/alert_types.hpp \ -libtorrent/allocate_resources.hpp \ -libtorrent/bandwidth_manager.hpp \ -libtorrent/bencode.hpp \ -libtorrent/buffer.hpp \ -libtorrent/connection_queue.hpp \ -libtorrent/config.hpp \ -libtorrent/debug.hpp \ -libtorrent/disk_io_thread.hpp \ -libtorrent/entry.hpp \ -libtorrent/escape_string.hpp \ -libtorrent/extensions.hpp \ -libtorrent/file.hpp \ -libtorrent/file_pool.hpp \ -libtorrent/fingerprint.hpp \ -libtorrent/hasher.hpp \ -libtorrent/http_connection.hpp \ -libtorrent/http_stream.hpp \ -libtorrent/http_tracker_connection.hpp \ -libtorrent/identify_client.hpp \ -libtorrent/instantiate_connection.hpp \ -libtorrent/intrusive_ptr_base.hpp \ -libtorrent/invariant_check.hpp \ -libtorrent/io.hpp \ -libtorrent/ip_filter.hpp \ -libtorrent/lsd.hpp \ -libtorrent/peer.hpp \ -libtorrent/peer_connection.hpp \ -libtorrent/bt_peer_connection.hpp \ -libtorrent/web_peer_connection.hpp \ -libtorrent/pe_crypto.hpp \ -libtorrent/natpmp.hpp \ -libtorrent/pch.hpp \ -libtorrent/peer_id.hpp \ -libtorrent/peer_info.hpp \ -libtorrent/peer_request.hpp \ -libtorrent/piece_block_progress.hpp \ -libtorrent/piece_picker.hpp \ -libtorrent/policy.hpp \ -libtorrent/proxy_base.hpp \ -libtorrent/random_sample.hpp \ -libtorrent/resource_request.hpp \ -libtorrent/session.hpp \ -libtorrent/session_settings.hpp \ -libtorrent/session_status.hpp \ -libtorrent/size_type.hpp \ -libtorrent/socket.hpp \ -libtorrent/socket_type.hpp \ -libtorrent/socks4_stream.hpp \ -libtorrent/socks5_stream.hpp \ -libtorrent/stat.hpp \ -libtorrent/storage.hpp \ -libtorrent/time.hpp \ -libtorrent/torrent.hpp \ -libtorrent/torrent_handle.hpp \ -libtorrent/torrent_info.hpp \ -libtorrent/tracker_manager.hpp \ -libtorrent/udp_tracker_connection.hpp \ -libtorrent/utf8.hpp \ -libtorrent/xml_parse.hpp \ -libtorrent/variant_stream.hpp \ -libtorrent/version.hpp \ -libtorrent/time.hpp \ -libtorrent/aux_/allocate_resources_impl.hpp \ -libtorrent/aux_/session_impl.hpp \ -libtorrent/extensions/metadata_transfer.hpp \ -libtorrent/extensions/ut_pex.hpp \ -libtorrent/extensions/logger.hpp \ -\ -libtorrent/kademlia/closest_nodes.hpp \ -libtorrent/kademlia/dht_tracker.hpp \ -libtorrent/kademlia/find_data.hpp \ -libtorrent/kademlia/logging.hpp \ -libtorrent/kademlia/msg.hpp \ -libtorrent/kademlia/node.hpp \ -libtorrent/kademlia/node_entry.hpp \ -libtorrent/kademlia/node_id.hpp \ -libtorrent/kademlia/observer.hpp \ -libtorrent/kademlia/packet_iterator.hpp \ -libtorrent/kademlia/refresh.hpp \ -libtorrent/kademlia/routing_table.hpp \ -libtorrent/kademlia/rpc_manager.hpp \ -libtorrent/kademlia/traversal_algorithm.hpp \ -\ -libtorrent/asio.hpp \ -libtorrent/asio/basic_datagram_socket.hpp \ -libtorrent/asio/basic_deadline_timer.hpp \ -libtorrent/asio/basic_io_object.hpp \ -libtorrent/asio/basic_socket.hpp \ -libtorrent/asio/basic_socket_acceptor.hpp \ -libtorrent/asio/basic_socket_iostream.hpp \ -libtorrent/asio/basic_socket_streambuf.hpp \ -libtorrent/asio/basic_stream_socket.hpp \ -libtorrent/asio/basic_streambuf.hpp \ -libtorrent/asio/buffer.hpp \ -libtorrent/asio/buffered_read_stream.hpp \ -libtorrent/asio/buffered_read_stream_fwd.hpp \ -libtorrent/asio/buffered_stream.hpp \ -libtorrent/asio/buffered_stream_fwd.hpp \ -libtorrent/asio/buffered_write_stream.hpp \ -libtorrent/asio/buffered_write_stream_fwd.hpp \ -libtorrent/asio/completion_condition.hpp \ -libtorrent/asio/datagram_socket_service.hpp \ -libtorrent/asio/deadline_timer.hpp \ -libtorrent/asio/deadline_timer_service.hpp \ -libtorrent/asio/detail/bind_handler.hpp \ -libtorrent/asio/detail/buffer_resize_guard.hpp \ -libtorrent/asio/detail/buffered_stream_storage.hpp \ -libtorrent/asio/detail/call_stack.hpp \ -libtorrent/asio/detail/const_buffers_iterator.hpp \ -libtorrent/asio/detail/consuming_buffers.hpp \ -libtorrent/asio/detail/deadline_timer_service.hpp \ -libtorrent/asio/detail/epoll_reactor.hpp \ -libtorrent/asio/detail/epoll_reactor_fwd.hpp \ -libtorrent/asio/detail/event.hpp \ -libtorrent/asio/detail/fd_set_adapter.hpp \ -libtorrent/asio/detail/handler_alloc_helpers.hpp \ -libtorrent/asio/detail/handler_invoke_helpers.hpp \ -libtorrent/asio/detail/hash_map.hpp \ -libtorrent/asio/detail/io_control.hpp \ -libtorrent/asio/detail/kqueue_reactor.hpp \ -libtorrent/asio/detail/kqueue_reactor_fwd.hpp \ -libtorrent/asio/detail/local_free_on_block_exit.hpp \ -libtorrent/asio/detail/mutex.hpp \ -libtorrent/asio/detail/noncopyable.hpp \ -libtorrent/asio/detail/null_event.hpp \ -libtorrent/asio/detail/null_mutex.hpp \ -libtorrent/asio/detail/null_signal_blocker.hpp \ -libtorrent/asio/detail/null_thread.hpp \ -libtorrent/asio/detail/null_tss_ptr.hpp \ -libtorrent/asio/detail/old_win_sdk_compat.hpp \ -libtorrent/asio/detail/pipe_select_interrupter.hpp \ -libtorrent/asio/detail/pop_options.hpp \ -libtorrent/asio/detail/posix_event.hpp \ -libtorrent/asio/detail/posix_fd_set_adapter.hpp \ -libtorrent/asio/detail/posix_mutex.hpp \ -libtorrent/asio/detail/posix_signal_blocker.hpp \ -libtorrent/asio/detail/posix_thread.hpp \ -libtorrent/asio/detail/posix_tss_ptr.hpp \ -libtorrent/asio/detail/push_options.hpp \ -libtorrent/asio/detail/reactive_socket_service.hpp \ -libtorrent/asio/detail/reactor_op_queue.hpp \ -libtorrent/asio/detail/resolver_service.hpp \ -libtorrent/asio/detail/scoped_lock.hpp \ -libtorrent/asio/detail/select_interrupter.hpp \ -libtorrent/asio/detail/select_reactor.hpp \ -libtorrent/asio/detail/select_reactor_fwd.hpp \ -libtorrent/asio/detail/service_registry.hpp \ -libtorrent/asio/detail/service_registry_fwd.hpp \ -libtorrent/asio/detail/service_base.hpp \ -libtorrent/asio/detail/service_id.hpp \ -libtorrent/asio/detail/signal_blocker.hpp \ -libtorrent/asio/detail/signal_init.hpp \ -libtorrent/asio/detail/socket_holder.hpp \ -libtorrent/asio/detail/socket_ops.hpp \ -libtorrent/asio/detail/socket_option.hpp \ -libtorrent/asio/detail/socket_select_interrupter.hpp \ -libtorrent/asio/detail/socket_types.hpp \ -libtorrent/asio/detail/strand_service.hpp \ -libtorrent/asio/detail/task_io_service.hpp \ -libtorrent/asio/detail/task_io_service_fwd.hpp \ -libtorrent/asio/detail/thread.hpp \ -libtorrent/asio/detail/throw_error.hpp \ -libtorrent/asio/detail/timer_queue.hpp \ -libtorrent/asio/detail/timer_queue_base.hpp \ -libtorrent/asio/detail/tss_ptr.hpp \ -libtorrent/asio/detail/win_event.hpp \ -libtorrent/asio/detail/win_fd_set_adapter.hpp \ -libtorrent/asio/detail/win_iocp_io_service.hpp \ -libtorrent/asio/detail/win_iocp_io_service_fwd.hpp \ -libtorrent/asio/detail/win_iocp_operation.hpp \ -libtorrent/asio/detail/win_iocp_socket_service.hpp \ -libtorrent/asio/detail/win_mutex.hpp \ -libtorrent/asio/detail/win_signal_blocker.hpp \ -libtorrent/asio/detail/win_thread.hpp \ -libtorrent/asio/detail/win_tss_ptr.hpp \ -libtorrent/asio/detail/winsock_init.hpp \ -libtorrent/asio/detail/wrapped_handler.hpp \ -libtorrent/asio/error.hpp \ -libtorrent/asio/error_code.hpp \ -libtorrent/asio/handler_alloc_hook.hpp \ -libtorrent/asio/handler_invoke_hook.hpp \ -libtorrent/asio/impl/error_code.ipp \ -libtorrent/asio/impl/io_service.ipp \ -libtorrent/asio/impl/read.ipp \ -libtorrent/asio/impl/read_until.ipp \ -libtorrent/asio/impl/write.ipp \ -libtorrent/asio/io_service.hpp \ -libtorrent/asio/ip/address.hpp \ -libtorrent/asio/ip/address_v4.hpp \ -libtorrent/asio/ip/address_v6.hpp \ -libtorrent/asio/ip/basic_endpoint.hpp \ -libtorrent/asio/ip/basic_resolver.hpp \ -libtorrent/asio/ip/basic_resolver_entry.hpp \ -libtorrent/asio/ip/basic_resolver_iterator.hpp \ -libtorrent/asio/ip/basic_resolver_query.hpp \ -libtorrent/asio/ip/detail/socket_option.hpp \ -libtorrent/asio/ip/host_name.hpp \ -libtorrent/asio/ip/multicast.hpp \ -libtorrent/asio/ip/resolver_query_base.hpp \ -libtorrent/asio/ip/resolver_service.hpp \ -libtorrent/asio/ip/tcp.hpp \ -libtorrent/asio/ip/udp.hpp \ -libtorrent/asio/is_read_buffered.hpp \ -libtorrent/asio/is_write_buffered.hpp \ -libtorrent/asio/placeholders.hpp \ -libtorrent/asio/read.hpp \ -libtorrent/asio/read_until.hpp \ -libtorrent/asio/socket_acceptor_service.hpp \ -libtorrent/asio/socket_base.hpp \ -libtorrent/asio/ssl/basic_context.hpp \ -libtorrent/asio/ssl/context.hpp \ -libtorrent/asio/ssl/context_base.hpp \ -libtorrent/asio/ssl/context_service.hpp \ -libtorrent/asio/ssl/detail/openssl_context_service.hpp \ -libtorrent/asio/ssl/detail/openssl_init.hpp \ -libtorrent/asio/ssl/detail/openssl_operation.hpp \ -libtorrent/asio/ssl/detail/openssl_stream_service.hpp \ -libtorrent/asio/ssl/detail/openssl_types.hpp \ -libtorrent/asio/ssl/stream.hpp \ -libtorrent/asio/ssl/stream_base.hpp \ -libtorrent/asio/ssl/stream_service.hpp \ -libtorrent/asio/ssl.hpp \ -libtorrent/asio/strand.hpp \ -libtorrent/asio/stream_socket_service.hpp \ -libtorrent/asio/streambuf.hpp \ -libtorrent/asio/system_error.hpp \ -libtorrent/asio/thread.hpp \ -libtorrent/asio/time_traits.hpp \ -libtorrent/asio/write.hpp - From 78e797796a5a24fbc7f5693791210e8916d6be23 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 2 Oct 2007 02:29:12 +0000 Subject: [PATCH 0173/1009] translation for tracker alerts --- deluge/i18n/POTFILES.in | 1 + deluge/ui/gtkui/torrentdetails.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/deluge/i18n/POTFILES.in b/deluge/i18n/POTFILES.in index 68d3ea661..a44469d96 100644 --- a/deluge/i18n/POTFILES.in +++ b/deluge/i18n/POTFILES.in @@ -2,3 +2,4 @@ deluge/ui/gtkui/glade/main_window.glade deluge/ui/gtkui/glade/preferences_dialog.glade deluge/plugins/queue/queue/gtkui.py deluge/ui/gtkui/torrentview.py +deluge/ui/gtkui/torrentdetails.py diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 507998830..96f205806 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -130,7 +130,20 @@ class TorrentDetails: self.eta.set_text(deluge.common.ftime(status["eta"])) self.share_ratio.set_text("%.3f" % status["ratio"]) self.tracker.set_text(status["tracker"]) - self.tracker_status.set_text(status["tracker_status"]) + if status["tracker_status"] == "Announce OK": + tracker_msg = _("Announce OK") + elif status["tracker_status"] == "Announce Sent": + tracker_msg = _("Announce Sent") + elif 'Alert' in status["tracker_status"]: + tracker_msg = status["tracker_status"].replace("Alert", \ + _("Alert")) + tracker_msg = tracker_msg.replace("HTTP code", _("HTTP code")) + tracker_msg = tracker_msg.replace("times in a row", \ + _("times in a row")) + elif 'Warning' in status["tracker_status"]: + tracker_msg = status["tracker_status"].replace("Warning", \ + _("Warning")) + self.tracker_status.set_text(tracker_msg) self.next_announce.set_text( deluge.common.ftime(status["next_announce"])) From cc4fc60db0a5414ab1360214578a0e96d82d3076 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 2 Oct 2007 03:02:06 +0000 Subject: [PATCH 0174/1009] redo translation for tracker status --- deluge/core/core.py | 16 + deluge/core/torrentmanager.py | 12 +- deluge/i18n/POTFILES.in | 2 +- deluge/i18n/deluge.pot | 466 ++++++++++++++++++------------ deluge/ui/gtkui/torrentdetails.py | 15 +- 5 files changed, 307 insertions(+), 204 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 3734bad34..414fc2444 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -36,6 +36,9 @@ import dbus import dbus.service from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) +import gettext +import locale +import pkg_resources import gobject @@ -73,6 +76,19 @@ DEFAULT_PREFS = { class Core(dbus.service.Object): def __init__(self, path="/org/deluge_torrent/Core"): + # Initialize gettext + locale.setlocale(locale.LC_MESSAGES, '') + locale.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + locale.textdomain("deluge") + gettext.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + gettext.textdomain("deluge") + gettext.install("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) log.debug("Core init..") # Setup DBUS diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 9fc66ea97..5b3cfb99d 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -378,23 +378,23 @@ class TorrentManager: # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Set the tracker status for the torrent - self.torrents[torrent_id].set_tracker_status("Announce OK") + self.torrents[torrent_id].set_tracker_status(_("Announce OK")) def on_alert_tracker_announce(self, alert): log.debug("on_alert_tracker_announce") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Set the tracker status for the torrent - self.torrents[torrent_id].set_tracker_status("Announce Sent") + self.torrents[torrent_id].set_tracker_status(_("Announce Sent")) def on_alert_tracker(self, alert): log.debug("on_alert_tracker") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) tracker_status = "%s: %s (%s=%s, %s=%s)" % \ - ("Alert", str(alert.msg()), - "HTTP code", alert.status_code, - "times in a row", alert.times_in_row) + (_("Alert"), str(alert.msg()), + _("HTTP code"), alert.status_code, + _("times in a row"), alert.times_in_row) # Set the tracker status for the torrent self.torrents[torrent_id].set_tracker_status(tracker_status) @@ -402,6 +402,6 @@ class TorrentManager: log.debug("on_alert_tracker_warning") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) - tracker_status = '%s: %s' % ("Warning", str(alert.msg())) + tracker_status = '%s: %s' % (_("Warning"), str(alert.msg())) # Set the tracker status for the torrent self.torrents[torrent_id].set_tracker_status(tracker_status) diff --git a/deluge/i18n/POTFILES.in b/deluge/i18n/POTFILES.in index a44469d96..50e25aa8e 100644 --- a/deluge/i18n/POTFILES.in +++ b/deluge/i18n/POTFILES.in @@ -2,4 +2,4 @@ deluge/ui/gtkui/glade/main_window.glade deluge/ui/gtkui/glade/preferences_dialog.glade deluge/plugins/queue/queue/gtkui.py deluge/ui/gtkui/torrentview.py -deluge/ui/gtkui/torrentdetails.py +deluge/core/torrentmanager.py diff --git a/deluge/i18n/deluge.pot b/deluge/i18n/deluge.pot index 16b9fc2d0..710d02109 100644 --- a/deluge/i18n/deluge.pot +++ b/deluge/i18n/deluge.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-09-15 18:22-0700\n" +"POT-Creation-Date: 2007-10-01 22:01-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,157 +28,157 @@ msgstr "" msgid "Add _URL" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:49 +#: deluge/ui/gtkui/glade/main_window.glade:50 msgid "_Clear Completed" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:69 +#: deluge/ui/gtkui/glade/main_window.glade:70 msgid "Quit & Shutdown Daemon" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:101 +#: deluge/ui/gtkui/glade/main_window.glade:102 msgid "_Edit" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:122 +#: deluge/ui/gtkui/glade/main_window.glade:123 msgid "_Torrent" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:129 +#: deluge/ui/gtkui/glade/main_window.glade:130 msgid "_View" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:137 +#: deluge/ui/gtkui/glade/main_window.glade:138 msgid "_Toolbar" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:145 +#: deluge/ui/gtkui/glade/main_window.glade:146 msgid "_Details" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:153 -msgid "Columns" +#: deluge/ui/gtkui/glade/main_window.glade:154 +msgid "_Columns" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:164 +#: deluge/ui/gtkui/glade/main_window.glade:165 msgid "_Help" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:192 +#: deluge/ui/gtkui/glade/main_window.glade:194 msgid "Add torrent" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:193 +#: deluge/ui/gtkui/glade/main_window.glade:195 msgid "Add Torrent" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:205 +#: deluge/ui/gtkui/glade/main_window.glade:207 msgid "Remove the selected torrents" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:206 +#: deluge/ui/gtkui/glade/main_window.glade:208 msgid "Remove Torrent" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:218 +#: deluge/ui/gtkui/glade/main_window.glade:220 msgid "Remove the finished torrents" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:219 +#: deluge/ui/gtkui/glade/main_window.glade:221 msgid "Clear Finished" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:240 +#: deluge/ui/gtkui/glade/main_window.glade:242 msgid "Pause the selected torrents" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:241 +#: deluge/ui/gtkui/glade/main_window.glade:243 msgid "Pause" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:254 +#: deluge/ui/gtkui/glade/main_window.glade:256 msgid "Resume the selected torrents" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:255 +#: deluge/ui/gtkui/glade/main_window.glade:257 msgid "Resume" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:275 -#: deluge/ui/gtkui/glade/main_window.glade:276 +#: deluge/ui/gtkui/glade/main_window.glade:277 +#: deluge/ui/gtkui/glade/main_window.glade:278 msgid "Preferences" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:420 -msgid "Name:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:439 -msgid "Next Announce:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:465 -msgid "Tracker Status:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:493 -msgid "Tracker:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:516 -msgid "Total Size:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:576 -msgid "# of files:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:595 -msgid "Torrent Info" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:666 -msgid "Availability:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:702 -msgid "Pieces:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:719 -msgid "ETA:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:740 -msgid "Peers:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:761 -#: deluge/ui/gtkui/glade/main_window.glade:782 -msgid "Speed:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:800 -msgid "Share Ratio:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:818 -msgid "Seeders:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:836 -msgid "Uploaded:" -msgstr "" - -#: deluge/ui/gtkui/glade/main_window.glade:854 +#: deluge/ui/gtkui/glade/main_window.glade:482 msgid "Downloaded:" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:966 +#: deluge/ui/gtkui/glade/main_window.glade:496 +msgid "Uploaded:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:514 +msgid "Seeders:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:532 +msgid "Share Ratio:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:551 +#: deluge/ui/gtkui/glade/main_window.glade:570 +msgid "Speed:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:591 +msgid "Peers:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:612 +msgid "ETA:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:629 +msgid "Pieces:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:667 +msgid "Availability:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:708 msgid "Statistics" msgstr "" -#: deluge/ui/gtkui/glade/main_window.glade:991 +#: deluge/ui/gtkui/glade/main_window.glade:750 +msgid "# of files:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:810 +msgid "Total Size:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:836 +msgid "Tracker:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:864 +msgid "Tracker Status:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:887 +msgid "Next Announce:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:909 +msgid "Name:" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:967 +msgid "Torrent Info" +msgstr "" + +#: deluge/ui/gtkui/glade/main_window.glade:993 msgid "Details" msgstr "" @@ -259,188 +259,196 @@ msgstr "" msgid "Test Active Port" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:469 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:470 msgid "Deluge will automatically choose a different port to use every time." msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:470 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:471 msgid "Use Random Ports" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:488 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:489 msgid "Active Port:" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:500 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:501 msgid "0000" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:530 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:531 msgid "Ports" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:562 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:563 msgid "Distributed hash table may improve the amount of active connections." msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:563 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:564 msgid "Enable Mainline DHT" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:576 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:577 msgid "DHT" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:609 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:610 msgid "Universal Plug and Play" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:610 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:611 msgid "UPnP" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:625 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:626 msgid "NAT Port Mapping Protocol" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:626 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:627 msgid "NAT-PMP" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:642 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:643 msgid "µTorrent Peer-Exchange" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:643 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:644 msgid "µTorrent-PeX" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:662 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:663 msgid "Network Extras" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:698 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:699 msgid "Inbound:" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:707 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:731 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:708 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:732 msgid "" -"Disabled\n" +"Forced\n" "Enabled\n" -"Forced" +"Disabled" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:721 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:722 msgid "Outbound:" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:750 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:751 msgid "Level:" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:759 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:760 msgid "" "Handshake\n" -"Either\n" -"Full Stream" +"Full Stream\n" +"Either" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:778 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:779 msgid "Prefer to encrypt the entire stream" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:795 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:796 msgid "Encryption" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:849 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:850 msgid "Bandwidth" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:887 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:911 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:977 -msgid "The maximum upload speed for all torrents. Set -1 for unlimited." -msgstr "" - #: deluge/ui/gtkui/glade/preferences_dialog.glade:889 -msgid "Maximum Upload Speed (KiB/s):" -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:900 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:925 -msgid "The maximum number of connections allowed. Set -1 for unlimited." -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:902 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1071 -msgid "Maximum Connections:" -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:913 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1058 -msgid "Maximum Upload Slots:" -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:943 -#: deluge/ui/gtkui/glade/preferences_dialog.glade:958 -msgid "The maximum download speed for all torrents. Set -1 for unlimited." -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:945 -msgid "Maximum Download Speed (KiB/s):" -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:996 msgid "The maximum upload slots for all torrents. Set -1 for unlimited." msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1018 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:908 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:978 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1002 +msgid "The maximum upload speed for all torrents. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:928 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:946 +msgid "The maximum download speed for all torrents. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:948 +msgid "Maximum Download Speed (KiB/s):" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:960 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:991 +msgid "The maximum number of connections allowed. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:980 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1105 +msgid "Maximum Upload Slots:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:993 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1094 +msgid "Maximum Connections:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1004 +msgid "Maximum Upload Speed (KiB/s):" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1019 msgid "Global Bandwidth Usage" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1081 -msgid "The maximum number of connections per torrent. Set -1 for unlimited." -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1097 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1058 msgid "The maximum upload slots per torrent. Set -1 for unlimited." msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1120 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1077 +msgid "The maximum number of connections per torrent. Set -1 for unlimited." +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1121 msgid "Per Torrent Bandwidth Usage" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1174 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1175 msgid "Other" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1208 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1209 msgid "Enable system tray icon" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1223 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1224 msgid "Minimize to tray on close" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1242 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1243 msgid "Start in tray" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1263 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1264 msgid "Password protect system tray" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1288 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1291 msgid "Password:" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1322 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1325 msgid "System Tray" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1372 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1363 +msgid "Open folder with:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1378 +msgid "Custom:" +msgstr "" + +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1395 msgid "" "Auto-detect (xdg-open)\n" "Konqueror\n" @@ -448,59 +456,51 @@ msgid "" "Thunar" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1396 -msgid "Custom:" -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1413 -msgid "Open folder with:" -msgstr "" - -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1430 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1437 msgid "Desktop File Manager" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1465 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1477 msgid "" "Deluge will check our servers and will tell you if a newer version has been " "released" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1466 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1478 msgid "Be alerted about new releases" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1483 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1497 msgid "Updates" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1519 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1533 msgid "" "Help us improve Deluge by sending us your Python version, PyGTK version, OS " "and processor types. Absolutely no other information is sent." msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1538 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1552 msgid "Yes, please send anonymous statistics" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1557 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1571 msgid "System Information" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1611 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1625 msgid "Plugins" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1703 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1717 msgid "gtk-cancel" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1715 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1729 msgid "gtk-apply" msgstr "" -#: deluge/ui/gtkui/glade/preferences_dialog.glade:1730 +#: deluge/ui/gtkui/glade/preferences_dialog.glade:1744 msgid "gtk-ok" msgstr "" @@ -519,3 +519,103 @@ msgstr "" #: deluge/plugins/queue/queue/gtkui.py:106 msgid "Queue selected torrents down" msgstr "" + +#: deluge/ui/gtkui/torrentview.py:75 +msgid "Queued" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:76 +msgid "Checking" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:77 +msgid "Connecting" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:78 +msgid "Downloading Metadata" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:79 +msgid "Downloading" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:80 +msgid "Finished" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:81 +msgid "Seeding" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:82 +msgid "Allocating" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:83 +msgid "Paused" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:115 +msgid "Name" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:117 +msgid "Size" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:121 +msgid "Progress" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:125 +msgid "Seeders" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:130 +msgid "Peers" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:135 +msgid "Down Speed" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:139 +msgid "Up Speed" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:143 +msgid "ETA" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:147 +msgid "Ratio" +msgstr "" + +#: deluge/ui/gtkui/torrentview.py:151 +msgid "Avail" +msgstr "" + +#: deluge/core/torrentmanager.py:381 +msgid "Announce OK" +msgstr "" + +#: deluge/core/torrentmanager.py:388 +msgid "Announce Sent" +msgstr "" + +#: deluge/core/torrentmanager.py:395 +msgid "Alert" +msgstr "" + +#: deluge/core/torrentmanager.py:396 +msgid "HTTP code" +msgstr "" + +#: deluge/core/torrentmanager.py:397 +msgid "times in a row" +msgstr "" + +#: deluge/core/torrentmanager.py:405 +msgid "Warning" +msgstr "" diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 96f205806..507998830 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -130,20 +130,7 @@ class TorrentDetails: self.eta.set_text(deluge.common.ftime(status["eta"])) self.share_ratio.set_text("%.3f" % status["ratio"]) self.tracker.set_text(status["tracker"]) - if status["tracker_status"] == "Announce OK": - tracker_msg = _("Announce OK") - elif status["tracker_status"] == "Announce Sent": - tracker_msg = _("Announce Sent") - elif 'Alert' in status["tracker_status"]: - tracker_msg = status["tracker_status"].replace("Alert", \ - _("Alert")) - tracker_msg = tracker_msg.replace("HTTP code", _("HTTP code")) - tracker_msg = tracker_msg.replace("times in a row", \ - _("times in a row")) - elif 'Warning' in status["tracker_status"]: - tracker_msg = status["tracker_status"].replace("Warning", \ - _("Warning")) - self.tracker_status.set_text(tracker_msg) + self.tracker_status.set_text(status["tracker_status"]) self.next_announce.set_text( deluge.common.ftime(status["next_announce"])) From a17328fff77c3b4b2ce3d7fe11933cbf57dfa335 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 2 Oct 2007 03:06:40 +0000 Subject: [PATCH 0175/1009] Fix exception when removing torrents. --- deluge/core/torrentmanager.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 5b3cfb99d..5a50fb3e1 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -173,6 +173,11 @@ class TorrentManager: # The torrent was not added to the session return None + # Create a Torrent object + torrent = Torrent(filename, handle, compact) + # Add the torrent object to the dictionary + self.torrents[torrent.torrent_id] = torrent + # Set per-torrent limits handle.set_max_connections(self.max_connections) handle.set_max_uploads(self.max_uploads) @@ -199,10 +204,7 @@ class TorrentManager: log.warning("Unable to save torrent file: %s", filename) log.debug("Torrent %s added.", handle.info_hash()) - # Create a Torrent object - torrent = Torrent(filename, handle, compact) - # Add the torrent object to the dictionary - self.torrents[torrent.torrent_id] = torrent + # Save the session state self.save_state() return torrent.torrent_id @@ -378,15 +380,21 @@ class TorrentManager: # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Set the tracker status for the torrent - self.torrents[torrent_id].set_tracker_status(_("Announce OK")) + try: + self.torrents[torrent_id].set_tracker_status(_("Announce OK")) + except KeyError: + log.debug("torrent_id doesn't exist.") def on_alert_tracker_announce(self, alert): log.debug("on_alert_tracker_announce") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Set the tracker status for the torrent - self.torrents[torrent_id].set_tracker_status(_("Announce Sent")) - + try: + self.torrents[torrent_id].set_tracker_status(_("Announce Sent")) + except KeyError: + log.debug("torrent_id doesn't exist.") + def on_alert_tracker(self, alert): log.debug("on_alert_tracker") # Get the torrent_id @@ -396,7 +404,10 @@ class TorrentManager: _("HTTP code"), alert.status_code, _("times in a row"), alert.times_in_row) # Set the tracker status for the torrent - self.torrents[torrent_id].set_tracker_status(tracker_status) + try: + self.torrents[torrent_id].set_tracker_status(tracker_status) + except KeyError: + log.debug("torrent_id doesn't exist.") def on_alert_tracker_warning(self, alert): log.debug("on_alert_tracker_warning") @@ -404,4 +415,7 @@ class TorrentManager: torrent_id = str(alert.handle.info_hash()) tracker_status = '%s: %s' % (_("Warning"), str(alert.msg())) # Set the tracker status for the torrent - self.torrents[torrent_id].set_tracker_status(tracker_status) + try: + self.torrents[torrent_id].set_tracker_status(tracker_status) + except KeyError: + log.debug("torrent_id doesn't exist.") From 170c3810b3464671a5f72b48679eb358f9d13c03 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 3 Oct 2007 02:57:05 +0000 Subject: [PATCH 0176/1009] Save torrent state every 5 minutes. --- TODO | 2 -- deluge/core/torrentmanager.py | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 305832816..f649136f3 100644 --- a/TODO +++ b/TODO @@ -14,6 +14,4 @@ to add menuitems to the torrentmenu in an easy way. * Restart daemon function * Sync the details pane to current trunk -* Automatically save torrent state every ~5 minutes, much like how - configmanager does it. * Docstrings! diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 5a50fb3e1..04dbbdd17 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -37,6 +37,8 @@ import pickle import os.path import os +import gobject + import deluge.libtorrent as lt import deluge.common @@ -75,6 +77,9 @@ class TorrentManager: self.torrents = {} # Try to load the state from file self.load_state() + + # Save the state every 5 minutes + self.save_state_timer = gobject.timeout_add(300000, self.save_state) # Register set functions self.config.register_set_function("max_connections_per_torrent", @@ -317,6 +322,9 @@ class TorrentManager: state_file.close() except IOError: log.warning("Unable to save state file.") + + # We return True so that the timer thread will continue + return True def delete_fastresume(self, torrent_id): """Deletes the .fastresume file""" From 2eafe71a2cc9f6199e74ff72483c4be1cba7eaa0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 3 Oct 2007 03:04:22 +0000 Subject: [PATCH 0177/1009] Implement close to tray. --- TODO | 1 - deluge/ui/gtkui/mainwindow.py | 21 +++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/TODO b/TODO index f649136f3..e0f3fb893 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ * Have the ui better handle not being able to connect to the daemon. -* Tray locking and other tray options (close to tray, start in tray..) * Add state saving to listview.. this includes saving column size and position. * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index ec7c7118b..46c40420a 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -68,9 +68,10 @@ class MainWindow: self.is_minimized = False # Connect events - self.window.connect("window-state-event", self.window_state_event) - self.window.connect("configure-event", self.window_configure_event) - self.vpaned.connect("notify::position", self.vpaned_position_event) + self.window.connect("window-state-event", self.on_window_state_event) + self.window.connect("configure-event", self.on_window_configure_event) + self.window.connect("delete-event", self.on_window_delete_event) + self.vpaned.connect("notify::position", self.on_vpaned_position_event) # Initialize various components of the gtkui self.menubar = MenuBar(self) @@ -141,14 +142,14 @@ class MainWindow: self.vpaned.set_position( self.config["window_height"] - self.config["window_pane_position"]) - def window_configure_event(self, widget, event): + def on_window_configure_event(self, widget, event): if self.config["window_maximized"] == False: self.config.set("window_x_pos", self.window.get_position()[0]) self.config.set("window_y_pos", self.window.get_position()[1]) self.config.set("window_width", event.width) self.config.set("window_height", event.height) - def window_state_event(self, widget, event): + def on_window_state_event(self, widget, event): if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED: if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED: self.config.set("window_maximized", True) @@ -165,7 +166,15 @@ class MainWindow: self.update() return False - def vpaned_position_event(self, obj, param): + def on_window_delete_event(self, widget, event): + if self.config["close_to_tray"] and self.config["enable_system_tray"]: + self.hide() + else: + self.quit() + + return True + + def on_vpaned_position_event(self, obj, param): self.config.set("window_pane_position", self.config["window_height"] - self.vpaned.get_position()) From 27360c6cf8bb7696179d46769f33b06245cc0a17 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Wed, 3 Oct 2007 05:51:41 +0000 Subject: [PATCH 0178/1009] show 8 '*' for password --- .../ui/gtkui/glade/preferences_dialog.glade | 225 +++++++++--------- deluge/ui/gtkui/preferences.py | 12 +- 2 files changed, 117 insertions(+), 120 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 7592595c2..8563d26e6 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -883,40 +883,71 @@ Either 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -941,74 +972,43 @@ Either - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1052,24 +1052,29 @@ Either 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1087,24 +1092,19 @@ Either - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1302,6 +1302,7 @@ Either GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 16 + ******** False @@ -1356,33 +1357,15 @@ Either 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1412,20 +1395,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 7e071fc1b..23574360c 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -162,8 +162,6 @@ class Preferences: self.gtkui_config["start_in_tray"]) self.glade.get_widget("chk_lock_tray").set_active( self.gtkui_config["lock_tray"]) - self.glade.get_widget("txt_tray_password").set_text( - self.gtkui_config["tray_password"]) self.glade.get_widget("combo_file_manager").set_active( self.gtkui_config["stock_file_manager"]) self.glade.get_widget("txt_open_folder_location").set_text( @@ -251,12 +249,10 @@ class Preferences: self.glade.get_widget("chk_start_in_tray").get_active() new_gtkui_config["lock_tray"] = \ self.glade.get_widget("chk_lock_tray").get_active() - if len(self.glade.get_widget("txt_tray_password").get_text()) == 40: - password = self.glade.get_widget("txt_tray_password").get_text() - else: - password = sha.new(self.glade.get_widget("txt_tray_password").\ - get_text()).hexdigest() - new_gtkui_config["tray_password"] = password + passhex = sha.new(self.glade.get_widget("txt_tray_password").get_text())\ + .hexdigest() + if passhex != "c07eb5a8c0dc7bb81c217b67f11c3b7a5e95ffd7": + new_gtkui_config["tray_password"] = passhex new_gtkui_config["stock_file_manager"] = \ self.glade.get_widget("combo_file_manager").get_active() new_gtkui_config["open_folder_location"] = \ From 1a6afef6b8f98285a8e575afb0b13d256fcb2705 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Wed, 3 Oct 2007 22:29:42 +0000 Subject: [PATCH 0179/1009] lt sync 1638 --- .../include/libtorrent/aux_/session_impl.hpp | 50 +++- .../include/libtorrent/broadcast_socket.hpp | 2 +- .../include/libtorrent/bt_peer_connection.hpp | 13 +- .../include/libtorrent/chained_buffer.hpp | 192 +++++++++++++ libtorrent/include/libtorrent/debug.hpp | 15 +- .../include/libtorrent/disk_io_thread.hpp | 9 + libtorrent/include/libtorrent/enum_net.hpp | 1 + .../libtorrent/http_tracker_connection.hpp | 22 +- .../include/libtorrent/intrusive_ptr_base.hpp | 6 + libtorrent/include/libtorrent/lsd.hpp | 3 +- libtorrent/include/libtorrent/natpmp.hpp | 3 +- .../include/libtorrent/peer_connection.hpp | 54 ++-- libtorrent/include/libtorrent/policy.hpp | 9 +- libtorrent/include/libtorrent/storage.hpp | 3 + libtorrent/include/libtorrent/time.hpp | 3 + libtorrent/include/libtorrent/torrent.hpp | 1 + libtorrent/include/libtorrent/upnp.hpp | 3 +- .../libtorrent/web_peer_connection.hpp | 2 +- libtorrent/src/broadcast_socket.cpp | 41 +-- libtorrent/src/bt_peer_connection.cpp | 191 ++++++------- libtorrent/src/disk_io_thread.cpp | 39 +-- libtorrent/src/enum_net.cpp | 77 ++++++ libtorrent/src/http_connection.cpp | 4 +- libtorrent/src/http_tracker_connection.cpp | 6 +- libtorrent/src/kademlia/rpc_manager.cpp | 2 +- libtorrent/src/lsd.cpp | 83 +++--- libtorrent/src/natpmp.cpp | 29 +- libtorrent/src/peer_connection.cpp | 136 +++++---- libtorrent/src/policy.cpp | 257 ++++++++---------- libtorrent/src/session.cpp | 8 + libtorrent/src/session_impl.cpp | 105 +++++-- libtorrent/src/storage.cpp | 7 +- libtorrent/src/torrent.cpp | 36 ++- libtorrent/src/torrent_handle.cpp | 18 +- libtorrent/src/upnp.cpp | 24 +- libtorrent/src/web_peer_connection.cpp | 10 +- 36 files changed, 943 insertions(+), 521 deletions(-) create mode 100644 libtorrent/include/libtorrent/chained_buffer.hpp diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index afb358afe..6b76b43f9 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -115,6 +115,7 @@ namespace libtorrent std::vector unfinished_pieces; std::vector block_info; std::vector peers; + std::vector banned_peers; entry resume_data; // this is true if this torrent is being processed (checked) @@ -168,6 +169,10 @@ namespace libtorrent // thread started to run the main downloader loop struct session_impl: boost::noncopyable { + + // the size of each allocation that is chained in the send buffer + enum { send_buffer_size = 200 }; + #ifndef NDEBUG friend class ::libtorrent::peer_connection; #endif @@ -329,6 +334,24 @@ namespace libtorrent { return m_dht_proxy; } #endif +#ifdef TORRENT_STATS + void log_buffer_usage() + { + int send_buffer_capacity = 0; + int used_send_buffer = 0; + for (connection_map::const_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + send_buffer_capacity += i->second->send_buffer_capacity(); + used_send_buffer += i->second->send_buffer_size(); + } + assert(send_buffer_capacity >= used_send_buffer); + m_buffer_usage_logger << log_time() << " send_buffer_size: " << send_buffer_capacity << std::endl; + m_buffer_usage_logger << log_time() << " used_send_buffer: " << used_send_buffer << std::endl; + m_buffer_usage_logger << log_time() << " send_buffer_utilization: " + << (used_send_buffer * 100.f / send_buffer_capacity) << std::endl; + } +#endif void start_lsd(); void start_natpmp(); void start_upnp(); @@ -339,11 +362,25 @@ namespace libtorrent // handles delayed alerts alert_manager m_alerts; + + std::pair allocate_buffer(int size); + void free_buffer(char* buf, int size); + void free_disk_buffer(char* buf); // private: void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); + // handles disk io requests asynchronously + // peers have pointers into the disk buffer + // pool, and must be destructed before this + // object. + disk_io_thread m_disk_thread; + + // this pool is used to allocate and recycle send + // buffers from. + boost::pool<> m_send_buffers; + // this is where all active sockets are stored. // the selector can sleep while there's no activity on // them @@ -358,9 +395,6 @@ namespace libtorrent // when they are destructed. file_pool m_files; - // handles disk io requests asynchronously - disk_io_thread m_disk_thread; - // this is a list of half-open tcp connections // (only outgoing connections) // this has to be one of the last @@ -507,9 +541,9 @@ namespace libtorrent pe_settings m_pe_settings; #endif - boost::shared_ptr m_natpmp; - boost::shared_ptr m_upnp; - boost::shared_ptr m_lsd; + boost::intrusive_ptr m_natpmp; + boost::intrusive_ptr m_upnp; + boost::intrusive_ptr m_lsd; // the timer used to fire the second_tick deadline_timer m_timer; @@ -526,6 +560,10 @@ namespace libtorrent // logger used to write bandwidth usage statistics std::ofstream m_stats_logger; int m_second_counter; + // used to log send buffer usage statistics + std::ofstream m_buffer_usage_logger; + // the number of send buffers that are allocated + int m_buffer_allocations; #endif #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) boost::shared_ptr create_log(std::string const& name diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index 485d1bd1f..df656f4ea 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -46,7 +46,7 @@ namespace libtorrent bool is_multicast(address const& addr); bool is_any(address const& addr); - address_v4 guess_local_address(asio::io_service&); + address guess_local_address(asio::io_service&); typedef boost::function receive_handler_t; diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index 0fcba89a8..53e9667fc 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -208,7 +208,7 @@ namespace libtorrent void write_cancel(peer_request const& r); void write_bitfield(std::vector const& bitfield); void write_have(int index); - void write_piece(peer_request const& r, char const* buffer); + void write_piece(peer_request const& r, char* buffer); void write_handshake(); #ifndef TORRENT_DISABLE_EXTENSIONS void write_extensions(); @@ -270,8 +270,17 @@ namespace libtorrent // these functions encrypt the send buffer if m_rc4_encrypted // is true, otherwise it passes the call to the // peer_connection functions of the same names - void send_buffer(char* begin, char* end); + void send_buffer(char* buf, int size); buffer::interval allocate_send_buffer(int size); + template + void append_send_buffer(char* buffer, int size, Destructor const& destructor) + { +#ifndef TORRENT_DISABLE_ENCRYPTION + if (m_rc4_encrypted) + m_RC4_handler->encrypt(buffer, size); +#endif + peer_connection::append_send_buffer(buffer, size, destructor); + } void setup_send(); // Returns offset at which bytestream (src, src + src_size) diff --git a/libtorrent/include/libtorrent/chained_buffer.hpp b/libtorrent/include/libtorrent/chained_buffer.hpp new file mode 100644 index 000000000..ba5e1ab2f --- /dev/null +++ b/libtorrent/include/libtorrent/chained_buffer.hpp @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CHAINED_BUFFER_HPP_INCLUDED +#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace libtorrent +{ + struct chained_buffer + { + chained_buffer(): m_bytes(0), m_capacity(0) {} + + struct buffer_t + { + boost::function free; // destructs the buffer + char* buf; // the first byte of the buffer + int size; // the total size of the buffer + + char* start; // the first byte to send/receive in the buffer + int used_size; // this is the number of bytes to send/receive + }; + + bool empty() const { return m_bytes == 0; } + int size() const { return m_bytes; } + int capacity() const { return m_capacity; } + + void pop_front(int bytes_to_pop) + { + assert(bytes_to_pop <= m_bytes); + while (bytes_to_pop > 0 && !m_vec.empty()) + { + buffer_t& b = m_vec.front(); + if (b.used_size > bytes_to_pop) + { + b.start += bytes_to_pop; + b.used_size -= bytes_to_pop; + m_bytes -= bytes_to_pop; + assert(m_bytes <= m_capacity); + assert(m_bytes >= 0); + assert(m_capacity >= 0); + break; + } + + b.free(b.buf); + m_bytes -= b.used_size; + m_capacity -= b.size; + bytes_to_pop -= b.used_size; + assert(m_bytes >= 0); + assert(m_capacity >= 0); + assert(m_bytes <= m_capacity); + m_vec.pop_front(); + } + } + + template + void append_buffer(char* buffer, int size, int used_size, D const& destructor) + { + assert(size >= used_size); + buffer_t b; + b.buf = buffer; + b.size = size; + b.start = buffer; + b.used_size = used_size; + b.free = destructor; + m_vec.push_back(b); + + m_bytes += used_size; + m_capacity += size; + assert(m_bytes <= m_capacity); + } + + // returns the number of bytes available at the + // end of the last chained buffer. + int space_in_last_buffer() + { + if (m_vec.empty()) return 0; + buffer_t& b = m_vec.back(); + return b.size - b.used_size - (b.start - b.buf); + } + + // tries to copy the given buffer to the end of the + // last chained buffer. If there's not enough room + // it returns false + bool append(char const* buf, int size) + { + char* insert = allocate_appendix(size); + if (insert == 0) return false; + std::memcpy(insert, buf, size); + return true; + } + + // tries to allocate memory from the end + // of the last buffer. If there isn't + // enough room, returns 0 + char* allocate_appendix(int size) + { + if (m_vec.empty()) return 0; + buffer_t& b = m_vec.back(); + char* insert = b.start + b.used_size; + if (insert + size > b.buf + b.size) return 0; + b.used_size += size; + m_bytes += size; + assert(m_bytes <= m_capacity); + return insert; + } + + std::list const& build_iovec(int to_send) + { + m_tmp_vec.clear(); + + for (std::list::iterator i = m_vec.begin() + , end(m_vec.end()); to_send > 0 && i != end; ++i) + { + if (i->used_size > to_send) + { + assert(to_send > 0); + m_tmp_vec.push_back(asio::const_buffer(i->start, to_send)); + break; + } + assert(i->used_size > 0); + m_tmp_vec.push_back(asio::const_buffer(i->start, i->used_size)); + to_send -= i->used_size; + } + return m_tmp_vec; + } + + ~chained_buffer() + { + for (std::list::iterator i = m_vec.begin() + , end(m_vec.end()); i != end; ++i) + { + i->free(i->buf); + } + } + + private: + + // this is the list of all the buffers we want to + // send + std::list m_vec; + + // this is the number of bytes in the send buf. + // this will always be equal to the sum of the + // size of all buffers in vec + int m_bytes; + + // the total size of all buffers in the chain + // including unused space + int m_capacity; + + // this is the vector of buffers used when + // invoking the async write call + std::list m_tmp_vec; + }; +} + +#endif + diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp index 1bb645a8e..0d3158417 100755 --- a/libtorrent/include/libtorrent/debug.hpp +++ b/libtorrent/include/libtorrent/debug.hpp @@ -60,10 +60,17 @@ namespace libtorrent { logger(fs::path const& filename, int instance, bool append = true) { - fs::path dir(fs::complete("libtorrent_logs" + boost::lexical_cast(instance))); - if (!fs::exists(dir)) fs::create_directories(dir); - m_file.open((dir / filename).string().c_str(), std::ios_base::out | (append ? std::ios_base::app : std::ios_base::out)); - *this << "\n\n\n*** starting log ***\n"; + try + { + fs::path dir(fs::complete("libtorrent_logs" + boost::lexical_cast(instance))); + if (!fs::exists(dir)) fs::create_directories(dir); + m_file.open((dir / filename).string().c_str(), std::ios_base::out | (append ? std::ios_base::app : std::ios_base::out)); + *this << "\n\n\n*** starting log ***\n"; + } + catch (std::exception& e) + { + std::cerr << "failed to create log '" << filename.string() << "': " << e.what() << std::endl; + } } template diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index 61ca9bc53..b893aaf60 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -94,6 +94,11 @@ namespace libtorrent disk_io_thread(int block_size = 16 * 1024); ~disk_io_thread(); +#ifdef TORRENT_STATS + int disk_allocations() const + { return m_allocations; } +#endif + // aborts read operations void stop(boost::intrusive_ptr s); void add_job(disk_io_job const& j @@ -110,6 +115,7 @@ namespace libtorrent void operator()(); char* allocate_buffer(); + void free_buffer(char* buf); private: @@ -129,6 +135,9 @@ namespace libtorrent #ifdef TORRENT_DISK_STATS std::ofstream m_log; #endif +#ifdef TORRENT_STATS + int m_allocations; +#endif // thread for performing blocking disk io operations boost::thread m_disk_io_thread; diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp index 0da76ff36..a794c705c 100644 --- a/libtorrent/include/libtorrent/enum_net.hpp +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { std::vector
enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); + address router_for_interface(address const interface, asio::error_code& ec); } #endif diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index 76c3aac98..70be3054a 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -69,13 +69,20 @@ namespace libtorrent { public: http_parser(); - template - T header(char const* key) const; + std::string const& header(char const* key) const + { + static std::string empty; + std::map::const_iterator i + = m_header.find(key); + if (i == m_header.end()) return empty; + return i->second; + } + std::string const& protocol() const { return m_protocol; } int status_code() const { return m_status_code; } std::string const& method() const { return m_method; } std::string const& path() const { return m_path; } - std::string message() const { return m_server_message; } + std::string const& message() const { return m_server_message; } buffer::const_interval get_body() const; bool header_finished() const { return m_state == read_body; } bool finished() const { return m_finished; } @@ -103,15 +110,6 @@ namespace libtorrent bool m_finished; }; - template - T http_parser::header(char const* key) const - { - std::map::const_iterator i - = m_header.find(key); - if (i == m_header.end()) return T(); - return boost::lexical_cast(i->second); - } - class TORRENT_EXPORT http_tracker_connection : public tracker_connection { diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index d2c35ffe3..4d3c5b855 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -60,6 +60,12 @@ namespace libtorrent delete static_cast(s); } + boost::intrusive_ptr self() + { return boost::intrusive_ptr((T*)this); } + + boost::intrusive_ptr self() const + { return boost::intrusive_ptr((T const*)this); } + int refcount() const { return m_refs; } intrusive_ptr_base(): m_refs(0) {} diff --git a/libtorrent/include/libtorrent/lsd.hpp b/libtorrent/include/libtorrent/lsd.hpp index e8eaf0df1..6fb6b7c7b 100644 --- a/libtorrent/include/libtorrent/lsd.hpp +++ b/libtorrent/include/libtorrent/lsd.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_id.hpp" #include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" #include #include @@ -52,7 +53,7 @@ namespace libtorrent typedef boost::function peer_callback_t; -class lsd : boost::noncopyable +class lsd : public intrusive_ptr_base { public: lsd(io_service& ios, address const& listen_interface diff --git a/libtorrent/include/libtorrent/natpmp.hpp b/libtorrent/include/libtorrent/natpmp.hpp index 1c0ffd0be..3b9923972 100644 --- a/libtorrent/include/libtorrent/natpmp.hpp +++ b/libtorrent/include/libtorrent/natpmp.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_NATPMP_HPP #include "libtorrent/socket.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" #include @@ -49,7 +50,7 @@ namespace libtorrent // std::string: error message typedef boost::function portmap_callback_t; -class natpmp +class natpmp : public intrusive_ptr_base { public: natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb); diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index ac9aa0322..ce7e61ec5 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -73,6 +74,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type.hpp" #include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/chained_buffer.hpp" namespace libtorrent { @@ -356,14 +358,23 @@ namespace libtorrent virtual boost::optional downloading_piece_progress() const { - #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "downloading_piece_progress() dispatched to the base class!\n"; - #endif +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "downloading_piece_progress() dispatched to the base class!\n"; +#endif return boost::optional(); } - void send_buffer(char const* begin, char const* end); + void send_buffer(char const* begin, int size); buffer::interval allocate_send_buffer(int size); + template + void append_send_buffer(char* buffer, int size, Destructor const& destructor) + { + m_send_buffer.append_buffer(buffer, size, size, destructor); +#ifdef TORRENT_STATS + m_ses.m_buffer_usage_logger << log_time() << " append_send_buffer: " << size << std::endl; + m_ses.log_buffer_usage(); +#endif + } void setup_send(); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES @@ -376,6 +387,12 @@ namespace libtorrent bool has_country() const { return m_country[0] != 0; } #endif + int send_buffer_size() const + { return m_send_buffer.size(); } + + int send_buffer_capacity() const + { return m_send_buffer.capacity(); } + protected: virtual void get_specific_peer_info(peer_info& p) const = 0; @@ -388,7 +405,7 @@ namespace libtorrent virtual void write_cancel(peer_request const& r) = 0; virtual void write_have(int index) = 0; virtual void write_keepalive() = 0; - virtual void write_piece(peer_request const& r, char const* buffer) = 0; + virtual void write_piece(peer_request const& r, char* buffer) = 0; virtual void write_reject_request(peer_request const& r) = 0; virtual void write_allow_fast(int piece) = 0; @@ -401,13 +418,6 @@ namespace libtorrent virtual void on_sent(asio::error_code const& error , std::size_t bytes_transferred) = 0; - int send_buffer_size() const - { - return (int)m_send_buffer[0].size() - + (int)m_send_buffer[1].size() - - m_write_pos; - } - #ifndef TORRENT_DISABLE_ENCRYPTION buffer::interval wr_recv_buffer() { @@ -512,31 +522,13 @@ namespace libtorrent int m_recv_pos; buffer m_recv_buffer; - // this is the buffer where data that is - // to be sent is stored until it gets - // consumed by send(). Since asio requires - // the memory buffer that is given to async. - // operations to remain valid until the operation - // finishes, there has to be two buffers. While - // waiting for a async_write operation on one - // buffer, the other is used to write data to - // be queued up. - buffer m_send_buffer[2]; - // the current send buffer is the one to write to. - // (m_current_send_buffer + 1) % 2 is the - // buffer we're currently waiting for. - int m_current_send_buffer; + chained_buffer m_send_buffer; // the number of bytes we are currently reading // from disk, that will be added to the send // buffer as soon as they complete int m_reading_bytes; - // if the sending buffer doesn't finish in one send - // operation, this is the position within that buffer - // where the next operation should continue - int m_write_pos; - // timeouts ptime m_last_receive; ptime m_last_sent; diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 7a789ec8c..551cb4f2c 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -80,9 +80,10 @@ namespace libtorrent // for peer choking management void pulse(); + struct peer; // this is called once for every peer we get from // the tracker, pex, lsd or dht. - void peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid + policy::peer* peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int source, char flags); // called when an incoming connection is accepted @@ -212,8 +213,8 @@ namespace libtorrent int num_peers() const { return m_peers.size(); } - typedef std::list::iterator iterator; - typedef std::list::const_iterator const_iterator; + typedef std::multimap::iterator iterator; + typedef std::multimap::const_iterator const_iterator; iterator begin_peer() { return m_peers.begin(); } iterator end_peer() { return m_peers.end(); } @@ -237,7 +238,7 @@ namespace libtorrent iterator find_disconnect_candidate(); iterator find_connect_candidate(); - std::list m_peers; + std::multimap m_peers; torrent* m_torrent; diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 9db79ea3d..67d74153d 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -184,6 +184,9 @@ namespace libtorrent std::pair check_files(std::vector& pieces , int& num_pieces, boost::recursive_mutex& mutex); + // frees a buffer that was returned from a read operation + void free_buffer(char* buf); + void write_resume_data(entry& rd) const; bool verify_resume_data(entry& rd, std::string& error); diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index 27d61af9d..d3470c89d 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -49,6 +49,8 @@ namespace libtorrent std::strftime(str, 200, "%b %d %X", timeinfo); return str; } + + std::string log_time(); } #if (!defined (__MACH__) && !defined (_WIN32) && (!defined(_POSIX_MONOTONIC_CLOCK) \ @@ -389,5 +391,6 @@ namespace libtorrent #endif #endif + #endif diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index bcc54899f..90ebfea31 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -551,6 +551,7 @@ namespace libtorrent private: void on_files_released(int ret, disk_io_job const& j); + void on_torrent_paused(int ret, disk_io_job const& j); void on_storage_moved(int ret, disk_io_job const& j); void on_piece_verified(int ret, disk_io_job const& j diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index fc0650631..2c819df5f 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/broadcast_socket.hpp" #include "libtorrent/http_connection.hpp" #include "libtorrent/connection_queue.hpp" +#include "libtorrent/intrusive_ptr_base.hpp" #include #include @@ -62,7 +63,7 @@ namespace libtorrent // std::string: error message typedef boost::function portmap_callback_t; -class upnp : boost::noncopyable +class upnp : public intrusive_ptr_base { public: upnp(io_service& ios, connection_queue& cc diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index 1290f14a1..8c9360e8f 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -122,7 +122,7 @@ namespace libtorrent void write_request(peer_request const& r); void write_cancel(peer_request const& r) {} void write_have(int index) {} - void write_piece(peer_request const& r, char const* buffer) { assert(false); } + void write_piece(peer_request const& r, char* buffer) { assert(false); } void write_keepalive() {} void on_connected(); void write_reject_request(peer_request const&) {} diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 359c0e0d2..8872b59b1 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -51,23 +51,6 @@ namespace libtorrent || (ip & 0xffff0000) == 0xc0a80000); } - address_v4 guess_local_address(asio::io_service& ios) - { - // make a best guess of the interface we're using and its IP - udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); - for (;i != udp::resolver_iterator(); ++i) - { - address const& a = i->endpoint().address(); - // ignore non-IPv4 addresses - if (!a.is_v4()) break; - // ignore the loopback - if (a.to_v4() == address_v4::loopback()) continue; - } - if (i == udp::resolver_iterator()) return address_v4::any(); - return i->endpoint().address().to_v4(); - } - bool is_loopback(address const& addr) { if (addr.is_v4()) @@ -92,6 +75,30 @@ namespace libtorrent return addr.to_v6() == address_v6::any(); } + address guess_local_address(asio::io_service& ios) + { + // make a best guess of the interface we're using and its IP + asio::error_code ec; + std::vector
const& interfaces = enum_net_interfaces(ios, ec); + address ret = address_v4::any(); + for (std::vector
::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + address const& a = *i; + if (is_loopback(a) + || is_multicast(a) + || is_any(a)) continue; + + // prefer a v4 address, but return a v6 if + // there are no v4 + if (a.is_v4()) return a; + + if (ret != address_v4::any()) + ret = a; + } + return ret; + } + broadcast_socket::broadcast_socket(asio::io_service& ios , udp::endpoint const& multicast_endpoint , receive_handler_t const& handler diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index e27a7f4c3..71407d37f 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -251,12 +251,10 @@ namespace libtorrent (*m_logger) << time_now_string() << " ==> DHT_PORT [ " << listen_port << " ]\n"; #endif - buffer::interval packet = allocate_send_buffer(7); - detail::write_uint32(3, packet.begin); - detail::write_uint8(msg_dht_port, packet.begin); - detail::write_uint16(listen_port, packet.begin); - assert(packet.begin == packet.end); - setup_send(); + char msg[] = {0,0,0,3, msg_dht_port, 0, 0}; + char* ptr = msg + 5; + detail::write_uint16(listen_port, ptr); + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_have_all() @@ -270,8 +268,8 @@ namespace libtorrent (*m_logger) << time_now_string() << " ==> HAVE_ALL\n"; #endif - char buf[] = {0,0,0,1, msg_have_all}; - send_buffer(buf, buf + sizeof(buf)); + char msg[] = {0,0,0,1, msg_have_all}; + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_have_none() @@ -285,8 +283,8 @@ namespace libtorrent (*m_logger) << time_now_string() << " ==> HAVE_NONE\n"; #endif - char buf[] = {0,0,0,1, msg_have_none}; - send_buffer(buf, buf + sizeof(buf)); + char msg[] = {0,0,0,1, msg_have_none}; + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_reject_request(peer_request const& r) @@ -296,22 +294,12 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); assert(associated_torrent().lock()->valid_metadata()); - char buf[] = {0,0,0,13, msg_reject_request}; - - buffer::interval i = allocate_send_buffer(17); - - std::copy(buf, buf + 5, i.begin); - i.begin += 5; - - // index - detail::write_int32(r.piece, i.begin); - // begin - detail::write_int32(r.start, i.begin); - // length - detail::write_int32(r.length, i.begin); - assert(i.begin == i.end); - - setup_send(); + char msg[] = {0,0,0,13, msg_reject_request,0,0,0,0, 0,0,0,0, 0,0,0,0}; + char* ptr = msg + 5; + detail::write_int32(r.piece, ptr); // index + detail::write_int32(r.start, ptr); // begin + detail::write_int32(r.length, ptr); // length + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_allow_fast(int piece) @@ -321,11 +309,10 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); assert(associated_torrent().lock()->valid_metadata()); - char buf[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; - - char* ptr = buf + 5; + char msg[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; + char* ptr = msg + 5; detail::write_int32(piece, ptr); - send_buffer(buf, buf + sizeof(buf)); + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::get_specific_peer_info(peer_info& p) const @@ -556,8 +543,8 @@ namespace libtorrent assert(secret); hasher h; - const char keyA[] = "keyA"; - const char keyB[] = "keyB"; + static const char keyA[] = "keyA"; + static const char keyB[] = "keyB"; // encryption rc4 longkeys // outgoing connection : hash ('keyA',S,SKEY) @@ -587,17 +574,16 @@ namespace libtorrent #endif } - void bt_peer_connection::send_buffer(char* begin, char* end) + void bt_peer_connection::send_buffer(char* buf, int size) { - assert (begin); - assert (end); - assert (end > begin); - assert (!m_rc4_encrypted || m_encrypted); + assert(buf); + assert(size > 0); + assert(!m_rc4_encrypted || m_encrypted); if (m_rc4_encrypted) - m_RC4_handler->encrypt(begin, end - begin); + m_RC4_handler->encrypt(buf, size); - peer_connection::send_buffer(begin, end); + peer_connection::send_buffer(buf, size); } buffer::interval bt_peer_connection::allocate_send_buffer(int size) @@ -606,6 +592,7 @@ namespace libtorrent if (m_rc4_encrypted) { + assert(m_enc_send_buffer.left() == 0); m_enc_send_buffer = peer_connection::allocate_send_buffer(size); return m_enc_send_buffer; } @@ -620,24 +607,24 @@ namespace libtorrent { assert(!m_rc4_encrypted || m_encrypted); - if (m_rc4_encrypted) + if (m_rc4_encrypted && m_enc_send_buffer.left()) { - assert (m_enc_send_buffer.begin); - assert (m_enc_send_buffer.end); - assert (m_enc_send_buffer.left() > 0); + assert(m_enc_send_buffer.begin); + assert(m_enc_send_buffer.end); - m_RC4_handler->encrypt (m_enc_send_buffer.begin, m_enc_send_buffer.left()); + m_RC4_handler->encrypt(m_enc_send_buffer.begin, m_enc_send_buffer.left()); + m_enc_send_buffer.end = m_enc_send_buffer.begin; } peer_connection::setup_send(); } int bt_peer_connection::get_syncoffset(char const* src, int src_size, - char const* target, int target_size) const + char const* target, int target_size) const { - assert (target_size >= src_size); - assert (src_size > 0); - assert (src); - assert (target); + assert(target_size >= src_size); + assert(src_size > 0); + assert(src); + assert(target); int traverse_limit = target_size - src_size; @@ -1288,8 +1275,8 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); - char buf[] = {0,0,0,0}; - send_buffer(buf, buf + sizeof(buf)); + char msg[] = {0,0,0,0}; + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_cancel(peer_request const& r) @@ -1299,22 +1286,12 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); assert(associated_torrent().lock()->valid_metadata()); - char buf[] = {0,0,0,13, msg_cancel}; - - buffer::interval i = allocate_send_buffer(17); - - std::copy(buf, buf + 5, i.begin); - i.begin += 5; - - // index - detail::write_int32(r.piece, i.begin); - // begin - detail::write_int32(r.start, i.begin); - // length - detail::write_int32(r.length, i.begin); - assert(i.begin == i.end); - - setup_send(); + char msg[17] = {0,0,0,13, msg_cancel}; + char* ptr = msg + 5; + detail::write_int32(r.piece, ptr); // index + detail::write_int32(r.start, ptr); // begin + detail::write_int32(r.length, ptr); // length + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_request(peer_request const& r) @@ -1324,22 +1301,13 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); assert(associated_torrent().lock()->valid_metadata()); - char buf[] = {0,0,0,13, msg_request}; + char msg[17] = {0,0,0,13, msg_request}; + char* ptr = msg + 5; - buffer::interval i = allocate_send_buffer(17); - - std::copy(buf, buf + 5, i.begin); - i.begin += 5; - - // index - detail::write_int32(r.piece, i.begin); - // begin - detail::write_int32(r.start, i.begin); - // length - detail::write_int32(r.length, i.begin); - assert(i.begin == i.end); - - setup_send(); + detail::write_int32(r.piece, ptr); // index + detail::write_int32(r.start, ptr); // begin + detail::write_int32(r.length, ptr); // length + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_bitfield(std::vector const& bitfield) @@ -1526,7 +1494,7 @@ namespace libtorrent if (is_choked()) return; char msg[] = {0,0,0,1,msg_choke}; - send_buffer(msg, msg + sizeof(msg)); + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_unchoke() @@ -1536,7 +1504,7 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_unchoke}; - send_buffer(msg, msg + sizeof(msg)); + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_interested() @@ -1546,7 +1514,7 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_interested}; - send_buffer(msg, msg + sizeof(msg)); + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_not_interested() @@ -1556,7 +1524,7 @@ namespace libtorrent assert(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_not_interested}; - send_buffer(msg, msg + sizeof(msg)); + send_buffer(msg, sizeof(msg)); } void bt_peer_connection::write_have(int index) @@ -1567,34 +1535,39 @@ namespace libtorrent assert(index < associated_torrent().lock()->torrent_file().num_pieces()); assert(m_sent_handshake && m_sent_bitfield); - const int packet_size = 9; - char msg[packet_size] = {0,0,0,5,msg_have}; + char msg[] = {0,0,0,5,msg_have,0,0,0,0}; char* ptr = msg + 5; detail::write_int32(index, ptr); - send_buffer(msg, msg + packet_size); + send_buffer(msg, sizeof(msg)); } - void bt_peer_connection::write_piece(peer_request const& r, char const* buffer) + void bt_peer_connection::write_piece(peer_request const& r, char* buffer) { INVARIANT_CHECK; assert(m_sent_handshake && m_sent_bitfield); - const int packet_size = 4 + 5 + 4 + r.length; - boost::shared_ptr t = associated_torrent().lock(); assert(t); - buffer::interval i = allocate_send_buffer(packet_size); - - detail::write_int32(packet_size-4, i.begin); - detail::write_uint8(msg_piece, i.begin); - detail::write_int32(r.piece, i.begin); - detail::write_int32(r.start, i.begin); + char msg[4 + 1 + 4 + 4]; + char* ptr = msg; + assert(r.length <= 16 * 1024); + detail::write_int32(r.length + 1 + 4 + 4, ptr); + detail::write_uint8(msg_piece, ptr); + detail::write_int32(r.piece, ptr); + detail::write_int32(r.start, ptr); + send_buffer(msg, sizeof(msg)); + + append_send_buffer(buffer, r.length + , boost::bind(&session_impl::free_disk_buffer + , boost::ref(m_ses), _1)); + +/* + buffer::interval i = allocate_send_buffer(r.length); std::memcpy(i.begin, buffer, r.length); - - assert(i.begin + r.length == i.end); - + t->filesystem().free_buffer(buffer); +*/ m_payloads.push_back(range(send_buffer_size() - r.length, r.length)); setup_send(); } @@ -1607,13 +1580,13 @@ namespace libtorrent : m_id(id), m_pc(pc) { assert(pc); } - bool operator()(policy::peer const& p) const + bool operator()(std::pair const& p) const { - return p.connection != m_pc - && p.connection - && p.connection->pid() == m_id - && !p.connection->pid().is_all_zeros() - && p.ip.address() == m_pc->remote().address(); + return p.second.connection != m_pc + && p.second.connection + && p.second.connection->pid() == m_id + && !p.second.connection->pid().is_all_zeros() + && p.second.ip.address() == m_pc->remote().address(); } peer_id const& m_id; @@ -2308,7 +2281,7 @@ namespace libtorrent , match_peer_id(pid, this)); if (i != p.end_peer()) { - assert(i->connection->pid() == pid); + assert(i->second.connection->pid() == pid); // we found another connection with the same peer-id // which connection should be closed in order to be // sure that the other end closes the same connection? @@ -2318,7 +2291,7 @@ namespace libtorrent // if not, we should close the outgoing one. if (pid < m_ses.get_peer_id() && is_local()) { - i->connection->disconnect(); + i->second.connection->disconnect(); } else { diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 4fb2cfb3a..c7f766ac4 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -37,18 +37,6 @@ POSSIBILITY OF SUCH DAMAGE. #ifdef TORRENT_DISK_STATS #include "libtorrent/time.hpp" -#include - -namespace -{ - std::string log_time() - { - using namespace libtorrent; - static ptime start = time_now(); - return boost::lexical_cast( - total_milliseconds(time_now() - start)); - } -} #endif @@ -64,7 +52,9 @@ namespace libtorrent #endif , m_disk_io_thread(boost::ref(*this)) { - +#ifdef TORRENT_STATS + m_allocations = 0; +#endif #ifdef TORRENT_DISK_STATS m_log.open("disk_io_thread.log", std::ios::trunc); #endif @@ -188,9 +178,21 @@ namespace libtorrent char* disk_io_thread::allocate_buffer() { boost::mutex::scoped_lock l(m_mutex); +#ifdef TORRENT_STATS + ++m_allocations; +#endif return (char*)m_pool.ordered_malloc(); } + void disk_io_thread::free_buffer(char* buf) + { + boost::mutex::scoped_lock l(m_mutex); +#ifdef TORRENT_STATS + --m_allocations; +#endif + m_pool.ordered_free(buf); + } + void disk_io_thread::operator()() { for (;;) @@ -225,10 +227,14 @@ namespace libtorrent #ifdef TORRENT_DISK_STATS m_log << log_time() << " read " << j.buffer_size << std::endl; #endif + free_buffer = false; if (j.buffer == 0) { l.lock(); j.buffer = (char*)m_pool.ordered_malloc(); +#ifdef TORRENT_STATS + ++m_allocations; +#endif l.unlock(); assert(j.buffer_size <= m_block_size); if (j.buffer == 0) @@ -238,10 +244,6 @@ namespace libtorrent break; } } - else - { - free_buffer = false; - } ret = j.storage->read_impl(j.buffer, j.piece, j.offset , j.buffer_size); @@ -301,6 +303,9 @@ namespace libtorrent { l.lock(); m_pool.ordered_free(j.buffer); +#ifdef TORRENT_STATS + --m_allocations; +#endif } } } diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index 94c74e855..585fa0f38 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -34,6 +34,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#elif defined WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include #endif #include "libtorrent/enum_net.hpp" @@ -136,6 +142,77 @@ namespace libtorrent return ret; } + address router_for_interface(address const interface, asio::error_code& ec) + { +#ifdef WIN32 + + // Load Iphlpapi library + HMODULE iphlp = LoadLibraryA("Iphlpapi.dll"); + if (!iphlp) + { + ec = asio::error::fault; + return address_v4::any(); + } + + // Get GetAdaptersInfo() pointer + typedef DWORD (WINAPI *GetAdaptersInfo_t)(PIP_ADAPTER_INFO, PULONG); + GetAdaptersInfo_t GetAdaptersInfo = (GetAdaptersInfo_t)GetProcAddress(iphlp, "GetAdaptersInfo"); + if (!GetAdaptersInfo) + { + FreeLibrary(iphlp); + ec = asio::error::fault; + return address_v4::any(); + } + + PIP_ADAPTER_INFO adapter_info = 0; + ULONG out_buf_size = 0; + if (GetAdaptersInfo(adapter_info, &out_buf_size) != ERROR_BUFFER_OVERFLOW) + { + FreeLibrary(iphlp); + ec = asio::error::fault; + return address_v4::any(); + } + + adapter_info = (IP_ADAPTER_INFO*)malloc(out_buf_size); + if (!adapter_info) + { + FreeLibrary(iphlp); + ec = asio::error::fault; + return address_v4::any(); + } + + address ret; + if (GetAdaptersInfo(adapter_info, &out_buf_size) == NO_ERROR) + { + PIP_ADAPTER_INFO adapter = adapter_info; + while (adapter != 0) + { + if (interface == address::from_string(adapter->IpAddressList.IpAddress.String, ec)) + { + ret = address::from_string(adapter->GatewayList.IpAddress.String, ec); + break; + } + adapter = adapter->Next; + } + } + + // Free memory + free(adapter_info); + FreeLibrary(iphlp); + + return ret; + +#else + // TODO: temporary implementation + if (!interface.is_v4()) + { + ec = asio::error::fault; + return address_v4::any(); + } + return address_v4((interface.to_v4().to_ulong() & 0xffffff00) | 1); +#endif + } + } diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 2b306ca6d..5dd568655 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -235,7 +235,7 @@ void http_connection::on_read(asio::error_code const& e m_called = true; char const* data = 0; std::size_t size = 0; - if (m_bottled) + if (m_bottled && m_parser.header_finished()) { data = m_parser.get_body().begin; size = m_parser.get_body().left(); @@ -263,7 +263,7 @@ void http_connection::on_read(asio::error_code const& e if (code >= 300 && code < 400) { // attempt a redirect - std::string url = m_parser.header("location"); + std::string const& url = m_parser.header("location"); if (url.empty()) { // missing location header diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 64dea2e2e..8c54cf7ff 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -679,7 +679,7 @@ namespace libtorrent if (m_parser.header_finished()) { - int cl = m_parser.header("content-length"); + int cl = atoi(m_parser.header("content-length").c_str()); if (cl > m_settings.tracker_maximum_response_length) { fail(-1, "content-length is greater than maximum response length"); @@ -718,7 +718,7 @@ namespace libtorrent return; } - std::string location = m_parser.header("location"); + std::string location = m_parser.header("location"); boost::shared_ptr cb = requester(); @@ -763,7 +763,7 @@ namespace libtorrent buffer::const_interval buf(&m_buffer[0] + m_parser.body_start(), &m_buffer[0] + m_recv_pos); - std::string content_encoding = m_parser.header("content-encoding"); + std::string content_encoding = m_parser.header("content-encoding"); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (cb) cb->debug_log("content-encoding: \"" + content_encoding + "\""); diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp index 93eac8565..247c79a0f 100644 --- a/libtorrent/src/kademlia/rpc_manager.cpp +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -83,7 +83,7 @@ void intrusive_ptr_release(observer const* o) { boost::pool<>& p = o->pool_allocator; o->~observer(); - p.ordered_free(const_cast(o)); + p.free(const_cast(o)); } } diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index d7590ec47..44d7b19d4 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -49,8 +49,8 @@ using namespace libtorrent; namespace libtorrent { - // defined in upnp.cpp - address_v4 guess_local_address(asio::io_service&); + // defined in broadcast_socket.cpp + address guess_local_address(asio::io_service&); } lsd::lsd(io_service& ios, address const& listen_interface @@ -58,7 +58,7 @@ lsd::lsd(io_service& ios, address const& listen_interface : m_callback(cb) , m_retry_count(0) , m_socket(ios, udp::endpoint(address_v4::from_string("239.192.152.143"), 6771) - , bind(&lsd::on_announce, this, _1, _2, _3)) + , bind(&lsd::on_announce, self(), _1, _2, _3)) , m_broadcast_timer(ios) , m_disabled(false) { @@ -96,7 +96,7 @@ void lsd::announce(sha1_hash const& ih, int listen_port) #endif m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); - m_broadcast_timer.async_wait(bind(&lsd::resend_announce, this, _1, msg)); + m_broadcast_timer.async_wait(bind(&lsd::resend_announce, self(), _1, msg)); } void lsd::resend_announce(asio::error_code const& e, std::string msg) try @@ -111,7 +111,7 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try return; m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); - m_broadcast_timer.async_wait(bind(&lsd::resend_announce, this, _1, msg)); + m_broadcast_timer.async_wait(bind(&lsd::resend_announce, self(), _1, msg)); } catch (std::exception&) {} @@ -121,48 +121,53 @@ void lsd::on_announce(udp::endpoint const& from, char* buffer { using namespace libtorrent::detail; - char* p = buffer; - char* end = buffer + bytes_transferred; - char* line = std::find(p, end, '\n'); - for (char* i = p; i < line; ++i) *i = std::tolower(*i); -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << time_now_string() - << " <== announce: " << std::string(p, line) << std::endl; -#endif - if (line == end || (line - p >= 9 && std::memcmp("bt-search", p, 9))) + http_parser p; + + p.incoming(buffer::const_interval(buffer, buffer + bytes_transferred)); + + if (!p.header_finished()) { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << time_now_string() - << " *** assumed 'bt-search', ignoring" << std::endl; + m_log << time_now_string() + << " <== announce: incomplete HTTP message\n"; #endif return; } - p = line + 1; - int port = 0; - sha1_hash ih(0); - while (p != end) + + if (p.method() != "bt-search") { - line = std::find(p, end, '\n'); - if (line == end) break; - *line = 0; - for (char* i = p; i < line; ++i) *i = std::tolower(*i); - if (line - p >= 5 && memcmp(p, "port:", 5) == 0) - { - p += 5; - while (*p == ' ') ++p; - port = atoi(p); - } - else if (line - p >= 9 && memcmp(p, "infohash:", 9) == 0) - { - p += 9; - while (*p == ' ') ++p; - if (line - p > 40) p[40] = 0; - try { ih = boost::lexical_cast(p); } - catch (std::exception&) {} - } - p = line + 1; +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " <== announce: invalid HTTP method: " << p.method() << std::endl; +#endif + return; } + std::string const& port_str = p.header("port"); + if (port_str.empty()) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " <== announce: invalid BT-SEARCH, missing port" << std::endl; +#endif + return; + } + + std::string const& ih_str = p.header("infohash"); + if (ih_str.empty()) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " <== announce: invalid BT-SEARCH, missing infohash" << std::endl; +#endif + return; + } + + sha1_hash ih(0); + std::istringstream ih_sstr(ih_str); + ih_sstr >> ih; + int port = atoi(port_str.c_str()); + if (!ih.is_all_zeros() && port != 0) { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index bdcabce9a..38ae52413 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/natpmp.hpp" #include "libtorrent/io.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/enum_net.hpp" using boost::bind; using namespace libtorrent; @@ -48,7 +49,7 @@ namespace libtorrent { // defined in upnp.cpp bool is_local(address const& a); - address_v4 guess_local_address(asio::io_service&); + address guess_local_address(asio::io_service&); } natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb) @@ -71,10 +72,10 @@ natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callbac void natpmp::rebind(address const& listen_interface) try { - address_v4 local = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) + address local = address_v4::any(); + if (listen_interface != address_v4::any()) { - local = listen_interface.to_v4(); + local = listen_interface; } else { @@ -101,14 +102,12 @@ void natpmp::rebind(address const& listen_interface) try m_disabled = false; - // assume the router is located on the local - // network as x.x.x.1 - udp::endpoint nat_endpoint( - address_v4((local.to_ulong() & 0xffffff00) | 1), 5351); + asio::error_code ec; + udp::endpoint nat_endpoint(router_for_interface(local, ec), 5351); + if (ec) + throw std::runtime_error("cannot retrieve router address"); if (nat_endpoint == m_nat_endpoint) return; - - // TODO: find a better way to figure out the router IP m_nat_endpoint = nat_endpoint; #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) @@ -161,7 +160,7 @@ void natpmp::update_mapping(int i, int port) m_retry_count = 0; send_map_request(i); m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) - , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + , m_remote, bind(&natpmp::on_reply, self(), _1, _2)); } } @@ -194,7 +193,7 @@ void natpmp::send_map_request(int i) try // linear back-off instead of exponential ++m_retry_count; m_send_timer.expires_from_now(milliseconds(250 * m_retry_count)); - m_send_timer.async_wait(bind(&natpmp::resend_request, this, i, _1)); + m_send_timer.async_wait(bind(&natpmp::resend_request, self(), i, _1)); } catch (std::exception& e) { @@ -227,7 +226,7 @@ void natpmp::on_reply(asio::error_code const& e if (m_remote != m_nat_endpoint) { m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) - , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + , m_remote, bind(&natpmp::on_reply, self(), _1, _2)); return; } @@ -346,7 +345,7 @@ void natpmp::update_expiration_timer() if (min_index >= 0) { m_refresh_timer.expires_from_now(min_expire - now); - m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, this, _1, min_index)); + m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index)); } } @@ -369,7 +368,7 @@ void natpmp::refresh_mapping(int i) m_retry_count = 0; send_map_request(i); m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) - , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + , m_remote, bind(&natpmp::on_reply, self(), _1, _2)); } } diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index ad5087325..209833de5 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -81,9 +81,7 @@ namespace libtorrent , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) - , m_current_send_buffer(0) , m_reading_bytes(0) - , m_write_pos(0) , m_last_receive(time_now()) , m_last_sent(time_now()) , m_socket(s) @@ -161,9 +159,7 @@ namespace libtorrent , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) - , m_current_send_buffer(0) , m_reading_bytes(0) - , m_write_pos(0) , m_last_receive(time_now()) , m_last_sent(time_now()) , m_socket(s) @@ -734,6 +730,15 @@ namespace libtorrent << " *** PIECE NOT IN REQUEST QUEUE\n"; } #endif + if (has_peer_choked()) + { + // if we're choked and we got a rejection of + // a piece in the allowed fast set, remove it + // from the allow fast set. + std::vector::iterator i = std::find( + m_allowed_fast.begin(), m_allowed_fast.end(), r.piece); + if (i != m_allowed_fast.end()) m_allowed_fast.erase(i); + } if (m_request_queue.empty()) { if (m_download_queue.size() < 2) @@ -1601,6 +1606,15 @@ namespace libtorrent && t->have_piece(index)) return; + if (index < 0 || index >= int(m_have_piece.size())) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " <== INVALID_ALLOWED_FAST [ " << index << " | s: " + << int(m_have_piece.size()) << " ]\n"; +#endif + return; + } + m_allowed_fast.push_back(index); // if the peer has the piece and we want @@ -2082,8 +2096,7 @@ namespace libtorrent p.remote_dl_rate = 0; } - p.send_buffer_size = int(m_send_buffer[0].capacity() - + m_send_buffer[1].capacity()); + p.send_buffer_size = m_send_buffer.capacity(); } void peer_connection::cut_receive_buffer(int size, int packet_size) @@ -2386,8 +2399,7 @@ namespace libtorrent shared_ptr t = m_torrent.lock(); if (m_bandwidth_limit[upload_channel].quota_left() == 0 - && (!m_send_buffer[m_current_send_buffer].empty() - || !m_send_buffer[(m_current_send_buffer + 1) & 1].empty()) + && !m_send_buffer.empty() && !m_connecting && t && !m_ignore_bandwidth_limits) @@ -2415,32 +2427,21 @@ namespace libtorrent assert(!m_writing); - int sending_buffer = (m_current_send_buffer + 1) & 1; - if (m_send_buffer[sending_buffer].empty()) - { - // this means we have to swap buffer, because there's no - // previous buffer we're still waiting for. - std::swap(m_current_send_buffer, sending_buffer); - m_write_pos = 0; - } - // send the actual buffer - if (!m_send_buffer[sending_buffer].empty()) + if (!m_send_buffer.empty()) { - int amount_to_send = (int)m_send_buffer[sending_buffer].size() - m_write_pos; + int amount_to_send = m_send_buffer.size(); int quota_left = m_bandwidth_limit[upload_channel].quota_left(); if (!m_ignore_bandwidth_limits && amount_to_send > quota_left) amount_to_send = quota_left; assert(amount_to_send > 0); - assert(m_write_pos < (int)m_send_buffer[sending_buffer].size()); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "async_write " << amount_to_send << " bytes\n"; #endif - m_socket->async_write_some(asio::buffer( - &m_send_buffer[sending_buffer][m_write_pos], amount_to_send) - , bind(&peer_connection::on_send_data, self(), _1, _2)); + std::list const& vec = m_send_buffer.build_iovec(amount_to_send); + m_socket->async_write_some(vec, bind(&peer_connection::on_send_data, self(), _1, _2)); m_writing = true; } @@ -2511,10 +2512,32 @@ namespace libtorrent m_recv_buffer.resize(m_packet_size); } - void peer_connection::send_buffer(char const* begin, char const* end) + void peer_connection::send_buffer(char const* buf, int size) { - buffer& buf = m_send_buffer[m_current_send_buffer]; - buf.insert(buf.end(), begin, end); + int free_space = m_send_buffer.space_in_last_buffer(); + if (free_space > size) free_space = size; + if (free_space > 0) + { + m_send_buffer.append(buf, free_space); + size -= free_space; + buf += free_space; +#ifdef TORRENT_STATS + m_ses.m_buffer_usage_logger << log_time() << " send_buffer: " + << free_space << std::endl; + m_ses.log_buffer_usage(); +#endif + } + if (size <= 0) return; + + std::pair buffer = m_ses.allocate_buffer(size); + assert(buffer.second >= size); + std::memcpy(buffer.first, buf, size); + m_send_buffer.append_buffer(buffer.first, buffer.second, size + , bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second)); +#ifdef TORRENT_STATS + m_ses.m_buffer_usage_logger << log_time() << " send_buffer_alloc: " << size << std::endl; + m_ses.log_buffer_usage(); +#endif setup_send(); } @@ -2522,10 +2545,29 @@ namespace libtorrent // return value is destructed buffer::interval peer_connection::allocate_send_buffer(int size) { - buffer& buf = m_send_buffer[m_current_send_buffer]; - buf.resize(buf.size() + size); - buffer::interval ret(&buf[0] + buf.size() - size, &buf[0] + buf.size()); - return ret; + char* insert = m_send_buffer.allocate_appendix(size); + if (insert == 0) + { + std::pair buffer = m_ses.allocate_buffer(size); + assert(buffer.second >= size); + m_send_buffer.append_buffer(buffer.first, buffer.second, size + , bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second)); + buffer::interval ret(buffer.first, buffer.first + size); +#ifdef TORRENT_STATS + m_ses.m_buffer_usage_logger << log_time() << " allocate_buffer_alloc: " << size << std::endl; + m_ses.log_buffer_usage(); +#endif + return ret; + } + else + { +#ifdef TORRENT_STATS + m_ses.m_buffer_usage_logger << log_time() << " allocate_buffer: " << size << std::endl; + m_ses.log_buffer_usage(); +#endif + buffer::interval ret(insert, insert + size); + return ret; + } } template @@ -2647,8 +2689,7 @@ namespace libtorrent // if we have requests or pending data to be sent or announcements to be made // we want to send data - return (!m_send_buffer[m_current_send_buffer].empty() - || !m_send_buffer[(m_current_send_buffer + 1) & 1].empty()) + return !m_send_buffer.empty() && (m_bandwidth_limit[upload_channel].quota_left() > 0 || m_ignore_bandwidth_limits) && !m_connecting; @@ -2763,6 +2804,9 @@ namespace libtorrent INVARIANT_CHECK; assert(m_writing); + + m_send_buffer.pop_front(bytes_transferred); + m_writing = false; if (!m_ignore_bandwidth_limits) @@ -2772,9 +2816,6 @@ namespace libtorrent (*m_logger) << "wrote " << bytes_transferred << " bytes\n"; #endif - m_write_pos += bytes_transferred; - - if (error) { #ifdef TORRENT_VERBOSE_LOGGING @@ -2787,34 +2828,11 @@ namespace libtorrent assert(!m_connecting); assert(bytes_transferred > 0); - int sending_buffer = (m_current_send_buffer + 1) & 1; - - assert(int(m_send_buffer[sending_buffer].size()) >= m_write_pos); - if (int(m_send_buffer[sending_buffer].size()) == m_write_pos) - { - m_send_buffer[sending_buffer].clear(); - m_write_pos = 0; - } - m_last_sent = time_now(); on_sent(error, bytes_transferred); fill_send_buffer(); - if (m_choked) - { - for (int i = 0; i < 2; ++i) - { - if (int(m_send_buffer[i].size()) < 64 - && int(m_send_buffer[i].capacity()) > 128) - { - buffer tmp(m_send_buffer[i]); - tmp.swap(m_send_buffer[i]); - assert(m_send_buffer[i].capacity() == m_send_buffer[i].size()); - } - } - } - setup_send(); } catch (std::exception& e) @@ -2876,8 +2894,6 @@ namespace libtorrent } } */ - assert(m_write_pos <= int(m_send_buffer[ - (m_current_send_buffer + 1) & 1].size())); // extremely expensive invariant check /* diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 49671c5db..212e09f3c 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -138,26 +138,14 @@ namespace return free_upload; } - struct match_peer_address - { - match_peer_address(address const& addr) - : m_addr(addr) - {} - - bool operator()(policy::peer const& p) const - { return p.ip.address() == m_addr; } - - address const& m_addr; - }; - struct match_peer_endpoint { match_peer_endpoint(tcp::endpoint const& ep) : m_ep(ep) {} - bool operator()(policy::peer const& p) const - { return p.ip == m_ep; } + bool operator()(std::pair const& p) const + { return p.second.ip == m_ep; } tcp::endpoint const& m_ep; }; @@ -168,8 +156,8 @@ namespace : m_id(id_) {} - bool operator()(policy::peer const& p) const - { return p.connection && p.connection->pid() == m_id; } + bool operator()(std::pair const& p) const + { return p.second.connection && p.second.connection->pid() == m_id; } peer_id const& m_id; }; @@ -180,11 +168,11 @@ namespace : m_conn(c) {} - bool operator()(policy::peer const& p) const + bool operator()(std::pair const& p) const { - return p.connection == &m_conn - || (p.ip == m_conn.remote() - && p.type == policy::peer::connectable); + return p.second.connection == &m_conn + || (p.second.ip == m_conn.remote() + && p.second.type == policy::peer::connectable); } peer_connection const& m_conn; @@ -362,35 +350,35 @@ namespace libtorrent piece_picker* p = 0; if (m_torrent->has_picker()) p = &m_torrent->picker(); - for (std::list::iterator i = m_peers.begin() + for (iterator i = m_peers.begin() , end(m_peers.end()); i != end;) { - if ((ses.m_ip_filter.access(i->ip.address()) & ip_filter::blocked) == 0) + if ((ses.m_ip_filter.access(i->second.ip.address()) & ip_filter::blocked) == 0) { ++i; continue; } - if (i->connection) + if (i->second.connection) { - i->connection->disconnect(); + i->second.connection->disconnect(); if (ses.m_alerts.should_post(alert::info)) { - ses.m_alerts.post_alert(peer_blocked_alert(i->ip.address() + ses.m_alerts.post_alert(peer_blocked_alert(i->second.ip.address() , "disconnected blocked peer")); } - assert(i->connection == 0 - || i->connection->peer_info_struct() == 0); + assert(i->second.connection == 0 + || i->second.connection->peer_info_struct() == 0); } else { if (ses.m_alerts.should_post(alert::info)) { - ses.m_alerts.post_alert(peer_blocked_alert(i->ip.address() + ses.m_alerts.post_alert(peer_blocked_alert(i->second.ip.address() , "blocked peer removed from peer list")); } } - if (p) p->clear_peer(&(*i)); + if (p) p->clear_peer(&i->second); m_peers.erase(i++); } } @@ -489,7 +477,7 @@ namespace libtorrent for (iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - peer_connection* c = i->connection; + peer_connection* c = i->second.connection; if (c == 0) continue; if (c->is_disconnecting()) continue; @@ -497,13 +485,13 @@ namespace libtorrent // isn't interesting if (disconnect_peer != m_peers.end() && c->is_interesting() - && !disconnect_peer->connection->is_interesting()) + && !disconnect_peer->second.connection->is_interesting()) continue; double transferred_amount = (double)c->statistics().total_payload_download(); - time_duration connected_time = now - i->connected; + time_duration connected_time = now - i->second.connected; double connected_time_in_seconds = total_seconds(connected_time); @@ -513,7 +501,7 @@ namespace libtorrent // prefer to disconnect uninteresting peers, and secondly slow peers if (transfer_rate <= slowest_transfer_rate || (disconnect_peer != m_peers.end() - && disconnect_peer->connection->is_interesting() + && disconnect_peer->second.connection->is_interesting() && !c->is_interesting())) { slowest_transfer_rate = transfer_rate; @@ -540,21 +528,21 @@ namespace libtorrent for (iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - if (i->connection) continue; - if (i->banned) continue; - if (i->type == peer::not_connectable) continue; - if (i->seed && finished) continue; - if (i->failcount >= max_failcount) continue; - if (now - i->connected < seconds(i->failcount * min_reconnect_time)) + if (i->second.connection) continue; + if (i->second.banned) continue; + if (i->second.type == peer::not_connectable) continue; + if (i->second.seed && finished) continue; + if (i->second.failcount >= max_failcount) continue; + if (now - i->second.connected < seconds(i->second.failcount * min_reconnect_time)) continue; - if (ses.m_port_filter.access(i->ip.port()) & port_filter::blocked) + if (ses.m_port_filter.access(i->second.ip.port()) & port_filter::blocked) continue; - assert(i->connected <= now); + assert(i->second.connected <= now); - if (i->connected <= min_connect_time) + if (i->second.connected <= min_connect_time) { - min_connect_time = i->connected; + min_connect_time = i->second.connected; candidate = i; } } @@ -685,11 +673,14 @@ namespace libtorrent for (iterator i = m_peers.begin(); i != m_peers.end();) { // this timeout has to be customizable! - if (i->connection == 0 - && i->connected != min_time() - && now - i->connected > minutes(120)) + // don't remove banned peers, they should + // remain banned + if (i->second.connection == 0 + && i->second.connected != min_time() + && !i->second.banned + && now - i->second.connected > minutes(120)) { - if (p) p->clear_peer(&(*i)); + if (p) p->clear_peer(&i->second); m_peers.erase(i++); } else @@ -899,12 +890,12 @@ namespace libtorrent for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - if (!i->connection - || i->connection->is_connecting() - || i->connection->is_disconnecting() - || !i->connection->is_peer_interested()) + if (!i->second.connection + || i->second.connection->is_connecting() + || i->second.connection->is_disconnecting() + || !i->second.connection->is_peer_interested()) continue; - if (i->connection->is_choked()) ++ret; + if (i->second.connection->is_choked()) ++ret; } return ret; } @@ -948,23 +939,20 @@ namespace libtorrent } else { - i = std::find_if( - m_peers.begin() - , m_peers.end() - , match_peer_address(c.remote().address())); + i = m_peers.find(c.remote().address()); } if (i != m_peers.end()) { - if (i->banned) + if (i->second.banned) throw protocol_error("ip address banned, closing"); - if (i->connection != 0) + if (i->second.connection != 0) { - assert(i->connection != &c); + assert(i->second.connection != &c); // the new connection is a local (outgoing) connection // or the current one is already connected - if (!i->connection->is_connecting() || c.is_local()) + if (!i->second.connection->is_connecting() || c.is_local()) { throw protocol_error("duplicate connection, closing"); } @@ -975,10 +963,7 @@ namespace libtorrent " is connecting and this connection is incoming. closing existing " "connection in favour of this one"); #endif - i->connection->disconnect(); -#ifndef NDEBUG - check_invariant(); -#endif + i->second.connection->disconnect(); } } } @@ -989,27 +974,23 @@ namespace libtorrent assert(c.remote() == c.get_socket()->remote_endpoint()); peer p(c.remote(), peer::not_connectable, 0); - m_peers.push_back(p); - i = boost::prior(m_peers.end()); -#ifndef NDEBUG - check_invariant(); -#endif + i = m_peers.insert(std::make_pair(c.remote().address(), p)); } assert(m_torrent->connection_for(c.remote()) == &c); - c.set_peer_info(&*i); - assert(i->connection == 0); - c.add_stat(i->prev_amount_download, i->prev_amount_upload); - i->prev_amount_download = 0; - i->prev_amount_upload = 0; - i->connection = &c; - assert(i->connection); - i->connected = time_now(); + c.set_peer_info(&i->second); + assert(i->second.connection == 0); + c.add_stat(i->second.prev_amount_download, i->second.prev_amount_upload); + i->second.prev_amount_download = 0; + i->second.prev_amount_upload = 0; + i->second.connection = &c; + assert(i->second.connection); + i->second.connected = time_now(); // m_last_optimistic_disconnect = time_now(); } - void policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid + policy::peer* policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int src, char flags) { // too expensive @@ -1017,7 +998,7 @@ namespace libtorrent // just ignore the obviously invalid entries if (remote.address() == address() || remote.port() == 0) - return; + return 0; aux::session_impl& ses = m_torrent->session(); @@ -1029,7 +1010,7 @@ namespace libtorrent ses.m_alerts.post_alert(peer_blocked_alert(remote.address() , "outgoing port blocked, peer not added to peer list")); } - return; + return 0; } try @@ -1038,23 +1019,15 @@ namespace libtorrent if (m_torrent->settings().allow_multiple_connections_per_ip) { - i = std::find_if( - m_peers.begin() - , m_peers.end() - , match_peer_endpoint(remote)); + std::pair range = m_peers.equal_range(remote.address()); + i = std::find_if(range.first, range.second, match_peer_endpoint(remote)); - if (i == m_peers.end()) - i = std::find_if( - m_peers.begin() - , m_peers.end() - , match_peer_id(pid)); + if (i == range.second) + i = std::find_if(m_peers.begin(), m_peers.end(), match_peer_id(pid)); } else { - i = std::find_if( - m_peers.begin() - , m_peers.end() - , match_peer_address(remote.address())); + i = m_peers.find(remote.address()); } if (i == m_peers.end()) @@ -1067,20 +1040,17 @@ namespace libtorrent ses.m_alerts.post_alert(peer_blocked_alert(remote.address() , "blocked peer not added to peer list")); } - return; + return 0; } // we don't have any info about this peer. // add a new entry peer p(remote, peer::connectable, src); - m_peers.push_back(p); - // the iterator is invalid - // because of the push_back() - i = boost::prior(m_peers.end()); + i = m_peers.insert(std::make_pair(remote.address(), p)); #ifndef TORRENT_DISABLE_ENCRYPTION if (flags & 0x01) p.pe_support = true; #endif - if (flags & 0x02) i->seed = true; + if (flags & 0x02) i->second.seed = true; // try to send a DHT ping to this peer // as well, to figure out if it supports @@ -1093,42 +1063,42 @@ namespace libtorrent } else { - i->type = peer::connectable; + i->second.type = peer::connectable; // in case we got the ip from a remote connection, port is // not known, so save it. Client may also have changed port // for some reason. - i->ip = remote; - i->source |= src; + i->second.ip = remote; + i->second.source |= src; // if this peer has failed before, decrease the // counter to allow it another try, since somebody // else is appearantly able to connect to it // if it comes from the DHT it might be stale though - if (i->failcount > 0 && src != peer_info::dht) - --i->failcount; + if (i->second.failcount > 0 && src != peer_info::dht) + --i->second.failcount; // if we're connected to this peer // we already know if it's a seed or not // so we don't have to trust this source - if ((flags & 0x02) && !i->connection) i->seed = true; + if ((flags & 0x02) && !i->second.connection) i->second.seed = true; - if (i->connection) +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (i->second.connection) { // this means we're already connected // to this peer. don't connect to // it again. -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) m_torrent->debug_log("already connected to peer: " + remote.address().to_string() + ":" + boost::lexical_cast(remote.port()) + " " - + boost::lexical_cast(i->connection->pid())); -#endif + + boost::lexical_cast(i->second.connection->pid())); - assert(i->connection->associated_torrent().lock().get() == m_torrent); - return; + assert(i->second.connection->associated_torrent().lock().get() == m_torrent); } +#endif } + return &i->second; } catch(std::exception& e) { @@ -1138,6 +1108,7 @@ namespace libtorrent peer_error_alert(remote, pid, e.what())); } } + return 0; } // this is called when we are choked by a peer @@ -1159,12 +1130,12 @@ namespace libtorrent for (iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - if (i->connection == 0) continue; + if (i->second.connection == 0) continue; // if we're not interested, we will not become interested - if (!i->connection->is_interesting()) continue; - if (!i->connection->has_piece(index)) continue; + if (!i->second.connection->is_interesting()) continue; + if (!i->second.connection->has_piece(index)) continue; - i->connection->update_interest(); + i->second.connection->update_interest(); } } } @@ -1187,8 +1158,8 @@ namespace libtorrent // INVARIANT_CHECK; assert(std::find_if(m_peers.begin(), m_peers.end() - , boost::bind(std::equal_to(), bind(&peer::connection, _1) - , &c)) != m_peers.end()); + , boost::bind(std::equal_to(), bind(&peer::connection + , bind(&iterator::value_type::second, _1)), &c)) != m_peers.end()); // if the peer is choked and we have upload slots left, // then unchoke it. Another condition that has to be met @@ -1303,23 +1274,23 @@ namespace libtorrent iterator p = find_connect_candidate(); if (p == m_peers.end()) return false; - assert(!p->banned); - assert(!p->connection); - assert(p->type == peer::connectable); + assert(!p->second.banned); + assert(!p->second.connection); + assert(p->second.type == peer::connectable); try { - p->connected = time_now(); - p->connection = m_torrent->connect_to_peer(&*p); - assert(p->connection == m_torrent->connection_for(p->ip)); - if (p->connection == 0) + p->second.connected = time_now(); + p->second.connection = m_torrent->connect_to_peer(&p->second); + assert(p->second.connection == m_torrent->connection_for(p->second.ip)); + if (p->second.connection == 0) { - ++p->failcount; + ++p->second.failcount; return false; } - p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); - p->prev_amount_download = 0; - p->prev_amount_upload = 0; + p->second.connection->add_stat(p->second.prev_amount_download, p->second.prev_amount_upload); + p->second.prev_amount_download = 0; + p->second.prev_amount_upload = 0; return true; } catch (std::exception& e) @@ -1329,7 +1300,7 @@ namespace libtorrent << e.what() << "'\n"; #endif std::cerr << e.what() << std::endl; - ++p->failcount; + ++p->second.failcount; return false; } } @@ -1340,10 +1311,10 @@ namespace libtorrent if (p == m_peers.end()) return false; #if defined(TORRENT_VERBOSE_LOGGING) - (*p->connection->m_logger) << "*** CLOSING CONNECTION 'too many connections'\n"; + (*p->second.connection->m_logger) << "*** CLOSING CONNECTION 'too many connections'\n"; #endif - p->connection->disconnect(); + p->second.connection->disconnect(); return true; } @@ -1425,21 +1396,23 @@ namespace libtorrent int total_connections = 0; int nonempty_connections = 0; - std::set
unique_test; - std::set unique_test2; + std::set unique_test; for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - peer const& p = *i; + peer const& p = i->second; if (!m_torrent->settings().allow_multiple_connections_per_ip) - assert(unique_test.find(p.ip.address()) == unique_test.end()); - assert(unique_test2.find(p.ip) == unique_test2.end()); - unique_test.insert(p.ip.address()); - unique_test2.insert(p.ip); + { + assert(m_peers.count(p.ip.address()) == 1); + } + else + { + assert(unique_test.count(p.ip) == 0); + unique_test.insert(p.ip); + } ++total_connections; if (!p.connection) { -// assert(m_torrent->connection_for(p.ip) == 0); continue; } if (!m_torrent->settings().allow_multiple_connections_per_ip) @@ -1493,10 +1466,8 @@ namespace libtorrent { policy::peer* p = static_cast(*i); if (p == 0) continue; - std::list::const_iterator k = m_peers.begin(); - for (; k != m_peers.end(); ++k) - if (&(*k) == p) break; - assert(k != m_peers.end()); + assert(std::find_if(m_peers.begin(), m_peers.end() + , match_peer_connection(*p->connection)) != m_peers.end()); } } diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 6298b3c2c..478c6fe93 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -83,6 +83,14 @@ using libtorrent::aux::session_impl; namespace libtorrent { + std::string log_time() + { + static const ptime start = time_now(); + char ret[200]; + std::sprintf(ret, "%d", total_milliseconds(time_now() - start)); + return ret; + } + namespace aux { filesystem_init::filesystem_init() diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index a58906156..2eed1c328 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -205,6 +205,15 @@ namespace detail // lock the session to add the new torrent session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); mutex::scoped_lock l2(m_mutex); + + if (m_torrents.empty() || m_torrents.front() != t) + { + // this means the torrent was removed right after it was + // added. Abort the checking. + t.reset(); + continue; + } + // clear the resume data now that it has been used // (the fast resume data is now parsed and stored in t) t->resume_data = entry(); @@ -214,6 +223,7 @@ namespace detail { INVARIANT_CHECK; + assert(!m_torrents.empty()); assert(m_torrents.front() == t); t->torrent_ptr->files_checked(t->unfinished_pieces); @@ -244,6 +254,14 @@ namespace detail t->torrent_ptr->get_policy().peer_from_tracker(*i, id , peer_info::resume_data, 0); } + + for (std::vector::const_iterator i = t->banned_peers.begin(); + i != t->banned_peers.end(); ++i) + { + policy::peer* p = t->torrent_ptr->get_policy().peer_from_tracker(*i, id + , peer_info::resume_data, 0); + if (p) p->banned = true; + } } else { @@ -507,7 +525,8 @@ namespace detail std::pair listen_port_range , fingerprint const& cl_fprint , char const* listen_interface) - : m_strand(m_io_service) + : m_send_buffers(send_buffer_size) + , m_strand(m_io_service) , m_files(40) , m_half_open(m_io_service) , m_download_channel(m_io_service, peer_connection::download_channel) @@ -546,7 +565,7 @@ namespace detail #endif #ifdef TORRENT_STATS - m_stats_logger.open("session_stats.log"); + m_stats_logger.open("session_stats.log", std::ios::trunc); m_stats_logger << "1. second\n" "2. upload rate\n" @@ -555,7 +574,9 @@ namespace detail "5. seeding torrents\n" "6. peers\n" "7. connecting peers\n" + "8. disk block buffers\n" "\n"; + m_buffer_usage_logger.open("buffer_stats.log", std::ios::trunc); m_second_counter = 0; #endif @@ -999,7 +1020,8 @@ namespace detail { session_impl::mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; +// too expensive +// INVARIANT_CHECK; if (e) { @@ -1031,7 +1053,7 @@ namespace detail else ++downloading_torrents; } - int num_connections = 0; + int num_complete_connections = 0; int num_half_open = 0; for (connection_map::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) @@ -1039,7 +1061,7 @@ namespace detail if (i->second->is_connecting()) ++num_half_open; else - ++num_connections; + ++num_complete_connections; } m_stats_logger @@ -1048,8 +1070,9 @@ namespace detail << m_stat.download_rate() << "\t" << downloading_torrents << "\t" << seeding_torrents << "\t" - << num_connections << "\t" + << num_complete_connections << "\t" << num_half_open << "\t" + << m_disk_thread.disk_allocations() << "\t" << std::endl; #endif @@ -1755,7 +1778,6 @@ namespace detail assert(m_torrents.find(i_hash) == m_torrents.end()); return; } - l.unlock(); if (h.m_chk) { @@ -2195,9 +2217,9 @@ namespace detail INVARIANT_CHECK; - m_lsd.reset(new lsd(m_io_service + m_lsd = new lsd(m_io_service , m_listen_interface.address() - , bind(&session_impl::on_lsd_peer, this, _1, _2))); + , bind(&session_impl::on_lsd_peer, this, _1, _2)); } void session_impl::start_natpmp() @@ -2206,10 +2228,10 @@ namespace detail INVARIANT_CHECK; - m_natpmp.reset(new natpmp(m_io_service + m_natpmp = new natpmp(m_io_service , m_listen_interface.address() , bind(&session_impl::on_port_mapping - , this, _1, _2, _3))); + , this, _1, _2, _3)); m_natpmp->set_mappings(m_listen_interface.port(), #ifndef TORRENT_DISABLE_DHT @@ -2224,11 +2246,11 @@ namespace detail INVARIANT_CHECK; - m_upnp.reset(new upnp(m_io_service, m_half_open + m_upnp = new upnp(m_io_service, m_half_open , m_listen_interface.address() , m_settings.user_agent , bind(&session_impl::on_port_mapping - , this, _1, _2, _3))); + , this, _1, _2, _3)); m_upnp->set_mappings(m_listen_interface.port(), #ifndef TORRENT_DISABLE_DHT @@ -2240,7 +2262,7 @@ namespace detail void session_impl::stop_lsd() { mutex_t::scoped_lock l(m_mutex); - m_lsd.reset(); + m_lsd = 0; } void session_impl::stop_natpmp() @@ -2248,7 +2270,7 @@ namespace detail mutex_t::scoped_lock l(m_mutex); if (m_natpmp.get()) m_natpmp->close(); - m_natpmp.reset(); + m_natpmp = 0; } void session_impl::stop_upnp() @@ -2256,9 +2278,38 @@ namespace detail mutex_t::scoped_lock l(m_mutex); if (m_upnp.get()) m_upnp->close(); - m_upnp.reset(); + m_upnp = 0; } + void session_impl::free_disk_buffer(char* buf) + { + m_disk_thread.free_buffer(buf); + } + + std::pair session_impl::allocate_buffer(int size) + { + int num_buffers = (size + send_buffer_size - 1) / send_buffer_size; +#ifdef TORRENT_STATS + m_buffer_allocations += num_buffers; + m_buffer_usage_logger << log_time() << " protocol_buffer: " + << (m_buffer_allocations * send_buffer_size) << std::endl; +#endif + return std::make_pair((char*)m_send_buffers.ordered_malloc(num_buffers) + , num_buffers * send_buffer_size); + } + + void session_impl::free_buffer(char* buf, int size) + { + assert(size % send_buffer_size == 0); + int num_buffers = size / send_buffer_size; +#ifdef TORRENT_STATS + m_buffer_allocations -= num_buffers; + assert(m_buffer_allocations >= 0); + m_buffer_usage_logger << log_time() << " protocol_buffer: " + << (m_buffer_allocations * send_buffer_size) << std::endl; +#endif + m_send_buffers.ordered_free(buf, num_buffers); + } #ifndef NDEBUG void session_impl::check_invariant() const @@ -2328,9 +2379,9 @@ namespace detail // the peers - if (rd.find_key("peers")) + if (entry* peers_entry = rd.find_key("peers")) { - entry::list_type& peer_list = rd["peers"].list(); + entry::list_type& peer_list = peers_entry->list(); std::vector tmp_peers; tmp_peers.reserve(peer_list.size()); @@ -2346,6 +2397,24 @@ namespace detail peers.swap(tmp_peers); } + if (entry* banned_peers_entry = rd.find_key("banned_peers")) + { + entry::list_type& peer_list = banned_peers_entry->list(); + + std::vector tmp_peers; + tmp_peers.reserve(peer_list.size()); + for (entry::list_type::iterator i = peer_list.begin(); + i != peer_list.end(); ++i) + { + tcp::endpoint a( + address::from_string((*i)["ip"].string()) + , (unsigned short)(*i)["port"].integer()); + tmp_peers.push_back(a); + } + + banned_peers.swap(tmp_peers); + } + // read piece map const entry::list_type& slots = rd["slots"].list(); if ((int)slots.size() > info.num_pieces()) diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index dbf6a9382..53d8379a0 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -1065,6 +1065,11 @@ namespace libtorrent return m_storage->verify_resume_data(rd, error); } + void piece_manager::free_buffer(char* buf) + { + m_io_thread.free_buffer(buf); + } + void piece_manager::async_release_files( boost::function const& handler) { @@ -1243,7 +1248,7 @@ namespace libtorrent , block_size); crc.update(&buf[0], block_size); } - if (bi[num_blocks - 1].state == piece_picker::block_info::state_finished) + if (num_blocks > 0 && bi[num_blocks - 1].state == piece_picker::block_info::state_finished) { m_storage->read( &buf[0] diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 6e941e9ec..35abc2c49 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -775,6 +775,8 @@ namespace libtorrent if (total_done >= m_torrent_file->total_size()) { + // Thist happens when a piece has been downloaded completely + // but not yet verified against the hash std::copy(m_have_pieces.begin(), m_have_pieces.end() , std::ostream_iterator(std::cerr, " ")); std::cerr << std::endl; @@ -1022,11 +1024,26 @@ namespace libtorrent #endif disconnect_all(); - if (m_owning_storage.get()) m_storage->async_release_files(); + if (m_owning_storage.get()) + m_storage->async_release_files( + bind(&torrent::on_files_released, shared_from_this(), _1, _2)); + m_owning_storage = 0; } void torrent::on_files_released(int ret, disk_io_job const& j) + { +/* + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(torrent_paused_alert(get_handle(), "torrent paused")); + } +*/ + } + + void torrent::on_torrent_paused(int ret, disk_io_job const& j) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -1464,9 +1481,6 @@ namespace libtorrent m_policy->connection_closed(*p); p->set_peer_info(0); m_connections.erase(i); -#ifndef NDEBUG - m_policy->check_invariant(); -#endif } catch (std::exception& e) { @@ -1731,7 +1745,7 @@ namespace libtorrent m_resolving_country = false; // must be ordered in increasing order - country_entry country_map[] = + static const country_entry country_map[] = { { 4, "AF"}, { 8, "AL"}, { 10, "AQ"}, { 12, "DZ"}, { 16, "AS"} , { 20, "AD"}, { 24, "AO"}, { 28, "AG"}, { 31, "AZ"}, { 32, "AR"} @@ -1801,7 +1815,7 @@ namespace libtorrent // look up the country code in the map const int size = sizeof(country_map)/sizeof(country_map[0]); country_entry tmp = {country, ""}; - country_entry* i = + country_entry const* i = std::lower_bound(country_map, country_map + size, tmp , bind(&country_entry::code, _1) < bind(&country_entry::code, _2)); if (i == country_map + size @@ -2118,7 +2132,9 @@ namespace libtorrent , bind(&peer_connection::disconnect, _1)); assert(m_storage); - m_storage->async_release_files(); + // we need to keep the object alive during this operation + m_storage->async_release_files( + bind(&torrent::on_files_released, shared_from_this(), _1, _2)); } // called when torrent is complete (all pieces downloaded) @@ -2381,6 +2397,8 @@ namespace libtorrent #ifndef NDEBUG void torrent::check_invariant() const { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + int num_uploads = 0; std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -2550,10 +2568,8 @@ namespace libtorrent if (m_owning_storage.get()) { assert(m_storage); - // TOOD: add a callback which posts - // an alert for the client to sync. with m_storage->async_release_files( - bind(&torrent::on_files_released, shared_from_this(), _1, _2)); + bind(&torrent::on_torrent_paused, shared_from_this(), _1, _2)); } } diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index ebef802a8..d52a18dd2 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -363,7 +363,7 @@ namespace libtorrent aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); if (d != 0) { - torrent_status st; + torrent_status st = d->torrent_ptr->status(); if (d->processing) { @@ -737,14 +737,23 @@ namespace libtorrent } // write local peers - ret["peers"] = entry::list_type(); entry::list_type& peer_list = ret["peers"].list(); + entry::list_type& banned_peer_list = ret["banned_peers"].list(); policy& pol = t->get_policy(); for (policy::iterator i = pol.begin_peer() , end(pol.end_peer()); i != end; ++i) { + if (i->second.banned) + { + tcp::endpoint ip = i->second.ip; + entry peer(entry::dictionary_t); + peer["ip"] = ip.address().to_string(); + peer["port"] = ip.port(); + banned_peer_list.push_back(peer); + continue; + } // we cannot save remote connection // since we don't know their listen port // unless they gave us their listen port @@ -752,10 +761,9 @@ namespace libtorrent // so, if the peer is not connectable (i.e. we // don't know its listen port) or if it has // been banned, don't save it. - if (i->type == policy::peer::not_connectable - || i->banned) continue; + if (i->second.type == policy::peer::not_connectable) continue; - tcp::endpoint ip = i->ip; + tcp::endpoint ip = i->second.ip; entry peer(entry::dictionary_t); peer["ip"] = ip.address().to_string(); peer["port"] = ip.port(); diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 87f950b48..f1ee92636 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -56,7 +56,7 @@ using namespace libtorrent; namespace libtorrent { bool is_local(address const& a); - address_v4 guess_local_address(asio::io_service&); + address guess_local_address(asio::io_service&); } upnp::upnp(io_service& ios, connection_queue& cc @@ -70,7 +70,7 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_io_service(ios) , m_strand(ios) , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900) - , m_strand.wrap(bind(&upnp::on_reply, this, _1, _2, _3)), false) + , m_strand.wrap(bind(&upnp::on_reply, self(), _1, _2, _3)), false) , m_broadcast_timer(ios) , m_refresh_timer(ios) , m_disabled(false) @@ -119,7 +119,7 @@ void upnp::discover_device() try ++m_retry_count; m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); m_broadcast_timer.async_wait(m_strand.wrap(bind(&upnp::resend_request - , this, _1))); + , self(), _1))); #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() @@ -203,7 +203,7 @@ try try { d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, self(), _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); } @@ -300,7 +300,7 @@ try return; } - std::string url = p.header("location"); + std::string url = p.header("location"); if (url.empty()) { #ifdef TORRENT_UPNP_LOGGING @@ -393,7 +393,7 @@ try try { d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, self(), _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); } @@ -480,9 +480,9 @@ void upnp::map_port(rootdevice& d, int i) assert(d.service_namespace); d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2 + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, self(), _1, _2 , boost::ref(d), i)), true - , bind(&upnp::create_port_mapping, this, _1, boost::ref(d), i))); + , bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i))); d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) , seconds(10)); @@ -523,9 +523,9 @@ void upnp::unmap_port(rootdevice& d, int i) return; } d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2 + , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, self(), _1, _2 , boost::ref(d), i)), true - , bind(&upnp::delete_port_mapping, this, boost::ref(d), i))); + , bind(&upnp::delete_port_mapping, self(), boost::ref(d), i))); d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) , seconds(10)); @@ -851,7 +851,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e || next_expire > d.mapping[mapping].expires) { m_refresh_timer.expires_at(d.mapping[mapping].expires); - m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, this, _1))); + m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, self(), _1))); } } else @@ -962,7 +962,7 @@ void upnp::on_expire(asio::error_code const& e) try if (next_expire != max_time()) { m_refresh_timer.expires_at(next_expire); - m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, this, _1))); + m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, self(), _1))); } } catch (std::exception&) diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index a307fc9cb..bc09b4935 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -297,7 +297,7 @@ namespace libtorrent (*m_logger) << request << "\n"; #endif - send_buffer(request.c_str(), request.c_str() + request.size()); + send_buffer(request.c_str(), request.size()); } // -------------------------- @@ -387,7 +387,7 @@ namespace libtorrent { // this means we got a redirection request // look for the location header - std::string location = m_parser.header("location"); + std::string location = m_parser.header("location"); if (location.empty()) { @@ -423,7 +423,7 @@ namespace libtorrent throw std::runtime_error("redirecting to " + location); } - std::string server_version = m_parser.header("server"); + std::string const& server_version = m_parser.header("server"); if (!server_version.empty()) { m_server_string = "URL seed @ "; @@ -445,7 +445,7 @@ namespace libtorrent size_type range_end; if (m_parser.status_code() == 206) { - std::stringstream range_str(m_parser.header("content-range")); + std::stringstream range_str(m_parser.header("content-range")); char dummy; std::string bytes; range_str >> bytes >> range_start >> dummy >> range_end; @@ -461,7 +461,7 @@ namespace libtorrent else { range_start = 0; - range_end = m_parser.header("content-length"); + range_end = atol(m_parser.header("content-length").c_str()); if (range_end == -1) { // we should not try this server again. From 5db5f423ceb663f6812d54a9087701fc84d53492 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Wed, 3 Oct 2007 22:46:30 +0000 Subject: [PATCH 0180/1009] disk io priority fix --- libtorrent/src/disk_io_thread.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index c7f766ac4..112e76927 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -133,29 +133,27 @@ namespace libtorrent // when we're reading, we may not skip // ahead of any write operation that overlaps // the region we're reading - for (; i != m_jobs.rend(); ++i) + for (; i != m_jobs.rend(); i++) { - if (i->action == disk_io_job::read && *i < j) - break; + // if *i should come before j, stop + // and insert j before i + if (*i < j) break; + // if we come across a write operation that + // overlaps the region we're reading, we need + // to stop if (i->action == disk_io_job::write && i->storage == j.storage && i->piece == j.piece && range_overlap(i->offset, i->buffer_size , j.offset, j.buffer_size)) - { - // we have to stop, and we haven't - // found a suitable place for this job - // so just queue it up at the end - i = m_jobs.rbegin(); break; - } } } else if (j.action == disk_io_job::write) { for (; i != m_jobs.rend(); ++i) { - if (i->action == disk_io_job::write && *i < j) + if (*i < j) { if (i != m_jobs.rbegin() && i.base()->storage.get() != j.storage.get()) @@ -165,7 +163,12 @@ namespace libtorrent } } - if (i == m_jobs.rend()) i = m_jobs.rbegin(); + // if we are placed in front of all other jobs, put it on the back of + // the queue, to sweep the disk in the same direction, and to avoid + // starvation. The exception is if the priority is higher than the + // job at the front of the queue + if (i == m_jobs.rend() && (m_jobs.empty() || j.priority <= m_jobs.back().priority)) + i = m_jobs.rbegin(); std::deque::iterator k = m_jobs.insert(i.base(), j); k->callback.swap(const_cast&>(f)); @@ -311,3 +314,4 @@ namespace libtorrent } } + From 3841ddd3dc8f8f511ef454ae2881e5a0daa1fc02 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 4 Oct 2007 00:07:26 +0000 Subject: [PATCH 0181/1009] moved block_downloading_alert, block_finished_alert and piece_finished_alert to debug level...yay performance --- libtorrent/include/libtorrent/alert_types.hpp | 6 +++--- libtorrent/src/disk_io_thread.cpp | 1 - libtorrent/src/peer_connection.cpp | 4 ++-- libtorrent/src/torrent.cpp | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index bfbfa8ae4..2f1998aae 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -191,7 +191,7 @@ namespace libtorrent const torrent_handle& h , int piece_num , const std::string& msg) - : torrent_alert(h, alert::warning, msg) + : torrent_alert(h, alert::debug, msg) , piece_index(piece_num) { assert(piece_index >= 0);} @@ -208,7 +208,7 @@ namespace libtorrent , int block_num , int piece_num , const std::string& msg) - : torrent_alert(h, alert::warning, msg) + : torrent_alert(h, alert::debug, msg) , block_index(block_num) , piece_index(piece_num) { assert(block_index >= 0 && piece_index >= 0);} @@ -228,7 +228,7 @@ namespace libtorrent , int block_num , int piece_num , const std::string& msg) - : torrent_alert(h, alert::warning, msg) + : torrent_alert(h, alert::debug, msg) , peer_speedmsg(speedmsg) , block_index(block_num) , piece_index(piece_num) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 112e76927..ea177b58d 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -314,4 +314,3 @@ namespace libtorrent } } - diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 209833de5..83f1d32f7 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -1402,7 +1402,7 @@ namespace libtorrent assert(p.start == j.offset); piece_block block_finished(p.piece, p.start / t->block_size()); picker.mark_as_finished(block_finished, peer_info_struct()); - if (t->alerts().should_post(alert::info)) + if (t->alerts().should_post(alert::debug)) { t->alerts().post_alert(block_finished_alert(t->get_handle(), block_finished.block_index, block_finished.piece_index, "block finished")); @@ -1680,7 +1680,7 @@ namespace libtorrent if (!t->picker().mark_as_downloading(block, peer_info_struct(), state)) return; - if (t->alerts().should_post(alert::info)) + if (t->alerts().should_post(alert::debug)) { t->alerts().post_alert(block_downloading_alert(t->get_handle(), speedmsg, block.block_index, block.piece_index, "block downloading")); diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 35abc2c49..33fbe9b40 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -825,7 +825,7 @@ namespace libtorrent if (passed_hash_check) { - if (m_ses.m_alerts.should_post(alert::info)) + if (m_ses.m_alerts.should_post(alert::debug)) { m_ses.m_alerts.post_alert(piece_finished_alert(get_handle() , index, "piece finished")); From 838ef287daff0910e1f70261b04c2d3f3e8d8b59 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 4 Oct 2007 00:09:45 +0000 Subject: [PATCH 0182/1009] accidentaly reverted disk io priority fix, so here it is again --- libtorrent/src/disk_io_thread.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index ea177b58d..112e76927 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -314,3 +314,4 @@ namespace libtorrent } } + From 06e148c2ed75ce32cb4a19c242be3a3c1edc1f1d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 4 Oct 2007 00:43:41 +0000 Subject: [PATCH 0183/1009] Add PluginManagerBase class. Only load plugins that are in the 'enabled_plugins' key in the config files. --- TODO | 2 - deluge/core/alertmanager.py | 2 +- deluge/core/core.py | 3 +- deluge/core/pluginmanager.py | 39 ++---------- deluge/pluginmanagerbase.py | 104 +++++++++++++++++++++++++++++++ deluge/ui/gtkui/gtkui.py | 3 +- deluge/ui/gtkui/pluginmanager.py | 31 ++------- 7 files changed, 119 insertions(+), 65 deletions(-) create mode 100644 deluge/pluginmanagerbase.py diff --git a/TODO b/TODO index e0f3fb893..2a25d946c 100644 --- a/TODO +++ b/TODO @@ -4,8 +4,6 @@ intended functionality. * Add plugin list and the ability to load/unload them from the preferences dialog. -* Have core keep track of which plugins are activated or not, so it only loads - those on start-up. * Have the UI request a list of activated plugins on start-up so it nows which plugins to load. * Figure out easy way for user-made plugins to add i18n support. diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index 898648669..d86f8db11 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -61,7 +61,7 @@ class AlertManager: # Append the handler to the list in the handlers dictionary self.handlers[alert_type].append(handler) - log.debug("Registered handler %s for alert %s", handler, alert_type) + log.debug("Registered handler for alert %s", alert_type) def deregister_handler(self, handler): """De-registers the 'handler' function from all alert types.""" diff --git a/deluge/core/core.py b/deluge/core/core.py index 414fc2444..609fdd778 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -71,7 +71,8 @@ DEFAULT_PREFS = { "max_download_speed": -1.0, "max_upload_slots_global": -1, "max_connections_per_torrent": -1, - "max_upload_slots_per_torrent": -1 + "max_upload_slots_per_torrent": -1, + "enabled_plugins": ["Queue"] } class Core(dbus.service.Object): diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 3196b8d84..cf0827375 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -33,13 +33,10 @@ """PluginManager for Core""" -import os.path - -import pkg_resources - +import deluge.pluginmanagerbase from deluge.log import LOG as log -class PluginManager: +class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): """PluginManager handles the loading of plugins and provides plugins with functions to access parts of the core.""" @@ -52,34 +49,10 @@ class PluginManager: self.status_fields = {} - # This will load any .eggs in the plugins folder inside the main - # deluge egg.. Need to scan the local plugin folder too. - - plugin_dir = os.path.join(os.path.dirname(__file__), "..", "plugins") - - pkg_resources.working_set.add_entry(plugin_dir) - pkg_env = pkg_resources.Environment([plugin_dir]) - - self.plugins = {} - for name in pkg_env: - egg = pkg_env[name][0] - egg.activate() - for name in egg.get_entry_map("deluge.plugin.core"): - entry_point = egg.get_entry_info("deluge.plugin.core", name) - cls = entry_point.load() - instance = cls(self) - self.plugins[name] = instance - log.info("Load plugin %s", name) - - def shutdown(self): - log.debug("PluginManager shutting down..") - for plugin in self.plugins.values(): - plugin.core.shutdown() - del self.plugins - - def __getitem__(self, key): - return self.plugins[key] - + # Call the PluginManagerBase constructor + deluge.pluginmanagerbase.PluginManagerBase.__init__( + self, "core.conf", "deluge.plugin.core") + def register_status_field(self, field, function): """Register a new status field. This can be used in the same way the client requests other status information from core.""" diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py new file mode 100644 index 000000000..f65d7e263 --- /dev/null +++ b/deluge/pluginmanagerbase.py @@ -0,0 +1,104 @@ +# +# pluginmanagerbase.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +"""PluginManagerBase""" + +import os.path + +import pkg_resources + +import deluge.common +from deluge.configmanager import ConfigManager +from deluge.log import LOG as log + +class PluginManagerBase: + """PluginManagerBase is a base class for PluginManagers to inherit""" + + def __init__(self, config_file, entry_name): + log.debug("Plugin manager init..") + + self.config = ConfigManager(config_file) + + # This is the entry we want to load.. + self.entry_name = entry_name + + # Loaded plugins + self.plugins = {} + + # Scan the plugin folders for plugins + self.scan_for_plugins() + + # Load plugins that are enabled in the config. + for name in self.config["enabled_plugins"]: + self.load_plugin(name) + + def shutdown(self): + log.debug("PluginManager shutting down..") + for plugin in self.plugins.values(): + plugin.core.shutdown() + del self.plugins + + def __getitem__(self, key): + return self.plugins[key] + + def get_available_plugins(self): + """Returns a list of the available plugins (name, version)""" + return self.available_plugins + + def scan_for_plugins(self): + """Scans for available plugins""" + plugin_dir = os.path.join(os.path.dirname(__file__), "plugins") + user_plugin_dir = os.path.join(deluge.common.get_config_dir("plugins")) + + pkg_resources.working_set.add_entry(plugin_dir) + pkg_resources.working_set.add_entry(user_plugin_dir) + self.pkg_env = pkg_resources.Environment([plugin_dir, user_plugin_dir]) + + self.available_plugins = [] + for name in self.pkg_env: + pkg_name = str(self.pkg_env[name][0]).split()[0] + pkg_version = str(self.pkg_env[name][0]).split()[1] + + log.debug("Found plugin: %s %s", pkg_name, pkg_version) + self.available_plugins.append(pkg_name) + + def load_plugin(self, name, version=None): + """Loads a plugin with optional version""" + egg = self.pkg_env[name][0] + egg.activate() + for name in egg.get_entry_map(self.entry_name): + entry_point = egg.get_entry_info(self.entry_name, name) + cls = entry_point.load() + instance = cls(self) + self.plugins[name] = instance + log.info("Load plugin %s", name) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 8669c4f5a..5d4ab0e06 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -66,7 +66,8 @@ DEFAULT_PREFS = { "window_height": 480, "window_pane_position": -1, "tray_download_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], - "tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0] + "tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], + "enabled_plugins": ["Queue"] } class GtkUI: diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 64424c920..07cbe1ca2 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -31,39 +31,16 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import os.path - -import pkg_resources - +import deluge.pluginmanagerbase from deluge.log import LOG as log -class PluginManager: +class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): def __init__(self, gtkui): self._gtkui = gtkui - # This will load any .eggs in the plugins folder inside the main - # deluge egg.. Need to scan the local plugin folder too. - - plugin_dir = os.path.join(os.path.dirname(__file__), "../..", "plugins") - - pkg_resources.working_set.add_entry(plugin_dir) - pkg_env = pkg_resources.Environment([plugin_dir]) - - self.plugins = {} - for name in pkg_env: - egg = pkg_env[name][0] - egg.activate() - modules = [] - for name in egg.get_entry_map("deluge.plugin.ui.gtk"): - entry_point = egg.get_entry_info("deluge.plugin.ui.gtk", name) - cls = entry_point.load() - instance = cls(self) - self.plugins[name] = instance - log.info("Loaded plugin %s", name) - - def __getitem__(self, key): - return self.plugins[key] + deluge.pluginmanagerbase.PluginManagerBase.__init__( + self, "gtkui.conf", "deluge.plugin.ui.gtk") def get_torrentview(self): """Returns a reference to the torrentview component""" From 2707afc9c83ec360ee496fc37442580be3ec4358 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 4 Oct 2007 00:53:39 +0000 Subject: [PATCH 0184/1009] Show Queue menu and toolbuttons properly. --- TODO | 1 + deluge/plugins/queue/queue/gtkui.py | 1 + deluge/ui/gtkui/toolbar.py | 3 +++ 3 files changed, 5 insertions(+) diff --git a/TODO b/TODO index 2a25d946c..52628f091 100644 --- a/TODO +++ b/TODO @@ -12,3 +12,4 @@ * Restart daemon function * Sync the details pane to current trunk * Docstrings! +* Change core to use dbus variants instead of byte-arrays diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index f4db6a9f3..30e301d60 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -112,6 +112,7 @@ class GtkUI: queue_image.set_from_stock(gtk.STOCK_SORT_ASCENDING, gtk.ICON_SIZE_MENU) queue_menuitem.set_image(queue_image) queue_menuitem.set_submenu(menu) + queue_menuitem.show_all() self.plugin.get_torrentmenu().append(queue_menuitem) ## Menu callbacks ## diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 95058e125..f975fc9b0 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -75,6 +75,9 @@ class ToolBar: # Append the button to the toolbar self.toolbar.insert(toolbutton, -1) + # Show the new toolbutton + toolbutton.show() + return def add_separator(self, position=None): From 8e96afe626dd46a592b5cb38dbec718ae3d388d8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 4 Oct 2007 01:21:39 +0000 Subject: [PATCH 0185/1009] Fix display of availability. --- deluge/core/torrent.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 2c790b8b9..082abf474 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -115,14 +115,19 @@ class Torrent: state = status.state if status.paused: state = deluge.common.TORRENT_STATE.index("Paused") - + + # Adjust status.distributed_copies to return a non-negative value + distributed_copies = status.distributed_copies + if distributed_copies < 0: + distributed_copies = 0.0 + full_status = { "name": self.handle.torrent_info().name(), "total_size": self.handle.torrent_info().total_size(), "num_files": self.handle.torrent_info().num_files(), "num_pieces": self.handle.torrent_info().num_pieces(), "piece_length": self.handle.torrent_info().piece_length(), - "distributed_copies": status.distributed_copies, + "distributed_copies": distributed_copies, "total_done": status.total_done, "total_uploaded": self.total_uploaded + status.total_payload_upload, "state": int(state), From 7f88289f1f4196acac7a1c1fac9aa823221acf62 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 4 Oct 2007 15:31:23 +0000 Subject: [PATCH 0186/1009] lt 1643 --- libtorrent/src/bt_peer_connection.cpp | 4 ++++ libtorrent/src/disk_io_thread.cpp | 1 - libtorrent/src/peer_connection.cpp | 8 ++++++++ libtorrent/src/session_impl.cpp | 5 ----- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 71407d37f..56d9307a9 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -202,6 +202,10 @@ namespace libtorrent // completes correctly pi->pe_support = false; + // if this fails, we need to reconnect + // fast. + pi->connected = time_now() - seconds(m_ses.settings().min_reconnect_time); + write_pe1_2_dhkey(); m_state = read_pe_dhkey; reset_recv_buffer(dh_key_len); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 112e76927..ea177b58d 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -314,4 +314,3 @@ namespace libtorrent } } - diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 83f1d32f7..327bb2074 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -739,6 +739,14 @@ namespace libtorrent m_allowed_fast.begin(), m_allowed_fast.end(), r.piece); if (i != m_allowed_fast.end()) m_allowed_fast.erase(i); } + else + { + std::vector::iterator i = std::find(m_suggested_pieces.begin() + , m_suggested_pieces.end(), r.piece); + if (i != m_suggested_pieces.end()) + m_suggested_pieces.erase(i); + } + if (m_request_queue.empty()) { if (m_download_queue.size() < 2) diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 2eed1c328..5734225e0 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -1597,11 +1597,6 @@ namespace detail { assert(!save_path.empty()); - // if you get this assert, you haven't managed to - // open a listen port. call listen_on() first. - if (m_listen_sockets.empty()) - throw std::runtime_error("no listen socket opened"); - if (ti->begin_files() == ti->end_files()) throw std::runtime_error("no files in torrent"); From 8a0692fa4fcaa9fb9e5bcf3d938de09a741573d5 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 4 Oct 2007 21:14:32 +0000 Subject: [PATCH 0187/1009] fix storage of banned peers lt 1645 --- libtorrent/include/libtorrent/intrusive_ptr_base.hpp | 2 ++ libtorrent/src/session_impl.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index 4d3c5b855..b78aea7b0 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -45,6 +45,8 @@ namespace libtorrent intrusive_ptr_base(intrusive_ptr_base const&) : m_refs(0) {} + intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) {} + friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) { assert(s->m_refs >= 0); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 5734225e0..4001b35d7 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -391,6 +391,14 @@ namespace detail processing->torrent_ptr->get_policy().peer_from_tracker(*i, id , peer_info::resume_data, 0); } + + for (std::vector::const_iterator i = processing->banned_peers.begin(); + i != processing->banned_peers.end(); ++i) + { + policy::peer* p = processing->torrent_ptr->get_policy().peer_from_tracker(*i, id + , peer_info::resume_data, 0); + if (p) p->banned = true; + } } else { From 17f62130fe7d0924317d676b42ed470f79b052f2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 4 Oct 2007 22:21:47 +0000 Subject: [PATCH 0188/1009] Add 'save_path' to add_torrent(). Save the 'save_path' in the torrent state. --- deluge/core/core.py | 16 +- deluge/core/torrent.py | 7 +- deluge/core/torrentmanager.py | 19 +- deluge/ui/functions.py | 4 +- deluge/ui/gtkui/glade/main_window.glade | 1078 ++++++++++++----------- 5 files changed, 579 insertions(+), 545 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 609fdd778..109e96b71 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -182,12 +182,16 @@ class Core(dbus.service.Object): gobject.idle_add(self._shutdown) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="say", out_signature="b") - def add_torrent_file(self, filename, filedump): + in_signature="ssay", out_signature="b") + def add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content """ - torrent_id = self.torrents.add(filename, filedump) + if save_path == "": + save_path = None + + torrent_id = self.torrents.add(filename, filedump=filedump, + save_path=save_path) # Run the plugin hooks for 'post_torrent_add' self.plugins.run_post_torrent_add(torrent_id) @@ -201,8 +205,8 @@ class Core(dbus.service.Object): return False @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="b") - def add_torrent_url(self, url): + in_signature="ss", out_signature="b") + def add_torrent_url(self, url, save_path): log.info("Attempting to add url %s", url) # Get the actual filename of the torrent from the url provided. @@ -221,7 +225,7 @@ class Core(dbus.service.Object): return False # Add the torrent to session - return self.add_torrent_file(filename, filedump) + return self.add_torrent_file(filename, save_path, filedump) @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", out_signature="") diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 082abf474..2a248650b 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -38,7 +38,7 @@ import deluge.common class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ - def __init__(self, filename, handle, compact): + def __init__(self, filename, handle, compact, save_path): # Set the filename self.filename = filename # Set the libtorrent handle @@ -49,6 +49,8 @@ class Torrent: self.total_uploaded = 0 # Set the allocation mode self.compact = compact + # Where the torrent is being saved to + self.save_path = save_path # The tracker status self.tracker_status = "" @@ -59,7 +61,8 @@ class Torrent: def get_state(self): """Returns the state of this torrent for saving to the session state""" status = self.handle.status() - return (self.torrent_id, self.filename, self.compact, status.paused) + return (self.torrent_id, self.filename, self.compact, status.paused, + self.save_path) def get_eta(self): """Returns the ETA in seconds for this torrent""" diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 04dbbdd17..d61057e32 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -47,11 +47,12 @@ from deluge.core.torrent import Torrent from deluge.log import LOG as log class TorrentState: - def __init__(self, torrent_id, filename, compact, paused): + def __init__(self, torrent_id, filename, compact, paused, save_path): self.torrent_id = torrent_id self.filename = filename self.compact = compact self.paused = paused + self.save_path = save_path class TorrentManagerState: def __init__(self): @@ -117,7 +118,8 @@ class TorrentManager: """Returns a list of torrent_ids""" return self.torrents.keys() - def add(self, filename, filedump=None, compact=None, paused=False): + def add(self, filename, filedump=None, compact=None, paused=False, + save_path=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) @@ -160,6 +162,10 @@ class TorrentManager: torrent_filedump = lt.bdecode(filedump) handle = None + # Make sure we have a valid download_location + if save_path is None: + save_path = self.config["download_location"] + # Make sure we are adding it with the correct allocation method. if compact is None: compact = self.config["compact_allocation"] @@ -167,7 +173,7 @@ class TorrentManager: try: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), - self.config["download_location"], + save_path, resume_data=fastresume, compact_mode=compact, paused=paused) @@ -179,7 +185,8 @@ class TorrentManager: return None # Create a Torrent object - torrent = Torrent(filename, handle, compact) + torrent = Torrent(filename, handle, compact, + save_path) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent @@ -187,7 +194,7 @@ class TorrentManager: handle.set_max_connections(self.max_connections) handle.set_max_uploads(self.max_uploads) - log.debug("Attemping to save torrent file: %s", filename) + log.debug("Attempting to save torrent file: %s", filename) # Test if the torrentfiles_location is accessible if os.access( os.path.join(self.config["torrentfiles_location"]), os.F_OK) \ @@ -303,7 +310,7 @@ class TorrentManager: # Try to add the torrents in the state to the session for torrent_state in state.torrents: self.add(torrent_state.filename, compact=torrent_state.compact, - paused=torrent_state.paused) + paused=torrent_state.paused, save_path=torrent_state.save_path) def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 3c894f67e..1bd5d0a5f 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -85,7 +85,7 @@ def add_torrent_file(torrent_files): f = open(torrent_file, "rb") # Get the filename because the core doesn't want a path. (path, filename) = os.path.split(torrent_file) - result = core.add_torrent_file(filename, f.read()) + result = core.add_torrent_file(filename, str(), f.read()) f.close() if result is False: # The torrent was not added successfully. @@ -96,7 +96,7 @@ def add_torrent_url(torrent_url): core = get_core() from deluge.common import is_url if is_url(torrent_url): - result = core.add_torrent_url(torrent_url) + result = core.add_torrent_url(torrent_url, str()) if result is False: # The torrent url was not added successfully. log.warning("Torrent %s was not added successfully.", torrent_url) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 80b78d6b8..6e9aaca04 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -348,6 +348,286 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -380,277 +660,18 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - + True 0 + True + PANGO_WRAP_WORD_CHAR 3 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 4 5 + @@ -677,18 +698,277 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 - 4 - 5 - + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 @@ -718,266 +998,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 6 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - From c2f7c362b3131c2f840d0f34d9feecc98905eccf Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 4 Oct 2007 23:54:58 +0000 Subject: [PATCH 0189/1009] Use dbus variants instead of pickling objects. --- TODO | 1 - deluge/core/core.py | 41 ++++++++++++++--------------------------- deluge/ui/functions.py | 26 +++++--------------------- 3 files changed, 19 insertions(+), 49 deletions(-) diff --git a/TODO b/TODO index 52628f091..2a25d946c 100644 --- a/TODO +++ b/TODO @@ -12,4 +12,3 @@ * Restart daemon function * Sync the details pane to current trunk * Docstrings! -* Change core to use dbus variants instead of byte-arrays diff --git a/deluge/core/core.py b/deluge/core/core.py index 109e96b71..9d6926d03 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -272,38 +272,30 @@ class Core(dbus.service.Object): @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="sas", - out_signature="ay") + out_signature="a{sv}") def get_torrent_status(self, torrent_id, keys): # Convert the array of strings to a python list of strings - nkeys = [] - for key in keys: - nkeys.append(str(key)) - # Pickle the status dictionary from the torrent + keys = deluge.common.pythonize(keys) + # Build the status dictionary try: - status = self.torrents[torrent_id].get_status(nkeys) + status = self.torrents[torrent_id].get_status(keys) except KeyError: # The torrent_id is not found in the torrentmanager, so return None - status = None - status = pickle.dumps(status) - return status + return None # Get the leftover fields and ask the plugin manager to fill them - leftover_fields = list(set(nkeys) - set(status.keys())) + leftover_fields = list(set(keys) - set(status.keys())) if len(leftover_fields) > 0: status.update(self.plugins.get_status(torrent_id, leftover_fields)) - status = pickle.dumps(status) return status @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="", - out_signature="ay") + out_signature="as") def get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager - torrent_list = self.torrents.get_torrent_list() - # Pickle the list and send it - session_state = pickle.dumps(torrent_list) - return session_state + return self.torrents.get_torrent_list() @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") def save_state(self): @@ -313,33 +305,28 @@ class Core(dbus.service.Object): @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="", - out_signature="ay") + out_signature="a{sv}") def get_config(self): """Get all the preferences as a dictionary""" - config = self.config.get_config() - config = pickle.dumps(config) - return config + return self.config.get_config() @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", in_signature="s", - out_signature="ay") + out_signature="v") def get_config_value(self, key): """Get the config value for key""" try: value = self.config[key] except KeyError: return None - - value = pickle.dumps(value) + return value @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="ay") + in_signature="a{sv}") def set_config(self, config): """Set the config with values from dictionary""" - # Convert the byte array into the dictionary - config = "".join(chr(b) for b in config) - config = pickle.loads(config) + config = deluge.common.pythonize(config) # Load all the values into the configuration for key in config.keys(): self.config[key] = config[key] diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 1bd5d0a5f..d8c45f87f 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -42,6 +42,7 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade +import deluge.common from deluge.log import LOG as log def get_core(): @@ -130,46 +131,29 @@ def force_reannounce(torrent_ids): def get_torrent_status(core, torrent_id, keys): """Builds the status dictionary and returns it""" - status = core.get_torrent_status(torrent_id, keys) - # Join the array of bytes into a string for pickle to read - status = "".join(chr(b) for b in status) - # De-serialize the object - status = pickle.loads(status) - return status + return deluge.common.pythonize(core.get_torrent_status(torrent_id, keys)) def get_session_state(core=None): # Get the core if not supplied if core is None: core = get_core() - state = core.get_session_state() - # Join the array of bytes into a string for pickle to read - state = "".join(chr(b) for b in state) - # De-serialize the object - state = pickle.loads(state) - return state + return deluge.common.pythonize(core.get_session_state()) def get_config(core=None): if core is None: core = get_core() - config = core.get_config() - config = "".join(chr(b) for b in config) - config = pickle.loads(config) - return config + return deluge.common.pythonize(core.get_config()) def get_config_value(key, core=None): if core is None: core = get_core() - config = core.get_config_value(key) - config = "".join(chr(b) for b in config) - config = pickle.loads(config) - return config + return deluge.common.pythonize(core.get_config_value(key)) def set_config(config, core=None): if config == {}: return if core is None: core = get_core() - config = pickle.dumps(config) core.set_config(config) def get_listen_port(core=None): From cc7d7e8c621a43f6c0e21dc4c979a98a41b15e02 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 5 Oct 2007 00:30:19 +0000 Subject: [PATCH 0190/1009] Show save_path in TorrentDetails. --- TODO | 1 - deluge/core/torrent.py | 3 +- deluge/ui/gtkui/glade/main_window.glade | 1101 ++++++++++++----------- deluge/ui/gtkui/torrentdetails.py | 4 +- 4 files changed, 556 insertions(+), 553 deletions(-) diff --git a/TODO b/TODO index 2a25d946c..6b85e7f8d 100644 --- a/TODO +++ b/TODO @@ -10,5 +10,4 @@ * Change the menubar.py gtkui component to menus.py and add support for plugins to add menuitems to the torrentmenu in an easy way. * Restart daemon function -* Sync the details pane to current trunk * Docstrings! diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 2a248650b..755a7673c 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -149,7 +149,8 @@ class Torrent: "eta": self.get_eta(), "ratio": self.get_ratio(), "tracker": status.current_tracker, - "tracker_status": self.tracker_status + "tracker_status": self.tracker_status, + "save_path": self.save_path } # Create the desired status dictionary and return it diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 6e9aaca04..abbfc68fc 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -348,286 +348,6 @@ 1 2 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - True @@ -660,18 +380,277 @@ 4 5 - + + True + 0 + + + 1 + 2 + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 4 5 - @@ -698,277 +677,18 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - + 0 + True + PANGO_WRAP_WORD_CHAR - 1 - 2 + 3 + 4 4 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 + @@ -998,6 +718,287 @@ GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 507998830..40a97e0b9 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -71,6 +71,7 @@ class TorrentDetails: self.tracker_status = glade.get_widget("summary_tracker_status") self.next_announce = glade.get_widget("summary_next_announce") self.eta = glade.get_widget("summary_eta") + self.torrent_path = glade.get_widget("summary_torrent_path") def update(self): # Only update if this page is showing @@ -93,7 +94,7 @@ class TorrentDetails: "total_payload_upload", "download_payload_rate", "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", - "tracker_status"] + "tracker_status", "save_path"] status = functions.get_torrent_status(self.core, selected, status_keys) @@ -133,6 +134,7 @@ class TorrentDetails: self.tracker_status.set_text(status["tracker_status"]) self.next_announce.set_text( deluge.common.ftime(status["next_announce"])) + self.torrent_path.set_text(status["save_path"]) def clear(self): # Only update if this page is showing From 84cd2da8dd467c9de496febbd4692e624306e9a7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 5 Oct 2007 00:58:26 +0000 Subject: [PATCH 0191/1009] Implement plugin management api in the core. --- deluge/core/core.py | 25 +++++++++++++++++++++++-- deluge/pluginmanagerbase.py | 27 ++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 9d6926d03..6e46be79e 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -349,13 +349,34 @@ class Core(dbus.service.Object): """Returns the payload download rate""" return self.session.status().payload_download_rate - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", out_signature="d") def get_upload_rate(self): """Returns the payload upload rate""" return self.session.status().payload_upload_rate - + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + out_signature="as") + def get_available_plugins(self): + """Returns a list of plugins available in the core""" + return self.plugins.get_available_plugins() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + out_signature="as") + def get_enabled_plugins(self): + """Returns a list of enabled plugins in the core""" + return self.plugins.get_enabled_plugins() + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s") + def enable_plugin(self, plugin): + self.plugins.enable_plugin(plugin) + + @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", + in_signature="s") + def disable_plugin(self, plugin): + self.plugins.disable_plugin(plugin) + # Signals @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", signature="s") diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index f65d7e263..2ed1b79f0 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -60,7 +60,7 @@ class PluginManagerBase: # Load plugins that are enabled in the config. for name in self.config["enabled_plugins"]: - self.load_plugin(name) + self.enable_plugin(name) def shutdown(self): log.debug("PluginManager shutting down..") @@ -72,9 +72,13 @@ class PluginManagerBase: return self.plugins[key] def get_available_plugins(self): - """Returns a list of the available plugins (name, version)""" + """Returns a list of the available plugins name""" return self.available_plugins + def get_enabled_plugins(self): + """Returns a list of enabled plugins""" + return self.plugins.key() + def scan_for_plugins(self): """Scans for available plugins""" plugin_dir = os.path.join(os.path.dirname(__file__), "plugins") @@ -92,8 +96,12 @@ class PluginManagerBase: log.debug("Found plugin: %s %s", pkg_name, pkg_version) self.available_plugins.append(pkg_name) - def load_plugin(self, name, version=None): - """Loads a plugin with optional version""" + def enable_plugin(self, name): + """Enables a plugin""" + if name not in self.available_plugins: + log.warning("Cannot enable non-existant plugin %s", name) + return + egg = self.pkg_env[name][0] egg.activate() for name in egg.get_entry_map(self.entry_name): @@ -101,4 +109,13 @@ class PluginManagerBase: cls = entry_point.load() instance = cls(self) self.plugins[name] = instance - log.info("Load plugin %s", name) + log.info("Plugin %s enabled..", name) + + def disable_plugin(self, name): + """Disables a plugin""" + try: + del self.plugins[name] + except: + log.warning("Unable to disable non-existant plugin %s", name) + + log.info("Plugin %s disabled..", name) From d240ba3c01dca1606d31282b7150b2a668a84aac Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 5 Oct 2007 05:05:07 +0000 Subject: [PATCH 0192/1009] Preferences plugin tab stuff. --- deluge/pluginmanagerbase.py | 2 +- deluge/ui/functions.py | 10 + .../ui/gtkui/glade/preferences_dialog.glade | 226 +++++++++--------- deluge/ui/gtkui/listview.py | 6 +- deluge/ui/gtkui/mainwindow.py | 6 +- deluge/ui/gtkui/preferences.py | 42 +++- deluge/ui/gtkui/systemtray.py | 1 - 7 files changed, 171 insertions(+), 122 deletions(-) diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 2ed1b79f0..bce76b70b 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -77,7 +77,7 @@ class PluginManagerBase: def get_enabled_plugins(self): """Returns a list of enabled plugins""" - return self.plugins.key() + return self.plugins.keys() def scan_for_plugins(self): """Scans for available plugins""" diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index d8c45f87f..fb6b5dcfc 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -161,6 +161,16 @@ def get_listen_port(core=None): core = get_core() return int(core.get_listen_port()) +def get_available_plugins(core=None): + if core is None: + core = get_core() + return deluge.common.pythonize(core.get_available_plugins()) + +def get_enabled_plugins(core=None): + if core is None: + core = get_core() + return deluge.common.pythonize(core.get_enabled_plugins()) + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 8563d26e6..6b130df42 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,13 +1,13 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Deluge Preferences GTK_WIN_POS_CENTER_ON_PARENT - 500 + 510 520 True GDK_WINDOW_TYPE_HINT_DIALOG @@ -883,71 +883,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -972,43 +941,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1052,29 +1052,24 @@ Either 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1092,19 +1087,24 @@ Either - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1357,15 +1357,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1395,38 +1413,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index e8cfdc701..1f80953cc 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -297,17 +297,15 @@ class ListView: column.set_cell_data_func(render, function, tuple(self.columns[header].column_indices)) elif column_type == "texticon": - column.pack_start(render[pixbuf]) + column.pack_start(render[pixbuf], False) if function is not None: column.set_cell_data_func(render[pixbuf], function, self.columns[header].column_indices[pixbuf]) - column.pack_start(render[text]) + column.pack_start(render[text], True) column.add_attribute(render[text], "text", self.columns[header].column_indices[text]) elif column_type == None: return - - column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 46c40420a..5d882cbb4 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -61,7 +61,7 @@ class MainWindow: self.window.set_icon(deluge.common.get_logo(32)) self.vpaned = self.main_glade.get_widget("vpaned") # Load the window state - self.load_window_geometry() + self.load_window_state() # Keep track of window's minimization state so that we don't update the # UI when it is minimized. @@ -101,6 +101,8 @@ class MainWindow: return True def show(self): + # Load the state prior to showing + self.load_window_state() self.window.show() def hide(self): @@ -130,7 +132,7 @@ class MainWindow: self.hide() gtk.main_quit() - def load_window_geometry(self): + def load_window_state(self): x = self.config["window_x_pos"] y = self.config["window_y_pos"] w = self.config["window_width"] diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 23574360c..ef9fee42b 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -66,11 +66,26 @@ class Preferences: self.liststore.append([i, category]) i += 1 + # Setup plugin tab listview + self.plugin_liststore = gtk.ListStore(str, bool) + self.plugin_listview = self.glade.get_widget("plugin_listview") + self.plugin_listview.set_model(self.plugin_liststore) + render = gtk.CellRendererToggle() + render.connect("toggled", self.on_plugin_toggled) + render.set_property("activatable", True) + self.plugin_listview.append_column( + gtk.TreeViewColumn(_("Enabled"), render, active=1)) + self.plugin_listview.append_column( + gtk.TreeViewColumn(_("Plugin"), gtk.CellRendererText(), text=0)) + # Connect to the 'changed' event of TreeViewSelection to get selection # changes. self.treeview.get_selection().connect("changed", self.on_selection_changed) + self.plugin_listview.get_selection().connect("changed", + self.on_plugin_selection_changed) + self.glade.signal_autoconnect({ "on_pref_dialog_delete_event": self.on_pref_dialog_delete_event, "on_button_ok_clicked": self.on_button_ok_clicked, @@ -177,6 +192,21 @@ class Preferences: self.glade.get_widget("chk_send_info").set_active( self.gtkui_config["send_info"]) + ## Plugins tab ## + all_plugins = functions.get_available_plugins() + enabled_plugins = functions.get_enabled_plugins() + # Clear the existing list so we don't duplicate entries. + self.plugin_liststore.clear() + # Iterate through the lists and add them to the liststore + for plugin in all_plugins: + if plugin in enabled_plugins: + enabled = True + else: + enabled = False + row = self.plugin_liststore.append() + self.plugin_liststore.set_value(row, 0, plugin) + self.plugin_liststore.set_value(row, 1, enabled) + # Now show the dialog self.pref_dialog.show() @@ -359,4 +389,14 @@ class Preferences: url = "http://deluge-torrent.org/test-port.php?port=%s" % \ functions.get_listen_port(self.core) functions.open_url_in_browser(url) - + + def on_plugin_toggled(self, renderer, path): + log.debug("on_plugin_toggled") + row = self.plugin_liststore.get_iter_from_string(path) + name = self.plugin_liststore.get_value(row, 0) + value = self.plugin_liststore.get_value(row, 1) + self.plugin_liststore.set_value(row, 1, not value) + + def on_plugin_selection_changed(self, treeselection): + log.debug("on_plugin_selection_changed") + diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 4e78b3df1..3b1bd64d5 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -134,7 +134,6 @@ class SystemTray: if self.config["lock_tray"] == True: self.unlock_tray("mainwinshow") else: - self.window.load_window_geometry() self.window.show() # Force UI update as we don't update it while in tray self.window.update() From 94fd3c0f379ffb85afa133682f90389d0a8b530c Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 6 Oct 2007 04:22:06 +0000 Subject: [PATCH 0193/1009] lt sync 1649 --- libtorrent/include/libtorrent/alert_types.hpp | 8 +- libtorrent/include/libtorrent/assert.hpp | 22 +- .../include/libtorrent/aux_/session_impl.hpp | 4 +- .../include/libtorrent/bandwidth_manager.hpp | 46 +- libtorrent/include/libtorrent/bencode.hpp | 10 +- .../include/libtorrent/bt_peer_connection.hpp | 6 +- libtorrent/include/libtorrent/buffer.hpp | 20 +- .../include/libtorrent/chained_buffer.hpp | 24 +- libtorrent/include/libtorrent/fingerprint.hpp | 14 +- libtorrent/include/libtorrent/hasher.hpp | 12 +- .../include/libtorrent/http_connection.hpp | 2 +- .../include/libtorrent/intrusive_ptr_base.hpp | 8 +- .../include/libtorrent/invariant_check.hpp | 4 +- libtorrent/include/libtorrent/ip_filter.hpp | 22 +- .../libtorrent/kademlia/routing_table.hpp | 6 +- .../include/libtorrent/peer_connection.hpp | 12 +- libtorrent/include/libtorrent/peer_id.hpp | 6 +- .../include/libtorrent/piece_picker.hpp | 12 +- .../include/libtorrent/random_sample.hpp | 2 +- .../include/libtorrent/session_impl.hpp | 594 ++++++++++++++++++ libtorrent/include/libtorrent/stat.hpp | 28 +- libtorrent/include/libtorrent/time.hpp | 12 +- libtorrent/include/libtorrent/torrent.hpp | 30 +- .../include/libtorrent/torrent_handle.hpp | 4 +- .../include/libtorrent/torrent_info.hpp | 24 +- .../include/libtorrent/variant_stream.hpp | 42 +- .../libtorrent/web_peer_connection.hpp | 2 +- libtorrent/include/libtorrent/xml_parse.hpp | 4 +- libtorrent/src/alert.cpp | 2 +- libtorrent/src/assert.cpp | 4 + libtorrent/src/broadcast_socket.cpp | 2 +- libtorrent/src/bt_peer_connection.cpp | 285 ++++----- libtorrent/src/connection_queue.cpp | 8 +- libtorrent/src/disk_io_thread.cpp | 10 +- libtorrent/src/entry.cpp | 12 +- libtorrent/src/escape_string.cpp | 8 +- libtorrent/src/file.cpp | 21 +- libtorrent/src/file_pool.cpp | 16 +- libtorrent/src/file_win.cpp | 27 +- libtorrent/src/http_connection.cpp | 8 +- libtorrent/src/http_tracker_connection.cpp | 24 +- libtorrent/src/identify_client.cpp | 2 +- libtorrent/src/ip_filter.cpp | 8 +- libtorrent/src/kademlia/closest_nodes.cpp | 2 +- libtorrent/src/kademlia/dht_tracker.cpp | 34 +- libtorrent/src/kademlia/find_data.cpp | 2 +- libtorrent/src/kademlia/node.cpp | 8 +- libtorrent/src/kademlia/node_id.cpp | 2 +- libtorrent/src/kademlia/routing_table.cpp | 36 +- libtorrent/src/kademlia/rpc_manager.cpp | 42 +- .../src/kademlia/traversal_algorithm.cpp | 6 +- libtorrent/src/metadata_transfer.cpp | 62 +- libtorrent/src/natpmp.cpp | 4 +- libtorrent/src/pe_crypto.cpp | 4 +- libtorrent/src/peer_connection.cpp | 260 ++++---- libtorrent/src/piece_picker.cpp | 432 ++++++------- libtorrent/src/policy.cpp | 148 ++--- libtorrent/src/session.cpp | 14 +- libtorrent/src/session_impl.cpp | 162 ++--- libtorrent/src/socks5_stream.cpp | 2 +- libtorrent/src/storage.cpp | 312 ++++----- libtorrent/src/torrent.cpp | 296 ++++----- libtorrent/src/torrent_handle.cpp | 118 ++-- libtorrent/src/torrent_info.cpp | 48 +- libtorrent/src/tracker_manager.cpp | 12 +- libtorrent/src/udp_tracker_connection.cpp | 2 +- libtorrent/src/upnp.cpp | 8 +- libtorrent/src/ut_pex.cpp | 4 +- libtorrent/src/web_peer_connection.cpp | 48 +- 69 files changed, 2048 insertions(+), 1437 deletions(-) diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 2f1998aae..a46eb7817 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -115,7 +115,7 @@ namespace libtorrent , std::string const& msg) : torrent_alert(h, alert::info, msg) , piece_index(index) - { assert(index >= 0);} + { TORRENT_ASSERT(index >= 0);} virtual std::auto_ptr clone() const { return std::auto_ptr(new hash_failed_alert(*this)); } @@ -193,7 +193,7 @@ namespace libtorrent , const std::string& msg) : torrent_alert(h, alert::debug, msg) , piece_index(piece_num) - { assert(piece_index >= 0);} + { TORRENT_ASSERT(piece_index >= 0);} int piece_index; @@ -211,7 +211,7 @@ namespace libtorrent : torrent_alert(h, alert::debug, msg) , block_index(block_num) , piece_index(piece_num) - { assert(block_index >= 0 && piece_index >= 0);} + { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0);} int block_index; int piece_index; @@ -232,7 +232,7 @@ namespace libtorrent , peer_speedmsg(speedmsg) , block_index(block_num) , piece_index(piece_num) - { assert(block_index >= 0 && piece_index >= 0);} + { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0);} std::string peer_speedmsg; int block_index; diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp index 6577acc46..246e3b51b 100644 --- a/libtorrent/include/libtorrent/assert.hpp +++ b/libtorrent/include/libtorrent/assert.hpp @@ -30,25 +30,19 @@ POSSIBILITY OF SUCH DAMAGE. */ -#include - -#ifndef NDEBUG -#if (defined __linux__ || defined __MACH__) && defined __GNUC__ -#ifdef assert -#undef assert -#endif +#ifndef TORRENT_ASSERT #include "libtorrent/config.hpp" +#include + +#if (defined __linux__ || defined __MACH__) && defined __GNUC__ && !defined(NDEBUG) TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file, char const* function); - -#define assert(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) - -#endif +#define TORRENT_ASSERT(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) #else -#ifndef assert -#define assert(x) (void) -#endif +#define TORRENT_ASSERT(x) assert(x) +#endif + #endif diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 6b76b43f9..d069d73e3 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -297,7 +297,7 @@ namespace libtorrent void unchoke_peer(peer_connection& c) { torrent* t = c.associated_torrent().lock().get(); - assert(t); + TORRENT_ASSERT(t); if (t->unchoke_peer(c)) ++m_num_unchoked; } @@ -345,7 +345,7 @@ namespace libtorrent send_buffer_capacity += i->second->send_buffer_capacity(); used_send_buffer += i->second->send_buffer_size(); } - assert(send_buffer_capacity >= used_send_buffer); + TORRENT_ASSERT(send_buffer_capacity >= used_send_buffer); m_buffer_usage_logger << log_time() << " send_buffer_size: " << send_buffer_capacity << std::endl; m_buffer_usage_logger << log_time() << " used_send_buffer: " << used_send_buffer << std::endl; m_buffer_usage_logger << log_time() << " send_buffer_utilization: " diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 38aa67f43..83df5b371 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -111,14 +111,14 @@ struct bandwidth_limit void assign(int amount) throw() { - assert(amount > 0); + TORRENT_ASSERT(amount > 0); m_current_rate += amount; m_quota_left += amount; } void use_quota(int amount) throw() { - assert(amount <= m_quota_left); + TORRENT_ASSERT(amount <= m_quota_left); m_quota_left -= amount; } @@ -129,7 +129,7 @@ struct bandwidth_limit void expire(int amount) throw() { - assert(amount >= 0); + TORRENT_ASSERT(amount >= 0); m_current_rate -= amount; } @@ -165,7 +165,7 @@ private: template T clamp(T val, T ceiling, T floor) throw() { - assert(ceiling >= floor); + TORRENT_ASSERT(ceiling >= floor); if (val >= ceiling) return ceiling; else if (val <= floor) return floor; return val; @@ -186,7 +186,7 @@ struct bandwidth_manager void throttle(int limit) throw() { mutex_t::scoped_lock l(m_mutex); - assert(limit >= 0); + TORRENT_ASSERT(limit >= 0); m_limit = limit; } @@ -204,9 +204,9 @@ struct bandwidth_manager , bool non_prioritized) throw() { INVARIANT_CHECK; - assert(blk > 0); + TORRENT_ASSERT(blk > 0); - assert(!peer->ignore_bandwidth_limits()); + TORRENT_ASSERT(!peer->ignore_bandwidth_limits()); // make sure this peer isn't already in line // waiting for bandwidth @@ -214,11 +214,11 @@ struct bandwidth_manager for (typename queue_t::iterator i = m_queue.begin() , end(m_queue.end()); i != end; ++i) { - assert(i->peer < peer || peer < i->peer); + TORRENT_ASSERT(i->peer < peer || peer < i->peer); } #endif - assert(peer->max_assignable_bandwidth(m_channel) > 0); + TORRENT_ASSERT(peer->max_assignable_bandwidth(m_channel) > 0); boost::shared_ptr t = peer->associated_torrent().lock(); m_queue.push_back(bw_queue_entry(peer, blk, non_prioritized)); if (!non_prioritized) @@ -257,7 +257,7 @@ struct bandwidth_manager current_quota += i->amount; } - assert(current_quota == m_current_quota); + TORRENT_ASSERT(current_quota == m_current_quota); } #endif @@ -279,7 +279,7 @@ private: m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); #ifndef NDEBUG } - catch (std::exception&) { assert(false); } + catch (std::exception&) { TORRENT_ASSERT(false); } #endif } @@ -292,7 +292,7 @@ private: if (e) return; - assert(!m_history.empty()); + TORRENT_ASSERT(!m_history.empty()); ptime now(time_now()); while (!m_history.empty() && m_history.back().expires_at <= now) @@ -300,7 +300,7 @@ private: history_entry e = m_history.back(); m_history.pop_back(); m_current_quota -= e.amount; - assert(m_current_quota >= 0); + TORRENT_ASSERT(m_current_quota >= 0); intrusive_ptr c = e.peer; shared_ptr t = e.tor.lock(); if (!c->is_disconnecting()) c->expire_bandwidth(m_channel, e.amount); @@ -322,7 +322,7 @@ private: } catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); } #endif } @@ -356,9 +356,9 @@ private: while (!m_queue.empty() && amount > 0) { - assert(amount == limit - m_current_quota); + TORRENT_ASSERT(amount == limit - m_current_quota); bw_queue_entry qe = m_queue.front(); - assert(qe.max_block_size > 0); + TORRENT_ASSERT(qe.max_block_size > 0); m_queue.pop_front(); shared_ptr t = qe.peer->associated_torrent().lock(); @@ -366,7 +366,7 @@ private: if (qe.peer->is_disconnecting()) { t->expire_bandwidth(m_channel, qe.max_block_size); - assert(amount == limit - m_current_quota); + TORRENT_ASSERT(amount == limit - m_current_quota); continue; } @@ -380,7 +380,7 @@ private: if (max_assignable == 0) { t->expire_bandwidth(m_channel, qe.max_block_size); - assert(amount == limit - m_current_quota); + TORRENT_ASSERT(amount == limit - m_current_quota); continue; } @@ -434,20 +434,20 @@ private: // than the max_bandwidth_block_size int hand_out_amount = (std::min)((std::min)(block_size, max_assignable) , amount); - assert(hand_out_amount > 0); - assert(amount == limit - m_current_quota); + TORRENT_ASSERT(hand_out_amount > 0); + TORRENT_ASSERT(amount == limit - m_current_quota); amount -= hand_out_amount; - assert(hand_out_amount <= qe.max_block_size); + TORRENT_ASSERT(hand_out_amount <= qe.max_block_size); t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); qe.peer->assign_bandwidth(m_channel, hand_out_amount); add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); - assert(amount == limit - m_current_quota); + TORRENT_ASSERT(amount == limit - m_current_quota); } #ifndef NDEBUG } catch (std::exception& e) - { assert(false); }; + { TORRENT_ASSERT(false); }; #endif m_in_hand_out_bandwidth = false; } diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index 9e670c10b..66da191ab 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -151,7 +151,7 @@ namespace libtorrent template void read_string(InIt& in, InIt end, int len, std::string& str) { - assert(len >= 0); + TORRENT_ASSERT(len >= 0); for (int i = 0; i < len; ++i) { if (in == end) throw invalid_encoding(); @@ -214,7 +214,7 @@ namespace libtorrent { ++in; // 'i' std::string val = read_until(in, end, 'e'); - assert(*in == 'e'); + TORRENT_ASSERT(*in == 'e'); ++in; // 'e' ret = entry(entry::int_t); ret.integer() = boost::lexical_cast(val); @@ -233,7 +233,7 @@ namespace libtorrent bdecode_recursive(in, end, e); if (in == end) throw invalid_encoding(); } - assert(*in == 'e'); + TORRENT_ASSERT(*in == 'e'); ++in; // 'e' } break; @@ -251,7 +251,7 @@ namespace libtorrent bdecode_recursive(in, end, e); if (in == end) throw invalid_encoding(); } - assert(*in == 'e'); + TORRENT_ASSERT(*in == 'e'); ++in; // 'e' } break; @@ -261,7 +261,7 @@ namespace libtorrent if (isdigit((unsigned char)*in)) { std::string len_s = read_until(in, end, ':'); - assert(*in == ':'); + TORRENT_ASSERT(*in == ':'); ++in; // ':' int len = std::atoi(len_s.c_str()); ret = entry(entry::string_t); diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index 53e9667fc..dc5237a7d 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -342,8 +342,8 @@ namespace libtorrent : start(s) , length(l) { - assert(s >= 0); - assert(l > 0); + TORRENT_ASSERT(s >= 0); + TORRENT_ASSERT(l > 0); } int start; int length; @@ -375,7 +375,7 @@ namespace libtorrent int m_sync_bytes_read; // hold information about latest allocated send buffer - // need to check for non zero (begin, end) for operations with this + // need to check for non zero (begin, end) for operations with this buffer::interval m_enc_send_buffer; // initialized during write_pe1_2_dhkey, and destroyed on diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index 6af8817e2..55ab99d9f 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -51,11 +51,11 @@ public: char operator[](int index) const { - assert(begin + index < end); + TORRENT_ASSERT(begin + index < end); return begin[index]; } - int left() const { assert(end >= begin); return end - begin; } + int left() const { TORRENT_ASSERT(end >= begin); return end - begin; } char* begin; char* end; @@ -70,7 +70,7 @@ public: char operator[](int index) const { - assert(begin + index < end); + TORRENT_ASSERT(begin + index < end); return begin[index]; } @@ -80,7 +80,7 @@ public: && end == p_interval.end); } - int left() const { assert(end >= begin); return end - begin; } + int left() const { TORRENT_ASSERT(end >= begin); return end - begin; } char const* begin; char const* end; @@ -142,9 +142,9 @@ public: void erase(char* begin, char* end) { - assert(end <= m_end); - assert(begin >= m_begin); - assert(begin <= end); + TORRENT_ASSERT(end <= m_end); + TORRENT_ASSERT(begin >= m_begin); + TORRENT_ASSERT(begin <= end); if (end == m_end) { resize(begin - m_begin); @@ -160,7 +160,7 @@ public: void reserve(std::size_t n) { if (n <= capacity()) return; - assert(n > 0); + TORRENT_ASSERT(n > 0); char* buf = (char*)::operator new(n); std::size_t s = size(); @@ -172,8 +172,8 @@ public: } bool empty() const { return m_begin == m_end; } - char& operator[](std::size_t i) { assert(i >= 0 && i < size()); return m_begin[i]; } - char const& operator[](std::size_t i) const { assert(i >= 0 && i < size()); return m_begin[i]; } + char& operator[](std::size_t i) { TORRENT_ASSERT(i >= 0 && i < size()); return m_begin[i]; } + char const& operator[](std::size_t i) const { TORRENT_ASSERT(i >= 0 && i < size()); return m_begin[i]; } char* begin() { return m_begin; } char const* begin() const { return m_begin; } diff --git a/libtorrent/include/libtorrent/chained_buffer.hpp b/libtorrent/include/libtorrent/chained_buffer.hpp index ba5e1ab2f..82fcb65c5 100644 --- a/libtorrent/include/libtorrent/chained_buffer.hpp +++ b/libtorrent/include/libtorrent/chained_buffer.hpp @@ -60,7 +60,7 @@ namespace libtorrent void pop_front(int bytes_to_pop) { - assert(bytes_to_pop <= m_bytes); + TORRENT_ASSERT(bytes_to_pop <= m_bytes); while (bytes_to_pop > 0 && !m_vec.empty()) { buffer_t& b = m_vec.front(); @@ -69,9 +69,9 @@ namespace libtorrent b.start += bytes_to_pop; b.used_size -= bytes_to_pop; m_bytes -= bytes_to_pop; - assert(m_bytes <= m_capacity); - assert(m_bytes >= 0); - assert(m_capacity >= 0); + TORRENT_ASSERT(m_bytes <= m_capacity); + TORRENT_ASSERT(m_bytes >= 0); + TORRENT_ASSERT(m_capacity >= 0); break; } @@ -79,9 +79,9 @@ namespace libtorrent m_bytes -= b.used_size; m_capacity -= b.size; bytes_to_pop -= b.used_size; - assert(m_bytes >= 0); - assert(m_capacity >= 0); - assert(m_bytes <= m_capacity); + TORRENT_ASSERT(m_bytes >= 0); + TORRENT_ASSERT(m_capacity >= 0); + TORRENT_ASSERT(m_bytes <= m_capacity); m_vec.pop_front(); } } @@ -89,7 +89,7 @@ namespace libtorrent template void append_buffer(char* buffer, int size, int used_size, D const& destructor) { - assert(size >= used_size); + TORRENT_ASSERT(size >= used_size); buffer_t b; b.buf = buffer; b.size = size; @@ -100,7 +100,7 @@ namespace libtorrent m_bytes += used_size; m_capacity += size; - assert(m_bytes <= m_capacity); + TORRENT_ASSERT(m_bytes <= m_capacity); } // returns the number of bytes available at the @@ -134,7 +134,7 @@ namespace libtorrent if (insert + size > b.buf + b.size) return 0; b.used_size += size; m_bytes += size; - assert(m_bytes <= m_capacity); + TORRENT_ASSERT(m_bytes <= m_capacity); return insert; } @@ -147,11 +147,11 @@ namespace libtorrent { if (i->used_size > to_send) { - assert(to_send > 0); + TORRENT_ASSERT(to_send > 0); m_tmp_vec.push_back(asio::const_buffer(i->start, to_send)); break; } - assert(i->used_size > 0); + TORRENT_ASSERT(i->used_size > 0); m_tmp_vec.push_back(asio::const_buffer(i->start, i->used_size)); to_send -= i->used_size; } diff --git a/libtorrent/include/libtorrent/fingerprint.hpp b/libtorrent/include/libtorrent/fingerprint.hpp index 712be6979..237fef065 100755 --- a/libtorrent/include/libtorrent/fingerprint.hpp +++ b/libtorrent/include/libtorrent/fingerprint.hpp @@ -50,12 +50,12 @@ namespace libtorrent , revision_version(revision) , tag_version(tag) { - assert(id_string); - assert(major >= 0); - assert(minor >= 0); - assert(revision >= 0); - assert(tag >= 0); - assert(std::strlen(id_string) == 2); + TORRENT_ASSERT(id_string); + TORRENT_ASSERT(major >= 0); + TORRENT_ASSERT(minor >= 0); + TORRENT_ASSERT(revision >= 0); + TORRENT_ASSERT(tag >= 0); + TORRENT_ASSERT(std::strlen(id_string) == 2); name[0] = id_string[0]; name[1] = id_string[1]; } @@ -83,7 +83,7 @@ namespace libtorrent { if (v >= 0 && v < 10) return '0' + v; else if (v >= 10) return 'A' + (v - 10); - assert(false); + TORRENT_ASSERT(false); return '0'; } diff --git a/libtorrent/include/libtorrent/hasher.hpp b/libtorrent/include/libtorrent/hasher.hpp index 71b7f9ede..f1dba7d1a 100755 --- a/libtorrent/include/libtorrent/hasher.hpp +++ b/libtorrent/include/libtorrent/hasher.hpp @@ -70,8 +70,8 @@ namespace libtorrent void update(const char* data, int len) { - assert(data != 0); - assert(len > 0); + TORRENT_ASSERT(data != 0); + TORRENT_ASSERT(len > 0); m_adler = adler32(m_adler, (const Bytef*)data, len); } unsigned long final() const { return m_adler; } @@ -91,14 +91,14 @@ namespace libtorrent hasher(const char* data, int len) { SHA1_Init(&m_context); - assert(data != 0); - assert(len > 0); + TORRENT_ASSERT(data != 0); + TORRENT_ASSERT(len > 0); SHA1_Update(&m_context, reinterpret_cast(data), len); } void update(const char* data, int len) { - assert(data != 0); - assert(len > 0); + TORRENT_ASSERT(data != 0); + TORRENT_ASSERT(len > 0); SHA1_Update(&m_context, reinterpret_cast(data), len); } diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index ccc145413..4a70a902c 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -82,7 +82,7 @@ struct http_connection : boost::enable_shared_from_this, boost: , m_connection_ticket(-1) , m_cc(cc) { - assert(!m_handler.empty()); + TORRENT_ASSERT(!m_handler.empty()); } void rate_limit(int limit); diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index b78aea7b0..c7fbe46ad 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -49,15 +49,15 @@ namespace libtorrent friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) { - assert(s->m_refs >= 0); - assert(s != 0); + TORRENT_ASSERT(s->m_refs >= 0); + TORRENT_ASSERT(s != 0); ++s->m_refs; } friend void intrusive_ptr_release(intrusive_ptr_base const* s) { - assert(s->m_refs > 0); - assert(s != 0); + TORRENT_ASSERT(s->m_refs > 0); + TORRENT_ASSERT(s != 0); if (--s->m_refs == 0) delete static_cast(s); } diff --git a/libtorrent/include/libtorrent/invariant_check.hpp b/libtorrent/include/libtorrent/invariant_check.hpp index 3eaacf34c..3075b8975 100755 --- a/libtorrent/include/libtorrent/invariant_check.hpp +++ b/libtorrent/include/libtorrent/invariant_check.hpp @@ -40,7 +40,7 @@ namespace libtorrent } catch (...) { - assert(false); + TORRENT_ASSERT(false); } } @@ -52,7 +52,7 @@ namespace libtorrent } catch (...) { - assert(false); + TORRENT_ASSERT(false); } } diff --git a/libtorrent/include/libtorrent/ip_filter.hpp b/libtorrent/include/libtorrent/ip_filter.hpp index 7b8cc0e17..eee76cdc4 100644 --- a/libtorrent/include/libtorrent/ip_filter.hpp +++ b/libtorrent/include/libtorrent/ip_filter.hpp @@ -156,16 +156,16 @@ namespace detail using boost::next; using boost::prior; - assert(!m_access_list.empty()); - assert(first < last || first == last); + TORRENT_ASSERT(!m_access_list.empty()); + TORRENT_ASSERT(first < last || first == last); typename range_t::iterator i = m_access_list.upper_bound(first); typename range_t::iterator j = m_access_list.upper_bound(last); if (i != m_access_list.begin()) --i; - assert(j != m_access_list.begin()); - assert(j != i); + TORRENT_ASSERT(j != m_access_list.begin()); + TORRENT_ASSERT(j != i); int first_access = i->access; int last_access = prior(j)->access; @@ -179,8 +179,8 @@ namespace detail --i; first_access = i->access; } - assert(!m_access_list.empty()); - assert(i != m_access_list.end()); + TORRENT_ASSERT(!m_access_list.empty()); + TORRENT_ASSERT(i != m_access_list.end()); if (i != j) m_access_list.erase(next(i), j); if (i->start == first) @@ -200,22 +200,22 @@ namespace detail || (j == m_access_list.end() && last != max_addr())) { - assert(j == m_access_list.end() || last < minus_one(j->start)); + TORRENT_ASSERT(j == m_access_list.end() || last < minus_one(j->start)); if (last_access != flags) j = m_access_list.insert(j, range(plus_one(last), last_access)); } if (j != m_access_list.end() && j->access == flags) m_access_list.erase(j); - assert(!m_access_list.empty()); + TORRENT_ASSERT(!m_access_list.empty()); } int access(Addr const& addr) const { - assert(!m_access_list.empty()); + TORRENT_ASSERT(!m_access_list.empty()); typename range_t::const_iterator i = m_access_list.upper_bound(addr); if (i != m_access_list.begin()) --i; - assert(i != m_access_list.end()); - assert(i->start <= addr && (boost::next(i) == m_access_list.end() + TORRENT_ASSERT(i != m_access_list.end()); + TORRENT_ASSERT(i->start <= addr && (boost::next(i) == m_access_list.end() || addr < boost::next(i)->start)); return i->access; } diff --git a/libtorrent/include/libtorrent/kademlia/routing_table.hpp b/libtorrent/include/libtorrent/kademlia/routing_table.hpp index 9e10a3483..acb4c1885 100644 --- a/libtorrent/include/libtorrent/kademlia/routing_table.hpp +++ b/libtorrent/include/libtorrent/kademlia/routing_table.hpp @@ -123,7 +123,7 @@ namespace aux void increment() { - assert(m_bucket_iterator != m_bucket_end); + TORRENT_ASSERT(m_bucket_iterator != m_bucket_end); ++m_iterator; while (m_iterator == m_bucket_iterator->first.end()) { @@ -135,7 +135,7 @@ namespace aux node_entry const& dereference() const { - assert(m_bucket_iterator != m_bucket_end); + TORRENT_ASSERT(m_bucket_iterator != m_bucket_end); return *m_iterator; } @@ -194,7 +194,7 @@ public: int bucket_size(int bucket) { - assert(bucket >= 0 && bucket < 160); + TORRENT_ASSERT(bucket >= 0 && bucket < 160); return (int)m_buckets[bucket].first.size(); } int bucket_size() const { return m_bucket_size; } diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index ce7e61ec5..2ea3c7d34 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -176,6 +176,9 @@ namespace libtorrent void set_non_prioritized(bool b) { m_non_prioritized = b; } + void fast_reconnect(bool r) { m_fast_reconnect = r; } + bool fast_reconnect() const { return m_fast_reconnect; } + // this adds an announcement in the announcement queue // it will let the peer know that we have the given piece void announce_piece(int index); @@ -380,7 +383,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void set_country(char const* c) { - assert(strlen(c) == 2); + TORRENT_ASSERT(strlen(c) == 2); m_country[0] = c[0]; m_country[1] = c[1]; } @@ -732,6 +735,13 @@ namespace libtorrent // the number of bytes send to the disk-io // thread that hasn't yet been completely written. int m_outstanding_writing_bytes; + + // if this is true, the disconnection + // timestamp is not updated when the connection + // is closed. This means the time until we can + // reconnect to this peer is shorter, and likely + // immediate. + bool m_fast_reconnect; #ifndef NDEBUG public: diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp index 57303e2fd..13d857f99 100755 --- a/libtorrent/include/libtorrent/peer_id.hpp +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -58,7 +58,7 @@ namespace libtorrent big_number(std::string const& s) { - assert(s.size() >= 20); + TORRENT_ASSERT(s.size() >= 20); int sl = int(s.size()) < size ? int(s.size()) : size; std::memcpy(m_number, &s[0], sl); } @@ -126,10 +126,10 @@ namespace libtorrent } unsigned char& operator[](int i) - { assert(i >= 0 && i < number_size); return m_number[i]; } + { TORRENT_ASSERT(i >= 0 && i < number_size); return m_number[i]; } unsigned char const& operator[](int i) const - { assert(i >= 0 && i < number_size); return m_number[i]; } + { TORRENT_ASSERT(i >= 0 && i < number_size); return m_number[i]; } typedef const unsigned char* const_iterator; typedef unsigned char* iterator; diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index 64f6203d5..ec6fc5bd3 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -282,7 +282,7 @@ namespace libtorrent // functor that compares indices on downloading_pieces struct has_index { - has_index(int i): index(i) { assert(i >= 0); } + has_index(int i): index(i) { TORRENT_ASSERT(i >= 0); } bool operator()(const downloading_piece& p) const { return p.index == index; } int index; @@ -308,8 +308,8 @@ namespace libtorrent , piece_priority(1) , index(index_) { - assert(peer_count_ >= 0); - assert(index_ >= 0); + TORRENT_ASSERT(peer_count_ >= 0); + TORRENT_ASSERT(index_ >= 0); } // the number of peers that has this piece @@ -341,7 +341,7 @@ namespace libtorrent }; bool have() const { return index == we_have_index; } - void set_have() { index = we_have_index; assert(have()); } + void set_have() { index = we_have_index; TORRENT_ASSERT(have()); } bool filtered() const { return piece_priority == filter_priority; } void filtered(bool f) { piece_priority = f ? filter_priority : 0; } @@ -447,8 +447,8 @@ namespace libtorrent inline int piece_picker::blocks_in_piece(int index) const { - assert(index >= 0); - assert(index < (int)m_piece_map.size()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_map.size()); if (index+1 == (int)m_piece_map.size()) return m_blocks_in_last_piece; else diff --git a/libtorrent/include/libtorrent/random_sample.hpp b/libtorrent/include/libtorrent/random_sample.hpp index 8d85080df..1eaaec1e3 100644 --- a/libtorrent/include/libtorrent/random_sample.hpp +++ b/libtorrent/include/libtorrent/random_sample.hpp @@ -49,7 +49,7 @@ namespace libtorrent Distance m = 0; Distance N = std::distance(start, end); - assert(N >= n); + TORRENT_ASSERT(N >= n); while (m < n) { diff --git a/libtorrent/include/libtorrent/session_impl.hpp b/libtorrent/include/libtorrent/session_impl.hpp index e69de29bb..67c3fef1d 100644 --- a/libtorrent/include/libtorrent/session_impl.hpp +++ b/libtorrent/include/libtorrent/session_impl.hpp @@ -0,0 +1,594 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED +#define TORRENT_SESSION_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/policy.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/connection_queue.hpp" +#include "libtorrent/disk_io_thread.hpp" + +namespace libtorrent +{ + + namespace fs = boost::filesystem; + + namespace aux + { + struct session_impl; + + // this data is shared between the main thread and the + // thread that initialize pieces + struct piece_checker_data + { + piece_checker_data() + : processing(false), progress(0.f), abort(false) {} + + boost::shared_ptr torrent_ptr; + fs::path save_path; + + sha1_hash info_hash; + + void parse_resume_data( + const entry& rd + , const torrent_info& info + , std::string& error); + + std::vector piece_map; + std::vector unfinished_pieces; + std::vector block_info; + std::vector peers; + entry resume_data; + + // this is true if this torrent is being processed (checked) + // if it is not being processed, then it can be removed from + // the queue without problems, otherwise the abort flag has + // to be set. + bool processing; + + // is filled in by storage::initialize_pieces() + // and represents the progress. It should be a + // value in the range [0, 1] + float progress; + + // abort defaults to false and is typically + // filled in by torrent_handle when the user + // aborts the torrent + bool abort; + }; + + struct checker_impl: boost::noncopyable + { + checker_impl(session_impl& s): m_ses(s), m_abort(false) {} + void operator()(); + piece_checker_data* find_torrent(const sha1_hash& info_hash); + void remove_torrent(sha1_hash const& info_hash); + +#ifndef NDEBUG + void check_invariant() const; +#endif + + // when the files has been checked + // the torrent is added to the session + session_impl& m_ses; + + mutable boost::mutex m_mutex; + boost::condition m_cond; + + // a list of all torrents that are currently in queue + // or checking their files + std::deque > m_torrents; + std::deque > m_processing; + + bool m_abort; + }; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + struct tracker_logger; +#endif + + // this is the link between the main thread and the + // thread started to run the main downloader loop + struct session_impl: boost::noncopyable + { +#ifndef NDEBUG + friend class ::libtorrent::peer_connection; +#endif + friend struct checker_impl; + friend class invariant_access; + typedef std::map + , boost::intrusive_ptr > + connection_map; + typedef std::map > torrent_map; + + session_impl( + std::pair listen_port_range + , fingerprint const& cl_fprint + , char const* listen_interface = "0.0.0.0"); + ~session_impl(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::function(torrent*)> ext); +#endif + void operator()(); + + void open_listen_port(); + + void async_accept(); + void on_incoming_connection(boost::shared_ptr const& s + , boost::weak_ptr const& as, asio::error_code const& e); + + // must be locked to access the data + // in this struct + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + + boost::weak_ptr find_torrent(const sha1_hash& info_hash); + peer_id const& get_peer_id() const { return m_peer_id; } + + void close_connection(boost::intrusive_ptr const& p); + void connection_failed(boost::shared_ptr const& s + , tcp::endpoint const& a, char const* message); + + void set_settings(session_settings const& s); + session_settings const& settings() const { return m_settings; } + +#ifndef TORRENT_DISABLE_DHT + void add_dht_node(std::pair const& node); + void add_dht_node(udp::endpoint n); + void add_dht_router(std::pair const& node); + void set_dht_settings(dht_settings const& s); + dht_settings const& get_dht_settings() const { return m_dht_settings; } + void start_dht(entry const& startup_state); + void stop_dht(); + entry dht_state() const; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + void set_pe_settings(pe_settings const& settings); + pe_settings const& get_pe_settings() const { return m_pe_settings; } +#endif + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); + + bool is_aborted() const { return m_abort; } + + void set_ip_filter(ip_filter const& f); + void set_port_filter(port_filter const& f); + + bool listen_on( + std::pair const& port_range + , const char* net_interface = 0); + bool is_listening() const; + + torrent_handle add_torrent( + torrent_info const& ti + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc); + + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , fs::path const& save_path + , entry const& resume_data + , bool compact_mode + , int block_size + , storage_constructor_type sc); + + void remove_torrent(torrent_handle const& h); + + std::vector get_torrents(); + + void set_severity_level(alert::severity_t s); + std::auto_ptr pop_alert(); + + int upload_rate_limit() const; + int download_rate_limit() const; + + void set_download_rate_limit(int bytes_per_second); + void set_upload_rate_limit(int bytes_per_second); + void set_max_half_open_connections(int limit); + void set_max_connections(int limit); + void set_max_uploads(int limit); + + int max_connections() const { return m_max_connections; } + int max_uploads() const { return m_max_uploads; } + + int num_uploads() const { return m_num_unchoked; } + int num_connections() const + { return m_connections.size(); } + + void unchoke_peer(peer_connection& c) + { + c.send_unchoke(); + ++m_num_unchoked; + } + + session_status status() const; + void set_peer_id(peer_id const& id); + void set_key(int key); + unsigned short listen_port() const; + + void abort(); + + torrent_handle find_torrent_handle(sha1_hash const& info_hash); + + void announce_lsd(sha1_hash const& ih); + + void set_peer_proxy(proxy_settings const& s) + { m_peer_proxy = s; } + void set_web_seed_proxy(proxy_settings const& s) + { m_web_seed_proxy = s; } + void set_tracker_proxy(proxy_settings const& s) + { m_tracker_proxy = s; } + + proxy_settings const& peer_proxy() const + { return m_peer_proxy; } + proxy_settings const& web_seed_proxy() const + { return m_web_seed_proxy; } + proxy_settings const& tracker_proxy() const + { return m_tracker_proxy; } + +#ifndef TORRENT_DISABLE_DHT + void set_dht_proxy(proxy_settings const& s) + { m_dht_proxy = s; } + proxy_settings const& dht_proxy() const + { return m_dht_proxy; } +#endif + + void start_lsd(); + void start_natpmp(); + void start_upnp(); + + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + // handles delayed alerts + alert_manager m_alerts; + +// private: + + void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); + + // this is where all active sockets are stored. + // the selector can sleep while there's no activity on + // them + io_service m_io_service; + asio::strand m_strand; + + // the file pool that all storages in this session's + // torrents uses. It sets a limit on the number of + // open files by this session. + // file pool must be destructed after the torrents + // since they will still have references to it + // when they are destructed. + file_pool m_files; + + // handles disk io requests asynchronously + disk_io_thread m_disk_thread; + + // this is a list of half-open tcp connections + // (only outgoing connections) + // this has to be one of the last + // members to be destructed + connection_queue m_half_open; + + // the bandwidth manager is responsible for + // handing out bandwidth to connections that + // asks for it, it can also throttle the + // rate. + bandwidth_manager m_download_channel; + bandwidth_manager m_upload_channel; + + bandwidth_manager* m_bandwidth_manager[2]; + + tracker_manager m_tracker_manager; + torrent_map m_torrents; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + + // filters incoming connections + ip_filter m_ip_filter; + + // filters outgoing connections + port_filter m_port_filter; + + // the peer id that is generated at the start of the session + peer_id m_peer_id; + + // the key is an id that is used to identify the + // client with the tracker only. It is randomized + // at startup + int m_key; + + // the range of ports we try to listen on + std::pair m_listen_port_range; + + // the ip-address of the interface + // we are supposed to listen on. + // if the ip is set to zero, it means + // that we should let the os decide which + // interface to listen on + tcp::endpoint m_listen_interface; + + // this is typically set to the same as the local + // listen port. In case a NAT port forward was + // successfully opened, this will be set to the + // port that is open on the external (NAT) interface + // on the NAT box itself. This is the port that has + // to be published to peers, since this is the port + // the client is reachable through. + int m_external_listen_port; + + boost::shared_ptr m_listen_socket; + + // the settings for the client + session_settings m_settings; + // the proxy settings for different + // kinds of connections + proxy_settings m_peer_proxy; + proxy_settings m_web_seed_proxy; + proxy_settings m_tracker_proxy; +#ifndef TORRENT_DISABLE_DHT + proxy_settings m_dht_proxy; +#endif + + // set to true when the session object + // is being destructed and the thread + // should exit + volatile bool m_abort; + + int m_max_uploads; + int m_max_connections; + + // the number of unchoked peers + int m_num_unchoked; + + // this is initialized to the unchoke_interval + // session_setting and decreased every second. + // when it reaches zero, it is reset to the + // unchoke_interval and the unchoke set is + // recomputed. + int m_unchoke_time_scaler; + + // works like unchoke_time_scaler but it + // is only decresed when the unchoke set + // is recomputed, and when it reaches zero, + // the optimistic unchoke is moved to another peer. + int m_optimistic_unchoke_time_scaler; + + // works like unchoke_time_scaler. Each time + // it reaches 0, and all the connections are + // used, the worst connection will be disconnected + // from the torrent with the most peers + int m_disconnect_time_scaler; + + // statistics gathered from all torrents. + stat m_stat; + + // is false by default and set to true when + // the first incoming connection is established + // this is used to know if the client is behind + // NAT or not. + bool m_incoming_connection; + + void second_tick(asio::error_code const& e); + ptime m_last_tick; + +#ifndef TORRENT_DISABLE_DHT + boost::intrusive_ptr m_dht; + dht_settings m_dht_settings; + // if this is set to true, the dht listen port + // will be set to the same as the tcp listen port + // and will be synchronlized with it as it changes + // it defaults to true + bool m_dht_same_port; + + // see m_external_listen_port. This is the same + // but for the udp port used by the DHT. + int m_external_udp_port; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + pe_settings m_pe_settings; +#endif + + boost::shared_ptr m_natpmp; + boost::shared_ptr m_upnp; + boost::shared_ptr m_lsd; + + // the timer used to fire the second_tick + deadline_timer m_timer; + + // the index of the torrent that will be offered to + // connect to a peer next time second_tick is called. + // This implements a round robin. + int m_next_connect_torrent; +#ifndef NDEBUG + void check_invariant(const char *place = 0); +#endif + +#ifdef TORRENT_STATS + // logger used to write bandwidth usage statistics + std::ofstream m_stats_logger; + int m_second_counter; +#endif +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr create_log(std::string const& name + , int instance, bool append = true); + + // this list of tracker loggers serves as tracker_callbacks when + // shutting down. This list is just here to keep them alive during + // whe shutting down process + std::list > m_tracker_loggers; + + public: + boost::shared_ptr m_logger; + private: +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list(torrent*)> > extension_list_t; + + extension_list_t m_extensions; +#endif + + // data shared between the main thread + // and the checker thread + checker_impl m_checker_impl; + + // the main working thread + boost::scoped_ptr m_thread; + + // the thread that calls initialize_pieces() + // on all torrents before they start downloading + boost::scoped_ptr m_checker_thread; + }; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + struct tracker_logger : request_callback + { + tracker_logger(session_impl& ses): m_ses(ses) {} + void tracker_warning(std::string const& str) + { + debug_log("*** tracker warning: " + str); + } + + void tracker_response(tracker_request const& + , std::vector& peers + , int interval + , int complete + , int incomplete) + { + std::stringstream s; + s << "TRACKER RESPONSE:\n" + "interval: " << interval << "\n" + "peers:\n"; + for (std::vector::const_iterator i = peers.begin(); + i != peers.end(); ++i) + { + s << " " << std::setfill(' ') << std::setw(16) << i->ip + << " " << std::setw(5) << std::dec << i->port << " "; + if (!i->pid.is_all_zeros()) s << " " << i->pid; + s << "\n"; + } + debug_log(s.str()); + } + + void tracker_request_timed_out( + tracker_request const&) + { + debug_log("*** tracker timed out"); + } + + void tracker_request_error( + tracker_request const& + , int response_code + , const std::string& str) + { + debug_log(std::string("*** tracker error: ") + + boost::lexical_cast(response_code) + ": " + + str); + } + + void debug_log(const std::string& line) + { + (*m_ses.m_logger) << line << "\n"; + } + session_impl& m_ses; + }; +#endif + + } +} + + +#endif + diff --git a/libtorrent/include/libtorrent/stat.hpp b/libtorrent/include/libtorrent/stat.hpp index 24e477a37..ef9b7780b 100755 --- a/libtorrent/include/libtorrent/stat.hpp +++ b/libtorrent/include/libtorrent/stat.hpp @@ -90,8 +90,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(bytes_payload >= 0); - assert(bytes_protocol >= 0); + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); m_downloaded_payload += bytes_payload; m_total_download_payload += bytes_payload; @@ -103,8 +103,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(bytes_payload >= 0); - assert(bytes_protocol >= 0); + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); m_uploaded_payload += bytes_payload; m_total_upload_payload += bytes_payload; @@ -132,8 +132,8 @@ namespace libtorrent // transfers from earlier connections. void add_stat(size_type downloaded, size_type uploaded) { - assert(downloaded >= 0); - assert(uploaded >= 0); + TORRENT_ASSERT(downloaded >= 0); + TORRENT_ASSERT(uploaded >= 0); m_total_download_payload += downloaded; m_total_upload_payload += uploaded; } @@ -143,14 +143,14 @@ namespace libtorrent #ifndef NDEBUG void check_invariant() const { - assert(m_mean_upload_rate >= 0); - assert(m_mean_download_rate >= 0); - assert(m_mean_upload_payload_rate >= 0); - assert(m_mean_download_payload_rate >= 0); - assert(m_total_upload_payload >= 0); - assert(m_total_download_payload >= 0); - assert(m_total_upload_protocol >= 0); - assert(m_total_download_protocol >= 0); + TORRENT_ASSERT(m_mean_upload_rate >= 0); + TORRENT_ASSERT(m_mean_download_rate >= 0); + TORRENT_ASSERT(m_mean_upload_payload_rate >= 0); + TORRENT_ASSERT(m_mean_download_payload_rate >= 0); + TORRENT_ASSERT(m_total_upload_payload >= 0); + TORRENT_ASSERT(m_total_download_payload >= 0); + TORRENT_ASSERT(m_total_upload_protocol >= 0); + TORRENT_ASSERT(m_total_download_protocol >= 0); } #endif diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index d3470c89d..1aae81d3a 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -188,7 +188,7 @@ namespace libtorrent if (timebase_info.denom == 0) mach_timebase_info(&timebase_info); // make sure we don't overflow - assert((at >= 0 && at >= at / 1000 * timebase_info.numer / timebase_info.denom) + TORRENT_ASSERT((at >= 0 && at >= at / 1000 * timebase_info.numer / timebase_info.denom) || (at < 0 && at < at / 1000 * timebase_info.numer / timebase_info.denom)); return at / 1000 * timebase_info.numer / timebase_info.denom; } @@ -199,11 +199,11 @@ namespace libtorrent if (timebase_info.denom == 0) { mach_timebase_info(&timebase_info); - assert(timebase_info.numer > 0); - assert(timebase_info.denom > 0); + TORRENT_ASSERT(timebase_info.numer > 0); + TORRENT_ASSERT(timebase_info.denom > 0); } // make sure we don't overflow - assert((ms >= 0 && ms <= ms * timebase_info.denom / timebase_info.numer * 1000) + TORRENT_ASSERT((ms >= 0 && ms <= ms * timebase_info.denom / timebase_info.numer * 1000) || (ms < 0 && ms > ms * timebase_info.denom / timebase_info.numer * 1000)); return ms * timebase_info.denom / timebase_info.numer * 1000; } @@ -269,7 +269,7 @@ namespace libtorrent #ifndef NDEBUG // make sure we don't overflow boost::int64_t ret = (pc * 1000 / performace_counter_frequency.QuadPart) * 1000; - assert((pc >= 0 && pc >= ret) || (pc < 0 && pc < ret)); + TORRENT_ASSERT((pc >= 0 && pc >= ret) || (pc < 0 && pc < ret)); #endif return (pc * 1000 / performace_counter_frequency.QuadPart) * 1000; } @@ -282,7 +282,7 @@ namespace libtorrent #ifndef NDEBUG // make sure we don't overflow boost::int64_t ret = (ms / 1000) * performace_counter_frequency.QuadPart / 1000; - assert((ms >= 0 && ms <= ret) + TORRENT_ASSERT((ms >= 0 && ms <= ret) || (ms < 0 && ms > ret)); #endif return (ms / 1000) * performace_counter_frequency.QuadPart / 1000; diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 90ebfea31..4bb68436c 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -152,7 +152,7 @@ namespace libtorrent void set_sequenced_download_threshold(int threshold); bool verify_resume_data(entry& rd, std::string& error) - { assert(m_storage); return m_storage->verify_resume_data(rd, error); } + { TORRENT_ASSERT(m_storage); return m_storage->verify_resume_data(rd, error); } void second_tick(stat& accumulator, float tick_interval); @@ -205,7 +205,7 @@ namespace libtorrent peer_connection* connect_to_peer(policy::peer* peerinfo); void set_ratio(float ratio) - { assert(ratio >= 0.0f); m_ratio = ratio; } + { TORRENT_ASSERT(ratio >= 0.0f); m_ratio = ratio; } float ratio() const { return m_ratio; } @@ -362,7 +362,7 @@ namespace libtorrent // returns true if we have downloaded the given piece bool have_piece(int index) const { - assert(index >= 0 && index < (signed)m_have_pieces.size()); + TORRENT_ASSERT(index >= 0 && index < (signed)m_have_pieces.size()); return m_have_pieces[index]; } @@ -377,14 +377,14 @@ namespace libtorrent { if (m_picker.get()) { - assert(!is_seed()); - assert(index >= 0 && index < (signed)m_have_pieces.size()); + TORRENT_ASSERT(!is_seed()); + TORRENT_ASSERT(index >= 0 && index < (signed)m_have_pieces.size()); m_picker->inc_refcount(index); } #ifndef NDEBUG else { - assert(is_seed()); + TORRENT_ASSERT(is_seed()); } #endif } @@ -393,13 +393,13 @@ namespace libtorrent { if (m_picker.get()) { - assert(!is_seed()); + TORRENT_ASSERT(!is_seed()); m_picker->inc_refcount_all(); } #ifndef NDEBUG else { - assert(is_seed()); + TORRENT_ASSERT(is_seed()); } #endif } @@ -409,19 +409,19 @@ namespace libtorrent { if (m_picker.get()) { - assert(!is_seed()); - assert(index >= 0 && index < (signed)m_have_pieces.size()); + TORRENT_ASSERT(!is_seed()); + TORRENT_ASSERT(index >= 0 && index < (signed)m_have_pieces.size()); m_picker->dec_refcount(index); } #ifndef NDEBUG else { - assert(is_seed()); + TORRENT_ASSERT(is_seed()); } #endif } - int block_size() const { assert(m_block_size > 0); return m_block_size; } + int block_size() const { TORRENT_ASSERT(m_block_size > 0); return m_block_size; } // this will tell all peers that we just got his piece // and also let the piece picker know that we have this piece @@ -464,7 +464,7 @@ namespace libtorrent void piece_finished(int index, bool passed_hash_check); void piece_failed(int index); void received_redundant_data(int num_bytes) - { assert(num_bytes > 0); m_total_redundant_bytes += num_bytes; } + { TORRENT_ASSERT(num_bytes > 0); m_total_redundant_bytes += num_bytes; } // this is true if we have all the pieces bool is_seed() const @@ -485,7 +485,7 @@ namespace libtorrent alert_manager& alerts() const; piece_picker& picker() { - assert(m_picker.get()); + TORRENT_ASSERT(m_picker.get()); return *m_picker; } bool has_picker() const @@ -494,7 +494,7 @@ namespace libtorrent } policy& get_policy() { - assert(m_policy); + TORRENT_ASSERT(m_policy); return *m_policy; } piece_manager& filesystem(); diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 287c57305..ec2d60ea5 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -398,8 +398,8 @@ namespace libtorrent , m_chk(c) , m_info_hash(h) { - assert(m_ses != 0); - assert(m_chk != 0); + TORRENT_ASSERT(m_ses != 0); + TORRENT_ASSERT(m_chk != 0); } #ifndef NDEBUG diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index 492fda48d..89744d0af 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -126,7 +126,7 @@ namespace libtorrent std::vector const& url_seeds() const { - assert(!m_half_metadata); + TORRENT_ASSERT(!m_half_metadata); return m_url_seeds; } @@ -168,7 +168,7 @@ namespace libtorrent int num_files(bool storage = false) const { - assert(m_piece_length > 0); + TORRENT_ASSERT(m_piece_length > 0); if (!storage || m_remapped_files.empty()) return (int)m_files.size(); else @@ -179,23 +179,23 @@ namespace libtorrent { if (!storage || m_remapped_files.empty()) { - assert(index >= 0 && index < (int)m_files.size()); + TORRENT_ASSERT(index >= 0 && index < (int)m_files.size()); return m_files[index]; } else { - assert(index >= 0 && index < (int)m_remapped_files.size()); + TORRENT_ASSERT(index >= 0 && index < (int)m_remapped_files.size()); return m_remapped_files[index]; } } const std::vector& trackers() const { return m_urls; } - size_type total_size() const { assert(m_piece_length > 0); return m_total_size; } - size_type piece_length() const { assert(m_piece_length > 0); return m_piece_length; } - int num_pieces() const { assert(m_piece_length > 0); return m_num_pieces; } + size_type total_size() const { TORRENT_ASSERT(m_piece_length > 0); return m_total_size; } + size_type piece_length() const { TORRENT_ASSERT(m_piece_length > 0); return m_piece_length; } + int num_pieces() const { TORRENT_ASSERT(m_piece_length > 0); return m_num_pieces; } const sha1_hash& info_hash() const { return m_info_hash; } - const std::string& name() const { assert(m_piece_length > 0); return m_name; } + const std::string& name() const { TORRENT_ASSERT(m_piece_length > 0); return m_name; } // ------- start deprecation ------- // this functionaily will be removed in a future version @@ -213,9 +213,9 @@ namespace libtorrent const sha1_hash& hash_for_piece(int index) const { - assert(index >= 0); - assert(index < (int)m_piece_hash.size()); - assert(!m_half_metadata); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_hash.size()); + TORRENT_ASSERT(!m_half_metadata); return m_piece_hash[index]; } @@ -232,7 +232,7 @@ namespace libtorrent nodes_t const& nodes() const { - assert(!m_half_metadata); + TORRENT_ASSERT(!m_half_metadata); return m_nodes; } diff --git a/libtorrent/include/libtorrent/variant_stream.hpp b/libtorrent/include/libtorrent/variant_stream.hpp index 4b32c9349..4f45f5e01 100644 --- a/libtorrent/include/libtorrent/variant_stream.hpp +++ b/libtorrent/include/libtorrent/variant_stream.hpp @@ -534,7 +534,7 @@ public: template std::size_t read_some(Mutable_Buffers const& buffers, asio::error_code& ec) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::read_some_visitor_ec(buffers, ec) , m_variant @@ -544,7 +544,7 @@ public: template std::size_t read_some(Mutable_Buffers const& buffers) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::read_some_visitor(buffers) , m_variant @@ -554,7 +554,7 @@ public: template void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::async_read_some_visitor(buffers, handler) , m_variant @@ -564,7 +564,7 @@ public: template void async_write_some(Const_Buffers const& buffers, Handler const& handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::async_write_some_visitor(buffers, handler) , m_variant @@ -574,7 +574,7 @@ public: template void async_connect(endpoint_type const& endpoint, Handler const& handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::async_connect_visitor(endpoint, handler), m_variant ); @@ -583,7 +583,7 @@ public: template void io_control(IO_Control_Command& ioc) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::io_control_visitor(ioc), m_variant ); @@ -592,7 +592,7 @@ public: template void io_control(IO_Control_Command& ioc, asio::error_code& ec) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::io_control_visitor_ec(ioc, ec) , m_variant @@ -601,14 +601,14 @@ public: void bind(endpoint_type const& endpoint) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor(aux::bind_visitor(endpoint), m_variant); } template void bind(endpoint_type const& endpoint, Error_Handler const& error_handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::bind_visitor(endpoint, error_handler), m_variant ); @@ -616,14 +616,14 @@ public: void open(protocol_type const& p) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor(aux::open_visitor(p), m_variant); } template void open(protocol_type const& p, Error_Handler const& error_handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::open_visitor(p, error_handler), m_variant ); @@ -631,14 +631,14 @@ public: void close() { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor(aux::close_visitor<>(), m_variant); } template void close(Error_Handler const& error_handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); boost::apply_visitor( aux::close_visitor(error_handler), m_variant ); @@ -646,14 +646,14 @@ public: std::size_t in_avail() { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor(aux::in_avail_visitor<>(), m_variant); } template std::size_t in_avail(Error_Handler const& error_handler) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::in_avail_visitor(error_handler), m_variant ); @@ -661,13 +661,13 @@ public: endpoint_type remote_endpoint() { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor(aux::remote_endpoint_visitor(), m_variant); } endpoint_type remote_endpoint(asio::error_code& ec) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::remote_endpoint_visitor_ec(ec), m_variant ); @@ -675,13 +675,13 @@ public: endpoint_type local_endpoint() { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor(aux::local_endpoint_visitor(), m_variant); } endpoint_type local_endpoint(asio::error_code& ec) { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::local_endpoint_visitor_ec(ec), m_variant ); @@ -689,7 +689,7 @@ public: asio::io_service& io_service() { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::io_service_visitor(), m_variant ); @@ -697,7 +697,7 @@ public: lowest_layer_type& lowest_layer() { - assert(instantiated()); + TORRENT_ASSERT(instantiated()); return boost::apply_visitor( aux::lowest_layer_visitor(), m_variant ); diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index 8c9360e8f..8871ad8ec 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -122,7 +122,7 @@ namespace libtorrent void write_request(peer_request const& r); void write_cancel(peer_request const& r) {} void write_have(int index) {} - void write_piece(peer_request const& r, char* buffer) { assert(false); } + void write_piece(peer_request const& r, char* buffer) { TORRENT_ASSERT(false); } void write_keepalive() {} void on_connected(); void write_reject_request(peer_request const&) {} diff --git a/libtorrent/include/libtorrent/xml_parse.hpp b/libtorrent/include/libtorrent/xml_parse.hpp index 67ae406f0..aaf71a816 100644 --- a/libtorrent/include/libtorrent/xml_parse.hpp +++ b/libtorrent/include/libtorrent/xml_parse.hpp @@ -68,7 +68,7 @@ namespace libtorrent { if (p != end) { - assert(*p == '<'); + TORRENT_ASSERT(*p == '<'); *p = 0; } token = xml_string; @@ -98,7 +98,7 @@ namespace libtorrent break; } - assert(*p == '>'); + TORRENT_ASSERT(*p == '>'); // save the character that terminated the tag name // it could be both '>' and ' '. char save = *tag_name_end; diff --git a/libtorrent/src/alert.cpp b/libtorrent/src/alert.cpp index 80ae6ead8..1401a5e4a 100755 --- a/libtorrent/src/alert.cpp +++ b/libtorrent/src/alert.cpp @@ -96,7 +96,7 @@ namespace libtorrent { { boost::mutex::scoped_lock lock(m_mutex); - assert(!m_alerts.empty()); + TORRENT_ASSERT(!m_alerts.empty()); alert* result = m_alerts.front(); m_alerts.pop(); diff --git a/libtorrent/src/assert.cpp b/libtorrent/src/assert.cpp index b4f011978..1073e05a3 100644 --- a/libtorrent/src/assert.cpp +++ b/libtorrent/src/assert.cpp @@ -69,5 +69,9 @@ void assert_fail(char const* expr, int line, char const* file, char const* funct abort(); } +#else + +void assert_fail(char const* expr, int line, char const* file, char const* function) {} + #endif diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 8872b59b1..4c2e9397c 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -106,7 +106,7 @@ namespace libtorrent : m_multicast_endpoint(multicast_endpoint) , m_on_receive(handler) { - assert(is_multicast(m_multicast_endpoint.address())); + TORRENT_ASSERT(is_multicast(m_multicast_endpoint.address())); using namespace asio::ip::multicast; diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 56d9307a9..21deec1d4 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -192,7 +192,7 @@ namespace libtorrent } else if (out_enc_policy == pe_settings::enabled) { - assert(peer_info_struct()); + TORRENT_ASSERT(peer_info_struct()); policy::peer* pi = peer_info_struct(); if (pi->pe_support == true) @@ -205,6 +205,7 @@ namespace libtorrent // if this fails, we need to reconnect // fast. pi->connected = time_now() - seconds(m_ses.settings().min_reconnect_time); + fast_reconnect(true); write_pe1_2_dhkey(); m_state = read_pe_dhkey; @@ -237,7 +238,7 @@ namespace libtorrent void bt_peer_connection::on_metadata() { boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); write_bitfield(t->pieces()); #ifndef TORRENT_DISABLE_DHT if (m_supports_dht_port && m_ses.m_dht) @@ -249,7 +250,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() @@ -264,7 +265,7 @@ namespace libtorrent void bt_peer_connection::write_have_all() { INVARIANT_CHECK; - assert(m_sent_handshake && !m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && !m_sent_bitfield); #ifndef NDEBUG m_sent_bitfield = true; #endif @@ -279,7 +280,7 @@ namespace libtorrent void bt_peer_connection::write_have_none() { INVARIANT_CHECK; - assert(m_sent_handshake && !m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && !m_sent_bitfield); #ifndef NDEBUG m_sent_bitfield = true; #endif @@ -295,8 +296,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); - assert(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); char msg[] = {0,0,0,13, msg_reject_request,0,0,0,0, 0,0,0,0, 0,0,0,0}; char* ptr = msg + 5; @@ -310,8 +311,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); - assert(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); char msg[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; char* ptr = msg + 5; @@ -321,7 +322,7 @@ namespace libtorrent void bt_peer_connection::get_specific_peer_info(peer_info& p) const { - assert(!associated_torrent().expired()); + TORRENT_ASSERT(!associated_torrent().expired()); if (is_interesting()) p.flags |= peer_info::interesting; if (is_choked()) p.flags |= peer_info::choked; @@ -360,10 +361,10 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(!m_DH_key_exchange.get()); - assert(!m_sent_handshake); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!m_DH_key_exchange.get()); + TORRENT_ASSERT(!m_sent_handshake); #ifdef TORRENT_VERBOSE_LOGGING if (is_local()) @@ -380,7 +381,7 @@ namespace libtorrent buffer::interval send_buf = allocate_send_buffer(dh_key_len + pad_size); - std::copy (m_DH_key_exchange->get_local_key(), + std::copy(m_DH_key_exchange->get_local_key(), m_DH_key_exchange->get_local_key() + dh_key_len, send_buf.begin); @@ -396,13 +397,13 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(is_local()); - assert(!m_sent_handshake); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(is_local()); + TORRENT_ASSERT(!m_sent_handshake); boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); hasher h; sha1_hash const& info_hash = t->torrent_file().info_hash(); @@ -443,7 +444,7 @@ namespace libtorrent m_DH_key_exchange.reset(); // secret should be invalid at this point // write the verification constant and crypto field - assert(send_buf.left() == 8 + 4 + 2 + pad_size + 2); + TORRENT_ASSERT(send_buf.left() == 8 + 4 + 2 + pad_size + 2); int encrypt_size = send_buf.left(); int crypto_provide = 0; @@ -469,7 +470,7 @@ namespace libtorrent write_pe_vc_cryptofield(send_buf, crypto_provide, pad_size); m_RC4_handler->encrypt(send_buf.end - encrypt_size, encrypt_size); - assert(send_buf.begin == send_buf.end); + TORRENT_ASSERT(send_buf.begin == send_buf.end); setup_send(); } @@ -477,11 +478,11 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!is_local()); - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(crypto_select == 0x02 || crypto_select == 0x01); - assert(!m_sent_handshake); + TORRENT_ASSERT(!is_local()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(crypto_select == 0x02 || crypto_select == 0x01); + TORRENT_ASSERT(!m_sent_handshake); int pad_size = 0; // rand() % 512; // Keep 0 for now @@ -512,12 +513,12 @@ namespace libtorrent { INVARIANT_CHECK; - assert(crypto_field <= 0x03 && crypto_field > 0); - assert(pad_size == 0); // pad not used yet + TORRENT_ASSERT(crypto_field <= 0x03 && crypto_field > 0); + TORRENT_ASSERT(pad_size == 0); // pad not used yet // vc,crypto_field,len(pad),pad, (len(ia)) - assert( (write_buf.left() == 8+4+2+pad_size+2 && is_local()) || + TORRENT_ASSERT( (write_buf.left() == 8+4+2+pad_size+2 && is_local()) || (write_buf.left() == 8+4+2+pad_size && !is_local()) ); - assert(!m_sent_handshake); + TORRENT_ASSERT(!m_sent_handshake); // encrypt(vc, crypto_provide/select, len(Pad), len(IA)) // len(pad) is zero for now, len(IA) only for outgoing connections @@ -544,7 +545,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(secret); + TORRENT_ASSERT(secret); hasher h; static const char keyA[] = "keyA"; @@ -570,8 +571,8 @@ namespace libtorrent h.update((char const*)stream_key.begin(), 20); const sha1_hash remote_key = h.final(); - assert(!m_RC4_handler.get()); - m_RC4_handler.reset (new RC4_handler (local_key, remote_key)); + TORRENT_ASSERT(!m_RC4_handler.get()); + m_RC4_handler.reset(new RC4_handler (local_key, remote_key)); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " computed RC4 keys\n"; @@ -580,9 +581,9 @@ namespace libtorrent void bt_peer_connection::send_buffer(char* buf, int size) { - assert(buf); - assert(size > 0); - assert(!m_rc4_encrypted || m_encrypted); + TORRENT_ASSERT(buf); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(!m_rc4_encrypted || m_encrypted); if (m_rc4_encrypted) m_RC4_handler->encrypt(buf, size); @@ -592,11 +593,11 @@ namespace libtorrent buffer::interval bt_peer_connection::allocate_send_buffer(int size) { - assert(!m_rc4_encrypted || m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted || m_encrypted); if (m_rc4_encrypted) { - assert(m_enc_send_buffer.left() == 0); + TORRENT_ASSERT(m_enc_send_buffer.left() == 0); m_enc_send_buffer = peer_connection::allocate_send_buffer(size); return m_enc_send_buffer; } @@ -609,12 +610,12 @@ namespace libtorrent void bt_peer_connection::setup_send() { - assert(!m_rc4_encrypted || m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted || m_encrypted); if (m_rc4_encrypted && m_enc_send_buffer.left()) { - assert(m_enc_send_buffer.begin); - assert(m_enc_send_buffer.end); + TORRENT_ASSERT(m_enc_send_buffer.begin); + TORRENT_ASSERT(m_enc_send_buffer.end); m_RC4_handler->encrypt(m_enc_send_buffer.begin, m_enc_send_buffer.left()); m_enc_send_buffer.end = m_enc_send_buffer.begin; @@ -625,10 +626,10 @@ namespace libtorrent int bt_peer_connection::get_syncoffset(char const* src, int src_size, char const* target, int target_size) const { - assert(target_size >= src_size); - assert(src_size > 0); - assert(src); - assert(target); + TORRENT_ASSERT(target_size >= src_size); + TORRENT_ASSERT(src_size > 0); + TORRENT_ASSERT(src); + TORRENT_ASSERT(target); int traverse_limit = target_size - src_size; @@ -672,13 +673,13 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_sent_handshake); + TORRENT_ASSERT(!m_sent_handshake); #ifndef NDEBUG m_sent_handshake = true; #endif boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); // add handshake to the send buffer const char version_string[] = "BitTorrent protocol"; @@ -725,7 +726,7 @@ namespace libtorrent , m_ses.get_peer_id().end() , i.begin); i.begin += 20; - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " ==> HANDSHAKE\n"; @@ -736,7 +737,7 @@ namespace libtorrent boost::optional bt_peer_connection::downloading_piece_progress() const { boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); buffer::const_interval recv_buffer = receive_buffer(); // are we currently receiving a 'piece' message? @@ -790,7 +791,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 1) throw protocol_error("'choke' message size != 1"); m_statistics.received_bytes(0, received); @@ -800,7 +801,7 @@ namespace libtorrent if (!m_supports_fast) { boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); while (!request_queue().empty()) { piece_block const& b = request_queue().front(); @@ -821,7 +822,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 1) throw protocol_error("'unchoke' message size != 1"); m_statistics.received_bytes(0, received); @@ -838,7 +839,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 1) throw protocol_error("'interested' message size != 1"); m_statistics.received_bytes(0, received); @@ -855,7 +856,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 1) throw protocol_error("'not interested' message size != 1"); m_statistics.received_bytes(0, received); @@ -872,7 +873,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 5) throw protocol_error("'have' message size != 5"); m_statistics.received_bytes(0, received); @@ -894,10 +895,10 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); // if we don't have the metedata, we cannot // verify the bitfield size @@ -935,7 +936,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 13) throw protocol_error("'request' message size != 13"); m_statistics.received_bytes(0, received); @@ -960,7 +961,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); buffer::const_interval recv_buffer = receive_buffer(); int recv_pos = recv_buffer.end - recv_buffer.begin; @@ -976,9 +977,9 @@ namespace libtorrent else { // received a bit of both - assert(recv_pos - received < 9); - assert(recv_pos > 9); - assert(9 - (recv_pos - received) <= 9); + TORRENT_ASSERT(recv_pos - received < 9); + TORRENT_ASSERT(recv_pos > 9); + TORRENT_ASSERT(9 - (recv_pos - received) <= 9); m_statistics.received_bytes( recv_pos - 9 , 9 - (recv_pos - received)); @@ -1004,7 +1005,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 13) throw protocol_error("'cancel' message size != 13"); m_statistics.received_bytes(0, received); @@ -1032,7 +1033,7 @@ namespace libtorrent if (!m_supports_dht_port) throw protocol_error("got 'dht_port' message from peer that doesn't support it"); - assert(received > 0); + TORRENT_ASSERT(received > 0); if (packet_size() != 3) throw protocol_error("'dht_port' message size != 3"); m_statistics.received_bytes(0, received); @@ -1128,7 +1129,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); m_statistics.received_bytes(0, received); if (packet_size() < 2) throw protocol_error("'extended' message smaller than 2 bytes"); @@ -1139,7 +1140,7 @@ namespace libtorrent buffer::const_interval recv_buffer = receive_buffer(); if (recv_buffer.left() < 2) return; - assert(*recv_buffer.begin == msg_extended); + TORRENT_ASSERT(*recv_buffer.begin == msg_extended); ++recv_buffer.begin; int extended_id = detail::read_uint8(recv_buffer.begin); @@ -1169,7 +1170,7 @@ namespace libtorrent if (!packet_finished()) return; boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); buffer::const_interval recv_buffer = receive_buffer(); @@ -1237,7 +1238,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(received > 0); + TORRENT_ASSERT(received > 0); // this means the connection has been closed already if (associated_torrent().expired()) return false; @@ -1265,7 +1266,7 @@ namespace libtorrent + " size: " + boost::lexical_cast(packet_size())); } - assert(m_message_handler[packet_type] != 0); + TORRENT_ASSERT(m_message_handler[packet_type] != 0); // call the correct handler for this packet type (this->*m_message_handler[packet_type])(received); @@ -1277,7 +1278,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,0}; send_buffer(msg, sizeof(msg)); @@ -1287,8 +1288,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); - assert(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); char msg[17] = {0,0,0,13, msg_cancel}; char* ptr = msg + 5; @@ -1302,8 +1303,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); - assert(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); char msg[17] = {0,0,0,13, msg_request}; char* ptr = msg + 5; @@ -1319,12 +1320,12 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = associated_torrent().lock(); - assert(t); - assert(m_sent_handshake && !m_sent_bitfield); - assert(t->valid_metadata()); + TORRENT_ASSERT(t); + TORRENT_ASSERT(m_sent_handshake && !m_sent_bitfield); + TORRENT_ASSERT(t->valid_metadata()); // in this case, have_all or have_none should be sent instead - assert(!m_supports_fast || !t->is_seed() || t->num_pieces() != 0); + TORRENT_ASSERT(!m_supports_fast || !t->is_seed() || t->num_pieces() != 0); if (m_supports_fast && t->is_seed()) { @@ -1344,7 +1345,7 @@ namespace libtorrent int num_lazy_pieces = 0; int lazy_piece = 0; - assert(t->is_seed() == (std::count(bitfield.begin(), bitfield.end(), true) == num_pieces)); + TORRENT_ASSERT(t->is_seed() == (std::count(bitfield.begin(), bitfield.end(), true) == num_pieces)); if (t->is_seed() && m_ses.settings().lazy_bitfields) { num_lazy_pieces = (std::min)(50, num_pieces / 10); @@ -1354,7 +1355,7 @@ namespace libtorrent if (rand() % (num_pieces - i) >= num_lazy_pieces - lazy_piece) continue; lazy_pieces[lazy_piece++] = i; } - assert(lazy_piece == num_lazy_pieces); + TORRENT_ASSERT(lazy_piece == num_lazy_pieces); lazy_piece = 0; } @@ -1397,7 +1398,7 @@ namespace libtorrent if (bitfield[c]) i.begin[c >> 3] |= 1 << (7 - (c & 7)); } - assert(i.end - i.begin == (num_pieces + 7) / 8); + TORRENT_ASSERT(i.end - i.begin == (num_pieces + 7) / 8); #ifndef NDEBUG m_sent_bitfield = true; @@ -1428,8 +1429,8 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " ==> EXTENSIONS\n"; #endif - assert(m_supports_extensions); - assert(m_sent_handshake); + TORRENT_ASSERT(m_supports_extensions); + TORRENT_ASSERT(m_sent_handshake); entry handshake(entry::dictionary_t); entry extension_list(entry::dictionary_t); @@ -1478,7 +1479,7 @@ namespace libtorrent std::copy(msg.begin(), msg.end(), i.begin); i.begin += msg.size(); - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); #ifdef TORRENT_VERBOSE_LOGGING std::stringstream ext; @@ -1494,7 +1495,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); if (is_choked()) return; char msg[] = {0,0,0,1,msg_choke}; @@ -1505,7 +1506,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_unchoke}; send_buffer(msg, sizeof(msg)); @@ -1515,7 +1516,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_interested}; send_buffer(msg, sizeof(msg)); @@ -1525,7 +1526,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,1,msg_not_interested}; send_buffer(msg, sizeof(msg)); @@ -1534,10 +1535,10 @@ namespace libtorrent void bt_peer_connection::write_have(int index) { INVARIANT_CHECK; - assert(associated_torrent().lock()->valid_metadata()); - assert(index >= 0); - assert(index < associated_torrent().lock()->torrent_file().num_pieces()); - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < associated_torrent().lock()->torrent_file().num_pieces()); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); char msg[] = {0,0,0,5,msg_have,0,0,0,0}; char* ptr = msg + 5; @@ -1549,14 +1550,14 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_sent_handshake && m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); char msg[4 + 1 + 4 + 4]; char* ptr = msg; - assert(r.length <= 16 * 1024); + TORRENT_ASSERT(r.length <= 16 * 1024); detail::write_int32(r.length + 1 + 4 + 4, ptr); detail::write_uint8(msg_piece, ptr); detail::write_int32(r.piece, ptr); @@ -1582,7 +1583,7 @@ namespace libtorrent { match_peer_id(peer_id const& id, peer_connection const* pc) : m_id(id), m_pc(pc) - { assert(pc); } + { TORRENT_ASSERT(pc); } bool operator()(std::pair const& p) const { @@ -1615,7 +1616,7 @@ namespace libtorrent m_statistics.received_bytes(0, bytes_transferred); #ifndef TORRENT_DISABLE_ENCRYPTION - assert(in_handshake() || !m_rc4_encrypted || m_encrypted); + TORRENT_ASSERT(in_handshake() || !m_rc4_encrypted || m_encrypted); if (m_rc4_encrypted && m_encrypted) { buffer::interval wr_buf = wr_recv_buffer(); @@ -1682,17 +1683,17 @@ namespace libtorrent // synchash,skeyhash,vc,crypto_provide,len(pad),pad,encrypt(handshake) reset_recv_buffer(20+20+8+4+2+0+handshake_len); } - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } // cannot fall through into if (m_state == read_pe_synchash) { - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(!is_local()); - assert(recv_buffer == receive_buffer()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!is_local()); + TORRENT_ASSERT(recv_buffer == receive_buffer()); if (recv_buffer.left() < 20) { @@ -1706,7 +1707,7 @@ namespace libtorrent if (!m_sync_hash.get()) { - assert(m_sync_bytes_read == 0); + TORRENT_ASSERT(m_sync_bytes_read == 0); hasher h; // compute synchash (hash('req1',S)) @@ -1729,7 +1730,7 @@ namespace libtorrent cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+20) - m_sync_bytes_read)); - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } // found complete sync @@ -1749,10 +1750,10 @@ namespace libtorrent if (m_state == read_pe_skey_vc) { - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(!is_local()); - assert(packet_size() == 28); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!is_local()); + TORRENT_ASSERT(packet_size() == 28); if (!packet_finished()) return; @@ -1793,7 +1794,7 @@ namespace libtorrent { attach_to_torrent(info_hash); t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); } init_pe_RC4_handler(m_DH_key_exchange->get_secret(), info_hash); @@ -1828,10 +1829,10 @@ namespace libtorrent // cannot fall through into if (m_state == read_pe_syncvc) { - assert(is_local()); - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(recv_buffer == receive_buffer()); + TORRENT_ASSERT(is_local()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(recv_buffer == receive_buffer()); if (recv_buffer.left() < 8) { @@ -1846,14 +1847,14 @@ namespace libtorrent // generate the verification constant if (!m_sync_vc.get()) { - assert(m_sync_bytes_read == 0); + TORRENT_ASSERT(m_sync_bytes_read == 0); m_sync_vc.reset (new char[8]); std::fill(m_sync_vc.get(), m_sync_vc.get() + 8, 0); m_RC4_handler->decrypt(m_sync_vc.get(), 8); } - assert(m_sync_vc.get()); + TORRENT_ASSERT(m_sync_vc.get()); int syncoffset = get_syncoffset(m_sync_vc.get(), 8 , recv_buffer.begin, recv_buffer.left()); @@ -1867,7 +1868,7 @@ namespace libtorrent cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+8) - m_sync_bytes_read)); - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } // found complete sync @@ -1889,9 +1890,9 @@ namespace libtorrent if (m_state == read_pe_cryptofield) // local/remote { - assert(!m_encrypted); - assert(!m_rc4_encrypted); - assert(packet_size() == 4+2); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(packet_size() == 4+2); if (!packet_finished()) return; @@ -2001,7 +2002,7 @@ namespace libtorrent if (m_state == read_pe_pad) { - assert(!m_encrypted); + TORRENT_ASSERT(!m_encrypted); if (!packet_finished()) return; int pad_size = is_local() ? packet_size() : packet_size() - 2; @@ -2043,8 +2044,8 @@ namespace libtorrent if (m_state == read_pe_ia) { - assert(!is_local()); - assert(!m_encrypted); + TORRENT_ASSERT(!is_local()); + TORRENT_ASSERT(!m_encrypted); if (!packet_finished()) return; @@ -2073,7 +2074,7 @@ namespace libtorrent if (m_state == init_bt_handshake) { - assert(m_encrypted); + TORRENT_ASSERT(m_encrypted); // decrypt remaining received bytes if (m_rc4_encrypted) @@ -2103,7 +2104,7 @@ namespace libtorrent m_ses.get_pe_settings().out_enc_policy == pe_settings::enabled) { policy::peer* pi = peer_info_struct(); - assert(pi); + TORRENT_ASSERT(pi); pi->pe_support = true; } @@ -2137,7 +2138,7 @@ namespace libtorrent #endif m_state = read_pe_dhkey; cut_receive_buffer(0, dh_key_len); - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } @@ -2166,7 +2167,7 @@ namespace libtorrent // fall through if (m_state == read_info_hash) { - assert(packet_size() == 28); + TORRENT_ASSERT(packet_size() == 28); if (!packet_finished()) return; recv_buffer = receive_buffer(); @@ -2230,7 +2231,7 @@ namespace libtorrent } t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); // if this is a local connection, we have already // sent the handshake @@ -2238,7 +2239,7 @@ namespace libtorrent // if (t->valid_metadata()) // write_bitfield(t->pieces()); - assert(t->get_policy().has_connection(this)); + TORRENT_ASSERT(t->get_policy().has_connection(this)); m_state = read_peer_id; reset_recv_buffer(20); @@ -2249,10 +2250,10 @@ namespace libtorrent { if (!t) { - assert(!packet_finished()); // TODO + TORRENT_ASSERT(!packet_finished()); // TODO return; } - assert(packet_size() == 20); + TORRENT_ASSERT(packet_size() == 20); if (!packet_finished()) return; recv_buffer = receive_buffer(); @@ -2285,7 +2286,7 @@ namespace libtorrent , match_peer_id(pid, this)); if (i != p.end_peer()) { - assert(i->second.connection->pid() == pid); + TORRENT_ASSERT(i->second.connection->pid() == pid); // we found another connection with the same peer-id // which connection should be closed in order to be // sure that the other end closes the same connection? @@ -2352,7 +2353,7 @@ namespace libtorrent m_ses.get_pe_settings().out_enc_policy == pe_settings::enabled) { policy::peer* pi = peer_info_struct(); - assert(pi); + TORRENT_ASSERT(pi); pi->pe_support = false; } @@ -2369,7 +2370,7 @@ namespace libtorrent #endif } - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } @@ -2407,24 +2408,24 @@ namespace libtorrent m_state = read_packet; reset_recv_buffer(packet_size); } - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } if (m_state == read_packet) { - assert(recv_buffer == receive_buffer()); + TORRENT_ASSERT(recv_buffer == receive_buffer()); if (!t) return; if (dispatch_message(bytes_transferred)) { m_state = read_packet_size; reset_recv_buffer(4); } - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); return; } - assert(!packet_finished()); + TORRENT_ASSERT(!packet_finished()); } // -------------------------- @@ -2469,7 +2470,7 @@ namespace libtorrent std::remove_if(m_payloads.begin(), m_payloads.end(), range_below_zero) , m_payloads.end()); - assert(amount_payload <= (int)bytes_transferred); + TORRENT_ASSERT(amount_payload <= (int)bytes_transferred); m_statistics.sent_bytes(amount_payload, bytes_transferred - amount_payload); } @@ -2477,10 +2478,10 @@ namespace libtorrent void bt_peer_connection::check_invariant() const { #ifndef TORRENT_DISABLE_ENCRYPTION - assert( (bool(m_state != read_pe_dhkey) || m_DH_key_exchange.get()) + TORRENT_ASSERT( (bool(m_state != read_pe_dhkey) || m_DH_key_exchange.get()) || !is_local()); - assert(!m_rc4_encrypted || m_RC4_handler.get()); + TORRENT_ASSERT(!m_rc4_encrypted || m_RC4_handler.get()); #endif if (!m_in_constructor) peer_connection::check_invariant(); @@ -2490,7 +2491,7 @@ namespace libtorrent for (std::deque::const_iterator i = m_payloads.begin(); i != m_payloads.end() - 1; ++i) { - assert(i->start + i->length <= (i+1)->start); + TORRENT_ASSERT(i->start + i->length <= (i+1)->start); } } } diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index 0b3f5ff54..1caeb99fc 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -102,7 +102,7 @@ namespace libtorrent { if (i->connecting) ++num_connecting; } - assert(num_connecting == m_num_connecting); + TORRENT_ASSERT(num_connecting == m_num_connecting); } #endif @@ -118,7 +118,7 @@ namespace libtorrent , m_queue.end(), boost::bind(&entry::connecting, _1) == false); while (i != m_queue.end()) { - assert(i->connecting == false); + TORRENT_ASSERT(i->connecting == false); ptime expire = time_now() + i->timeout; if (m_num_connecting == 0) { @@ -143,7 +143,7 @@ namespace libtorrent #ifndef NDEBUG struct function_guard { - function_guard(bool& v): val(v) { assert(!val); val = true; } + function_guard(bool& v): val(v) { TORRENT_ASSERT(!val); val = true; } ~function_guard() { val = false; } bool& val; @@ -159,7 +159,7 @@ namespace libtorrent function_guard guard_(m_in_timeout_function); #endif - assert(!e || e == asio::error::operation_aborted); + TORRENT_ASSERT(!e || e == asio::error::operation_aborted); if (e) return; ptime next_expire = max_time(); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index ea177b58d..b6b2e20a7 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -124,7 +124,7 @@ namespace libtorrent void disk_io_thread::add_job(disk_io_job const& j , boost::function const& f) { - assert(!j.callback); + TORRENT_ASSERT(!j.callback); boost::mutex::scoped_lock l(m_mutex); std::deque::reverse_iterator i = m_jobs.rbegin(); @@ -174,7 +174,7 @@ namespace libtorrent k->callback.swap(const_cast&>(f)); if (j.action == disk_io_job::write) m_queue_buffer_size += j.buffer_size; - assert(j.storage.get()); + TORRENT_ASSERT(j.storage.get()); m_signal.notify_all(); } @@ -239,7 +239,7 @@ namespace libtorrent ++m_allocations; #endif l.unlock(); - assert(j.buffer_size <= m_block_size); + TORRENT_ASSERT(j.buffer_size <= m_block_size); if (j.buffer == 0) { ret = -1; @@ -257,8 +257,8 @@ namespace libtorrent #ifdef TORRENT_DISK_STATS m_log << log_time() << " write " << j.buffer_size << std::endl; #endif - assert(j.buffer); - assert(j.buffer_size <= m_block_size); + TORRENT_ASSERT(j.buffer); + TORRENT_ASSERT(j.buffer_size <= m_block_size); j.storage->write_impl(j.buffer, j.piece, j.offset , j.buffer_size); diff --git a/libtorrent/src/entry.cpp b/libtorrent/src/entry.cpp index 16dffc275..50c6967cc 100755 --- a/libtorrent/src/entry.cpp +++ b/libtorrent/src/entry.cpp @@ -50,7 +50,7 @@ namespace template void call_destructor(T* o) { - assert(o); + TORRENT_ASSERT(o); o->~T(); } @@ -206,7 +206,7 @@ namespace libtorrent case dictionary_t: return dict() == e.dict(); default: - assert(m_type == undefined_t); + TORRENT_ASSERT(m_type == undefined_t); return true; } } @@ -229,7 +229,7 @@ namespace libtorrent new (data) dictionary_type; break; default: - assert(m_type == undefined_t); + TORRENT_ASSERT(m_type == undefined_t); m_type = undefined_t; } } @@ -273,7 +273,7 @@ namespace libtorrent call_destructor(reinterpret_cast(data)); break; default: - assert(m_type == undefined_t); + TORRENT_ASSERT(m_type == undefined_t); break; } } @@ -281,12 +281,12 @@ namespace libtorrent void entry::swap(entry& e) { // not implemented - assert(false); + TORRENT_ASSERT(false); } void entry::print(std::ostream& os, int indent) const { - assert(indent >= 0); + TORRENT_ASSERT(indent >= 0); for (int i = 0; i < indent; ++i) os << " "; switch (m_type) { diff --git a/libtorrent/src/escape_string.cpp b/libtorrent/src/escape_string.cpp index faff3de95..323a3e12b 100755 --- a/libtorrent/src/escape_string.cpp +++ b/libtorrent/src/escape_string.cpp @@ -87,8 +87,8 @@ namespace libtorrent std::string escape_string(const char* str, int len) { - assert(str != 0); - assert(len >= 0); + TORRENT_ASSERT(str != 0); + TORRENT_ASSERT(len >= 0); // http://www.ietf.org/rfc/rfc2396.txt // section 2.3 // some trackers seems to require that ' is escaped @@ -121,8 +121,8 @@ namespace libtorrent std::string escape_path(const char* str, int len) { - assert(str != 0); - assert(len >= 0); + TORRENT_ASSERT(str != 0); + TORRENT_ASSERT(len >= 0); static const char unreserved_chars[] = "/-_.!~*()" "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "0123456789"; diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 72876d528..53288b2e5 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "libtorrent/pch.hpp" +#include "libtorrent/assert.hpp" #ifdef _WIN32 // windows part @@ -91,7 +92,7 @@ namespace if (m == (mode_in | mode_out)) return O_RDWR | O_CREAT | O_BINARY | O_RANDOM; if (m == mode_out) return O_WRONLY | O_CREAT | O_BINARY | O_RANDOM; if (m == mode_in) return O_RDONLY | O_BINARY | O_RANDOM; - assert(false); + TORRENT_ASSERT(false); return 0; } @@ -157,7 +158,7 @@ namespace libtorrent void open(fs::path const& path, int mode) { - assert(path.is_complete()); + TORRENT_ASSERT(path.is_complete()); close(); #if defined(_WIN32) && defined(UNICODE) std::wstring wpath(safe_convert(path.native_file_string())); @@ -204,8 +205,8 @@ namespace libtorrent size_type read(char* buf, size_type num_bytes) { - assert(m_open_mode & mode_in); - assert(m_fd != -1); + TORRENT_ASSERT(m_open_mode & mode_in); + TORRENT_ASSERT(m_fd != -1); #ifdef _WIN32 size_type ret = ::_read(m_fd, buf, num_bytes); @@ -223,8 +224,8 @@ namespace libtorrent size_type write(const char* buf, size_type num_bytes) { - assert(m_open_mode & mode_out); - assert(m_fd != -1); + TORRENT_ASSERT(m_open_mode & mode_out); + TORRENT_ASSERT(m_fd != -1); // TODO: Test this a bit more, what happens with random failures in // the files? @@ -261,8 +262,8 @@ namespace libtorrent size_type seek(size_type offset, int m = 1) { - assert(m_open_mode); - assert(m_fd != -1); + TORRENT_ASSERT(m_open_mode); + TORRENT_ASSERT(m_fd != -1); int seekdir = (m == 1)?SEEK_SET:SEEK_END; #ifdef _WIN32 @@ -288,8 +289,8 @@ namespace libtorrent size_type tell() { - assert(m_open_mode); - assert(m_fd != -1); + TORRENT_ASSERT(m_open_mode); + TORRENT_ASSERT(m_fd != -1); #ifdef _WIN32 return _telli64(m_fd); diff --git a/libtorrent/src/file_pool.cpp b/libtorrent/src/file_pool.cpp index ab4ea8d6c..7bdf24085 100644 --- a/libtorrent/src/file_pool.cpp +++ b/libtorrent/src/file_pool.cpp @@ -43,9 +43,9 @@ namespace libtorrent boost::shared_ptr file_pool::open_file(void* st, fs::path const& p, file::open_mode m) { - assert(st != 0); - assert(p.is_complete()); - assert(m == file::in || m == (file::in | file::out)); + TORRENT_ASSERT(st != 0); + TORRENT_ASSERT(p.is_complete()); + TORRENT_ASSERT(m == file::in || m == (file::in | file::out)); boost::mutex::scoped_lock l(m_mutex); typedef nth_index::type path_view; path_view& pt = get<0>(m_files); @@ -69,7 +69,7 @@ namespace libtorrent // close the file before we open it with // the new read/write privilages i->file_ptr.reset(); - assert(e.file_ptr.unique()); + TORRENT_ASSERT(e.file_ptr.unique()); e.file_ptr.reset(); e.file_ptr.reset(new file(p, m)); e.mode = m; @@ -86,7 +86,7 @@ namespace libtorrent lru_view& lt = get<1>(m_files); lru_view::iterator i = lt.begin(); // the first entry in this view is the least recently used - assert(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); + TORRENT_ASSERT(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); lt.erase(i); } lru_file_entry e(boost::shared_ptr(new file(p, m))); @@ -100,7 +100,7 @@ namespace libtorrent void file_pool::release(void* st) { boost::mutex::scoped_lock l(m_mutex); - assert(st != 0); + TORRENT_ASSERT(st != 0); using boost::tie; typedef nth_index::type key_view; @@ -113,7 +113,7 @@ namespace libtorrent void file_pool::resize(int size) { - assert(size > 0); + TORRENT_ASSERT(size > 0); if (size == m_size) return; boost::mutex::scoped_lock l(m_mutex); m_size = size; @@ -126,7 +126,7 @@ namespace libtorrent while (int(m_files.size()) > m_size) { // the first entry in this view is the least recently used - assert(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); + TORRENT_ASSERT(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); lt.erase(i++); } } diff --git a/libtorrent/src/file_win.cpp b/libtorrent/src/file_win.cpp index 9d2c2f4bf..7fde0028f 100644 --- a/libtorrent/src/file_win.cpp +++ b/libtorrent/src/file_win.cpp @@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/file.hpp" #include "libtorrent/utf8.hpp" +#include "libtorrent/assert.hpp" #ifdef UNICODE #include "libtorrent/storage.hpp" @@ -136,8 +137,8 @@ namespace libtorrent void open(const char *file_name, open_flags flags) { - assert(file_name); - assert(flags & (read_flag | write_flag)); + TORRENT_ASSERT(file_name); + TORRENT_ASSERT(flags & (read_flag | write_flag)); DWORD access_mask = 0; if (flags & read_flag) @@ -145,7 +146,7 @@ namespace libtorrent if (flags & write_flag) access_mask |= GENERIC_WRITE; - assert(access_mask & (GENERIC_READ | GENERIC_WRITE)); + TORRENT_ASSERT(access_mask & (GENERIC_READ | GENERIC_WRITE)); #ifdef UNICODE std::wstring wfile_name(safe_convert(file_name)); @@ -198,8 +199,8 @@ namespace libtorrent size_type write(const char* buffer, size_type num_bytes) { - assert(buffer); - assert((DWORD)num_bytes == num_bytes); + TORRENT_ASSERT(buffer); + TORRENT_ASSERT((DWORD)num_bytes == num_bytes); DWORD bytes_written = 0; if (num_bytes != 0) { @@ -218,9 +219,9 @@ namespace libtorrent size_type read(char* buffer, size_type num_bytes) { - assert(buffer); - assert(num_bytes >= 0); - assert((DWORD)num_bytes == num_bytes); + TORRENT_ASSERT(buffer); + TORRENT_ASSERT(num_bytes >= 0); + TORRENT_ASSERT((DWORD)num_bytes == num_bytes); DWORD bytes_read = 0; if (num_bytes != 0) @@ -250,8 +251,8 @@ namespace libtorrent size_type seek(size_type pos, seek_mode from_where) { - assert(pos >= 0 || from_where != seek_begin); - assert(pos <= 0 || from_where != seek_end); + TORRENT_ASSERT(pos >= 0 || from_where != seek_begin); + TORRENT_ASSERT(pos <= 0 || from_where != seek_end); LARGE_INTEGER offs; offs.QuadPart = pos; if (FALSE == SetFilePointerEx( @@ -281,7 +282,7 @@ namespace libtorrent } size_type pos = offs.QuadPart; - assert(pos >= 0); + TORRENT_ASSERT(pos >= 0); return pos; } /* @@ -294,7 +295,7 @@ namespace libtorrent } size_type size = s.QuadPart; - assert(size >= 0); + TORRENT_ASSERT(size >= 0); return size; } */ @@ -330,7 +331,7 @@ namespace libtorrent void file::open(boost::filesystem::path const& p, open_mode m) { - assert(p.is_complete()); + TORRENT_ASSERT(p.is_complete()); m_impl->open(p.native_file_string().c_str(), impl::open_flags(m.m_mask)); } diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 5dd568655..08be387cc 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -148,7 +148,7 @@ void http_connection::on_resolve(asio::error_code const& e m_handler(e, m_parser, 0, 0); return; } - assert(i != tcp::resolver::iterator()); + TORRENT_ASSERT(i != tcp::resolver::iterator()); m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i) , bind(&http_connection::on_connect_timeout, shared_from_this()) , m_timeout); @@ -225,7 +225,7 @@ void http_connection::on_read(asio::error_code const& e if (m_rate_limit) { m_download_quota -= bytes_transferred; - assert(m_download_quota >= 0); + TORRENT_ASSERT(m_download_quota >= 0); } if (e == asio::error::eof) @@ -254,7 +254,7 @@ void http_connection::on_read(asio::error_code const& e } m_read_pos += bytes_transferred; - assert(m_read_pos <= int(m_recvbuffer.size())); + TORRENT_ASSERT(m_read_pos <= int(m_recvbuffer.size())); // having a nonempty path means we should handle redirects if (m_redirect && m_parser.header_finished()) @@ -306,7 +306,7 @@ void http_connection::on_read(asio::error_code const& e } else { - assert(!m_bottled); + TORRENT_ASSERT(!m_bottled); m_handler(e, m_parser, &m_recvbuffer[0], m_read_pos); m_read_pos = 0; m_last_receive = time_now(); diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 8c54cf7ff..de1783b58 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -119,7 +119,7 @@ namespace libtorrent boost::tuple http_parser::incoming(buffer::const_interval recv_buffer) { - assert(recv_buffer.left() >= m_recv_buffer.left()); + TORRENT_ASSERT(recv_buffer.left() >= m_recv_buffer.left()); boost::tuple ret(0, 0); // early exit if there's nothing new in the receive buffer @@ -129,7 +129,7 @@ namespace libtorrent char const* pos = recv_buffer.begin + m_recv_pos; if (m_state == read_status) { - assert(!m_finished); + TORRENT_ASSERT(!m_finished); char const* newline = std::find(pos, recv_buffer.end, '\n'); // if we don't have a full line yet, wait. if (newline == recv_buffer.end) return ret; @@ -166,7 +166,7 @@ namespace libtorrent if (m_state == read_header) { - assert(!m_finished); + TORRENT_ASSERT(!m_finished); char const* newline = std::find(pos, recv_buffer.end, '\n'); std::string line; @@ -226,7 +226,7 @@ namespace libtorrent m_content_length = range_end - range_start + 1; } - assert(m_recv_pos <= (int)recv_buffer.left()); + TORRENT_ASSERT(m_recv_pos <= (int)recv_buffer.left()); newline = std::find(pos, recv_buffer.end, '\n'); } } @@ -238,7 +238,7 @@ namespace libtorrent && m_content_length >= 0) incoming = m_content_length - m_recv_pos + m_body_start_pos; - assert(incoming >= 0); + TORRENT_ASSERT(incoming >= 0); m_recv_pos += incoming; boost::get<0>(ret) += incoming; @@ -253,7 +253,7 @@ namespace libtorrent buffer::const_interval http_parser::get_body() const { - assert(m_state == read_body); + TORRENT_ASSERT(m_state == read_body); if (m_content_length >= 0) return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos , m_recv_buffer.begin + (std::min)(m_recv_pos @@ -534,7 +534,7 @@ namespace libtorrent != bind_interface().is_v4(); ++target); if (target == end) { - assert(target_address.address().is_v4() != bind_interface().is_v4()); + TORRENT_ASSERT(target_address.address().is_v4() != bind_interface().is_v4()); if (cb) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; @@ -619,7 +619,7 @@ namespace libtorrent if (cb) cb->debug_log("tracker send data completed"); #endif restart_read_timeout(); - assert(m_buffer.size() - m_recv_pos > 0); + TORRENT_ASSERT(m_buffer.size() - m_recv_pos > 0); m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive , self(), _1, _2)); @@ -650,7 +650,7 @@ namespace libtorrent } restart_read_timeout(); - assert(bytes_transferred > 0); + TORRENT_ASSERT(bytes_transferred > 0); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) boost::shared_ptr cb = requester(); if (cb) cb->debug_log("tracker connection reading " @@ -669,7 +669,7 @@ namespace libtorrent fail(200, "too large tracker response"); return; } - assert(http_buffer_size > 0); + TORRENT_ASSERT(http_buffer_size > 0); if ((int)m_buffer.size() + http_buffer_size > m_settings.tracker_maximum_response_length) m_buffer.resize(m_settings.tracker_maximum_response_length); @@ -700,7 +700,7 @@ namespace libtorrent return; } - assert(m_buffer.size() - m_recv_pos > 0); + TORRENT_ASSERT(m_buffer.size() - m_recv_pos > 0); m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive , self(), _1, _2)); @@ -816,7 +816,7 @@ namespace libtorrent #ifndef NDEBUG catch (...) { - assert(false); + TORRENT_ASSERT(false); } #endif } diff --git a/libtorrent/src/identify_client.cpp b/libtorrent/src/identify_client.cpp index 7fa808f20..4888f4a95 100755 --- a/libtorrent/src/identify_client.cpp +++ b/libtorrent/src/identify_client.cpp @@ -267,7 +267,7 @@ namespace #ifndef NDEBUG for (int i = 1; i < size; ++i) { - assert(compare_id(name_map[i-1] + TORRENT_ASSERT(compare_id(name_map[i-1] , name_map[i])); } #endif diff --git a/libtorrent/src/ip_filter.cpp b/libtorrent/src/ip_filter.cpp index cf368c4d1..05334e578 100644 --- a/libtorrent/src/ip_filter.cpp +++ b/libtorrent/src/ip_filter.cpp @@ -43,23 +43,23 @@ namespace libtorrent { if (first.is_v4()) { - assert(last.is_v4()); + TORRENT_ASSERT(last.is_v4()); m_filter4.add_rule(first.to_v4(), last.to_v4(), flags); } else if (first.is_v6()) { - assert(last.is_v6()); + TORRENT_ASSERT(last.is_v6()); m_filter6.add_rule(first.to_v6(), last.to_v6(), flags); } else - assert(false); + TORRENT_ASSERT(false); } int ip_filter::access(address const& addr) const { if (addr.is_v4()) return m_filter4.access(addr.to_v4()); - assert(addr.is_v6()); + TORRENT_ASSERT(addr.is_v6()); return m_filter6.access(addr.to_v6()); } diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp index a3849ed69..3fbbef07d 100644 --- a/libtorrent/src/kademlia/closest_nodes.cpp +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -51,7 +51,7 @@ void closest_nodes_observer::reply(msg const& in) { if (!m_algorithm) { - assert(false); + TORRENT_ASSERT(false); return; } diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index c9908a163..b2981f7cd 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -126,15 +126,15 @@ namespace libtorrent { namespace dht void intrusive_ptr_add_ref(dht_tracker const* c) { - assert(c != 0); - assert(c->m_refs >= 0); + TORRENT_ASSERT(c != 0); + TORRENT_ASSERT(c->m_refs >= 0); ++c->m_refs; } void intrusive_ptr_release(dht_tracker const* c) { - assert(c != 0); - assert(c->m_refs > 0); + TORRENT_ASSERT(c != 0); + TORRENT_ASSERT(c->m_refs > 0); if (--c->m_refs == 0) delete c; } @@ -247,7 +247,7 @@ namespace libtorrent { namespace dht #ifndef NDEBUG std::cerr << "exception-type: " << typeid(exc).name() << std::endl; std::cerr << "what: " << exc.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); #endif }; @@ -263,7 +263,7 @@ namespace libtorrent { namespace dht } catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); }; void dht_tracker::rebind(asio::ip::address listen_interface, int listen_port) @@ -375,7 +375,7 @@ namespace libtorrent { namespace dht } catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); }; void dht_tracker::announce(sha1_hash const& ih, int listen_port @@ -411,7 +411,7 @@ namespace libtorrent { namespace dht using libtorrent::entry; using libtorrent::bdecode; - assert(bytes_transferred > 0); + TORRENT_ASSERT(bytes_transferred > 0); entry e = bdecode(m_in_buf[current_buffer].begin() , m_in_buf[current_buffer].end()); @@ -654,7 +654,7 @@ namespace libtorrent { namespace dht } TORRENT_LOG(dht_tracker) << e; #endif - assert(m.message_id != messages::error); + TORRENT_ASSERT(m.message_id != messages::error); m_dht.incoming(m); } catch (std::exception& e) @@ -670,7 +670,7 @@ namespace libtorrent { namespace dht } catch (std::exception& e) { - assert(false); + TORRENT_ASSERT(false); }; entry dht_tracker::state() const @@ -725,7 +725,7 @@ namespace libtorrent { namespace dht } catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); }; void dht_tracker::add_router_node(std::pair const& node) @@ -744,7 +744,7 @@ namespace libtorrent { namespace dht } catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); }; void dht_tracker::on_bootstrap() @@ -800,7 +800,7 @@ namespace libtorrent { namespace dht using libtorrent::bencode; using libtorrent::entry; entry e(entry::dictionary_t); - assert(!m.transaction_id.empty() || m.message_id == messages::error); + TORRENT_ASSERT(!m.transaction_id.empty() || m.message_id == messages::error); e["t"] = m.transaction_id; static char const version_str[] = {'L', 'T' , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR}; @@ -814,10 +814,10 @@ namespace libtorrent { namespace dht if (m.message_id == messages::error) { - assert(m.reply); + TORRENT_ASSERT(m.reply); e["y"] = "e"; entry error_list(entry::list_t); - assert(m.error_code > 200 && m.error_code <= 204); + TORRENT_ASSERT(m.error_code > 200 && m.error_code <= 204); error_list.list().push_back(entry(m.error_code)); error_list.list().push_back(entry(m.error_msg)); e["e"] = error_list; @@ -891,7 +891,7 @@ namespace libtorrent { namespace dht if (m.write_token.type() != entry::undefined_t) a["token"] = m.write_token; - assert(m.message_id <= messages::error); + TORRENT_ASSERT(m.message_id <= messages::error); e["q"] = messages::ids[m.message_id]; #ifdef TORRENT_DHT_VERBOSE_LOGGING @@ -973,7 +973,7 @@ namespace libtorrent { namespace dht // m_send may fail with "no route to host" // but it shouldn't throw since an error code // is passed in instead - assert(false); + TORRENT_ASSERT(false); } }} diff --git a/libtorrent/src/kademlia/find_data.cpp b/libtorrent/src/kademlia/find_data.cpp index 4ada42fb3..5db80fe80 100644 --- a/libtorrent/src/kademlia/find_data.cpp +++ b/libtorrent/src/kademlia/find_data.cpp @@ -49,7 +49,7 @@ void find_data_observer::reply(msg const& m) { if (!m_algorithm) { - assert(false); + TORRENT_ASSERT(false); return; } diff --git a/libtorrent/src/kademlia/node.cpp b/libtorrent/src/kademlia/node.cpp index 74641ec43..be42c8635 100644 --- a/libtorrent/src/kademlia/node.cpp +++ b/libtorrent/src/kademlia/node.cpp @@ -219,7 +219,7 @@ void node_impl::new_write_key() void node_impl::refresh_bucket(int bucket) try { - assert(bucket >= 0 && bucket < 160); + TORRENT_ASSERT(bucket >= 0 && bucket < 160); // generate a random node_id within the given bucket node_id target = generate_id(); @@ -243,7 +243,7 @@ void node_impl::refresh_bucket(int bucket) try target[(num_bits - 1) / 8] |= (~(m_id[(num_bits - 1) / 8])) & (0x80 >> ((num_bits - 1) % 8)); - assert(distance_exp(m_id, target) == bucket); + TORRENT_ASSERT(distance_exp(m_id, target) == bucket); std::vector start; start.reserve(m_table.bucket_size()); @@ -323,7 +323,7 @@ time_duration node_impl::refresh_timeout() } if (next < now) { - assert(refresh > -1); + TORRENT_ASSERT(refresh > -1); #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(node) << "refreshing bucket: " << refresh; #endif @@ -484,7 +484,7 @@ void node_impl::incoming_request(msg const& m) on_announce(m, reply); break; default: - assert(false); + TORRENT_ASSERT(false); }; if (m_table.need_node(m.id)) diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp index ad06c515d..52a5c766a 100644 --- a/libtorrent/src/kademlia/node_id.cpp +++ b/libtorrent/src/kademlia/node_id.cpp @@ -80,7 +80,7 @@ int distance_exp(node_id const& n1, node_id const& n2) for (node_id::const_iterator i = n1.begin(), j = n2.begin() , end(n1.end()); i != end; ++i, ++j, --byte) { - assert(byte >= 0); + TORRENT_ASSERT(byte >= 0); boost::uint8_t t = *i ^ *j; if (t == 0) continue; // we have found the first non-zero byte diff --git a/libtorrent/src/kademlia/routing_table.cpp b/libtorrent/src/kademlia/routing_table.cpp index 45091481c..02a7ffb12 100644 --- a/libtorrent/src/kademlia/routing_table.cpp +++ b/libtorrent/src/kademlia/routing_table.cpp @@ -164,8 +164,8 @@ void routing_table::touch_bucket(int bucket) ptime routing_table::next_refresh(int bucket) { - assert(bucket < 160); - assert(bucket >= 0); + TORRENT_ASSERT(bucket < 160); + TORRENT_ASSERT(bucket >= 0); // lower than or equal to since a refresh of bucket 0 will // effectively refresh the lowest active bucket as well if (bucket < m_lowest_active_bucket && bucket > 0) @@ -186,8 +186,8 @@ void routing_table::replacement_cache(bucket_t& nodes) const bool routing_table::need_node(node_id const& id) { int bucket_index = distance_exp(m_id, id); - assert(bucket_index < (int)m_buckets.size()); - assert(bucket_index >= 0); + TORRENT_ASSERT(bucket_index < (int)m_buckets.size()); + TORRENT_ASSERT(bucket_index >= 0); bucket_t& b = m_buckets[bucket_index].first; bucket_t& rb = m_buckets[bucket_index].second; @@ -209,8 +209,8 @@ bool routing_table::need_node(node_id const& id) void routing_table::node_failed(node_id const& id) { int bucket_index = distance_exp(m_id, id); - assert(bucket_index < (int)m_buckets.size()); - assert(bucket_index >= 0); + TORRENT_ASSERT(bucket_index < (int)m_buckets.size()); + TORRENT_ASSERT(bucket_index >= 0); bucket_t& b = m_buckets[bucket_index].first; bucket_t& rb = m_buckets[bucket_index].second; @@ -228,7 +228,7 @@ void routing_table::node_failed(node_id const& id) if (i->fail_count >= m_settings.max_fail_count) { b.erase(i); - assert(m_lowest_active_bucket <= bucket_index); + TORRENT_ASSERT(m_lowest_active_bucket <= bucket_index); while (m_buckets[m_lowest_active_bucket].first.empty() && m_lowest_active_bucket < 160) { @@ -259,8 +259,8 @@ bool routing_table::node_seen(node_id const& id, udp::endpoint addr) { if (m_router_nodes.find(addr) != m_router_nodes.end()) return false; int bucket_index = distance_exp(m_id, id); - assert(bucket_index < (int)m_buckets.size()); - assert(bucket_index >= 0); + TORRENT_ASSERT(bucket_index < (int)m_buckets.size()); + TORRENT_ASSERT(bucket_index >= 0); bucket_t& b = m_buckets[bucket_index].first; bucket_t::iterator i = std::find_if(b.begin(), b.end() @@ -274,7 +274,7 @@ bool routing_table::node_seen(node_id const& id, udp::endpoint addr) { // TODO: what do we do if we see a node with // the same id as a node at a different address? -// assert(i->addr == addr); +// TORRENT_ASSERT(i->addr == addr); // we already have the node in our bucket // just move it to the back since it was @@ -371,11 +371,11 @@ void routing_table::find_node(node_id const& target // vector. std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) , bind(&node_entry::fail_count, _1)); - assert((int)l.size() <= count); + TORRENT_ASSERT((int)l.size() <= count); if ((int)l.size() == count) { - assert(std::count_if(l.begin(), l.end() + TORRENT_ASSERT(std::count_if(l.begin(), l.end() , boost::bind(&node_entry::fail_count, _1) != 0) == 0); return; } @@ -399,7 +399,7 @@ void routing_table::find_node(node_id const& target std::copy(tmpb.begin(), tmpb.begin() + to_copy , std::back_inserter(l)); - assert((int)l.size() <= m_bucket_size); + TORRENT_ASSERT((int)l.size() <= m_bucket_size); // return if we have enough nodes or if the bucket index // is the biggest index available (there are no more buckets) @@ -407,7 +407,7 @@ void routing_table::find_node(node_id const& target if ((int)l.size() == count || bucket_index == (int)m_buckets.size() - 1) { - assert(std::count_if(l.begin(), l.end() + TORRENT_ASSERT(std::count_if(l.begin(), l.end() , boost::bind(&node_entry::fail_count, _1) != 0) == 0); return; } @@ -421,16 +421,16 @@ void routing_table::find_node(node_id const& target if ((int)l.size() >= count) { l.erase(l.begin() + count, l.end()); - assert(std::count_if(l.begin(), l.end() + TORRENT_ASSERT(std::count_if(l.begin(), l.end() , boost::bind(&node_entry::fail_count, _1) != 0) == 0); return; } } - assert((int)l.size() == count + TORRENT_ASSERT((int)l.size() == count || std::distance(l.begin(), l.end()) < m_bucket_size); - assert((int)l.size() <= count); + TORRENT_ASSERT((int)l.size() <= count); - assert(std::count_if(l.begin(), l.end() + TORRENT_ASSERT(std::count_if(l.begin(), l.end() , boost::bind(&node_entry::fail_count, _1) != 0) == 0); } diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp index 247c79a0f..086b8fc44 100644 --- a/libtorrent/src/kademlia/rpc_manager.cpp +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -70,15 +70,15 @@ TORRENT_DEFINE_LOG(rpc) void intrusive_ptr_add_ref(observer const* o) { - assert(o->m_refs >= 0); - assert(o != 0); + TORRENT_ASSERT(o->m_refs >= 0); + TORRENT_ASSERT(o != 0); ++o->m_refs; } void intrusive_ptr_release(observer const* o) { - assert(o->m_refs > 0); - assert(o != 0); + TORRENT_ASSERT(o->m_refs > 0); + TORRENT_ASSERT(o != 0); if (--o->m_refs == 0) { boost::pool<>& p = o->pool_allocator; @@ -138,16 +138,16 @@ rpc_manager::~rpc_manager() #ifndef NDEBUG void rpc_manager::check_invariant() const { - assert(m_oldest_transaction_id >= 0); - assert(m_oldest_transaction_id < max_transactions); - assert(m_next_transaction_id >= 0); - assert(m_next_transaction_id < max_transactions); - assert(!m_transactions[m_next_transaction_id]); + TORRENT_ASSERT(m_oldest_transaction_id >= 0); + TORRENT_ASSERT(m_oldest_transaction_id < max_transactions); + TORRENT_ASSERT(m_next_transaction_id >= 0); + TORRENT_ASSERT(m_next_transaction_id < max_transactions); + TORRENT_ASSERT(!m_transactions[m_next_transaction_id]); for (int i = (m_next_transaction_id + 1) % max_transactions; i != m_oldest_transaction_id; i = (i + 1) % max_transactions) { - assert(!m_transactions[i]); + TORRENT_ASSERT(!m_transactions[i]); } } #endif @@ -246,7 +246,7 @@ bool rpc_manager::incoming(msg const& m) } else { - assert(m.message_id != messages::error); + TORRENT_ASSERT(m.message_id != messages::error); // this is an incoming request m_incoming(m); } @@ -268,8 +268,8 @@ time_duration rpc_manager::tick() for (;m_next_transaction_id != m_oldest_transaction_id; m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions) { - assert(m_oldest_transaction_id >= 0); - assert(m_oldest_transaction_id < max_transactions); + TORRENT_ASSERT(m_oldest_transaction_id >= 0); + TORRENT_ASSERT(m_oldest_transaction_id < max_transactions); observer_ptr o = m_transactions[m_oldest_transaction_id]; if (!o) continue; @@ -311,9 +311,9 @@ unsigned int rpc_manager::new_transaction_id(observer_ptr o) // since that would break the invariant m_aborted_transactions.push_back(m_transactions[m_next_transaction_id]); m_transactions[m_next_transaction_id] = 0; - assert(m_oldest_transaction_id == m_next_transaction_id); + TORRENT_ASSERT(m_oldest_transaction_id == m_next_transaction_id); } - assert(!m_transactions[tid]); + TORRENT_ASSERT(!m_transactions[tid]); m_transactions[tid] = o; if (m_oldest_transaction_id == m_next_transaction_id) { @@ -332,7 +332,7 @@ void rpc_manager::update_oldest_transaction_id() { INVARIANT_CHECK; - assert(m_oldest_transaction_id != m_next_transaction_id); + TORRENT_ASSERT(m_oldest_transaction_id != m_next_transaction_id); while (!m_transactions[m_oldest_transaction_id]) { m_oldest_transaction_id = (m_oldest_transaction_id + 1) @@ -358,7 +358,7 @@ void rpc_manager::invoke(int message_id, udp::endpoint target_addr m.reply = false; m.id = m_our_id; m.addr = target_addr; - assert(!m_transactions[m_next_transaction_id]); + TORRENT_ASSERT(!m_transactions[m_next_transaction_id]); #ifndef NDEBUG int potential_new_id = m_next_transaction_id; #endif @@ -383,7 +383,7 @@ void rpc_manager::invoke(int message_id, udp::endpoint target_addr catch (std::exception& e) { // m_send may fail with "no route to host" - assert(potential_new_id == m_next_transaction_id); + TORRENT_ASSERT(potential_new_id == m_next_transaction_id); o->abort(); } } @@ -394,7 +394,7 @@ void rpc_manager::reply(msg& m) if (m_destructing) return; - assert(m.reply); + TORRENT_ASSERT(m.reply); m.piggy_backed_ping = false; m.id = m_our_id; @@ -406,7 +406,7 @@ void rpc_manager::reply_with_ping(msg& m) INVARIANT_CHECK; if (m_destructing) return; - assert(m.reply); + TORRENT_ASSERT(m.reply); m.piggy_backed_ping = true; m.id = m_our_id; @@ -416,7 +416,7 @@ void rpc_manager::reply_with_ping(msg& m) io::write_uint16(m_next_transaction_id, out); observer_ptr o(new (allocator().malloc()) null_observer(allocator())); - assert(!m_transactions[m_next_transaction_id]); + TORRENT_ASSERT(!m_transactions[m_next_transaction_id]); o->sent = time_now(); o->target_addr = m.addr; diff --git a/libtorrent/src/kademlia/traversal_algorithm.cpp b/libtorrent/src/kademlia/traversal_algorithm.cpp index ceb977f19..b0d3c5e74 100644 --- a/libtorrent/src/kademlia/traversal_algorithm.cpp +++ b/libtorrent/src/kademlia/traversal_algorithm.cpp @@ -67,7 +67,7 @@ void traversal_algorithm::add_entry(node_id const& id, udp::endpoint addr, unsig if (i == m_results.end() || i->id != id) { - assert(std::find_if(m_results.begin(), m_results.end() + TORRENT_ASSERT(std::find_if(m_results.begin(), m_results.end() , bind(&result::id, _1) == id) == m_results.end()); #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(traversal) << "adding result: " << id << " " << addr; @@ -110,11 +110,11 @@ void traversal_algorithm::failed(node_id const& id, bool prevent_request) ) ); - assert(i != m_results.end()); + TORRENT_ASSERT(i != m_results.end()); if (i != m_results.end()) { - assert(i->flags & result::queried); + TORRENT_ASSERT(i->flags & result::queried); m_failed.insert(i->addr); #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(traversal) << "failed: " << i->id << " " << i->addr; diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index 0623b156f..e02a2d758 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -65,10 +65,10 @@ namespace libtorrent { namespace std::pair req_to_offset(std::pair req, int total_size) { - assert(req.first >= 0); - assert(req.second > 0); - assert(req.second <= 256); - assert(req.first + req.second <= 256); + TORRENT_ASSERT(req.first >= 0); + TORRENT_ASSERT(req.second > 0); + TORRENT_ASSERT(req.second <= 256); + TORRENT_ASSERT(req.first + req.second <= 256); int start = div_round_up(req.first * total_size, 256); int size = div_round_up((req.first + req.second) * total_size, 256) - start; @@ -82,15 +82,15 @@ namespace libtorrent { namespace std::pair ret(start, size); - assert(start >= 0); - assert(size > 0); - assert(start <= 256); - assert(start + size <= 256); + TORRENT_ASSERT(start >= 0); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(start <= 256); + TORRENT_ASSERT(start + size <= 256); // assert the identity of this function #ifndef NDEBUG std::pair identity = req_to_offset(ret, total_size); - assert(offset == identity); + TORRENT_ASSERT(offset == identity); #endif return ret; } @@ -124,10 +124,10 @@ namespace libtorrent { namespace bencode(std::back_inserter(m_metadata) , m_torrent.torrent_file().create_info_metadata()); - assert(hasher(&m_metadata[0], m_metadata.size()).final() + TORRENT_ASSERT(hasher(&m_metadata[0], m_metadata.size()).final() == m_torrent.torrent_file().info_hash()); } - assert(!m_metadata.empty()); + TORRENT_ASSERT(!m_metadata.empty()); return m_metadata; } @@ -149,7 +149,7 @@ namespace libtorrent { namespace std::pair req = offset_to_req(std::make_pair(offset, size) , total_size); - assert(req.first + req.second <= (int)m_have_metadata.size()); + TORRENT_ASSERT(req.first + req.second <= (int)m_have_metadata.size()); std::fill( m_have_metadata.begin() + req.first @@ -203,7 +203,7 @@ namespace libtorrent { namespace { for (int i = req.first; i < req.first + req.second; ++i) { - assert(m_requested_metadata[i] > 0); + TORRENT_ASSERT(m_requested_metadata[i] > 0); if (m_requested_metadata[i] > 0) --m_requested_metadata[i]; } @@ -288,11 +288,11 @@ namespace libtorrent { namespace void write_metadata_request(std::pair req) { - assert(req.first >= 0); - assert(req.second > 0); - assert(req.first + req.second <= 256); - assert(!m_pc.associated_torrent().expired()); - assert(!m_pc.associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(req.first >= 0); + TORRENT_ASSERT(req.second > 0); + TORRENT_ASSERT(req.first + req.second <= 256); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + TORRENT_ASSERT(!m_pc.associated_torrent().lock()->valid_metadata()); int start = req.first; int size = req.second; @@ -309,17 +309,17 @@ namespace libtorrent { namespace detail::write_uint8(0, i.begin); detail::write_uint8(start, i.begin); detail::write_uint8(size - 1, i.begin); - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); m_pc.setup_send(); } void write_metadata(std::pair req) { - assert(req.first >= 0); - assert(req.second > 0); - assert(req.second <= 256); - assert(req.first + req.second <= 256); - assert(!m_pc.associated_torrent().expired()); + TORRENT_ASSERT(req.first >= 0); + TORRENT_ASSERT(req.second > 0); + TORRENT_ASSERT(req.second <= 256); + TORRENT_ASSERT(req.first + req.second <= 256); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); // abort if the peer doesn't support the metadata extension if (m_message_index == 0) return; @@ -344,7 +344,7 @@ namespace libtorrent { namespace std::copy(metadata.begin() + offset.first , metadata.begin() + offset.first + offset.second, i.begin); i.begin += offset.second; - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); } else { @@ -356,7 +356,7 @@ namespace libtorrent { namespace detail::write_uint8(m_message_index, i.begin); // means 'have no data' detail::write_uint8(2, i.begin); - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); } m_pc.setup_send(); } @@ -521,7 +521,7 @@ namespace libtorrent { namespace // the number of blocks to request int num_blocks = 256 / (peers + 1); if (num_blocks < 1) num_blocks = 1; - assert(num_blocks <= 128); + TORRENT_ASSERT(num_blocks <= 128); int min_element = (std::numeric_limits::max)(); int best_index = 0; @@ -543,10 +543,10 @@ namespace libtorrent { namespace for (int i = ret.first; i < ret.first + ret.second; ++i) m_requested_metadata[i]++; - assert(ret.first >= 0); - assert(ret.second > 0); - assert(ret.second <= 256); - assert(ret.first + ret.second <= 256); + TORRENT_ASSERT(ret.first >= 0); + TORRENT_ASSERT(ret.second > 0); + TORRENT_ASSERT(ret.second <= 256); + TORRENT_ASSERT(ret.first + ret.second <= 256); return ret; } diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index 38ae52413..42cf89e37 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -168,7 +168,7 @@ void natpmp::send_map_request(int i) try { using namespace libtorrent::detail; - assert(m_currently_mapping == -1 + TORRENT_ASSERT(m_currently_mapping == -1 || m_currently_mapping == i); m_currently_mapping = i; mapping& m = m_mappings[i]; @@ -232,7 +232,7 @@ void natpmp::on_reply(asio::error_code const& e m_send_timer.cancel(); - assert(m_currently_mapping >= 0); + TORRENT_ASSERT(m_currently_mapping >= 0); int i = m_currently_mapping; mapping& m = m_mappings[i]; diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index 981eca63d..955a7fea0 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -66,7 +66,7 @@ namespace libtorrent { int len_dh = sizeof(m_dh_prime); // must equal DH_size(m_DH) if (key_size != len_dh) { - assert(key_size > 0 && key_size < len_dh); + TORRENT_ASSERT(key_size > 0 && key_size < len_dh); int pad_zero_size = len_dh - key_size; std::fill(m_dh_local_key, m_dh_local_key + pad_zero_size, 0); @@ -100,7 +100,7 @@ namespace libtorrent { if (secret_size != 96) { - assert(secret_size < 96 && secret_size > 0); + TORRENT_ASSERT(secret_size < 96 && secret_size > 0); std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); } std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 327bb2074..9b975ab11 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -119,6 +119,7 @@ namespace libtorrent , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) , m_outstanding_writing_bytes(0) + , m_fast_reconnect(false) #ifndef NDEBUG , m_in_constructor(true) #endif @@ -133,7 +134,7 @@ namespace libtorrent #endif boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); std::fill(m_peer_id.begin(), m_peer_id.end(), 0); if (t->ready_for_connections()) @@ -195,6 +196,7 @@ namespace libtorrent , m_remote_dl_rate(0) , m_remote_dl_update(time_now()) , m_outstanding_writing_bytes(0) + , m_fast_reconnect(false) #ifndef NDEBUG , m_in_constructor(true) #endif @@ -207,7 +209,7 @@ namespace libtorrent m_remote = m_socket->remote_endpoint(); #ifdef TORRENT_VERBOSE_LOGGING - assert(m_socket->remote_endpoint() == remote()); + TORRENT_ASSERT(m_socket->remote_endpoint() == remote()); m_logger = m_ses.create_log(remote().address().to_string() + "_" + boost::lexical_cast(remote().port()), m_ses.listen_port()); (*m_logger) << "*** INCOMING CONNECTION\n"; @@ -221,7 +223,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); bool interested = false; const std::vector& we_have = t->pieces(); @@ -245,7 +247,7 @@ namespace libtorrent // may throw an asio error if socket has disconnected catch (std::exception& e) {} - assert(is_interesting() == interested); + TORRENT_ASSERT(is_interesting() == interested); } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -260,7 +262,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); int num_allowed_pieces = m_ses.settings().allowed_fast_set_size; int num_pieces = t->torrent_file().num_pieces(); @@ -321,9 +323,9 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); - assert(t->valid_metadata()); - assert(t->ready_for_connections()); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(t->ready_for_connections()); m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); @@ -374,7 +376,7 @@ namespace libtorrent peer_connection::~peer_connection() { // INVARIANT_CHECK; - assert(m_disconnecting); + TORRENT_ASSERT(m_disconnecting); #ifdef TORRENT_VERBOSE_LOGGING if (m_logger) @@ -385,10 +387,10 @@ namespace libtorrent #endif #ifndef NDEBUG if (m_peer_info) - assert(m_peer_info->connection == 0); + TORRENT_ASSERT(m_peer_info->connection == 0); boost::shared_ptr t = m_torrent.lock(); - if (t) assert(t->connection_for(remote()) != this); + if (t) TORRENT_ASSERT(t->connection_for(remote()) != this); #endif } @@ -414,8 +416,8 @@ namespace libtorrent write_have(index); #ifndef NDEBUG boost::shared_ptr t = m_torrent.lock(); - assert(t); - assert(t->have_piece(index)); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->have_piece(index)); #endif } @@ -424,10 +426,10 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); - assert(t->valid_metadata()); - assert(i >= 0); - assert(i < t->torrent_file().num_pieces()); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < t->torrent_file().num_pieces()); return m_have_piece[i]; } @@ -514,9 +516,9 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); - assert(t->valid_metadata()); + TORRENT_ASSERT(t->valid_metadata()); torrent_info const& ti = t->torrent_file(); return p.piece >= 0 @@ -539,8 +541,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_disconnecting); - assert(m_torrent.expired()); + TORRENT_ASSERT(!m_disconnecting); + TORRENT_ASSERT(m_torrent.expired()); boost::weak_ptr wpt = m_ses.find_torrent(ih); boost::shared_ptr t = wpt.lock(); @@ -578,27 +580,27 @@ namespace libtorrent throw std::runtime_error("connection rejected by paused torrent"); } - assert(m_torrent.expired()); + TORRENT_ASSERT(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. t->attach_peer(this); m_torrent = wpt; - assert(!m_torrent.expired()); + TORRENT_ASSERT(!m_torrent.expired()); // if the torrent isn't ready to accept // connections yet, we'll have to wait with // our initialization if (t->ready_for_connections()) init(); - assert(!m_torrent.expired()); + TORRENT_ASSERT(!m_torrent.expired()); // assume the other end has no pieces // if we don't have valid metadata yet, // leave the vector unallocated - assert(m_num_pieces == 0); + TORRENT_ASSERT(m_num_pieces == 0); std::fill(m_have_piece.begin(), m_have_piece.end(), false); - assert(!m_torrent.expired()); + TORRENT_ASSERT(!m_torrent.expired()); } // message handlers @@ -625,7 +627,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -681,7 +683,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -801,7 +803,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -827,7 +829,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -862,16 +864,12 @@ namespace libtorrent m_became_uninterested = time_now(); - // clear the request queue if the client isn't interested - m_requests.clear(); -// setup_send(); - #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " <== NOT_INTERESTED\n"; #endif boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); m_peer_interested = false; t->get_policy().not_interested(*this); @@ -886,7 +884,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -944,7 +942,7 @@ namespace libtorrent if (is_seed()) { - assert(m_peer_info); + TORRENT_ASSERT(m_peer_info); m_peer_info->seed = true; if (t->is_finished()) { @@ -963,7 +961,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -1006,7 +1004,7 @@ namespace libtorrent return; } - assert(t->valid_metadata()); + TORRENT_ASSERT(t->valid_metadata()); int num_pieces = std::count(bitfield.begin(), bitfield.end(), true); if (num_pieces == int(m_have_piece.size())) @@ -1087,7 +1085,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -1225,7 +1223,7 @@ namespace libtorrent for (std::vector::const_iterator i = dl_queue.begin(); i != dl_queue.end(); ++i) { - assert(i->finished <= blocks_per_piece); + TORRENT_ASSERT(i->finished <= blocks_per_piece); } } } @@ -1244,7 +1242,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -1292,8 +1290,8 @@ namespace libtorrent std::vector finished_blocks; piece_block block_finished(p.piece, p.start / t->block_size()); - assert(p.start % t->block_size() == 0); - assert(p.length == t->block_size() + TORRENT_ASSERT(p.start % t->block_size() == 0); + TORRENT_ASSERT(p.length == t->block_size() || p.length == t->torrent_file().total_size() % t->block_size()); std::deque::iterator b @@ -1366,7 +1364,7 @@ namespace libtorrent fs.async_write(p, data, bind(&peer_connection::on_disk_write_complete , self(), _1, _2, p, t)); m_outstanding_writing_bytes += p.length; - assert(!m_reading); + TORRENT_ASSERT(!m_reading); picker.mark_as_writing(block_finished, peer_info_struct()); } @@ -1376,7 +1374,7 @@ namespace libtorrent session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); m_outstanding_writing_bytes -= p.length; - assert(m_outstanding_writing_bytes >= 0); + TORRENT_ASSERT(m_outstanding_writing_bytes >= 0); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " *** on_disk_write_complete() " << p.length << "\n"; @@ -1406,8 +1404,8 @@ namespace libtorrent piece_picker& picker = t->picker(); - assert(p.piece == j.piece); - assert(p.start == j.offset); + TORRENT_ASSERT(p.piece == j.piece); + TORRENT_ASSERT(p.start == j.offset); piece_block block_finished(p.piece, p.start / t->block_size()); picker.mark_as_finished(block_finished, peer_info_struct()); if (t->alerts().should_post(alert::debug)) @@ -1443,7 +1441,7 @@ namespace libtorrent catch (std::exception const& e) { std::cerr << e.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); } #endif } @@ -1511,7 +1509,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " <== HAVE_ALL\n"; @@ -1549,7 +1547,7 @@ namespace libtorrent if (t->is_finished()) throw protocol_error("seed to seed connection redundant, disconnecting"); - assert(!m_have_piece.empty()); + TORRENT_ASSERT(!m_have_piece.empty()); std::fill(m_have_piece.begin(), m_have_piece.end(), true); m_num_pieces = m_have_piece.size(); @@ -1567,7 +1565,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " <== HAVE_NONE\n"; @@ -1582,7 +1580,7 @@ namespace libtorrent #endif if (m_peer_info) m_peer_info->seed = false; - assert(!m_have_piece.empty() || !t->ready_for_connections()); + TORRENT_ASSERT(!m_have_piece.empty() || !t->ready_for_connections()); } // ----------------------------- @@ -1594,7 +1592,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " <== ALLOWED_FAST [ " << index << " ]\n"; @@ -1641,7 +1639,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); m_allowed_fast.erase(std::remove_if(m_allowed_fast.begin() , m_allowed_fast.end(), bind(&torrent::have_piece, t, _1)) @@ -1656,15 +1654,15 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); - assert(t->valid_metadata()); - assert(block.piece_index >= 0); - assert(block.piece_index < t->torrent_file().num_pieces()); - assert(block.block_index >= 0); - assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); - assert(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); - assert(!t->have_piece(block.piece_index)); + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.piece_index < t->torrent_file().num_pieces()); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.block_index < t->torrent_file().piece_size(block.piece_index)); + TORRENT_ASSERT(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); + TORRENT_ASSERT(!t->have_piece(block.piece_index)); piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); @@ -1702,14 +1700,14 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); - assert(t->valid_metadata()); + TORRENT_ASSERT(t->valid_metadata()); - assert(block.piece_index >= 0); - assert(block.piece_index < t->torrent_file().num_pieces()); - assert(block.block_index >= 0); - assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.piece_index < t->torrent_file().num_pieces()); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.block_index < t->torrent_file().piece_size(block.piece_index)); // if all the peers that requested this block has been // cancelled, then just ignore the cancel. @@ -1741,8 +1739,8 @@ namespace libtorrent int block_size = (std::min)((int)t->torrent_file().piece_size(block.piece_index)-block_offset, t->block_size()); - assert(block_size > 0); - assert(block_size <= t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); peer_request r; r.piece = block.piece_index; @@ -1762,7 +1760,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_peer_info || !m_peer_info->optimistically_unchoked); + TORRENT_ASSERT(!m_peer_info || !m_peer_info->optimistically_unchoked); if (m_choked) return; write_choke(); @@ -1775,6 +1773,10 @@ namespace libtorrent m_last_choke = time_now(); #endif m_num_invalid_requests = 0; + + // reject the requests we have in the queue + std::for_each(m_requests.begin(), m_requests.end() + , bind(&peer_connection::write_reject_request, this, _1)); m_requests.clear(); } @@ -1825,7 +1827,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); if ((int)m_download_queue.size() >= m_desired_queue_size) return; @@ -1837,8 +1839,8 @@ namespace libtorrent int block_offset = block.block_index * t->block_size(); int block_size = (std::min)((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); - assert(block_size > 0); - assert(block_size <= t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); peer_request r; r.piece = block.piece_index; @@ -1883,14 +1885,14 @@ namespace libtorrent block_offset = block.block_index * t->block_size(); block_size = (std::min)((int)t->torrent_file().piece_size( block.piece_index) - block_offset, t->block_size()); - assert(block_size > 0); - assert(block_size <= t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); r.length += block_size; } } - assert(verify_piece(r)); + TORRENT_ASSERT(verify_piece(r)); #ifndef TORRENT_DISABLE_EXTENSIONS bool handled = false; @@ -1982,7 +1984,7 @@ namespace libtorrent void peer_connection::set_upload_limit(int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (limit == -1) limit = (std::numeric_limits::max)(); if (limit < 10) limit = 10; m_upload_limit = limit; @@ -1991,7 +1993,7 @@ namespace libtorrent void peer_connection::set_download_limit(int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (limit == -1) limit = (std::numeric_limits::max)(); if (limit < 10) limit = 10; m_download_limit = limit; @@ -2003,7 +2005,7 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); float ratio = t->ratio(); @@ -2028,7 +2030,7 @@ namespace libtorrent void peer_connection::get_peer_info(peer_info& p) const { - assert(!associated_torrent().expired()); + TORRENT_ASSERT(!associated_torrent().expired()); p.down_speed = statistics().download_rate(); p.up_speed = statistics().upload_rate(); @@ -2111,10 +2113,10 @@ namespace libtorrent { INVARIANT_CHECK; - assert(packet_size > 0); - assert(int(m_recv_buffer.size()) >= size); - assert(int(m_recv_buffer.size()) >= m_recv_pos); - assert(m_recv_pos >= size); + TORRENT_ASSERT(packet_size > 0); + TORRENT_ASSERT(int(m_recv_buffer.size()) >= size); + TORRENT_ASSERT(int(m_recv_buffer.size()) >= m_recv_pos); + TORRENT_ASSERT(m_recv_pos >= size); if (size > 0) std::memmove(&m_recv_buffer[0], &m_recv_buffer[0] + size, m_recv_pos - size); @@ -2139,7 +2141,7 @@ namespace libtorrent ptime now(time_now()); boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); on_tick(); @@ -2168,7 +2170,7 @@ namespace libtorrent // torrent for it const int block_size = m_request_large_blocks ? t->torrent_file().piece_length() : t->block_size(); - assert(block_size > 0); + TORRENT_ASSERT(block_size > 0); m_desired_queue_size = static_cast(queue_time * statistics().download_rate() / block_size); @@ -2302,14 +2304,14 @@ namespace libtorrent && (send_buffer_size() + m_reading_bytes < buffer_size_watermark) && !m_choked) { - assert(t->valid_metadata()); + TORRENT_ASSERT(t->valid_metadata()); peer_request& r = m_requests.front(); - assert(r.piece >= 0); - assert(r.piece < (int)m_have_piece.size()); - assert(t->have_piece(r.piece)); - assert(r.start + r.length <= t->torrent_file().piece_size(r.piece)); - assert(r.length > 0 && r.start >= 0); + TORRENT_ASSERT(r.piece >= 0); + TORRENT_ASSERT(r.piece < (int)m_have_piece.size()); + TORRENT_ASSERT(t->have_piece(r.piece)); + TORRENT_ASSERT(r.start + r.length <= t->torrent_file().piece_size(r.piece)); + TORRENT_ASSERT(r.length > 0 && r.start >= 0); t->filesystem().async_read(r, bind(&peer_connection::on_disk_read_complete , self(), _1, _2, r)); @@ -2369,13 +2371,13 @@ namespace libtorrent m_bandwidth_limit[channel].assign(amount); if (channel == upload_channel) { - assert(m_writing); + TORRENT_ASSERT(m_writing); m_writing = false; setup_send(); } else if (channel == download_channel) { - assert(m_reading); + TORRENT_ASSERT(m_reading); m_reading = false; setup_receive(); } @@ -2415,14 +2417,14 @@ namespace libtorrent // in this case, we have data to send, but no // bandwidth. So, we simply request bandwidth // from the torrent - assert(t); + TORRENT_ASSERT(t); if (m_bandwidth_limit[upload_channel].max_assignable() > 0) { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "req bandwidth [ " << upload_channel << " ]\n"; #endif - assert(!m_writing); + TORRENT_ASSERT(!m_writing); // peers that we are not interested in are non-prioritized m_writing = true; t->request_bandwidth(upload_channel, self() @@ -2433,7 +2435,7 @@ namespace libtorrent if (!can_write()) return; - assert(!m_writing); + TORRENT_ASSERT(!m_writing); // send the actual buffer if (!m_send_buffer.empty()) @@ -2443,7 +2445,7 @@ namespace libtorrent if (!m_ignore_bandwidth_limits && amount_to_send > quota_left) amount_to_send = quota_left; - assert(amount_to_send > 0); + TORRENT_ASSERT(amount_to_send > 0); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "async_write " << amount_to_send << " bytes\n"; @@ -2486,7 +2488,7 @@ namespace libtorrent if (!can_read()) return; - assert(m_packet_size > 0); + TORRENT_ASSERT(m_packet_size > 0); int max_receive = m_packet_size - m_recv_pos; int quota_left = m_bandwidth_limit[download_channel].quota_left(); if (!m_ignore_bandwidth_limits && max_receive > quota_left) @@ -2494,10 +2496,10 @@ namespace libtorrent if (max_receive == 0) return; - assert(m_recv_pos >= 0); - assert(m_packet_size > 0); + TORRENT_ASSERT(m_recv_pos >= 0); + TORRENT_ASSERT(m_packet_size > 0); - assert(can_read()); + TORRENT_ASSERT(can_read()); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "async_read " << max_receive << " bytes\n"; #endif @@ -2508,7 +2510,7 @@ namespace libtorrent void peer_connection::reset_recv_buffer(int packet_size) { - assert(packet_size > 0); + TORRENT_ASSERT(packet_size > 0); if (m_recv_pos > m_packet_size) { cut_receive_buffer(m_packet_size, packet_size); @@ -2538,7 +2540,7 @@ namespace libtorrent if (size <= 0) return; std::pair buffer = m_ses.allocate_buffer(size); - assert(buffer.second >= size); + TORRENT_ASSERT(buffer.second >= size); std::memcpy(buffer.first, buf, size); m_send_buffer.append_buffer(buffer.first, buffer.second, size , bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second)); @@ -2557,7 +2559,7 @@ namespace libtorrent if (insert == 0) { std::pair buffer = m_ses.allocate_buffer(size); - assert(buffer.second >= size); + TORRENT_ASSERT(buffer.second >= size); m_send_buffer.append_buffer(buffer.first, buffer.second, size , bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second)); buffer::interval ret(buffer.first, buffer.first + size); @@ -2601,7 +2603,7 @@ namespace libtorrent INVARIANT_CHECK; - assert(m_reading); + TORRENT_ASSERT(m_reading); m_reading = false; if (error) @@ -2624,16 +2626,16 @@ namespace libtorrent if (m_disconnecting) return; - assert(m_packet_size > 0); - assert(bytes_transferred > 0); + TORRENT_ASSERT(m_packet_size > 0); + TORRENT_ASSERT(bytes_transferred > 0); m_last_receive = time_now(); m_recv_pos += bytes_transferred; - assert(m_recv_pos <= int(m_recv_buffer.size())); + TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size())); on_receive(error, bytes_transferred); - assert(m_packet_size > 0); + TORRENT_ASSERT(m_packet_size > 0); if (m_peer_choked && m_recv_pos == 0 @@ -2686,7 +2688,7 @@ namespace libtorrent catch (...) { // all exceptions should derive from std::exception - assert(false); + TORRENT_ASSERT(false); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); m_ses.connection_failed(m_socket, remote(), "connection failed for unknown reason"); } @@ -2731,10 +2733,10 @@ namespace libtorrent m_connection_ticket = ticket; boost::shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); m_queued = false; - assert(m_connecting); + TORRENT_ASSERT(m_connecting); m_socket->open(t->get_interface().protocol()); // set the socket to non-blocking, so that we can @@ -2794,7 +2796,7 @@ namespace libtorrent catch (...) { // all exceptions should derive from std::exception - assert(false); + TORRENT_ASSERT(false); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); m_ses.connection_failed(m_socket, remote(), "connection failed for unkown reason"); } @@ -2811,7 +2813,7 @@ namespace libtorrent INVARIANT_CHECK; - assert(m_writing); + TORRENT_ASSERT(m_writing); m_send_buffer.pop_front(bytes_transferred); @@ -2833,8 +2835,8 @@ namespace libtorrent } if (m_disconnecting) return; - assert(!m_connecting); - assert(bytes_transferred > 0); + TORRENT_ASSERT(!m_connecting); + TORRENT_ASSERT(bytes_transferred > 0); m_last_sent = time_now(); @@ -2851,7 +2853,7 @@ namespace libtorrent catch (...) { // all exceptions should derive from std::exception - assert(false); + TORRENT_ASSERT(false); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); m_ses.connection_failed(m_socket, remote(), "connection failed for unknown reason"); } @@ -2862,11 +2864,11 @@ namespace libtorrent { if (m_peer_info) { - assert(m_peer_info->connection == this + TORRENT_ASSERT(m_peer_info->connection == this || m_peer_info->connection == 0); if (m_peer_info->optimistically_unchoked) - assert(!is_choked()); + TORRENT_ASSERT(!is_choked()); } boost::shared_ptr t = m_torrent.lock(); @@ -2877,17 +2879,17 @@ namespace libtorrent for (torrent_map::iterator i = m.begin(), end(m.end()); i != end; ++i) { torrent& t = *i->second; - assert(t.connection_for(m_remote) != this); + TORRENT_ASSERT(t.connection_for(m_remote) != this); } return; } - assert(t->connection_for(remote()) != 0 || m_in_constructor); + TORRENT_ASSERT(t->connection_for(remote()) != 0 || m_in_constructor); if (!m_in_constructor && t->connection_for(remote()) != this && !m_ses.settings().allow_multiple_connections_per_ip) { - assert(false); + TORRENT_ASSERT(false); } // expensive when using checked iterators @@ -2898,7 +2900,7 @@ namespace libtorrent , m_have_piece.end(), true); if (m_num_pieces != piece_count) { - assert(false); + TORRENT_ASSERT(false); } } */ @@ -2923,11 +2925,11 @@ namespace libtorrent std::find(m_download_queue.begin(), m_download_queue.end() , piece_block(i->index, j)) != m_download_queue.end()) { - assert(i->info[j].peer == m_remote); + TORRENT_ASSERT(i->info[j].peer == m_remote); } else { - assert(i->info[j].peer != m_remote || i->info[j].finished); + TORRENT_ASSERT(i->info[j].peer != m_remote || i->info[j].finished); } } } @@ -2997,7 +2999,7 @@ namespace libtorrent peer_connection::peer_speed_t peer_connection::peer_speed() { shared_ptr t = m_torrent.lock(); - assert(t); + TORRENT_ASSERT(t); int download_rate = int(statistics().download_payload_rate()); int torrent_download_rate = int(t->statistics().download_payload_rate()); diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 398573d33..652426806 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -62,8 +62,8 @@ namespace libtorrent , m_num_have(0) , m_sequenced_download_threshold(100) { - assert(blocks_per_piece > 0); - assert(total_num_blocks >= 0); + TORRENT_ASSERT(blocks_per_piece > 0); + TORRENT_ASSERT(total_num_blocks >= 0); #ifndef NDEBUG m_files_checked_called = false; #endif @@ -77,7 +77,7 @@ namespace libtorrent m_blocks_in_last_piece = total_num_blocks % blocks_per_piece; if (m_blocks_in_last_piece == 0) m_blocks_in_last_piece = blocks_per_piece; - assert(m_blocks_in_last_piece <= m_blocks_per_piece); + TORRENT_ASSERT(m_blocks_in_last_piece <= m_blocks_per_piece); // allocate the piece_map to cover all pieces // and make them invalid (as if though we already had every piece) @@ -133,15 +133,15 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(index >= 0); - assert(index < int(m_piece_map.size())); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); if (m_piece_map[index].downloading) { std::vector::const_iterator piece = std::find_if( m_downloads.begin(), m_downloads.end() , bind(&downloading_piece::index, _1) == index); - assert(piece != m_downloads.end()); + TORRENT_ASSERT(piece != m_downloads.end()); st = *piece; st.info = 0; return; @@ -166,7 +166,7 @@ namespace libtorrent if (sequenced_download_threshold == m_sequenced_download_threshold) return; - assert(sequenced_download_threshold > 0); + TORRENT_ASSERT(sequenced_download_threshold > 0); if (sequenced_download_threshold <= 0) return; int old_limit = m_sequenced_download_threshold; @@ -201,7 +201,7 @@ namespace libtorrent , end(in.end()); i != end; ++i) { m_piece_map[*i].index = c++; - assert(m_piece_map[*i].priority(old_limit) == old_limit * 2); + TORRENT_ASSERT(m_piece_map[*i].priority(old_limit) == old_limit * 2); } } } @@ -214,7 +214,7 @@ namespace libtorrent , end(in.end()); i != end; ++i) { m_piece_map[*i].index = c++; - assert(m_piece_map[*i].priority( + TORRENT_ASSERT(m_piece_map[*i].priority( sequenced_download_threshold) == sequenced_download_threshold * 2); } } @@ -254,7 +254,7 @@ namespace libtorrent m_downloads.begin(), m_downloads.end() , bind(&downloading_piece::info, _1) == &m_block_info[(m_downloads.size() - 1) * m_blocks_per_piece]); - assert(other != m_downloads.end()); + TORRENT_ASSERT(other != m_downloads.end()); if (i != other) { @@ -269,22 +269,22 @@ namespace libtorrent void piece_picker::verify_pick(std::vector const& picked , std::vector const& bitfield) const { - assert(bitfield.size() == m_piece_map.size()); + TORRENT_ASSERT(bitfield.size() == m_piece_map.size()); for (std::vector::const_iterator i = picked.begin() , end(picked.end()); i != end; ++i) { - assert(i->piece_index >= 0); - assert(i->piece_index < int(bitfield.size())); - assert(bitfield[i->piece_index]); - assert(!m_piece_map[i->piece_index].have()); + TORRENT_ASSERT(i->piece_index >= 0); + TORRENT_ASSERT(i->piece_index < int(bitfield.size())); + TORRENT_ASSERT(bitfield[i->piece_index]); + TORRENT_ASSERT(!m_piece_map[i->piece_index].have()); } } void piece_picker::check_invariant(const torrent* t) const { - assert(sizeof(piece_pos) == 4); + TORRENT_ASSERT(sizeof(piece_pos) == 4); - assert(m_piece_info.empty() || m_piece_info[0].empty()); + TORRENT_ASSERT(m_piece_info.empty() || m_piece_info[0].empty()); if (!m_downloads.empty()) { @@ -293,15 +293,15 @@ namespace libtorrent { downloading_piece const& dp = *i; downloading_piece const& next = *(i + 1); - assert(dp.finished + dp.writing >= next.finished + next.writing); + TORRENT_ASSERT(dp.finished + dp.writing >= next.finished + next.writing); } } if (t != 0) - assert((int)m_piece_map.size() == t->torrent_file().num_pieces()); + TORRENT_ASSERT((int)m_piece_map.size() == t->torrent_file().num_pieces()); for (int i = m_sequenced_download_threshold * 2 + 1; i < int(m_piece_info.size()); ++i) - assert(m_piece_info[i].empty()); + TORRENT_ASSERT(m_piece_info[i].empty()); for (std::vector::const_iterator i = m_downloads.begin() , end(m_downloads.end()); i != end; ++i) @@ -328,10 +328,10 @@ namespace libtorrent ++num_writing; } } - assert(blocks_requested == (i->state != none)); - assert(num_requested == i->requested); - assert(num_writing == i->writing); - assert(num_finished == i->finished); + TORRENT_ASSERT(blocks_requested == (i->state != none)); + TORRENT_ASSERT(num_requested == i->requested); + TORRENT_ASSERT(num_writing == i->writing); + TORRENT_ASSERT(num_finished == i->finished); } @@ -362,7 +362,7 @@ namespace libtorrent if (peer->second->has_piece(index)) actual_peer_count++; } - assert((int)i->peer_count == actual_peer_count); + TORRENT_ASSERT((int)i->peer_count == actual_peer_count); /* int num_downloaders = 0; for (std::vector::const_iterator peer = t->begin(); @@ -377,11 +377,11 @@ namespace libtorrent if (i->downloading) { - assert(num_downloaders == 1); + TORRENT_ASSERT(num_downloaders == 1); } else { - assert(num_downloaders == 0); + TORRENT_ASSERT(num_downloaders == 0); } */ } @@ -389,8 +389,8 @@ namespace libtorrent if (i->index == piece_pos::we_have_index) { - assert(t == 0 || t->have_piece(index)); - assert(i->downloading == 0); + TORRENT_ASSERT(t == 0 || t->have_piece(index)); + TORRENT_ASSERT(i->downloading == 0); /* // make sure there's no entry // with this index. (there shouldn't @@ -399,7 +399,7 @@ namespace libtorrent { for (int j = 0; j < int(m_piece_info[i].size()); ++j) { - assert(m_piece_info[i][j] != index); + TORRENT_ASSERT(m_piece_info[i][j] != index); } } */ @@ -407,22 +407,22 @@ namespace libtorrent else { if (t != 0) - assert(!t->have_piece(index)); + TORRENT_ASSERT(!t->have_piece(index)); int prio = i->priority(m_sequenced_download_threshold); - assert(prio < int(m_piece_info.size())); + TORRENT_ASSERT(prio < int(m_piece_info.size())); if (prio > 0) { const std::vector& vec = m_piece_info[prio]; assert (i->index < vec.size()); - assert(vec[i->index] == index); + TORRENT_ASSERT(vec[i->index] == index); } /* for (int k = 0; k < int(m_piece_info.size()); ++k) { for (int j = 0; j < int(m_piece_info[k].size()); ++j) { - assert(int(m_piece_info[k][j]) != index + TORRENT_ASSERT(int(m_piece_info[k][j]) != index || (prio > 0 && prio == k && int(i->index) == j)); } } @@ -433,16 +433,16 @@ namespace libtorrent , has_index(index)); if (i->downloading == 1) { - assert(count == 1); + TORRENT_ASSERT(count == 1); } else { - assert(count == 0); + TORRENT_ASSERT(count == 0); } } - assert(num_have == m_num_have); - assert(num_filtered == m_num_filtered); - assert(num_have_filtered == m_num_have_filtered); + TORRENT_ASSERT(num_have == m_num_have); + TORRENT_ASSERT(num_filtered == m_num_filtered); + TORRENT_ASSERT(num_have_filtered == m_num_have_filtered); } #endif @@ -474,34 +474,34 @@ namespace libtorrent } else { - assert(peer_count > min_availability); + TORRENT_ASSERT(peer_count > min_availability); ++fraction_part; } } - assert(integer_part + fraction_part == num_pieces); + TORRENT_ASSERT(integer_part + fraction_part == num_pieces); return float(min_availability) + (fraction_part / num_pieces); } void piece_picker::add(int index) { - assert(index >= 0); - assert(index < int(m_piece_map.size())); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); piece_pos& p = m_piece_map[index]; - assert(!p.filtered()); - assert(!p.have()); + TORRENT_ASSERT(!p.filtered()); + TORRENT_ASSERT(!p.have()); int priority = p.priority(m_sequenced_download_threshold); - assert(priority > 0); + TORRENT_ASSERT(priority > 0); if (int(m_piece_info.size()) <= priority) m_piece_info.resize(priority + 1); - assert(int(m_piece_info.size()) > priority); + TORRENT_ASSERT(int(m_piece_info.size()) > priority); if (is_ordered(priority)) { // the piece should be inserted ordered, not randomly std::vector& v = m_piece_info[priority]; -// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); +// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); std::vector::iterator i = std::lower_bound(v.begin(), v.end() , index/*, std::greater()*/); p.index = i - v.begin(); @@ -510,9 +510,9 @@ namespace libtorrent for (;i != v.end(); ++i) { ++m_piece_map[*i].index; - assert(v[m_piece_map[*i].index] == *i); + TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); } -// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); +// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); } else if (m_piece_info[priority].size() < 2) { @@ -542,17 +542,17 @@ namespace libtorrent // to place it at the correct position in the vectors. void piece_picker::move(int priority, int elem_index) { - assert(priority > 0); - assert(elem_index >= 0); - assert(m_files_checked_called); + TORRENT_ASSERT(priority > 0); + TORRENT_ASSERT(elem_index >= 0); + TORRENT_ASSERT(m_files_checked_called); - assert(int(m_piece_info.size()) > priority); - assert(int(m_piece_info[priority].size()) > elem_index); + TORRENT_ASSERT(int(m_piece_info.size()) > priority); + TORRENT_ASSERT(int(m_piece_info[priority].size()) > elem_index); int index = m_piece_info[priority][elem_index]; // update the piece_map piece_pos& p = m_piece_map[index]; - assert(int(p.index) == elem_index || p.have()); + TORRENT_ASSERT(int(p.index) == elem_index || p.have()); int new_priority = p.priority(m_sequenced_download_threshold); @@ -562,7 +562,7 @@ namespace libtorrent && new_priority > 0) { m_piece_info.resize(new_priority + 1); - assert(int(m_piece_info.size()) > new_priority); + TORRENT_ASSERT(int(m_piece_info.size()) > new_priority); } if (new_priority == 0) @@ -573,7 +573,7 @@ namespace libtorrent { // the piece should be inserted ordered, not randomly std::vector& v = m_piece_info[new_priority]; -// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); +// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); std::vector::iterator i = std::lower_bound(v.begin(), v.end() , index/*, std::greater()*/); p.index = i - v.begin(); @@ -582,9 +582,9 @@ namespace libtorrent for (;i != v.end(); ++i) { ++m_piece_map[*i].index; - assert(v[m_piece_map[*i].index] == *i); + TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); } -// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); +// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); } else if (m_piece_info[new_priority].size() < 2) { @@ -608,8 +608,8 @@ namespace libtorrent p.index = dst_index; m_piece_info[new_priority][p.index] = index; } - assert(new_priority == 0 || p.index < m_piece_info[p.priority(m_sequenced_download_threshold)].size()); - assert(new_priority == 0 || m_piece_info[p.priority(m_sequenced_download_threshold)][p.index] == index); + TORRENT_ASSERT(new_priority == 0 || p.index < m_piece_info[p.priority(m_sequenced_download_threshold)].size()); + TORRENT_ASSERT(new_priority == 0 || m_piece_info[p.priority(m_sequenced_download_threshold)][p.index] == index); if (is_ordered(priority)) { @@ -620,7 +620,7 @@ namespace libtorrent i != v.end(); ++i) { --m_piece_map[*i].index; - assert(v[m_piece_map[*i].index] == *i); + TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); } } else @@ -633,16 +633,16 @@ namespace libtorrent // update the entry we moved from the back m_piece_map[replace_index].index = elem_index; - assert(int(m_piece_info[priority].size()) > elem_index); + TORRENT_ASSERT(int(m_piece_info[priority].size()) > elem_index); // this may not necessarily be the case. If we've just updated the threshold and are updating // the piece map -// assert((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); - assert(int(m_piece_map[replace_index].index) == elem_index); - assert(m_piece_info[priority][elem_index] == replace_index); +// TORRENT_ASSERT((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); + TORRENT_ASSERT(int(m_piece_map[replace_index].index) == elem_index); + TORRENT_ASSERT(m_piece_info[priority][elem_index] == replace_index); } else { - assert(int(m_piece_info[priority].size()) == elem_index+1); + TORRENT_ASSERT(int(m_piece_info[priority].size()) == elem_index+1); } m_piece_info[priority].pop_back(); @@ -651,13 +651,13 @@ namespace libtorrent void piece_picker::sort_piece(std::vector::iterator dp) { - assert(m_piece_map[dp->index].downloading); + TORRENT_ASSERT(m_piece_map[dp->index].downloading); if (dp == m_downloads.begin()) return; int complete = dp->writing + dp->finished; for (std::vector::iterator i = dp, j(dp-1); i != m_downloads.begin(); --i, --j) { - assert(j >= m_downloads.begin()); + TORRENT_ASSERT(j >= m_downloads.begin()); if (j->finished + j->writing >= complete) return; using std::swap; swap(*j, *i); @@ -669,17 +669,17 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(index >= 0); - assert(index < (int)m_piece_map.size()); - assert(m_files_checked_called); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_map.size()); + TORRENT_ASSERT(m_files_checked_called); - assert(m_piece_map[index].downloading == 1); + TORRENT_ASSERT(m_piece_map[index].downloading == 1); std::vector::iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); erase_download_piece(i); piece_pos& p = m_piece_map[index]; @@ -702,7 +702,7 @@ namespace libtorrent void piece_picker::inc_refcount_all() { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(m_files_checked_called); + TORRENT_ASSERT(m_files_checked_called); // in general priority = availability * 2 // see piece_block::priority() @@ -716,16 +716,16 @@ namespace libtorrent { m_piece_info.push_back(std::vector()); } - assert(m_piece_info.rbegin()->empty()); - assert((m_piece_info.rbegin()+1)->empty()); + TORRENT_ASSERT(m_piece_info.rbegin()->empty()); + TORRENT_ASSERT((m_piece_info.rbegin()+1)->empty()); typedef std::vector > piece_info_t; for (piece_info_t::reverse_iterator i = m_piece_info.rbegin(), j(i+1) , k(j+1), end(m_piece_info.rend()); k != end; ++i, ++j, ++k) { k->swap(*i); } - assert(m_piece_info.begin()->empty()); - assert((m_piece_info.begin()+1)->empty()); + TORRENT_ASSERT(m_piece_info.begin()->empty()); + TORRENT_ASSERT((m_piece_info.begin()+1)->empty()); // if we have some priorities that are clamped to the // sequenced download, move that vector back down @@ -757,14 +757,14 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); - assert(prev_prio < int(m_piece_info.size())); + TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); ++i->peer_count; // if the assumption that the priority would // increase by 2 when increasing the availability // by one isn't true for this particular piece, correct it. // that assumption is true for all pieces with priority 0 or 1 int new_prio = i->priority(m_sequenced_download_threshold); - assert(new_prio <= cap_index); + TORRENT_ASSERT(new_prio <= cap_index); if (prev_prio == 0 && new_prio > 0) { add(i - m_piece_map.begin()); @@ -772,17 +772,17 @@ namespace libtorrent } if (new_prio == 0) { - assert(prev_prio == 0); + TORRENT_ASSERT(prev_prio == 0); continue; } if (prev_prio == cap_index) { - assert(new_prio == cap_index); + TORRENT_ASSERT(new_prio == cap_index); continue; } if (new_prio == prev_prio + 2 && new_prio != cap_index) { - assert(new_prio != cap_index); + TORRENT_ASSERT(new_prio != cap_index); continue; } if (prev_prio + 2 >= cap_index) @@ -791,8 +791,8 @@ namespace libtorrent // passed the sequenced download threshold ++prev_prio; } - assert(prev_prio + 2 != cap_index); - assert(prev_prio + 2 != new_prio); + TORRENT_ASSERT(prev_prio + 2 != cap_index); + TORRENT_ASSERT(prev_prio + 2 != new_prio); move(prev_prio + 2, i->index); } } @@ -800,9 +800,9 @@ namespace libtorrent void piece_picker::dec_refcount_all() { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(m_files_checked_called); - assert(m_piece_info.size() >= 2); - assert(m_piece_info.front().empty()); + TORRENT_ASSERT(m_files_checked_called); + TORRENT_ASSERT(m_piece_info.size() >= 2); + TORRENT_ASSERT(m_piece_info.front().empty()); // swap all vectors two steps down if (m_piece_info.size() > 2) { @@ -824,14 +824,14 @@ namespace libtorrent // the last two to get the same layout in both cases m_piece_info[last_index].swap(m_piece_info[last_index-1]); } - assert(m_piece_info.back().empty()); + TORRENT_ASSERT(m_piece_info.back().empty()); int pushed_out_index = m_piece_info.size() - 2; int cap_index = m_sequenced_download_threshold * 2; - assert(m_piece_info[last_index].empty()); + TORRENT_ASSERT(m_piece_info[last_index].empty()); if (last_index >= cap_index) { - assert(pushed_out_index == cap_index - 1 + TORRENT_ASSERT(pushed_out_index == cap_index - 1 || m_piece_info[cap_index - 1].empty()); m_piece_info[cap_index].swap(m_piece_info[cap_index - 2]); if (cap_index == pushed_out_index) @@ -847,9 +847,9 @@ namespace libtorrent , end(m_piece_map.end()); i != end; ++i) { int prev_prio = i->priority(m_sequenced_download_threshold); - assert(prev_prio < int(m_piece_info.size())); - assert(pushed_out_index < int(m_piece_info.size())); - assert(i->peer_count > 0); + TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); + TORRENT_ASSERT(pushed_out_index < int(m_piece_info.size())); + TORRENT_ASSERT(i->peer_count > 0); --i->peer_count; // if the assumption that the priority would // decrease by 2 when decreasing the availability @@ -857,7 +857,7 @@ namespace libtorrent // that assumption is true for all pieces with priority 0 or 1 if (prev_prio == 0) { - assert(i->priority(m_sequenced_download_threshold) == 0); + TORRENT_ASSERT(i->priority(m_sequenced_download_threshold) == 0); continue; } @@ -875,7 +875,7 @@ namespace libtorrent { // if this piece was pushed down to priority 0, it was // removed - assert(new_prio > 0); + TORRENT_ASSERT(new_prio > 0); add(i - m_piece_map.begin()); continue; } @@ -893,18 +893,18 @@ namespace libtorrent void piece_picker::inc_refcount(int i) { // TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(i >= 0); - assert(i < (int)m_piece_map.size()); - assert(m_files_checked_called); + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < (int)m_piece_map.size()); + TORRENT_ASSERT(m_files_checked_called); piece_pos& p = m_piece_map[i]; int index = p.index; int prev_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); + TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); - assert(p.peer_count < piece_pos::max_peer_count); + TORRENT_ASSERT(p.peer_count < piece_pos::max_peer_count); p.peer_count++; - assert(p.peer_count != 0); + TORRENT_ASSERT(p.peer_count != 0); // if we have the piece or if it's filtered // we don't have to move any entries in the piece_info vector @@ -929,15 +929,15 @@ namespace libtorrent { // TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(m_files_checked_called); - assert(i >= 0); - assert(i < (int)m_piece_map.size()); + TORRENT_ASSERT(m_files_checked_called); + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < (int)m_piece_map.size()); piece_pos& p = m_piece_map[i]; int prev_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); + TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); int index = p.index; - assert(p.peer_count > 0); + TORRENT_ASSERT(p.peer_count > 0); if (p.peer_count > 0) p.peer_count--; @@ -954,26 +954,26 @@ namespace libtorrent void piece_picker::we_have(int index) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(index >= 0); - assert(index < (int)m_piece_map.size()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_map.size()); piece_pos& p = m_piece_map[index]; int info_index = p.index; int priority = p.priority(m_sequenced_download_threshold); - assert(priority < int(m_piece_info.size())); + TORRENT_ASSERT(priority < int(m_piece_info.size())); - assert(p.downloading == 1); - assert(!p.have()); + TORRENT_ASSERT(p.downloading == 1); + TORRENT_ASSERT(!p.have()); std::vector::iterator i = std::find_if(m_downloads.begin() , m_downloads.end() , has_index(index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); erase_download_piece(i); p.downloading = 0; - assert(std::find_if(m_downloads.begin(), m_downloads.end() + TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(index)) == m_downloads.end()); if (p.have()) return; @@ -985,7 +985,7 @@ namespace libtorrent ++m_num_have; p.set_have(); if (priority == 0) return; - assert(p.priority(m_sequenced_download_threshold) == 0); + TORRENT_ASSERT(p.priority(m_sequenced_download_threshold) == 0); move(priority, info_index); } @@ -993,10 +993,10 @@ namespace libtorrent bool piece_picker::set_piece_priority(int index, int new_piece_priority) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(new_piece_priority >= 0); - assert(new_piece_priority <= 7); - assert(index >= 0); - assert(index < (int)m_piece_map.size()); + TORRENT_ASSERT(new_piece_priority >= 0); + TORRENT_ASSERT(new_piece_priority <= 7); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_map.size()); piece_pos& p = m_piece_map[index]; @@ -1004,7 +1004,7 @@ namespace libtorrent if (new_piece_priority == int(p.piece_priority)) return false; int prev_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); + TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); bool ret = false; if (new_piece_priority == piece_pos::filter_priority @@ -1023,12 +1023,12 @@ namespace libtorrent else --m_num_filtered; ret = true; } - assert(m_num_filtered >= 0); - assert(m_num_have_filtered >= 0); + TORRENT_ASSERT(m_num_filtered >= 0); + TORRENT_ASSERT(m_num_have_filtered >= 0); p.piece_priority = new_piece_priority; int new_priority = p.priority(m_sequenced_download_threshold); - assert(prev_priority < int(m_piece_info.size())); + TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); if (new_priority == prev_priority) return false; @@ -1045,8 +1045,8 @@ namespace libtorrent int piece_picker::piece_priority(int index) const { - assert(index >= 0); - assert(index < (int)m_piece_map.size()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_map.size()); return m_piece_map[index].piece_priority; } @@ -1099,11 +1099,11 @@ namespace libtorrent , bool on_parole, std::vector const& suggested_pieces) const { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(num_blocks > 0); - assert(pieces.size() == m_piece_map.size()); - assert(m_files_checked_called); + TORRENT_ASSERT(num_blocks > 0); + TORRENT_ASSERT(pieces.size() == m_piece_map.size()); + TORRENT_ASSERT(m_files_checked_called); - assert(m_piece_info.begin() != m_piece_info.end()); + TORRENT_ASSERT(m_piece_info.begin() != m_piece_info.end()); // this will be filled with blocks that we should not request // unless we can't find num_blocks among the other ones. @@ -1151,8 +1151,8 @@ namespace libtorrent for (std::vector::const_iterator i = suggested_pieces.begin() , end(suggested_pieces.end()); i != end; ++i) { - assert(*i >= 0); - assert(*i < int(m_piece_map.size())); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(*i < int(m_piece_map.size())); if (!can_pick(*i, pieces)) continue; if (m_piece_map[*i].priority(m_sequenced_download_threshold) == bucket_index) suggested_bucket.push_back(*i); @@ -1168,7 +1168,7 @@ namespace libtorrent num_blocks = add_blocks(*bucket, pieces , interesting_blocks, num_blocks , prefer_whole_pieces, peer, suggested_bucket); - assert(num_blocks >= 0); + TORRENT_ASSERT(num_blocks >= 0); } } else @@ -1201,8 +1201,8 @@ namespace libtorrent boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces); for (int k = start; k < end; ++k) { - assert(m_piece_map[piece].downloading == false); - assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[piece].downloading == false); + TORRENT_ASSERT(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); int num_blocks_in_piece = blocks_in_piece(k); if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; @@ -1229,7 +1229,7 @@ namespace libtorrent bool piece_picker::can_pick(int piece, std::vector const& bitmask) const { - assert(piece >= 0 && piece < int(m_piece_map.size())); + TORRENT_ASSERT(piece >= 0 && piece < int(m_piece_map.size())); return bitmask[piece] && !m_piece_map[piece].have() && !m_piece_map[piece].downloading @@ -1282,8 +1282,8 @@ namespace libtorrent for (std::vector::const_iterator i = piece_list.begin(); i != piece_list.end(); ++i) { - assert(*i >= 0); - assert(*i < (int)m_piece_map.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(*i < (int)m_piece_map.size()); // if the peer doesn't have the piece // skip it @@ -1293,12 +1293,12 @@ namespace libtorrent if (std::find(ignore.begin(), ignore.end(), *i) != ignore.end()) continue; // skip the piece is the priority is 0 - assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); int num_blocks_in_piece = blocks_in_piece(*i); - assert(m_piece_map[*i].downloading == 0); - assert(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[*i].downloading == 0); + TORRENT_ASSERT(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); // pick a new piece if (prefer_whole_pieces == 0) @@ -1315,7 +1315,7 @@ namespace libtorrent boost::tie(start, end) = expand_piece(*i, prefer_whole_pieces, pieces); for (int k = start; k < end; ++k) { - assert(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); num_blocks_in_piece = blocks_in_piece(k); for (int j = 0; j < num_blocks_in_piece; ++j) { @@ -1379,7 +1379,7 @@ namespace libtorrent if (info.state != block_info::state_none) continue; - assert(i->info[j].state == block_info::state_none); + TORRENT_ASSERT(i->info[j].state == block_info::state_none); // if the piece is fast and the peer is slow, or vice versa, // add the block as a backup. @@ -1402,13 +1402,13 @@ namespace libtorrent // if we prefer whole pieces, continue picking from this // piece even though we have num_blocks if (prefer_whole_pieces > 0) continue; - assert(num_blocks >= 0); + TORRENT_ASSERT(num_blocks >= 0); if (num_blocks <= 0) break; } if (num_blocks <= 0) break; } - assert(num_blocks >= 0 || prefer_whole_pieces > 0); + TORRENT_ASSERT(num_blocks >= 0 || prefer_whole_pieces > 0); #ifndef NDEBUG verify_pick(interesting_blocks, pieces); @@ -1500,7 +1500,7 @@ namespace libtorrent std::cerr << std::endl; } - assert(false); + TORRENT_ASSERT(false); } } #endif @@ -1541,7 +1541,7 @@ namespace libtorrent && can_pick(start, have)) --start; ++start; - assert(start >= 0); + TORRENT_ASSERT(start >= 0); int end = piece + 1; int upper_limit = start + whole_pieces; if (upper_limit > int(m_piece_map.size())) upper_limit = int(m_piece_map.size()); @@ -1553,38 +1553,38 @@ namespace libtorrent bool piece_picker::is_piece_finished(int index) const { - assert(index < (int)m_piece_map.size()); - assert(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_map.size()); + TORRENT_ASSERT(index >= 0); if (m_piece_map[index].downloading == 0) { - assert(std::find_if(m_downloads.begin(), m_downloads.end() + TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(index)) == m_downloads.end()); return false; } std::vector::const_iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); - assert(i != m_downloads.end()); - assert((int)i->finished <= m_blocks_per_piece); + TORRENT_ASSERT(i != m_downloads.end()); + TORRENT_ASSERT((int)i->finished <= m_blocks_per_piece); int max_blocks = blocks_in_piece(index); if ((int)i->finished < max_blocks) return false; #ifndef NDEBUG for (int k = 0; k < max_blocks; ++k) { - assert(i->info[k].state == block_info::state_finished); + TORRENT_ASSERT(i->info[k].state == block_info::state_finished); } #endif - assert((int)i->finished == max_blocks); + TORRENT_ASSERT((int)i->finished == max_blocks); return true; } bool piece_picker::is_requested(piece_block block) const { - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); if (m_piece_map[block.piece_index].downloading == 0) return false; std::vector::const_iterator i @@ -1593,36 +1593,36 @@ namespace libtorrent , m_downloads.end() , has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); return i->info[block.block_index].state == block_info::state_requested; } bool piece_picker::is_downloaded(piece_block block) const { - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true; if (m_piece_map[block.piece_index].downloading == 0) return false; std::vector::const_iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); return i->info[block.block_index].state == block_info::state_finished || i->info[block.block_index].state == block_info::state_writing; } bool piece_picker::is_finished(piece_block block) const { - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true; if (m_piece_map[block.piece_index].downloading == 0) return false; std::vector::const_iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); return i->info[block.block_index].state == block_info::state_finished; } @@ -1632,18 +1632,18 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); - assert(block.block_index < blocks_in_piece(block.piece_index)); - assert(!m_piece_map[block.piece_index].have()); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(!m_piece_map[block.piece_index].have()); piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { int prio = p.priority(m_sequenced_download_threshold); - assert(prio < int(m_piece_info.size())); - assert(prio > 0); + TORRENT_ASSERT(prio < int(m_piece_info.size())); + TORRENT_ASSERT(prio > 0); p.downloading = 1; move(prio, p.index); @@ -1660,12 +1660,12 @@ namespace libtorrent { std::vector::iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); block_info& info = i->info[block.block_index]; if (info.state == block_info::state_writing || info.state == block_info::state_finished) return false; - assert(info.state == block_info::state_none + TORRENT_ASSERT(info.state == block_info::state_none || (info.state == block_info::state_requested && (info.num_peers > 0))); info.peer = peer; @@ -1682,17 +1682,17 @@ namespace libtorrent int piece_picker::num_peers(piece_block block) const { - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); - assert(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); piece_pos const& p = m_piece_map[block.piece_index]; if (!p.downloading) return 0; std::vector::const_iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); block_info const& info = i->info[block.block_index]; return info.num_peers; @@ -1713,26 +1713,26 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); - assert(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); - assert(m_piece_map[block.piece_index].downloading); + TORRENT_ASSERT(m_piece_map[block.piece_index].downloading); std::vector::iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); block_info& info = i->info[block.block_index]; info.peer = peer; - assert(info.state == block_info::state_requested); + TORRENT_ASSERT(info.state == block_info::state_requested); if (info.state == block_info::state_requested) --i->requested; - assert(i->requested >= 0); - assert(info.state != block_info::state_writing); + TORRENT_ASSERT(i->requested >= 0); + TORRENT_ASSERT(info.state != block_info::state_writing); ++i->writing; info.state = block_info::state_writing; if (info.num_peers > 0) --info.num_peers; - assert(info.num_peers >= 0); + TORRENT_ASSERT(info.num_peers >= 0); if (i->requested == 0) { @@ -1745,10 +1745,10 @@ namespace libtorrent void piece_picker::mark_as_finished(piece_block block, void* peer) { - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); - assert(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); piece_pos& p = m_piece_map[block.piece_index]; @@ -1756,19 +1756,19 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(peer == 0); + TORRENT_ASSERT(peer == 0); int prio = p.priority(m_sequenced_download_threshold); - assert(prio < int(m_piece_info.size())); + TORRENT_ASSERT(prio < int(m_piece_info.size())); p.downloading = 1; if (prio > 0) move(prio, p.index); - else assert(p.priority(m_sequenced_download_threshold) == 0); + else TORRENT_ASSERT(p.priority(m_sequenced_download_threshold) == 0); downloading_piece& dp = add_download_piece(); dp.state = none; dp.index = block.piece_index; block_info& info = dp.info[block.block_index]; info.peer = peer; - assert(info.state == block_info::state_none); + TORRENT_ASSERT(info.state == block_info::state_none); if (info.state != block_info::state_finished) { ++dp.finished; @@ -1782,12 +1782,12 @@ namespace libtorrent std::vector::iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); block_info& info = i->info[block.block_index]; info.peer = peer; - assert(info.state == block_info::state_writing + TORRENT_ASSERT(info.state == block_info::state_writing || peer == 0); - assert(i->writing >= 0); + TORRENT_ASSERT(i->writing >= 0); ++i->finished; if (info.state == block_info::state_writing) { @@ -1804,10 +1804,10 @@ namespace libtorrent void piece_picker::get_downloaders(std::vector& d, int index) const { - assert(index >= 0 && index <= (int)m_piece_map.size()); + TORRENT_ASSERT(index >= 0 && index <= (int)m_piece_map.size()); std::vector::const_iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); d.clear(); for (int j = 0; j < blocks_in_piece(index); ++j) @@ -1825,7 +1825,7 @@ namespace libtorrent if (i == m_downloads.end()) return 0; - assert(block.block_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); if (i->info[block.block_index].state == block_info::state_none) return 0; @@ -1837,25 +1837,25 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - assert(block.piece_index >= 0); - assert(block.block_index >= 0); - assert(block.piece_index < (int)m_piece_map.size()); - assert(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); if (m_piece_map[block.piece_index].downloading == 0) { - assert(std::find_if(m_downloads.begin(), m_downloads.end() + TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); return; } std::vector::iterator i = std::find_if(m_downloads.begin() , m_downloads.end(), has_index(block.piece_index)); - assert(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads.end()); block_info& info = i->info[block.block_index]; --info.num_peers; - assert(info.num_peers >= 0); + TORRENT_ASSERT(info.num_peers >= 0); if (info.num_peers > 0) return; if (i->info[block.block_index].state == block_info::state_finished @@ -1864,8 +1864,8 @@ namespace libtorrent return; } - assert(block.block_index < blocks_in_piece(block.piece_index)); - assert(i->info[block.block_index].state == block_info::state_requested); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(i->info[block.block_index].state == block_info::state_requested); // clear this block as being downloaded info.state = block_info::state_none; @@ -1881,13 +1881,13 @@ namespace libtorrent erase_download_piece(i); piece_pos& p = m_piece_map[block.piece_index]; int prev_prio = p.priority(m_sequenced_download_threshold); - assert(prev_prio < int(m_piece_info.size())); + TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); p.downloading = 0; int prio = p.priority(m_sequenced_download_threshold); if (prev_prio == 0 && prio > 0) add(block.piece_index); else if (prio > 0) move(prio, p.index); - assert(std::find_if(m_downloads.begin(), m_downloads.end() + TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); } else if (i->requested == 0) diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 212e09f3c..5c69ea3bd 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -83,16 +83,16 @@ namespace // (and we should not consider it free). If the share diff is // negative, there's no free download to get from this peer. size_type diff = i->second->share_diff(); - assert(diff < (std::numeric_limits::max)()); + TORRENT_ASSERT(diff < (std::numeric_limits::max)()); if (i->second->is_peer_interested() || diff <= 0) continue; - assert(diff > 0); + TORRENT_ASSERT(diff > 0); i->second->add_free_upload(-diff); accumulator += diff; - assert(accumulator > 0); + TORRENT_ASSERT(accumulator > 0); } - assert(accumulator >= 0); + TORRENT_ASSERT(accumulator >= 0); return accumulator; } @@ -110,7 +110,7 @@ namespace for (torrent::peer_iterator i = start; i != end; ++i) { size_type d = i->second->share_diff(); - assert(d < (std::numeric_limits::max)()); + TORRENT_ASSERT(d < (std::numeric_limits::max)()); total_diff += d; if (!i->second->is_peer_interested() || i->second->share_diff() >= 0) continue; ++num_peers; @@ -191,8 +191,8 @@ namespace libtorrent { if (t.is_seed()) return; - assert(t.valid_metadata()); - assert(c.peer_info_struct() != 0 || !dynamic_cast(&c)); + TORRENT_ASSERT(t.valid_metadata()); + TORRENT_ASSERT(c.peer_info_struct() != 0 || !dynamic_cast(&c)); int num_requests = c.desired_queue_size() - (int)c.download_queue().size() - (int)c.request_queue().size(); @@ -200,7 +200,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*c.m_logger) << time_now_string() << " PIECE_PICKER [ req: " << num_requests << " ]\n"; #endif - assert(c.desired_queue_size() > 0); + TORRENT_ASSERT(c.desired_queue_size() > 0); // if our request queue is already full, we // don't have to make any new requests yet if (num_requests <= 0) return; @@ -224,7 +224,7 @@ namespace libtorrent // the number of blocks we want, but it will try to make the picked // blocks be from whole pieces, possibly by returning more blocks // than we requested. - assert(c.remote() == c.get_socket()->remote_endpoint()); + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint()); piece_picker::piece_state_t state; peer_connection::peer_speed_t speed = c.peer_speed(); @@ -298,13 +298,13 @@ namespace libtorrent continue; } - assert(p.num_peers(*i) == 0); + TORRENT_ASSERT(p.num_peers(*i) == 0); // ok, we found a piece that's not being downloaded // by somebody else. request it from this peer // and return c.add_request(*i); - assert(p.num_peers(*i) == 1); - assert(p.is_requested(*i)); + TORRENT_ASSERT(p.num_peers(*i) == 1); + TORRENT_ASSERT(p.is_requested(*i)); num_requests--; } @@ -331,7 +331,7 @@ namespace libtorrent #ifndef NDEBUG piece_picker::downloading_piece st; p.piece_info(i->piece_index, st); - assert(st.requested + st.finished + st.writing == p.blocks_in_piece(i->piece_index)); + TORRENT_ASSERT(st.requested + st.finished + st.writing == p.blocks_in_piece(i->piece_index)); #endif c.add_request(*i); c.send_block_requests(); @@ -341,7 +341,7 @@ namespace libtorrent : m_torrent(t) , m_available_free_upload(0) // , m_last_optimistic_disconnect(min_time()) - { assert(t); } + { TORRENT_ASSERT(t); } // disconnects and removes all peers that are now filtered void policy::ip_filter_updated() @@ -367,7 +367,7 @@ namespace libtorrent ses.m_alerts.post_alert(peer_blocked_alert(i->second.ip.address() , "disconnected blocked peer")); } - assert(i->second.connection == 0 + TORRENT_ASSERT(i->second.connection == 0 || i->second.connection->peer_info_struct() == 0); } else @@ -427,7 +427,7 @@ namespace libtorrent worst_peer = i; continue; } - assert(unchoked_counter == 0); + TORRENT_ASSERT(unchoked_counter == 0); return worst_peer; } @@ -538,7 +538,7 @@ namespace libtorrent if (ses.m_port_filter.access(i->second.ip.port()) & port_filter::blocked) continue; - assert(i->second.connected <= now); + TORRENT_ASSERT(i->second.connected <= now); if (i->second.connected <= min_connect_time) { @@ -547,7 +547,7 @@ namespace libtorrent } } - assert(min_connect_time <= now); + TORRENT_ASSERT(min_connect_time <= now); return candidate; } @@ -556,7 +556,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_num_unchoked > 0); + TORRENT_ASSERT(m_num_unchoked > 0); // first choice candidate. // it is a candidate we owe nothing to and which has been unchoked // the longest. @@ -604,7 +604,7 @@ namespace libtorrent } } if (candidate != m_peers.end()) return candidate; - assert(second_candidate != m_peers.end()); + TORRENT_ASSERT(second_candidate != m_peers.end()); return second_candidate; } @@ -637,7 +637,7 @@ namespace libtorrent iterator p = find_seed_unchoke_candidate(); if (p != m_peers.end()) { - assert(p->connection->is_choked()); + TORRENT_ASSERT(p->connection->is_choked()); p->connection->send_unchoke(); p->last_optimistically_unchoked = time_now(); ++m_num_unchoked; @@ -652,7 +652,7 @@ namespace libtorrent iterator p = find_seed_choke_candidate(); if (p != m_peers.end()) { - assert(!p->connection->is_choked()); + TORRENT_ASSERT(!p->connection->is_choked()); p->connection->send_choke(); --m_num_unchoked; } @@ -730,7 +730,7 @@ namespace libtorrent { bool ret = disconnect_one_peer(); (void)ret; - assert(ret); + TORRENT_ASSERT(ret); --num_connected_peers; } } @@ -777,10 +777,10 @@ namespace libtorrent { iterator p = find_seed_choke_candidate(); --m_num_unchoked; - assert(p != m_peers.end()); + TORRENT_ASSERT(p != m_peers.end()); if (p == m_peers.end()) break; - assert(!p->connection->is_choked()); + TORRENT_ASSERT(!p->connection->is_choked()); p->connection->send_choke(); } while (m_num_unchoked > m_torrent->m_uploads_quota.given); } @@ -790,11 +790,11 @@ namespace libtorrent // unchoked peer with one of the choked // TODO: This rotation should happen // far less frequent than this! - assert(m_num_unchoked <= m_torrent->num_peers()); + TORRENT_ASSERT(m_num_unchoked <= m_torrent->num_peers()); iterator p = find_seed_unchoke_candidate(); if (p != m_peers.end()) { - assert(p->connection->is_choked()); + TORRENT_ASSERT(p->connection->is_choked()); seed_choke_one_peer(); p->connection->send_unchoke(); ++m_num_unchoked; @@ -841,7 +841,7 @@ namespace libtorrent if (m_torrent->m_uploads_quota.given < m_torrent->num_peers()) { - assert(m_torrent->m_uploads_quota.given >= 0); + TORRENT_ASSERT(m_torrent->m_uploads_quota.given >= 0); // make sure we don't have too many // unchoked peers @@ -851,8 +851,8 @@ namespace libtorrent { iterator p = find_choke_candidate(); if (p == m_peers.end()) break; - assert(p != m_peers.end()); - assert(!p->connection->is_choked()); + TORRENT_ASSERT(p != m_peers.end()); + TORRENT_ASSERT(!p->connection->is_choked()); p->connection->send_choke(); --m_num_unchoked; } while (m_num_unchoked > m_torrent->m_uploads_quota.given); @@ -864,11 +864,11 @@ namespace libtorrent { // optimistic unchoke. trade the 'worst' // unchoked peer with one of the choked - assert(m_num_unchoked <= m_torrent->num_peers()); + TORRENT_ASSERT(m_num_unchoked <= m_torrent->num_peers()); iterator p = find_unchoke_candidate(); if (p != m_peers.end()) { - assert(p->connection->is_choked()); + TORRENT_ASSERT(p->connection->is_choked()); choke_one_peer(); p->connection->send_unchoke(); ++m_num_unchoked; @@ -902,7 +902,7 @@ namespace libtorrent void policy::new_connection(peer_connection& c) { - assert(!c.is_local()); + TORRENT_ASSERT(!c.is_local()); INVARIANT_CHECK; @@ -912,7 +912,7 @@ namespace libtorrent // TODO: only allow _one_ connection to use this // override at a time - assert(c.remote() == c.get_socket()->remote_endpoint()); + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint()); if (m_torrent->num_peers() >= m_torrent->max_connections() && m_torrent->session().num_connections() >= m_torrent->session().max_connections() @@ -949,7 +949,7 @@ namespace libtorrent if (i->second.connection != 0) { - assert(i->second.connection != &c); + TORRENT_ASSERT(i->second.connection != &c); // the new connection is a local (outgoing) connection // or the current one is already connected if (!i->second.connection->is_connecting() || c.is_local()) @@ -971,21 +971,21 @@ namespace libtorrent { // we don't have any info about this peer. // add a new entry - assert(c.remote() == c.get_socket()->remote_endpoint()); + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint()); peer p(c.remote(), peer::not_connectable, 0); i = m_peers.insert(std::make_pair(c.remote().address(), p)); } - assert(m_torrent->connection_for(c.remote()) == &c); + TORRENT_ASSERT(m_torrent->connection_for(c.remote()) == &c); c.set_peer_info(&i->second); - assert(i->second.connection == 0); + TORRENT_ASSERT(i->second.connection == 0); c.add_stat(i->second.prev_amount_download, i->second.prev_amount_upload); i->second.prev_amount_download = 0; i->second.prev_amount_upload = 0; i->second.connection = &c; - assert(i->second.connection); + TORRENT_ASSERT(i->second.connection); i->second.connected = time_now(); // m_last_optimistic_disconnect = time_now(); } @@ -1094,7 +1094,7 @@ namespace libtorrent + boost::lexical_cast(remote.port()) + " " + boost::lexical_cast(i->second.connection->pid())); - assert(i->second.connection->associated_torrent().lock().get() == m_torrent); + TORRENT_ASSERT(i->second.connection->associated_torrent().lock().get() == m_torrent); } #endif } @@ -1122,7 +1122,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(index >= 0 && index < m_torrent->torrent_file().num_pieces()); + TORRENT_ASSERT(index >= 0 && index < m_torrent->torrent_file().num_pieces()); if (successfully_verified) { @@ -1157,7 +1157,7 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(std::find_if(m_peers.begin(), m_peers.end() + TORRENT_ASSERT(std::find_if(m_peers.begin(), m_peers.end() , boost::bind(std::equal_to(), bind(&peer::connection , bind(&iterator::value_type::second, _1)), &c)) != m_peers.end()); @@ -1213,7 +1213,7 @@ namespace libtorrent if (m_torrent->ratio() != 0.f) { - assert(c.share_diff() < (std::numeric_limits::max)()); + TORRENT_ASSERT(c.share_diff() < (std::numeric_limits::max)()); size_type diff = c.share_diff(); if (diff > 0 && c.is_seed()) { @@ -1242,10 +1242,10 @@ namespace libtorrent iterator p = find_unchoke_candidate(); if (p == m_peers.end()) return false; - assert(p->connection); - assert(!p->connection->is_disconnecting()); + TORRENT_ASSERT(p->connection); + TORRENT_ASSERT(!p->connection->is_disconnecting()); - assert(p->connection->is_choked()); + TORRENT_ASSERT(p->connection->is_choked()); p->connection->send_unchoke(); p->last_optimistically_unchoked = time_now(); ++m_num_unchoked; @@ -1258,9 +1258,9 @@ namespace libtorrent iterator p = find_choke_candidate(); if (p == m_peers.end()) return; - assert(p->connection); - assert(!p->connection->is_disconnecting()); - assert(!p->connection->is_choked()); + TORRENT_ASSERT(p->connection); + TORRENT_ASSERT(!p->connection->is_disconnecting()); + TORRENT_ASSERT(!p->connection->is_choked()); p->connection->send_choke(); --m_num_unchoked; } @@ -1269,20 +1269,20 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(m_torrent->want_more_peers()); + TORRENT_ASSERT(m_torrent->want_more_peers()); iterator p = find_connect_candidate(); if (p == m_peers.end()) return false; - assert(!p->second.banned); - assert(!p->second.connection); - assert(p->second.type == peer::connectable); + TORRENT_ASSERT(!p->second.banned); + TORRENT_ASSERT(!p->second.connection); + TORRENT_ASSERT(p->second.type == peer::connectable); try { p->second.connected = time_now(); p->second.connection = m_torrent->connect_to_peer(&p->second); - assert(p->second.connection == m_torrent->connection_for(p->second.ip)); + TORRENT_ASSERT(p->second.connection == m_torrent->connection_for(p->second.ip)); if (p->second.connection == 0) { ++p->second.failcount; @@ -1326,7 +1326,7 @@ namespace libtorrent peer* p = c.peer_info_struct(); - assert((std::find_if( + TORRENT_ASSERT((std::find_if( m_peers.begin() , m_peers.end() , match_peer_connection(c)) @@ -1335,12 +1335,16 @@ namespace libtorrent // if we couldn't find the connection in our list, just ignore it. if (p == 0) return; - assert(p->connection == &c); + TORRENT_ASSERT(p->connection == &c); p->connection = 0; p->optimistically_unchoked = false; - p->connected = time_now(); + // if fast reconnect is true, we won't + // update the timestamp, and it will remain + // the time when we initiated the connection. + if (!c.fast_reconnect()) + p->connected = time_now(); if (c.failed()) { @@ -1353,8 +1357,8 @@ namespace libtorrent // because it isn't necessary. if (m_torrent->ratio() != 0.f) { - assert(c.associated_torrent().lock().get() == m_torrent); - assert(c.share_diff() < (std::numeric_limits::max)()); + TORRENT_ASSERT(c.associated_torrent().lock().get() == m_torrent); + TORRENT_ASSERT(c.share_diff() < (std::numeric_limits::max)()); m_available_free_upload += c.share_diff(); } p->prev_amount_download += c.statistics().total_payload_download(); @@ -1378,8 +1382,8 @@ namespace libtorrent // too expensive // INVARIANT_CHECK; - assert(c); - try { assert(c->remote() == c->get_socket()->remote_endpoint()); } + TORRENT_ASSERT(c); + try { TORRENT_ASSERT(c->remote() == c->get_socket()->remote_endpoint()); } catch (std::exception&) {} return std::find_if( @@ -1403,11 +1407,11 @@ namespace libtorrent peer const& p = i->second; if (!m_torrent->settings().allow_multiple_connections_per_ip) { - assert(m_peers.count(p.ip.address()) == 1); + TORRENT_ASSERT(m_peers.count(p.ip.address()) == 1); } else { - assert(unique_test.count(p.ip) == 0); + TORRENT_ASSERT(unique_test.count(p.ip) == 0); unique_test.insert(p.ip); } ++total_connections; @@ -1419,16 +1423,16 @@ namespace libtorrent { std::vector conns; m_torrent->connection_for(p.ip.address(), conns); - assert(std::find_if(conns.begin(), conns.end() + TORRENT_ASSERT(std::find_if(conns.begin(), conns.end() , boost::bind(std::equal_to(), _1, p.connection)) != conns.end()); } if (p.optimistically_unchoked) { - assert(p.connection); - assert(!p.connection->is_choked()); + TORRENT_ASSERT(p.connection); + TORRENT_ASSERT(!p.connection->is_choked()); } - assert(p.connection->peer_info_struct() == 0 + TORRENT_ASSERT(p.connection->peer_info_struct() == 0 || p.connection->peer_info_struct() == &p); ++nonempty_connections; if (!p.connection->is_disconnecting()) @@ -1466,7 +1470,7 @@ namespace libtorrent { policy::peer* p = static_cast(*i); if (p == 0) continue; - assert(std::find_if(m_peers.begin(), m_peers.end() + TORRENT_ASSERT(std::find_if(m_peers.begin(), m_peers.end() , match_peer_connection(*p->connection)) != m_peers.end()); } } @@ -1480,7 +1484,7 @@ namespace libtorrent // be added to the torrent and then to the policy. // that's why the two second cases are in there. /* - assert(connected_peers == num_torrent_peers + TORRENT_ASSERT(connected_peers == num_torrent_peers || (connected_peers == num_torrent_peers + 1 && connected_peers > 0) || (connected_peers + 1 == num_torrent_peers @@ -1509,14 +1513,14 @@ namespace libtorrent , source(src) , connection(0) { - assert(connected < time_now()); + TORRENT_ASSERT(connected < time_now()); } size_type policy::peer::total_download() const { if (connection != 0) { - assert(prev_amount_download == 0); + TORRENT_ASSERT(prev_amount_download == 0); return connection->statistics().total_payload_download(); } else @@ -1529,7 +1533,7 @@ namespace libtorrent { if (connection != 0) { - assert(prev_amount_upload == 0); + TORRENT_ASSERT(prev_amount_upload == 0); return connection->statistics().total_payload_upload(); } else diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 478c6fe93..68cbd619f 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -110,14 +110,14 @@ namespace libtorrent : m_impl(new session_impl(listen_port_range, id, listen_interface)) { // turn off the filename checking in boost.filesystem - assert(listen_port_range.first > 0); - assert(listen_port_range.first < listen_port_range.second); + TORRENT_ASSERT(listen_port_range.first > 0); + TORRENT_ASSERT(listen_port_range.first < listen_port_range.second); #ifndef NDEBUG // this test was added after it came to my attention // that devstudios managed c++ failed to generate // correct code for boost.function boost::function0 test = boost::ref(*m_impl); - assert(!test.empty()); + TORRENT_ASSERT(!test.empty()); #endif } @@ -126,13 +126,13 @@ namespace libtorrent { #ifndef NDEBUG boost::function0 test = boost::ref(*m_impl); - assert(!test.empty()); + TORRENT_ASSERT(!test.empty()); #endif } session::~session() { - assert(m_impl); + TORRENT_ASSERT(m_impl); // if there is at least one destruction-proxy // abort the session and let the destructor // of the proxy to syncronize @@ -190,7 +190,7 @@ namespace libtorrent , bool paused , storage_constructor_type sc) { - assert(!ti.m_half_metadata); + TORRENT_ASSERT(!ti.m_half_metadata); boost::intrusive_ptr tip(new torrent_info(ti)); return m_impl->add_torrent(tip, save_path, resume_data , compact_mode, sc, paused, 0); @@ -205,7 +205,7 @@ namespace libtorrent , storage_constructor_type sc , void* userdata) { - assert(!ti->m_half_metadata); + TORRENT_ASSERT(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data , compact_mode, sc, paused, userdata); } diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 4001b35d7..6f42438b0 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -223,8 +223,8 @@ namespace detail { INVARIANT_CHECK; - assert(!m_torrents.empty()); - assert(m_torrents.front() == t); + TORRENT_ASSERT(!m_torrents.empty()); + TORRENT_ASSERT(m_torrents.front() == t); t->torrent_ptr->files_checked(t->unfinished_pieces); m_torrents.pop_front(); @@ -275,7 +275,7 @@ namespace detail // move the torrent from // m_torrents to m_processing - assert(m_torrents.front() == t); + TORRENT_ASSERT(m_torrents.front() == t); m_torrents.pop_front(); m_processing.push_back(t); @@ -303,7 +303,7 @@ namespace detail } t->torrent_ptr->abort(); - assert(!m_torrents.empty()); + TORRENT_ASSERT(!m_torrents.empty()); m_torrents.pop_front(); } catch(...) @@ -312,16 +312,16 @@ namespace detail std::cerr << "error while checking resume data\n"; #endif mutex::scoped_lock l(m_mutex); - assert(!m_torrents.empty()); + TORRENT_ASSERT(!m_torrents.empty()); m_torrents.pop_front(); - assert(false); + TORRENT_ASSERT(false); } if (!processing) continue; try { - assert(processing); + TORRENT_ASSERT(processing); float finished = false; float progress = 0.f; @@ -335,8 +335,8 @@ namespace detail processing->progress = progress; if (processing->abort) { - assert(!m_processing.empty()); - assert(m_processing.front() == processing); + TORRENT_ASSERT(!m_processing.empty()); + TORRENT_ASSERT(m_processing.front() == processing); processing->torrent_ptr->abort(); @@ -358,8 +358,8 @@ namespace detail INVARIANT_CHECK; - assert(!m_processing.empty()); - assert(m_processing.front() == processing); + TORRENT_ASSERT(!m_processing.empty()); + TORRENT_ASSERT(m_processing.front() == processing); // TODO: factor out the adding of torrents to the session // and to the checker thread to avoid duplicating the @@ -426,7 +426,7 @@ namespace detail processing->torrent_ptr->get_handle() , e.what())); } - assert(!m_processing.empty()); + TORRENT_ASSERT(!m_processing.empty()); processing->torrent_ptr->abort(); @@ -444,7 +444,7 @@ namespace detail std::cerr << "error while checking files\n"; #endif mutex::scoped_lock l(m_mutex); - assert(!m_processing.empty()); + TORRENT_ASSERT(!m_processing.empty()); processing.reset(); m_processing.pop_front(); @@ -454,7 +454,7 @@ namespace detail processing->processing = true; } - assert(false); + TORRENT_ASSERT(false); } } } @@ -484,7 +484,7 @@ namespace detail { if ((*i)->info_hash == info_hash) { - assert((*i)->processing == false); + TORRENT_ASSERT((*i)->processing == false); m_torrents.erase(i); return; } @@ -494,13 +494,13 @@ namespace detail { if ((*i)->info_hash == info_hash) { - assert((*i)->processing == false); + TORRENT_ASSERT((*i)->processing == false); m_processing.erase(i); return; } } - assert(false); + TORRENT_ASSERT(false); } #ifndef NDEBUG @@ -509,14 +509,14 @@ namespace detail for (std::deque >::const_iterator i = m_torrents.begin(); i != m_torrents.end(); ++i) { - assert(*i); - assert((*i)->torrent_ptr); + TORRENT_ASSERT(*i); + TORRENT_ASSERT((*i)->torrent_ptr); } for (std::deque >::const_iterator i = m_processing.begin(); i != m_processing.end(); ++i) { - assert(*i); - assert((*i)->torrent_ptr); + TORRENT_ASSERT(*i); + TORRENT_ASSERT((*i)->torrent_ptr); } } #endif @@ -593,7 +593,7 @@ namespace detail m_key = rand() + (rand() << 15) + (rand() << 30); std::string print = cl_fprint.to_string(); - assert(print.length() <= 20); + TORRENT_ASSERT(print.length() <= 20); // the client's fingerprint std::copy( @@ -638,7 +638,7 @@ namespace detail void session_impl::abort() { mutex_t::scoped_lock l(m_mutex); - assert(!m_abort); + TORRENT_ASSERT(!m_abort); // abort the main thread m_abort = true; m_io_service.stop(); @@ -676,11 +676,11 @@ namespace detail INVARIANT_CHECK; - assert(s.connection_speed > 0); - assert(s.file_pool_size > 0); + TORRENT_ASSERT(s.connection_speed > 0); + TORRENT_ASSERT(s.file_pool_size > 0); // less than 5 seconds unchoke interval is insane - assert(s.unchoke_interval >= 5); + TORRENT_ASSERT(s.unchoke_interval >= 5); m_settings = s; m_files.resize(m_settings.file_pool_size); // replace all occurances of '\n' with ' '. @@ -706,7 +706,7 @@ namespace detail while (ec && retries > 0) { ec = asio::error_code(); - assert(!ec); + TORRENT_ASSERT(!ec); --retries; ep.port(ep.port() + 1); s.sock->bind(ep, ec); @@ -992,7 +992,7 @@ namespace detail #ifndef NDEBUG catch (...) { - assert(false); + TORRENT_ASSERT(false); }; #endif @@ -1003,7 +1003,7 @@ namespace detail // too expensive // INVARIANT_CHECK; - assert(p->is_disconnecting()); + TORRENT_ASSERT(p->is_disconnecting()); connection_map::iterator i = m_connections.find(p->get_socket()); if (i != m_connections.end()) { @@ -1124,7 +1124,7 @@ namespace detail ++i; if (i == m_torrents.end()) { - assert(m_next_connect_torrent == num_torrents); + TORRENT_ASSERT(m_next_connect_torrent == num_torrents); i = m_torrents.begin(); m_next_connect_torrent = 0; } @@ -1183,7 +1183,7 @@ namespace detail i != m_torrents.end();) { torrent& t = *i->second; - assert(!t.is_aborted()); + TORRENT_ASSERT(!t.is_aborted()); if (t.should_request()) { tracker_request req = t.generate_tracker_request(); @@ -1269,9 +1269,9 @@ namespace detail , end(peers.end()); i != end; ++i) { peer_connection* p = *i; - assert(p); + TORRENT_ASSERT(p); torrent* t = p->associated_torrent().lock().get(); - assert(t); + TORRENT_ASSERT(t); if (unchoke_set_size > 0) { if (p->is_choked()) @@ -1283,7 +1283,7 @@ namespace detail --unchoke_set_size; ++m_num_unchoked; - assert(p->peer_info_struct()); + TORRENT_ASSERT(p->peer_info_struct()); if (p->peer_info_struct()->optimistically_unchoked) { // force a new optimistic unchoke @@ -1293,7 +1293,7 @@ namespace detail } else { - assert(p->peer_info_struct()); + TORRENT_ASSERT(p->peer_info_struct()); if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) t->choke_peer(*p); if (!p->is_choked()) @@ -1317,7 +1317,7 @@ namespace detail , end(m_connections.end()); i != end; ++i) { peer_connection* p = i->second.get(); - assert(p); + TORRENT_ASSERT(p); policy::peer* pi = p->peer_info_struct(); if (!pi) continue; torrent* t = p->associated_torrent().lock().get(); @@ -1325,8 +1325,8 @@ namespace detail if (pi->optimistically_unchoked) { - assert(!p->is_choked()); - assert(current_optimistic_unchoke == m_connections.end()); + TORRENT_ASSERT(!p->is_choked()); + TORRENT_ASSERT(current_optimistic_unchoke == m_connections.end()); current_optimistic_unchoke = i; } @@ -1348,7 +1348,7 @@ namespace detail if (current_optimistic_unchoke != m_connections.end()) { torrent* t = current_optimistic_unchoke->second->associated_torrent().lock().get(); - assert(t); + TORRENT_ASSERT(t); current_optimistic_unchoke->second->peer_info_struct()->optimistically_unchoked = false; t->choke_peer(*current_optimistic_unchoke->second); } @@ -1358,9 +1358,9 @@ namespace detail } torrent* t = optimistic_unchoke_candidate->second->associated_torrent().lock().get(); - assert(t); + TORRENT_ASSERT(t); bool ret = t->unchoke_peer(*optimistic_unchoke_candidate->second); - assert(ret); + TORRENT_ASSERT(ret); optimistic_unchoke_candidate->second->peer_info_struct()->optimistically_unchoked = true; } } @@ -1382,7 +1382,7 @@ namespace detail , bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _1)) < bind(&torrent::num_peers, bind(&torrent_map::value_type::second, _2))); - assert(i != m_torrents.end()); + TORRENT_ASSERT(i != m_torrents.end()); i->second->get_policy().disconnect_one_peer(); } } @@ -1391,7 +1391,7 @@ namespace detail { #ifndef NDEBUG std::cerr << exc.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); #endif }; // msvc 7.1 seems to require this @@ -1411,7 +1411,7 @@ namespace detail try { m_io_service.run(); - assert(m_abort == true); + TORRENT_ASSERT(m_abort == true); } catch (std::exception& e) { @@ -1419,7 +1419,7 @@ namespace detail std::cerr << e.what() << "\n"; std::string err = e.what(); #endif - assert(false); + TORRENT_ASSERT(false); } } while (!m_abort); @@ -1455,7 +1455,7 @@ namespace detail && !i->second->trackers().empty()) { tracker_request req = i->second->generate_tracker_request(); - assert(!m_listen_sockets.empty()); + TORRENT_ASSERT(!m_listen_sockets.empty()); req.listen_port = 0; if (!m_listen_sockets.empty()) req.listen_port = m_listen_sockets.front().external_port; @@ -1501,7 +1501,7 @@ namespace detail #endif l.lock(); - assert(m_abort); + TORRENT_ASSERT(m_abort); m_abort = true; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1514,7 +1514,7 @@ namespace detail for (torrent_map::iterator i = m_torrents.begin(); i != m_torrents.end(); ++i) { - assert(i->second->num_peers() == 0); + TORRENT_ASSERT(i->second->num_peers() == 0); } #endif @@ -1523,8 +1523,8 @@ namespace detail #endif m_torrents.clear(); - assert(m_torrents.empty()); - assert(m_connections.empty()); + TORRENT_ASSERT(m_torrents.empty()); + TORRENT_ASSERT(m_connections.empty()); } @@ -1539,7 +1539,7 @@ namespace detail = m_torrents.begin(); j != m_torrents.end(); ++j) { torrent* p = boost::get_pointer(j->second); - assert(p); + TORRENT_ASSERT(p); } #endif if (i != m_torrents.end()) return i->second; @@ -1603,7 +1603,7 @@ namespace detail , bool paused , void* userdata) { - assert(!save_path.empty()); + TORRENT_ASSERT(!save_path.empty()); if (ti->begin_files() == ti->end_files()) throw std::runtime_error("no files in torrent"); @@ -1683,7 +1683,7 @@ namespace detail { // TODO: support resume data in this case - assert(!save_path.empty()); + TORRENT_ASSERT(!save_path.empty()); { // lock the checker_thread mutex::scoped_lock l(m_checker_impl.m_mutex); @@ -1703,7 +1703,7 @@ namespace detail throw duplicate_torrent(); // you cannot add new torrents to a session that is closing down - assert(!is_aborted()); + TORRENT_ASSERT(!is_aborted()); // create the torrent and the data associated with // the checker thread and store it before starting @@ -1732,8 +1732,8 @@ namespace detail void session_impl::remove_torrent(const torrent_handle& h) { if (h.m_ses != this) return; - assert(h.m_chk == &m_checker_impl || h.m_chk == 0); - assert(h.m_ses != 0); + TORRENT_ASSERT(h.m_chk == &m_checker_impl || h.m_chk == 0); + TORRENT_ASSERT(h.m_ses != 0); mutex_t::scoped_lock l(m_mutex); @@ -1750,8 +1750,8 @@ namespace detail && !t.torrent_file().trackers().empty()) { tracker_request req = t.generate_tracker_request(); - assert(req.event == tracker_request::stopped); - assert(!m_listen_sockets.empty()); + TORRENT_ASSERT(req.event == tracker_request::stopped); + TORRENT_ASSERT(!m_listen_sockets.empty()); req.listen_port = 0; if (!m_listen_sockets.empty()) req.listen_port = m_listen_sockets.front().external_port; @@ -1778,7 +1778,7 @@ namespace detail sha1_hash i_hash = t.torrent_file().info_hash(); #endif m_torrents.erase(i); - assert(m_torrents.find(i_hash) == m_torrents.end()); + TORRENT_ASSERT(m_torrents.find(i_hash) == m_torrents.end()); return; } @@ -1985,7 +1985,7 @@ namespace detail // basically, make sure you call listen_on() before // start_dht(). See documentation for listen_on() for // more information. - assert(m_listen_interface.port() > 0); + TORRENT_ASSERT(m_listen_interface.port() > 0); m_dht_settings.service_port = m_listen_interface.port(); } m_external_udp_port = m_dht_settings.service_port; @@ -2035,21 +2035,21 @@ namespace detail entry session_impl::dht_state() const { - assert(m_dht); + TORRENT_ASSERT(m_dht); mutex_t::scoped_lock l(m_mutex); return m_dht->state(); } void session_impl::add_dht_node(std::pair const& node) { - assert(m_dht); + TORRENT_ASSERT(m_dht); mutex_t::scoped_lock l(m_mutex); m_dht->add_node(node); } void session_impl::add_dht_router(std::pair const& node) { - assert(m_dht); + TORRENT_ASSERT(m_dht); mutex_t::scoped_lock l(m_mutex); m_dht->add_router_node(node); } @@ -2090,7 +2090,7 @@ namespace detail #endif m_thread->join(); - assert(m_torrents.empty()); + TORRENT_ASSERT(m_torrents.empty()); // it's important that the main thread is closed completely before // the checker thread is terminated. Because all the connections @@ -2117,8 +2117,8 @@ namespace detail #endif m_checker_thread->join(); - assert(m_torrents.empty()); - assert(m_connections.empty()); + TORRENT_ASSERT(m_torrents.empty()); + TORRENT_ASSERT(m_connections.empty()); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " shutdown complete!\n"; #endif @@ -2126,7 +2126,7 @@ namespace detail void session_impl::set_max_uploads(int limit) { - assert(limit > 0 || limit == -1); + TORRENT_ASSERT(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; @@ -2137,7 +2137,7 @@ namespace detail void session_impl::set_max_connections(int limit) { - assert(limit > 0 || limit == -1); + TORRENT_ASSERT(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; @@ -2148,7 +2148,7 @@ namespace detail void session_impl::set_max_half_open_connections(int limit) { - assert(limit > 0 || limit == -1); + TORRENT_ASSERT(limit > 0 || limit == -1); mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; @@ -2159,7 +2159,7 @@ namespace detail void session_impl::set_download_rate_limit(int bytes_per_second) { - assert(bytes_per_second > 0 || bytes_per_second == -1); + TORRENT_ASSERT(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; @@ -2170,7 +2170,7 @@ namespace detail void session_impl::set_upload_rate_limit(int bytes_per_second) { - assert(bytes_per_second > 0 || bytes_per_second == -1); + TORRENT_ASSERT(bytes_per_second > 0 || bytes_per_second == -1); mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; @@ -2303,11 +2303,11 @@ namespace detail void session_impl::free_buffer(char* buf, int size) { - assert(size % send_buffer_size == 0); + TORRENT_ASSERT(size % send_buffer_size == 0); int num_buffers = size / send_buffer_size; #ifdef TORRENT_STATS m_buffer_allocations -= num_buffers; - assert(m_buffer_allocations >= 0); + TORRENT_ASSERT(m_buffer_allocations >= 0); m_buffer_usage_logger << log_time() << " protocol_buffer: " << (m_buffer_allocations * send_buffer_size) << std::endl; #endif @@ -2317,14 +2317,14 @@ namespace detail #ifndef NDEBUG void session_impl::check_invariant() const { - assert(m_max_connections > 0); - assert(m_max_uploads > 0); + TORRENT_ASSERT(m_max_connections > 0); + TORRENT_ASSERT(m_max_uploads > 0); int unchokes = 0; int num_optimistic = 0; for (connection_map::const_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - assert(i->second); + TORRENT_ASSERT(i->second); boost::shared_ptr t = i->second->associated_torrent().lock(); if (!i->second->is_choked()) ++unchokes; @@ -2332,17 +2332,17 @@ namespace detail && i->second->peer_info_struct()->optimistically_unchoked) { ++num_optimistic; - assert(!i->second->is_choked()); + TORRENT_ASSERT(!i->second->is_choked()); } if (t && i->second->peer_info_struct()) { - assert(t->get_policy().has_connection(boost::get_pointer(i->second))); + TORRENT_ASSERT(t->get_policy().has_connection(boost::get_pointer(i->second))); } } - assert(num_optimistic == 0 || num_optimistic == 1); + TORRENT_ASSERT(num_optimistic == 0 || num_optimistic == 1); if (m_num_unchoked != unchokes) { - assert(false); + TORRENT_ASSERT(false); } } #endif @@ -2508,7 +2508,7 @@ namespace detail return; } - assert(*slot_iter == p.index); + TORRENT_ASSERT(*slot_iter == p.index); int slot_index = static_cast(slot_iter - tmp_pieces.begin()); unsigned long adler = torrent_ptr->filesystem().piece_crc( diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp index a6b5544e4..a40cd33d0 100644 --- a/libtorrent/src/socks5_stream.cpp +++ b/libtorrent/src/socks5_stream.cpp @@ -211,7 +211,7 @@ namespace libtorrent write_uint8(m_remote_endpoint.address().is_v4()?1:4, p); // address type write_address(m_remote_endpoint.address(), p); write_uint16(m_remote_endpoint.port(), p); - assert(p - &m_buffer[0] == int(m_buffer.size())); + TORRENT_ASSERT(p - &m_buffer[0] == int(m_buffer.size())); asio::async_write(m_sock, asio::buffer(m_buffer) , boost::bind(&socks5_stream::connect1, this, _1, h)); diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 53d8379a0..f855dce8b 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -355,9 +355,9 @@ namespace libtorrent : m_info(info) , m_files(fp) { - assert(info->begin_files(true) != info->end_files(true)); + TORRENT_ASSERT(info->begin_files(true) != info->end_files(true)); m_save_path = fs::complete(path); - assert(m_save_path.is_complete()); + TORRENT_ASSERT(m_save_path.is_complete()); } void release_files(); @@ -400,7 +400,7 @@ namespace libtorrent partial.update(&m_scratch_buffer[0], ph.offset); whole.update(&m_scratch_buffer[0], slot_size1); hasher partial_copy = ph.h; - assert(ph.offset == 0 || partial_copy.final() == partial.final()); + TORRENT_ASSERT(ph.offset == 0 || partial_copy.final() == partial.final()); #endif int slot_size = piece_size - ph.offset; if (slot_size > 0) @@ -411,7 +411,7 @@ namespace libtorrent } #ifndef NDEBUG sha1_hash ret = ph.h.final(); - assert(ret == whole.final()); + TORRENT_ASSERT(ret == whole.final()); return ret; #else return ph.h.final(); @@ -690,20 +690,20 @@ namespace libtorrent , int size , bool fill_zero) { - assert(buf != 0); - assert(slot >= 0 && slot < m_info->num_pieces()); - assert(offset >= 0); - assert(offset < m_info->piece_size(slot)); - assert(size > 0); + TORRENT_ASSERT(buf != 0); + TORRENT_ASSERT(slot >= 0 && slot < m_info->num_pieces()); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset < m_info->piece_size(slot)); + TORRENT_ASSERT(size > 0); #ifndef NDEBUG std::vector slices = m_info->map_block(slot, offset, size, true); - assert(!slices.empty()); + TORRENT_ASSERT(!slices.empty()); #endif size_type start = slot * (size_type)m_info->piece_length() + offset; - assert(start + size <= m_info->total_size()); + TORRENT_ASSERT(start + size <= m_info->total_size()); // find the file iterator and file offset size_type file_offset = start; @@ -722,9 +722,9 @@ namespace libtorrent boost::shared_ptr in(m_files.open_file( this, m_save_path / file_iter->path, file::in)); - assert(file_offset < file_iter->size); + TORRENT_ASSERT(file_offset < file_iter->size); - assert(slices[0].offset == file_offset); + TORRENT_ASSERT(slices[0].offset == file_offset); size_type new_pos = in->seek(file_offset); if (new_pos != file_offset) @@ -738,7 +738,7 @@ namespace libtorrent #ifndef NDEBUG size_type in_tell = in->tell(); - assert(in_tell == file_offset); + TORRENT_ASSERT(in_tell == file_offset); #endif int left_to_read = size; @@ -747,7 +747,7 @@ namespace libtorrent if (offset + left_to_read > slot_size) left_to_read = slot_size - offset; - assert(left_to_read >= 0); + TORRENT_ASSERT(left_to_read >= 0); size_type result = left_to_read; @@ -764,10 +764,10 @@ namespace libtorrent if (read_bytes > 0) { #ifndef NDEBUG - assert(int(slices.size()) > counter); + TORRENT_ASSERT(int(slices.size()) > counter); size_type slice_size = slices[counter].size; - assert(slice_size == read_bytes); - assert(m_info->file_at(slices[counter].file_index, true).path + TORRENT_ASSERT(slice_size == read_bytes); + TORRENT_ASSERT(m_info->file_at(slices[counter].file_index, true).path == file_iter->path); #endif @@ -785,7 +785,7 @@ namespace libtorrent left_to_read -= read_bytes; buf_pos += read_bytes; - assert(buf_pos >= 0); + TORRENT_ASSERT(buf_pos >= 0); file_offset += read_bytes; } @@ -815,16 +815,16 @@ namespace libtorrent , int offset , int size) { - assert(buf != 0); - assert(slot >= 0); - assert(slot < m_info->num_pieces()); - assert(offset >= 0); - assert(size > 0); + TORRENT_ASSERT(buf != 0); + TORRENT_ASSERT(slot >= 0); + TORRENT_ASSERT(slot < m_info->num_pieces()); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(size > 0); #ifndef NDEBUG std::vector slices = m_info->map_block(slot, offset, size, true); - assert(!slices.empty()); + TORRENT_ASSERT(!slices.empty()); #endif size_type start = slot * (size_type)m_info->piece_length() + offset; @@ -840,15 +840,15 @@ namespace libtorrent file_offset -= file_iter->size; ++file_iter; - assert(file_iter != m_info->end_files(true)); + TORRENT_ASSERT(file_iter != m_info->end_files(true)); } fs::path p(m_save_path / file_iter->path); boost::shared_ptr out = m_files.open_file( this, p, file::out | file::in); - assert(file_offset < file_iter->size); - assert(slices[0].offset == file_offset); + TORRENT_ASSERT(file_offset < file_iter->size); + TORRENT_ASSERT(slices[0].offset == file_offset); size_type pos = out->seek(file_offset); @@ -865,7 +865,7 @@ namespace libtorrent if (offset + left_to_write > slot_size) left_to_write = slot_size - offset; - assert(left_to_write >= 0); + TORRENT_ASSERT(left_to_write >= 0); int buf_pos = 0; #ifndef NDEBUG @@ -876,19 +876,19 @@ namespace libtorrent int write_bytes = left_to_write; if (file_offset + write_bytes > file_iter->size) { - assert(file_iter->size >= file_offset); + TORRENT_ASSERT(file_iter->size >= file_offset); write_bytes = static_cast(file_iter->size - file_offset); } if (write_bytes > 0) { - assert(int(slices.size()) > counter); - assert(slices[counter].size == write_bytes); - assert(m_info->file_at(slices[counter].file_index, true).path + TORRENT_ASSERT(int(slices.size()) > counter); + TORRENT_ASSERT(slices[counter].size == write_bytes); + TORRENT_ASSERT(m_info->file_at(slices[counter].file_index, true).path == file_iter->path); - assert(buf_pos >= 0); - assert(write_bytes >= 0); + TORRENT_ASSERT(buf_pos >= 0); + TORRENT_ASSERT(write_bytes >= 0); size_type written = out->write(buf + buf_pos, write_bytes); if (written != write_bytes) @@ -900,9 +900,9 @@ namespace libtorrent left_to_write -= write_bytes; buf_pos += write_bytes; - assert(buf_pos >= 0); + TORRENT_ASSERT(buf_pos >= 0); file_offset += write_bytes; - assert(file_offset <= file_iter->size); + TORRENT_ASSERT(file_offset <= file_iter->size); } if (left_to_write > 0) @@ -912,7 +912,7 @@ namespace libtorrent #endif ++file_iter; - assert(file_iter != m_info->end_files(true)); + TORRENT_ASSERT(file_iter != m_info->end_files(true)); fs::path p = m_save_path / file_iter->path; file_offset = 0; out = m_files.open_file( @@ -931,7 +931,7 @@ namespace libtorrent bool supports_sparse_files(fs::path const& p) { - assert(p.is_complete()); + TORRENT_ASSERT(p.is_complete()); #if defined(_WIN32) // assume windows API is available DWORD max_component_len = 0; @@ -1105,7 +1105,7 @@ namespace libtorrent j.priority = priority; // if a buffer is not specified, only one block can be read // since that is the size of the pool allocator's buffers - assert(r.length <= 16 * 1024 || buffer != 0); + TORRENT_ASSERT(r.length <= 16 * 1024 || buffer != 0); m_io_thread.add_job(j, handler); } @@ -1114,7 +1114,7 @@ namespace libtorrent , char const* buffer , boost::function const& handler) { - assert(r.length <= 16 * 1024); + TORRENT_ASSERT(r.length <= 16 * 1024); disk_io_job j; j.storage = this; @@ -1157,7 +1157,7 @@ namespace libtorrent } int slot = m_piece_to_slot[piece]; - assert(slot != has_no_slot); + TORRENT_ASSERT(slot != has_no_slot); return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece)); } @@ -1204,12 +1204,12 @@ namespace libtorrent INVARIANT_CHECK; - assert(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - assert(m_piece_to_slot[piece_index] >= 0); + TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); int slot_index = m_piece_to_slot[piece_index]; - assert(slot_index >= 0); + TORRENT_ASSERT(slot_index >= 0); m_slot_to_piece[slot_index] = unassigned; m_piece_to_slot[piece_index] = has_no_slot; @@ -1218,7 +1218,7 @@ namespace libtorrent int piece_manager::slot_for_piece(int piece_index) const { - assert(piece_index >= 0 && piece_index < m_info->num_pieces()); + TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces()); return m_piece_to_slot[piece_index]; } @@ -1228,9 +1228,9 @@ namespace libtorrent , piece_picker::block_info const* bi) try { - assert(slot_index >= 0); - assert(slot_index < m_info->num_pieces()); - assert(block_size > 0); + TORRENT_ASSERT(slot_index >= 0); + TORRENT_ASSERT(slot_index < m_info->num_pieces()); + TORRENT_ASSERT(block_size > 0); adler32_crc crc; std::vector buf(block_size); @@ -1270,14 +1270,14 @@ namespace libtorrent , int offset , int size) { - assert(buf); - assert(offset >= 0); - assert(size > 0); - assert(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - assert(m_piece_to_slot[piece_index] >= 0 + TORRENT_ASSERT(buf); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0 && m_piece_to_slot[piece_index] < (int)m_slot_to_piece.size()); int slot = m_piece_to_slot[piece_index]; - assert(slot >= 0 && slot < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size()); return m_storage->read(buf, slot, offset, size); } @@ -1287,15 +1287,15 @@ namespace libtorrent , int offset , int size) { - assert(buf); - assert(offset >= 0); - assert(size > 0); - assert(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(buf); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); if (offset == 0) { partial_hash& ph = m_piece_hasher[piece_index]; - assert(ph.offset == 0); + TORRENT_ASSERT(ph.offset == 0); ph.offset = size; ph.h.update(buf, size); } @@ -1304,8 +1304,8 @@ namespace libtorrent std::map::iterator i = m_piece_hasher.find(piece_index); if (i != m_piece_hasher.end()) { - assert(i->second.offset > 0); - assert(offset >= i->second.offset); + TORRENT_ASSERT(i->second.offset > 0); + TORRENT_ASSERT(offset >= i->second.offset); if (offset == i->second.offset) { i->second.offset += size; @@ -1315,7 +1315,7 @@ namespace libtorrent } int slot = allocate_slot_for_piece(piece_index); - assert(slot >= 0 && slot < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size()); m_storage->write(buf, slot, offset, size); } @@ -1329,13 +1329,13 @@ namespace libtorrent { // INVARIANT_CHECK; - assert((int)have_pieces.size() == m_info->num_pieces()); + TORRENT_ASSERT((int)have_pieces.size() == m_info->num_pieces()); const int piece_size = static_cast(m_info->piece_length()); const int last_piece_size = static_cast(m_info->piece_size( m_info->num_pieces() - 1)); - assert((int)piece_data.size() >= last_piece_size); + TORRENT_ASSERT((int)piece_data.size() >= last_piece_size); // calculate a small digest, with the same // size as the last piece. And a large digest @@ -1343,7 +1343,7 @@ namespace libtorrent hasher small_digest; small_digest.update(&piece_data[0], last_piece_size); hasher large_digest(small_digest); - assert(piece_size - last_piece_size >= 0); + TORRENT_ASSERT(piece_size - last_piece_size >= 0); if (piece_size - last_piece_size > 0) { large_digest.update( @@ -1395,7 +1395,7 @@ namespace libtorrent // we have already found a piece with // this index. int other_slot = m_piece_to_slot[piece_index]; - assert(other_slot >= 0); + TORRENT_ASSERT(other_slot >= 0); // take one of the other matching pieces // that hasn't already been assigned @@ -1410,7 +1410,7 @@ namespace libtorrent if (other_piece >= 0) { // replace the old slot with 'other_piece' - assert(have_pieces[other_piece] == false); + TORRENT_ASSERT(have_pieces[other_piece] == false); have_pieces[other_piece] = true; m_slot_to_piece[other_slot] = other_piece; m_piece_to_slot[other_piece] = other_slot; @@ -1426,8 +1426,8 @@ namespace libtorrent m_slot_to_piece[other_slot] = unassigned; m_free_slots.push_back(other_slot); } - assert(m_piece_to_slot[piece_index] != current_slot); - assert(m_piece_to_slot[piece_index] >= 0); + TORRENT_ASSERT(m_piece_to_slot[piece_index] != current_slot); + TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); m_piece_to_slot[piece_index] = has_no_slot; #ifndef NDEBUG // to make the assert happy, a few lines down @@ -1439,8 +1439,8 @@ namespace libtorrent ++num_pieces; } - assert(have_pieces[piece_index] == false); - assert(m_piece_to_slot[piece_index] == has_no_slot); + TORRENT_ASSERT(have_pieces[piece_index] == false); + TORRENT_ASSERT(m_piece_to_slot[piece_index] == has_no_slot); have_pieces[piece_index] = true; return piece_index; @@ -1462,8 +1462,8 @@ namespace libtorrent // lock because we're writing to have_pieces boost::recursive_mutex::scoped_lock l(mutex); - assert(have_pieces[free_piece] == false); - assert(m_piece_to_slot[free_piece] == has_no_slot); + TORRENT_ASSERT(have_pieces[free_piece] == false); + TORRENT_ASSERT(m_piece_to_slot[free_piece] == has_no_slot); have_pieces[free_piece] = true; ++num_pieces; @@ -1471,7 +1471,7 @@ namespace libtorrent } else { - assert(free_piece == unassigned); + TORRENT_ASSERT(free_piece == unassigned); return unassigned; } } @@ -1489,7 +1489,7 @@ namespace libtorrent INVARIANT_CHECK; - assert(m_info->piece_length() > 0); + TORRENT_ASSERT(m_info->piece_length() > 0); m_compact_mode = compact_mode; @@ -1539,7 +1539,7 @@ namespace libtorrent } else { - assert(data.piece_map[i] == unallocated); + TORRENT_ASSERT(data.piece_map[i] == unallocated); m_unallocated_slots.push_back(i); } } @@ -1600,7 +1600,7 @@ namespace libtorrent std::pair piece_manager::check_files( std::vector& pieces, int& num_pieces, boost::recursive_mutex& mutex) { - assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); if (m_state == state_allocating) { @@ -1622,7 +1622,7 @@ namespace libtorrent // if we're not in compact mode, make sure the // pieces are spread out and placed at their // final position. - assert(!m_unallocated_slots.empty()); + TORRENT_ASSERT(!m_unallocated_slots.empty()); if (!m_fill_mode) { @@ -1649,7 +1649,7 @@ namespace libtorrent if (!m_unallocated_slots.empty() && !m_compact_mode) { - assert(!m_fill_mode); + TORRENT_ASSERT(!m_fill_mode); std::vector().swap(m_unallocated_slots); std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); m_free_slots.resize(m_info->num_pieces()); @@ -1661,7 +1661,7 @@ namespace libtorrent return std::make_pair(true, 1.f); } - assert(m_state == state_full_check); + TORRENT_ASSERT(m_state == state_full_check); // ------------------------ // DO THE FULL CHECK @@ -1692,8 +1692,8 @@ namespace libtorrent int piece_index = identify_data(m_piece_data, m_current_slot , pieces, num_pieces, m_hash_to_piece, mutex); - assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - assert(piece_index == unassigned || piece_index >= 0); + TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0); const bool this_should_move = piece_index >= 0 && m_slot_to_piece[piece_index] != unallocated; const bool other_should_move = m_piece_to_slot[m_current_slot] != has_no_slot; @@ -1727,10 +1727,10 @@ namespace libtorrent // case 1 if (this_should_move && !other_should_move) { - assert(piece_index != m_current_slot); + TORRENT_ASSERT(piece_index != m_current_slot); const int other_slot = piece_index; - assert(other_slot >= 0); + TORRENT_ASSERT(other_slot >= 0); int other_piece = m_slot_to_piece[other_slot]; m_slot_to_piece[other_slot] = piece_index; @@ -1742,7 +1742,7 @@ namespace libtorrent { std::vector::iterator i = std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); - assert(i != m_free_slots.end()); + TORRENT_ASSERT(i != m_free_slots.end()); m_free_slots.erase(i); m_free_slots.push_back(m_current_slot); } @@ -1752,17 +1752,17 @@ namespace libtorrent else m_storage->move_slot(m_current_slot, other_slot); - assert(m_slot_to_piece[m_current_slot] == unassigned + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); } // case 2 else if (!this_should_move && other_should_move) { - assert(piece_index != m_current_slot); + TORRENT_ASSERT(piece_index != m_current_slot); const int other_piece = m_current_slot; const int other_slot = m_piece_to_slot[other_piece]; - assert(other_slot >= 0); + TORRENT_ASSERT(other_slot >= 0); m_slot_to_piece[m_current_slot] = other_piece; m_slot_to_piece[other_slot] = piece_index; @@ -1780,27 +1780,27 @@ namespace libtorrent { m_storage->move_slot(other_slot, m_current_slot); } - assert(m_slot_to_piece[m_current_slot] == unassigned + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); } else if (this_should_move && other_should_move) { - assert(piece_index != m_current_slot); - assert(piece_index >= 0); + TORRENT_ASSERT(piece_index != m_current_slot); + TORRENT_ASSERT(piece_index >= 0); const int piece1 = m_slot_to_piece[piece_index]; const int piece2 = m_current_slot; const int slot1 = piece_index; const int slot2 = m_piece_to_slot[piece2]; - assert(slot1 >= 0); - assert(slot2 >= 0); - assert(piece2 >= 0); + TORRENT_ASSERT(slot1 >= 0); + TORRENT_ASSERT(slot2 >= 0); + TORRENT_ASSERT(piece2 >= 0); if (slot1 == slot2) { // this means there are only two pieces involved in the swap - assert(piece1 >= 0); + TORRENT_ASSERT(piece1 >= 0); // movement diagram: // +-------------------------------+ @@ -1813,18 +1813,18 @@ namespace libtorrent m_piece_to_slot[piece_index] = slot1; m_piece_to_slot[piece1] = m_current_slot; - assert(piece1 == m_current_slot); - assert(piece_index == slot1); + TORRENT_ASSERT(piece1 == m_current_slot); + TORRENT_ASSERT(piece_index == slot1); m_storage->swap_slots(m_current_slot, slot1); - assert(m_slot_to_piece[m_current_slot] == unassigned + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); } else { - assert(slot1 != slot2); - assert(piece1 != piece2); + TORRENT_ASSERT(slot1 != slot2); + TORRENT_ASSERT(piece1 != piece2); // movement diagram: // +-----------------------------------------+ @@ -1842,7 +1842,7 @@ namespace libtorrent { std::vector::iterator i = std::find(m_free_slots.begin(), m_free_slots.end(), slot1); - assert(i != m_free_slots.end()); + TORRENT_ASSERT(i != m_free_slots.end()); m_free_slots.erase(i); m_free_slots.push_back(slot2); } @@ -1858,15 +1858,15 @@ namespace libtorrent m_storage->move_slot(slot2, m_current_slot); } - assert(m_slot_to_piece[m_current_slot] == unassigned + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); } } else { - assert(m_piece_to_slot[m_current_slot] == has_no_slot || piece_index != m_current_slot); - assert(m_slot_to_piece[m_current_slot] == unallocated); - assert(piece_index == unassigned || m_piece_to_slot[piece_index] == has_no_slot); + TORRENT_ASSERT(m_piece_to_slot[m_current_slot] == has_no_slot || piece_index != m_current_slot); + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unallocated); + TORRENT_ASSERT(piece_index == unassigned || m_piece_to_slot[piece_index] == has_no_slot); // the slot was identified as piece 'piece_index' if (piece_index != unassigned) @@ -1876,7 +1876,7 @@ namespace libtorrent m_slot_to_piece[m_current_slot] = piece_index; - assert(m_slot_to_piece[m_current_slot] == unassigned + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); } } @@ -1892,14 +1892,14 @@ namespace libtorrent if (file_offset > current_offset) break; } - assert(file_offset > current_offset); + TORRENT_ASSERT(file_offset > current_offset); int skip_blocks = static_cast( (file_offset - current_offset + m_info->piece_length() - 1) / m_info->piece_length()); for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) { - assert(m_slot_to_piece[i] == unallocated); + TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); m_unallocated_slots.push_back(i); } @@ -1910,17 +1910,17 @@ namespace libtorrent if (m_current_slot >= m_info->num_pieces()) { - assert(m_current_slot == m_info->num_pieces()); + TORRENT_ASSERT(m_current_slot == m_info->num_pieces()); // clear the memory we've been using std::vector().swap(m_piece_data); std::multimap().swap(m_hash_to_piece); m_state = state_allocating; - assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); return std::make_pair(false, 1.f); } - assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); } @@ -1931,23 +1931,23 @@ namespace libtorrent // INVARIANT_CHECK; - assert(piece_index >= 0); - assert(piece_index < (int)m_piece_to_slot.size()); - assert(m_piece_to_slot.size() == m_slot_to_piece.size()); + TORRENT_ASSERT(piece_index >= 0); + TORRENT_ASSERT(piece_index < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_piece_to_slot.size() == m_slot_to_piece.size()); int slot_index = m_piece_to_slot[piece_index]; if (slot_index != has_no_slot) { - assert(slot_index >= 0); - assert(slot_index < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(slot_index >= 0); + TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); return slot_index; } if (m_free_slots.empty()) { allocate_slots(1); - assert(!m_free_slots.empty()); + TORRENT_ASSERT(!m_free_slots.empty()); } std::vector::iterator iter( @@ -1958,8 +1958,8 @@ namespace libtorrent if (iter == m_free_slots.end()) { - assert(m_slot_to_piece[piece_index] != unassigned); - assert(!m_free_slots.empty()); + TORRENT_ASSERT(m_slot_to_piece[piece_index] != unassigned); + TORRENT_ASSERT(!m_free_slots.empty()); iter = m_free_slots.end() - 1; // special case to make sure we don't use the last slot @@ -1968,7 +1968,7 @@ namespace libtorrent { if (m_free_slots.size() == 1) allocate_slots(1); - assert(m_free_slots.size() > 1); + TORRENT_ASSERT(m_free_slots.size() > 1); // assumes that all allocated slots // are put at the end of the free_slots vector iter = m_free_slots.end() - 1; @@ -1978,7 +1978,7 @@ namespace libtorrent slot_index = *iter; m_free_slots.erase(iter); - assert(m_slot_to_piece[slot_index] == unassigned); + TORRENT_ASSERT(m_slot_to_piece[slot_index] == unassigned); m_slot_to_piece[slot_index] = piece_index; m_piece_to_slot[piece_index] = slot_index; @@ -2007,7 +2007,7 @@ namespace libtorrent #endif int piece_at_our_slot = m_slot_to_piece[piece_index]; - assert(m_piece_to_slot[piece_at_our_slot] == piece_index); + TORRENT_ASSERT(m_piece_to_slot[piece_at_our_slot] == piece_index); std::swap( m_slot_to_piece[piece_index] @@ -2019,8 +2019,8 @@ namespace libtorrent m_storage->move_slot(piece_index, slot_index); - assert(m_slot_to_piece[piece_index] == piece_index); - assert(m_piece_to_slot[piece_index] == piece_index); + TORRENT_ASSERT(m_slot_to_piece[piece_index] == piece_index); + TORRENT_ASSERT(m_piece_to_slot[piece_index] == piece_index); slot_index = piece_index; @@ -2029,20 +2029,20 @@ namespace libtorrent #endif } - assert(slot_index >= 0); - assert(slot_index < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(slot_index >= 0); + TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); return slot_index; } bool piece_manager::allocate_slots(int num_slots, bool abort_on_disk) { - assert(num_slots > 0); + TORRENT_ASSERT(num_slots > 0); boost::recursive_mutex::scoped_lock lock(m_mutex); // INVARIANT_CHECK; - assert(!m_unallocated_slots.empty()); + TORRENT_ASSERT(!m_unallocated_slots.empty()); const int stack_buffer_size = 16*1024; char zeroes[stack_buffer_size]; @@ -2055,8 +2055,8 @@ namespace libtorrent // INVARIANT_CHECK; int pos = m_unallocated_slots.front(); - assert(m_slot_to_piece[pos] == unallocated); - assert(m_piece_to_slot[pos] != pos); + TORRENT_ASSERT(m_slot_to_piece[pos] == unallocated); + TORRENT_ASSERT(m_piece_to_slot[pos] != pos); int new_free_slot = pos; if (m_piece_to_slot[pos] != has_no_slot) @@ -2085,7 +2085,7 @@ namespace libtorrent if (abort_on_disk && written) return true; } - assert(m_free_slots.size() > 0); + TORRENT_ASSERT(m_free_slots.size() > 0); return written; } @@ -2095,26 +2095,26 @@ namespace libtorrent boost::recursive_mutex::scoped_lock lock(m_mutex); if (m_piece_to_slot.empty()) return; - assert((int)m_piece_to_slot.size() == m_info->num_pieces()); - assert((int)m_slot_to_piece.size() == m_info->num_pieces()); + TORRENT_ASSERT((int)m_piece_to_slot.size() == m_info->num_pieces()); + TORRENT_ASSERT((int)m_slot_to_piece.size() == m_info->num_pieces()); for (std::vector::const_iterator i = m_free_slots.begin(); i != m_free_slots.end(); ++i) { - assert(*i < (int)m_slot_to_piece.size()); - assert(*i >= 0); - assert(m_slot_to_piece[*i] == unassigned); - assert(std::find(i+1, m_free_slots.end(), *i) + TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(m_slot_to_piece[*i] == unassigned); + TORRENT_ASSERT(std::find(i+1, m_free_slots.end(), *i) == m_free_slots.end()); } for (std::vector::const_iterator i = m_unallocated_slots.begin(); i != m_unallocated_slots.end(); ++i) { - assert(*i < (int)m_slot_to_piece.size()); - assert(*i >= 0); - assert(m_slot_to_piece[*i] == unallocated); - assert(std::find(i+1, m_unallocated_slots.end(), *i) + TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(m_slot_to_piece[*i] == unallocated); + TORRENT_ASSERT(std::find(i+1, m_unallocated_slots.end(), *i) == m_unallocated_slots.end()); } @@ -2123,46 +2123,46 @@ namespace libtorrent // Check domain of piece_to_slot's elements if (m_piece_to_slot[i] != has_no_slot) { - assert(m_piece_to_slot[i] >= 0); - assert(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(m_piece_to_slot[i] >= 0); + TORRENT_ASSERT(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); } // Check domain of slot_to_piece's elements if (m_slot_to_piece[i] != unallocated && m_slot_to_piece[i] != unassigned) { - assert(m_slot_to_piece[i] >= 0); - assert(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_slot_to_piece[i] >= 0); + TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); } // do more detailed checks on piece_to_slot if (m_piece_to_slot[i] >= 0) { - assert(m_slot_to_piece[m_piece_to_slot[i]] == i); + TORRENT_ASSERT(m_slot_to_piece[m_piece_to_slot[i]] == i); if (m_piece_to_slot[i] != i) { - assert(m_slot_to_piece[i] == unallocated); + TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); } } else { - assert(m_piece_to_slot[i] == has_no_slot); + TORRENT_ASSERT(m_piece_to_slot[i] == has_no_slot); } // do more detailed checks on slot_to_piece if (m_slot_to_piece[i] >= 0) { - assert(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); - assert(m_piece_to_slot[m_slot_to_piece[i]] == i); + TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_piece_to_slot[m_slot_to_piece[i]] == i); #ifdef TORRENT_STORAGE_DEBUG - assert( + TORRENT_ASSERT( std::find( m_unallocated_slots.begin() , m_unallocated_slots.end() , i) == m_unallocated_slots.end() ); - assert( + TORRENT_ASSERT( std::find( m_free_slots.begin() , m_free_slots.end() @@ -2173,7 +2173,7 @@ namespace libtorrent else if (m_slot_to_piece[i] == unallocated) { #ifdef TORRENT_STORAGE_DEBUG - assert(m_unallocated_slots.empty() + TORRENT_ASSERT(m_unallocated_slots.empty() || (std::find( m_unallocated_slots.begin() , m_unallocated_slots.end() @@ -2184,7 +2184,7 @@ namespace libtorrent else if (m_slot_to_piece[i] == unassigned) { #ifdef TORRENT_STORAGE_DEBUG - assert( + TORRENT_ASSERT( std::find( m_free_slots.begin() , m_free_slots.end() @@ -2194,7 +2194,7 @@ namespace libtorrent } else { - assert(false && "m_slot_to_piece[i] is invalid"); + TORRENT_ASSERT(false && "m_slot_to_piece[i] is invalid"); } } } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 33fbe9b40..840525c8a 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -114,7 +114,7 @@ namespace find_peer_by_ip(tcp::endpoint const& a, const torrent* t) : ip(a) , tor(t) - { assert(t != 0); } + { TORRENT_ASSERT(t != 0); } bool operator()(const session_impl::connection_map::value_type& c) const { @@ -309,7 +309,7 @@ namespace libtorrent // been closed by the time the torrent is destructed. And they are // supposed to be closed. So we can still do the invariant check. - assert(m_connections.empty()); + TORRENT_ASSERT(m_connections.empty()); INVARIANT_CHECK; @@ -321,7 +321,7 @@ namespace libtorrent } #endif - assert(m_abort); + TORRENT_ASSERT(m_abort); if (!m_connections.empty()) disconnect_all(); } @@ -344,9 +344,9 @@ namespace libtorrent // shared_from_this() void torrent::init() { - assert(m_torrent_file->is_valid()); - assert(m_torrent_file->num_files() > 0); - assert(m_torrent_file->total_size() >= 0); + TORRENT_ASSERT(m_torrent_file->is_valid()); + TORRENT_ASSERT(m_torrent_file->num_files() > 0); + TORRENT_ASSERT(m_torrent_file->total_size() >= 0); m_have_pieces.resize(m_torrent_file->num_pieces(), false); // the shared_from_this() will create an intentional @@ -423,7 +423,7 @@ namespace libtorrent catch (std::exception& e) { std::cerr << e.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); }; #endif @@ -662,7 +662,7 @@ namespace libtorrent size_type total_done = m_num_pieces * m_torrent_file->piece_length(); - assert(m_num_pieces < m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_num_pieces < m_torrent_file->num_pieces()); // if we have the last piece, we have to correct // the amount we have, since the first calculation @@ -676,8 +676,8 @@ namespace libtorrent wanted_done += corr; } - assert(total_done <= m_torrent_file->total_size()); - assert(wanted_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(total_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(wanted_done <= m_torrent_file->total_size()); const std::vector& dl_queue = m_picker->get_download_queue(); @@ -690,22 +690,22 @@ namespace libtorrent { int corr = 0; int index = i->index; - assert(!m_have_pieces[index]); - assert(i->finished <= m_picker->blocks_in_piece(index)); + if (m_have_pieces[index]) continue; + TORRENT_ASSERT(i->finished <= m_picker->blocks_in_piece(index)); #ifndef NDEBUG for (std::vector::const_iterator j = boost::next(i); j != dl_queue.end(); ++j) { - assert(j->index != index); + TORRENT_ASSERT(j->index != index); } #endif for (int j = 0; j < blocks_per_piece; ++j) { - assert(m_picker->is_finished(piece_block(index, j)) == (i->info[j].state == piece_picker::block_info::state_finished)); + TORRENT_ASSERT(m_picker->is_finished(piece_block(index, j)) == (i->info[j].state == piece_picker::block_info::state_finished)); corr += (i->info[j].state == piece_picker::block_info::state_finished) * m_block_size; - assert(index != last_piece || j < m_picker->blocks_in_last_piece() + TORRENT_ASSERT(index != last_piece || j < m_picker->blocks_in_last_piece() || i->info[j].state != piece_picker::block_info::state_finished); } @@ -723,8 +723,8 @@ namespace libtorrent wanted_done += corr; } - assert(total_done <= m_torrent_file->total_size()); - assert(wanted_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(total_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(wanted_done <= m_torrent_file->total_size()); std::map downloading_piece; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -753,13 +753,13 @@ namespace libtorrent downloading_piece[block] = p->bytes_downloaded; } #ifndef NDEBUG - assert(p->bytes_downloaded <= p->full_block_bytes); + TORRENT_ASSERT(p->bytes_downloaded <= p->full_block_bytes); int last_piece = m_torrent_file->num_pieces() - 1; if (p->piece_index == last_piece && p->block_index == m_torrent_file->piece_size(last_piece) / block_size()) - assert(p->full_block_bytes == m_torrent_file->piece_size(last_piece) % block_size()); + TORRENT_ASSERT(p->full_block_bytes == m_torrent_file->piece_size(last_piece) % block_size()); else - assert(p->full_block_bytes == block_size()); + TORRENT_ASSERT(p->full_block_bytes == block_size()); #endif } } @@ -806,12 +806,12 @@ namespace libtorrent } - assert(total_done <= m_torrent_file->total_size()); - assert(wanted_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(total_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(wanted_done <= m_torrent_file->total_size()); #endif - assert(total_done >= wanted_done); + TORRENT_ASSERT(total_done >= wanted_done); return make_tuple(total_done, wanted_done); } @@ -833,7 +833,7 @@ namespace libtorrent // the following call may cause picker to become invalid // in case we just became a seed announce_piece(index); - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); // if we just became a seed, picker is now invalid, since it // is deallocated by the torrent once it starts seeding if (!was_finished @@ -850,7 +850,7 @@ namespace libtorrent { #ifndef NDEBUG std::cerr << e.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); #endif } } @@ -872,7 +872,7 @@ namespace libtorrent catch (std::exception const& e) { std::cerr << e.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); } #endif @@ -883,7 +883,7 @@ namespace libtorrent if (!was_seed && is_seed()) { - assert(passed_hash_check); + TORRENT_ASSERT(passed_hash_check); completed(); } @@ -892,7 +892,7 @@ namespace libtorrent catch (std::exception const& e) { std::cerr << e.what() << std::endl; - assert(false); + TORRENT_ASSERT(false); } #endif @@ -907,11 +907,11 @@ namespace libtorrent // (total_done == m_torrent_file->total_size()) => is_seed() // INVARIANT_CHECK; - assert(m_storage); - assert(m_storage->refcount() > 0); - assert(m_picker.get()); - assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_storage); + TORRENT_ASSERT(m_storage->refcount() > 0); + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); if (m_ses.m_alerts.should_post(alert::info)) { @@ -949,7 +949,7 @@ namespace libtorrent { std::vector conns; connection_for(p->ip.address(), conns); - assert(p->connection == 0 + TORRENT_ASSERT(p->connection == 0 || std::find_if(conns.begin(), conns.end() , boost::bind(std::equal_to(), _1, p->connection)) != conns.end()); @@ -997,10 +997,10 @@ namespace libtorrent // start with redownloading the pieces that the client // that has sent the least number of pieces m_picker->restore_piece(index); - assert(m_storage); + TORRENT_ASSERT(m_storage); m_storage->mark_failed(index); - assert(m_have_pieces[index] == false); + TORRENT_ASSERT(m_have_pieces[index] == false); } void torrent::abort() @@ -1057,8 +1057,8 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); std::vector downloaders; m_picker->get_downloaders(downloaders, index); @@ -1072,7 +1072,7 @@ namespace libtorrent m_num_pieces++; m_have_pieces[index] = true; - assert(std::accumulate(m_have_pieces.begin(), m_have_pieces.end(), 0) + TORRENT_ASSERT(std::accumulate(m_have_pieces.begin(), m_have_pieces.end(), 0) == m_num_pieces); m_picker->we_have(index); @@ -1115,7 +1115,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) { avail.clear(); @@ -1129,13 +1129,13 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; // this call is only valid on torrents with metadata - assert(m_picker.get()); - assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); bool filter_updated = m_picker->set_piece_priority(index, priority); if (filter_updated) update_peer_interest(); @@ -1145,13 +1145,13 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) return 1; // this call is only valid on torrents with metadata - assert(m_picker.get()); - assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); return m_picker->piece_priority(index); } @@ -1161,18 +1161,18 @@ namespace libtorrent INVARIANT_CHECK; // this call is only valid on torrents with metadata - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; - assert(m_picker.get()); + TORRENT_ASSERT(m_picker.get()); int index = 0; bool filter_updated = false; for (std::vector::const_iterator i = pieces.begin() , end(pieces.end()); i != end; ++i, ++index) { - assert(*i >= 0); - assert(*i <= 7); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(*i <= 7); filter_updated |= m_picker->set_piece_priority(index, *i); } if (filter_updated) update_peer_interest(); @@ -1183,7 +1183,7 @@ namespace libtorrent INVARIANT_CHECK; // this call is only valid on torrents with metadata - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) { pieces.clear(); @@ -1191,7 +1191,7 @@ namespace libtorrent return; } - assert(m_picker.get()); + TORRENT_ASSERT(m_picker.get()); m_picker->piece_priorities(pieces); } @@ -1212,7 +1212,7 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert(int(files.size()) == m_torrent_file->num_files()); + TORRENT_ASSERT(int(files.size()) == m_torrent_file->num_files()); size_type position = 0; @@ -1233,7 +1233,7 @@ namespace libtorrent // already set (to avoid problems with overlapping pieces) int start_piece = int(start / piece_length); int last_piece = int((position - 1) / piece_length); - assert(last_piece <= int(pieces.size())); + TORRENT_ASSERT(last_piece <= int(pieces.size())); // if one piece spans several files, we might // come here several times with the same start_piece, end_piece std::for_each(pieces.begin() + start_piece @@ -1255,13 +1255,13 @@ namespace libtorrent { INVARIANT_CHECK; - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; // this call is only valid on torrents with metadata - assert(m_picker.get()); - assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); m_picker->set_piece_priority(index, filter ? 1 : 0); update_peer_interest(); @@ -1272,10 +1272,10 @@ namespace libtorrent INVARIANT_CHECK; // this call is only valid on torrents with metadata - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; - assert(m_picker.get()); + TORRENT_ASSERT(m_picker.get()); int index = 0; for (std::vector::const_iterator i = bitmask.begin() @@ -1293,12 +1293,12 @@ namespace libtorrent bool torrent::is_piece_filtered(int index) const { // this call is only valid on torrents with metadata - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) return false; - assert(m_picker.get()); - assert(index >= 0); - assert(index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); return m_picker->piece_priority(index) == 0; } @@ -1308,7 +1308,7 @@ namespace libtorrent INVARIANT_CHECK; // this call is only valid on torrents with metadata - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); if (is_seed()) { bitmask.clear(); @@ -1316,7 +1316,7 @@ namespace libtorrent return; } - assert(m_picker.get()); + TORRENT_ASSERT(m_picker.get()); m_picker->filtered_pieces(bitmask); } @@ -1329,7 +1329,7 @@ namespace libtorrent // the bitmask need to have exactly one bit for every file // in the torrent - assert((int)bitmask.size() == m_torrent_file->num_files()); + TORRENT_ASSERT((int)bitmask.size() == m_torrent_file->num_files()); size_type position = 0; @@ -1361,7 +1361,7 @@ namespace libtorrent void torrent::replace_trackers(std::vector const& urls) { - assert(!urls.empty()); + TORRENT_ASSERT(!urls.empty()); m_trackers = urls; if (m_currently_trying_tracker >= (int)m_trackers.size()) m_currently_trying_tracker = (int)m_trackers.size()-1; @@ -1372,7 +1372,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_trackers.empty()); + TORRENT_ASSERT(!m_trackers.empty()); m_next_request = time_now() + seconds(tracker_retry_delay_max); @@ -1408,8 +1408,8 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!c.is_choked()); - assert(m_num_uploads > 0); + TORRENT_ASSERT(!c.is_choked()); + TORRENT_ASSERT(m_num_uploads > 0); c.send_choke(); --m_num_uploads; } @@ -1418,7 +1418,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(c.is_choked()); + TORRENT_ASSERT(c.is_choked()); if (m_num_uploads >= m_max_uploads) return false; c.send_unchoke(); ++m_num_uploads; @@ -1438,24 +1438,24 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(p != 0); + TORRENT_ASSERT(p != 0); peer_iterator i = m_connections.find(p->remote()); if (i == m_connections.end()) { - assert(false); + TORRENT_ASSERT(false); return; } if (ready_for_connections()) { - assert(p->associated_torrent().lock().get() == this); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); if (p->is_seed()) { if (m_picker.get()) { - assert(!is_seed()); + TORRENT_ASSERT(!is_seed()); m_picker->dec_refcount_all(); } } @@ -1487,7 +1487,7 @@ namespace libtorrent #ifndef NDEBUG std::string err = e.what(); #endif - assert(false); + TORRENT_ASSERT(false); }; void torrent::connect_to_url_seed(std::string const& url) @@ -1582,7 +1582,7 @@ namespace libtorrent } catch (std::exception& exc) { - assert(false); + TORRENT_ASSERT(false); }; void torrent::on_name_lookup(asio::error_code const& e, tcp::resolver::iterator host @@ -1667,10 +1667,10 @@ namespace libtorrent try { - assert(m_connections.find(a) == m_connections.end()); + TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); // add the newly connected peer to this torrent's peer list - assert(m_connections.find(a) == m_connections.end()); + TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); m_connections.insert( std::make_pair(a, boost::get_pointer(c))); m_ses.m_connections.insert(std::make_pair(s, c)); @@ -1698,7 +1698,7 @@ namespace libtorrent #ifndef NDEBUG std::cerr << exc.what() << std::endl; #endif - assert(false); + TORRENT_ASSERT(false); }; #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES @@ -1838,22 +1838,22 @@ namespace libtorrent { INVARIANT_CHECK; - assert(peerinfo); - assert(peerinfo->connection == 0); + TORRENT_ASSERT(peerinfo); + TORRENT_ASSERT(peerinfo->connection == 0); #ifndef NDEBUG // this asserts that we don't have duplicates in the policy's peer list peer_iterator i_ = m_connections.find(peerinfo->ip); - assert(i_ == m_connections.end() + TORRENT_ASSERT(i_ == m_connections.end() || i_->second->is_disconnecting() || dynamic_cast(i_->second) == 0 || m_ses.settings().allow_multiple_connections_per_ip); #endif - assert(want_more_peers()); - assert(m_ses.num_connections() < m_ses.max_connections()); + TORRENT_ASSERT(want_more_peers()); + TORRENT_ASSERT(m_ses.num_connections() < m_ses.max_connections()); tcp::endpoint const& a(peerinfo->ip); - assert((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); + TORRENT_ASSERT((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); boost::shared_ptr s = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy()); @@ -1875,7 +1875,7 @@ namespace libtorrent try { - assert(m_connections.find(a) == m_connections.end()); + TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); // add the newly connected peer to this torrent's peer list m_connections.insert( @@ -1889,7 +1889,7 @@ namespace libtorrent } catch (std::exception& e) { - assert(false); + TORRENT_ASSERT(false); // TODO: post an error alert! std::map::iterator i = m_connections.find(a); if (i != m_connections.end()) m_connections.erase(i); @@ -1905,7 +1905,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(!m_torrent_file->is_valid()); + TORRENT_ASSERT(!m_torrent_file->is_valid()); m_torrent_file->parse_info_section(metadata); init(); @@ -1922,7 +1922,7 @@ namespace libtorrent typedef session_impl::torrent_map torrent_map; torrent_map::iterator i = m_ses.m_torrents.find( m_torrent_file->info_hash()); - assert(i != m_ses.m_torrents.end()); + TORRENT_ASSERT(i != m_ses.m_torrents.end()); m_ses.m_torrents.erase(i); // and notify the thread that it got another // job in its queue @@ -1939,14 +1939,14 @@ namespace libtorrent { // INVARIANT_CHECK; - assert(p != 0); - assert(!p->is_local()); + TORRENT_ASSERT(p != 0); + TORRENT_ASSERT(!p->is_local()); std::map::iterator c = m_connections.find(p->remote()); if (c != m_connections.end()) { - assert(p != c->second); + TORRENT_ASSERT(p != c->second); // we already have a peer_connection to this ip. // It may currently be waiting for completing a // connection attempt that might fail. So, @@ -1970,7 +1970,7 @@ namespace libtorrent throw protocol_error("session is closing"); } - assert(m_connections.find(p->remote()) == m_connections.end()); + TORRENT_ASSERT(m_connections.find(p->remote()) == m_connections.end()); peer_iterator ci = m_connections.insert( std::make_pair(p->remote(), p)).first; try @@ -1986,8 +1986,8 @@ namespace libtorrent if (pp) p->add_extension(pp); } #endif - assert(connection_for(p->remote()) == p); - assert(ci->second == p); + TORRENT_ASSERT(connection_for(p->remote()) == p); + TORRENT_ASSERT(ci->second == p); m_policy->new_connection(*ci->second); } catch (std::exception& e) @@ -1995,7 +1995,7 @@ namespace libtorrent m_connections.erase(ci); throw; } - assert(p->remote() == p->get_socket()->remote_endpoint()); + TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint()); #ifndef NDEBUG m_policy->check_invariant(); @@ -2018,7 +2018,7 @@ namespace libtorrent while (!m_connections.empty()) { peer_connection& p = *m_connections.begin()->second; - assert(p.associated_torrent().lock().get() == this); + TORRENT_ASSERT(p.associated_torrent().lock().get() == this); #if defined(TORRENT_VERBOSE_LOGGING) if (m_abort) @@ -2030,7 +2030,7 @@ namespace libtorrent std::size_t size = m_connections.size(); #endif p.disconnect(); - assert(m_connections.size() <= size); + TORRENT_ASSERT(m_connections.size() <= size); } } @@ -2043,7 +2043,7 @@ namespace libtorrent , boost::intrusive_ptr const& p , bool non_prioritized) { - assert(m_bandwidth_limit[channel].throttle() > 0); + TORRENT_ASSERT(m_bandwidth_limit[channel].throttle() > 0); int block_size = m_bandwidth_limit[channel].throttle() / 10; if (block_size <= 0) block_size = 1; @@ -2066,7 +2066,7 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - assert(amount > 0); + TORRENT_ASSERT(amount > 0); m_bandwidth_limit[channel].expire(amount); while (!m_bandwidth_queue[channel].empty()) @@ -2094,8 +2094,8 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - assert(amount > 0); - assert(amount <= blk); + TORRENT_ASSERT(amount > 0); + TORRENT_ASSERT(amount <= blk); if (amount < blk) expire_bandwidth(channel, blk - amount); } @@ -2119,7 +2119,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - assert(i->second->associated_torrent().lock().get() == this); + TORRENT_ASSERT(i->second->associated_torrent().lock().get() == this); if (i->second->is_seed()) { #if defined(TORRENT_VERBOSE_LOGGING) @@ -2131,7 +2131,7 @@ namespace libtorrent std::for_each(seeds.begin(), seeds.end() , bind(&peer_connection::disconnect, _1)); - assert(m_storage); + TORRENT_ASSERT(m_storage); // we need to keep the object alive during this operation m_storage->async_release_files( bind(&torrent::on_files_released, shared_from_this(), _1, _2)); @@ -2155,7 +2155,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(index >= 0); + TORRENT_ASSERT(index >= 0); if (index >= (int)m_trackers.size()) return (int)m_trackers.size()-1; while (index > 0 && m_trackers[index].tier == m_trackers[index-1].tier) @@ -2210,12 +2210,12 @@ namespace libtorrent { INVARIANT_CHECK; - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); bool done = true; try { - assert(m_storage); - assert(m_owning_storage.get()); + TORRENT_ASSERT(m_storage); + TORRENT_ASSERT(m_owning_storage.get()); done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces , m_compact_mode); } @@ -2239,15 +2239,15 @@ namespace libtorrent std::pair torrent::check_files() { - assert(m_torrent_file->is_valid()); + TORRENT_ASSERT(m_torrent_file->is_valid()); INVARIANT_CHECK; - assert(m_owning_storage.get()); + TORRENT_ASSERT(m_owning_storage.get()); std::pair progress(true, 1.f); try { - assert(m_storage); + TORRENT_ASSERT(m_storage); progress = m_storage->check_files(m_have_pieces, m_num_pieces , m_ses.m_mutex); } @@ -2275,7 +2275,7 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - assert(m_torrent_file->is_valid()); + TORRENT_ASSERT(m_torrent_file->is_valid()); INVARIANT_CHECK; if (!is_seed()) @@ -2379,7 +2379,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_owning_storage.get()); + TORRENT_ASSERT(m_owning_storage.get()); return *m_owning_storage; } @@ -2413,53 +2413,53 @@ namespace libtorrent if (!p.is_choked()) ++num_uploads; torrent* associated_torrent = p.associated_torrent().lock().get(); if (associated_torrent != this) - assert(false); + TORRENT_ASSERT(false); } - assert(num_uploads == m_num_uploads); + TORRENT_ASSERT(num_uploads == m_num_uploads); if (has_picker()) { for (std::map::iterator i = num_requests.begin() , end(num_requests.end()); i != end; ++i) { - assert(m_picker->num_peers(i->first) == i->second); + TORRENT_ASSERT(m_picker->num_peers(i->first) == i->second); } } if (valid_metadata()) { - assert(m_abort || int(m_have_pieces.size()) == m_torrent_file->num_pieces()); + TORRENT_ASSERT(m_abort || int(m_have_pieces.size()) == m_torrent_file->num_pieces()); } else { - assert(m_abort || m_have_pieces.empty()); + TORRENT_ASSERT(m_abort || m_have_pieces.empty()); } /* for (policy::const_iterator i = m_policy->begin_peer() , end(m_policy->end_peer()); i != end; ++i) { - assert(i->connection == const_cast(this)->connection_for(i->ip)); + TORRENT_ASSERT(i->connection == const_cast(this)->connection_for(i->ip)); } */ size_type total_done = quantized_bytes_done(); if (m_torrent_file->is_valid()) { if (is_seed()) - assert(total_done == m_torrent_file->total_size()); + TORRENT_ASSERT(total_done == m_torrent_file->total_size()); else - assert(total_done != m_torrent_file->total_size()); + TORRENT_ASSERT(total_done != m_torrent_file->total_size()); } else { - assert(total_done == 0); + TORRENT_ASSERT(total_done == 0); } // This check is very expensive. - assert(m_num_pieces + TORRENT_ASSERT(m_num_pieces == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); - assert(!valid_metadata() || m_block_size > 0); - assert(!valid_metadata() || (m_torrent_file->piece_length() % m_block_size) == 0); -// if (is_seed()) assert(m_picker.get() == 0); + TORRENT_ASSERT(!valid_metadata() || m_block_size > 0); + TORRENT_ASSERT(!valid_metadata() || (m_torrent_file->piece_length() % m_block_size) == 0); +// if (is_seed()) TORRENT_ASSERT(m_picker.get() == 0); } #endif @@ -2478,21 +2478,21 @@ namespace libtorrent void torrent::set_max_uploads(int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_uploads = limit; } void torrent::set_max_connections(int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = (std::numeric_limits::max)(); m_max_connections = limit; } void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); peer_connection* p = connection_for(ip); if (p == 0) return; p->set_upload_limit(limit); @@ -2500,7 +2500,7 @@ namespace libtorrent void torrent::set_peer_download_limit(tcp::endpoint ip, int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); peer_connection* p = connection_for(ip); if (p == 0) return; p->set_download_limit(limit); @@ -2508,7 +2508,7 @@ namespace libtorrent void torrent::set_upload_limit(int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = (std::numeric_limits::max)(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::upload_channel].throttle(limit); @@ -2523,7 +2523,7 @@ namespace libtorrent void torrent::set_download_limit(int limit) { - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = (std::numeric_limits::max)(); if (limit < num_peers() * 10) limit = num_peers() * 10; m_bandwidth_limit[peer_connection::download_channel].throttle(limit); @@ -2567,7 +2567,7 @@ namespace libtorrent // files and flush all cached data if (m_owning_storage.get()) { - assert(m_storage); + TORRENT_ASSERT(m_storage); m_storage->async_release_files( bind(&torrent::on_torrent_paused, shared_from_this(), _1, _2)); } @@ -2682,7 +2682,7 @@ namespace libtorrent bool torrent::try_connect_peer() { - assert(want_more_peers()); + TORRENT_ASSERT(want_more_peers()); return m_policy->connect_one_peer(); } @@ -2690,11 +2690,11 @@ namespace libtorrent { INVARIANT_CHECK; - assert(m_storage); - assert(m_storage->refcount() > 0); - assert(piece_index >= 0); - assert(piece_index < m_torrent_file->num_pieces()); - assert(piece_index < (int)m_have_pieces.size()); + TORRENT_ASSERT(m_storage); + TORRENT_ASSERT(m_storage->refcount() > 0); + TORRENT_ASSERT(piece_index >= 0); + TORRENT_ASSERT(piece_index < m_torrent_file->num_pieces()); + TORRENT_ASSERT(piece_index < (int)m_have_pieces.size()); m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified , shared_from_this(), _1, _2, f)); @@ -2718,7 +2718,7 @@ namespace libtorrent void torrent::file_progress(std::vector& fp) const { - assert(valid_metadata()); + TORRENT_ASSERT(valid_metadata()); fp.clear(); fp.resize(m_torrent_file->num_files(), 0.f); @@ -2746,7 +2746,7 @@ namespace libtorrent ret.start = 0; size -= bytes_step; } - assert(size == 0); + TORRENT_ASSERT(size == 0); fp[i] = static_cast(done) / m_torrent_file->file_at(i).size; } @@ -2756,7 +2756,7 @@ namespace libtorrent { INVARIANT_CHECK; - assert(std::accumulate( + TORRENT_ASSERT(std::accumulate( m_have_pieces.begin() , m_have_pieces.end() , 0) == m_num_pieces); @@ -2852,7 +2852,7 @@ namespace libtorrent st.total_wanted -= filtered_pieces * m_torrent_file->piece_length(); } - assert(st.total_wanted >= st.total_wanted_done); + TORRENT_ASSERT(st.total_wanted >= st.total_wanted_done); if (st.total_wanted == 0) st.progress = 1.f; else st.progress = st.total_wanted_done @@ -2867,7 +2867,7 @@ namespace libtorrent } else if (is_seed()) { - assert(st.total_done == m_torrent_file->total_size()); + TORRENT_ASSERT(st.total_done == m_torrent_file->total_size()); st.state = torrent_status::seeding; } else if (st.total_wanted_done == st.total_wanted) diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index d52a18dd2..f6b5d47de 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -111,7 +111,7 @@ namespace libtorrent void torrent_handle::check_invariant() const { - assert((m_ses == 0 && m_chk == 0) || (m_ses != 0 && m_chk != 0)); + TORRENT_ASSERT((m_ses == 0 && m_chk == 0) || (m_ses != 0 && m_chk != 0)); } #endif @@ -121,9 +121,9 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); - assert(max_uploads >= 2 || max_uploads == -1); + TORRENT_ASSERT(max_uploads >= 2 || max_uploads == -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -135,7 +135,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -147,9 +147,9 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); - assert(max_connections >= 2 || max_connections == -1); + TORRENT_ASSERT(max_connections >= 2 || max_connections == -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -159,10 +159,10 @@ namespace libtorrent void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const { INVARIANT_CHECK; - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -172,10 +172,10 @@ namespace libtorrent void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const { INVARIANT_CHECK; - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -187,9 +187,9 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -201,7 +201,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -213,9 +213,9 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); - assert(limit >= -1); + TORRENT_ASSERT(limit >= -1); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -227,7 +227,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -240,7 +240,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -252,7 +252,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -264,7 +264,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -276,7 +276,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -288,7 +288,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -300,7 +300,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -313,7 +313,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -325,7 +325,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -355,7 +355,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -391,7 +391,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -403,7 +403,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -416,7 +416,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -428,7 +428,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -440,7 +440,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -452,7 +452,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -464,7 +464,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); std::vector ret; session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); @@ -478,7 +478,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -492,7 +492,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -504,7 +504,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -516,7 +516,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -528,7 +528,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); std::vector ret; session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); @@ -542,7 +542,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -557,7 +557,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -569,7 +569,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -581,7 +581,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -593,7 +593,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -606,7 +606,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -618,7 +618,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -632,7 +632,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) return false; - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -653,7 +653,7 @@ namespace libtorrent std::vector piece_index; if (m_ses == 0) return entry(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -718,11 +718,11 @@ namespace libtorrent v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) ? (1 << k) : 0; bitmask.insert(bitmask.end(), v); - assert(bits == 8 || j == num_bitmask_bytes - 1); + TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); } piece_struct["bitmask"] = bitmask; - assert(t->filesystem().slot_for_piece(i->index) >= 0); + TORRENT_ASSERT(t->filesystem().slot_for_piece(i->index) >= 0); unsigned long adler = t->filesystem().piece_crc( t->filesystem().slot_for_piece(i->index) @@ -781,7 +781,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -793,7 +793,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -822,7 +822,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -837,7 +837,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -851,9 +851,9 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); - assert(ratio >= 0.f); + TORRENT_ASSERT(ratio >= 0.f); if (ratio < 1.f && ratio > 0.f) ratio = 1.f; @@ -868,7 +868,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -880,7 +880,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); @@ -893,7 +893,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); v.clear(); @@ -927,7 +927,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - assert(m_chk); + TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); @@ -981,7 +981,7 @@ namespace libtorrent if (pbp && pbp->piece_index == i->index && pbp->block_index == j) { bi.bytes_progress = pbp->bytes_downloaded; - assert(bi.bytes_progress <= bi.block_size); + TORRENT_ASSERT(bi.bytes_progress <= bi.block_size); } else { diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index 76c10e572..3d30dadd5 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -208,7 +208,7 @@ namespace /* void remove_dir(fs::path& p) { - assert(p.begin() != p.end()); + TORRENT_ASSERT(p.begin() != p.end()); path tmp; for (path::iterator i = boost::next(p.begin()); i != p.end(); ++i) tmp /= *i; @@ -311,12 +311,12 @@ namespace libtorrent { if (size & (1 << i)) { - assert((size & ~(1 << i)) == 0); + TORRENT_ASSERT((size & ~(1 << i)) == 0); break; } } #endif - assert(!m_half_metadata); + TORRENT_ASSERT(!m_half_metadata); m_piece_length = size; m_num_pieces = static_cast( @@ -420,7 +420,7 @@ namespace libtorrent std::vector info_section_buf; entry gen_info_section = create_info_metadata(); bencode(std::back_inserter(info_section_buf), gen_info_section); - assert(hasher(&info_section_buf[0], info_section_buf.size()).final() + TORRENT_ASSERT(hasher(&info_section_buf[0], info_section_buf.size()).final() == m_info_hash); #endif } @@ -554,7 +554,7 @@ namespace libtorrent void torrent_info::add_file(fs::path file, size_type size) { -// assert(file.begin() != file.end()); +// TORRENT_ASSERT(file.begin() != file.end()); if (!file.has_branch_path()) { @@ -562,15 +562,15 @@ namespace libtorrent // path to the file (branch_path), which means that // all the other files need to be in the same top // directory as the first file. - assert(m_files.empty()); - assert(!m_multifile); + TORRENT_ASSERT(m_files.empty()); + TORRENT_ASSERT(!m_multifile); m_name = file.string(); } else { #ifndef NDEBUG if (!m_files.empty()) - assert(m_name == *file.begin()); + TORRENT_ASSERT(m_name == *file.begin()); #endif m_multifile = true; m_name = *file.begin(); @@ -616,7 +616,7 @@ namespace libtorrent entry torrent_info::create_info_metadata() const { // you have to add files to the torrent first - assert(!m_files.empty()); + TORRENT_ASSERT(!m_files.empty()); entry info(m_extra_info); @@ -644,8 +644,8 @@ namespace libtorrent fs::path const* file_path; if (i->orig_path) file_path = &(*i->orig_path); else file_path = &i->path; - assert(file_path->has_branch_path()); - assert(*file_path->begin() == m_name); + TORRENT_ASSERT(file_path->has_branch_path()); + TORRENT_ASSERT(*file_path->begin() == m_name); for (fs::path::iterator j = boost::next(file_path->begin()); j != file_path->end(); ++j) @@ -672,7 +672,7 @@ namespace libtorrent entry torrent_info::create_torrent() const { - assert(m_piece_length > 0); + TORRENT_ASSERT(m_piece_length > 0); if (m_files.empty()) { @@ -760,14 +760,14 @@ namespace libtorrent void torrent_info::set_hash(int index, const sha1_hash& h) { - assert(index >= 0); - assert(index < (int)m_piece_hash.size()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < (int)m_piece_hash.size()); m_piece_hash[index] = h; } void torrent_info::convert_file_names() { - assert(false); + TORRENT_ASSERT(false); } void torrent_info::seed_free() @@ -806,13 +806,13 @@ namespace libtorrent size_type torrent_info::piece_size(int index) const { - assert(index >= 0 && index < num_pieces()); + TORRENT_ASSERT(index >= 0 && index < num_pieces()); if (index == num_pieces()-1) { size_type size = total_size() - (num_pieces() - 1) * piece_length(); - assert(size > 0); - assert(size <= piece_length()); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(size <= piece_length()); return size; } else @@ -852,11 +852,11 @@ namespace libtorrent std::vector torrent_info::map_block(int piece, size_type offset , int size, bool storage) const { - assert(num_files() > 0); + TORRENT_ASSERT(num_files() > 0); std::vector ret; size_type start = piece * (size_type)m_piece_length + offset; - assert(start + size <= m_total_size); + TORRENT_ASSERT(start + size <= m_total_size); // find the file iterator and file offset // TODO: make a vector that can map piece -> file index in O(1) @@ -866,7 +866,7 @@ namespace libtorrent int counter = 0; for (file_iter = begin_files(storage);; ++counter, ++file_iter) { - assert(file_iter != end_files(storage)); + TORRENT_ASSERT(file_iter != end_files(storage)); if (file_offset < file_iter->size) { file_slice f; @@ -878,7 +878,7 @@ namespace libtorrent ret.push_back(f); } - assert(size >= 0); + TORRENT_ASSERT(size >= 0); if (size <= 0) break; file_offset -= file_iter->size; @@ -889,8 +889,8 @@ namespace libtorrent peer_request torrent_info::map_file(int file_index, size_type file_offset , int size, bool storage) const { - assert(file_index < num_files(storage)); - assert(file_index >= 0); + TORRENT_ASSERT(file_index < num_files(storage)); + TORRENT_ASSERT(file_index >= 0); size_type offset = file_offset + file_at(file_index, storage).offset; peer_request ret; diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 981eb4caf..37dd941eb 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -84,8 +84,8 @@ namespace libtorrent // returns -1 if gzip header is invalid or the header size in bytes int gzip_header(const char* buf, int size) { - assert(buf != 0); - assert(size > 0); + TORRENT_ASSERT(buf != 0); + TORRENT_ASSERT(size > 0); const unsigned char* buffer = reinterpret_cast(buf); const int total_size = size; @@ -162,7 +162,7 @@ namespace libtorrent , request_callback* requester , int maximum_tracker_response_length) { - assert(maximum_tracker_response_length > 0); + TORRENT_ASSERT(maximum_tracker_response_length > 0); int header_len = gzip_header(&buffer[0], (int)buffer.size()); if (header_len < 0) @@ -349,7 +349,7 @@ namespace libtorrent } catch (std::exception& e) { - assert(false); + TORRENT_ASSERT(false); } tracker_connection::tracker_connection( @@ -490,11 +490,11 @@ namespace libtorrent , boost::weak_ptr c) { mutex_t::scoped_lock l(m_mutex); - assert(req.num_want >= 0); + TORRENT_ASSERT(req.num_want >= 0); if (req.event == tracker_request::stopped) req.num_want = 0; - assert(!m_abort || req.event == tracker_request::stopped); + TORRENT_ASSERT(!m_abort || req.event == tracker_request::stopped); if (m_abort && req.event != tracker_request::stopped) return; diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index cd500d98c..1ba50b3c6 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -126,7 +126,7 @@ namespace libtorrent != bind_interface().is_v4(); ++target); if (target == end) { - assert(target_address.address().is_v4() != bind_interface().is_v4()); + TORRENT_ASSERT(target_address.address().is_v4() != bind_interface().is_v4()); if (cb) { std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index f1ee92636..4bdc20987 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -222,7 +222,7 @@ try #ifndef NDEBUG catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); }; #endif @@ -413,7 +413,7 @@ try #ifndef NDEBUG catch (std::exception&) { - assert(false); + TORRENT_ASSERT(false); }; #endif @@ -476,8 +476,8 @@ void upnp::map_port(rootdevice& d, int i) return; } d.mapping[i].need_update = false; - assert(!d.upnp_connection); - assert(d.service_namespace); + TORRENT_ASSERT(!d.upnp_connection); + TORRENT_ASSERT(d.service_namespace); d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, self(), _1, _2 diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 18fe715ee..071407005 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -304,7 +304,7 @@ namespace libtorrent { namespace std::copy(pex_msg.begin(), pex_msg.end(), i.begin); i.begin += pex_msg.size(); - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); m_pc.setup_send(); } @@ -367,7 +367,7 @@ namespace libtorrent { namespace std::copy(pex_msg.begin(), pex_msg.end(), i.begin); i.begin += pex_msg.size(); - assert(i.begin == i.end); + TORRENT_ASSERT(i.begin == i.end); m_pc.setup_send(); } diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index bc09b4935..21208454e 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -75,7 +75,7 @@ namespace libtorrent // we only want left-over bandwidth set_non_prioritized(true); shared_ptr tor = t.lock(); - assert(tor); + TORRENT_ASSERT(tor); int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); // we always prefer downloading 1 MB chunks @@ -115,7 +115,7 @@ namespace libtorrent return boost::optional(); boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); piece_block_progress ret; @@ -148,7 +148,7 @@ namespace libtorrent void web_peer_connection::on_connected() { boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); // this is always a seed incoming_bitfield(std::vector( @@ -164,9 +164,9 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); - assert(t->valid_metadata()); + TORRENT_ASSERT(t->valid_metadata()); bool single_file_request = false; if (!m_path.empty() && m_path[m_path.size() - 1] != '/') @@ -288,7 +288,7 @@ namespace libtorrent request += "\r\nConnection: keep-alive"; request += "\r\n\r\n"; m_first_request = false; - assert(f.file_index >= 0); + TORRENT_ASSERT(f.file_index >= 0); m_file_requests.push_back(f.file_index); } } @@ -329,7 +329,7 @@ namespace libtorrent } boost::shared_ptr t = associated_torrent().lock(); - assert(t); + TORRENT_ASSERT(t); incoming_piece_fragment(); @@ -345,9 +345,9 @@ namespace libtorrent boost::tie(payload, protocol) = m_parser.incoming(recv_buffer); m_statistics.received_bytes(payload, protocol); - assert(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); + TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); - assert(recv_buffer.left() <= packet_size()); + TORRENT_ASSERT(recv_buffer.left() <= packet_size()); // this means the entire status line hasn't been received yet if (m_parser.status_code() == -1) break; @@ -403,7 +403,7 @@ namespace libtorrent // add the redirected url and remove the current one if (!single_file_request) { - assert(!m_file_requests.empty()); + TORRENT_ASSERT(!m_file_requests.empty()); int file_index = m_file_requests.front(); torrent_info const& info = t->torrent_file(); @@ -493,7 +493,7 @@ namespace libtorrent // skip the http header and the blocks we've already read. The // http_body.begin is now in sync with the request at the front // of the request queue -// assert(in_range.start - int(m_piece.size()) <= front_request.start); +// TORRENT_ASSERT(in_range.start - int(m_piece.size()) <= front_request.start); // the http response body consists of 3 parts // 1. the middle of a block or the ending of a block @@ -520,14 +520,14 @@ namespace libtorrent int copy_size = (std::min)((std::min)(front_request.length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); m_piece.resize(piece_size + copy_size); - assert(copy_size > 0); + TORRENT_ASSERT(copy_size > 0); std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size); - assert(int(m_piece.size()) <= front_request.length); + TORRENT_ASSERT(int(m_piece.size()) <= front_request.length); recv_buffer.begin += copy_size; m_received_body += copy_size; m_body_start += copy_size; - assert(m_received_body <= range_end - range_start); - assert(int(m_piece.size()) <= front_request.length); + TORRENT_ASSERT(m_received_body <= range_end - range_start); + TORRENT_ASSERT(int(m_piece.size()) <= front_request.length); if (int(m_piece.size()) == front_request.length) { // each call to incoming_piece() may result in us becoming @@ -541,9 +541,9 @@ namespace libtorrent cut_receive_buffer(m_body_start, t->block_size() + 1024); m_body_start = 0; recv_buffer = receive_buffer(); - assert(m_received_body <= range_end - range_start); + TORRENT_ASSERT(m_received_body <= range_end - range_start); m_piece.clear(); - assert(m_piece.empty()); + TORRENT_ASSERT(m_piece.empty()); } } @@ -554,13 +554,13 @@ namespace libtorrent { peer_request r = m_requests.front(); m_requests.pop_front(); - assert(recv_buffer.left() >= r.length); + TORRENT_ASSERT(recv_buffer.left() >= r.length); incoming_piece(r, recv_buffer.begin); if (associated_torrent().expired()) return; m_received_body += r.length; - assert(receive_buffer().begin + m_body_start == recv_buffer.begin); - assert(m_received_body <= range_end - range_start); + TORRENT_ASSERT(receive_buffer().begin + m_body_start == recv_buffer.begin); + TORRENT_ASSERT(m_received_body <= range_end - range_start); cut_receive_buffer(r.length + m_body_start, t->block_size() + 1024); m_body_start = 0; recv_buffer = receive_buffer(); @@ -577,7 +577,7 @@ namespace libtorrent int piece_size = int(m_piece.size()); int copy_size = (std::min)((std::min)(m_requests.front().length - piece_size , recv_buffer.left()), int(range_end - range_start - m_received_body)); - assert(copy_size >= 0); + TORRENT_ASSERT(copy_size >= 0); if (copy_size > 0) { m_piece.resize(piece_size + copy_size); @@ -586,11 +586,11 @@ namespace libtorrent m_received_body += copy_size; m_body_start += copy_size; } - assert(m_received_body == range_end - range_start); + TORRENT_ASSERT(m_received_body == range_end - range_start); } } - assert(m_received_body <= range_end - range_start); + TORRENT_ASSERT(m_received_body <= range_end - range_start); if (m_received_body == range_end - range_start) { cut_receive_buffer(recv_buffer.begin - receive_buffer().begin @@ -642,7 +642,7 @@ namespace libtorrent void web_peer_connection::check_invariant() const { /* - assert(m_num_pieces == std::count( + TORRENT_ASSERT(m_num_pieces == std::count( m_have_piece.begin() , m_have_piece.end() , true)); From 8f6029219d36736b57f14fb2650969ed4c13a4f1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 6 Oct 2007 04:35:15 +0000 Subject: [PATCH 0194/1009] Fix adding torrents with a unicode save_path. --- deluge/common.py | 2 +- deluge/config.py | 4 ++-- deluge/core/torrentmanager.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index b044da775..e0cb2900c 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -187,6 +187,6 @@ def pythonize(var): ) for klass in [unicode, str, bool, int, float, long]: - if isinstance(var,klass): + if isinstance(var, klass): return klass(var) return var diff --git a/deluge/config.py b/deluge/config.py index bbb9dd6ed..6afc95fdf 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -113,7 +113,7 @@ class Config: """Set the 'key' with 'value'.""" # Sets the "key" with "value" in the config dict if self.config[key] != value: - log.debug("Setting '%s' to %s", key, value) + log.debug("Setting '%s' to %s of type %s", key, type(value), value) self.config[key] = value # Run the set_function for this key if any try: @@ -128,7 +128,7 @@ class Config: # invalid try: value = self.config[key] - log.debug("Getting '%s' as %s", key, value) + log.debug("Getting '%s' as %s of type %s", key, value, type(value)) return value except KeyError: log.warning("Key does not exist, returning None") diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index d61057e32..a1c62025d 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -173,7 +173,7 @@ class TorrentManager: try: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), - save_path, + str(save_path), resume_data=fastresume, compact_mode=compact, paused=paused) diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 07cbe1ca2..9c46ada4f 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -32,13 +32,22 @@ # statement from all source files in the program, then also delete it here. import deluge.pluginmanagerbase +import deluge.ui.functions as functions +from deluge.configmanager import ConfigManager from deluge.log import LOG as log class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): def __init__(self, gtkui): + self.config = ConfigManager("gtkui.conf") self._gtkui = gtkui + # Update the enabled_plugins from the core + enabled_plugins = functions.get_enabled_plugins() + enabled_plugins += self.config["enabled_plugins"] + enabled_plugins = list(set(enabled_plugins)) + self.config["enabled_plugins"] = enabled_plugins + deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "gtkui.conf", "deluge.plugin.ui.gtk") From 7fe0392c90452673fced48042f3846838d28b946 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 6 Oct 2007 17:31:48 +0000 Subject: [PATCH 0195/1009] Fix config debug output. --- deluge/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index 6afc95fdf..f1ed0c1b8 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -113,7 +113,7 @@ class Config: """Set the 'key' with 'value'.""" # Sets the "key" with "value" in the config dict if self.config[key] != value: - log.debug("Setting '%s' to %s of type %s", key, type(value), value) + log.debug("Setting '%s' to %s of %s", key, value, type(value)) self.config[key] = value # Run the set_function for this key if any try: @@ -128,7 +128,7 @@ class Config: # invalid try: value = self.config[key] - log.debug("Getting '%s' as %s of type %s", key, value, type(value)) + log.debug("Getting '%s' as %s of %s", key, value, type(value)) return value except KeyError: log.warning("Key does not exist, returning None") From 54b1665c506ce617ccc040a71290a3cfea74f4d7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 6 Oct 2007 17:32:41 +0000 Subject: [PATCH 0196/1009] libtorrent build fix --- libtorrent/src/file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 53288b2e5..67024cf81 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -31,7 +31,6 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "libtorrent/pch.hpp" -#include "libtorrent/assert.hpp" #ifdef _WIN32 // windows part @@ -82,6 +81,7 @@ BOOST_STATIC_ASSERT(sizeof(lseek(0, 0, 0)) >= 8); #include "libtorrent/storage.hpp" #endif +#include "libtorrent/assert.hpp" namespace { From 33fd818933745d965507a08dee67e0b7db3e2a41 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 7 Oct 2007 01:53:03 +0000 Subject: [PATCH 0197/1009] Save and load total_uploaded from torrent state. --- deluge/core/torrent.py | 6 +++--- deluge/core/torrentmanager.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 755a7673c..9f2935360 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -38,7 +38,7 @@ import deluge.common class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ - def __init__(self, filename, handle, compact, save_path): + def __init__(self, filename, handle, compact, save_path, total_uploaded=0): # Set the filename self.filename = filename # Set the libtorrent handle @@ -46,7 +46,7 @@ class Torrent: # Set the torrent_id for this torrent self.torrent_id = str(handle.info_hash()) # This is for saving the total uploaded between sessions - self.total_uploaded = 0 + self.total_uploaded = total_uploaded # Set the allocation mode self.compact = compact # Where the torrent is being saved to @@ -62,7 +62,7 @@ class Torrent: """Returns the state of this torrent for saving to the session state""" status = self.handle.status() return (self.torrent_id, self.filename, self.compact, status.paused, - self.save_path) + self.save_path, self.total_uploaded) def get_eta(self): """Returns the ETA in seconds for this torrent""" diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index a1c62025d..5123716ed 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -47,12 +47,14 @@ from deluge.core.torrent import Torrent from deluge.log import LOG as log class TorrentState: - def __init__(self, torrent_id, filename, compact, paused, save_path): + def __init__(self, torrent_id, filename, compact, paused, save_path, + total_uploaded): self.torrent_id = torrent_id self.filename = filename self.compact = compact self.paused = paused self.save_path = save_path + self.total_uploaded = total_uploaded class TorrentManagerState: def __init__(self): @@ -119,7 +121,7 @@ class TorrentManager: return self.torrents.keys() def add(self, filename, filedump=None, compact=None, paused=False, - save_path=None): + save_path=None, total_uploaded=0): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) @@ -186,7 +188,8 @@ class TorrentManager: # Create a Torrent object torrent = Torrent(filename, handle, compact, - save_path) + save_path, total_uploaded) + # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent @@ -310,7 +313,8 @@ class TorrentManager: # Try to add the torrents in the state to the session for torrent_state in state.torrents: self.add(torrent_state.filename, compact=torrent_state.compact, - paused=torrent_state.paused, save_path=torrent_state.save_path) + paused=torrent_state.paused, save_path=torrent_state.save_path, + total_uploaded=torrent_state.total_uploaded) def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" From ae30cdb6778a5067f500554a504bc98f2ef7e7c3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 7 Oct 2007 01:53:58 +0000 Subject: [PATCH 0198/1009] Fix an issue with large torrent sizes. --- deluge/ui/gtkui/torrentview.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index d22a75ccf..657c45bce 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -37,6 +37,7 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade import gettext +import gobject import deluge.common import deluge.ui.functions as functions @@ -116,7 +117,7 @@ class TorrentView(listview.ListView): function=cell_data_statusicon) self.add_func_column(_("Size"), listview.cell_data_size, - [long], + [gobject.TYPE_UINT64], status_field=["total_size"]) self.add_progress_column(_("Progress"), status_field=["progress", "state"], @@ -220,9 +221,13 @@ class TorrentView(listview.ListView): column_index = self.get_column_index(column) if type(column_index) is not list: # We only have a single list store column we need to update - model.set_value(row, + try: + model.set_value(row, column_index, status[self.columns[column].status_field[0]]) + except TypeError: + log.warning("Unable to update column %s with value: %s", + column, status[self.columns[column].status_field[0]]) else: # We have more than 1 liststore column to update for index in column_index: From 85767b148abc34eccf5cdcf8de4a57e432bf7940 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 7 Oct 2007 03:33:36 +0000 Subject: [PATCH 0199/1009] fix tray locking pop-up on quit while disabled --- deluge/ui/gtkui/systemtray.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 3b1bd64d5..2f270e495 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -176,7 +176,10 @@ class SystemTray: if self.window.visible(): self.window.quit() else: - self.unlock_tray("quitui") + if self.config["lock_tray"] == True: + self.unlock_tray("quitui") + else: + self.window.quit() def on_menuitem_quitdaemon_activate(self, menuitem): log.debug("on_menuitem_quitdaemon_activate") @@ -184,7 +187,11 @@ class SystemTray: self.window.quit() functions.shutdown() else: - self.unlock_tray("quitdaemon") + if self.config["lock_tray"] == True: + self.unlock_tray("quitdaemon") + else: + self.window.quit() + functions.shutdown() def build_menu_radio_list(self, value_list, callback, pref_value=None, suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, From 515305e950f02be3b3e008e255a86fb9f388813c Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 7 Oct 2007 03:41:56 +0000 Subject: [PATCH 0200/1009] fixes files that were left open in write mode by mistake, policy invariant check fix, fixed static assert being hit --- libtorrent/src/policy.cpp | 1 + libtorrent/src/storage.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 5c69ea3bd..dff860bd2 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -1470,6 +1470,7 @@ namespace libtorrent { policy::peer* p = static_cast(*i); if (p == 0) continue; + if (p->connection == 0) continue; TORRENT_ASSERT(std::find_if(m_peers.begin(), m_peers.end() , match_peer_connection(*p->connection)) != m_peers.end()); } diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index f855dce8b..1cac99e44 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -460,6 +460,8 @@ namespace libtorrent ->set_size(file_iter->size); } } + // close files that were opened in write mode + m_files.release(this); } void storage::release_files() From 50877923eae7cd4090d940c95857f47075069f18 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 7 Oct 2007 20:29:43 +0000 Subject: [PATCH 0201/1009] pickle->cPickle --- deluge/config.py | 8 ++++---- deluge/core/core.py | 1 - deluge/core/torrentmanager.py | 6 +++--- deluge/ui/functions.py | 1 - 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index f1ed0c1b8..c9b0ed893 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -33,7 +33,7 @@ """Configuration class used to access/create/modify configuration files.""" -import pickle +import cPickle import deluge.common from deluge.log import LOG as log @@ -71,7 +71,7 @@ class Config: # Un-pickle the file and update the config dictionary log.debug("Opening pickled file for load..") pkl_file = open(filename, "rb") - filedump = pickle.load(pkl_file) + filedump = cPickle.load(pkl_file) self.config.update(filedump) pkl_file.close() except IOError: @@ -91,7 +91,7 @@ class Config: try: log.debug("Opening pickled file for comparison..") pkl_file = open(filename, "rb") - filedump = pickle.load(pkl_file) + filedump = cPickle.load(pkl_file) pkl_file.close() if filedump == self.config: # The config has not changed so lets just return @@ -103,7 +103,7 @@ class Config: try: log.debug("Opening pickled file for save..") pkl_file = open(filename, "wb") - pickle.dump(self.config, pkl_file) + cPickle.dump(self.config, pkl_file) log.debug("Closing pickled file..") pkl_file.close() except IOError: diff --git a/deluge/core/core.py b/deluge/core/core.py index 6e46be79e..fcd152db4 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -31,7 +31,6 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import pickle import dbus import dbus.service from dbus.mainloop.glib import DBusGMainLoop diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 5123716ed..3cea9beae 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -33,7 +33,7 @@ """TorrentManager handles Torrent objects""" -import pickle +import cPickle import os.path import os @@ -305,7 +305,7 @@ class TorrentManager: log.debug("Opening torrent state file for load.") state_file = open(deluge.common.get_config_dir("torrents.state"), "rb") - state = pickle.load(state_file) + state = cPickle.load(state_file) state_file.close() except IOError: log.warning("Unable to load state file.") @@ -329,7 +329,7 @@ class TorrentManager: log.debug("Saving torrent state file.") state_file = open(deluge.common.get_config_dir("torrents.state"), "wb") - pickle.dump(state, state_file) + cPickle.dump(state, state_file) state_file.close() except IOError: log.warning("Unable to save state file.") diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index fb6b5dcfc..e085d7024 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -32,7 +32,6 @@ # statement from all source files in the program, then also delete it here. import os.path -import pickle import dbus from dbus.mainloop.glib import DBusGMainLoop From 8f974b80676b8a4de7dc0d5b94b1d4bf4c8b74bb Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 7 Oct 2007 21:03:25 +0000 Subject: [PATCH 0202/1009] better shutdown sequence in lt --- libtorrent/include/libtorrent/buffer.hpp | 4 +-- .../include/libtorrent/session_settings.hpp | 2 +- .../include/libtorrent/tracker_manager.hpp | 1 + libtorrent/src/session_impl.cpp | 35 ++++++++++++++----- libtorrent/src/torrent.cpp | 1 + libtorrent/src/tracker_manager.cpp | 5 +++ 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index 55ab99d9f..a823a7677 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -172,8 +172,8 @@ public: } bool empty() const { return m_begin == m_end; } - char& operator[](std::size_t i) { TORRENT_ASSERT(i >= 0 && i < size()); return m_begin[i]; } - char const& operator[](std::size_t i) const { TORRENT_ASSERT(i >= 0 && i < size()); return m_begin[i]; } + char& operator[](std::size_t i) { TORRENT_ASSERT(i < size()); return m_begin[i]; } + char const& operator[](std::size_t i) const { TORRENT_ASSERT(i < size()); return m_begin[i]; } char* begin() { return m_begin; } char const* begin() const { return m_begin; } diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index 3a145c687..fbf2c8a03 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -85,7 +85,7 @@ namespace libtorrent : user_agent(user_agent_) , tracker_completion_timeout(60) , tracker_receive_timeout(20) - , stop_tracker_timeout(10) + , stop_tracker_timeout(5) , tracker_maximum_response_length(1024*1024) , piece_timeout(120) , request_queue_time(3.f) diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index b1b4d87ac..07c377a0f 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -234,6 +234,7 @@ namespace libtorrent void remove_request(tracker_connection const*); bool empty() const; + int num_requests() const; private: diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 6f42438b0..3ae3839b4 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -638,11 +638,26 @@ namespace detail void session_impl::abort() { mutex_t::scoped_lock l(m_mutex); - TORRENT_ASSERT(!m_abort); + if (m_abort) return; + m_io_service.stop(); +#if defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " *** ABORT CALLED ***\n"; +#endif // abort the main thread m_abort = true; - m_io_service.stop(); - l.unlock(); + if (m_lsd) m_lsd->close(); + if (m_upnp) m_upnp->close(); + if (m_natpmp) m_natpmp->close(); +#ifndef TORRENT_DISABLE_DHT + if (m_dht) m_dht->stop(); +#endif + m_timer.cancel(); + // abort all torrents + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + i->second->abort(); + } mutex::scoped_lock l2(m_checker_impl.m_mutex); // abort the checker thread @@ -1031,17 +1046,17 @@ namespace detail // too expensive // INVARIANT_CHECK; + if (m_abort) return; + if (e) { #if defined(TORRENT_LOGGING) (*m_logger) << "*** SECOND TIMER FAILED " << e.message() << "\n"; #endif - m_abort = true; - m_io_service.stop(); + abort(); return; } - if (m_abort) return; float tick_interval = total_microseconds(time_now() - m_last_tick) / 1000000.f; m_last_tick = time_now(); @@ -1488,6 +1503,10 @@ namespace detail m_settings.stop_tracker_timeout) && !m_tracker_manager.empty()) { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " " << m_tracker_manager.num_requests() + << " tracker requests pending\n"; +#endif tracker_timer.expires_from_now(milliseconds(100)); tracker_timer.async_wait(m_strand.wrap( bind(&io_service::stop, &m_io_service))); @@ -2072,9 +2091,7 @@ namespace detail session_impl::~session_impl() { -#ifndef TORRENT_DISABLE_DHT - stop_dht(); -#endif + abort(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << "\n\n *** shutting down session *** \n\n"; diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 840525c8a..a094fa749 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -1029,6 +1029,7 @@ namespace libtorrent bind(&torrent::on_files_released, shared_from_this(), _1, _2)); m_owning_storage = 0; + m_announce_timer.cancel(); } void torrent::on_files_released(int ret, disk_io_job const& j) diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 37dd941eb..0118e5802 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -584,4 +584,9 @@ namespace libtorrent return m_connections.empty(); } + int tracker_manager::num_requests() const + { + mutex_t::scoped_lock l(m_mutex); + return m_connections.size(); + } } From 2b9d8cc670bb0d2f97dc44a46273e78e52a1b8ac Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 7 Oct 2007 23:54:04 +0000 Subject: [PATCH 0203/1009] fixed dead lock and fixed a problem in recent shutdown cleanup --- libtorrent/src/session_impl.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 3ae3839b4..83498ac14 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -337,11 +337,15 @@ namespace detail { TORRENT_ASSERT(!m_processing.empty()); TORRENT_ASSERT(m_processing.front() == processing); + m_processing.pop_front(); + // make sure the lock order is correct + l.unlock(); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + l.lock(); processing->torrent_ptr->abort(); processing.reset(); - m_processing.pop_front(); if (!m_processing.empty()) { processing = m_processing.front(); @@ -426,12 +430,13 @@ namespace detail processing->torrent_ptr->get_handle() , e.what())); } - TORRENT_ASSERT(!m_processing.empty()); processing->torrent_ptr->abort(); + if (!m_processing.empty() + && m_processing.front() == processing) + m_processing.pop_front(); processing.reset(); - m_processing.pop_front(); if (!m_processing.empty()) { processing = m_processing.front(); @@ -639,7 +644,6 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); if (m_abort) return; - m_io_service.stop(); #if defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " *** ABORT CALLED ***\n"; #endif @@ -659,6 +663,8 @@ namespace detail i->second->abort(); } + m_io_service.stop(); + mutex::scoped_lock l2(m_checker_impl.m_mutex); // abort the checker thread m_checker_impl.m_abort = true; From b4cb4e7bcc15d292ee70e1aad5d32f11cbde8921 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 12 Oct 2007 23:37:42 +0000 Subject: [PATCH 0204/1009] Import the 0.6 xmlrpc branch.. Work in progess. --- deluge/core/core.py | 218 ++++++++++++------------------ deluge/core/daemon.py | 18 +-- deluge/ui/functions.py | 103 +++++++------- deluge/ui/gtkui/gtkui.py | 4 +- deluge/ui/gtkui/statusbar.py | 9 +- deluge/ui/gtkui/systemtray.py | 11 +- deluge/ui/gtkui/torrentdetails.py | 6 +- deluge/ui/gtkui/torrentview.py | 5 +- 8 files changed, 153 insertions(+), 221 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index fcd152db4..c830ed496 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -31,15 +31,17 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import dbus -import dbus.service -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) import gettext import locale import pkg_resources +import sys +import pickle +import SimpleXMLRPCServer +from SocketServer import ThreadingMixIn +import xmlrpclib import gobject +import threading import deluge.libtorrent as lt from deluge.configmanager import ConfigManager @@ -73,9 +75,27 @@ DEFAULT_PREFS = { "max_upload_slots_per_torrent": -1, "enabled_plugins": ["Queue"] } + +class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): + def __init__(self): + log.debug("Core init..") + threading.Thread.__init__(self) -class Core(dbus.service.Object): - def __init__(self, path="/org/deluge_torrent/Core"): + # Setup the xmlrpc server + try: + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( + self, ("localhost", 6666), logRequests=False, allow_none=True) + except: + log.info("Daemon already running or port not available..") + sys.exit(0) + + # Register all export_* functions + for func in dir(self): + if func.startswith("export_"): + self.register_function(getattr(self, "%s" % func), func[7:]) + + self.register_introspection_functions() + # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') locale.bindtextdomain("deluge", @@ -89,13 +109,9 @@ class Core(dbus.service.Object): gettext.install("deluge", pkg_resources.resource_filename( "deluge", "i18n")) - log.debug("Core init..") - # Setup DBUS - bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", - bus=dbus.SessionBus()) - dbus.service.Object.__init__(self, bus_name, path) - + def run(self): + """Starts the core""" # Get config self.config = ConfigManager("core.conf", DEFAULT_PREFS) @@ -119,29 +135,29 @@ class Core(dbus.service.Object): # Register set functions in the Config self.config.register_set_function("listen_ports", - self.on_set_listen_ports) + self._on_set_listen_ports) self.config.register_set_function("random_port", - self.on_set_random_port) - self.config.register_set_function("dht", self.on_set_dht) - self.config.register_set_function("upnp", self.on_set_upnp) - self.config.register_set_function("natpmp", self.on_set_natpmp) - self.config.register_set_function("utpex", self.on_set_utpex) + self._on_set_random_port) + self.config.register_set_function("dht", self._on_set_dht) + self.config.register_set_function("upnp", self._on_set_upnp) + self.config.register_set_function("natpmp", self._on_set_natpmp) + self.config.register_set_function("utpex", self._on_set_utpex) self.config.register_set_function("enc_in_policy", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("enc_out_policy", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("enc_level", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("enc_prefer_rc4", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("max_connections_global", - self.on_set_max_connections_global) + self._on_set_max_connections_global) self.config.register_set_function("max_upload_speed", - self.on_set_max_upload_speed) + self._on_set_max_upload_speed) self.config.register_set_function("max_download_speed", - self.on_set_max_download_speed) + self._on_set_max_download_speed) self.config.register_set_function("max_upload_slots_global", - self.on_set_max_upload_slots_global) + self._on_set_max_upload_slots_global) # Start the AlertManager self.alerts = AlertManager(self.session) @@ -154,16 +170,18 @@ class Core(dbus.service.Object): # Register alert handlers self.alerts.register_handler("torrent_paused_alert", - self.on_alert_torrent_paused) - - log.debug("Starting main loop..") + self._on_alert_torrent_paused) + + t = threading.Thread(target=self.serve_forever) + t.start() + gobject.threads_init() + self.loop = gobject.MainLoop() self.loop.run() - + def _shutdown(self): """This is called by a thread from shutdown()""" log.info("Shutting down core..") - self.loop.quit() self.plugins.shutdown() self.torrents.shutdown() # Make sure the config file has been saved @@ -171,25 +189,22 @@ class Core(dbus.service.Object): del self.config del deluge.configmanager del self.session + self.loop.quit() # Exported Methods - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", out_signature="") - def shutdown(self): + def export_shutdown(self): """Shutdown the core""" # Make shutdown an async call gobject.idle_add(self._shutdown) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="ssay", out_signature="b") - def add_torrent_file(self, filename, save_path, filedump): + def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content """ if save_path == "": save_path = None - torrent_id = self.torrents.add(filename, filedump=filedump, + torrent_id = self.torrents.add(filename, filedump=filedump.data, save_path=save_path) # Run the plugin hooks for 'post_torrent_add' @@ -203,9 +218,7 @@ class Core(dbus.service.Object): # Return False because the torrent was not added successfully return False - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="ss", out_signature="b") - def add_torrent_url(self, url, save_path): + def export_add_torrent_url(self, url, save_path): log.info("Attempting to add url %s", url) # Get the actual filename of the torrent from the url provided. @@ -224,11 +237,9 @@ class Core(dbus.service.Object): return False # Add the torrent to session - return self.add_torrent_file(filename, save_path, filedump) + return self.export_add_torrent_file(filename, save_path, filedump) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def remove_torrent(self, torrent_id): + def export_remove_torrent(self, torrent_id): log.debug("Removing torrent %s from the core.", torrent_id) if self.torrents.remove(torrent_id): # Run the plugin hooks for 'post_torrent_remove' @@ -236,43 +247,32 @@ class Core(dbus.service.Object): # Emit the torrent_removed signal self.torrent_removed(torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def force_reannounce(self, torrent_id): + def export_force_reannounce(self, torrent_id): log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id) self.torrents.force_reannounce(torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def pause_torrent(self, torrent_id): + def export_pause_torrent(self, torrent_id): log.debug("Pausing torrent %s", torrent_id) if not self.torrents.pause(torrent_id): log.warning("Error pausing torrent %s", torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") - def pause_all_torrents(self): + def export_pause_all_torrents(self): """Pause all torrents in the session""" if not self.torrents.pause_all(): log.warning("Error pausing all torrents..") - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") - def resume_all_torrents(self): + def export_resume_all_torrents(self): """Resume all torrents in the session""" if self.torrents.resume_all(): # Emit the 'torrent_all_resumed' signal self.torrent_all_resumed() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def resume_torrent(self, torrent_id): + def export_resume_torrent(self, torrent_id): log.debug("Resuming torrent %s", torrent_id) if self.torrents.resume(torrent_id): self.torrent_resumed(torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="sas", - out_signature="a{sv}") - def get_torrent_status(self, torrent_id, keys): + def export_get_torrent_status(self, torrent_id, keys): # Convert the array of strings to a python list of strings keys = deluge.common.pythonize(keys) # Build the status dictionary @@ -286,33 +286,23 @@ class Core(dbus.service.Object): leftover_fields = list(set(keys) - set(status.keys())) if len(leftover_fields) > 0: status.update(self.plugins.get_status(torrent_id, leftover_fields)) - return status + return xmlrpclib.Binary(pickle.dumps(status)) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", - out_signature="as") - def get_session_state(self): + def export_get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager return self.torrents.get_torrent_list() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") - def save_state(self): + def export_save_state(self): """Save the current session state to file.""" # Have the TorrentManager save it's state self.torrents.save_state() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", - out_signature="a{sv}") - def get_config(self): + def export_get_config(self): """Get all the preferences as a dictionary""" return self.config.get_config() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", - out_signature="v") - def get_config_value(self, key): + def export_get_config_value(self, key): """Get the config value for key""" try: value = self.config[key] @@ -320,105 +310,77 @@ class Core(dbus.service.Object): return None return value - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="a{sv}") - def set_config(self, config): + + def export_set_config(self, config): """Set the config with values from dictionary""" config = deluge.common.pythonize(config) # Load all the values into the configuration for key in config.keys(): self.config[key] = config[key] - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="i") - def get_listen_port(self): + + def export_get_listen_port(self): """Returns the active listen port""" return self.session.listen_port() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="i") - def get_num_connections(self): + def export_get_num_connections(self): """Returns the current number of connections""" return self.session.num_connections() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="d") - def get_download_rate(self): + def export_get_download_rate(self): """Returns the payload download rate""" return self.session.status().payload_download_rate - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="d") - def get_upload_rate(self): + def export_get_upload_rate(self): """Returns the payload upload rate""" return self.session.status().payload_upload_rate - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="as") - def get_available_plugins(self): + def export_get_available_plugins(self): """Returns a list of plugins available in the core""" return self.plugins.get_available_plugins() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="as") - def get_enabled_plugins(self): + def export_get_enabled_plugins(self): """Returns a list of enabled plugins in the core""" return self.plugins.get_enabled_plugins() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s") - def enable_plugin(self, plugin): + def export_enable_plugin(self, plugin): self.plugins.enable_plugin(plugin) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s") - def disable_plugin(self, plugin): + def export_disable_plugin(self, plugin): self.plugins.disable_plugin(plugin) # Signals - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_removed(self, torrent_id): """Emitted when a torrent has been removed from the core""" log.debug("torrent_remove signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_paused(self, torrent_id): """Emitted when a torrent is paused""" log.debug("torrent_paused signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_resumed(self, torrent_id): """Emitted when a torrent is resumed""" log.debug("torrent_resumed signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge") def torrent_all_paused(self): """Emitted when all torrents have been paused""" log.debug("torrent_all_paused signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge") def torrent_all_resumed(self): """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") # Config set functions - def on_set_listen_ports(self, key, value): + def _on_set_listen_ports(self, key, value): # Only set the listen ports if random_port is not true if self.config["random_port"] is not True: log.debug("listen port range set to %s-%s", value[0], value[1]) self.session.listen_on(value[0], value[1]) - def on_set_random_port(self, key, value): + def _on_set_random_port(self, key, value): log.debug("random port value set to %s", value) # We need to check if the value has been changed to true and false # and then handle accordingly. @@ -436,33 +398,33 @@ class Core(dbus.service.Object): listen_ports[1]) self.session.listen_on(listen_ports[0], listen_ports[1]) - def on_set_dht(self, key, value): + def _on_set_dht(self, key, value): log.debug("dht value set to %s", value) if value: self.session.start_dht(None) else: self.session.stop_dht() - def on_set_upnp(self, key, value): + def _on_set_upnp(self, key, value): log.debug("upnp value set to %s", value) if value: self.session.start_upnp() else: self.session.stop_upnp() - def on_set_natpmp(self, key, value): + def _on_set_natpmp(self, key, value): log.debug("natpmp value set to %s", value) if value: self.session.start_natpmp() else: self.session.stop_natpmp() - def on_set_utpex(self, key, value): + def _on_set_utpex(self, key, value): log.debug("utpex value set to %s", value) if value: self.session.add_extension(lt.create_ut_pex_plugin) - def on_set_encryption(self, key, value): + def _on_set_encryption(self, key, value): log.debug("encryption value %s set to %s..", key, value) pe_settings = lt.pe_settings() pe_settings.out_enc_policy = \ @@ -479,26 +441,26 @@ class Core(dbus.service.Object): set.allowed_enc_level, set.prefer_rc4) - def on_set_max_connections_global(self, key, value): + def _on_set_max_connections_global(self, key, value): log.debug("max_connections_global set to %s..", value) self.session.set_max_connections(value) - def on_set_max_upload_speed(self, key, value): + def _on_set_max_upload_speed(self, key, value): log.debug("max_upload_speed set to %s..", value) # We need to convert Kb/s to B/s self.session.set_upload_rate_limit(int(value * 1024)) - def on_set_max_download_speed(self, key, value): + def _on_set_max_download_speed(self, key, value): log.debug("max_download_speed set to %s..", value) # We need to convert Kb/s to B/s self.session.set_download_rate_limit(int(value * 1024)) - def on_set_max_upload_slots_global(self, key, value): + def _on_set_max_upload_slots_global(self, key, value): log.debug("max_upload_slots_global set to %s..", value) self.session.set_max_uploads(value) ## Alert handlers ## - def on_alert_torrent_paused(self, alert): + def _on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index e4bc29603..024cebe54 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -31,23 +31,13 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import dbus -from dbus.mainloop.glib import DBusGMainLoop - from deluge.core.core import Core from deluge.log import LOG as log class Daemon: def __init__(self): - # Check to see if the daemon is already running and if not, start it - bus = dbus.SessionBus() - obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") - iface = dbus.Interface(obj, "org.freedesktop.DBus") - if iface.NameHasOwner("org.deluge_torrent.Deluge"): - # Daemon is running so lets tell the user - log.info("Daemon is already running..") - else: - # Daemon is not running so lets start up the core - log.debug("Daemon is not running..") - self.core = Core() + # Start the core as a thread and join it until it's done + self.core = Core() + self.core.start() + self.core.join() diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index e085d7024..7a0e614a8 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -32,10 +32,12 @@ # statement from all source files in the program, then also delete it here. import os.path +import pickle -import dbus -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) +import xmlrpclib +#import dbus +#from dbus.mainloop.glib import DBusGMainLoop +#DBusGMainLoop(set_as_default=True) import pygtk pygtk.require('2.0') @@ -44,16 +46,22 @@ import gtk, gtk.glade import deluge.common from deluge.log import LOG as log +class CoreProxy: + def __init__(self): + self._core = None + + def get_core(self): + if self._core is None: + log.debug("Creating ServerProxy..") + self._core = xmlrpclib.ServerProxy("http://localhost:6666") + + return self._core + +_core = CoreProxy() + def get_core(): """Get the core object and return it""" - log.debug("Getting core proxy object from DBUS..") - # Get the proxy object from DBUS - bus = dbus.SessionBus() - proxy = bus.get_object("org.deluge_torrent.Deluge", - "/org/deluge_torrent/Core") - core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") - log.debug("Got core proxy object..") - return core + return _core.get_core() def get_core_plugin(plugin): """Get the core plugin object and return it""" @@ -66,9 +74,7 @@ def get_core_plugin(plugin): def shutdown(): """Shutdown the core daemon""" - core = get_core() - core.shutdown() - return + get_core().shutdown() def add_torrent_file(torrent_files): """Adds torrent files to the core @@ -78,25 +84,25 @@ def add_torrent_file(torrent_files): log.debug("No torrent files selected..") return log.debug("Attempting to add torrent files: %s", torrent_files) - core = get_core() for torrent_file in torrent_files: # Open the .torrent file for reading because we need to send it's # contents to the core. f = open(torrent_file, "rb") # Get the filename because the core doesn't want a path. (path, filename) = os.path.split(torrent_file) - result = core.add_torrent_file(filename, str(), f.read()) + fdump = xmlrpclib.Binary(f.read()) f.close() + result = get_core().add_torrent_file(filename, str(), fdump) + if result is False: # The torrent was not added successfully. log.warning("Torrent %s was not added successfully.", filename) def add_torrent_url(torrent_url): """Adds torrents to the core via url""" - core = get_core() from deluge.common import is_url if is_url(torrent_url): - result = core.add_torrent_url(torrent_url, str()) + result = get_core().add_torrent_url(torrent_url, str()) if result is False: # The torrent url was not added successfully. log.warning("Torrent %s was not added successfully.", torrent_url) @@ -106,69 +112,52 @@ def add_torrent_url(torrent_url): def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) - core = get_core() for torrent_id in torrent_ids: - core.remove_torrent(torrent_id) + get_core().remove_torrent(torrent_id) def pause_torrent(torrent_ids): """Pauses torrent_ids""" - core = get_core() for torrent_id in torrent_ids: - core.pause_torrent(torrent_id) + get_core().pause_torrent(torrent_id) def resume_torrent(torrent_ids): """Resume torrent_ids""" - core = get_core() for torrent_id in torrent_ids: - core.resume_torrent(torrent_id) + get_core().resume_torrent(torrent_id) def force_reannounce(torrent_ids): """Reannounce to trackers""" - core = get_core() for torrent_id in torrent_ids: - core.force_reannounce(torrent_id) + get_core().force_reannounce(torrent_id) -def get_torrent_status(core, torrent_id, keys): +def get_torrent_status(torrent_id, keys): """Builds the status dictionary and returns it""" - return deluge.common.pythonize(core.get_torrent_status(torrent_id, keys)) + status = get_core().get_torrent_status(torrent_id, keys) + return pickle.loads(status.data) -def get_session_state(core=None): - # Get the core if not supplied - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_session_state()) +def get_session_state(): + return get_core().get_session_state() -def get_config(core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_config()) -def get_config_value(key, core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_config_value(key)) +def get_config(): + return get_core().get_config() + +def get_config_value(key): + return get_core().get_config_value(key) -def set_config(config, core=None): +def set_config(config): if config == {}: return - if core is None: - core = get_core() - core.set_config(config) + get_core().set_config(config) -def get_listen_port(core=None): - if core is None: - core = get_core() - return int(core.get_listen_port()) +def get_listen_port(): + return int(get_core().get_listen_port()) -def get_available_plugins(core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_available_plugins()) +def get_available_plugins(): + return get_core().get_available_plugins() -def get_enabled_plugins(core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_enabled_plugins()) +def get_enabled_plugins(): + return get_core().get_enabled_plugins() def open_url_in_browser(url): """Opens link in the desktop's default browser""" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 5d4ab0e06..6d0b0f6a7 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -93,7 +93,7 @@ class GtkUI: self.mainwindow = MainWindow() # Start the signal receiver - self.signal_receiver = Signals(self) + #self.signal_receiver = Signals(self) # Initalize the plugins self.plugins = PluginManager(self) @@ -112,6 +112,6 @@ class GtkUI: # Clean-up del self.mainwindow - del self.signal_receiver + #del self.signal_receiver del self.plugins del deluge.configmanager diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index d9c79f5ff..64d0f9379 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -74,8 +74,7 @@ class StatusBar: def update(self): # Set the max connections label - max_connections = functions.get_config_value("max_connections_global", - core=self.core) + max_connections = functions.get_config_value("max_connections_global") if max_connections < 0: max_connections = _("Unlimited") @@ -83,8 +82,7 @@ class StatusBar: self.core.get_num_connections(), max_connections)) # Set the download speed label - max_download_speed = functions.get_config_value("max_download_speed", - core=self.core) + max_download_speed = functions.get_config_value("max_download_speed") if max_download_speed < 0: max_download_speed = _("Unlimited") else: @@ -95,8 +93,7 @@ class StatusBar: max_download_speed)) # Set the upload speed label - max_upload_speed = functions.get_config_value("max_upload_speed", - core=self.core) + max_upload_speed = functions.get_config_value("max_upload_speed") if max_upload_speed < 0: max_upload_speed = _("Unlimited") else: diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 2f270e495..14767206d 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -86,14 +86,13 @@ class SystemTray: # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( self.config["tray_download_speed_list"], self.tray_setbwdown, - functions.get_config_value("max_download_speed", - core=self.core), _("KiB/s"), show_notset=True, - show_other=True) + functions.get_config_value("max_download_speed"), + _("KiB/s"), show_notset=True, show_other=True) # Create the Upload speed list sub-menu submenu_bwupset = self.build_menu_radio_list( self.config["tray_upload_speed_list"], self.tray_setbwup, - functions.get_config_value("max_upload_speed", core=self.core), + functions.get_config_value("max_upload_speed"), _("KiB/s"), show_notset=True, show_other=True) # Add the sub-menus to the tray menu @@ -282,7 +281,7 @@ class SystemTray: spin_title.set_text(_("%s Speed (KiB/s):" % string)) spin_speed = dialog_glade.get_widget("spin_speed") spin_speed.set_value( - functions.get_config_value(core_key, core=self.core)) + functions.get_config_value(core_key)) spin_speed.select_region(0, -1) response = speed_dialog.run() if response == 1: # OK Response @@ -295,7 +294,7 @@ class SystemTray: # Set the config in the core value = float(value) config_to_set = {core_key: value} - functions.set_config(config_to_set, core=self.core) + functions.set_config(config_to_set) # Update the tray speed limit list if value not in self.config[ui_key] and value >= 0: diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 40a97e0b9..03cc0c0bf 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -47,8 +47,6 @@ class TorrentDetails: self.window = window glade = self.window.main_glade - self.core = functions.get_core() - self.notebook = glade.get_widget("torrent_info") self.details_tab = glade.get_widget("torrentdetails_tab") @@ -95,9 +93,7 @@ class TorrentDetails: "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", "tracker_status", "save_path"] - status = functions.get_torrent_status(self.core, - selected, - status_keys) + status = functions.get_torrent_status(selected, status_keys) # Check to see if we got valid data from the core if status is None: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 657c45bce..dbf961862 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -104,7 +104,6 @@ class TorrentView(listview.ListView): listview.ListView.__init__(self, self.window.main_glade.get_widget("torrent_view")) log.debug("TorrentView Init..") - self.core = functions.get_core() # Register the columns menu with the listview so it gets updated # accordingly. @@ -166,7 +165,7 @@ class TorrentView(listview.ListView): # We need to get the core session state to know which torrents are in # the session so we can add them to our list. - session_state = functions.get_session_state(self.core) + session_state = functions.get_session_state() for torrent_id in session_state: self.add_row(torrent_id) @@ -213,7 +212,7 @@ class TorrentView(listview.ListView): # Remove duplicates from status_key list status_keys = list(set(status_keys)) - status = functions.get_torrent_status(self.core, torrent_id, + status = functions.get_torrent_status(torrent_id, status_keys) # Set values for each column in the row From 64a94ec197ed19a760df3cc0aa8cfbc9b602f2d8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 14 Oct 2007 07:06:19 +0000 Subject: [PATCH 0205/1009] Add SignalReceiver and use it in the client. Add SignalManager in the core for emitting signals to clients. --- deluge/core/core.py | 22 +++++++- deluge/core/signalmanager.py | 53 ++++++++++++++++++ deluge/ui/gtkui/gtkui.py | 5 +- deluge/ui/gtkui/signals.py | 20 ++++--- deluge/ui/signalreceiver.py | 105 +++++++++++++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 deluge/core/signalmanager.py create mode 100644 deluge/ui/signalreceiver.py diff --git a/deluge/core/core.py b/deluge/core/core.py index c830ed496..6d266ad0c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -49,6 +49,7 @@ import deluge.common from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager from deluge.core.alertmanager import AlertManager +from deluge.core.signalmanager import SignalManager from deluge.log import LOG as log DEFAULT_PREFS = { @@ -76,7 +77,10 @@ DEFAULT_PREFS = { "enabled_plugins": ["Queue"] } -class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): +class Core( + threading.Thread, + ThreadingMixIn, + SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self): log.debug("Core init..") threading.Thread.__init__(self) @@ -161,7 +165,10 @@ class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServ # Start the AlertManager self.alerts = AlertManager(self.session) - + + # Start the SignalManager + self.signals = SignalManager() + # Start the TorrentManager self.torrents = TorrentManager(self.session, self.alerts) @@ -197,6 +204,11 @@ class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServ # Make shutdown an async call gobject.idle_add(self._shutdown) + def export_register_client(self, uri): + """Registers a client with the signal manager so that signals are + sent to it.""" + self.signals.register_client(uri) + def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content @@ -352,26 +364,32 @@ class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServ def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") + self.signals.emit("torrent_added", torrent_id) def torrent_removed(self, torrent_id): """Emitted when a torrent has been removed from the core""" log.debug("torrent_remove signal emitted") + self.signals.emit("torrent_removed", torrent_id) def torrent_paused(self, torrent_id): """Emitted when a torrent is paused""" log.debug("torrent_paused signal emitted") + self.signals.emit("torrent_paused", torrent_id) def torrent_resumed(self, torrent_id): """Emitted when a torrent is resumed""" log.debug("torrent_resumed signal emitted") + self.signals.emit("torrent_resumed", torrent_id) def torrent_all_paused(self): """Emitted when all torrents have been paused""" log.debug("torrent_all_paused signal emitted") + self.signals.emit("torrent_all_paused", torrent_id) def torrent_all_resumed(self): """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") + self.signals.emit("torrent_all_resumed", torrent_id) # Config set functions def _on_set_listen_ports(self, key, value): diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py new file mode 100644 index 000000000..ec3fdf318 --- /dev/null +++ b/deluge/core/signalmanager.py @@ -0,0 +1,53 @@ +# +# signalmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import xmlrpclib + +from deluge.log import LOG as log + +class SignalManager: + def __init__(self): + self.clients = [] + + def register_client(self, uri): + """Registers a client to emit signals to.""" + log.debug("Registering %s as a signal reciever..", uri) + self.clients.append(xmlrpclib.ServerProxy(uri)) + + def emit(self, signal, data): + for client in self.clients: + try: + client.emit_signal(signal, data) + except: + log.warning("Unable to emit signal to client %s", client) + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 6d0b0f6a7..f11c89198 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -93,8 +93,8 @@ class GtkUI: self.mainwindow = MainWindow() # Start the signal receiver - #self.signal_receiver = Signals(self) - + self.signal_receiver = Signals(self) + # Initalize the plugins self.plugins = PluginManager(self) @@ -102,6 +102,7 @@ class GtkUI: self.mainwindow.start() # Start the gtk main loop + gtk.gdk.threads_init() gtk.main() log.debug("gtkui shutting down..") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 1698af094..37b9f35e3 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -31,21 +31,23 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import deluge.ui.functions as functions +from deluge.ui.signalreceiver import SignalReceiver from deluge.log import LOG as log class Signals: def __init__(self, ui): self.ui = ui - self.core = functions.get_core() - self.core.connect_to_signal("torrent_added", self.torrent_added_signal) - self.core.connect_to_signal("torrent_removed", - self.torrent_removed_signal) - self.core.connect_to_signal("torrent_paused", self.torrent_paused) - self.core.connect_to_signal("torrent_resumed", self.torrent_resumed) - self.core.connect_to_signal("torrent_all_paused", + self.receiver = SignalReceiver(6667, "http://localhost:6666") + self.receiver.start() + self.receiver.connect_to_signal("torrent_added", + self.torrent_added_signal) + self.receiver.connect_to_signal("torrent_removed", + self.torrent_removed_signal) + self.receiver.connect_to_signal("torrent_paused", self.torrent_paused) + self.receiver.connect_to_signal("torrent_resumed", self.torrent_resumed) + self.receiver.connect_to_signal("torrent_all_paused", self.torrent_all_paused) - self.core.connect_to_signal("torrent_all_resumed", + self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) def torrent_added_signal(self, torrent_id): diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py new file mode 100644 index 000000000..d22809942 --- /dev/null +++ b/deluge/ui/signalreceiver.py @@ -0,0 +1,105 @@ +# +# signalreceiver.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import sys +import SimpleXMLRPCServer +from SocketServer import ThreadingMixIn +import xmlrpclib +import threading + +from deluge.log import LOG as log + +class SignalReceiver( + threading.Thread, + ThreadingMixIn, + SimpleXMLRPCServer.SimpleXMLRPCServer): + + def __init__(self, port, core_uri): + log.debug("SignalReceiver init..") + threading.Thread.__init__(self) + + # Daemonize the thread so it exits when the main program does + self.setDaemon(True) + + # Setup the xmlrpc server + try: + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( + self, ("localhost", port), logRequests=False, allow_none=True) + except: + log.info("SignalReceiver already running or port not available..") + sys.exit(0) + + self.signals = {} + + # Register the emit_signal function + self.register_function(self.emit_signal) + + # Register the signal receiver with the core + # FIXME: send actual URI not localhost + core = xmlrpclib.ServerProxy(core_uri) + core.register_client("http://localhost:" + str(port)) + + def run(self): + """This gets called when we start the thread""" + t = threading.Thread(target=self.serve_forever) + t.start() + + def emit_signal(self, signal, data): + """Exported method used by the core to emit a signal to the client""" + log.debug("Received signal %s with data %s from core..", signal, data) + try: + if data != None: + for callback in self.signals[signal]: + try: + callback(data) + except: + log.warning("Unable to call callback for signal %s", + signal) + else: + for callback in self.signals[signal]: + try: + callback() + except: + log.warning("Unable to call callback for signal %s", + signal) + except KeyError: + log.debug("There are no callbacks registered for this signal..") + + def connect_to_signal(self, signal, callback): + """Connect to a signal""" + try: + self.signals[signal].append(callback) + except KeyError: + self.signals[signal] = [callback] + + From 5b7b027f4888abaee4ab11e64b2c025eb1579392 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 01:56:47 +0000 Subject: [PATCH 0206/1009] Save the uploaded_total in torrent state. --- deluge/core/torrent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 9f2935360..6c1b9f903 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -62,7 +62,7 @@ class Torrent: """Returns the state of this torrent for saving to the session state""" status = self.handle.status() return (self.torrent_id, self.filename, self.compact, status.paused, - self.save_path, self.total_uploaded) + self.save_path, self.total_uploaded + status.total_payload_upload) def get_eta(self): """Returns the ETA in seconds for this torrent""" From 287d96d1e71440097de532c1b78c047a62a6f127 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 03:49:45 +0000 Subject: [PATCH 0207/1009] Include SimpleXMLRPCServer from Python 2.5.1 --- deluge/SimpleXMLRPCServer.py | 595 +++++++++++++++++++++++++++++++++++ deluge/core/core.py | 2 +- deluge/ui/signalreceiver.py | 2 +- 3 files changed, 597 insertions(+), 2 deletions(-) create mode 100644 deluge/SimpleXMLRPCServer.py diff --git a/deluge/SimpleXMLRPCServer.py b/deluge/SimpleXMLRPCServer.py new file mode 100644 index 000000000..7a9f26faa --- /dev/null +++ b/deluge/SimpleXMLRPCServer.py @@ -0,0 +1,595 @@ +"""Simple XML-RPC Server. + +This module can be used to create simple XML-RPC servers +by creating a server and either installing functions, a +class instance, or by extending the SimpleXMLRPCServer +class. + +It can also be used to handle XML-RPC requests in a CGI +environment using CGIXMLRPCRequestHandler. + +A list of possible usage patterns follows: + +1. Install functions: + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_function(pow) +server.register_function(lambda x,y: x+y, 'add') +server.serve_forever() + +2. Install an instance: + +class MyFuncs: + def __init__(self): + # make all of the string functions available through + # string.func_name + import string + self.string = string + def _listMethods(self): + # implement this method so that system.listMethods + # knows to advertise the strings methods + return list_public_methods(self) + \ + ['string.' + method for method in list_public_methods(self.string)] + def pow(self, x, y): return pow(x, y) + def add(self, x, y) : return x + y + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(MyFuncs()) +server.serve_forever() + +3. Install an instance with custom dispatch method: + +class Math: + def _listMethods(self): + # this method must be present for system.listMethods + # to work + return ['add', 'pow'] + def _methodHelp(self, method): + # this method must be present for system.methodHelp + # to work + if method == 'add': + return "add(2,3) => 5" + elif method == 'pow': + return "pow(x, y[, z]) => number" + else: + # By convention, return empty + # string if no help is available + return "" + def _dispatch(self, method, params): + if method == 'pow': + return pow(*params) + elif method == 'add': + return params[0] + params[1] + else: + raise 'bad method' + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(Math()) +server.serve_forever() + +4. Subclass SimpleXMLRPCServer: + +class MathServer(SimpleXMLRPCServer): + def _dispatch(self, method, params): + try: + # We are forcing the 'export_' prefix on methods that are + # callable through XML-RPC to prevent potential security + # problems + func = getattr(self, 'export_' + method) + except AttributeError: + raise Exception('method "%s" is not supported' % method) + else: + return func(*params) + + def export_add(self, x, y): + return x + y + +server = MathServer(("localhost", 8000)) +server.serve_forever() + +5. CGI script: + +server = CGIXMLRPCRequestHandler() +server.register_function(pow) +server.handle_request() +""" + +# Written by Brian Quinlan (brian@sweetapp.com). +# Based on code written by Fredrik Lundh. + +import xmlrpclib +from xmlrpclib import Fault +import SocketServer +import BaseHTTPServer +import sys +import os +try: + import fcntl +except ImportError: + fcntl = None + +def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): + """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d + + Resolves a dotted attribute name to an object. Raises + an AttributeError if any attribute in the chain starts with a '_'. + + If the optional allow_dotted_names argument is false, dots are not + supported and this function operates similar to getattr(obj, attr). + """ + + if allow_dotted_names: + attrs = attr.split('.') + else: + attrs = [attr] + + for i in attrs: + if i.startswith('_'): + raise AttributeError( + 'attempt to access private attribute "%s"' % i + ) + else: + obj = getattr(obj,i) + return obj + +def list_public_methods(obj): + """Returns a list of attribute strings, found in the specified + object, which represent callable attributes""" + + return [member for member in dir(obj) + if not member.startswith('_') and + callable(getattr(obj, member))] + +def remove_duplicates(lst): + """remove_duplicates([2,2,2,1,3,3]) => [3,1,2] + + Returns a copy of a list without duplicates. Every list + item must be hashable and the order of the items in the + resulting list is not defined. + """ + u = {} + for x in lst: + u[x] = 1 + + return u.keys() + +class SimpleXMLRPCDispatcher: + """Mix-in class that dispatches XML-RPC requests. + + This class is used to register XML-RPC method handlers + and then to dispatch them. There should never be any + reason to instantiate this class directly. + """ + + def __init__(self, allow_none, encoding): + self.funcs = {} + self.instance = None + self.allow_none = allow_none + self.encoding = encoding + + def register_instance(self, instance, allow_dotted_names=False): + """Registers an instance to respond to XML-RPC requests. + + Only one instance can be installed at a time. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. Methods beginning with an '_' + are considered private and will not be called by + SimpleXMLRPCServer. + + If a registered function matches a XML-RPC request, then it + will be called instead of the registered instance. + + If the optional allow_dotted_names argument is true and the + instance does not have a _dispatch method, method names + containing dots are supported and resolved, as long as none of + the name segments start with an '_'. + + *** SECURITY WARNING: *** + + Enabling the allow_dotted_names options allows intruders + to access your module's global variables and may allow + intruders to execute arbitrary code on your machine. Only + use this option on a secure, closed network. + + """ + + self.instance = instance + self.allow_dotted_names = allow_dotted_names + + def register_function(self, function, name = None): + """Registers a function to respond to XML-RPC requests. + + The optional name argument can be used to set a Unicode name + for the function. + """ + + if name is None: + name = function.__name__ + self.funcs[name] = function + + def register_introspection_functions(self): + """Registers the XML-RPC introspection methods in the system + namespace. + + see http://xmlrpc.usefulinc.com/doc/reserved.html + """ + + self.funcs.update({'system.listMethods' : self.system_listMethods, + 'system.methodSignature' : self.system_methodSignature, + 'system.methodHelp' : self.system_methodHelp}) + + def register_multicall_functions(self): + """Registers the XML-RPC multicall method in the system + namespace. + + see http://www.xmlrpc.com/discuss/msgReader$1208""" + + self.funcs.update({'system.multicall' : self.system_multicall}) + + def _marshaled_dispatch(self, data, dispatch_method = None): + """Dispatches an XML-RPC method from marshalled (XML) data. + + XML-RPC methods are dispatched from the marshalled (XML) data + using the _dispatch method and the result is returned as + marshalled data. For backwards compatibility, a dispatch + function can be provided as an argument (see comment in + SimpleXMLRPCRequestHandler.do_POST) but overriding the + existing method through subclassing is the prefered means + of changing method dispatch behavior. + """ + + try: + params, method = xmlrpclib.loads(data) + + # generate response + if dispatch_method is not None: + response = dispatch_method(method, params) + else: + response = self._dispatch(method, params) + # wrap response in a singleton tuple + response = (response,) + response = xmlrpclib.dumps(response, methodresponse=1, + allow_none=self.allow_none, encoding=self.encoding) + except Fault, fault: + response = xmlrpclib.dumps(fault, allow_none=self.allow_none, + encoding=self.encoding) + except: + # report exception back to server + response = xmlrpclib.dumps( + xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)), + encoding=self.encoding, allow_none=self.allow_none, + ) + + return response + + def system_listMethods(self): + """system.listMethods() => ['add', 'subtract', 'multiple'] + + Returns a list of the methods supported by the server.""" + + methods = self.funcs.keys() + if self.instance is not None: + # Instance can implement _listMethod to return a list of + # methods + if hasattr(self.instance, '_listMethods'): + methods = remove_duplicates( + methods + self.instance._listMethods() + ) + # if the instance has a _dispatch method then we + # don't have enough information to provide a list + # of methods + elif not hasattr(self.instance, '_dispatch'): + methods = remove_duplicates( + methods + list_public_methods(self.instance) + ) + methods.sort() + return methods + + def system_methodSignature(self, method_name): + """system.methodSignature('add') => [double, int, int] + + Returns a list describing the signature of the method. In the + above example, the add method takes two integers as arguments + and returns a double result. + + This server does NOT support system.methodSignature.""" + + # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html + + return 'signatures not supported' + + def system_methodHelp(self, method_name): + """system.methodHelp('add') => "Adds two integers together" + + Returns a string containing documentation for the specified method.""" + + method = None + if self.funcs.has_key(method_name): + method = self.funcs[method_name] + elif self.instance is not None: + # Instance can implement _methodHelp to return help for a method + if hasattr(self.instance, '_methodHelp'): + return self.instance._methodHelp(method_name) + # if the instance has a _dispatch method then we + # don't have enough information to provide help + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name, + self.allow_dotted_names + ) + except AttributeError: + pass + + # Note that we aren't checking that the method actually + # be a callable object of some kind + if method is None: + return "" + else: + import pydoc + return pydoc.getdoc(method) + + def system_multicall(self, call_list): + """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ +[[4], ...] + + Allows the caller to package multiple XML-RPC calls into a single + request. + + See http://www.xmlrpc.com/discuss/msgReader$1208 + """ + + results = [] + for call in call_list: + method_name = call['methodName'] + params = call['params'] + + try: + # XXX A marshalling error in any response will fail the entire + # multicall. If someone cares they should fix this. + results.append([self._dispatch(method_name, params)]) + except Fault, fault: + results.append( + {'faultCode' : fault.faultCode, + 'faultString' : fault.faultString} + ) + except: + results.append( + {'faultCode' : 1, + 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)} + ) + return results + + def _dispatch(self, method, params): + """Dispatches the XML-RPC method. + + XML-RPC calls are forwarded to a registered function that + matches the called XML-RPC method name. If no such function + exists then the call is forwarded to the registered instance, + if available. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. + + Methods beginning with an '_' are considered private and will + not be called. + """ + + func = None + try: + # check to see if a matching function has been registered + func = self.funcs[method] + except KeyError: + if self.instance is not None: + # check for a _dispatch method + if hasattr(self.instance, '_dispatch'): + return self.instance._dispatch(method, params) + else: + # call instance method directly + try: + func = resolve_dotted_attribute( + self.instance, + method, + self.allow_dotted_names + ) + except AttributeError: + pass + + if func is not None: + return func(*params) + else: + raise Exception('method "%s" is not supported' % method) + +class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple XML-RPC request handler class. + + Handles all HTTP POST requests and attempts to decode them as + XML-RPC requests. + """ + + # Class attribute listing the accessible path components; + # paths not on this list will result in a 404 error. + rpc_paths = ('/', '/RPC2') + + def is_rpc_path_valid(self): + if self.rpc_paths: + return self.path in self.rpc_paths + else: + # If .rpc_paths is empty, just assume all paths are legal + return True + + def do_POST(self): + """Handles the HTTP POST request. + + Attempts to interpret all HTTP POST requests as XML-RPC calls, + which are forwarded to the server's _dispatch method for handling. + """ + + # Check that the path is legal + if not self.is_rpc_path_valid(): + self.report_404() + return + + try: + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + max_chunk_size = 10*1024*1024 + size_remaining = int(self.headers["content-length"]) + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + L.append(self.rfile.read(chunk_size)) + size_remaining -= len(L[-1]) + data = ''.join(L) + + # In previous versions of SimpleXMLRPCServer, _dispatch + # could be overridden in this class, instead of in + # SimpleXMLRPCDispatcher. To maintain backwards compatibility, + # check to see if a subclass implements _dispatch and dispatch + # using that method if present. + response = self.server._marshaled_dispatch( + data, getattr(self, '_dispatch', None) + ) + except: # This should only happen if the module is buggy + # internal error, report as HTTP server error + self.send_response(500) + self.end_headers() + else: + # got a valid XML RPC response + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def report_404 (self): + # Report a 404 error + self.send_response(404) + response = 'No such page' + self.send_header("Content-type", "text/plain") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def log_request(self, code='-', size='-'): + """Selectively log an accepted request.""" + + if self.server.logRequests: + BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) + +class SimpleXMLRPCServer(SocketServer.TCPServer, + SimpleXMLRPCDispatcher): + """Simple XML-RPC server. + + Simple XML-RPC server that allows functions and a single instance + to be installed to handle requests. The default implementation + attempts to dispatch XML-RPC calls to the functions or instance + installed in the server. Override the _dispatch method inhereted + from SimpleXMLRPCDispatcher to change this behavior. + """ + + allow_reuse_address = True + + def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, + logRequests=True, allow_none=False, encoding=None): + self.logRequests = logRequests + + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + SocketServer.TCPServer.__init__(self, addr, requestHandler) + + # [Bug #1222790] If possible, set close-on-exec flag; if a + # method spawns a subprocess, the subprocess shouldn't have + # the listening socket open. + if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): + flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) + +class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): + """Simple handler for XML-RPC data passed through CGI.""" + + def __init__(self, allow_none=False, encoding=None): + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + + def handle_xmlrpc(self, request_text): + """Handle a single XML-RPC request""" + + response = self._marshaled_dispatch(request_text) + + print 'Content-Type: text/xml' + print 'Content-Length: %d' % len(response) + print + sys.stdout.write(response) + + def handle_get(self): + """Handle a single HTTP GET request. + + Default implementation indicates an error because + XML-RPC uses the POST method. + """ + + code = 400 + message, explain = \ + BaseHTTPServer.BaseHTTPRequestHandler.responses[code] + + response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ + { + 'code' : code, + 'message' : message, + 'explain' : explain + } + print 'Status: %d %s' % (code, message) + print 'Content-Type: text/html' + print 'Content-Length: %d' % len(response) + print + sys.stdout.write(response) + + def handle_request(self, request_text = None): + """Handle a single XML-RPC request passed through a CGI post method. + + If no XML data is given then it is read from stdin. The resulting + XML-RPC response is printed to stdout along with the correct HTTP + headers. + """ + + if request_text is None and \ + os.environ.get('REQUEST_METHOD', None) == 'GET': + self.handle_get() + else: + # POST data is normally available through stdin + if request_text is None: + request_text = sys.stdin.read() + + self.handle_xmlrpc(request_text) + +if __name__ == '__main__': + print 'Running XML-RPC server on port 8000' + server = SimpleXMLRPCServer(("localhost", 8000)) + server.register_function(pow) + server.register_function(lambda x,y: x+y, 'add') + server.serve_forever() diff --git a/deluge/core/core.py b/deluge/core/core.py index 6d266ad0c..d792dde3c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -37,7 +37,7 @@ import pkg_resources import sys import pickle -import SimpleXMLRPCServer +import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import xmlrpclib import gobject diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index d22809942..1990e6146 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -32,7 +32,7 @@ # statement from all source files in the program, then also delete it here. import sys -import SimpleXMLRPCServer +import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import xmlrpclib import threading From 1440e78caebe19f9926144c24e024ae0f9882120 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:01:46 +0000 Subject: [PATCH 0208/1009] Add xmlrpclib from Python 2.5.1 --- deluge/core/core.py | 2 +- deluge/ui/functions.py | 5 +- deluge/ui/signalreceiver.py | 2 +- deluge/xmlrpclib.py | 1488 +++++++++++++++++++++++++++++++++++ 4 files changed, 1491 insertions(+), 6 deletions(-) create mode 100644 deluge/xmlrpclib.py diff --git a/deluge/core/core.py b/deluge/core/core.py index d792dde3c..6c2340145 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -39,7 +39,7 @@ import pickle import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn -import xmlrpclib +import deluge.xmlrpclib as xmlrpclib import gobject import threading diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 7a0e614a8..2334d1b6d 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -34,10 +34,7 @@ import os.path import pickle -import xmlrpclib -#import dbus -#from dbus.mainloop.glib import DBusGMainLoop -#DBusGMainLoop(set_as_default=True) +import deluge.xmlrpclib as xmlrpclib import pygtk pygtk.require('2.0') diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 1990e6146..7816a8d5a 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -34,7 +34,7 @@ import sys import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn -import xmlrpclib +import xmlrpclib as xmlrpclib import threading from deluge.log import LOG as log diff --git a/deluge/xmlrpclib.py b/deluge/xmlrpclib.py new file mode 100644 index 000000000..9305e1018 --- /dev/null +++ b/deluge/xmlrpclib.py @@ -0,0 +1,1488 @@ +# +# XML-RPC CLIENT LIBRARY +# $Id: xmlrpclib.py 41594 2005-12-04 19:11:17Z andrew.kuchling $ +# +# an XML-RPC client interface for Python. +# +# the marshalling and response parser code can also be used to +# implement XML-RPC servers. +# +# Notes: +# this version is designed to work with Python 2.1 or newer. +# +# History: +# 1999-01-14 fl Created +# 1999-01-15 fl Changed dateTime to use localtime +# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service +# 1999-01-19 fl Fixed array data element (from Skip Montanaro) +# 1999-01-21 fl Fixed dateTime constructor, etc. +# 1999-02-02 fl Added fault handling, handle empty sequences, etc. +# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro) +# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8) +# 2000-11-28 fl Changed boolean to check the truth value of its argument +# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches +# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1) +# 2001-03-28 fl Make sure response tuple is a singleton +# 2001-03-29 fl Don't require empty params element (from Nicholas Riley) +# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2) +# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod) +# 2001-09-03 fl Allow Transport subclass to override getparser +# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup) +# 2001-10-01 fl Remove containers from memo cache when done with them +# 2001-10-01 fl Use faster escape method (80% dumps speedup) +# 2001-10-02 fl More dumps microtuning +# 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum) +# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow +# 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems) +# 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix) +# 2002-03-17 fl Avoid buffered read when possible (from James Rucker) +# 2002-04-07 fl Added pythondoc comments +# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers +# 2002-05-15 fl Added error constants (from Andrew Kuchling) +# 2002-06-27 fl Merged with Python CVS version +# 2002-10-22 fl Added basic authentication (based on code from Phillip Eby) +# 2003-01-22 sm Add support for the bool type +# 2003-02-27 gvr Remove apply calls +# 2003-04-24 sm Use cStringIO if available +# 2003-04-25 ak Add support for nil +# 2003-06-15 gn Add support for time.struct_time +# 2003-07-12 gp Correct marshalling of Faults +# 2003-10-31 mvl Add multicall support +# 2004-08-20 mvl Bump minimum supported Python version to 2.1 +# +# Copyright (c) 1999-2002 by Secret Labs AB. +# Copyright (c) 1999-2002 by Fredrik Lundh. +# +# info@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The XML-RPC client interface is +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +# +# things to look into some day: + +# TODO: sort out True/False/boolean issues for Python 2.3 + +""" +An XML-RPC client interface for Python. + +The marshalling and response parser code can also be used to +implement XML-RPC servers. + +Exported exceptions: + + Error Base class for client errors + ProtocolError Indicates an HTTP protocol error + ResponseError Indicates a broken response package + Fault Indicates an XML-RPC fault package + +Exported classes: + + ServerProxy Represents a logical connection to an XML-RPC server + + MultiCall Executor of boxcared xmlrpc requests + Boolean boolean wrapper to generate a "boolean" XML-RPC value + DateTime dateTime wrapper for an ISO 8601 string or time tuple or + localtime integer value to generate a "dateTime.iso8601" + XML-RPC value + Binary binary data wrapper + + SlowParser Slow but safe standard parser (based on xmllib) + Marshaller Generate an XML-RPC params chunk from a Python data structure + Unmarshaller Unmarshal an XML-RPC response from incoming XML event message + Transport Handles an HTTP transaction to an XML-RPC server + SafeTransport Handles an HTTPS transaction to an XML-RPC server + +Exported constants: + + True + False + +Exported functions: + + boolean Convert any Python value to an XML-RPC boolean + getparser Create instance of the fastest available parser & attach + to an unmarshalling object + dumps Convert an argument tuple or a Fault instance to an XML-RPC + request (or response, if the methodresponse option is used). + loads Convert an XML-RPC packet to unmarshalled data plus a method + name (None if not present). +""" + +import re, string, time, operator + +from types import * + +# -------------------------------------------------------------------- +# Internal stuff + +try: + unicode +except NameError: + unicode = None # unicode support not available + +try: + import datetime +except ImportError: + datetime = None + +try: + _bool_is_builtin = False.__class__.__name__ == "bool" +except NameError: + _bool_is_builtin = 0 + +def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): + # decode non-ascii string (if possible) + if unicode and encoding and is8bit(data): + data = unicode(data, encoding) + return data + +def escape(s, replace=string.replace): + s = replace(s, "&", "&") + s = replace(s, "<", "<") + return replace(s, ">", ">",) + +if unicode: + def _stringify(string): + # convert to 7-bit ascii if possible + try: + return string.encode("ascii") + except UnicodeError: + return string +else: + def _stringify(string): + return string + +__version__ = "1.0.1" + +# xmlrpc integer limits +MAXINT = 2L**31-1 +MININT = -2L**31 + +# -------------------------------------------------------------------- +# Error constants (from Dan Libby's specification at +# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) + +# Ranges of errors +PARSE_ERROR = -32700 +SERVER_ERROR = -32600 +APPLICATION_ERROR = -32500 +SYSTEM_ERROR = -32400 +TRANSPORT_ERROR = -32300 + +# Specific errors +NOT_WELLFORMED_ERROR = -32700 +UNSUPPORTED_ENCODING = -32701 +INVALID_ENCODING_CHAR = -32702 +INVALID_XMLRPC = -32600 +METHOD_NOT_FOUND = -32601 +INVALID_METHOD_PARAMS = -32602 +INTERNAL_ERROR = -32603 + +# -------------------------------------------------------------------- +# Exceptions + +## +# Base class for all kinds of client-side errors. + +class Error(Exception): + """Base class for client errors.""" + def __str__(self): + return repr(self) + +## +# Indicates an HTTP-level protocol error. This is raised by the HTTP +# transport layer, if the server returns an error code other than 200 +# (OK). +# +# @param url The target URL. +# @param errcode The HTTP error code. +# @param errmsg The HTTP error message. +# @param headers The HTTP header dictionary. + +class ProtocolError(Error): + """Indicates an HTTP protocol error.""" + def __init__(self, url, errcode, errmsg, headers): + Error.__init__(self) + self.url = url + self.errcode = errcode + self.errmsg = errmsg + self.headers = headers + def __repr__(self): + return ( + "" % + (self.url, self.errcode, self.errmsg) + ) + +## +# Indicates a broken XML-RPC response package. This exception is +# raised by the unmarshalling layer, if the XML-RPC response is +# malformed. + +class ResponseError(Error): + """Indicates a broken response package.""" + pass + +## +# Indicates an XML-RPC fault response package. This exception is +# raised by the unmarshalling layer, if the XML-RPC response contains +# a fault string. This exception can also used as a class, to +# generate a fault XML-RPC message. +# +# @param faultCode The XML-RPC fault code. +# @param faultString The XML-RPC fault string. + +class Fault(Error): + """Indicates an XML-RPC fault package.""" + def __init__(self, faultCode, faultString, **extra): + Error.__init__(self) + self.faultCode = faultCode + self.faultString = faultString + def __repr__(self): + return ( + "" % + (self.faultCode, repr(self.faultString)) + ) + +# -------------------------------------------------------------------- +# Special values + +## +# Wrapper for XML-RPC boolean values. Use the xmlrpclib.True and +# xmlrpclib.False constants, or the xmlrpclib.boolean() function, to +# generate boolean XML-RPC values. +# +# @param value A boolean value. Any true value is interpreted as True, +# all other values are interpreted as False. + +if _bool_is_builtin: + boolean = Boolean = bool + # to avoid breaking code which references xmlrpclib.{True,False} + True, False = True, False +else: + class Boolean: + """Boolean-value wrapper. + + Use True or False to generate a "boolean" XML-RPC value. + """ + + def __init__(self, value = 0): + self.value = operator.truth(value) + + def encode(self, out): + out.write("%d\n" % self.value) + + def __cmp__(self, other): + if isinstance(other, Boolean): + other = other.value + return cmp(self.value, other) + + def __repr__(self): + if self.value: + return "" % id(self) + else: + return "" % id(self) + + def __int__(self): + return self.value + + def __nonzero__(self): + return self.value + + True, False = Boolean(1), Boolean(0) + + ## + # Map true or false value to XML-RPC boolean values. + # + # @def boolean(value) + # @param value A boolean value. Any true value is mapped to True, + # all other values are mapped to False. + # @return xmlrpclib.True or xmlrpclib.False. + # @see Boolean + # @see True + # @see False + + def boolean(value, _truefalse=(False, True)): + """Convert any Python value to XML-RPC 'boolean'.""" + return _truefalse[operator.truth(value)] + +## +# Wrapper for XML-RPC DateTime values. This converts a time value to +# the format used by XML-RPC. +#

+# The value can be given as a string in the format +# "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by +# time.localtime()), or an integer value (as returned by time.time()). +# The wrapper uses time.localtime() to convert an integer to a time +# tuple. +# +# @param value The time, given as an ISO 8601 string, a time +# tuple, or a integer time value. + +class DateTime: + """DateTime wrapper for an ISO 8601 string or time tuple or + localtime integer value to generate 'dateTime.iso8601' XML-RPC + value. + """ + + def __init__(self, value=0): + if not isinstance(value, StringType): + if datetime and isinstance(value, datetime.datetime): + self.value = value.strftime("%Y%m%dT%H:%M:%S") + return + if datetime and isinstance(value, datetime.date): + self.value = value.strftime("%Y%m%dT%H:%M:%S") + return + if datetime and isinstance(value, datetime.time): + today = datetime.datetime.now().strftime("%Y%m%d") + self.value = value.strftime(today+"T%H:%M:%S") + return + if not isinstance(value, (TupleType, time.struct_time)): + if value == 0: + value = time.time() + value = time.localtime(value) + value = time.strftime("%Y%m%dT%H:%M:%S", value) + self.value = value + + def __cmp__(self, other): + if isinstance(other, DateTime): + other = other.value + return cmp(self.value, other) + + ## + # Get date/time value. + # + # @return Date/time value, as an ISO 8601 string. + + def __str__(self): + return self.value + + def __repr__(self): + return "" % (repr(self.value), id(self)) + + def decode(self, data): + data = str(data) + self.value = string.strip(data) + + def encode(self, out): + out.write("") + out.write(self.value) + out.write("\n") + +def _datetime(data): + # decode xml element contents into a DateTime structure. + value = DateTime() + value.decode(data) + return value + +def _datetime_type(data): + t = time.strptime(data, "%Y%m%dT%H:%M:%S") + return datetime.datetime(*tuple(t)[:6]) + +## +# Wrapper for binary data. This can be used to transport any kind +# of binary data over XML-RPC, using BASE64 encoding. +# +# @param data An 8-bit string containing arbitrary data. + +import base64 +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +class Binary: + """Wrapper for binary data.""" + + def __init__(self, data=None): + self.data = data + + ## + # Get buffer contents. + # + # @return Buffer contents, as an 8-bit string. + + def __str__(self): + return self.data or "" + + def __cmp__(self, other): + if isinstance(other, Binary): + other = other.data + return cmp(self.data, other) + + def decode(self, data): + self.data = base64.decodestring(data) + + def encode(self, out): + out.write("\n") + base64.encode(StringIO.StringIO(self.data), out) + out.write("\n") + +def _binary(data): + # decode xml element contents into a Binary structure + value = Binary() + value.decode(data) + return value + +WRAPPERS = (DateTime, Binary) +if not _bool_is_builtin: + WRAPPERS = WRAPPERS + (Boolean,) + +# -------------------------------------------------------------------- +# XML parsers + +try: + # optional xmlrpclib accelerator + import _xmlrpclib + FastParser = _xmlrpclib.Parser + FastUnmarshaller = _xmlrpclib.Unmarshaller +except (AttributeError, ImportError): + FastParser = FastUnmarshaller = None + +try: + import _xmlrpclib + FastMarshaller = _xmlrpclib.Marshaller +except (AttributeError, ImportError): + FastMarshaller = None + +# +# the SGMLOP parser is about 15x faster than Python's builtin +# XML parser. SGMLOP sources can be downloaded from: +# +# http://www.pythonware.com/products/xml/sgmlop.htm +# + +try: + import sgmlop + if not hasattr(sgmlop, "XMLParser"): + raise ImportError +except ImportError: + SgmlopParser = None # sgmlop accelerator not available +else: + class SgmlopParser: + def __init__(self, target): + + # setup callbacks + self.finish_starttag = target.start + self.finish_endtag = target.end + self.handle_data = target.data + self.handle_xml = target.xml + + # activate parser + self.parser = sgmlop.XMLParser() + self.parser.register(self) + self.feed = self.parser.feed + self.entity = { + "amp": "&", "gt": ">", "lt": "<", + "apos": "'", "quot": '"' + } + + def close(self): + try: + self.parser.close() + finally: + self.parser = self.feed = None # nuke circular reference + + def handle_proc(self, tag, attr): + m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr) + if m: + self.handle_xml(m.group(1), 1) + + def handle_entityref(self, entity): + # entity + try: + self.handle_data(self.entity[entity]) + except KeyError: + self.handle_data("&%s;" % entity) + +try: + from xml.parsers import expat + if not hasattr(expat, "ParserCreate"): + raise ImportError +except ImportError: + ExpatParser = None # expat not available +else: + class ExpatParser: + # fast expat parser for Python 2.0 and later. this is about + # 50% slower than sgmlop, on roundtrip testing + def __init__(self, target): + self._parser = parser = expat.ParserCreate(None, None) + self._target = target + parser.StartElementHandler = target.start + parser.EndElementHandler = target.end + parser.CharacterDataHandler = target.data + encoding = None + if not parser.returns_unicode: + encoding = "utf-8" + target.xml(encoding, None) + + def feed(self, data): + self._parser.Parse(data, 0) + + def close(self): + self._parser.Parse("", 1) # end of data + del self._target, self._parser # get rid of circular references + +class SlowParser: + """Default XML parser (based on xmllib.XMLParser).""" + # this is about 10 times slower than sgmlop, on roundtrip + # testing. + def __init__(self, target): + import xmllib # lazy subclassing (!) + if xmllib.XMLParser not in SlowParser.__bases__: + SlowParser.__bases__ = (xmllib.XMLParser,) + self.handle_xml = target.xml + self.unknown_starttag = target.start + self.handle_data = target.data + self.handle_cdata = target.data + self.unknown_endtag = target.end + try: + xmllib.XMLParser.__init__(self, accept_utf8=1) + except TypeError: + xmllib.XMLParser.__init__(self) # pre-2.0 + +# -------------------------------------------------------------------- +# XML-RPC marshalling and unmarshalling code + +## +# XML-RPC marshaller. +# +# @param encoding Default encoding for 8-bit strings. The default +# value is None (interpreted as UTF-8). +# @see dumps + +class Marshaller: + """Generate an XML-RPC params chunk from a Python data structure. + + Create a Marshaller instance for each set of parameters, and use + the "dumps" method to convert your data (represented as a tuple) + to an XML-RPC params chunk. To write a fault response, pass a + Fault instance instead. You may prefer to use the "dumps" module + function for this purpose. + """ + + # by the way, if you don't understand what's going on in here, + # that's perfectly ok. + + def __init__(self, encoding=None, allow_none=0): + self.memo = {} + self.data = None + self.encoding = encoding + self.allow_none = allow_none + + dispatch = {} + + def dumps(self, values): + out = [] + write = out.append + dump = self.__dump + if isinstance(values, Fault): + # fault instance + write("\n") + dump({'faultCode': values.faultCode, + 'faultString': values.faultString}, + write) + write("\n") + else: + # parameter block + # FIXME: the xml-rpc specification allows us to leave out + # the entire block if there are no parameters. + # however, changing this may break older code (including + # old versions of xmlrpclib.py), so this is better left as + # is for now. See @XMLRPC3 for more information. /F + write("\n") + for v in values: + write("\n") + dump(v, write) + write("\n") + write("\n") + result = string.join(out, "") + return result + + def __dump(self, value, write): + try: + f = self.dispatch[type(value)] + except KeyError: + raise TypeError, "cannot marshal %s objects" % type(value) + else: + f(self, value, write) + + def dump_nil (self, value, write): + if not self.allow_none: + raise TypeError, "cannot marshal None unless allow_none is enabled" + write("") + dispatch[NoneType] = dump_nil + + def dump_int(self, value, write): + # in case ints are > 32 bits + if value > MAXINT or value < MININT: + raise OverflowError, "int exceeds XML-RPC limits" + write("") + write(str(value)) + write("\n") + dispatch[IntType] = dump_int + + if _bool_is_builtin: + def dump_bool(self, value, write): + write("") + write(value and "1" or "0") + write("\n") + dispatch[bool] = dump_bool + + def dump_long(self, value, write): + if value > MAXINT or value < MININT: + raise OverflowError, "long int exceeds XML-RPC limits" + write("") + write(str(int(value))) + write("\n") + dispatch[LongType] = dump_long + + def dump_double(self, value, write): + write("") + write(repr(value)) + write("\n") + dispatch[FloatType] = dump_double + + def dump_string(self, value, write, escape=escape): + write("") + write(escape(value)) + write("\n") + dispatch[StringType] = dump_string + + if unicode: + def dump_unicode(self, value, write, escape=escape): + value = value.encode(self.encoding) + write("") + write(escape(value)) + write("\n") + dispatch[UnicodeType] = dump_unicode + + def dump_array(self, value, write): + i = id(value) + if self.memo.has_key(i): + raise TypeError, "cannot marshal recursive sequences" + self.memo[i] = None + dump = self.__dump + write("\n") + for v in value: + dump(v, write) + write("\n") + del self.memo[i] + dispatch[TupleType] = dump_array + dispatch[ListType] = dump_array + + def dump_struct(self, value, write, escape=escape): + i = id(value) + if self.memo.has_key(i): + raise TypeError, "cannot marshal recursive dictionaries" + self.memo[i] = None + dump = self.__dump + write("\n") + for k, v in value.items(): + write("\n") + if type(k) is not StringType: + if unicode and type(k) is UnicodeType: + k = k.encode(self.encoding) + else: + raise TypeError, "dictionary key must be string" + write("%s\n" % escape(k)) + dump(v, write) + write("\n") + write("\n") + del self.memo[i] + dispatch[DictType] = dump_struct + + if datetime: + def dump_datetime(self, value, write): + write("") + write(value.strftime("%Y%m%dT%H:%M:%S")) + write("\n") + dispatch[datetime.datetime] = dump_datetime + + def dump_date(self, value, write): + write("") + write(value.strftime("%Y%m%dT00:00:00")) + write("\n") + dispatch[datetime.date] = dump_date + + def dump_time(self, value, write): + write("") + write(datetime.datetime.now().date().strftime("%Y%m%dT")) + write(value.strftime("%H:%M:%S")) + write("\n") + dispatch[datetime.time] = dump_time + + def dump_instance(self, value, write): + # check for special wrappers + if value.__class__ in WRAPPERS: + self.write = write + value.encode(self) + del self.write + else: + # store instance attributes as a struct (really?) + self.dump_struct(value.__dict__, write) + dispatch[InstanceType] = dump_instance + +## +# XML-RPC unmarshaller. +# +# @see loads + +class Unmarshaller: + """Unmarshal an XML-RPC response, based on incoming XML event + messages (start, data, end). Call close() to get the resulting + data structure. + + Note that this reader is fairly tolerant, and gladly accepts bogus + XML-RPC data without complaining (but not bogus XML). + """ + + # and again, if you don't understand what's going on in here, + # that's perfectly ok. + + def __init__(self, use_datetime=0): + self._type = None + self._stack = [] + self._marks = [] + self._data = [] + self._methodname = None + self._encoding = "utf-8" + self.append = self._stack.append + self._use_datetime = use_datetime + if use_datetime and not datetime: + raise ValueError, "the datetime module is not available" + + def close(self): + # return response tuple and target method + if self._type is None or self._marks: + raise ResponseError() + if self._type == "fault": + raise Fault(**self._stack[0]) + return tuple(self._stack) + + def getmethodname(self): + return self._methodname + + # + # event handlers + + def xml(self, encoding, standalone): + self._encoding = encoding + # FIXME: assert standalone == 1 ??? + + def start(self, tag, attrs): + # prepare to handle this element + if tag == "array" or tag == "struct": + self._marks.append(len(self._stack)) + self._data = [] + self._value = (tag == "value") + + def data(self, text): + self._data.append(text) + + def end(self, tag, join=string.join): + # call the appropriate end tag handler + try: + f = self.dispatch[tag] + except KeyError: + pass # unknown tag ? + else: + return f(self, join(self._data, "")) + + # + # accelerator support + + def end_dispatch(self, tag, data): + # dispatch data + try: + f = self.dispatch[tag] + except KeyError: + pass # unknown tag ? + else: + return f(self, data) + + # + # element decoders + + dispatch = {} + + def end_nil (self, data): + self.append(None) + self._value = 0 + dispatch["nil"] = end_nil + + def end_boolean(self, data): + if data == "0": + self.append(False) + elif data == "1": + self.append(True) + else: + raise TypeError, "bad boolean value" + self._value = 0 + dispatch["boolean"] = end_boolean + + def end_int(self, data): + self.append(int(data)) + self._value = 0 + dispatch["i4"] = end_int + dispatch["int"] = end_int + + def end_double(self, data): + self.append(float(data)) + self._value = 0 + dispatch["double"] = end_double + + def end_string(self, data): + if self._encoding: + data = _decode(data, self._encoding) + self.append(_stringify(data)) + self._value = 0 + dispatch["string"] = end_string + dispatch["name"] = end_string # struct keys are always strings + + def end_array(self, data): + mark = self._marks.pop() + # map arrays to Python lists + self._stack[mark:] = [self._stack[mark:]] + self._value = 0 + dispatch["array"] = end_array + + def end_struct(self, data): + mark = self._marks.pop() + # map structs to Python dictionaries + dict = {} + items = self._stack[mark:] + for i in range(0, len(items), 2): + dict[_stringify(items[i])] = items[i+1] + self._stack[mark:] = [dict] + self._value = 0 + dispatch["struct"] = end_struct + + def end_base64(self, data): + value = Binary() + value.decode(data) + self.append(value) + self._value = 0 + dispatch["base64"] = end_base64 + + def end_dateTime(self, data): + value = DateTime() + value.decode(data) + if self._use_datetime: + value = _datetime_type(data) + self.append(value) + dispatch["dateTime.iso8601"] = end_dateTime + + def end_value(self, data): + # if we stumble upon a value element with no internal + # elements, treat it as a string element + if self._value: + self.end_string(data) + dispatch["value"] = end_value + + def end_params(self, data): + self._type = "params" + dispatch["params"] = end_params + + def end_fault(self, data): + self._type = "fault" + dispatch["fault"] = end_fault + + def end_methodName(self, data): + if self._encoding: + data = _decode(data, self._encoding) + self._methodname = data + self._type = "methodName" # no params + dispatch["methodName"] = end_methodName + +## Multicall support +# + +class _MultiCallMethod: + # some lesser magic to store calls made to a MultiCall object + # for batch execution + def __init__(self, call_list, name): + self.__call_list = call_list + self.__name = name + def __getattr__(self, name): + return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) + def __call__(self, *args): + self.__call_list.append((self.__name, args)) + +class MultiCallIterator: + """Iterates over the results of a multicall. Exceptions are + thrown in response to xmlrpc faults.""" + + def __init__(self, results): + self.results = results + + def __getitem__(self, i): + item = self.results[i] + if type(item) == type({}): + raise Fault(item['faultCode'], item['faultString']) + elif type(item) == type([]): + return item[0] + else: + raise ValueError,\ + "unexpected type in multicall result" + +class MultiCall: + """server -> a object used to boxcar method calls + + server should be a ServerProxy object. + + Methods can be added to the MultiCall using normal + method call syntax e.g.: + + multicall = MultiCall(server_proxy) + multicall.add(2,3) + multicall.get_address("Guido") + + To execute the multicall, call the MultiCall object e.g.: + + add_result, address = multicall() + """ + + def __init__(self, server): + self.__server = server + self.__call_list = [] + + def __repr__(self): + return "" % id(self) + + __str__ = __repr__ + + def __getattr__(self, name): + return _MultiCallMethod(self.__call_list, name) + + def __call__(self): + marshalled_list = [] + for name, args in self.__call_list: + marshalled_list.append({'methodName' : name, 'params' : args}) + + return MultiCallIterator(self.__server.system.multicall(marshalled_list)) + +# -------------------------------------------------------------------- +# convenience functions + +## +# Create a parser object, and connect it to an unmarshalling instance. +# This function picks the fastest available XML parser. +# +# return A (parser, unmarshaller) tuple. + +def getparser(use_datetime=0): + """getparser() -> parser, unmarshaller + + Create an instance of the fastest available parser, and attach it + to an unmarshalling object. Return both objects. + """ + if use_datetime and not datetime: + raise ValueError, "the datetime module is not available" + if FastParser and FastUnmarshaller: + if use_datetime: + mkdatetime = _datetime_type + else: + mkdatetime = _datetime + target = FastUnmarshaller(True, False, _binary, mkdatetime, Fault) + parser = FastParser(target) + else: + target = Unmarshaller(use_datetime=use_datetime) + if FastParser: + parser = FastParser(target) + elif SgmlopParser: + parser = SgmlopParser(target) + elif ExpatParser: + parser = ExpatParser(target) + else: + parser = SlowParser(target) + return parser, target + +## +# Convert a Python tuple or a Fault instance to an XML-RPC packet. +# +# @def dumps(params, **options) +# @param params A tuple or Fault instance. +# @keyparam methodname If given, create a methodCall request for +# this method name. +# @keyparam methodresponse If given, create a methodResponse packet. +# If used with a tuple, the tuple must be a singleton (that is, +# it must contain exactly one element). +# @keyparam encoding The packet encoding. +# @return A string containing marshalled data. + +def dumps(params, methodname=None, methodresponse=None, encoding=None, + allow_none=0): + """data [,options] -> marshalled data + + Convert an argument tuple or a Fault instance to an XML-RPC + request (or response, if the methodresponse option is used). + + In addition to the data object, the following options can be given + as keyword arguments: + + methodname: the method name for a methodCall packet + + methodresponse: true to create a methodResponse packet. + If this option is used with a tuple, the tuple must be + a singleton (i.e. it can contain only one element). + + encoding: the packet encoding (default is UTF-8) + + All 8-bit strings in the data structure are assumed to use the + packet encoding. Unicode strings are automatically converted, + where necessary. + """ + + assert isinstance(params, TupleType) or isinstance(params, Fault),\ + "argument must be tuple or Fault instance" + + if isinstance(params, Fault): + methodresponse = 1 + elif methodresponse and isinstance(params, TupleType): + assert len(params) == 1, "response tuple must be a singleton" + + if not encoding: + encoding = "utf-8" + + if FastMarshaller: + m = FastMarshaller(encoding) + else: + m = Marshaller(encoding, allow_none) + + data = m.dumps(params) + + if encoding != "utf-8": + xmlheader = "\n" % str(encoding) + else: + xmlheader = "\n" # utf-8 is default + + # standard XML-RPC wrappings + if methodname: + # a method call + if not isinstance(methodname, StringType): + methodname = methodname.encode(encoding) + data = ( + xmlheader, + "\n" + "", methodname, "\n", + data, + "\n" + ) + elif methodresponse: + # a method response, or a fault structure + data = ( + xmlheader, + "\n", + data, + "\n" + ) + else: + return data # return as is + return string.join(data, "") + +## +# Convert an XML-RPC packet to a Python object. If the XML-RPC packet +# represents a fault condition, this function raises a Fault exception. +# +# @param data An XML-RPC packet, given as an 8-bit string. +# @return A tuple containing the unpacked data, and the method name +# (None if not present). +# @see Fault + +def loads(data, use_datetime=0): + """data -> unmarshalled data, method name + + Convert an XML-RPC packet to unmarshalled data plus a method + name (None if not present). + + If the XML-RPC packet represents a fault condition, this function + raises a Fault exception. + """ + p, u = getparser(use_datetime=use_datetime) + p.feed(data) + p.close() + return u.close(), u.getmethodname() + + +# -------------------------------------------------------------------- +# request dispatcher + +class _Method: + # some magic to bind an XML-RPC method to an RPC server. + # supports "nested" methods (e.g. examples.getStateName) + def __init__(self, send, name): + self.__send = send + self.__name = name + def __getattr__(self, name): + return _Method(self.__send, "%s.%s" % (self.__name, name)) + def __call__(self, *args): + return self.__send(self.__name, args) + +## +# Standard transport class for XML-RPC over HTTP. +#

+# You can create custom transports by subclassing this method, and +# overriding selected methods. + +class Transport: + """Handles an HTTP transaction to an XML-RPC server.""" + + # client identifier (may be overridden) + user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ + + def __init__(self, use_datetime=0): + self._use_datetime = use_datetime + + ## + # Send a complete request, and parse the response. + # + # @param host Target host. + # @param handler Target PRC handler. + # @param request_body XML-RPC request body. + # @param verbose Debugging flag. + # @return Parsed response. + + def request(self, host, handler, request_body, verbose=0): + # issue XML-RPC request + + h = self.make_connection(host) + if verbose: + h.set_debuglevel(1) + + self.send_request(h, handler, request_body) + self.send_host(h, host) + self.send_user_agent(h) + self.send_content(h, request_body) + + errcode, errmsg, headers = h.getreply() + + if errcode != 200: + raise ProtocolError( + host + handler, + errcode, errmsg, + headers + ) + + self.verbose = verbose + + try: + sock = h._conn.sock + except AttributeError: + sock = None + + return self._parse_response(h.getfile(), sock) + + ## + # Create parser. + # + # @return A 2-tuple containing a parser and a unmarshaller. + + def getparser(self): + # get parser and unmarshaller + return getparser(use_datetime=self._use_datetime) + + ## + # Get authorization info from host parameter + # Host may be a string, or a (host, x509-dict) tuple; if a string, + # it is checked for a "user:pw@host" format, and a "Basic + # Authentication" header is added if appropriate. + # + # @param host Host descriptor (URL or (URL, x509 info) tuple). + # @return A 3-tuple containing (actual host, extra headers, + # x509 info). The header and x509 fields may be None. + + def get_host_info(self, host): + + x509 = {} + if isinstance(host, TupleType): + host, x509 = host + + import urllib + auth, host = urllib.splituser(host) + + if auth: + import base64 + auth = base64.encodestring(urllib.unquote(auth)) + auth = string.join(string.split(auth), "") # get rid of whitespace + extra_headers = [ + ("Authorization", "Basic " + auth) + ] + else: + extra_headers = None + + return host, extra_headers, x509 + + ## + # Connect to server. + # + # @param host Target host. + # @return A connection handle. + + def make_connection(self, host): + # create a HTTP connection object from a host descriptor + import httplib + host, extra_headers, x509 = self.get_host_info(host) + return httplib.HTTP(host) + + ## + # Send request header. + # + # @param connection Connection handle. + # @param handler Target RPC handler. + # @param request_body XML-RPC body. + + def send_request(self, connection, handler, request_body): + connection.putrequest("POST", handler) + + ## + # Send host name. + # + # @param connection Connection handle. + # @param host Host name. + + def send_host(self, connection, host): + host, extra_headers, x509 = self.get_host_info(host) + connection.putheader("Host", host) + if extra_headers: + if isinstance(extra_headers, DictType): + extra_headers = extra_headers.items() + for key, value in extra_headers: + connection.putheader(key, value) + + ## + # Send user-agent identifier. + # + # @param connection Connection handle. + + def send_user_agent(self, connection): + connection.putheader("User-Agent", self.user_agent) + + ## + # Send request body. + # + # @param connection Connection handle. + # @param request_body XML-RPC request body. + + def send_content(self, connection, request_body): + connection.putheader("Content-Type", "text/xml") + connection.putheader("Content-Length", str(len(request_body))) + connection.endheaders() + if request_body: + connection.send(request_body) + + ## + # Parse response. + # + # @param file Stream. + # @return Response tuple and target method. + + def parse_response(self, file): + # compatibility interface + return self._parse_response(file, None) + + ## + # Parse response (alternate interface). This is similar to the + # parse_response method, but also provides direct access to the + # underlying socket object (where available). + # + # @param file Stream. + # @param sock Socket handle (or None, if the socket object + # could not be accessed). + # @return Response tuple and target method. + + def _parse_response(self, file, sock): + # read response from input file/socket, and parse it + + p, u = self.getparser() + + while 1: + if sock: + response = sock.recv(1024) + else: + response = file.read(1024) + if not response: + break + if self.verbose: + print "body:", repr(response) + p.feed(response) + + file.close() + p.close() + + return u.close() + +## +# Standard transport class for XML-RPC over HTTPS. + +class SafeTransport(Transport): + """Handles an HTTPS transaction to an XML-RPC server.""" + + # FIXME: mostly untested + + def make_connection(self, host): + # create a HTTPS connection object from a host descriptor + # host may be a string, or a (host, x509-dict) tuple + import httplib + host, extra_headers, x509 = self.get_host_info(host) + try: + HTTPS = httplib.HTTPS + except AttributeError: + raise NotImplementedError( + "your version of httplib doesn't support HTTPS" + ) + else: + return HTTPS(host, None, **(x509 or {})) + +## +# Standard server proxy. This class establishes a virtual connection +# to an XML-RPC server. +#

+# This class is available as ServerProxy and Server. New code should +# use ServerProxy, to avoid confusion. +# +# @def ServerProxy(uri, **options) +# @param uri The connection point on the server. +# @keyparam transport A transport factory, compatible with the +# standard transport class. +# @keyparam encoding The default encoding used for 8-bit strings +# (default is UTF-8). +# @keyparam verbose Use a true value to enable debugging output. +# (printed to standard output). +# @see Transport + +class ServerProxy: + """uri [,options] -> a logical connection to an XML-RPC server + + uri is the connection point on the server, given as + scheme://host/target. + + The standard implementation always supports the "http" scheme. If + SSL socket support is available (Python 2.0), it also supports + "https". + + If the target part and the slash preceding it are both omitted, + "/RPC2" is assumed. + + The following options can be given as keyword arguments: + + transport: a transport factory + encoding: the request encoding (default is UTF-8) + + All 8-bit strings passed to the server proxy are assumed to use + the given encoding. + """ + + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=0, use_datetime=0): + # establish a "logical" server connection + + # get the url + import urllib + type, uri = urllib.splittype(uri) + if type not in ("http", "https"): + raise IOError, "unsupported XML-RPC protocol" + self.__host, self.__handler = urllib.splithost(uri) + if not self.__handler: + self.__handler = "/RPC2" + + if transport is None: + if type == "https": + transport = SafeTransport(use_datetime=use_datetime) + else: + transport = Transport(use_datetime=use_datetime) + self.__transport = transport + + self.__encoding = encoding + self.__verbose = verbose + self.__allow_none = allow_none + + def __request(self, methodname, params): + # call a method on the remote server + + request = dumps(params, methodname, encoding=self.__encoding, + allow_none=self.__allow_none) + + response = self.__transport.request( + self.__host, + self.__handler, + request, + verbose=self.__verbose + ) + + if len(response) == 1: + response = response[0] + + return response + + def __repr__(self): + return ( + "" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return _Method(self.__request, name) + + # note: to call a remote object with an non-standard name, use + # result getattr(server, "strange-python-name")(args) + +# compatibility + +Server = ServerProxy + +# -------------------------------------------------------------------- +# test code + +if __name__ == "__main__": + + # simple test program (from the XML-RPC specification) + + # server = ServerProxy("http://localhost:8000") # local server + server = ServerProxy("http://time.xmlrpc.com/RPC2") + + print server + + try: + print server.currentTime.getCurrentTime() + except Error, v: + print "ERROR", v + + multi = MultiCall(server) + multi.currentTime.getCurrentTime() + multi.currentTime.getCurrentTime() + try: + for response in multi(): + print response + except Error, v: + print "ERROR", v From 67c7f9a80e4bb34b15bdf10bbbf1fc8341687554 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:06:56 +0000 Subject: [PATCH 0209/1009] Fix preferences' function calls. --- deluge/ui/gtkui/preferences.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index ef9fee42b..9f65803a6 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -104,7 +104,7 @@ class Preferences: self.liststore.append([index, name]) def show(self): - self.core_config = functions.get_config(self.core) + self.core_config = functions.get_config() # Update the preferences dialog to reflect current config settings ## Downloads tab ## @@ -134,7 +134,7 @@ class Preferences: self.glade.get_widget("spin_port_max").set_value( self.core_config["listen_ports"][1]) self.glade.get_widget("active_port_label").set_text( - str(functions.get_listen_port(self.core))) + str(functions.get_listen_port())) self.glade.get_widget("chk_random_port").set_active( self.core_config["random_port"]) self.glade.get_widget("chk_dht").set_active( @@ -310,7 +310,7 @@ class Preferences: config_to_set[key] = new_core_config[key] # Set each changed config value in the core - functions.set_config(config_to_set, self.core) + functions.set_config(config_to_set) # Update the configuration self.core_config.update(config_to_set) @@ -387,7 +387,7 @@ class Preferences: def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") url = "http://deluge-torrent.org/test-port.php?port=%s" % \ - functions.get_listen_port(self.core) + functions.get_listen_port() functions.open_url_in_browser(url) def on_plugin_toggled(self, renderer, path): From d690e441949607aca0955d0088672ac96337706b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:25:05 +0000 Subject: [PATCH 0210/1009] Fix adding torrents by URL. --- deluge/core/core.py | 8 ++++++-- deluge/ui/signalreceiver.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 6c2340145..afd938b2a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -215,8 +215,12 @@ class Core( """ if save_path == "": save_path = None - - torrent_id = self.torrents.add(filename, filedump=filedump.data, + + # Make sure we are sending a string to add() + if not isinstance(filedump, str): + filedump = filedump.data + + torrent_id = self.torrents.add(filename, filedump=filedump, save_path=save_path) # Run the plugin hooks for 'post_torrent_add' diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 7816a8d5a..2df0435e2 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -34,7 +34,7 @@ import sys import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn -import xmlrpclib as xmlrpclib +import deluge.xmlrpclib as xmlrpclib import threading from deluge.log import LOG as log From b3d15c71176478e85fc6761f34484e14f70da770 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:35:37 +0000 Subject: [PATCH 0211/1009] Daemonize receiver thread. --- deluge/core/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/core/core.py b/deluge/core/core.py index afd938b2a..cdf219004 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -180,6 +180,7 @@ class Core( self._on_alert_torrent_paused) t = threading.Thread(target=self.serve_forever) + t.setDaemon(True) t.start() gobject.threads_init() From a16eb7625ca18e9cf01febe1db14071936c2b1d4 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 15 Oct 2007 04:50:17 +0000 Subject: [PATCH 0212/1009] lt updates --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 6b85e7f8d..17a054f7f 100644 --- a/TODO +++ b/TODO @@ -11,3 +11,4 @@ to add menuitems to the torrentmenu in an easy way. * Restart daemon function * Docstrings! +* Update libtorrent and bindings for sparse_mode allocation and remove_torrent options From b02b71e97849923f00edd681777b4254fca332ec Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 15 Oct 2007 06:11:37 +0000 Subject: [PATCH 0213/1009] lt sync 1677 --- libtorrent/bindings/python/src/session.cpp | 17 +- libtorrent/include/libtorrent/alert_types.hpp | 10 + .../include/libtorrent/aux_/session_impl.hpp | 20 +- .../include/libtorrent/disk_io_thread.hpp | 1 + .../include/libtorrent/peer_connection.hpp | 2 +- libtorrent/include/libtorrent/policy.hpp | 2 + libtorrent/include/libtorrent/session.hpp | 16 +- .../include/libtorrent/session_settings.hpp | 2 +- libtorrent/include/libtorrent/storage.hpp | 69 +- libtorrent/include/libtorrent/torrent.hpp | 9 +- .../include/libtorrent/torrent_handle.hpp | 5 +- libtorrent/src/bt_peer_connection.cpp | 5 +- libtorrent/src/disk_io_thread.cpp | 8 + libtorrent/src/peer_connection.cpp | 65 +- libtorrent/src/piece_picker.cpp | 2 - libtorrent/src/policy.cpp | 10 +- libtorrent/src/session.cpp | 16 +- libtorrent/src/session_impl.cpp | 47 +- libtorrent/src/storage.cpp | 748 ++++++++++-------- libtorrent/src/torrent.cpp | 66 +- libtorrent/src/torrent_handle.cpp | 17 +- 21 files changed, 670 insertions(+), 467 deletions(-) diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 6d4855c10..7332da2ea 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -87,10 +87,10 @@ namespace torrent_handle add_torrent(session& s, torrent_info const& ti , boost::filesystem::path const& save, entry const& resume - , bool compact, bool paused) + , storage_mode_t storage_mode, bool paused) { allow_threading_guard guard; - return s.add_torrent(ti, save, resume, compact, paused, default_storage_constructor); + return s.add_torrent(ti, save, resume, storage_mode, paused, default_storage_constructor); } } // namespace unnamed @@ -154,6 +154,17 @@ void bind_session() #endif ; + enum_("storage_mode_t") + .value("storage_mode_allocate", storage_mode_allocate) + .value("storage_mode_compact", storage_mode_compact) + .value("storage_mode_sparse", storage_mode_sparse) + ; + + enum_("options_t") + .value("none", session::none) + .value("delete_files", session::delete_files) + ; + class_("session", session_doc, no_init) .def( init(arg("fingerprint")=fingerprint("LT",0,1,0,0), session_init_doc) @@ -235,8 +246,6 @@ void bind_session() .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) ; - def("supports_sparse_files", &supports_sparse_files); - register_ptr_to_python >(); } diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index a46eb7817..6f41758fa 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -252,6 +252,16 @@ namespace libtorrent { return std::auto_ptr(new storage_moved_alert(*this)); } }; + struct TORRENT_EXPORT torrent_deleted_alert: torrent_alert + { + torrent_deleted_alert(torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new torrent_deleted_alert(*this)); } + }; + struct TORRENT_EXPORT torrent_paused_alert: torrent_alert { torrent_paused_alert(torrent_handle const& h, std::string const& msg) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index d069d73e3..bff8e3387 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -140,7 +140,7 @@ namespace libtorrent checker_impl(session_impl& s): m_ses(s), m_abort(false) {} void operator()(); piece_checker_data* find_torrent(const sha1_hash& info_hash); - void remove_torrent(sha1_hash const& info_hash); + void remove_torrent(sha1_hash const& info_hash, int options); #ifndef NDEBUG void check_invariant() const; @@ -254,7 +254,7 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata); @@ -265,12 +265,12 @@ namespace libtorrent , char const* name , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata); - void remove_torrent(torrent_handle const& h); + void remove_torrent(torrent_handle const& h, int options); std::vector get_torrents(); @@ -371,12 +371,6 @@ namespace libtorrent void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); - // handles disk io requests asynchronously - // peers have pointers into the disk buffer - // pool, and must be destructed before this - // object. - disk_io_thread m_disk_thread; - // this pool is used to allocate and recycle send // buffers from. boost::pool<> m_send_buffers; @@ -395,6 +389,12 @@ namespace libtorrent // when they are destructed. file_pool m_files; + // handles disk io requests asynchronously + // peers have pointers into the disk buffer + // pool, and must be destructed before this + // object. + disk_io_thread m_disk_thread; + // this is a list of half-open tcp connections // (only outgoing connections) // this has to be one of the last diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index b893aaf60..bd6d5e1ba 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -64,6 +64,7 @@ namespace libtorrent , hash , move_storage , release_files + , delete_files }; action_t action; diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 2ea3c7d34..e1581affe 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -176,7 +176,7 @@ namespace libtorrent void set_non_prioritized(bool b) { m_non_prioritized = b; } - void fast_reconnect(bool r) { m_fast_reconnect = r; } + void fast_reconnect(bool r); bool fast_reconnect() const { return m_fast_reconnect; } // this adds an announcement in the announcement queue diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 551cb4f2c..c38bb426c 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -156,6 +156,8 @@ namespace libtorrent // this is true if the peer is a seed bool seed; + int fast_reconnects; + // true if this peer currently is unchoked // because of an optimistic unchoke. // when the optimistic unchoke is moved to diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 3a9eb563b..5093e2336 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -115,7 +115,7 @@ namespace libtorrent : m_impl(impl) {} boost::shared_ptr m_impl; }; - + class TORRENT_EXPORT session: public boost::noncopyable, aux::eh_initializer { public: @@ -140,7 +140,7 @@ namespace libtorrent torrent_info const& ti , fs::path const& save_path , entry const& resume_data = entry() - , bool compact_mode = true + , storage_mode_t storage_mode = storage_mode_sparse , bool paused = false , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED; @@ -148,7 +148,7 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data = entry() - , bool compact_mode = true + , storage_mode_t storage_mode = storage_mode_sparse , bool paused = false , storage_constructor_type sc = default_storage_constructor , void* userdata = 0); @@ -159,7 +159,7 @@ namespace libtorrent , char const* name , fs::path const& save_path , entry const& resume_data = entry() - , bool compact_mode = true + , storage_mode_t storage_mode = storage_mode_sparse , bool paused = false , storage_constructor_type sc = default_storage_constructor , void* userdata = 0); @@ -219,7 +219,13 @@ namespace libtorrent // number of half open connections. int num_connections() const; - void remove_torrent(const torrent_handle& h); + enum options_t + { + none = 0, + delete_files = 1 + }; + + void remove_torrent(const torrent_handle& h, int options = none); void set_settings(session_settings const& s); session_settings const& settings(); diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index fbf2c8a03..a792e296a 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -99,7 +99,7 @@ namespace libtorrent , allow_multiple_connections_per_ip(false) , max_failcount(3) , min_reconnect_time(60) - , peer_connect_timeout(10) + , peer_connect_timeout(7) , ignore_limits_on_local_network(true) , connection_speed(20) , send_redundant_have(false) diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 67d74153d..68a81c75b 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -71,6 +71,13 @@ namespace libtorrent struct file_pool; struct disk_io_job; + enum storage_mode_t + { + storage_mode_allocate = 0, + storage_mode_sparse, + storage_mode_compact + }; + #if defined(_WIN32) && defined(UNICODE) TORRENT_EXPORT std::wstring safe_convert(std::string const& s); @@ -144,6 +151,10 @@ namespace libtorrent // writing. This is called when a torrent has finished // downloading. virtual void release_files() = 0; + + // this will close all open files and delete them + virtual void delete_files() = 0; + virtual ~storage_interface() {} }; @@ -155,10 +166,6 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& path, file_pool& fp); - // returns true if the filesystem the path relies on supports - // sparse files or automatic zero filling of files. - TORRENT_EXPORT bool supports_sparse_files(fs::path const& p); - struct disk_io_thread; class TORRENT_EXPORT piece_manager @@ -180,7 +187,8 @@ namespace libtorrent ~piece_manager(); bool check_fastresume(aux::piece_checker_data& d - , std::vector& pieces, int& num_pieces, bool compact_mode); + , std::vector& pieces, int& num_pieces, storage_mode_t storage_mode + , std::string& error_msg); std::pair check_files(std::vector& pieces , int& num_pieces, boost::recursive_mutex& mutex); @@ -191,8 +199,8 @@ namespace libtorrent bool verify_resume_data(entry& rd, std::string& error); bool is_allocating() const - { return m_state == state_allocating; } - + { return m_state == state_expand_pieces; } + void mark_failed(int index); unsigned long piece_crc( @@ -200,8 +208,9 @@ namespace libtorrent , int block_size , piece_picker::block_info const* bi); - int slot_for_piece(int piece_index) const; - + int slot_for(int piece) const; + int piece_for(int slot) const; + void async_read( peer_request const& r , boost::function const& handler @@ -221,6 +230,10 @@ namespace libtorrent boost::function const& handler = boost::function()); + void async_delete_files( + boost::function const& handler + = boost::function()); + void async_move_storage(fs::path const& p , boost::function const& handler); @@ -228,10 +241,11 @@ namespace libtorrent // slots to the piece that is stored (or // partially stored) there. -2 is the index // of unassigned pieces and -1 is unallocated - void export_piece_map(std::vector& pieces) const; + void export_piece_map(std::vector& pieces + , std::vector const& have) const; bool compact_allocation() const - { return m_compact_mode; } + { return m_storage_mode == storage_mode_compact; } #ifndef NDEBUG std::string name() const { return m_info->name(); } @@ -261,9 +275,11 @@ namespace libtorrent , int offset , int size); + void switch_to_full_mode(); sha1_hash hash_for_piece_impl(int piece); - void release_files_impl(); + void release_files_impl() { m_storage->release_files(); } + void delete_files_impl() { m_storage->delete_files(); } bool move_storage_impl(fs::path const& save_path); @@ -276,19 +292,7 @@ namespace libtorrent #endif boost::scoped_ptr m_storage; - // if this is true, pieces are always allocated at the - // lowest possible slot index. If it is false, pieces - // are always written to their final place immediately - bool m_compact_mode; - - // if this is true, pieces that haven't been downloaded - // will be filled with zeroes. Not filling with zeroes - // will not work in some cases (where a seek cannot pass - // the end of the file). - bool m_fill_mode; - - // a bitmask representing the pieces we have - std::vector m_have_piece; + storage_mode_t m_storage_mode; boost::intrusive_ptr m_info; @@ -329,10 +333,21 @@ namespace libtorrent state_create_files, // checking the files state_full_check, - // allocating files (in non-compact mode) - state_allocating + // move pieces to their final position + state_expand_pieces } m_state; int m_current_slot; + // used during check. If any piece is found + // that is not in its final position, this + // is set to true + bool m_out_of_place; + // used to move pieces while expanding + // the storage from compact allocation + // to full allocation + std::vector m_scratch_buffer; + std::vector m_scratch_buffer2; + // the piece that is in the scratch buffer + int m_scratch_piece; // this is saved in case we need to instantiate a new // storage (osed when remapping files) diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 4bb68436c..60140f2c2 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -101,7 +101,7 @@ namespace libtorrent , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t m_storage_mode , int block_size , storage_constructor_type sc , bool paused); @@ -116,7 +116,7 @@ namespace libtorrent , char const* name , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t m_storage_mode , int block_size , storage_constructor_type sc , bool paused); @@ -177,6 +177,8 @@ namespace libtorrent void resume(); bool is_paused() const { return m_paused; } + void delete_files(); + // ============ start deprecation ============= void filter_piece(int index, bool filter); void filter_pieces(std::vector const& bitmask); @@ -550,6 +552,7 @@ namespace libtorrent private: + void on_files_deleted(int ret, disk_io_job const& j); void on_files_released(int ret, disk_io_job const& j); void on_torrent_paused(int ret, disk_io_job const& j); void on_storage_moved(int ret, disk_io_job const& j); @@ -751,7 +754,7 @@ namespace libtorrent fs::path m_save_path; // determines the storage state for this torrent. - const bool m_compact_mode; + storage_mode_t m_storage_mode; // defaults to 16 kiB, but can be set by the user // when creating the torrent diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index ec2d60ea5..8ae2f7f41 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/torrent_info.hpp" #include "libtorrent/time.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" namespace libtorrent { @@ -106,7 +107,7 @@ namespace libtorrent , num_connections(0) , uploads_limit(0) , connections_limit(0) - , compact_mode(false) + , storage_mode(storage_mode_sparse) {} enum state_t @@ -216,7 +217,7 @@ namespace libtorrent // true if the torrent is saved in compact mode // false if it is saved in full allocation mode - bool compact_mode; + storage_mode_t storage_mode; }; struct TORRENT_EXPORT block_info diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 21deec1d4..0559aff95 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -204,7 +204,6 @@ namespace libtorrent // if this fails, we need to reconnect // fast. - pi->connected = time_now() - seconds(m_ses.settings().min_reconnect_time); fast_reconnect(true); write_pe1_2_dhkey(); @@ -802,9 +801,9 @@ namespace libtorrent { boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); - while (!request_queue().empty()) + while (!download_queue().empty()) { - piece_block const& b = request_queue().front(); + piece_block const& b = download_queue().front(); peer_request r; r.piece = b.piece_index; r.start = b.block_index * t->block_size(); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index b6b2e20a7..22ee12179 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -125,6 +125,7 @@ namespace libtorrent , boost::function const& f) { TORRENT_ASSERT(!j.callback); + TORRENT_ASSERT(j.storage); boost::mutex::scoped_lock l(m_mutex); std::deque::reverse_iterator i = m_jobs.rbegin(); @@ -220,6 +221,7 @@ namespace libtorrent bool free_buffer = true; try { + TORRENT_ASSERT(j.storage); #ifdef TORRENT_DISK_STATS ptime start = time_now(); #endif @@ -288,6 +290,12 @@ namespace libtorrent #endif j.storage->release_files_impl(); break; + case disk_io_job::delete_files: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " delete" << std::endl; +#endif + j.storage->delete_files_impl(); + break; } } catch (std::exception& e) diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 9b975ab11..625b8eacd 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -394,6 +394,16 @@ namespace libtorrent #endif } + void peer_connection::fast_reconnect(bool r) + { + if (peer_info_struct() && peer_info_struct()->fast_reconnects > 1) return; + m_fast_reconnect = r; + peer_info_struct()->connected = time_now() + - seconds(m_ses.settings().min_reconnect_time + * m_ses.settings().max_failcount); + if (peer_info_struct()) ++peer_info_struct()->fast_reconnects; + } + void peer_connection::announce_piece(int index) { // dont announce during handshake @@ -643,27 +653,23 @@ namespace libtorrent m_peer_choked = true; t->get_policy().choked(*this); - if (!t->is_seed()) + if (peer_info_struct() == 0 || !peer_info_struct()->on_parole) { - piece_picker& p = t->picker(); - // remove all pieces from this peers download queue and - // remove the 'downloading' flag from piece_picker. - for (std::deque::iterator i = m_download_queue.begin(); - i != m_download_queue.end(); ++i) + // if the peer is not in parole mode, clear the queued + // up block requests + if (!t->is_seed()) { - p.abort_download(*i); - } - for (std::deque::const_iterator i = m_request_queue.begin() - , end(m_request_queue.end()); i != end; ++i) - { - // since this piece was skipped, clear it and allow it to - // be requested from other peers - p.abort_download(*i); + piece_picker& p = t->picker(); + for (std::deque::const_iterator i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) + { + // since this piece was skipped, clear it and allow it to + // be requested from other peers + p.abort_download(*i); + } } + m_request_queue.clear(); } - - m_download_queue.clear(); - m_request_queue.clear(); } bool match_request(peer_request const& r, piece_block const& b, int block_size) @@ -707,23 +713,17 @@ namespace libtorrent { b = *i; m_download_queue.erase(i); - } - else - { - i = std::find_if(m_request_queue.begin(), m_request_queue.end() - , bind(match_request, boost::cref(r), _1, t->block_size())); - - if (i != m_request_queue.end()) + + // if the peer is in parole mode, keep the request + if (peer_info_struct() && peer_info_struct()->on_parole) { - b = *i; - m_request_queue.erase(i); + m_request_queue.push_front(b); + } + else if (!t->is_seed()) + { + piece_picker& p = t->picker(); + p.abort_download(b); } - } - - if (b.piece_index != -1 && !t->is_seed()) - { - piece_picker& p = t->picker(); - p.abort_download(b); } #ifdef TORRENT_VERBOSE_LOGGING else @@ -1932,7 +1932,6 @@ namespace libtorrent void peer_connection::timed_out() { - if (m_peer_info) ++m_peer_info->failcount; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_ses.m_logger) << "CONNECTION TIMED OUT: " << m_remote.address().to_string() << "\n"; diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 652426806..ebfe07637 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -1732,7 +1732,6 @@ namespace libtorrent ++i->writing; info.state = block_info::state_writing; if (info.num_peers > 0) --info.num_peers; - TORRENT_ASSERT(info.num_peers >= 0); if (i->requested == 0) { @@ -1855,7 +1854,6 @@ namespace libtorrent block_info& info = i->info[block.block_index]; --info.num_peers; - TORRENT_ASSERT(info.num_peers >= 0); if (info.num_peers > 0) return; if (i->info[block.block_index].state == block_info::state_finished diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index dff860bd2..4de01d055 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -986,7 +986,8 @@ namespace libtorrent i->second.prev_amount_upload = 0; i->second.connection = &c; TORRENT_ASSERT(i->second.connection); - i->second.connected = time_now(); + if (!c.fast_reconnect()) + i->second.connected = time_now(); // m_last_optimistic_disconnect = time_now(); } @@ -1045,10 +1046,10 @@ namespace libtorrent // we don't have any info about this peer. // add a new entry - peer p(remote, peer::connectable, src); - i = m_peers.insert(std::make_pair(remote.address(), p)); + i = m_peers.insert(std::make_pair(remote.address() + , peer(remote, peer::connectable, src))); #ifndef TORRENT_DISABLE_ENCRYPTION - if (flags & 0x01) p.pe_support = true; + if (flags & 0x01) i->second.pe_support = true; #endif if (flags & 0x02) i->second.seed = true; @@ -1503,6 +1504,7 @@ namespace libtorrent , failcount(0) , hashfails(0) , seed(false) + , fast_reconnects(0) , optimistically_unchoked(false) , last_optimistically_unchoked(min_time()) , connected(min_time()) diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 68cbd619f..5bdb6b07e 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -186,28 +186,28 @@ namespace libtorrent torrent_info const& ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , bool paused , storage_constructor_type sc) { TORRENT_ASSERT(!ti.m_half_metadata); boost::intrusive_ptr tip(new torrent_info(ti)); return m_impl->add_torrent(tip, save_path, resume_data - , compact_mode, sc, paused, 0); + , storage_mode, sc, paused, 0); } torrent_handle session::add_torrent( boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , bool paused , storage_constructor_type sc , void* userdata) { TORRENT_ASSERT(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data - , compact_mode, sc, paused, userdata); + , storage_mode, sc, paused, userdata); } torrent_handle session::add_torrent( @@ -216,18 +216,18 @@ namespace libtorrent , char const* name , fs::path const& save_path , entry const& e - , bool compact_mode + , storage_mode_t storage_mode , bool paused , storage_constructor_type sc , void* userdata) { return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e - , compact_mode, sc, paused, userdata); + , storage_mode, sc, paused, userdata); } - void session::remove_torrent(const torrent_handle& h) + void session::remove_torrent(const torrent_handle& h, int options) { - m_impl->remove_torrent(h); + m_impl->remove_torrent(h, options); } bool session::listen_on( diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 83498ac14..63c039010 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -189,9 +189,11 @@ namespace detail t->parse_resume_data(t->resume_data, t->torrent_ptr->torrent_file() , error_msg); + // lock the session to add the new torrent + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) { - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); m_ses.m_alerts.post_alert(fastresume_rejected_alert( t->torrent_ptr->get_handle() , error_msg)); @@ -202,8 +204,6 @@ namespace detail #endif } - // lock the session to add the new torrent - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); mutex::scoped_lock l2(m_mutex); if (m_torrents.empty() || m_torrents.front() != t) @@ -328,7 +328,7 @@ namespace detail boost::tie(finished, progress) = processing->torrent_ptr->check_files(); { - mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l2(m_mutex); INVARIANT_CHECK; @@ -340,9 +340,9 @@ namespace detail m_processing.pop_front(); // make sure the lock order is correct - l.unlock(); + l2.unlock(); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - l.lock(); + l2.lock(); processing->torrent_ptr->abort(); processing.reset(); @@ -481,7 +481,7 @@ namespace detail return 0; } - void checker_impl::remove_torrent(sha1_hash const& info_hash) + void checker_impl::remove_torrent(sha1_hash const& info_hash, int options) { INVARIANT_CHECK; for (std::deque >::iterator i @@ -490,6 +490,8 @@ namespace detail if ((*i)->info_hash == info_hash) { TORRENT_ASSERT((*i)->processing == false); + if (options & session::delete_files) + (*i)->torrent_ptr->delete_files(); m_torrents.erase(i); return; } @@ -500,6 +502,8 @@ namespace detail if ((*i)->info_hash == info_hash) { TORRENT_ASSERT((*i)->processing == false); + if (options & session::delete_files) + (*i)->torrent_ptr->delete_files(); m_processing.erase(i); return; } @@ -565,8 +569,19 @@ namespace detail , m_checker_impl(*this) { #ifdef WIN32 - // windows XP has a limit of 10 simultaneous connections - m_half_open.limit(8); + // windows XP has a limit on the number of + // simultaneous half-open TCP connections + DWORD windows_version = ::GetVersion(); + if ((windows_version & 0xff) >= 6) + { + // on vista the limit is 5 (in home edition) + m_half_open.limit(4); + } + else + { + // on XP SP2 it's 10 + m_half_open.limit(8); + } #endif m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; @@ -1623,7 +1638,7 @@ namespace detail boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata) @@ -1655,7 +1670,7 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, ti, save_path - , m_listen_interface, compact_mode, 16 * 1024 + , m_listen_interface, storage_mode, 16 * 1024 , sc, paused)); torrent_ptr->start(); @@ -1701,7 +1716,7 @@ namespace detail , char const* name , fs::path const& save_path , entry const& - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata) @@ -1735,7 +1750,7 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, tracker_url, info_hash, name - , save_path, m_listen_interface, compact_mode, 16 * 1024 + , save_path, m_listen_interface, storage_mode, 16 * 1024 , sc, paused)); torrent_ptr->start(); @@ -1754,7 +1769,7 @@ namespace detail return torrent_handle(this, &m_checker_impl, info_hash); } - void session_impl::remove_torrent(const torrent_handle& h) + void session_impl::remove_torrent(const torrent_handle& h, int options) { if (h.m_ses != this) return; TORRENT_ASSERT(h.m_chk == &m_checker_impl || h.m_chk == 0); @@ -1769,6 +1784,8 @@ namespace detail if (i != m_torrents.end()) { torrent& t = *i->second; + if (options & session::delete_files) + t.delete_files(); t.abort(); if ((!t.is_paused() || t.should_request()) @@ -1815,7 +1832,7 @@ namespace detail if (d != 0) { if (d->processing) d->abort = true; - else m_checker_impl.remove_torrent(h.m_info_hash); + else m_checker_impl.remove_torrent(h.m_info_hash, options); return; } } diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 1cac99e44..6671e38e9 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -361,6 +361,7 @@ namespace libtorrent } void release_files(); + void delete_files(); void initialize(bool allocate_files); bool move_storage(fs::path save_path); size_type read(char* buf, int slot, int offset, int size); @@ -447,7 +448,7 @@ namespace libtorrent } // if the file is empty, just create it. But also make sure - // the directory exits. + // the directory exists. if (file_iter->size == 0) { file(m_save_path / file_iter->path, file::out); @@ -470,6 +471,38 @@ namespace libtorrent std::vector().swap(m_scratch_buffer); } + void storage::delete_files() + { + // make sure we don't have the files open + m_files.release(this); + std::vector().swap(m_scratch_buffer); + + // delete the files from disk + std::set directories; + typedef std::set::iterator iter_t; + for (torrent_info::file_iterator i = m_info->begin_files(true) + , end(m_info->end_files(true)); i != end; ++i) + { + std::string p = (m_save_path / i->path).string(); + fs::path bp = i->path.branch_path(); + std::pair ret = directories.insert(bp.string()); + while (ret.second && !bp.empty()) + { + bp = bp.branch_path(); + std::pair ret = directories.insert(bp.string()); + } + std::remove(p.c_str()); + } + + // remove the directories. Reverse order to delete + // subdirectories first + std::for_each(directories.rbegin(), directories.rend() + , bind((int(*)(char const*))&std::remove, bind(&std::string::c_str, _1))); + + std::string p = (m_save_path / m_info->name()).string(); + std::remove(p.c_str()); + } + void storage::write_resume_data(entry& rd) const { std::vector > file_sizes @@ -931,107 +964,6 @@ namespace libtorrent return new storage(ti, path, fp); } - bool supports_sparse_files(fs::path const& p) - { - TORRENT_ASSERT(p.is_complete()); -#if defined(_WIN32) - // assume windows API is available - DWORD max_component_len = 0; - DWORD volume_flags = 0; - std::string root_device = p.root_name() + "\\"; -#if defined(UNICODE) - std::wstring wph(safe_convert(root_device)); - bool ret = ::GetVolumeInformation(wph.c_str(), 0 - , 0, 0, &max_component_len, &volume_flags, 0, 0); -#else - bool ret = ::GetVolumeInformation(root_device.c_str(), 0 - , 0, 0, &max_component_len, &volume_flags, 0, 0); -#endif - - if (!ret) return false; - if (volume_flags & FILE_SUPPORTS_SPARSE_FILES) - return true; -#endif - -#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) - // find the last existing directory of the save path - fs::path query_path = p; - while (!query_path.empty() && !exists(query_path)) - query_path = query_path.branch_path(); -#endif - -#if defined(__APPLE__) - - struct statfs fsinfo; - int ret = statfs(query_path.native_directory_string().c_str(), &fsinfo); - if (ret != 0) return false; - - attrlist request; - request.bitmapcount = ATTR_BIT_MAP_COUNT; - request.reserved = 0; - request.commonattr = 0; - request.volattr = ATTR_VOL_CAPABILITIES; - request.dirattr = 0; - request.fileattr = 0; - request.forkattr = 0; - - struct vol_capabilities_attr_buf - { - unsigned long length; - vol_capabilities_attr_t info; - } vol_cap; - - ret = getattrlist(fsinfo.f_mntonname, &request, &vol_cap - , sizeof(vol_cap), 0); - if (ret != 0) return false; - - if (vol_cap.info.capabilities[VOL_CAPABILITIES_FORMAT] - & (VOL_CAP_FMT_SPARSE_FILES | VOL_CAP_FMT_ZERO_RUNS)) - { - return true; - } - - // workaround for bugs in Mac OS X where zero run is not reported - if (!strcmp(fsinfo.f_fstypename, "hfs") - || !strcmp(fsinfo.f_fstypename, "ufs")) - return true; - - return false; -#endif - -#if defined(__linux__) || defined(__FreeBSD__) - struct statfs buf; - int err = statfs(query_path.native_directory_string().c_str(), &buf); - if (err == 0) - { - switch (buf.f_type) - { - case 0x5346544e: // NTFS - case 0xEF51: // EXT2 OLD - case 0xEF53: // EXT2 and EXT3 - case 0x00011954: // UFS - case 0x52654973: // ReiserFS - case 0x52345362: // Reiser4 - case 0x58465342: // XFS - case 0x65735546: // NTFS-3G - case 0x19540119: // UFS2 - return true; - } - } -#ifndef NDEBUG - else - { - std::cerr << "statfs returned " << err << std::endl; - std::cerr << "errno: " << errno << std::endl; - std::cerr << "path: " << query_path.native_directory_string() << std::endl; - } -#endif -#endif - - // TODO: POSIX implementation - return false; - } - // -- piece_manager ----------------------------------------------------- piece_manager::piece_manager( @@ -1042,15 +974,16 @@ namespace libtorrent , disk_io_thread& io , storage_constructor_type sc) : m_storage(sc(ti, save_path, fp)) - , m_compact_mode(false) - , m_fill_mode(true) + , m_storage_mode(storage_mode_sparse) , m_info(ti) , m_save_path(complete(save_path)) + , m_current_slot(0) + , m_out_of_place(false) + , m_scratch_piece(-1) , m_storage_constructor(sc) , m_io_thread(io) , m_torrent(torrent) { - m_fill_mode = !supports_sparse_files(save_path); } piece_manager::~piece_manager() @@ -1081,6 +1014,15 @@ namespace libtorrent m_io_thread.add_job(j, handler); } + void piece_manager::async_delete_files( + boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::delete_files; + m_io_thread.add_job(j, handler); + } + void piece_manager::async_move_storage(fs::path const& p , boost::function const& handler) { @@ -1158,16 +1100,11 @@ namespace libtorrent m_piece_hasher.erase(i); } - int slot = m_piece_to_slot[piece]; + int slot = slot_for(piece); TORRENT_ASSERT(slot != has_no_slot); return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece)); } - void piece_manager::release_files_impl() - { - m_storage->release_files(); - } - bool piece_manager::move_storage_impl(fs::path const& save_path) { if (m_storage->move_storage(save_path)) @@ -1177,26 +1114,39 @@ namespace libtorrent } return false; } + void piece_manager::export_piece_map( - std::vector& p) const + std::vector& p, std::vector const& have) const { boost::recursive_mutex::scoped_lock lock(m_mutex); INVARIANT_CHECK; - p.clear(); - std::vector::const_reverse_iterator last; - for (last = m_slot_to_piece.rbegin(); - last != m_slot_to_piece.rend(); ++last) + if (m_storage_mode == storage_mode_compact) { - if (*last != unallocated) break; - } + p.clear(); + p.reserve(m_info->num_pieces()); + std::vector::const_reverse_iterator last; + for (last = m_slot_to_piece.rbegin(); + last != m_slot_to_piece.rend(); ++last) + { + if (*last != unallocated && have[*last]) break; + } - for (std::vector::const_iterator i = - m_slot_to_piece.begin(); - i != last.base(); ++i) + for (std::vector::const_iterator i = + m_slot_to_piece.begin(); + i != last.base(); ++i) + { + p.push_back(have[*i] ? *i : unassigned); + } + } + else { - p.push_back(*i); + p.reserve(m_info->num_pieces()); + for (int i = 0; i < m_info->num_pieces(); ++i) + { + p.push_back(have[i] ? i : unassigned); + } } } @@ -1206,11 +1156,10 @@ namespace libtorrent INVARIANT_CHECK; + if (m_storage_mode != storage_mode_compact) return; + TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); - int slot_index = m_piece_to_slot[piece_index]; - TORRENT_ASSERT(slot_index >= 0); m_slot_to_piece[slot_index] = unassigned; @@ -1218,12 +1167,6 @@ namespace libtorrent m_free_slots.push_back(slot_index); } - int piece_manager::slot_for_piece(int piece_index) const - { - TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces()); - return m_piece_to_slot[piece_index]; - } - unsigned long piece_manager::piece_crc( int slot_index , int block_size @@ -1275,11 +1218,7 @@ namespace libtorrent TORRENT_ASSERT(buf); TORRENT_ASSERT(offset >= 0); TORRENT_ASSERT(size > 0); - TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0 - && m_piece_to_slot[piece_index] < (int)m_slot_to_piece.size()); - int slot = m_piece_to_slot[piece_index]; - TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size()); + int slot = slot_for(piece_index); return m_storage->read(buf, slot, offset, size); } @@ -1292,7 +1231,7 @@ namespace libtorrent TORRENT_ASSERT(buf); TORRENT_ASSERT(offset >= 0); TORRENT_ASSERT(size > 0); - TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces()); if (offset == 0) { @@ -1317,7 +1256,6 @@ namespace libtorrent } int slot = allocate_slot_for_piece(piece_index); - TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size()); m_storage->write(buf, slot, offset, size); } @@ -1426,7 +1364,8 @@ namespace libtorrent // that piece as unassigned, since this slot // is the correct place for the piece. m_slot_to_piece[other_slot] = unassigned; - m_free_slots.push_back(other_slot); + if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(other_slot); } TORRENT_ASSERT(m_piece_to_slot[piece_index] != current_slot); TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); @@ -1485,7 +1424,8 @@ namespace libtorrent bool piece_manager::check_fastresume( aux::piece_checker_data& data , std::vector& pieces - , int& num_pieces, bool compact_mode) + , int& num_pieces, storage_mode_t storage_mode + , std::string& error_msg) { boost::recursive_mutex::scoped_lock lock(m_mutex); @@ -1493,7 +1433,7 @@ namespace libtorrent TORRENT_ASSERT(m_info->piece_length() > 0); - m_compact_mode = compact_mode; + m_storage_mode = storage_mode; // This will corrupt the storage // use while debugging to find @@ -1503,9 +1443,13 @@ namespace libtorrent m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); m_slot_to_piece.resize(m_info->num_pieces(), unallocated); - m_free_slots.clear(); - m_unallocated_slots.clear(); + TORRENT_ASSERT(m_free_slots.empty()); + TORRENT_ASSERT(m_unallocated_slots.empty()); + // assume no piece is out of place (i.e. in a slot + // other than the one it should be in) + bool out_of_place = false; + pieces.clear(); pieces.resize(m_info->num_pieces(), false); num_pieces = 0; @@ -1513,13 +1457,14 @@ namespace libtorrent // if we have fast-resume info // use it instead of doing the actual checking if (!data.piece_map.empty() - && data.piece_map.size() <= m_slot_to_piece.size()) + && int(data.piece_map.size()) <= m_info->num_pieces()) { for (int i = 0; i < (int)data.piece_map.size(); ++i) { m_slot_to_piece[i] = data.piece_map[i]; if (data.piece_map[i] >= 0) { + if (data.piece_map[i] != i) out_of_place = true; m_piece_to_slot[data.piece_map[i]] = i; int found_piece = data.piece_map[i]; @@ -1537,27 +1482,54 @@ namespace libtorrent } else if (data.piece_map[i] == unassigned) { - m_free_slots.push_back(i); + if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(i); } else { TORRENT_ASSERT(data.piece_map[i] == unallocated); - m_unallocated_slots.push_back(i); + if (m_storage_mode == storage_mode_compact) + m_unallocated_slots.push_back(i); } } - m_unallocated_slots.reserve(int(pieces.size() - data.piece_map.size())); - for (int i = (int)data.piece_map.size(); i < (int)pieces.size(); ++i) + if (m_storage_mode == storage_mode_compact) { - m_unallocated_slots.push_back(i); + m_unallocated_slots.reserve(int(m_info->num_pieces() - data.piece_map.size())); + for (int i = (int)data.piece_map.size(); i < (int)m_info->num_pieces(); ++i) + { + m_unallocated_slots.push_back(i); + } + if (m_unallocated_slots.empty()) + { + switch_to_full_mode(); + } + } + else + { + if (!out_of_place) + { + // if no piece is out of place + // since we're in full allocation mode, we can + // forget the piece allocation tables + + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + m_state = state_create_files; + return false; + } + else + { + // in this case we're in full allocation mode, but + // we're resuming a compact allocated storage + m_state = state_expand_pieces; + m_current_slot = 0; + error_msg = "pieces needs to be reordered"; + return false; + } } - if (m_unallocated_slots.empty()) - m_state = state_create_files; - else if (m_compact_mode) - m_state = state_create_files; - else - m_state = state_allocating; + m_state = state_create_files; return false; } @@ -1572,18 +1544,13 @@ namespace libtorrent | | | v - | +------------+ - | | full_check | - | +------------+ - | | - | v - | +------------+ - |->| allocating | - | +------------+ - | | - | v - | +--------------+ - |->| create_files | + | +------------+ +---------------+ + | | full_check |-->| expand_pieses | + | +------------+ +---------------+ + | | | + | v | + | +--------------+ | + +->| create_files | <------+ +--------------+ | v @@ -1602,67 +1569,97 @@ namespace libtorrent std::pair piece_manager::check_files( std::vector& pieces, int& num_pieces, boost::recursive_mutex& mutex) { +#ifndef NDEBUG + boost::recursive_mutex::scoped_lock l_(mutex); TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - - if (m_state == state_allocating) - { - if (m_compact_mode || m_unallocated_slots.empty()) - { - m_state = state_create_files; - return std::make_pair(false, 1.f); - } - - if (int(m_unallocated_slots.size()) == m_info->num_pieces() - && !m_fill_mode) - { - // if there is not a single file on disk, just - // create the files - m_state = state_create_files; - return std::make_pair(false, 1.f); - } - - // if we're not in compact mode, make sure the - // pieces are spread out and placed at their - // final position. - TORRENT_ASSERT(!m_unallocated_slots.empty()); - - if (!m_fill_mode) - { - // if we're not filling the allocation - // just make sure we move the current pieces - // into place, and just skip all other - // allocation - // allocate_slots returns true if it had to - // move any data - allocate_slots(m_unallocated_slots.size(), true); - } - else - { - allocate_slots(1); - } - - return std::make_pair(false, 1.f - (float)m_unallocated_slots.size() - / (float)m_slot_to_piece.size()); - } + l_.unlock(); +#endif if (m_state == state_create_files) { - m_storage->initialize(!m_fill_mode && !m_compact_mode); - - if (!m_unallocated_slots.empty() && !m_compact_mode) - { - TORRENT_ASSERT(!m_fill_mode); - std::vector().swap(m_unallocated_slots); - std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); - m_free_slots.resize(m_info->num_pieces()); - for (int i = 0; i < m_info->num_pieces(); ++i) - m_free_slots[i] = i; - } - + m_storage->initialize(m_storage_mode == storage_mode_allocate); m_state = state_finished; return std::make_pair(true, 1.f); } + if (m_state == state_expand_pieces) + { + INVARIANT_CHECK; + + if (m_scratch_piece >= 0) + { + int piece = m_scratch_piece; + int other_piece = m_slot_to_piece[piece]; + m_scratch_piece = -1; + + if (other_piece >= 0) + { + if (m_scratch_buffer2.empty()) + m_scratch_buffer2.resize(m_info->piece_length()); + + m_storage->read(&m_scratch_buffer2[0], piece, 0, m_info->piece_size(other_piece)); + m_scratch_piece = other_piece; + m_piece_to_slot[other_piece] = unassigned; + } + + // the slot where this piece belongs is + // free. Just move the piece there. + m_storage->write(&m_scratch_buffer[0], piece, 0, m_info->piece_size(piece)); + m_piece_to_slot[piece] = piece; + m_slot_to_piece[piece] = piece; + + if (other_piece >= 0) + m_scratch_buffer.swap(m_scratch_buffer2); + + return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + } + + while (m_current_slot < m_info->num_pieces() + && (m_slot_to_piece[m_current_slot] == m_current_slot + || m_slot_to_piece[m_current_slot] < 0)) + { + ++m_current_slot; + } + + if (m_current_slot == m_info->num_pieces()) + { + m_state = state_create_files; + std::vector().swap(m_scratch_buffer); + std::vector().swap(m_scratch_buffer2); + if (m_storage_mode != storage_mode_compact) + { + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + } + return std::make_pair(false, 1.f); + } + + int piece = m_slot_to_piece[m_current_slot]; + TORRENT_ASSERT(piece >= 0); + int other_piece = m_slot_to_piece[piece]; + if (other_piece >= 0) + { + // there is another piece in the slot + // where this one goes. Store it in the scratch + // buffer until next iteration. + if (m_scratch_buffer.empty()) + m_scratch_buffer.resize(m_info->piece_length()); + + m_storage->read(&m_scratch_buffer[0], piece, 0, m_info->piece_size(other_piece)); + m_scratch_piece = other_piece; + m_piece_to_slot[other_piece] = unassigned; + } + + // the slot where this piece belongs is + // free. Just move the piece there. + m_storage->move_slot(m_current_slot, piece); + m_piece_to_slot[piece] = piece; + m_slot_to_piece[m_current_slot] = unassigned; + m_slot_to_piece[piece] = piece; + + return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + } + TORRENT_ASSERT(m_state == state_full_check); // ------------------------ @@ -1674,12 +1671,13 @@ namespace libtorrent // initialization for the full check if (m_hash_to_piece.empty()) { - m_current_slot = 0; for (int i = 0; i < m_info->num_pieces(); ++i) { m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); } + boost::recursive_mutex::scoped_lock l(mutex); std::fill(pieces.begin(), pieces.end(), false); + num_pieces = 0; } m_piece_data.resize(int(m_info->piece_length())); @@ -1694,6 +1692,10 @@ namespace libtorrent int piece_index = identify_data(m_piece_data, m_current_slot , pieces, num_pieces, m_hash_to_piece, mutex); + if (piece_index != m_current_slot + && piece_index >= 0) + m_out_of_place = true; + TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0); @@ -1745,8 +1747,11 @@ namespace libtorrent std::vector::iterator i = std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); TORRENT_ASSERT(i != m_free_slots.end()); - m_free_slots.erase(i); - m_free_slots.push_back(m_current_slot); + if (m_storage_mode == storage_mode_compact) + { + m_free_slots.erase(i); + m_free_slots.push_back(m_current_slot); + } } if (other_piece >= 0) @@ -1770,7 +1775,8 @@ namespace libtorrent m_slot_to_piece[other_slot] = piece_index; m_piece_to_slot[other_piece] = m_current_slot; - if (piece_index == unassigned) + if (piece_index == unassigned + && m_storage_mode == storage_mode_compact) m_free_slots.push_back(other_slot); if (piece_index >= 0) @@ -1845,8 +1851,11 @@ namespace libtorrent std::vector::iterator i = std::find(m_free_slots.begin(), m_free_slots.end(), slot1); TORRENT_ASSERT(i != m_free_slots.end()); - m_free_slots.erase(i); - m_free_slots.push_back(slot2); + if (m_storage_mode == storage_mode_compact) + { + m_free_slots.erase(i); + m_free_slots.push_back(slot2); + } } if (piece1 >= 0) @@ -1873,7 +1882,7 @@ namespace libtorrent // the slot was identified as piece 'piece_index' if (piece_index != unassigned) m_piece_to_slot[piece_index] = m_current_slot; - else + else if (m_storage_mode == storage_mode_compact) m_free_slots.push_back(m_current_slot); m_slot_to_piece[m_current_slot] = piece_index; @@ -1899,10 +1908,13 @@ namespace libtorrent (file_offset - current_offset + m_info->piece_length() - 1) / m_info->piece_length()); - for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) + if (m_storage_mode == storage_mode_compact) { - TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); - m_unallocated_slots.push_back(i); + for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) + { + TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); + m_unallocated_slots.push_back(i); + } } // current slot will increase by one at the end of the for-loop too @@ -1910,15 +1922,46 @@ namespace libtorrent } ++m_current_slot; - if (m_current_slot >= m_info->num_pieces()) + if (m_current_slot >= m_info->num_pieces()) { TORRENT_ASSERT(m_current_slot == m_info->num_pieces()); // clear the memory we've been using std::vector().swap(m_piece_data); std::multimap().swap(m_hash_to_piece); - m_state = state_allocating; + + if (m_storage_mode != storage_mode_compact) + { + if (!m_out_of_place) + { + // if no piece is out of place + // since we're in full allocation mode, we can + // forget the piece allocation tables + + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + m_state = state_create_files; + return std::make_pair(false, 1.f); + } + else + { + // in this case we're in full allocation mode, but + // we're resuming a compact allocated storage + m_state = state_expand_pieces; + m_current_slot = 0; + return std::make_pair(false, 0.f); + } + } + else if (m_unallocated_slots.empty()) + { + switch_to_full_mode(); + } + m_state = state_create_files; + +#ifndef NDEBUG + boost::recursive_mutex::scoped_lock l(mutex); TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); +#endif return std::make_pair(false, 1.f); } @@ -1927,10 +1970,26 @@ namespace libtorrent return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); } + void piece_manager::switch_to_full_mode() + { + TORRENT_ASSERT(m_storage_mode == storage_mode_compact); + TORRENT_ASSERT(m_unallocated_slots.empty()); + // we have allocated all slots, switch to + // full allocation mode in order to free + // some unnecessary memory. + m_storage_mode = storage_mode_sparse; + std::vector().swap(m_unallocated_slots); + std::vector().swap(m_free_slots); + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + } + int piece_manager::allocate_slot_for_piece(int piece_index) { boost::recursive_mutex::scoped_lock lock(m_mutex); + if (m_storage_mode != storage_mode_compact) return piece_index; + // INVARIANT_CHECK; TORRENT_ASSERT(piece_index >= 0); @@ -2030,26 +2089,27 @@ namespace libtorrent debug_log(); #endif } - TORRENT_ASSERT(slot_index >= 0); TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); + + if (m_unallocated_slots.empty()) + { + switch_to_full_mode(); + } + return slot_index; } bool piece_manager::allocate_slots(int num_slots, bool abort_on_disk) { - TORRENT_ASSERT(num_slots > 0); - boost::recursive_mutex::scoped_lock lock(m_mutex); + TORRENT_ASSERT(num_slots > 0); // INVARIANT_CHECK; TORRENT_ASSERT(!m_unallocated_slots.empty()); + TORRENT_ASSERT(m_storage_mode == storage_mode_compact); - const int stack_buffer_size = 16*1024; - char zeroes[stack_buffer_size]; - memset(zeroes, 0, stack_buffer_size); - bool written = false; for (int i = 0; i < num_slots && !m_unallocated_slots.empty(); ++i) @@ -2069,134 +2129,160 @@ namespace libtorrent m_piece_to_slot[pos] = pos; written = true; } - else if (m_fill_mode) - { - int piece_size = int(m_info->piece_size(pos)); - int offset = 0; - for (; piece_size > 0; piece_size -= stack_buffer_size - , offset += stack_buffer_size) - { - m_storage->write(zeroes, pos, offset - , (std::min)(piece_size, stack_buffer_size)); - } - written = true; - } m_unallocated_slots.erase(m_unallocated_slots.begin()); m_slot_to_piece[new_free_slot] = unassigned; m_free_slots.push_back(new_free_slot); - if (abort_on_disk && written) return true; + if (abort_on_disk && written) break; } TORRENT_ASSERT(m_free_slots.size() > 0); return written; } + int piece_manager::slot_for(int piece) const + { + if (m_storage_mode != storage_mode_compact) return piece; + TORRENT_ASSERT(piece < int(m_piece_to_slot.size())); + TORRENT_ASSERT(piece >= 0); + return m_piece_to_slot[piece]; + } + + int piece_manager::piece_for(int slot) const + { + if (m_storage_mode != storage_mode_compact) return slot; + TORRENT_ASSERT(slot < int(m_slot_to_piece.size())); + TORRENT_ASSERT(slot >= 0); + return m_slot_to_piece[slot]; + } + #ifndef NDEBUG void piece_manager::check_invariant() const { boost::recursive_mutex::scoped_lock lock(m_mutex); - if (m_piece_to_slot.empty()) return; - TORRENT_ASSERT((int)m_piece_to_slot.size() == m_info->num_pieces()); - TORRENT_ASSERT((int)m_slot_to_piece.size() == m_info->num_pieces()); - - for (std::vector::const_iterator i = m_free_slots.begin(); - i != m_free_slots.end(); ++i) + if (m_unallocated_slots.empty() && m_state == state_finished) { - TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(m_slot_to_piece[*i] == unassigned); - TORRENT_ASSERT(std::find(i+1, m_free_slots.end(), *i) - == m_free_slots.end()); + TORRENT_ASSERT(m_storage_mode != storage_mode_compact); } - - for (std::vector::const_iterator i = m_unallocated_slots.begin(); - i != m_unallocated_slots.end(); ++i) + + if (m_storage_mode != storage_mode_compact) { - TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(m_slot_to_piece[*i] == unallocated); - TORRENT_ASSERT(std::find(i+1, m_unallocated_slots.end(), *i) - == m_unallocated_slots.end()); + TORRENT_ASSERT(m_unallocated_slots.empty()); + TORRENT_ASSERT(m_free_slots.empty()); } - - for (int i = 0; i < m_info->num_pieces(); ++i) + + if (m_storage_mode != storage_mode_compact + && m_state != state_expand_pieces + && m_state != state_full_check) { - // Check domain of piece_to_slot's elements - if (m_piece_to_slot[i] != has_no_slot) + TORRENT_ASSERT(m_piece_to_slot.empty()); + TORRENT_ASSERT(m_slot_to_piece.empty()); + } + else + { + if (m_piece_to_slot.empty()) return; + + TORRENT_ASSERT((int)m_piece_to_slot.size() == m_info->num_pieces()); + TORRENT_ASSERT((int)m_slot_to_piece.size() == m_info->num_pieces()); + + for (std::vector::const_iterator i = m_free_slots.begin(); + i != m_free_slots.end(); ++i) { - TORRENT_ASSERT(m_piece_to_slot[i] >= 0); - TORRENT_ASSERT(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(m_slot_to_piece[*i] == unassigned); + TORRENT_ASSERT(std::find(i+1, m_free_slots.end(), *i) + == m_free_slots.end()); } - // Check domain of slot_to_piece's elements - if (m_slot_to_piece[i] != unallocated - && m_slot_to_piece[i] != unassigned) + for (std::vector::const_iterator i = m_unallocated_slots.begin(); + i != m_unallocated_slots.end(); ++i) { - TORRENT_ASSERT(m_slot_to_piece[i] >= 0); - TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(m_slot_to_piece[*i] == unallocated); + TORRENT_ASSERT(std::find(i+1, m_unallocated_slots.end(), *i) + == m_unallocated_slots.end()); } - // do more detailed checks on piece_to_slot - if (m_piece_to_slot[i] >= 0) + for (int i = 0; i < m_info->num_pieces(); ++i) { - TORRENT_ASSERT(m_slot_to_piece[m_piece_to_slot[i]] == i); - if (m_piece_to_slot[i] != i) + // Check domain of piece_to_slot's elements + if (m_piece_to_slot[i] != has_no_slot) { - TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); + TORRENT_ASSERT(m_piece_to_slot[i] >= 0); + TORRENT_ASSERT(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); } - } - else - { - TORRENT_ASSERT(m_piece_to_slot[i] == has_no_slot); - } - // do more detailed checks on slot_to_piece + // Check domain of slot_to_piece's elements + if (m_slot_to_piece[i] != unallocated + && m_slot_to_piece[i] != unassigned) + { + TORRENT_ASSERT(m_slot_to_piece[i] >= 0); + TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + } - if (m_slot_to_piece[i] >= 0) - { - TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[m_slot_to_piece[i]] == i); + // do more detailed checks on piece_to_slot + if (m_piece_to_slot[i] >= 0) + { + TORRENT_ASSERT(m_slot_to_piece[m_piece_to_slot[i]] == i); + if (m_piece_to_slot[i] != i) + { + TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); + } + } + else + { + TORRENT_ASSERT(m_piece_to_slot[i] == has_no_slot); + } + + // do more detailed checks on slot_to_piece + + if (m_slot_to_piece[i] >= 0) + { + TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_piece_to_slot[m_slot_to_piece[i]] == i); #ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT( - std::find( - m_unallocated_slots.begin() - , m_unallocated_slots.end() - , i) == m_unallocated_slots.end() - ); - TORRENT_ASSERT( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , i) == m_free_slots.end() - ); + TORRENT_ASSERT( + std::find( + m_unallocated_slots.begin() + , m_unallocated_slots.end() + , i) == m_unallocated_slots.end() + ); + TORRENT_ASSERT( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , i) == m_free_slots.end() + ); #endif - } - else if (m_slot_to_piece[i] == unallocated) - { + } + else if (m_slot_to_piece[i] == unallocated) + { #ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT(m_unallocated_slots.empty() - || (std::find( - m_unallocated_slots.begin() - , m_unallocated_slots.end() - , i) != m_unallocated_slots.end()) - ); + TORRENT_ASSERT(m_unallocated_slots.empty() + || (std::find( + m_unallocated_slots.begin() + , m_unallocated_slots.end() + , i) != m_unallocated_slots.end()) + ); #endif - } - else if (m_slot_to_piece[i] == unassigned) - { + } + else if (m_slot_to_piece[i] == unassigned) + { #ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , i) != m_free_slots.end() - ); + TORRENT_ASSERT( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , i) != m_free_slots.end() + ); #endif - } - else - { - TORRENT_ASSERT(false && "m_slot_to_piece[i] is invalid"); + } + else + { + TORRENT_ASSERT(false && "m_slot_to_piece[i] is invalid"); + } } } } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index a094fa749..840e488ba 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -154,7 +154,7 @@ namespace libtorrent , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t storage_mode , int block_size , storage_constructor_type sc , bool paused) @@ -195,7 +195,7 @@ namespace libtorrent , m_total_redundant_bytes(0) , m_net_interface(net_interface.address(), 0) , m_save_path(complete(save_path)) - , m_compact_mode(compact_mode) + , m_storage_mode(storage_mode) , m_default_block_size(block_size) , m_connections_initialized(true) , m_settings(ses.settings()) @@ -215,7 +215,7 @@ namespace libtorrent , char const* name , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t storage_mode , int block_size , storage_constructor_type sc , bool paused) @@ -255,7 +255,7 @@ namespace libtorrent , m_total_redundant_bytes(0) , m_net_interface(net_interface.address(), 0) , m_save_path(complete(save_path)) - , m_compact_mode(compact_mode) + , m_storage_mode(storage_mode) , m_default_block_size(block_size) , m_connections_initialized(false) , m_settings(ses.settings()) @@ -1032,6 +1032,16 @@ namespace libtorrent m_announce_timer.cancel(); } + void torrent::on_files_deleted(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(torrent_deleted_alert(get_handle(), "files deleted")); + } + } + void torrent::on_files_released(int ret, disk_io_job const& j) { /* @@ -1668,8 +1678,6 @@ namespace libtorrent try { - TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); - // add the newly connected peer to this torrent's peer list TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); m_connections.insert( @@ -1883,10 +1891,13 @@ namespace libtorrent std::make_pair(a, boost::get_pointer(c))); m_ses.m_connections.insert(std::make_pair(s, c)); + int timeout = settings().peer_connect_timeout; + if (peerinfo) timeout += 3 * peerinfo->failcount; + m_ses.m_half_open.enqueue( bind(&peer_connection::connect, c, _1) , bind(&peer_connection::timed_out, c) - , seconds(settings().peer_connect_timeout)); + , seconds(timeout)); } catch (std::exception& e) { @@ -2215,10 +2226,22 @@ namespace libtorrent bool done = true; try { + std::string error_msg; TORRENT_ASSERT(m_storage); TORRENT_ASSERT(m_owning_storage.get()); done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces - , m_compact_mode); + , m_storage_mode, error_msg); + + if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) + { + m_ses.m_alerts.post_alert(fastresume_rejected_alert( + get_handle(), error_msg)); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "fastresume data for " + << torrent_file().name() << " rejected: " + << error_msg << "\n"; +#endif + } } catch (std::exception& e) { @@ -2378,8 +2401,6 @@ namespace libtorrent piece_manager& torrent::filesystem() { - INVARIANT_CHECK; - TORRENT_ASSERT(m_owning_storage.get()); return *m_owning_storage; } @@ -2537,6 +2558,29 @@ namespace libtorrent return limit; } + void torrent::delete_files() + { +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** DELETING FILES IN TORRENT\n"; + } +#endif + + disconnect_all(); + m_paused = true; + // tell the tracker that we stopped + m_event = tracker_request::stopped; + + if (m_owning_storage.get()) + { + TORRENT_ASSERT(m_storage); + m_storage->async_delete_files( + bind(&torrent::on_files_deleted, shared_from_this(), _1, _2)); + } + } + void torrent::pause() { INVARIANT_CHECK; @@ -2768,7 +2812,7 @@ namespace libtorrent !boost::bind(&peer_connection::is_connecting , boost::bind(&std::map::value_type::second, _1))); - st.compact_mode = m_compact_mode; + st.storage_mode = m_storage_mode; st.num_complete = m_complete; st.num_incomplete = m_incomplete; diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index f6b5d47de..9418d50e8 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -661,7 +661,7 @@ namespace libtorrent if (!t->valid_metadata()) return entry(); - t->filesystem().export_piece_map(piece_index); + std::vector have_pieces = t->pieces(); entry ret(entry::dictionary_t); @@ -673,10 +673,6 @@ namespace libtorrent const sha1_hash& info_hash = t->torrent_file().info_hash(); ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); - ret["slots"] = entry(entry::list_t); - entry::list_type& slots = ret["slots"].list(); - std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); - // blocks per piece int num_blocks_per_piece = static_cast(t->torrent_file().piece_length()) / t->block_size(); @@ -706,6 +702,8 @@ namespace libtorrent // the unfinished piece's index piece_struct["piece"] = i->index; + have_pieces[i->index] = true; + std::string bitmask; const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); @@ -722,10 +720,10 @@ namespace libtorrent } piece_struct["bitmask"] = bitmask; - TORRENT_ASSERT(t->filesystem().slot_for_piece(i->index) >= 0); + TORRENT_ASSERT(t->filesystem().slot_for(i->index) >= 0); unsigned long adler = t->filesystem().piece_crc( - t->filesystem().slot_for_piece(i->index) + t->filesystem().slot_for(i->index) , t->block_size() , i->info); @@ -735,6 +733,11 @@ namespace libtorrent up.push_back(piece_struct); } } + + t->filesystem().export_piece_map(piece_index, have_pieces); + entry::list_type& slots = ret["slots"].list(); + std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); + // write local peers entry::list_type& peer_list = ret["peers"].list(); From c259aea06a571674b2829794ec168ec9247dae9a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 09:01:59 +0000 Subject: [PATCH 0214/1009] Fix adding/removing torrents with current libtorrent. --- deluge/core/torrentmanager.py | 10 ++++++++-- libtorrent/bindings/python/src/session.cpp | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 3cea9beae..222ddeebc 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -171,13 +171,19 @@ class TorrentManager: # Make sure we are adding it with the correct allocation method. if compact is None: compact = self.config["compact_allocation"] + + # Set the right storage_mode + if compact: + storage_mode = lt.storage_mode_t(1) + else: + storage_mode = lt.storage_mode_t(2) try: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), str(save_path), resume_data=fastresume, - compact_mode=compact, + storage_mode=storage_mode, paused=paused) except RuntimeError: log.warning("Error adding torrent") @@ -228,7 +234,7 @@ class TorrentManager: """Remove a torrent from the manager""" try: # Remove from libtorrent session - self.session.remove_torrent(self.torrents[torrent_id].handle) + self.session.remove_torrent(self.torrents[torrent_id].handle, 0) except RuntimeError, KeyError: log.warning("Error removing torrent") return False diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 7332da2ea..05ec2a497 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -185,8 +185,8 @@ void bind_session() .def( "add_torrent", &add_torrent , ( - arg("resume_data") = entry(), arg("compact_mode") = true - , arg("paused") = false + arg("resume_data") = entry(), arg("storage_mode") = storage_mode_sparse, + arg("paused") = false ) , session_add_torrent_doc ) From bf6a709340063dd8344617d36349c559baf0ce31 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 15:12:35 +0000 Subject: [PATCH 0215/1009] Start of ConnectionManager. --- deluge/ui/gtkui/connectionmanager.py | 36 ++++ .../ui/gtkui/glade/connection_manager.glade | 170 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 deluge/ui/gtkui/connectionmanager.py create mode 100644 deluge/ui/gtkui/glade/connection_manager.glade diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py new file mode 100644 index 000000000..d088e6c00 --- /dev/null +++ b/deluge/ui/gtkui/connectionmanager.py @@ -0,0 +1,36 @@ +# +# connectionmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +class ConnectionManager: + def __init__(self): + diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade new file mode 100644 index 000000000..301294e08 --- /dev/null +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -0,0 +1,170 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge Connection Manager + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <big><b>Deluge Connection Manager</b></big> + True + + + False + 1 + + + + + False + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_SPREAD + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + gtk-add + + + + + False + False + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + gtk-remove + + + + + False + False + 1 + + + + + False + 1 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_EDGE + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-connect + True + + + 1 + + + + + False + 2 + + + + + + From c852cfd7c154072df8cb15169b72c699a6903944 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 20 Oct 2007 16:55:38 +0000 Subject: [PATCH 0216/1009] Updates to the ConnectionManager stuff. This is a work in progress. Renamed functions.py to client.py. --- deluge/core/core.py | 10 +- deluge/core/signalmanager.py | 5 + deluge/ui/{functions.py => client.py} | 36 +- deluge/ui/gtkui/aboutdialog.py | 4 +- deluge/ui/gtkui/connectionmanager.py | 165 ++- .../ui/gtkui/glade/connection_manager.glade | 261 +++- deluge/ui/gtkui/glade/main_window.glade | 1114 +++++++++-------- deluge/ui/gtkui/gtkui.py | 6 +- deluge/ui/gtkui/mainwindow.py | 19 +- deluge/ui/gtkui/menubar.py | 22 +- deluge/ui/gtkui/pluginmanager.py | 13 +- deluge/ui/gtkui/preferences.py | 17 +- deluge/ui/gtkui/signals.py | 4 +- deluge/ui/gtkui/statusbar.py | 17 +- deluge/ui/gtkui/systemtray.py | 20 +- deluge/ui/gtkui/torrentdetails.py | 4 +- deluge/ui/gtkui/torrentview.py | 10 +- deluge/ui/signalreceiver.py | 6 + 18 files changed, 1093 insertions(+), 640 deletions(-) rename deluge/ui/{functions.py => client.py} (83%) diff --git a/deluge/core/core.py b/deluge/core/core.py index cdf219004..562da3c7c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -88,7 +88,7 @@ class Core( # Setup the xmlrpc server try: SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, ("localhost", 6666), logRequests=False, allow_none=True) + self, ("localhost", 58846), logRequests=False, allow_none=True) except: log.info("Daemon already running or port not available..") sys.exit(0) @@ -200,6 +200,10 @@ class Core( self.loop.quit() # Exported Methods + def export_ping(self): + """A method to see if the core is running""" + return True + def export_shutdown(self): """Shutdown the core""" # Make shutdown an async call @@ -209,6 +213,10 @@ class Core( """Registers a client with the signal manager so that signals are sent to it.""" self.signals.register_client(uri) + + def export_deregister_client(self, uri): + """De-registers a client with the signal manager.""" + self.signals.deregister_client(uri) def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index ec3fdf318..db018330f 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -39,6 +39,11 @@ class SignalManager: def __init__(self): self.clients = [] + def deregister_client(self, uri): + """Deregisters a client""" + log.debug("Deregistering %s as a signal reciever..", uri) + self.clients.remove(self.clients.index(uri)) + def register_client(self, uri): """Registers a client to emit signals to.""" log.debug("Registering %s as a signal reciever..", uri) diff --git a/deluge/ui/functions.py b/deluge/ui/client.py similarity index 83% rename from deluge/ui/functions.py rename to deluge/ui/client.py index 2334d1b6d..c84a864e6 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/client.py @@ -45,12 +45,27 @@ from deluge.log import LOG as log class CoreProxy: def __init__(self): + self._uri = None self._core = None + self._on_new_core_callbacks = [] + + def connect_on_new_core(self, callback): + """Connect a callback to be called when a new core is connected to.""" + self._on_new_core_callbacks.append(callback) + + def set_core_uri(self, uri): + log.info("Setting core uri as %s", uri) + self._uri = uri + # Get a new core + self.get_core() def get_core(self): - if self._core is None: + if self._core is None and self._uri is not None: log.debug("Creating ServerProxy..") - self._core = xmlrpclib.ServerProxy("http://localhost:6666") + self._core = xmlrpclib.ServerProxy(self._uri) + # Call any callbacks registered + for callback in self._on_new_core_callbacks: + callback() return self._core @@ -69,6 +84,14 @@ def get_core_plugin(plugin): core = dbus.Interface(proxy, "org.deluge_torrent.Deluge." + plugin) return core +def connect_on_new_core(callback): + """Connect a callback whenever a new core is connected to.""" + return _core.connect_on_new_core(callback) + +def set_core_uri(uri): + """Sets the core uri""" + return _core.set_core_uri(uri) + def shutdown(): """Shutdown the core daemon""" get_core().shutdown() @@ -156,6 +179,15 @@ def get_available_plugins(): def get_enabled_plugins(): return get_core().get_enabled_plugins() +def get_download_rate(): + return get_core().get_download_rate() + +def get_upload_rate(): + return get_core().get_upload_rate() + +def get_num_connections(): + return get_core().get_num_connections() + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 2c596580d..e53ef24f4 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -37,13 +37,13 @@ import gtk import pkg_resources import deluge.common -import deluge.ui.functions as functions +import deluge.ui.client as client class AboutDialog: def __init__(self): # Get the glade file for the about dialog def url_hook(dialog, url): - functions.open_url_in_browser(url) + client.open_url_in_browser(url) gtk.about_dialog_set_url_hook(url_hook) self.about = gtk.glade.XML(pkg_resources.resource_filename(\ "deluge.ui.gtkui", "glade/aboutdialog.glade")).get_widget(\ diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index d088e6c00..9fe9e4f31 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -31,6 +31,169 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import gtk, gtk.glade +import pkg_resources +import gobject +import socket + +import deluge.xmlrpclib as xmlrpclib +import deluge.common +import deluge.ui.client as client +from deluge.configmanager import ConfigManager +from deluge.log import LOG as log + +DEFAULT_CONFIG = { + "hosts": ["localhost:58846"] +} + class ConnectionManager: - def __init__(self): + def __init__(self, window): + # Get the glade file for the connection manager + self.glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/connection_manager.glade")) + + self.window = window + self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) + self.connection_manager = self.glade.get_widget("connection_manager") + self.hostlist = self.glade.get_widget("hostlist") + self.connection_manager.set_icon(deluge.common.get_logo(16)) + self.glade.get_widget("image1").set_from_pixbuf( + deluge.common.get_logo(32)) + + self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) + + # Fill in hosts from config file + for host in self.config["hosts"]: + row = self.liststore.append() + self.liststore.set_value(row, 1, host) + + # Setup host list treeview + self.hostlist.set_model(self.liststore) + render = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn("Status", render, pixbuf=0) + self.hostlist.append_column(column) + render = gtk.CellRendererText() + column = gtk.TreeViewColumn("Host", render, text=1) + self.hostlist.append_column(column) + + self.glade.signal_autoconnect({ + "on_button_addhost_clicked": self.on_button_addhost_clicked, + "on_button_removehost_clicked": self.on_button_removehost_clicked, + "on_button_startdaemon_clicked": \ + self.on_button_startdaemon_clicked, + "on_button_cancel_clicked": self.on_button_cancel_clicked, + "on_button_connect_clicked": self.on_button_connect_clicked, + }) + + self.connection_manager.connect("delete-event", self.on_delete_event) + + def show(self): + self.update_timer = gobject.timeout_add(5000, self.update) + self.update() + self.connection_manager.show_all() + + def hide(self): + self.connection_manager.hide() + gobject.source_remove(self.update_timer) + + def update(self): + """Updates the host status""" + def update_row(model=None, path=None, row=None, columns=None): + uri = model.get_value(row, 1) + uri = "http://" + uri + online = True + host = None + try: + host = xmlrpclib.ServerProxy(uri) + host.ping() + except socket.error: + print "socket.error!" + online = False + + print "online: ", online + del host + + if online: + image = gtk.STOCK_YES + else: + image = gtk.STOCK_NO + + pixbuf = self.connection_manager.render_icon( + image, gtk.ICON_SIZE_MENU) + + model.set_value(row, 0, pixbuf) + + self.liststore.foreach(update_row) + return True + + def save(self): + """Save the current host list to file""" + def append_row(model=None, path=None, row=None, columns=None): + hostlist.append(model.get_value(row, 1)) + + hostlist = [] + self.liststore.foreach(append_row, hostlist) + self.config["hosts"] = hostlist + self.config.save() + + ## Callbacks + def on_delete_event(self, widget, event): + self.hide() + return True + + def on_button_addhost_clicked(self, widget): + log.debug("on_button_addhost_clicked") + dialog = self.glade.get_widget("addhost_dialog") + dialog.set_icon(deluge.common.get_logo(16)) + hostname_entry = self.glade.get_widget("entry_hostname") + port_spinbutton = self.glade.get_widget("spinbutton_port") + response = dialog.run() + if response == 1: + # We add the host + hostname = hostname_entry.get_text() + if hostname.startswith("http://"): + hostname = hostname[7:] + + # Check to make sure the hostname is at least 1 character long + if len(hostname) < 1: + dialog.hide() + return + + # Get the port and concatenate the hostname string + port = port_spinbutton.get_value_as_int() + hostname = hostname + ":" + str(port) + row = self.liststore.append() + self.liststore.set_value(row, 1, hostname) + # Save the host list to file + self.save() + + dialog.hide() + + def on_button_removehost_clicked(self, widget): + log.debug("on_button_removehost_clicked") + # Get the selected rows + paths = self.hostlist.get_selection().get_selected_rows()[1] + for path in paths: + self.liststore.remove(self.liststore.get_iter(path)) + + # Save the host list + self.save() + + def on_button_startdaemon_clicked(self, widget): + log.debug("on_button_startdaemon_clicked") + + def on_button_cancel_clicked(self, widget): + log.debug("on_button_cancel_clicked") + self.hide() + + def on_button_connect_clicked(self, widget): + log.debug("on_button_connect_clicked") + paths = self.hostlist.get_selection().get_selected_rows()[1] + row = self.liststore.get_iter(paths[0]) + uri = self.liststore.get_value(row, 1) + uri = "http://" + uri + client.set_core_uri(uri) + self.window.start() + self.hide() diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index 301294e08..ba85aee3b 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,14 +1,23 @@ - + - + + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Deluge Connection Manager - - + 5 + Connection Manager + GTK_WIN_POS_CENTER_ON_PARENT + 350 + 300 + GDK_WINDOW_TYPE_HINT_DIALOG + False + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 True @@ -29,7 +38,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <big><b>Deluge Connection Manager</b></big> + <big><b>Connection Manager</b></big> True @@ -41,12 +50,14 @@ False 5 + 1 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 True @@ -60,7 +71,7 @@ GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -72,25 +83,41 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_SPREAD - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 + GTK_BUTTONBOX_START - + True + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - gtk-add + gtk-add + True + 0 + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + True + 0 + + + + 1 + + False @@ -98,61 +125,123 @@ - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 + 0 + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - gtk-remove + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-execute + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Start local daemon + True + + + 1 + + False False + GTK_PACK_END 1 False + False 1 - 1 + 10 + 2 - + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + checkbutton + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Options + + + label_item + + + + + False + 3 + + + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_EDGE + GTK_BUTTONBOX_END - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True + 0 + - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-connect True + 0 + 1 @@ -161,7 +250,123 @@ False - 2 + GTK_PACK_END + + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Add Host + GTK_WIN_POS_CENTER + True + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Hostname: + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port: + + + False + False + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 5 + 1 + 58846 1 65535 1 10 10 + True + + + False + False + 3 + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 1 + + + 1 + + + + + False + GTK_PACK_END diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index abbfc68fc..801b52a1a 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -113,6 +113,20 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Connection Manager + True + + + + gtk-connect + + + + @@ -348,6 +362,287 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -380,277 +675,18 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - + True 0 + True + PANGO_WRAP_WORD_CHAR 3 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 4 5 + @@ -677,18 +713,277 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 - 4 - 5 - + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 @@ -718,287 +1013,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index f11c89198..8d761072d 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -93,13 +93,13 @@ class GtkUI: self.mainwindow = MainWindow() # Start the signal receiver - self.signal_receiver = Signals(self) + #self.signal_receiver = Signals(self) # Initalize the plugins self.plugins = PluginManager(self) # Start the mainwindow and show it - self.mainwindow.start() + #self.mainwindow.start() # Start the gtk main loop gtk.gdk.threads_init() @@ -113,6 +113,6 @@ class GtkUI: # Clean-up del self.mainwindow - #del self.signal_receiver + # del self.signal_receiver del self.plugins del deluge.configmanager diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 5d882cbb4..5d4d14316 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -37,6 +37,7 @@ import gtk, gtk.glade import gobject import pkg_resources +import deluge.ui.client as client from deluge.configmanager import ConfigManager from menubar import MenuBar from toolbar import ToolBar @@ -45,6 +46,7 @@ from torrentdetails import TorrentDetails from preferences import Preferences from systemtray import SystemTray from statusbar import StatusBar +from connectionmanager import ConnectionManager import deluge.common from deluge.log import LOG as log @@ -81,15 +83,19 @@ class MainWindow: self.preferences = Preferences(self) self.systemtray = SystemTray(self) self.statusbar = StatusBar(self) - - def start(self): - """Start the update thread and show the window""" - self.update_timer = gobject.timeout_add(1000, self.update) + self.connectionmanager = ConnectionManager(self) + client.connect_on_new_core(self.start) if not(self.config["start_in_tray"] and \ self.config["enable_system_tray"]) and not \ self.window.get_property("visible"): log.debug("Showing window") self.show() + self.connectionmanager.show() + + def start(self): + """Start the update thread and show the window""" + self.torrentview.start() + self.update_timer = gobject.timeout_add(1000, self.update) def update(self): # Don't update the UI if the the window is minimized. @@ -121,7 +127,10 @@ class MainWindow: def quit(self): # Stop the update timer from running - gobject.source_remove(self.update_timer) + try: + gobject.source_remove(self.update_timer) + except: + pass del self.systemtray del self.menubar del self.toolbar diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index e7e3d8c0e..721228f3e 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -36,7 +36,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import deluge.ui.functions as functions +import deluge.ui.client as client from deluge.log import LOG as log @@ -70,6 +70,8 @@ class MenuBar: ## Edit Menu "on_menuitem_preferences_activate": \ self.on_menuitem_preferences_activate, + "on_menuitem_connectionmanager_activate": \ + self.on_menuitem_connectionmanager_activate, ## View Menu "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, @@ -97,14 +99,14 @@ class MenuBar: def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") from addtorrentdialog import AddTorrentDialog - functions.add_torrent_file(AddTorrentDialog().run()) + client.add_torrent_file(AddTorrentDialog().run()) def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") from addtorrenturl import AddTorrentUrl result = AddTorrentUrl().run() if result is not None: - functions.add_torrent_url(result) + client.add_torrent_url(result) def on_menuitem_clear_activate(self, data=None): log.debug("on_menuitem_clear_activate") @@ -113,7 +115,7 @@ class MenuBar: log.debug("on_menuitem_quitdaemon_activate") # Tell the core to shutdown self.window.quit() - functions.shutdown() + client.shutdown() def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") @@ -124,20 +126,24 @@ class MenuBar: log.debug("on_menuitem_preferences_activate") self.window.preferences.show() + def on_menuitem_connectionmanager_activate(self, data=None): + log.debug("on_menuitem_connectionmanager_activate") + self.window.connectionmanager.show() + ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") - functions.pause_torrent( + client.pause_torrent( self.window.torrentview.get_selected_torrents()) def on_menuitem_resume_activate(self, data=None): log.debug("on_menuitem_resume_activate") - functions.resume_torrent( + client.resume_torrent( self.window.torrentview.get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") - functions.force_reannounce( + client.force_reannounce( self.window.torrentview.get_selected_torrents()) def on_menuitem_edittrackers_activate(self, data=None): @@ -145,7 +151,7 @@ class MenuBar: def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") - functions.remove_torrent( + client.remove_torrent( self.window.torrentview.get_selected_torrents()) ## View Menu ## diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 9c46ada4f..57217d415 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -32,7 +32,7 @@ # statement from all source files in the program, then also delete it here. import deluge.pluginmanagerbase -import deluge.ui.functions as functions +import deluge.ui.client as client from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -41,16 +41,21 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): self.config = ConfigManager("gtkui.conf") self._gtkui = gtkui - + + # Register a callback with the client + client.connect_on_new_core(self.start) + + def start(self): + """Start the plugin manager""" # Update the enabled_plugins from the core - enabled_plugins = functions.get_enabled_plugins() + enabled_plugins = client.get_enabled_plugins() enabled_plugins += self.config["enabled_plugins"] enabled_plugins = list(set(enabled_plugins)) self.config["enabled_plugins"] = enabled_plugins deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "gtkui.conf", "deluge.plugin.ui.gtk") - + def get_torrentview(self): """Returns a reference to the torrentview component""" return self._gtkui.mainwindow.torrentview diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 9f65803a6..e78178243 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -37,7 +37,7 @@ import gtk, gtk.glade import pkg_resources from deluge.log import LOG as log -import deluge.ui.functions as functions +import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager @@ -51,7 +51,6 @@ class Preferences: self.pref_dialog.set_icon(deluge.common.get_logo(32)) self.treeview = self.glade.get_widget("treeview") self.notebook = self.glade.get_widget("notebook") - self.core = functions.get_core() self.gtkui_config = ConfigManager("gtkui.conf") # Setup the liststore for the categories (tab pages) self.liststore = gtk.ListStore(int, str) @@ -104,7 +103,7 @@ class Preferences: self.liststore.append([index, name]) def show(self): - self.core_config = functions.get_config() + self.core_config = client.get_config() # Update the preferences dialog to reflect current config settings ## Downloads tab ## @@ -134,7 +133,7 @@ class Preferences: self.glade.get_widget("spin_port_max").set_value( self.core_config["listen_ports"][1]) self.glade.get_widget("active_port_label").set_text( - str(functions.get_listen_port())) + str(client.get_listen_port())) self.glade.get_widget("chk_random_port").set_active( self.core_config["random_port"]) self.glade.get_widget("chk_dht").set_active( @@ -193,8 +192,8 @@ class Preferences: self.gtkui_config["send_info"]) ## Plugins tab ## - all_plugins = functions.get_available_plugins() - enabled_plugins = functions.get_enabled_plugins() + all_plugins = client.get_available_plugins() + enabled_plugins = client.get_enabled_plugins() # Clear the existing list so we don't duplicate entries. self.plugin_liststore.clear() # Iterate through the lists and add them to the liststore @@ -310,7 +309,7 @@ class Preferences: config_to_set[key] = new_core_config[key] # Set each changed config value in the core - functions.set_config(config_to_set) + client.set_config(config_to_set) # Update the configuration self.core_config.update(config_to_set) @@ -387,8 +386,8 @@ class Preferences: def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") url = "http://deluge-torrent.org/test-port.php?port=%s" % \ - functions.get_listen_port() - functions.open_url_in_browser(url) + client.get_listen_port() + client.open_url_in_browser(url) def on_plugin_toggled(self, renderer, path): log.debug("on_plugin_toggled") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 37b9f35e3..dff1f8533 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -37,7 +37,7 @@ from deluge.log import LOG as log class Signals: def __init__(self, ui): self.ui = ui - self.receiver = SignalReceiver(6667, "http://localhost:6666") + self.receiver = SignalReceiver(6667, "http://localhost:56684") self.receiver.start() self.receiver.connect_to_signal("torrent_added", self.torrent_added_signal) @@ -49,7 +49,7 @@ class Signals: self.torrent_all_paused) self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) - + def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 64d0f9379..5d619bc8d 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -34,13 +34,12 @@ import gtk import deluge.common -import deluge.ui.functions as functions +import deluge.ui.client as client class StatusBar: def __init__(self, window): self.window = window self.statusbar = self.window.main_glade.get_widget("statusbar") - self.core = functions.get_core() # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() @@ -69,36 +68,36 @@ class StatusBar: expand=False, fill=False) # Update once before showing - self.update() +# self.update() self.statusbar.show_all() def update(self): # Set the max connections label - max_connections = functions.get_config_value("max_connections_global") + max_connections = client.get_config_value("max_connections_global") if max_connections < 0: max_connections = _("Unlimited") self.label_connections.set_text("%s (%s)" % ( - self.core.get_num_connections(), max_connections)) + client.get_num_connections(), max_connections)) # Set the download speed label - max_download_speed = functions.get_config_value("max_download_speed") + max_download_speed = client.get_config_value("max_download_speed") if max_download_speed < 0: max_download_speed = _("Unlimited") else: max_download_speed = "%s %s" % (max_download_speed, _("KiB/s")) self.label_download_speed.set_text("%s/s (%s)" % ( - deluge.common.fsize(self.core.get_download_rate()), + deluge.common.fsize(client.get_download_rate()), max_download_speed)) # Set the upload speed label - max_upload_speed = functions.get_config_value("max_upload_speed") + max_upload_speed = client.get_config_value("max_upload_speed") if max_upload_speed < 0: max_upload_speed = _("Unlimited") else: max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s")) self.label_upload_speed.set_text("%s/s (%s)" % ( - deluge.common.fsize(self.core.get_upload_rate()), + deluge.common.fsize(client.get_upload_rate()), max_upload_speed)) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 14767206d..3f85072be 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -34,7 +34,7 @@ import gtk import pkg_resources -import deluge.ui.functions as functions +import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -49,7 +49,6 @@ class SystemTray: def enable(self): """Enables the system tray icon.""" log.debug("Enabling the system tray icon..") - self.core = functions.get_core() self.tray = gtk.status_icon_new_from_icon_name('deluge') self.tray.connect("activate", self.on_tray_clicked) self.tray.connect("popup-menu", self.on_tray_popup) @@ -79,6 +78,7 @@ class SystemTray: self.tray_glade.get_widget("upload-limit-image").set_from_file( deluge.common.get_pixmap("seeding16.png")) + def start(self): # Build the bandwidth speed limit menus self.build_tray_bwsetsubmenu() @@ -86,13 +86,13 @@ class SystemTray: # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( self.config["tray_download_speed_list"], self.tray_setbwdown, - functions.get_config_value("max_download_speed"), + client.get_config_value("max_download_speed"), _("KiB/s"), show_notset=True, show_other=True) # Create the Upload speed list sub-menu submenu_bwupset = self.build_menu_radio_list( self.config["tray_upload_speed_list"], self.tray_setbwup, - functions.get_config_value("max_upload_speed"), + client.get_config_value("max_upload_speed"), _("KiB/s"), show_notset=True, show_other=True) # Add the sub-menus to the tray menu @@ -160,7 +160,7 @@ class SystemTray: def on_menuitem_add_torrent_activate(self, menuitem): log.debug("on_menuitem_add_torrent_activate") from addtorrentdialog import AddTorrentDialog - functions.add_torrent_file(AddTorrentDialog().run()) + client.add_torrent_file(AddTorrentDialog().run()) def on_menuitem_pause_all_activate(self, menuitem): log.debug("on_menuitem_pause_all_activate") @@ -184,13 +184,13 @@ class SystemTray: log.debug("on_menuitem_quitdaemon_activate") if self.window.visible(): self.window.quit() - functions.shutdown() + client.shutdown() else: if self.config["lock_tray"] == True: self.unlock_tray("quitdaemon") else: self.window.quit() - functions.shutdown() + client.shutdown() def build_menu_radio_list(self, value_list, callback, pref_value=None, suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, @@ -281,7 +281,7 @@ class SystemTray: spin_title.set_text(_("%s Speed (KiB/s):" % string)) spin_speed = dialog_glade.get_widget("spin_speed") spin_speed.set_value( - functions.get_config_value(core_key)) + client.get_config_value(core_key)) spin_speed.select_region(0, -1) response = speed_dialog.run() if response == 1: # OK Response @@ -294,7 +294,7 @@ class SystemTray: # Set the config in the core value = float(value) config_to_set = {core_key: value} - functions.set_config(config_to_set) + client.set_config(config_to_set) # Update the tray speed limit list if value not in self.config[ui_key] and value >= 0: @@ -338,7 +338,7 @@ window, please enter your password")) log.debug("Showing main window via tray") self.window.show() elif comingnext == "quitdaemon": - functions.shutdown() + client.shutdown() self.window.hide() self.window.quit() elif comingnext == "quitui": diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 03cc0c0bf..df36673ec 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -38,7 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext -import deluge.ui.functions as functions +import deluge.ui.client as client import deluge.common from deluge.log import LOG as log @@ -93,7 +93,7 @@ class TorrentDetails: "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", "tracker_status", "save_path"] - status = functions.get_torrent_status(selected, status_keys) + status = client.get_torrent_status(selected, status_keys) # Check to see if we got valid data from the core if status is None: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index dbf961862..f01649fff 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,7 +40,7 @@ import gettext import gobject import deluge.common -import deluge.ui.functions as functions +import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview @@ -163,12 +163,14 @@ class TorrentView(listview.ListView): self.treeview.get_selection().connect("changed", self.on_selection_changed) + def start(self): + """Start the torrentview""" # We need to get the core session state to know which torrents are in # the session so we can add them to our list. - session_state = functions.get_session_state() + session_state = client.get_session_state() for torrent_id in session_state: self.add_row(torrent_id) - + def update(self, columns=None): """Update the view. If columns is not None, it will attempt to only update those columns selected. @@ -212,7 +214,7 @@ class TorrentView(listview.ListView): # Remove duplicates from status_key list status_keys = list(set(status_keys)) - status = functions.get_torrent_status(torrent_id, + status = client.get_torrent_status(torrent_id, status_keys) # Set values for each column in the row diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 2df0435e2..0c1f7e08e 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -48,6 +48,8 @@ class SignalReceiver( log.debug("SignalReceiver init..") threading.Thread.__init__(self) + self.port = port + # Daemonize the thread so it exits when the main program does self.setDaemon(True) @@ -68,7 +70,11 @@ class SignalReceiver( # FIXME: send actual URI not localhost core = xmlrpclib.ServerProxy(core_uri) core.register_client("http://localhost:" + str(port)) + + def __del__(self): + core.deregister_client("http://localhost:" + str(self.port)) + def run(self): """This gets called when we start the thread""" t = threading.Thread(target=self.serve_forever) From c4d4e7566701fe1bdc3fb8bb7a4c399b89b9fd45 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 20 Oct 2007 22:26:43 +0000 Subject: [PATCH 0217/1009] Change the gtkui to a new component based system. Properly handle daemon quitting unexpectedly. Many updates to the ConnectionManager. --- deluge/core/core.py | 2 +- deluge/pluginmanagerbase.py | 12 +- deluge/ui/client.py | 171 ++++++++++++++---- deluge/ui/component.py | 131 ++++++++++++++ deluge/ui/gtkui/connectionmanager.py | 168 +++++++++++++---- .../ui/gtkui/glade/connection_manager.glade | 19 +- deluge/ui/gtkui/gtkui.py | 42 ++++- deluge/ui/gtkui/mainwindow.py | 53 +----- deluge/ui/gtkui/menubar.py | 20 +- deluge/ui/gtkui/pluginmanager.py | 16 +- deluge/ui/gtkui/preferences.py | 12 +- deluge/ui/gtkui/statusbar.py | 41 ++++- deluge/ui/gtkui/systemtray.py | 9 +- deluge/ui/gtkui/toolbar.py | 22 ++- deluge/ui/gtkui/torrentdetails.py | 15 +- deluge/ui/gtkui/torrentview.py | 19 +- 16 files changed, 561 insertions(+), 191 deletions(-) create mode 100644 deluge/ui/component.py diff --git a/deluge/core/core.py b/deluge/core/core.py index 562da3c7c..5a3c26906 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -74,7 +74,7 @@ DEFAULT_PREFS = { "max_upload_slots_global": -1, "max_connections_per_torrent": -1, "max_upload_slots_per_torrent": -1, - "enabled_plugins": ["Queue"] + "enabled_plugins": [] } class Core( diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index bce76b70b..ae41be976 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -65,7 +65,7 @@ class PluginManagerBase: def shutdown(self): log.debug("PluginManager shutting down..") for plugin in self.plugins.values(): - plugin.core.shutdown() + plugin.disable() del self.plugins def __getitem__(self, key): @@ -113,9 +113,11 @@ class PluginManagerBase: def disable_plugin(self, name): """Disables a plugin""" - try: - del self.plugins[name] - except: - log.warning("Unable to disable non-existant plugin %s", name) + + self.plugins[name].disable() + + del self.plugins[name] +# except: + # log.warning("Unable to disable non-existant plugin %s", name) log.info("Plugin %s disabled..", name) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index c84a864e6..29f5af92c 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -33,6 +33,7 @@ import os.path import pickle +import socket import deluge.xmlrpclib as xmlrpclib @@ -48,17 +49,32 @@ class CoreProxy: self._uri = None self._core = None self._on_new_core_callbacks = [] - + self._on_no_core_callbacks = [] + def connect_on_new_core(self, callback): """Connect a callback to be called when a new core is connected to.""" self._on_new_core_callbacks.append(callback) - + + def connect_on_no_core(self, callback): + """Connect a callback to be called when the current core is disconnected + from.""" + self._on_no_core_callbacks.append(callback) + def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) self._uri = uri - # Get a new core - self.get_core() - + if self._uri == None: + for callback in self._on_no_core_callbacks: + callback() + self._core = None + else: + # Get a new core + self.get_core() + + def get_core_uri(self): + """Returns the URI of the core currently being used.""" + return self._uri + def get_core(self): if self._core is None and self._uri is not None: log.debug("Creating ServerProxy..") @@ -88,13 +104,24 @@ def connect_on_new_core(callback): """Connect a callback whenever a new core is connected to.""" return _core.connect_on_new_core(callback) +def connect_on_no_core(callback): + """Connect a callback whenever the core is disconnected from.""" + return _core.connect_on_no_core(callback) + def set_core_uri(uri): """Sets the core uri""" return _core.set_core_uri(uri) - + +def get_core_uri(): + """Get the core URI""" + return _core.get_core_uri() + def shutdown(): """Shutdown the core daemon""" - get_core().shutdown() + try: + get_core().shutdown() + except (AttributeError, socket.error): + set_core_uri(None) def add_torrent_file(torrent_files): """Adds torrent files to the core @@ -112,8 +139,12 @@ def add_torrent_file(torrent_files): (path, filename) = os.path.split(torrent_file) fdump = xmlrpclib.Binary(f.read()) f.close() - result = get_core().add_torrent_file(filename, str(), fdump) - + try: + result = get_core().add_torrent_file(filename, str(), fdump) + except (AttributeError, socket.error): + set_core_uri(None) + result = False + if result is False: # The torrent was not added successfully. log.warning("Torrent %s was not added successfully.", filename) @@ -122,7 +153,12 @@ def add_torrent_url(torrent_url): """Adds torrents to the core via url""" from deluge.common import is_url if is_url(torrent_url): - result = get_core().add_torrent_url(torrent_url, str()) + try: + result = get_core().add_torrent_url(torrent_url, str()) + except (AttributeError, socket.error): + set_core_uri(None) + result = False + if result is False: # The torrent url was not added successfully. log.warning("Torrent %s was not added successfully.", torrent_url) @@ -132,62 +168,125 @@ def add_torrent_url(torrent_url): def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) - for torrent_id in torrent_ids: - get_core().remove_torrent(torrent_id) - + try: + for torrent_id in torrent_ids: + get_core().remove_torrent(torrent_id) + except (AttributeError, socket.error): + set_core_uri(None) + def pause_torrent(torrent_ids): """Pauses torrent_ids""" - for torrent_id in torrent_ids: - get_core().pause_torrent(torrent_id) + try: + for torrent_id in torrent_ids: + get_core().pause_torrent(torrent_id) + except (AttributeError, socket.error): + set_core_uri(None) def resume_torrent(torrent_ids): """Resume torrent_ids""" - for torrent_id in torrent_ids: - get_core().resume_torrent(torrent_id) + try: + for torrent_id in torrent_ids: + get_core().resume_torrent(torrent_id) + except (AttributeError, socket.error): + set_core_uri(None) def force_reannounce(torrent_ids): """Reannounce to trackers""" - for torrent_id in torrent_ids: - get_core().force_reannounce(torrent_id) + try: + for torrent_id in torrent_ids: + get_core().force_reannounce(torrent_id) + except (AttributeError, socket.error): + set_core_uri(None) def get_torrent_status(torrent_id, keys): """Builds the status dictionary and returns it""" - status = get_core().get_torrent_status(torrent_id, keys) + try: + status = get_core().get_torrent_status(torrent_id, keys) + except (AttributeError, socket.error): + set_core_uri(None) + return {} return pickle.loads(status.data) def get_session_state(): - return get_core().get_session_state() - - + try: + state = get_core().get_session_state() + except (AttributeError, socket.error): + set_core_uri(None) + state = [] + return state + def get_config(): - return get_core().get_config() - + try: + config = get_core().get_config() + except (AttributeError, socket.error): + set_core_uri(None) + config = {} + return config + def get_config_value(key): - return get_core().get_config_value(key) + try: + config_value = get_core().get_config_value(key) + except (AttributeError, socket.error): + set_core_uri(None) + config_value = None + return config_value def set_config(config): if config == {}: return - get_core().set_config(config) + try: + get_core().set_config(config) + except (AttributeError, socket.error): + set_core_uri(None) def get_listen_port(): - return int(get_core().get_listen_port()) + try: + port = get_core().get_listen_port() + except (AttributeError, socket.error): + set_core_uri(None) + port = 0 + return int(port) def get_available_plugins(): - return get_core().get_available_plugins() + try: + available = get_core().get_available_plugins() + except (AttributeError, socket.error): + set_core_uri(None) + available = [] + return available def get_enabled_plugins(): - return get_core().get_enabled_plugins() - + try: + enabled = get_core().get_enabled_plugins() + except (AttributeError, socket.error): + set_core_uri(None) + enabled = [] + return enabled + def get_download_rate(): - return get_core().get_download_rate() - + try: + rate = get_core().get_download_rate() + except (AttributeError, socket.error): + set_core_uri(None) + rate = -1 + return rate + def get_upload_rate(): - return get_core().get_upload_rate() + try: + rate = get_core().get_upload_rate() + except (AttributeError, socket.error): + set_core_uri(None) + rate = -1 + return rate def get_num_connections(): - return get_core().get_num_connections() - + try: + num_connections = get_core().get_num_connections() + except (AttributeError, socket.error): + set_core_uri(None) + num_connections = 0 + return num_connections + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/component.py b/deluge/ui/component.py new file mode 100644 index 000000000..434c83391 --- /dev/null +++ b/deluge/ui/component.py @@ -0,0 +1,131 @@ +# +# component.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gobject +from deluge.log import LOG as log + +COMPONENT_STATE = [ + "Stopped", + "Started" +] + +class Component: + def __init__(self, name): + # Register with the ComponentRegistry + register(name, self) + self._state = COMPONENT_STATE.index("Stopped") + + def get_state(self): + return self._state + + def start(self): + pass + + def _start(self): + self._state = COMPONENT_STATE.index("Started") + + def stop(self): + pass + + def _stop(self): + self._state = COMPONENT_STATE.index("Stopped") + + def update(self): + pass + + +class ComponentRegistry: + def __init__(self): + self.components = {} + #self.component_state = {} + self.update_timer = None + + def register(self, name, obj): + """Registers a component""" + log.debug("Registered %s with ComponentRegistry..", name) + self.components[name] = obj + + def get(self, name): + """Returns a reference to the component 'name'""" + return self.components[name] + + def start(self, update_interval=1000): + """Starts all components""" + for component in self.components.keys(): + self.components[component].start() + self.components[component]._start() + + # Start the update timer + self.update_timer = gobject.timeout_add(update_interval, self.update) + # Do an update right away + self.update() + + def stop(self): + """Stops all components""" + for component in self.components.keys(): + self.components[component].stop() + self.components[component]._stop() + # Stop the update timer + gobject.source_remove(self.update_timer) + + def update(self): + """Updates all components""" + for component in self.components.keys(): + # Only update the component if it's started + if self.components[component].get_state() == \ + COMPONENT_STATE.index("Started"): + self.components[component].update() + + return True + +_ComponentRegistry = ComponentRegistry() + +def register(name, obj): + """Registers a UI component with the registry""" + _ComponentRegistry.register(name, obj) + +def start(): + """Starts all components""" + _ComponentRegistry.start() + +def stop(): + """Stops all components""" + _ComponentRegistry.stop() + +def update(): + """Updates all components""" + _ComponentRegistry.update() + +def get(component): + """Return a reference to the component""" + return _ComponentRegistry.get(component) diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 9fe9e4f31..33a87e379 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -36,6 +36,7 @@ import pkg_resources import gobject import socket +import deluge.ui.component as component import deluge.xmlrpclib as xmlrpclib import deluge.common import deluge.ui.client as client @@ -46,14 +47,24 @@ DEFAULT_CONFIG = { "hosts": ["localhost:58846"] } -class ConnectionManager: - def __init__(self, window): +HOSTLIST_COL_PIXBUF = 0 +HOSTLIST_COL_URI = 1 +HOSTLIST_COL_STATUS = 2 + +HOSTLIST_STATUS = [ + "Offline", + "Online", + "Connected" +] +class ConnectionManager(component.Component): + def __init__(self): + component.Component.__init__(self, "ConnectionManager") # Get the glade file for the connection manager self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/connection_manager.glade")) - self.window = window + self.window = component.get("MainWindow") self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) self.connection_manager = self.glade.get_widget("connection_manager") self.hostlist = self.glade.get_widget("hostlist") @@ -62,20 +73,21 @@ class ConnectionManager: self.glade.get_widget("image1").set_from_pixbuf( deluge.common.get_logo(32)) - self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) + self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, int) # Fill in hosts from config file for host in self.config["hosts"]: row = self.liststore.append() - self.liststore.set_value(row, 1, host) + self.liststore.set_value(row, HOSTLIST_COL_URI, host) # Setup host list treeview self.hostlist.set_model(self.liststore) render = gtk.CellRendererPixbuf() - column = gtk.TreeViewColumn("Status", render, pixbuf=0) + column = gtk.TreeViewColumn( + "Status", render, pixbuf=HOSTLIST_COL_PIXBUF) self.hostlist.append_column(column) render = gtk.CellRendererText() - column = gtk.TreeViewColumn("Host", render, text=1) + column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_URI) self.hostlist.append_column(column) self.glade.signal_autoconnect({ @@ -83,60 +95,126 @@ class ConnectionManager: "on_button_removehost_clicked": self.on_button_removehost_clicked, "on_button_startdaemon_clicked": \ self.on_button_startdaemon_clicked, - "on_button_cancel_clicked": self.on_button_cancel_clicked, + "on_button_close_clicked": self.on_button_close_clicked, "on_button_connect_clicked": self.on_button_connect_clicked, }) self.connection_manager.connect("delete-event", self.on_delete_event) + # Connect to the 'changed' event of TreeViewSelection to get selection + # changes. + self.hostlist.get_selection().connect("changed", + self.on_selection_changed) def show(self): - self.update_timer = gobject.timeout_add(5000, self.update) - self.update() + self._update_timer = gobject.timeout_add(5000, self._update) + self._update() self.connection_manager.show_all() def hide(self): self.connection_manager.hide() - gobject.source_remove(self.update_timer) + gobject.source_remove(self._update_timer) - def update(self): + def _update(self): """Updates the host status""" def update_row(model=None, path=None, row=None, columns=None): - uri = model.get_value(row, 1) + uri = model.get_value(row, HOSTLIST_COL_URI) uri = "http://" + uri - online = True - host = None - try: - host = xmlrpclib.ServerProxy(uri) - host.ping() - except socket.error: - print "socket.error!" - online = False + online = self.test_online_status(uri) - print "online: ", online - del host - if online: image = gtk.STOCK_YES + online = HOSTLIST_STATUS.index("Online") else: image = gtk.STOCK_NO - + online = HOSTLIST_STATUS.index("Offline") + + if uri == current_uri: + # We are connected to this host, so lets display the connected + # icon. + image = gtk.STOCK_CONNECT + online = HOSTLIST_STATUS.index("Connected") + pixbuf = self.connection_manager.render_icon( image, gtk.ICON_SIZE_MENU) - model.set_value(row, 0, pixbuf) - - self.liststore.foreach(update_row) - return True + model.set_value(row, HOSTLIST_COL_PIXBUF, pixbuf) + model.set_value(row, HOSTLIST_COL_STATUS, online) + current_uri = client.get_core_uri() + self.liststore.foreach(update_row) + # Update the buttons + self.update_buttons() + return True + + def update_buttons(self): + """Updates the buttons based on selection""" + # Get the selected row's URI + paths = self.hostlist.get_selection().get_selected_rows()[1] + # If nothing is selected, just return + if len(paths) < 1: + return + row = self.liststore.get_iter(paths[0]) + uri = self.liststore.get_value(row, HOSTLIST_COL_URI) + status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) + + # Check to see if a localhost is selected + localhost = False + if uri.split(":")[0] == "localhost" or uri.split(":")[0] == "127.0.0.1": + localhost = True + + # Make actual URI string + uri = "http://" + uri + + # See if this is the currently connected URI + if status == HOSTLIST_STATUS.index("Connected"): + # Display a disconnect button if we're connected to this host + self.glade.get_widget("button_connect").set_label("gtk-disconnect") + else: + self.glade.get_widget("button_connect").set_label("gtk-connect") + + # Update the start daemon button if the selected host is localhost + if localhost: + # Check to see if the host is online + if status == HOSTLIST_STATUS.index("Connected") \ + or status == HOSTLIST_STATUS.index("Online"): + self.glade.get_widget("image_startdaemon").set_from_stock( + gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + self.glade.get_widget("label_startdaemon").set_text( + "_Stop local daemon") + else: + # The localhost is not online + self.glade.get_widget("image_startdaemon").set_from_stock( + gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) + self.glade.get_widget("label_startdaemon").set_text( + "_Start local daemon") + # Make sure label is displayed correctly using mnemonics + self.glade.get_widget("label_startdaemon").set_use_underline( + True) + def save(self): """Save the current host list to file""" def append_row(model=None, path=None, row=None, columns=None): - hostlist.append(model.get_value(row, 1)) + hostlist.append(model.get_value(row, HOSTLIST_COL_URI)) hostlist = [] self.liststore.foreach(append_row, hostlist) self.config["hosts"] = hostlist self.config.save() + + def test_online_status(self, uri): + """Tests the status of URI.. Returns True or False depending on status. + """ + online = True + host = None + try: + host = xmlrpclib.ServerProxy(uri) + host.ping() + except socket.error: + online = False + + del host + + return online ## Callbacks def on_delete_event(self, widget, event): @@ -165,9 +243,11 @@ class ConnectionManager: port = port_spinbutton.get_value_as_int() hostname = hostname + ":" + str(port) row = self.liststore.append() - self.liststore.set_value(row, 1, hostname) + self.liststore.set_value(row, HOSTLIST_COL_URI, hostname) # Save the host list to file self.save() + # Update the status of the hosts + self._update() dialog.hide() @@ -184,16 +264,36 @@ class ConnectionManager: def on_button_startdaemon_clicked(self, widget): log.debug("on_button_startdaemon_clicked") - def on_button_cancel_clicked(self, widget): - log.debug("on_button_cancel_clicked") + def on_button_close_clicked(self, widget): + log.debug("on_button_close_clicked") self.hide() def on_button_connect_clicked(self, widget): log.debug("on_button_connect_clicked") paths = self.hostlist.get_selection().get_selected_rows()[1] row = self.liststore.get_iter(paths[0]) - uri = self.liststore.get_value(row, 1) + status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) + uri = self.liststore.get_value(row, HOSTLIST_COL_URI) uri = "http://" + uri + if status == HOSTLIST_STATUS.index("Connected"): + # If we are connected to this host, then we will disconnect. + client.set_core_uri(None) + self._update() + return + + # Test the host to see if it is online or not. We don't use the status + # column information because it can be up to 5 seconds out of sync. + if not self.test_online_status(uri): + log.warning("Host does not appear to be online..") + # Update the list to show proper status + self._update() + return + + # Status is OK, so lets change to this host client.set_core_uri(uri) self.window.start() self.hide() + + def on_selection_changed(self, treeselection): + log.debug("on_selection_changed") + self.update_buttons() diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index ba85aee3b..8eae805ab 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,6 +1,6 @@ - + True @@ -99,7 +99,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-add True - 0 @@ -111,7 +110,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-remove True - 0 @@ -130,7 +128,6 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 @@ -138,14 +135,14 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-execute - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Start local daemon @@ -193,7 +190,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK checkbutton - 0 True @@ -221,15 +217,14 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel + gtk-close True - 0 - + @@ -240,7 +235,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-connect True - 0 @@ -344,7 +338,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True - 0 diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 8d761072d..79509b56d 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -38,7 +38,17 @@ import gettext import locale import pkg_resources +import deluge.ui.component as component +import deluge.ui.client as client from mainwindow import MainWindow +from menubar import MenuBar +from toolbar import ToolBar +from torrentview import TorrentView +from torrentdetails import TorrentDetails +from preferences import Preferences +from systemtray import SystemTray +from statusbar import StatusBar +from connectionmanager import ConnectionManager from signals import Signals from pluginmanager import PluginManager from deluge.configmanager import ConfigManager @@ -67,7 +77,7 @@ DEFAULT_PREFS = { "window_pane_position": -1, "tray_download_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], "tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], - "enabled_plugins": ["Queue"] + "enabled_plugins": [] } class GtkUI: @@ -88,19 +98,31 @@ class GtkUI: # Make sure gtkui.conf has at least the defaults set config = ConfigManager("gtkui.conf", DEFAULT_PREFS) - - # Initialize the main window - self.mainwindow = MainWindow() + # We make sure that the UI components start once we get a core URI + client.connect_on_new_core(component.start) + client.connect_on_no_core(component.stop) + + # Initialize various components of the gtkui + self.mainwindow = MainWindow() + self.menubar = MenuBar() + self.toolbar = ToolBar() + self.torrentview = TorrentView() + self.torrentdetails = TorrentDetails() + self.preferences = Preferences() + self.systemtray = SystemTray() + self.statusbar = StatusBar() + self.connectionmanager = ConnectionManager() + # Start the signal receiver #self.signal_receiver = Signals(self) # Initalize the plugins self.plugins = PluginManager(self) - # Start the mainwindow and show it - #self.mainwindow.start() - + # Show the connection manager + self.connectionmanager.show() + # Start the gtk main loop gtk.gdk.threads_init() gtk.main() @@ -113,6 +135,12 @@ class GtkUI: # Clean-up del self.mainwindow + del self.systemtray + del self.menubar + del self.toolbar + del self.torrentview + del self.torrentdetails + del self.preferences # del self.signal_receiver del self.plugins del deluge.configmanager diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 5d4d14316..df95d0df1 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -38,21 +38,16 @@ import gobject import pkg_resources import deluge.ui.client as client +import deluge.ui.component as component from deluge.configmanager import ConfigManager -from menubar import MenuBar -from toolbar import ToolBar -from torrentview import TorrentView -from torrentdetails import TorrentDetails -from preferences import Preferences -from systemtray import SystemTray -from statusbar import StatusBar -from connectionmanager import ConnectionManager + import deluge.common from deluge.log import LOG as log -class MainWindow: +class MainWindow(component.Component): def __init__(self): + component.Component.__init__(self, "MainWindow") self.config = ConfigManager("gtkui.conf") # Get the glade file for the main window self.main_glade = gtk.glade.XML( @@ -74,38 +69,13 @@ class MainWindow: self.window.connect("configure-event", self.on_window_configure_event) self.window.connect("delete-event", self.on_window_delete_event) self.vpaned.connect("notify::position", self.on_vpaned_position_event) - - # Initialize various components of the gtkui - self.menubar = MenuBar(self) - self.toolbar = ToolBar(self) - self.torrentview = TorrentView(self) - self.torrentdetails = TorrentDetails(self) - self.preferences = Preferences(self) - self.systemtray = SystemTray(self) - self.statusbar = StatusBar(self) - self.connectionmanager = ConnectionManager(self) - client.connect_on_new_core(self.start) + if not(self.config["start_in_tray"] and \ self.config["enable_system_tray"]) and not \ self.window.get_property("visible"): log.debug("Showing window") self.show() - self.connectionmanager.show() - - def start(self): - """Start the update thread and show the window""" - self.torrentview.start() - self.update_timer = gobject.timeout_add(1000, self.update) - - def update(self): - # Don't update the UI if the the window is minimized. - if self.is_minimized == True: - return True - self.torrentview.update() - self.torrentdetails.update() - self.statusbar.update() - return True - + def show(self): # Load the state prior to showing self.load_window_state() @@ -126,17 +96,6 @@ class MainWindow: return self.window.get_property("visible") def quit(self): - # Stop the update timer from running - try: - gobject.source_remove(self.update_timer) - except: - pass - del self.systemtray - del self.menubar - del self.toolbar - del self.torrentview - del self.torrentdetails - del self.preferences del self.config self.hide() gtk.main_quit() diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 721228f3e..b50543784 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -36,14 +36,16 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources +import deluge.ui.component as component import deluge.ui.client as client from deluge.log import LOG as log -class MenuBar: - def __init__(self, window): +class MenuBar(component.Component): + def __init__(self): log.debug("MenuBar init..") - self.window = window + component.Component.__init__(self, "MenuBar") + self.window = component.get("MainWindow") # Get the torrent menu from the glade file torrentmenu_glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", @@ -124,27 +126,27 @@ class MenuBar: ## Edit Menu ## def on_menuitem_preferences_activate(self, data=None): log.debug("on_menuitem_preferences_activate") - self.window.preferences.show() + component.get("Preferences").show() def on_menuitem_connectionmanager_activate(self, data=None): log.debug("on_menuitem_connectionmanager_activate") - self.window.connectionmanager.show() + component.get("ConnectionManager").show() ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") client.pause_torrent( - self.window.torrentview.get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) def on_menuitem_resume_activate(self, data=None): log.debug("on_menuitem_resume_activate") client.resume_torrent( - self.window.torrentview.get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") client.force_reannounce( - self.window.torrentview.get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") @@ -152,7 +154,7 @@ class MenuBar: def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") client.remove_torrent( - self.window.torrentview.get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) ## View Menu ## def on_menuitem_toolbar_toggled(self, data=None): diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 57217d415..990beb598 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -31,6 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import deluge.ui.component as component import deluge.pluginmanagerbase import deluge.ui.client as client from deluge.configmanager import ConfigManager @@ -58,20 +59,25 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): def get_torrentview(self): """Returns a reference to the torrentview component""" - return self._gtkui.mainwindow.torrentview + #return self._gtkui.mainwindow.torrentview + return component.get("TorrentView") def get_toolbar(self): """Returns a reference to the toolbar component""" - return self._gtkui.mainwindow.toolbar +# return self._gtkui.mainwindow.toolbar + return component.get("ToolBar") def get_menubar(self): """Returns a reference to the menubar component""" - return self._gtkui.mainwindow.menubar + # return self._gtkui.mainwindow.menubar + return component.get("MenuBar") def get_torrentmenu(self): """Returns a reference to the torrentmenu component""" - return self._gtkui.mainwindow.menubar.torrentmenu +# return self._gtkui.mainwindow.menubar.torrentmenu + return component.get("MenuBar").torrentmenu def get_selected_torrents(self): """Returns a list of the selected torrent_ids""" - return self._gtkui.mainwindow.torrentview.get_selected_torrents() +# return self._gtkui.mainwindow.torrentview.get_selected_torrents() + return component.get("TorrentView").get_selected_torrents() diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index e78178243..cc39c8719 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -36,14 +36,16 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources +import deluge.ui.component as component from deluge.log import LOG as log import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager -class Preferences: - def __init__(self, window): - self.window = window +class Preferences(component.Component): + def __init__(self): + component.Component.__init__(self, "Preferences") + self.window = component.get("MainWindow") self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/preferences_dialog.glade")) @@ -395,6 +397,10 @@ class Preferences: name = self.plugin_liststore.get_value(row, 0) value = self.plugin_liststore.get_value(row, 1) self.plugin_liststore.set_value(row, 1, not value) + if not value: + functions.enable_plugin(name) + else: + functions.disable_plugin(name) def on_plugin_selection_changed(self, treeselection): log.debug("on_plugin_selection_changed") diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 5d619bc8d..ffe36710d 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -33,12 +33,15 @@ import gtk +import deluge.ui.component as component import deluge.common import deluge.ui.client as client +from deluge.log import LOG as log -class StatusBar: - def __init__(self, window): - self.window = window +class StatusBar(component.Component): + def __init__(self): + component.Component.__init__(self, "StatusBar") + self.window = component.get("MainWindow") self.statusbar = self.window.main_glade.get_widget("statusbar") # Add a HBox to the statusbar after removing the initial label widget @@ -47,8 +50,13 @@ class StatusBar: frame = self.statusbar.get_children()[0] frame.remove(frame.get_children()[0]) frame.add(self.hbox) - + # Show the not connected status bar + self.show_not_connected() + + def start(self): + log.debug("StatusBar start..") # Add in images and labels + self.clear_statusbar() image = gtk.Image() image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) self.hbox.pack_start(image, expand=False, fill=False) @@ -65,12 +73,28 @@ class StatusBar: self.hbox.pack_start(image, expand=False, fill=False) self.label_upload_speed = gtk.Label() self.hbox.pack_start(self.label_upload_speed, - expand=False, fill=False) + expand=False, fill=False) - # Update once before showing -# self.update() self.statusbar.show_all() - + + def stop(self): + # When stopped, we just show the not connected thingy + self.show_not_connected() + + def show_not_connected(self): + self.clear_statusbar() + image = gtk.Image() + image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + self.hbox.pack_start(image, expand=False, fill=False) + label = gtk.Label("Not connected to daemon..") + self.hbox.pack_start(label, expand=False, fill=False) + self.statusbar.show_all() + + def clear_statusbar(self): + def remove(child): + self.hbox.remove(child) + self.hbox.foreach(remove) + def update(self): # Set the max connections label max_connections = client.get_config_value("max_connections_global") @@ -101,3 +125,4 @@ class StatusBar: self.label_upload_speed.set_text("%s/s (%s)" % ( deluge.common.fsize(client.get_upload_rate()), max_upload_speed)) + diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 3f85072be..8edeabfa8 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -34,14 +34,16 @@ import gtk import pkg_resources +import deluge.ui.component as component import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log -class SystemTray: - def __init__(self, window): - self.window = window +class SystemTray(component.Component): + def __init__(self): + component.Component.__init__(self, "SystemTray") + self.window = component.get("MainWindow") self.config = ConfigManager("gtkui.conf") self.config.register_set_function("enable_system_tray", self.on_enable_system_tray_set) @@ -79,6 +81,7 @@ class SystemTray: deluge.common.get_pixmap("seeding16.png")) def start(self): + log.debug("SystemTray start..") # Build the bandwidth speed limit menus self.build_tray_bwsetsubmenu() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index f975fc9b0..e2cf8cd24 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -35,12 +35,14 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade +import deluge.ui.component as component from deluge.log import LOG as log -class ToolBar: - def __init__(self, window): +class ToolBar(component.Component): + def __init__(self): + component.Component.__init__(self, "ToolBar") log.debug("ToolBar Init..") - self.window = window + self.window = component.get("MainWindow") self.toolbar = self.window.main_glade.get_widget("toolbar") ### Connect Signals ### self.window.main_glade.signal_autoconnect({ @@ -94,34 +96,34 @@ class ToolBar: def on_toolbutton_add_clicked(self, data): log.debug("on_toolbutton_add_clicked") # Use the menubar's callback - self.window.menubar.on_menuitem_addtorrent_activate(data) + component.get("MenuBar").on_menuitem_addtorrent_activate(data) def on_toolbutton_remove_clicked(self, data): log.debug("on_toolbutton_remove_clicked") # Use the menubar's callbacks - self.window.menubar.on_menuitem_remove_activate(data) + component.get("MenuBar").on_menuitem_remove_activate(data) def on_toolbutton_clear_clicked(self, data): log.debug("on_toolbutton_clear_clicked") # Use the menubar's callbacks - self.window.menubar.on_menuitem_clear_activate(data) + component.get("MenuBar").on_menuitem_clear_activate(data) def on_toolbutton_pause_clicked(self, data): log.debug("on_toolbutton_pause_clicked") # Use the menubar's callbacks - self.window.menubar.on_menuitem_pause_activate(data) + component.get("MenuBar").on_menuitem_pause_activate(data) def on_toolbutton_resume_clicked(self, data): log.debug("on_toolbutton_resume_clicked") # Use the menubar's calbacks - self.window.menubar.on_menuitem_resume_activate(data) + component.get("MenuBar").on_menuitem_resume_activate(data) def on_toolbutton_preferences_clicked(self, data): log.debug("on_toolbutton_preferences_clicked") # Use the menubar's callbacks - self.window.menubar.on_menuitem_preferences_activate(data) + component.get("MenuBar").on_menuitem_preferences_activate(data) def on_toolbutton_plugins_clicked(self, data): log.debug("on_toolbutton_plugins_clicked") # Use the menubar's callbacks - self.window.menubar.on_menuitem_preferences_activate(data) + component.get("MenuBar").on_menuitem_preferences_activate(data) diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index df36673ec..46e352435 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -38,13 +38,15 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext +import deluge.ui.component as component import deluge.ui.client as client import deluge.common from deluge.log import LOG as log -class TorrentDetails: - def __init__(self, window): - self.window = window +class TorrentDetails(component.Component): + def __init__(self): + component.Component.__init__(self, "TorrentDetails") + self.window = component.get("MainWindow") glade = self.window.main_glade self.notebook = glade.get_widget("torrent_info") @@ -71,12 +73,16 @@ class TorrentDetails: self.eta = glade.get_widget("summary_eta") self.torrent_path = glade.get_widget("summary_torrent_path") + def stop(self): + self.clear() + def update(self): # Only update if this page is showing if self.notebook.page_num(self.details_tab) is \ self.notebook.get_current_page(): # Get the first selected torrent - selected = self.window.torrentview.get_selected_torrents() + #selected = self.window.torrentview.get_selected_torrents() + selected = component.get("TorrentView").get_selected_torrents() # Only use the first torrent in the list or return if None selected if selected is not None: @@ -154,3 +160,4 @@ class TorrentDetails: self.tracker_status.set_text("") self.next_announce.set_text("") self.eta.set_text("") + self.torrent_path.set_text("") diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index f01649fff..f412b8f98 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,6 +40,7 @@ import gettext import gobject import deluge.common +import deluge.ui.component as component import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview @@ -96,10 +97,11 @@ def cell_data_progress(column, cell, model, row, data): textstr = textstr + " %.2f%%" % value cell.set_property("text", textstr) -class TorrentView(listview.ListView): +class TorrentView(listview.ListView, component.Component): """TorrentView handles the listing of torrents.""" - def __init__(self, window): - self.window = window + def __init__(self): + component.Component.__init__(self, "TorrentView") + self.window = component.get("MainWindow") # Call the ListView constructor listview.ListView.__init__(self, self.window.main_glade.get_widget("torrent_view")) @@ -170,7 +172,12 @@ class TorrentView(listview.ListView): session_state = client.get_session_state() for torrent_id in session_state: self.add_row(torrent_id) - + + def stop(self): + """Stops the torrentview""" + # We need to clear the liststore + self.liststore.clear() + def update(self, columns=None): """Update the view. If columns is not None, it will attempt to only update those columns selected. @@ -294,12 +301,12 @@ class TorrentView(listview.ListView): # We only care about right-clicks if event.button == 3: # Show the Torrent menu from the MenuBar - torrentmenu = self.window.menubar.torrentmenu + torrentmenu = component.get("MenuBar").torrentmenu torrentmenu.popup(None, None, None, event.button, event.time) def on_selection_changed(self, treeselection): """This callback is know when the selection has changed.""" log.debug("on_selection_changed") - self.window.torrentdetails.update() + component.get("TorrentDetails").update() From 298fd1bc7fb185a7be809e36bf5c18272c1a6d56 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 21 Oct 2007 01:53:40 +0000 Subject: [PATCH 0218/1009] Fixed and enabled the SignalReceiver. --- deluge/core/signalmanager.py | 10 +++++----- deluge/ui/client.py | 13 ++++++++----- deluge/ui/gtkui/gtkui.py | 4 ++-- deluge/ui/gtkui/signals.py | 28 +++++++++++++++++----------- deluge/ui/signalreceiver.py | 34 ++++++++++++++++++++++++++-------- 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index db018330f..270414193 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -37,20 +37,20 @@ from deluge.log import LOG as log class SignalManager: def __init__(self): - self.clients = [] + self.clients = {} def deregister_client(self, uri): """Deregisters a client""" log.debug("Deregistering %s as a signal reciever..", uri) - self.clients.remove(self.clients.index(uri)) + del self.clients[uri] def register_client(self, uri): """Registers a client to emit signals to.""" log.debug("Registering %s as a signal reciever..", uri) - self.clients.append(xmlrpclib.ServerProxy(uri)) - + self.clients[uri] = xmlrpclib.ServerProxy(uri) + def emit(self, signal, data): - for client in self.clients: + for client in self.clients.values(): try: client.emit_signal(signal, data) except: diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 29f5af92c..fefba48a6 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -62,14 +62,17 @@ class CoreProxy: def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) - self._uri = uri - if self._uri == None: + + if uri == None: for callback in self._on_no_core_callbacks: callback() + self._uri = None self._core = None - else: - # Get a new core - self.get_core() + return + + self._uri = uri + # Get a new core + self.get_core() def get_core_uri(self): """Returns the URI of the core currently being used.""" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 79509b56d..92fcbaf0e 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -115,7 +115,7 @@ class GtkUI: self.connectionmanager = ConnectionManager() # Start the signal receiver - #self.signal_receiver = Signals(self) + self.signal_receiver = Signals() # Initalize the plugins self.plugins = PluginManager(self) @@ -141,6 +141,6 @@ class GtkUI: del self.torrentview del self.torrentdetails del self.preferences - # del self.signal_receiver + del self.signal_receiver del self.plugins del deluge.configmanager diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index dff1f8533..bbf4d7dca 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -31,13 +31,16 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import deluge.ui.component as component from deluge.ui.signalreceiver import SignalReceiver from deluge.log import LOG as log -class Signals: - def __init__(self, ui): - self.ui = ui - self.receiver = SignalReceiver(6667, "http://localhost:56684") +class Signals(component.Component): + def __init__(self): + component.Component.__init__(self, "Signals") + + def start(self): + self.receiver = SignalReceiver(6667) self.receiver.start() self.receiver.connect_to_signal("torrent_added", self.torrent_added_signal) @@ -50,31 +53,34 @@ class Signals: self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) + def stop(self): + self.receiver.shutdown() + def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) # Add the torrent to the treeview - self.ui.mainwindow.torrentview.add_row(torrent_id) + component.get("TorrentView").add_row(torrent_id) def torrent_removed_signal(self, torrent_id): log.debug("torrent_remove signal received..") log.debug("torrent id: %s", torrent_id) # Remove the torrent from the treeview - self.ui.mainwindow.torrentview.remove_row(torrent_id) - self.ui.mainwindow.torrentdetails.clear() + component.get("TorrentView").remove_row(torrent_id) + component.get("TorrentDetails").clear() def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") - self.ui.mainwindow.torrentview.update() + component.get("TorrentView").update() def torrent_resumed(self, torrent_id): log.debug("torrent_resumed signal received..") - self.ui.mainwindow.torrentview.update() + component.get("TorrentView").update() def torrent_all_paused(self): log.debug("torrent_all_paused signal received..") - self.ui.mainwindow.torrentview.update() + component.get("TorrentView").update() def torrent_all_resumed(self): log.debug("torrent_all_resumed signal received..") - self.ui.mainwindow.torrentview.update() + component.get("TorrentView").update() diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 0c1f7e08e..1bec7f433 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -32,6 +32,7 @@ # statement from all source files in the program, then also delete it here. import sys +import deluge.ui.client as client import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import deluge.xmlrpclib as xmlrpclib @@ -44,10 +45,12 @@ class SignalReceiver( ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): - def __init__(self, port, core_uri): + def __init__(self, port): log.debug("SignalReceiver init..") threading.Thread.__init__(self) - + + # Set to true so that the receiver thread will exit + self._shutdown = False self.port = port # Daemonize the thread so it exits when the main program does @@ -68,18 +71,33 @@ class SignalReceiver( # Register the signal receiver with the core # FIXME: send actual URI not localhost - core = xmlrpclib.ServerProxy(core_uri) + core = client.get_core() core.register_client("http://localhost:" + str(port)) - - def __del__(self): - core.deregister_client("http://localhost:" + str(self.port)) + def shutdown(self): + """Shutdowns receiver thread""" + self._shutdown = True + # De-register with the daemon so it doesn't try to send us more signals + client.get_core().deregister_client( + "http://localhost:" + str(self.port)) + + # Hacky.. sends a request to our local receiver to ensure that it + # shutdowns.. This is because handle_request() is a blocking call. + receiver = xmlrpclib.ServerProxy("http://localhost:" + str(self.port), + allow_none=True) + receiver.emit_signal("shutdown", None) def run(self): """This gets called when we start the thread""" - t = threading.Thread(target=self.serve_forever) + t = threading.Thread(target=self.handle_thread) t.start() - + + def handle_thread(self): + while not self._shutdown: + self.handle_request() + self._shutdown = False + self.server_close() + def emit_signal(self, signal, data): """Exported method used by the core to emit a signal to the client""" log.debug("Received signal %s with data %s from core..", signal, data) From 88a49d17c7d54aed7ebd6317bd6bdd93db884e7a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 21 Oct 2007 16:21:00 +0000 Subject: [PATCH 0219/1009] Stop local daemon now works in ConnectionManager. Some minor tweaks. --- deluge/ui/client.py | 2 +- deluge/ui/gtkui/connectionmanager.py | 17 +- deluge/ui/gtkui/glade/main_window.glade | 1117 +++++++++++------------ 3 files changed, 574 insertions(+), 562 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index fefba48a6..d7a517af2 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -63,7 +63,7 @@ class CoreProxy: def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) - if uri == None: + if uri == None and self._uri != None: for callback in self._on_no_core_callbacks: callback() self._uri = None diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 33a87e379..71cafdad1 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -68,7 +68,7 @@ class ConnectionManager(component.Component): self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) self.connection_manager = self.glade.get_widget("connection_manager") self.hostlist = self.glade.get_widget("hostlist") - self.connection_manager.set_icon(deluge.common.get_logo(16)) + self.connection_manager.set_icon(deluge.common.get_logo(32)) self.glade.get_widget("image1").set_from_pixbuf( deluge.common.get_logo(32)) @@ -106,7 +106,7 @@ class ConnectionManager(component.Component): self.on_selection_changed) def show(self): - self._update_timer = gobject.timeout_add(5000, self._update) + self._update_timer = gobject.timeout_add(1000, self._update) self._update() self.connection_manager.show_all() @@ -263,6 +263,19 @@ class ConnectionManager(component.Component): def on_button_startdaemon_clicked(self, widget): log.debug("on_button_startdaemon_clicked") + paths = self.hostlist.get_selection().get_selected_rows()[1] + row = self.liststore.get_iter(paths[0]) + status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) + if HOSTLIST_STATUS[status] == "Online" or\ + HOSTLIST_STATUS[status] == "Connected": + # We need to stop this daemon + uri = self.liststore.get_value(row, HOSTLIST_COL_URI) + uri = "http://" + uri + # Call the shutdown method on the daemon + core = xmlrpclib.ServerProxy(uri) + core.shutdown() + # Update display to show change + self.update() def on_button_close_clicked(self, widget): log.debug("on_button_close_clicked") diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 801b52a1a..4b622bb18 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -123,6 +123,7 @@ gtk-connect + 1 @@ -206,7 +207,7 @@ True Add torrent - Add Torrent + Add Torrent True gtk-add @@ -219,7 +220,7 @@ True Remove the selected torrents - Remove Torrent + Remove Torrent True gtk-remove @@ -232,7 +233,7 @@ True Remove the finished torrents - Clear Finished + Clear Finished True gtk-clear @@ -254,7 +255,7 @@ True Pause the selected torrents - Pause + Pause True gtk-media-pause @@ -268,7 +269,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Resume the selected torrents - Resume + Resume gtk-media-play @@ -289,7 +290,7 @@ True Preferences - Preferences + Preferences True gtk-preferences @@ -314,7 +315,6 @@ True - False GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC @@ -330,7 +330,6 @@ - True False @@ -362,287 +361,6 @@ 1 2 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - True @@ -675,18 +393,277 @@ 4 5 - + + True + 0 + + + 1 + 2 + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 4 5 - @@ -713,277 +690,18 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - + 0 + True + PANGO_WRAP_WORD_CHAR - 1 - 2 + 3 + 4 4 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 + @@ -1013,6 +731,287 @@ GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + From e03401e3aace4a53406d3a8d4414a0f8bebd7d85 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 05:45:32 +0000 Subject: [PATCH 0220/1009] Fix Preferences dialog so that it can load while not be connected to a daemon. Change ConnectionManager icon and add toolbar button. --- deluge/ui/client.py | 4 - deluge/ui/gtkui/connectionmanager.py | 3 +- deluge/ui/gtkui/glade/main_window.glade | 1117 ++++++++++++----------- deluge/ui/gtkui/preferences.py | 147 +-- 4 files changed, 660 insertions(+), 611 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index d7a517af2..1d33055f5 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -37,10 +37,6 @@ import socket import deluge.xmlrpclib as xmlrpclib -import pygtk -pygtk.require('2.0') -import gtk, gtk.glade - import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 71cafdad1..3536c343b 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -68,7 +68,8 @@ class ConnectionManager(component.Component): self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) self.connection_manager = self.glade.get_widget("connection_manager") self.hostlist = self.glade.get_widget("hostlist") - self.connection_manager.set_icon(deluge.common.get_logo(32)) + #self.connection_manager.set_icon(deluge.common.get_logo(32)) + self.glade.get_widget("image1").set_from_pixbuf( deluge.common.get_logo(32)) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 4b622bb18..55ea25228 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -122,7 +122,7 @@ - gtk-connect + gtk-network 1 @@ -299,6 +299,19 @@ False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Connection Manager + Connection Manager + gtk-network + + + + False + + False @@ -315,6 +328,7 @@ True + False GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC @@ -330,6 +344,7 @@ + True False @@ -361,6 +376,287 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -393,277 +689,18 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - + True 0 + True + PANGO_WRAP_WORD_CHAR 3 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 4 5 + @@ -690,18 +727,277 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 - 4 - 5 - + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 @@ -731,287 +1027,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index cc39c8719..8d22ca26f 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -105,69 +105,106 @@ class Preferences(component.Component): self.liststore.append([index, name]) def show(self): - self.core_config = client.get_config() # Update the preferences dialog to reflect current config settings - + self.core_config = client.get_config() + + if self.core_config != {} and self.core_config != None: + core_widgets = { + "download_path_button": \ + ("filename", self.core_config["download_location"]), + "radio_compact_allocation": \ + ("active", self.core_config["compact_allocation"]), + "radio_full_allocation": \ + ("not_active", self.core_config["compact_allocation"]), + "chk_prioritize_first_last_pieces": \ + ("active", + self.core_config["prioritize_first_last_pieces"]), + "spin_port_min": ("value", self.core_config["listen_ports"][0]), + "spin_port_max": ("value", self.core_config["listen_ports"][1]), + "active_port_label": ("text", str(client.get_listen_port())), + "chk_random_port": ("active", self.core_config["random_port"]), + "chk_dht": ("active", self.core_config["dht"]), + "chk_upnp": ("active", self.core_config["upnp"]), + "chk_natpmp": ("active", self.core_config["natpmp"]), + "chk_utpex": ("active", self.core_config["utpex"]), + "combo_encin": ("active", self.core_config["enc_in_policy"]), + "combo_encout": ("active", self.core_config["enc_out_policy"]), + "combo_enclevel": ("active", self.core_config["enc_level"]), + "chk_pref_rc4": ("active", self.core_config["enc_prefer_rc4"]), + "spin_max_connections_global": \ + ("value", self.core_config["max_connections_global"]), + "spin_max_download": \ + ("value", self.core_config["max_download_speed"]), + "spin_max_upload": \ + ("value", self.core_config["max_upload_speed"]), + "spin_max_upload_slots_global": \ + ("value", self.core_config["max_upload_slots_global"]), + "spin_max_connections_per_torrent": \ + ("value", self.core_config["max_connections_per_torrent"]), + "spin_max_upload_slots_per_torrent": \ + ("value", self.core_config["max_upload_slots_per_torrent"]) + } + + # Update the widgets accordingly + for key in core_widgets.keys(): + modifier = core_widgets[key][0] + value = core_widgets[key][1] + widget = self.glade.get_widget(key) + if type(widget) == gtk.FileChooserButton: + for child in widget.get_children(): + child.set_sensitive(True) + widget.set_sensitive(True) + + if modifier == "filename": + widget.set_filename(value) + elif modifier == "active": + widget.set_active(value) + elif modifier == "not_active": + widget.set_active(not value) + elif modifier == "value": + widget.set_value(value) + elif modifier == "text": + widget.set_text(value) + else: + core_widget_list = [ + "download_path_button", + "radio_compact_allocation", + "radio_full_allocation", + "chk_prioritize_first_last_pieces", + "spin_port_min", + "spin_port_max", + "active_port_label", + "chk_random_port", + "chk_dht", + "chk_upnp", + "chk_natpmp", + "chk_utpex", + "combo_encin", + "combo_encout", + "combo_enclevel", + "chk_pref_rc4", + "spin_max_connections_global", + "spin_max_download", + "spin_max_upload", + "spin_max_upload_slots_global", + "spin_max_connections_per_torrent", + "spin_max_upload_slots_per_torrent" + ] + # We don't appear to be connected to a daemon + for key in core_widget_list: + widget = self.glade.get_widget(key) + if type(widget) == gtk.FileChooserButton: + for child in widget.get_children(): + child.set_sensitive(False) + widget.set_sensitive(False) + ## Downloads tab ## self.glade.get_widget("radio_ask_save").set_active( self.gtkui_config["interactive_add"]) self.glade.get_widget("radio_save_all_to").set_active( not self.gtkui_config["interactive_add"]) - - # This one will need to be re-evaluated if the core is running on a - # different machine.. We won't be able to use the local file browser to - # choose a download location.. It will be specific to the machine core - # is running on. - self.glade.get_widget("download_path_button").set_filename( - self.core_config["download_location"]) - self.glade.get_widget("radio_compact_allocation").set_active( - self.core_config["compact_allocation"]) - self.glade.get_widget("radio_full_allocation").set_active( - not self.core_config["compact_allocation"]) self.glade.get_widget("chk_enable_files_dialog").set_active( self.gtkui_config["enable_files_dialog"]) - self.glade.get_widget("chk_prioritize_first_last_pieces").set_active( - self.core_config["prioritize_first_last_pieces"]) - - ## Network tab ## - self.glade.get_widget("spin_port_min").set_value( - self.core_config["listen_ports"][0]) - self.glade.get_widget("spin_port_max").set_value( - self.core_config["listen_ports"][1]) - self.glade.get_widget("active_port_label").set_text( - str(client.get_listen_port())) - self.glade.get_widget("chk_random_port").set_active( - self.core_config["random_port"]) - self.glade.get_widget("chk_dht").set_active( - self.core_config["dht"]) - self.glade.get_widget("chk_upnp").set_active( - self.core_config["upnp"]) - self.glade.get_widget("chk_natpmp").set_active( - self.core_config["natpmp"]) - self.glade.get_widget("chk_utpex").set_active( - self.core_config["utpex"]) - self.glade.get_widget("combo_encin").set_active( - self.core_config["enc_in_policy"]) - self.glade.get_widget("combo_encout").set_active( - self.core_config["enc_out_policy"]) - self.glade.get_widget("combo_enclevel").set_active( - self.core_config["enc_level"]) - self.glade.get_widget("chk_pref_rc4").set_active( - self.core_config["enc_prefer_rc4"]) - - ## Bandwidth tab ## - self.glade.get_widget("spin_max_connections_global").set_value( - self.core_config["max_connections_global"]) - self.glade.get_widget("spin_max_download").set_value( - self.core_config["max_download_speed"]) - self.glade.get_widget("spin_max_upload").set_value( - self.core_config["max_upload_speed"]) - self.glade.get_widget("spin_max_upload_slots_global").set_value( - self.core_config["max_upload_slots_global"]) - self.glade.get_widget("spin_max_connections_per_torrent").set_value( - self.core_config["max_connections_per_torrent"]) - self.glade.get_widget("spin_max_upload_slots_per_torrent").set_value( - self.core_config["max_upload_slots_per_torrent"]) ## Other tab ## self.glade.get_widget("chk_use_tray").set_active( From 40e1376bc427f7811a4024a08f8299e6eedd4aad Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 07:22:11 +0000 Subject: [PATCH 0221/1009] Hook up ConnectionManager tool button. Fix preferences when applying config with no daemon. --- deluge/ui/gtkui/preferences.py | 20 +++++++++++--------- deluge/ui/gtkui/toolbar.py | 12 ++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 8d22ca26f..baeb3d151 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -341,17 +341,19 @@ class Preferences(component.Component): self.gtkui_config[key] = new_gtkui_config[key] # Core - config_to_set = {} - for key in new_core_config.keys(): - # The values do not match so this needs to be updated - if self.core_config[key] != new_core_config[key]: - config_to_set[key] = new_core_config[key] + if client.get_core_uri() != None: + # Only do this if we're connected to a daemon + config_to_set = {} + for key in new_core_config.keys(): + # The values do not match so this needs to be updated + if self.core_config[key] != new_core_config[key]: + config_to_set[key] = new_core_config[key] - # Set each changed config value in the core - client.set_config(config_to_set) + # Set each changed config value in the core + client.set_config(config_to_set) - # Update the configuration - self.core_config.update(config_to_set) + # Update the configuration + self.core_config.update(config_to_set) # Re-show the dialog to make sure everything has been updated self.show() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index e2cf8cd24..3da269967 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -52,9 +52,9 @@ class ToolBar(component.Component): "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked, "on_toolbutton_resume_clicked": self.on_toolbutton_resume_clicked, "on_toolbutton_preferences_clicked": \ - self.on_toolbutton_preferences_clicked, - "on_toolbutton_plugins_clicked": \ - self.on_toolbutton_plugins_clicked, + self.on_toolbutton_preferences_clicked, + "on_toolbutton_connectionmanager_clicked": \ + self.on_toolbutton_connectionmanager_clicked }) def add_toolbutton(self, callback, label=None, image=None, stock=None, @@ -123,7 +123,7 @@ class ToolBar(component.Component): # Use the menubar's callbacks component.get("MenuBar").on_menuitem_preferences_activate(data) - def on_toolbutton_plugins_clicked(self, data): - log.debug("on_toolbutton_plugins_clicked") + def on_toolbutton_connectionmanager_clicked(self, data): + log.debug("on_toolbutton_connectionmanager_clicked") # Use the menubar's callbacks - component.get("MenuBar").on_menuitem_preferences_activate(data) + component.get("MenuBar").on_menuitem_connectionmanager_activate(data) From 57ff5be8239fb4e6ee20bb07c654217b2be99d35 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 07:40:59 +0000 Subject: [PATCH 0222/1009] Able to change the visibility of the toolbar and infopane. --- deluge/ui/gtkui/glade/main_window.glade | 1106 ++++++++++++----------- deluge/ui/gtkui/menubar.py | 6 +- deluge/ui/gtkui/toolbar.py | 6 + deluge/ui/gtkui/torrentdetails.py | 7 + 4 files changed, 571 insertions(+), 554 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 55ea25228..99f407784 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -153,14 +153,16 @@ _Toolbar True True + True - _Details + _Info Pane True True + @@ -376,287 +378,6 @@ 1 2 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - True @@ -689,18 +410,277 @@ 4 5 - + + True + 0 + + + 1 + 2 + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 4 5 - @@ -727,277 +707,18 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - + 0 + True + PANGO_WRAP_WORD_CHAR - 1 - 2 + 3 + 4 4 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 + @@ -1027,6 +748,287 @@ GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index b50543784..7ae91315a 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -157,11 +157,13 @@ class MenuBar(component.Component): component.get("TorrentView").get_selected_torrents()) ## View Menu ## - def on_menuitem_toolbar_toggled(self, data=None): + def on_menuitem_toolbar_toggled(self, value): log.debug("on_menuitem_toolbar_toggled") + component.get("ToolBar").visible(value.get_active()) - def on_menuitem_infopane_toggled(self, data=None): + def on_menuitem_infopane_toggled(self, value): log.debug("on_menuitem_infopane_toggled") + component.get("TorrentDetails").visible(value.get_active()) ## Help Menu ## def on_menuitem_about_activate(self, data=None): diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 3da269967..d59491357 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -57,6 +57,12 @@ class ToolBar(component.Component): self.on_toolbutton_connectionmanager_clicked }) + def visible(self, visible): + if visible: + self.toolbar.show() + else: + self.toolbar.hide() + def add_toolbutton(self, callback, label=None, image=None, stock=None, tooltip=None): """Adds a toolbutton to the toolbar""" diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 46e352435..aa6035855 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -73,6 +73,13 @@ class TorrentDetails(component.Component): self.eta = glade.get_widget("summary_eta") self.torrent_path = glade.get_widget("summary_torrent_path") + def visible(self, visible): + if visible: + self.notebook.show() + else: + self.notebook.hide() + self.window.vpaned.set_position(-1) + def stop(self): self.clear() From 0d3e319bea3c5e7b10f1a8ad3137b4b2a2f70a1b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 08:16:09 +0000 Subject: [PATCH 0223/1009] Change sensitivity of many UI elements based on state of connection to a daemon. --- deluge/ui/gtkui/glade/main_window.glade | 10 +++++++-- deluge/ui/gtkui/glade/torrent_menu.glade | 5 +++++ deluge/ui/gtkui/menubar.py | 26 ++++++++++++++++++++++++ deluge/ui/gtkui/toolbar.py | 16 +++++++++++++++ deluge/ui/gtkui/torrentdetails.py | 7 ++++++- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 99f407784..dead45def 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -24,6 +24,7 @@ True + False _Add Torrent True @@ -39,6 +40,7 @@ True + False Add _URL True @@ -47,6 +49,7 @@ True + False _Clear Completed True @@ -60,12 +63,10 @@ - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Quit & Shutdown Daemon True @@ -208,6 +209,7 @@ True + False Add torrent Add Torrent True @@ -221,6 +223,7 @@ True + False Remove the selected torrents Remove Torrent True @@ -234,6 +237,7 @@ True + False Remove the finished torrents Clear Finished True @@ -256,6 +260,7 @@ True + False Pause the selected torrents Pause True @@ -269,6 +274,7 @@ True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Resume the selected torrents Resume diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 22766def1..deb1cecbf 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -7,6 +7,7 @@ True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Pause True @@ -22,6 +23,7 @@ True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Resume selected torrents. Resu_me @@ -45,6 +47,7 @@ True + False _Update Tracker True @@ -61,6 +64,7 @@ True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Edit Trackers True @@ -83,6 +87,7 @@ True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Remove Torrent True diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 7ae91315a..12bea2206 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -95,6 +95,32 @@ class MenuBar(component.Component): }) + self.change_sensitivity = [ + "menuitem_addtorrent", + "menuitem_addurl", + "menuitem_clear" + ] + + def start(self): + for widget in self.change_sensitivity: + self.window.main_glade.get_widget(widget).set_sensitive(True) + + for child in self.torrentmenu: + child.set_sensitive(True) + + self.window.main_glade.get_widget("separatormenuitem").show() + self.window.main_glade.get_widget("menuitem_quitdaemon").show() + + def stop(self): + for widget in self.change_sensitivity: + self.window.main_glade.get_widget(widget).set_sensitive(False) + + for child in self.torrentmenu: + child.set_sensitive(False) + + self.window.main_glade.get_widget("separatormenuitem").hide() + self.window.main_glade.get_widget("menuitem_quitdaemon").hide() + ### Callbacks ### ## File Menu ## diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index d59491357..c67ae6b84 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -56,7 +56,23 @@ class ToolBar(component.Component): "on_toolbutton_connectionmanager_clicked": \ self.on_toolbutton_connectionmanager_clicked }) + self.change_sensitivity = [ + "toolbutton_add", + "toolbutton_remove", + "toolbutton_clear", + "toolbutton_pause", + "toolbutton_resume" + ] + + def start(self): + for widget in self.change_sensitivity: + self.window.main_glade.get_widget(widget).set_sensitive(True) + + def stop(self): + for widget in self.change_sensitivity: + self.window.main_glade.get_widget(widget).set_sensitive(False) + def visible(self, visible): if visible: self.toolbar.show() diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index aa6035855..b881a8153 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -52,6 +52,8 @@ class TorrentDetails(component.Component): self.notebook = glade.get_widget("torrent_info") self.details_tab = glade.get_widget("torrentdetails_tab") + self.is_visible = True + # Get the labels we need to update. self.progress_bar = glade.get_widget("progressbar") self.name = glade.get_widget("summary_name") @@ -80,13 +82,16 @@ class TorrentDetails(component.Component): self.notebook.hide() self.window.vpaned.set_position(-1) + self.is_visible = visible + def stop(self): self.clear() def update(self): # Only update if this page is showing if self.notebook.page_num(self.details_tab) is \ - self.notebook.get_current_page(): + self.notebook.get_current_page() and \ + self.notebook.get_property("visible"): # Get the first selected torrent #selected = self.window.torrentview.get_selected_torrents() selected = component.get("TorrentView").get_selected_torrents() From 1144ee802f159332577abfe78362fa1b3d9962de Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 09:52:20 +0000 Subject: [PATCH 0224/1009] SignalReceiver now initializes gobject threading. --- deluge/ui/client.py | 2 +- deluge/ui/gtkui/signals.py | 3 ++- deluge/ui/signalreceiver.py | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 1d33055f5..07990221b 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -1,5 +1,5 @@ # -# functions.py +# client.py # # Copyright (C) 2007 Andrew Resch ('andar') # diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index bbf4d7dca..39759a369 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -47,7 +47,8 @@ class Signals(component.Component): self.receiver.connect_to_signal("torrent_removed", self.torrent_removed_signal) self.receiver.connect_to_signal("torrent_paused", self.torrent_paused) - self.receiver.connect_to_signal("torrent_resumed", self.torrent_resumed) + self.receiver.connect_to_signal("torrent_resumed", + self.torrent_resumed) self.receiver.connect_to_signal("torrent_all_paused", self.torrent_all_paused) self.receiver.connect_to_signal("torrent_all_resumed", diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 1bec7f433..60f880830 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -32,6 +32,9 @@ # statement from all source files in the program, then also delete it here. import sys + +import gobject + import deluge.ui.client as client import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn @@ -47,6 +50,7 @@ class SignalReceiver( def __init__(self, port): log.debug("SignalReceiver init..") + gobject.threads_init() threading.Thread.__init__(self) # Set to true so that the receiver thread will exit @@ -105,14 +109,14 @@ class SignalReceiver( if data != None: for callback in self.signals[signal]: try: - callback(data) + gobject.idle_add(callback, data) except: log.warning("Unable to call callback for signal %s", signal) else: for callback in self.signals[signal]: try: - callback() + gobject.idle_add(callback) except: log.warning("Unable to call callback for signal %s", signal) From 294945dca2d5bc54365f27b4210e3b2eb177309d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 10:11:27 +0000 Subject: [PATCH 0225/1009] Fix icon on ConnectionManager. --- deluge/ui/gtkui/connectionmanager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 3536c343b..71cafdad1 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -68,8 +68,7 @@ class ConnectionManager(component.Component): self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) self.connection_manager = self.glade.get_widget("connection_manager") self.hostlist = self.glade.get_widget("hostlist") - #self.connection_manager.set_icon(deluge.common.get_logo(32)) - + self.connection_manager.set_icon(deluge.common.get_logo(32)) self.glade.get_widget("image1").set_from_pixbuf( deluge.common.get_logo(32)) From 60eeb8deedd31494218c6e8d78f36e6602996f89 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 24 Oct 2007 21:11:33 +0000 Subject: [PATCH 0226/1009] Minor touch-ups. --- TODO | 1 - deluge/core/alertmanager.py | 2 +- deluge/ui/signalreceiver.py | 7 +++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index 17a054f7f..6d8dc210c 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* Have the ui better handle not being able to connect to the daemon. * Add state saving to listview.. this includes saving column size and position. * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index d86f8db11..4df546005 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -84,7 +84,7 @@ class AlertManager: # Call any handlers for this alert type if alert_type in self.handlers.keys(): for handler in self.handlers[alert_type]: - handler(alert) + gobject.idle_add(handler, alert) alert = self.session.pop_alert() diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 60f880830..a5435e20c 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -82,8 +82,11 @@ class SignalReceiver( """Shutdowns receiver thread""" self._shutdown = True # De-register with the daemon so it doesn't try to send us more signals - client.get_core().deregister_client( - "http://localhost:" + str(self.port)) + try: + client.get_core().deregister_client( + "http://localhost:" + str(self.port)) + except (socket.error, AttributeError): + pass # Hacky.. sends a request to our local receiver to ensure that it # shutdowns.. This is because handle_request() is a blocking call. From 1f8884f903f42320508b111abaadbfa4b178f9ed Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 25 Oct 2007 07:42:14 +0000 Subject: [PATCH 0227/1009] Enabled 'start local daemon' in ConnectionManager. Change startup scripts: deluged runs daemon and deluge runs ui. Minor fix-ups. --- deluge/core/core.py | 14 +++++-- deluge/core/daemon.py | 4 +- deluge/main.py | 59 +++++++++++++--------------- deluge/ui/client.py | 14 ++++++- deluge/ui/gtkui/connectionmanager.py | 10 ++++- deluge/ui/signalreceiver.py | 1 + setup.py | 2 +- 7 files changed, 62 insertions(+), 42 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 5a3c26906..18283052f 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -53,6 +53,7 @@ from deluge.core.signalmanager import SignalManager from deluge.log import LOG as log DEFAULT_PREFS = { + "daemon_port": 58846, "compact_allocation": True, "download_location": deluge.common.get_default_download_dir(), "listen_ports": [6881, 6891], @@ -81,14 +82,21 @@ class Core( threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): - def __init__(self): + def __init__(self, port): log.debug("Core init..") threading.Thread.__init__(self) + + # Get config + self.config = ConfigManager("core.conf", DEFAULT_PREFS) + if port == None: + port = self.config["daemon_port"] + # Setup the xmlrpc server try: + log.info("Starting XMLRPC server on port %s", port) SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, ("localhost", 58846), logRequests=False, allow_none=True) + self, ("localhost", port), logRequests=False, allow_none=True) except: log.info("Daemon already running or port not available..") sys.exit(0) @@ -116,8 +124,6 @@ class Core( def run(self): """Starts the core""" - # Get config - self.config = ConfigManager("core.conf", DEFAULT_PREFS) # Create the client fingerprint version = [] diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index 024cebe54..6e23b6c95 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -35,9 +35,9 @@ from deluge.core.core import Core from deluge.log import LOG as log class Daemon: - def __init__(self): + def __init__(self, port): # Start the core as a thread and join it until it's done - self.core = Core() + self.core = Core(port) self.core.start() self.core.join() diff --git a/deluge/main.py b/deluge/main.py index 6a5ee878b..4a6e4dcc3 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -39,52 +39,47 @@ import os from optparse import OptionParser -from deluge.core.daemon import Daemon -from deluge.ui.ui import UI -from deluge.log import LOG as log import deluge.common -def main(): - """Entry point for Deluge""" +def start_ui(): + """Entry point for ui script""" # Setup the argument parser parser = OptionParser(usage="%prog [options] [actions]", version=deluge.common.get_version()) - parser.add_option("--daemon", dest="daemon", help="Start Deluge daemon", - metavar="DAEMON", action="store_true", default=False) - parser.add_option("--ui", dest="ui", help="Start Deluge UI", - metavar="UI", action="store_true", default=False) # Get the options and args from the OptionParser (options, args) = parser.parse_args() - log.info("Deluge %s", deluge.common.get_version()) - + from deluge.log import LOG as log + + log.info("Deluge ui %s", deluge.common.get_version()) log.debug("options: %s", options) log.debug("args: %s", args) - - pid = None - - # Start the daemon - if options.daemon: - log.info("Starting daemon..") - # We need to fork() the process to run it in the background... - # FIXME: We cannot use fork() on Windows - pid = os.fork() - if not pid: - # Since we are starting daemon this process will not start a UI - options.ui = False - # Create the daemon object - Daemon() - # Start the UI - if options.ui: - log.info("Starting ui..") - UI() - + from deluge.ui.ui import UI + log.info("Starting ui..") + UI() + def start_daemon(): """Entry point for daemon script""" - log.info("Deluge daemon %s", deluge.common.get_version()) + # Setup the argument parser + parser = OptionParser(usage="%prog [options] [actions]", + version=deluge.common.get_version()) + parser.add_option("-p", "--port", dest="port", + help="Port daemon will listen on", action="store", type="int") + + # Get the options and args from the OptionParser + (options, args) = parser.parse_args() + + from deluge.log import LOG as log + + log.info("Deluge daemon %s", deluge.common.get_version()) + log.debug("options: %s", options) + log.debug("args: %s", args) + + from deluge.core.daemon import Daemon + log.info("Starting daemon..") pid = os.fork() if not pid: - Daemon() + Daemon(options.port) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 07990221b..15f59b553 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -114,7 +114,19 @@ def set_core_uri(uri): def get_core_uri(): """Get the core URI""" return _core.get_core_uri() - + +def is_localhost(): + """Returns True if core is a localhost""" + # Get the uri + uri = _core.get_core_uri() + if uri != None: + # Get the host + host = uri[7:].split(":")[0] + if host == "localhost" or host == "127.0.0.1": + return True + + return False + def shutdown(): """Shutdown the core daemon""" try: diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 71cafdad1..9276226e3 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -35,6 +35,7 @@ import gtk, gtk.glade import pkg_resources import gobject import socket +import os import deluge.ui.component as component import deluge.xmlrpclib as xmlrpclib @@ -266,17 +267,22 @@ class ConnectionManager(component.Component): paths = self.hostlist.get_selection().get_selected_rows()[1] row = self.liststore.get_iter(paths[0]) status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) + uri = self.liststore.get_value(row, HOSTLIST_COL_URI) + port = uri.split(":")[1] if HOSTLIST_STATUS[status] == "Online" or\ HOSTLIST_STATUS[status] == "Connected": # We need to stop this daemon - uri = self.liststore.get_value(row, HOSTLIST_COL_URI) uri = "http://" + uri # Call the shutdown method on the daemon core = xmlrpclib.ServerProxy(uri) core.shutdown() # Update display to show change self.update() - + elif HOSTLIST_STATUS[status] == "Offline": + log.debug("Start localhost daemon..") + # Spawn a local daemon + os.popen("deluged -p %s" % port) + def on_button_close_clicked(self, widget): log.debug("on_button_close_clicked") self.hide() diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index a5435e20c..b3a8a1938 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -32,6 +32,7 @@ # statement from all source files in the program, then also delete it here. import sys +import socket import gobject diff --git a/setup.py b/setup.py index 0c04904b7..77a648e53 100644 --- a/setup.py +++ b/setup.py @@ -194,6 +194,6 @@ setup( cmdclass=cmdclass, entry_points = """ [console_scripts] - deluge = deluge.main:main + deluge = deluge.main:start_ui deluged = deluge.main:start_daemon """) From 2a315def045a01eee0c5085796783946f17e890c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 25 Oct 2007 08:47:44 +0000 Subject: [PATCH 0228/1009] Change toolbar button sensitivity based on selected torrent state. Patch from sadrul. --- deluge/ui/gtkui/signals.py | 5 +++ deluge/ui/gtkui/toolbar.py | 57 ++++++++++++++++++++++++++++++++-- deluge/ui/gtkui/torrentview.py | 1 + 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 39759a369..a648710c5 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -69,19 +69,24 @@ class Signals(component.Component): # Remove the torrent from the treeview component.get("TorrentView").remove_row(torrent_id) component.get("TorrentDetails").clear() + component.get("ToolBar").update_buttons() def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") component.get("TorrentView").update() + component.get("ToolBar").update_buttons() def torrent_resumed(self, torrent_id): log.debug("torrent_resumed signal received..") component.get("TorrentView").update() + component.get("ToolBar").update_buttons() def torrent_all_paused(self): log.debug("torrent_all_paused signal received..") component.get("TorrentView").update() + component.get("ToolBar").update_buttons() def torrent_all_resumed(self): log.debug("torrent_all_resumed signal received..") component.get("TorrentView").update() + component.get("ToolBar").update_buttons() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index c67ae6b84..bda9e21e5 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -34,11 +34,17 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade +import gobject import deluge.ui.component as component from deluge.log import LOG as log +from deluge.common import TORRENT_STATE +import deluge.ui.client as client class ToolBar(component.Component): + STATE_FINISHED = TORRENT_STATE.index("Finished") + STATE_SEEDING = TORRENT_STATE.index("Seeding") + STATE_PAUSED = TORRENT_STATE.index("Paused") def __init__(self): component.Component.__init__(self, "ToolBar") log.debug("ToolBar Init..") @@ -63,11 +69,11 @@ class ToolBar(component.Component): "toolbutton_pause", "toolbutton_resume" ] - - + def start(self): for widget in self.change_sensitivity: self.window.main_glade.get_widget(widget).set_sensitive(True) + gobject.idle_add(self.update_buttons) def stop(self): for widget in self.change_sensitivity: @@ -149,3 +155,50 @@ class ToolBar(component.Component): log.debug("on_toolbutton_connectionmanager_clicked") # Use the menubar's callbacks component.get("MenuBar").on_menuitem_connectionmanager_activate(data) + + def update_buttons(self): + log.debug("update_buttons") + # If all the selected torrents are paused, then disable the 'Pause' + # button. + # The same goes for the 'Resume' button. + pause = False + resume = False + + # Disable the 'Clear Seeders' button if there's no finished torrent + finished = False + + selecteds = component.get('TorrentView').get_selected_torrents() + if not selecteds : selecteds = [] + + for torrent in selecteds : + status = client.get_torrent_status(torrent, ['state'])['state'] + if status == self.STATE_PAUSED: + resume = True + elif status in [self.STATE_FINISHED, self.STATE_SEEDING]: + finished = True + pause = True + else: + pause = True + if pause and resume and finished: break + + # Enable the 'Remove Torrent' button only if there's some selected + # torrent. + remove = (len(selecteds ) > 0) + + if not finished: + torrents = client.get_session_state() + for torrent in torrents: + if torrent in selecteds: continue + status = client.get_torrent_status(torrent, ['state'])['state'] + if status in [self.STATE_FINISHED, self.STATE_SEEDING]: + finished = True + break + + for name, sensitive in (("toolbutton_pause", pause), + ("toolbutton_resume", resume), + ("toolbutton_remove", remove), + ("toolbutton_clear", finished)): + self.window.main_glade.get_widget(name).set_sensitive(sensitive) + + return False + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index f412b8f98..05f92de36 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -308,5 +308,6 @@ class TorrentView(listview.ListView, component.Component): """This callback is know when the selection has changed.""" log.debug("on_selection_changed") component.get("TorrentDetails").update() + component.get("ToolBar").update_buttons() From c030789e09c04e4ad13536b1642e4f8011e1d643 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 26 Oct 2007 02:32:40 +0000 Subject: [PATCH 0229/1009] Updates to ConnectionManager. Can now autostart localhost, autoconnect and not show dialog if desired. --- TODO | 2 + deluge/ui/client.py | 3 +- deluge/ui/gtkui/connectionmanager.py | 70 +- .../ui/gtkui/glade/connection_manager.glade | 58 +- deluge/ui/gtkui/glade/main_window.glade | 1104 ++++++++--------- deluge/ui/gtkui/gtkui.py | 11 +- 6 files changed, 684 insertions(+), 564 deletions(-) diff --git a/TODO b/TODO index 6d8dc210c..b4986a4dd 100644 --- a/TODO +++ b/TODO @@ -11,3 +11,5 @@ * Restart daemon function * Docstrings! * Update libtorrent and bindings for sparse_mode allocation and remove_torrent options +* Implement caching in client.py +* Create a new add torrent dialog diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 15f59b553..08bf18277 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -131,7 +131,8 @@ def shutdown(): """Shutdown the core daemon""" try: get_core().shutdown() - except (AttributeError, socket.error): + except: + # Ignore everything set_core_uri(None) def add_torrent_file(torrent_files): diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 9276226e3..6788ac452 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -67,6 +67,7 @@ class ConnectionManager(component.Component): self.window = component.get("MainWindow") self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) + self.gtkui_config = ConfigManager("gtkui.conf") self.connection_manager = self.glade.get_widget("connection_manager") self.hostlist = self.glade.get_widget("hostlist") self.connection_manager.set_icon(deluge.common.get_logo(32)) @@ -98,6 +99,9 @@ class ConnectionManager(component.Component): self.on_button_startdaemon_clicked, "on_button_close_clicked": self.on_button_close_clicked, "on_button_connect_clicked": self.on_button_connect_clicked, + "on_chk_autoconnect_toggled": self.on_chk_autoconnect_toggled, + "on_chk_autostart_toggled": self.on_chk_autostart_toggled, + "on_chk_donotshow_toggled": self.on_chk_donotshow_toggled }) self.connection_manager.connect("delete-event", self.on_delete_event) @@ -106,14 +110,58 @@ class ConnectionManager(component.Component): self.hostlist.get_selection().connect("changed", self.on_selection_changed) + # Auto connect to a host if applicable + if self.gtkui_config["autoconnect"] and \ + self.gtkui_config["autoconnect_host_uri"] != None: + uri = self.gtkui_config["autoconnect_host_uri"] + # Make sure the uri is proper + if uri[:7] != "http://": + uri = "http://" + uri + if self.test_online_status(uri): + # Host is online, so lets connect + client.set_core_uri(uri) + self.hide() + elif self.gtkui_config["autostart_localhost"]: + # Check to see if we are trying to connect to a localhost + if uri[7:].split(":")[0] == "localhost" or \ + uri[7:].split(":")[0] == "127.0.0.1": + # This is a localhost, so lets try to start it + port = uri[7:].split(":")[1] + os.popen("deluged -p %s" % port) + # We need to wait for the host to start before connecting + while not self.test_online_status(uri): + sleep(10) + client.set_core_uri(uri) + self.hide() + + def start(self): + if self.gtkui_config["autoconnect"]: + # We need to update the autoconnect_host_uri on connection to host + # start() gets called whenever we get a new connection to a host + self.gtkui_config["autoconnect_host_uri"] = client.get_core_uri() + def show(self): + # Set the checkbuttons according to config + self.glade.get_widget("chk_autoconnect").set_active( + self.gtkui_config["autoconnect"]) + self.glade.get_widget("chk_autostart").set_active( + self.gtkui_config["autostart_localhost"]) + self.glade.get_widget("chk_donotshow").set_active( + not self.gtkui_config["show_connection_manager_on_start"]) + + # Setup timer to update host status self._update_timer = gobject.timeout_add(1000, self._update) self._update() self.connection_manager.show_all() def hide(self): self.connection_manager.hide() - gobject.source_remove(self._update_timer) + try: + gobject.source_remove(self._update_timer) + except AttributeError: + # We are probably trying to hide the window without having it showed + # first. OK to ignore. + pass def _update(self): """Updates the host status""" @@ -310,9 +358,27 @@ class ConnectionManager(component.Component): # Status is OK, so lets change to this host client.set_core_uri(uri) - self.window.start() self.hide() + def on_chk_autoconnect_toggled(self, widget): + log.debug("on_chk_autoconnect_toggled") + value = widget.get_active() + self.gtkui_config["autoconnect"] = value + # If we are currently connected to a host, set that as the autoconnect + # host. + if client.get_core_uri() != None: + self.gtkui_config["autoconnect_host_uri"] = client.get_core_uri() + + def on_chk_autostart_toggled(self, widget): + log.debug("on_chk_autostart_toggled") + value = widget.get_active() + self.gtkui_config["autostart_localhost"] = value + + def on_chk_donotshow_toggled(self, widget): + log.debug("on_chk_donotshow_toggled") + value = widget.get_active() + self.gtkui_config["show_connection_manager_on_start"] = not value + def on_selection_changed(self, treeselection): log.debug("on_selection_changed") self.update_buttons() diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index 8eae805ab..4c7fb9005 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,6 +1,6 @@ - + True @@ -99,6 +99,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-add True + 0 @@ -110,6 +111,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-remove True + 0 @@ -128,6 +130,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 @@ -181,16 +184,56 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 5 + 5 + 5 - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - checkbutton - True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically connect to selected host on start-up + 0 + True + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically start localhost if needed + 0 + True + + + + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Do not show this dialog on start-up + 0 + True + + + + 2 + + @@ -224,6 +267,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-close True + 0 @@ -235,6 +279,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-connect True + 0 @@ -338,6 +383,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True + 0 diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index dead45def..b62b98cd7 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -238,8 +238,8 @@ True False - Remove the finished torrents - Clear Finished + Remove the seeding torrents + Clear Seeds True gtk-clear @@ -384,6 +384,287 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -416,277 +697,18 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - + True 0 + True + PANGO_WRAP_WORD_CHAR 3 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 4 5 + @@ -713,18 +735,277 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 - 4 - 5 - + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 @@ -754,287 +1035,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 92fcbaf0e..f5b5f06b7 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -77,7 +77,11 @@ DEFAULT_PREFS = { "window_pane_position": -1, "tray_download_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], "tray_upload_speed_list" : [5.0, 10.0, 30.0, 80.0, 300.0], - "enabled_plugins": [] + "enabled_plugins": [], + "show_connection_manager_on_start": True, + "autoconnect": False, + "autoconnect_host_uri": None, + "autostart_localhost": False } class GtkUI: @@ -112,7 +116,6 @@ class GtkUI: self.preferences = Preferences() self.systemtray = SystemTray() self.statusbar = StatusBar() - self.connectionmanager = ConnectionManager() # Start the signal receiver self.signal_receiver = Signals() @@ -121,7 +124,9 @@ class GtkUI: self.plugins = PluginManager(self) # Show the connection manager - self.connectionmanager.show() + self.connectionmanager = ConnectionManager() + if config["show_connection_manager_on_start"]: + self.connectionmanager.show() # Start the gtk main loop gtk.gdk.threads_init() From 8f592b63302f6d65625db3a1bde8ade9b114c371 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 27 Oct 2007 04:35:09 +0000 Subject: [PATCH 0230/1009] Fix some exceptions. --- deluge/main.py | 2 +- deluge/ui/gtkui/gtkui.py | 2 +- deluge/ui/signalreceiver.py | 6 +++++- deluge/ui/ui.py | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/deluge/main.py b/deluge/main.py index 4a6e4dcc3..3709eb729 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -58,7 +58,7 @@ def start_ui(): from deluge.ui.ui import UI log.info("Starting ui..") - UI() + UI(args) def start_daemon(): """Entry point for daemon script""" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index f5b5f06b7..bf44caf06 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -85,7 +85,7 @@ DEFAULT_PREFS = { } class GtkUI: - def __init__(self): + def __init__(self, args): # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') locale.bindtextdomain("deluge", diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index b3a8a1938..6def2edbc 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -93,7 +93,11 @@ class SignalReceiver( # shutdowns.. This is because handle_request() is a blocking call. receiver = xmlrpclib.ServerProxy("http://localhost:" + str(self.port), allow_none=True) - receiver.emit_signal("shutdown", None) + try: + receiver.emit_signal("shutdown", None) + except: + # We don't care about errors at this point + pass def run(self): """This gets called when we start the thread""" diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 6706c6a68..32b768ead 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -40,11 +40,11 @@ DEFAULT_PREFS = { } class UI: - def __init__(self): + def __init__(self, args): log.debug("UI init..") self.config = ConfigManager("ui.conf", DEFAULT_PREFS) if self.config["selected_ui"] == "gtk": log.info("Starting GtkUI..") from deluge.ui.gtkui.gtkui import GtkUI - ui = GtkUI() + ui = GtkUI(args) From ae81a28e6b0ea8bc9756941ac573c47b28a57034 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 28 Oct 2007 06:30:02 +0000 Subject: [PATCH 0231/1009] Add 'files' key to torrent status. --- deluge/core/torrent.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 6c1b9f903..ff3ebcb4e 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -94,7 +94,19 @@ class Torrent: return 0.0 return ratio - + + def get_files(self): + """Returns a list of files this torrent contains""" + ret = [] + files = self.handle.torrent_info().files() + for file in files: + ret.append({ + 'path': file.path, + 'size': file.size, + 'offset': file.offset + }) + return ret + def get_status(self, keys): """Returns the status of the torrent based on the keys provided""" # Create the full dictionary @@ -150,7 +162,8 @@ class Torrent: "ratio": self.get_ratio(), "tracker": status.current_tracker, "tracker_status": self.tracker_status, - "save_path": self.save_path + "save_path": self.save_path, + "files": self.get_files() } # Create the desired status dictionary and return it From ff1391b8bf27325916181997ff931ce151d7b9e1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 28 Oct 2007 06:36:25 +0000 Subject: [PATCH 0232/1009] Send all torrent status keys if keys is []. --- deluge/core/torrent.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index ff3ebcb4e..5b01a1395 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -169,8 +169,11 @@ class Torrent: # Create the desired status dictionary and return it status_dict = {} - for key in keys: - if key in full_status: - status_dict[key] = full_status[key] + if len(keys) == 0: + status_dict = full_status + else: + for key in keys: + if key in full_status: + status_dict[key] = full_status[key] return status_dict From 5d3275de016a78a2bd974fa8b37bf79416cb1c2e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 31 Oct 2007 08:16:35 +0000 Subject: [PATCH 0233/1009] Some plugin updates. --- deluge/core/pluginmanager.py | 3 +- deluge/plugins/queue/queue/__init__.py | 3 ++ deluge/plugins/queue/queue/core.py | 62 +++++++++----------------- deluge/plugins/queue/queue/gtkui.py | 13 +----- deluge/ui/client.py | 14 ++++++ deluge/ui/gtkui/pluginmanager.py | 3 ++ deluge/ui/gtkui/preferences.py | 4 +- deluge/ui/gtkui/torrentview.py | 6 +-- 8 files changed, 49 insertions(+), 59 deletions(-) diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index cf0827375..c5313c0ce 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -56,6 +56,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): def register_status_field(self, field, function): """Register a new status field. This can be used in the same way the client requests other status information from core.""" + log.debug("Registering status field %s with PluginManager", field) self.status_fields[field] = function def get_status(self, torrent_id, fields): @@ -66,7 +67,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): status[field] = self.status_fields[field](torrent_id) except KeyError: log.warning("Status field %s is not registered with the\ - PluginManager.") + PluginManager.", field) return status def register_hook(self, hook, function): diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py index 8f6655e0e..852a80fda 100644 --- a/deluge/plugins/queue/queue/__init__.py +++ b/deluge/plugins/queue/queue/__init__.py @@ -40,6 +40,9 @@ class CorePlugin: def __init__(self, plugin_manager): # Load the Core portion of the plugin self.core = Core(plugin_manager) + + def disable(self): + pass class GtkUIPlugin: def __init__(self, plugin_manager): diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index a82660b17..b69ab34f0 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -31,62 +31,52 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import dbus -import dbus.service -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) - from torrentqueue import TorrentQueue from deluge.log import LOG as log -class Core(dbus.service.Object): - def __init__(self, plugin, path="/org/deluge_torrent/Plugin/Queue"): +class Core: + def __init__(self, plugin): # Get the pluginmanager reference self.plugin = plugin - # Setup DBUS - bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", - bus=dbus.SessionBus()) - - dbus.service.Object.__init__(self, bus_name, path) - # Instantiate the TorrentQueue object self.queue = TorrentQueue() # Register core hooks - self.plugin.register_hook("post_torrent_add", self.post_torrent_add) + self.plugin.register_hook("post_torrent_add", self._post_torrent_add) self.plugin.register_hook("post_torrent_remove", - self.post_torrent_remove) + self._post_torrent_remove) # Register the 'queue' status field - self.plugin.register_status_field("queue", self.status_field_queue) + self.plugin.register_status_field("queue", self._status_field_queue) log.info("Queue Core plugin initialized..") + def disable(self): + pass + def shutdown(self): # Save the queue state self.queue.save_state() - + ## Hooks for core ## - def post_torrent_add(self, torrent_id): + def _post_torrent_add(self, torrent_id): if torrent_id is not None: self.queue.append(torrent_id) - def post_torrent_remove(self, torrent_id): + def _post_torrent_remove(self, torrent_id): if torrent_id is not None: self.queue.remove(torrent_id) ## Status field function ## - def status_field_queue(self, torrent_id): + def _status_field_queue(self, torrent_id): try: return self.queue[torrent_id]+1 except TypeError: return None ## Queueing functions ## - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_top(self, torrent_id): + def export_queue_top(self, torrent_id): log.debug("Attempting to queue %s to top", torrent_id) try: # If the queue method returns True, then we should emit a signal @@ -96,9 +86,7 @@ class Core(dbus.service.Object): log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_up(self, torrent_id): + def export_queue_up(self, torrent_id): log.debug("Attempting to queue %s to up", torrent_id) try: # If the queue method returns True, then we should emit a signal @@ -107,10 +95,8 @@ class Core(dbus.service.Object): except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_down(self, torrent_id): + + def export_queue_down(self, torrent_id): log.debug("Attempting to queue %s to down", torrent_id) try: # If the queue method returns True, then we should emit a signal @@ -120,9 +106,7 @@ class Core(dbus.service.Object): log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="") - def queue_bottom(self, torrent_id): + def export_queue_bottom(self, torrent_id): log.debug("Attempting to queue %s to bottom", torrent_id) try: # If the queue method returns True, then we should emit a signal @@ -132,24 +116,18 @@ class Core(dbus.service.Object): log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="", out_signature="as") - def get_queue_list(self): + def export_get_queue_list(self): """Returns the queue list. """ log.debug("Getting queue list") return self.queue.queue - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge.Queue", - in_signature="s", out_signature="i") - def get_position(self, torrent_id): + def export_get_position(self, torrent_id): """Returns the queue position of torrent_id""" log.debug("Getting queue position for %s", torrent_id) return self.queue[torrent_id] ## Signals ## - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge.Queue", - signature="") - def torrent_queue_changed(self): + def _torrent_queue_changed(self): """Emitted when a torrent queue position is changed""" log.debug("torrent_queue_changed signal emitted") diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 30e301d60..e9600df8a 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -31,10 +31,6 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import dbus -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) - import pkg_resources import gtk.glade import gettext @@ -58,11 +54,6 @@ class GtkUI: "deluge", "i18n")) log.debug("Queue GtkUI plugin initalized..") self.plugin = plugin_manager - # Get a reference to the core portion of the plugin - bus = dbus.SessionBus() - proxy = bus.get_object("org.deluge_torrent.Deluge", - "/org/deluge_torrent/Plugin/Queue") - self.core = dbus.Interface(proxy, "org.deluge_torrent.Deluge.Queue") # Get the queue menu from the glade file menu_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", @@ -81,8 +72,8 @@ class GtkUI: menu = menu_glade.get_widget("menu_queue") # Connect to the 'torrent_queue_changed' signal - self.core.connect_to_signal("torrent_queue_changed", - self.torrent_queue_changed_signal) + #self.core.connect_to_signal("torrent_queue_changed", + # self.torrent_queue_changed_signal) # Get the torrentview component from the plugin manager self.torrentview = self.plugin.get_torrentview() diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 08bf18277..703a3f0e1 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -298,6 +298,20 @@ def get_num_connections(): set_core_uri(None) num_connections = 0 return num_connections + +def enable_plugin(plugin): + try: + return get_core().enable_plugin(plugin) + except (AttributeError, socket.error): + set_core_uri(None) + return + +def disable_plugin(plugin): + try: + return get_core().disable_plugin(plugin) + except (AttributeError, socket.error): + set_core_uri(None) + return def open_url_in_browser(url): """Opens link in the desktop's default browser""" diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 990beb598..3d9bbfd12 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -46,6 +46,9 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): # Register a callback with the client client.connect_on_new_core(self.start) + deluge.pluginmanagerbase.PluginManagerBase.__init__( + self, "gtkui.conf", "deluge.plugin.gtkui") + def start(self): """Start the plugin manager""" # Update the enabled_plugins from the core diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index baeb3d151..4acb1a0d1 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -437,9 +437,9 @@ class Preferences(component.Component): value = self.plugin_liststore.get_value(row, 1) self.plugin_liststore.set_value(row, 1, not value) if not value: - functions.enable_plugin(name) + client.enable_plugin(name) else: - functions.disable_plugin(name) + client.disable_plugin(name) def on_plugin_selection_changed(self, treeselection): log.debug("on_plugin_selection_changed") diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 05f92de36..162cd824a 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -233,9 +233,9 @@ class TorrentView(listview.ListView, component.Component): model.set_value(row, column_index, status[self.columns[column].status_field[0]]) - except TypeError: - log.warning("Unable to update column %s with value: %s", - column, status[self.columns[column].status_field[0]]) + except (TypeError, KeyError): + log.warning("Unable to update column %s", + column) else: # We have more than 1 liststore column to update for index in column_index: From c11e61355b6782a9993452e1f7cf40fbfeb54c2b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 2 Nov 2007 02:40:44 +0000 Subject: [PATCH 0234/1009] Add testing plugin. Plugins should now enable/disable properly and save enabled/disabled state. --- deluge/core/core.py | 4 +- deluge/pluginmanagerbase.py | 30 +++++++------- deluge/plugins/__init__.py | 0 deluge/plugins/init.py | 9 +++++ deluge/plugins/testp/setup.py | 52 +++++++++++++++++++++++++ deluge/plugins/testp/testp/__init__.py | 54 ++++++++++++++++++++++++++ deluge/plugins/testp/testp/core.py | 44 +++++++++++++++++++++ deluge/plugins/testp/testp/gtkui.py | 43 ++++++++++++++++++++ deluge/plugins/testp/testp/ui.py | 44 +++++++++++++++++++++ deluge/ui/client.py | 10 +++-- deluge/ui/gtkui/gtkui.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 21 +++------- deluge/ui/gtkui/preferences.py | 2 + 13 files changed, 280 insertions(+), 35 deletions(-) create mode 100644 deluge/plugins/__init__.py create mode 100644 deluge/plugins/init.py create mode 100644 deluge/plugins/testp/setup.py create mode 100644 deluge/plugins/testp/testp/__init__.py create mode 100644 deluge/plugins/testp/testp/core.py create mode 100644 deluge/plugins/testp/testp/gtkui.py create mode 100644 deluge/plugins/testp/testp/ui.py diff --git a/deluge/core/core.py b/deluge/core/core.py index 18283052f..449d6c3f4 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -375,9 +375,11 @@ class Core( def export_enable_plugin(self, plugin): self.plugins.enable_plugin(plugin) - + return None + def export_disable_plugin(self, plugin): self.plugins.disable_plugin(plugin) + return None # Signals def torrent_added(self, torrent_id): diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index ae41be976..dadfb1759 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -90,34 +90,38 @@ class PluginManagerBase: self.available_plugins = [] for name in self.pkg_env: - pkg_name = str(self.pkg_env[name][0]).split()[0] + pkg_name = str(self.pkg_env[name][0]).split()[0].replace("-", " ") pkg_version = str(self.pkg_env[name][0]).split()[1] - log.debug("Found plugin: %s %s", pkg_name, pkg_version) self.available_plugins.append(pkg_name) - def enable_plugin(self, name): + def enable_plugin(self, plugin_name): """Enables a plugin""" - if name not in self.available_plugins: + if plugin_name not in self.available_plugins: log.warning("Cannot enable non-existant plugin %s", name) return - - egg = self.pkg_env[name][0] + + plugin_name = plugin_name.replace(" ", "-") + egg = self.pkg_env[plugin_name][0] egg.activate() for name in egg.get_entry_map(self.entry_name): entry_point = egg.get_entry_info(self.entry_name, name) cls = entry_point.load() instance = cls(self) - self.plugins[name] = instance - log.info("Plugin %s enabled..", name) + plugin_name = plugin_name.replace("-", " ") + self.plugins[plugin_name] = instance + if plugin_name not in self.config["enabled_plugins"]: + self.config["enabled_plugins"].append(plugin_name) + log.info("Plugin %s enabled..", plugin_name) def disable_plugin(self, name): """Disables a plugin""" self.plugins[name].disable() - - del self.plugins[name] -# except: - # log.warning("Unable to disable non-existant plugin %s", name) - + try: + del self.plugins[name] + self.config["enabled_plugins"].remove(plugin_name) + except KeyError: + log.warning("Plugin %s is not enabled..", name) + log.info("Plugin %s disabled..", name) diff --git a/deluge/plugins/__init__.py b/deluge/plugins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/plugins/init.py b/deluge/plugins/init.py new file mode 100644 index 000000000..5891c4616 --- /dev/null +++ b/deluge/plugins/init.py @@ -0,0 +1,9 @@ +class PluginBase: + def __init__(self): + pass + + def enable(self): + pass + def disable(self): + pass + diff --git a/deluge/plugins/testp/setup.py b/deluge/plugins/testp/setup.py new file mode 100644 index 000000000..2d972c15d --- /dev/null +++ b/deluge/plugins/testp/setup.py @@ -0,0 +1,52 @@ +# setup.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +""" +Test plugin +""" + +from setuptools import setup + +__author__ = "Andrew Resch" + +setup( + name="Test Plugin", + version="1.0", + description=__doc__, + author=__author__, + packages=["testp"], + package_data = {"testp": ["glade/*.glade"]}, + entry_points=""" + [deluge.plugin.core] + Testp = testp:CorePlugin + [deluge.plugin.gtkui] + Testp = testp:GtkUIPlugin + """ +) diff --git a/deluge/plugins/testp/testp/__init__.py b/deluge/plugins/testp/testp/__init__.py new file mode 100644 index 000000000..19ace0e75 --- /dev/null +++ b/deluge/plugins/testp/testp/__init__.py @@ -0,0 +1,54 @@ +# +# __init__.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + +from deluge.plugins.init import PluginBase + +class CorePlugin(PluginBase): + def __init__(self, plugin_manager): + # Load the Core portion of the plugin + try: + from core import Core + self.core = Core() + except: + pass + +class GtkUIPlugin(PluginBase): + def __init__(self, plugin_manager): + # Load the GtkUI portion of the plugin + try: + from gtkui import GtkUI + self.gtkui = GtkUI() + except: + pass diff --git a/deluge/plugins/testp/testp/core.py b/deluge/plugins/testp/testp/core.py new file mode 100644 index 000000000..2f6fb4bc8 --- /dev/null +++ b/deluge/plugins/testp/testp/core.py @@ -0,0 +1,44 @@ +# +# core.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + +class Core: + def __init__(self): + pass + + def enable(self): + pass + + def disable(self): + pass diff --git a/deluge/plugins/testp/testp/gtkui.py b/deluge/plugins/testp/testp/gtkui.py new file mode 100644 index 000000000..9beb6e76d --- /dev/null +++ b/deluge/plugins/testp/testp/gtkui.py @@ -0,0 +1,43 @@ +# +# gtkui.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log +import ui + +class GtkUI(ui.UI): + def __init__(self): + ui.UI.__init__(self) + log.debug("gtkui plugin initialized..") + + + diff --git a/deluge/plugins/testp/testp/ui.py b/deluge/plugins/testp/testp/ui.py new file mode 100644 index 000000000..babd15a3e --- /dev/null +++ b/deluge/plugins/testp/testp/ui.py @@ -0,0 +1,44 @@ +# +# ui.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + +class UI: + def __init__(self): + pass + + def enable(self): + pass + + def disable(self): + pass diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 703a3f0e1..6578684ee 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -217,6 +217,10 @@ def get_torrent_status(torrent_id, keys): except (AttributeError, socket.error): set_core_uri(None) return {} + + if status == None: + return {} + return pickle.loads(status.data) def get_session_state(): @@ -301,17 +305,15 @@ def get_num_connections(): def enable_plugin(plugin): try: - return get_core().enable_plugin(plugin) + get_core().enable_plugin(plugin) except (AttributeError, socket.error): set_core_uri(None) - return def disable_plugin(plugin): try: - return get_core().disable_plugin(plugin) + get_core().disable_plugin(plugin) except (AttributeError, socket.error): set_core_uri(None) - return def open_url_in_browser(url): """Opens link in the desktop's default browser""" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index bf44caf06..9a5b0ce27 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -121,7 +121,7 @@ class GtkUI: self.signal_receiver = Signals() # Initalize the plugins - self.plugins = PluginManager(self) + self.plugins = PluginManager() # Show the connection manager self.connectionmanager = ConnectionManager() diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 3d9bbfd12..b5084226a 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -37,17 +37,11 @@ import deluge.ui.client as client from deluge.configmanager import ConfigManager from deluge.log import LOG as log -class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): - def __init__(self, gtkui): - +class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, + component.Component): + def __init__(self): + component.Component.__init__(self, "PluginManager") self.config = ConfigManager("gtkui.conf") - self._gtkui = gtkui - - # Register a callback with the client - client.connect_on_new_core(self.start) - - deluge.pluginmanagerbase.PluginManagerBase.__init__( - self, "gtkui.conf", "deluge.plugin.gtkui") def start(self): """Start the plugin manager""" @@ -58,29 +52,24 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): self.config["enabled_plugins"] = enabled_plugins deluge.pluginmanagerbase.PluginManagerBase.__init__( - self, "gtkui.conf", "deluge.plugin.ui.gtk") + self, "gtkui.conf", "deluge.plugin.gtkui") def get_torrentview(self): """Returns a reference to the torrentview component""" - #return self._gtkui.mainwindow.torrentview return component.get("TorrentView") def get_toolbar(self): """Returns a reference to the toolbar component""" -# return self._gtkui.mainwindow.toolbar return component.get("ToolBar") def get_menubar(self): """Returns a reference to the menubar component""" - # return self._gtkui.mainwindow.menubar return component.get("MenuBar") def get_torrentmenu(self): """Returns a reference to the torrentmenu component""" -# return self._gtkui.mainwindow.menubar.torrentmenu return component.get("MenuBar").torrentmenu def get_selected_torrents(self): """Returns a list of the selected torrent_ids""" -# return self._gtkui.mainwindow.torrentview.get_selected_torrents() return component.get("TorrentView").get_selected_torrents() diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 4acb1a0d1..52d098503 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -438,8 +438,10 @@ class Preferences(component.Component): self.plugin_liststore.set_value(row, 1, not value) if not value: client.enable_plugin(name) + component.get("PluginManager").enable_plugin(name) else: client.disable_plugin(name) + component.get("PluginManager").disable_plugin(name) def on_plugin_selection_changed(self, treeselection): log.debug("on_plugin_selection_changed") From eacc6dd08f58395c7ea7c0cb8a2f94a67e13e139 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 3 Nov 2007 07:24:45 +0000 Subject: [PATCH 0235/1009] Queue plugin update. Plugin system updates. --- deluge/core/pluginmanager.py | 17 +++- deluge/pluginmanagerbase.py | 6 +- deluge/plugins/init.py | 48 ++++++++- deluge/plugins/queue/queue/__init__.py | 30 +++--- deluge/plugins/queue/queue/core.py | 25 +++-- deluge/plugins/queue/queue/gtkui.py | 133 +++++++------------------ deluge/plugins/queue/queue/ui.py | 116 +++++++++++++++++++++ deluge/plugins/queue/setup.py | 2 +- deluge/plugins/testp/testp/__init__.py | 8 +- deluge/plugins/testp/testp/core.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 40 +++++--- deluge/ui/gtkui/toolbar.py | 11 +- 12 files changed, 291 insertions(+), 147 deletions(-) create mode 100644 deluge/plugins/queue/queue/ui.py diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index c5313c0ce..c9ee11b5f 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -59,6 +59,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): log.debug("Registering status field %s with PluginManager", field) self.status_fields[field] = function + def deregister_status_field(self, field): + """Deregisters a status field""" + log.debug("Deregistering status field %s with PluginManager", field) + try: + del self.status_fields[field] + except: + log.warning("Unable to deregister status field %s", field) + def get_status(self, torrent_id, fields): """Return the value of status fields for the selected torrent_id.""" status = {} @@ -76,7 +84,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): self.hooks[hook].append(function) except KeyError: log.warning("Plugin attempting to register invalid hook.") - + + def deregister_hook(self, hook, function): + """Deregisters a hook function""" + try: + self.hooks[hook].remove(function) + except: + log.warning("Unable to deregister hook %s", hook) + def run_post_torrent_add(self, torrent_id): """This hook is run after a torrent has been added to the session.""" log.debug("run_post_torrent_add") diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index dadfb1759..b5da2ff7c 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -108,6 +108,7 @@ class PluginManagerBase: entry_point = egg.get_entry_info(self.entry_name, name) cls = entry_point.load() instance = cls(self) + instance.enable() plugin_name = plugin_name.replace("-", " ") self.plugins[plugin_name] = instance if plugin_name not in self.config["enabled_plugins"]: @@ -116,11 +117,10 @@ class PluginManagerBase: def disable_plugin(self, name): """Disables a plugin""" - - self.plugins[name].disable() try: + self.plugins[name].disable() del self.plugins[name] - self.config["enabled_plugins"].remove(plugin_name) + self.config["enabled_plugins"].remove(name) except KeyError: log.warning("Plugin %s is not enabled..", name) diff --git a/deluge/plugins/init.py b/deluge/plugins/init.py index 5891c4616..1221b6b74 100644 --- a/deluge/plugins/init.py +++ b/deluge/plugins/init.py @@ -1,9 +1,51 @@ +# +# init.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + class PluginBase: def __init__(self): - pass + self.plugin = None def enable(self): - pass + try: + self.plugin.enable() + except Exception, e: + log.warning("Unable to enable plugin: %s", e) + def disable(self): - pass + try: + self.plugin.disable() + except Exception, e: + log.warning("Unable to disable plugin: %s", e) diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py index 852a80fda..0d69a2054 100644 --- a/deluge/plugins/queue/queue/__init__.py +++ b/deluge/plugins/queue/queue/__init__.py @@ -31,20 +31,24 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -from core import Core -from gtkui import GtkUI - from deluge.log import LOG as log -class CorePlugin: - def __init__(self, plugin_manager): - # Load the Core portion of the plugin - self.core = Core(plugin_manager) - - def disable(self): - pass +from deluge.plugins.init import PluginBase -class GtkUIPlugin: - def __init__(self, plugin_manager): +class CorePlugin(PluginBase): + def __init__(self, plugin_api): + # Load the Core portion of the plugin + try: + from core import Core + self.plugin = Core(plugin_api) + except Exception, e: + log.debug("Did not load a Core plugin: %s", e) + +class GtkUIPlugin(PluginBase): + def __init__(self, plugin_api): # Load the GtkUI portion of the plugin - self.gtkui = GtkUI(plugin_manager) + try: + from gtkui import GtkUI + self.plugin = GtkUI(plugin_api) + except Exception, e: + log.debug("Did not load a GtkUI plugin: %s", e) diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index b69ab34f0..ca4d67b65 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -35,10 +35,12 @@ from torrentqueue import TorrentQueue from deluge.log import LOG as log class Core: - def __init__(self, plugin): - # Get the pluginmanager reference - self.plugin = plugin + def __init__(self, plugin_api): + # Get the plugin_api + self.plugin = plugin_api + log.info("Queue Core plugin initialized..") + def enable(self): # Instantiate the TorrentQueue object self.queue = TorrentQueue() @@ -50,14 +52,21 @@ class Core: # Register the 'queue' status field self.plugin.register_status_field("queue", self._status_field_queue) - log.info("Queue Core plugin initialized..") + log.debug("Queue Core plugin enabled..") def disable(self): - pass - - def shutdown(self): - # Save the queue state + # Save queue state self.queue.save_state() + # Delete the queue + del self.queue + self.queue = None + # De-register hooks + self.plugin.deregister_hook("post_torrent_add", self._post_torrent_add) + self.plugin.deregister_hook("post_torrent_remove", + self._post_torrent_remove) + + # De-register status fields + self.plugin.deregister_status_field("queue") ## Hooks for core ## def _post_torrent_add(self, torrent_id): diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index e9600df8a..8722b60c5 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -33,40 +33,29 @@ import pkg_resources import gtk.glade -import gettext -import locale from deluge.log import LOG as log +import ui -class GtkUI: - def __init__(self, plugin_manager): - # Initialize gettext - locale.setlocale(locale.LC_MESSAGES, '') - locale.bindtextdomain("deluge", - pkg_resources.resource_filename( - "deluge", "i18n")) - locale.textdomain("deluge") - gettext.bindtextdomain("deluge", - pkg_resources.resource_filename( - "deluge", "i18n")) - gettext.textdomain("deluge") - gettext.install("deluge", - pkg_resources.resource_filename( - "deluge", "i18n")) +class GtkUI(ui.UI): + def __init__(self, plugin_api): + log.debug("Calling UI init") + # Call UI constructor + ui.UI.__init__(self, plugin_api) log.debug("Queue GtkUI plugin initalized..") - self.plugin = plugin_manager - + + def load_interface(self): # Get the queue menu from the glade file menu_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", "glade/queuemenu.glade")) menu_glade.signal_autoconnect({ "on_menuitem_queuetop_activate": \ - self.on_menuitem_queuetop_activate, - "on_menuitem_queueup_activate": self.on_menuitem_queueup_activate, + self.on_queuetop_activate, + "on_menuitem_queueup_activate": self.on_queueup_activate, "on_menuitem_queuedown_activate": \ - self.on_menuitem_queuedown_activate, + self.on_queuedown_activate, "on_menuitem_queuebottom_activate": \ - self.on_menuitem_queuebottom_activate + self.on_queuebottom_activate }) menu = menu_glade.get_widget("menu_queue") @@ -76,93 +65,45 @@ class GtkUI: # self.torrent_queue_changed_signal) # Get the torrentview component from the plugin manager - self.torrentview = self.plugin.get_torrentview() + #self.torrentview = self.plugin.get_torrentview() # Add the '#' column at the first position - self.torrentview.add_text_column("#", + #self.torrentview.add_text_column("#", + self.plugin.add_torrentview_text_column("#", col_type=int, position=0, status_field=["queue"]) # Update the new column right away - self.torrentview.update(["#"]) + self.update_interface() # Add a toolbar buttons - self.plugin.get_toolbar().add_separator() - self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-up", + self.toolbar_sep = self.plugin.add_toolbar_separator() + self.toolbutton_up = self.plugin.add_toolbar_button( + stock="gtk-go-up", label=_("Queue Up"), tooltip=_("Queue selected torrents up"), - callback=self.on_toolbutton_queueup_clicked) + callback=self.on_queueup_activate) - self.plugin.get_toolbar().add_toolbutton(stock="gtk-go-down", + self.toolbutton_down = self.plugin.add_toolbar_button( + stock="gtk-go-down", label=_("Queue Down"), tooltip=_("Queue selected torrents down"), - callback=self.on_toolbutton_queuedown_clicked) + callback=self.on_queuedown_activate) # Add the queue menu to the torrent menu - queue_menuitem = gtk.ImageMenuItem("Queue") + self.queue_menuitem = gtk.ImageMenuItem("Queue") queue_image = gtk.Image() queue_image.set_from_stock(gtk.STOCK_SORT_ASCENDING, gtk.ICON_SIZE_MENU) - queue_menuitem.set_image(queue_image) - queue_menuitem.set_submenu(menu) - queue_menuitem.show_all() - self.plugin.get_torrentmenu().append(queue_menuitem) - - ## Menu callbacks ## - def on_menuitem_queuetop_activate(self, data=None): - log.debug("on_menuitem_queuetop_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - self.core.queue_top(torrent_id) - return - - def on_menuitem_queueup_activate(self, data=None): - log.debug("on_menuitem_queueup_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - self.core.queue_up(torrent_id) - return - - def on_menuitem_queuedown_activate(self, data=None): - log.debug("on_menuitem_queuedown_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - self.core.queue_down(torrent_id) - return - - def on_menuitem_queuebottom_activate(self, data=None): - log.debug("on_menuitem_queuebottom_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - self.core.queue_bottom(torrent_id) - return - - ## Toolbutton callbacks ## - def on_toolbutton_queuedown_clicked(self, widget): - log.debug("on_toolbutton_queuedown_clicked") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - self.core.queue_down(torrent_id) - return - - def on_toolbutton_queueup_clicked(self, widget): - log.debug("on_toolbutton_queueup_clicked") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - self.core.queue_up(torrent_id) - return + self.queue_menuitem.set_image(queue_image) + self.queue_menuitem.set_submenu(menu) + self.queue_menuitem.show_all() + self.plugin.add_torrentmenu_menu(self.queue_menuitem) - ## Signals ## - def torrent_queue_changed_signal(self): - """This function is called whenever we receive a 'torrent_queue_changed' - signal from the core plugin. - """ - log.debug("torrent_queue_changed signal received..") - # We only need to update the queue column - self.torrentview.update(["#"]) - return - + def unload_interface(self): + self.plugin.remove_torrentmenu_menu(self.queue_menuitem) + self.plugin.remove_toolbar_button(self.toolbar_sep) + self.plugin.remove_toolbar_button(self.toolbutton_up) + self.plugin.remove_toolbar_button(self.toolbutton_down) + self.plugin.remove_torrentview_column("#") + + def update_interface(self): + self.plugin.update_torrent_view(["#"]) diff --git a/deluge/plugins/queue/queue/ui.py b/deluge/plugins/queue/queue/ui.py new file mode 100644 index 000000000..7a188c371 --- /dev/null +++ b/deluge/plugins/queue/queue/ui.py @@ -0,0 +1,116 @@ +# +# ui.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gettext +import locale +import pkg_resources +from deluge.log import LOG as log + +class UI: + def __init__(self, plugin_api): + self.plugin = plugin_api + # Initialize gettext + locale.setlocale(locale.LC_MESSAGES, '') + locale.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + locale.textdomain("deluge") + gettext.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + gettext.textdomain("deluge") + gettext.install("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + + def enable(self): + log.debug("Enabling UI plugin") + # Load the interface and connect the callbacks + self.load_interface() + + def disable(self): + self.unload_interface() + + def load_interface(self): + pass + + def unload_interface(self): + pass + + def update_interface(self): + pass + + ## Menu callbacks ## + def on_queuetop_activate(self, data=None): + log.debug("on_menuitem_queuetop_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_top(torrent_id) + return + + def on_queueup_activate(self, data=None): + log.debug("on_menuitem_queueup_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_up(torrent_id) + return + + def on_queuedown_activate(self, data=None): + log.debug("on_menuitem_queuedown_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_down(torrent_id) + return + + def on_queuebottom_activate(self, data=None): + log.debug("on_menuitem_queuebottom_activate") + # Get the selected torrents + torrent_ids = self.plugin.get_selected_torrents() + for torrent_id in torrent_ids: + self.core.queue_bottom(torrent_id) + return + + ## Signals ## + def torrent_queue_changed_signal(self): + """This function is called whenever we receive a 'torrent_queue_changed' + signal from the core plugin. + """ + log.debug("torrent_queue_changed signal received..") + # We only need to update the queue column +# self.torrentview.update(["#"]) + self.update_interface() + return + diff --git a/deluge/plugins/queue/setup.py b/deluge/plugins/queue/setup.py index 4aa3ceec9..9f7231642 100644 --- a/deluge/plugins/queue/setup.py +++ b/deluge/plugins/queue/setup.py @@ -46,7 +46,7 @@ setup( entry_points=""" [deluge.plugin.core] Queue = queue:CorePlugin - [deluge.plugin.ui.gtk] + [deluge.plugin.gtkui] Queue = queue:GtkUIPlugin """ ) diff --git a/deluge/plugins/testp/testp/__init__.py b/deluge/plugins/testp/testp/__init__.py index 19ace0e75..6a417f2b3 100644 --- a/deluge/plugins/testp/testp/__init__.py +++ b/deluge/plugins/testp/testp/__init__.py @@ -36,19 +36,19 @@ from deluge.log import LOG as log from deluge.plugins.init import PluginBase class CorePlugin(PluginBase): - def __init__(self, plugin_manager): + def __init__(self, plugin_api): # Load the Core portion of the plugin try: from core import Core - self.core = Core() + self.plugin = Core(plugin_api) except: pass class GtkUIPlugin(PluginBase): - def __init__(self, plugin_manager): + def __init__(self, plugin_api): # Load the GtkUI portion of the plugin try: from gtkui import GtkUI - self.gtkui = GtkUI() + self.plugin = GtkUI() except: pass diff --git a/deluge/plugins/testp/testp/core.py b/deluge/plugins/testp/testp/core.py index 2f6fb4bc8..b49b6a84d 100644 --- a/deluge/plugins/testp/testp/core.py +++ b/deluge/plugins/testp/testp/core.py @@ -34,7 +34,7 @@ from deluge.log import LOG as log class Core: - def __init__(self): + def __init__(self, plugin_api): pass def enable(self): diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index b5084226a..ce56e33af 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -53,23 +53,33 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "gtkui.conf", "deluge.plugin.gtkui") - - def get_torrentview(self): - """Returns a reference to the torrentview component""" - return component.get("TorrentView") - def get_toolbar(self): - """Returns a reference to the toolbar component""" - return component.get("ToolBar") - - def get_menubar(self): - """Returns a reference to the menubar component""" - return component.get("MenuBar") - - def get_torrentmenu(self): - """Returns a reference to the torrentmenu component""" - return component.get("MenuBar").torrentmenu + ## Plugin functions.. will likely move to own class.. + def add_torrentview_text_column(self, *args, **kwargs): + return component.get("TorrentView").add_text_column(*args, **kwargs) + + def remove_torrentview_column(self, *args): + return component.get("TorrentView").remove_column(*args) + + def add_toolbar_separator(self): + return component.get("ToolBar").add_separator() + + def add_toolbar_button(self, *args, **kwargs): + return component.get("ToolBar").add_toolbutton(*args, **kwargs) + + def remove_toolbar_button(self, *args): + return component.get("ToolBar").remove(*args) + + def add_torrentmenu_menu(self, *args): + return component.get("MenuBar").torrentmenu.append(*args) + + def remove_torrentmenu_menu(self, *args): + return component.get("MenuBar").torrentmenu.remove(*args) + + def update_torrent_view(self, *args): + return component.get("TorrentView").update(*args) + def get_selected_torrents(self): """Returns a list of the selected torrent_ids""" return component.get("TorrentView").get_selected_torrents() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index bda9e21e5..a4f4bb716 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -108,7 +108,7 @@ class ToolBar(component.Component): # Show the new toolbutton toolbutton.show() - return + return toolbutton def add_separator(self, position=None): """Adds a separator toolitem""" @@ -118,8 +118,15 @@ class ToolBar(component.Component): else: # Append the separator self.toolbar.insert(sep, -1) - return + + sep.show() + return sep + + def remove(self, widget): + """Removes a widget from the toolbar""" + self.toolbar.remove(widget) + ### Callbacks ### def on_toolbutton_add_clicked(self, data): log.debug("on_toolbutton_add_clicked") From cfc05729eeb9465410bb430ce5d2e76795062594 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 4 Nov 2007 23:54:38 +0000 Subject: [PATCH 0236/1009] Queue plugin updates. --- deluge/core/core.py | 2 +- deluge/core/pluginmanager.py | 7 ++++++- deluge/pluginmanagerbase.py | 2 +- deluge/plugins/queue/queue/__init__.py | 8 ++++---- deluge/plugins/queue/queue/core.py | 16 ++++++--------- deluge/plugins/queue/queue/gtkui.py | 8 ++++---- deluge/plugins/queue/queue/ui.py | 28 ++++++++++++++++++-------- deluge/plugins/testp/testp/__init__.py | 6 +++--- 8 files changed, 45 insertions(+), 32 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 449d6c3f4..be86f36d7 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -179,7 +179,7 @@ class Core( self.torrents = TorrentManager(self.session, self.alerts) # Load plugins - self.plugins = PluginManager() + self.plugins = PluginManager(self) # Register alert handlers self.alerts.register_handler("torrent_paused_alert", diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index c9ee11b5f..c4b3e97da 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -40,7 +40,8 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): """PluginManager handles the loading of plugins and provides plugins with functions to access parts of the core.""" - def __init__(self): + def __init__(self, core): + self.core = core # Set up the hooks dictionary self.hooks = { "post_torrent_add": [], @@ -53,6 +54,10 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "core.conf", "deluge.plugin.core") + def get_core(self): + """Returns a reference to the core""" + return self.core + def register_status_field(self, field, function): """Register a new status field. This can be used in the same way the client requests other status information from core.""" diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index b5da2ff7c..8746203da 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -107,7 +107,7 @@ class PluginManagerBase: for name in egg.get_entry_map(self.entry_name): entry_point = egg.get_entry_info(self.entry_name, name) cls = entry_point.load() - instance = cls(self) + instance = cls(self, plugin_name.replace("-", "_")) instance.enable() plugin_name = plugin_name.replace("-", " ") self.plugins[plugin_name] = instance diff --git a/deluge/plugins/queue/queue/__init__.py b/deluge/plugins/queue/queue/__init__.py index 0d69a2054..37219e170 100644 --- a/deluge/plugins/queue/queue/__init__.py +++ b/deluge/plugins/queue/queue/__init__.py @@ -36,19 +36,19 @@ from deluge.log import LOG as log from deluge.plugins.init import PluginBase class CorePlugin(PluginBase): - def __init__(self, plugin_api): + def __init__(self, plugin_api, plugin_name): # Load the Core portion of the plugin try: from core import Core - self.plugin = Core(plugin_api) + self.plugin = Core(plugin_api, plugin_name) except Exception, e: log.debug("Did not load a Core plugin: %s", e) class GtkUIPlugin(PluginBase): - def __init__(self, plugin_api): + def __init__(self, plugin_api, plugin_name): # Load the GtkUI portion of the plugin try: from gtkui import GtkUI - self.plugin = GtkUI(plugin_api) + self.plugin = GtkUI(plugin_api, plugin_name) except Exception, e: log.debug("Did not load a GtkUI plugin: %s", e) diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index ca4d67b65..12f354b02 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -33,13 +33,9 @@ from torrentqueue import TorrentQueue from deluge.log import LOG as log +from deluge.plugins.corepluginbase import CorePluginBase -class Core: - def __init__(self, plugin_api): - # Get the plugin_api - self.plugin = plugin_api - log.info("Queue Core plugin initialized..") - +class Core(CorePluginBase): def enable(self): # Instantiate the TorrentQueue object self.queue = TorrentQueue() @@ -90,7 +86,7 @@ class Core: try: # If the queue method returns True, then we should emit a signal if self.queue.top(torrent_id): - self.torrent_queue_changed() + self._torrent_queue_changed() except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) @@ -100,7 +96,7 @@ class Core: try: # If the queue method returns True, then we should emit a signal if self.queue.up(torrent_id): - self.torrent_queue_changed() + self._torrent_queue_changed() except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) @@ -110,7 +106,7 @@ class Core: try: # If the queue method returns True, then we should emit a signal if self.queue.down(torrent_id): - self.torrent_queue_changed() + self._torrent_queue_changed() except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) @@ -120,7 +116,7 @@ class Core: try: # If the queue method returns True, then we should emit a signal if self.queue.bottom(torrent_id): - self.torrent_queue_changed() + self._torrent_queue_changed() except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 8722b60c5..ee0ef626e 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -37,10 +37,10 @@ from deluge.log import LOG as log import ui class GtkUI(ui.UI): - def __init__(self, plugin_api): + def __init__(self, plugin_api, plugin_name): log.debug("Calling UI init") # Call UI constructor - ui.UI.__init__(self, plugin_api) + ui.UI.__init__(self, plugin_api, plugin_name) log.debug("Queue GtkUI plugin initalized..") def load_interface(self): @@ -73,7 +73,7 @@ class GtkUI(ui.UI): position=0, status_field=["queue"]) # Update the new column right away - self.update_interface() + self.update() # Add a toolbar buttons self.toolbar_sep = self.plugin.add_toolbar_separator() @@ -105,5 +105,5 @@ class GtkUI(ui.UI): self.plugin.remove_toolbar_button(self.toolbutton_down) self.plugin.remove_torrentview_column("#") - def update_interface(self): + def update(self): self.plugin.update_torrent_view(["#"]) diff --git a/deluge/plugins/queue/queue/ui.py b/deluge/plugins/queue/queue/ui.py index 7a188c371..d50b8d7ca 100644 --- a/deluge/plugins/queue/queue/ui.py +++ b/deluge/plugins/queue/queue/ui.py @@ -34,10 +34,11 @@ import gettext import locale import pkg_resources +import deluge.ui.client as client from deluge.log import LOG as log class UI: - def __init__(self, plugin_api): + def __init__(self, plugin_api, plugin_name): self.plugin = plugin_api # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') @@ -67,7 +68,7 @@ class UI: def unload_interface(self): pass - def update_interface(self): + def update(self): pass ## Menu callbacks ## @@ -76,7 +77,10 @@ class UI: # Get the selected torrents torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: - self.core.queue_top(torrent_id) + try: + client.get_core().queue_queue_top(torrent_id) + except Exception, e: + log.debug("Unable to queue top torrent: %s", e) return def on_queueup_activate(self, data=None): @@ -84,7 +88,10 @@ class UI: # Get the selected torrents torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: - self.core.queue_up(torrent_id) + try: + client.get_core().queue_queue_up(torrent_id) + except Exception, e: + log.debug("Unable to queue up torrent: %s", e) return def on_queuedown_activate(self, data=None): @@ -92,7 +99,10 @@ class UI: # Get the selected torrents torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: - self.core.queue_down(torrent_id) + try: + client.get_core().queue_queue_down(torrent_id) + except Exception, e: + log.debug("Unable to queue down torrent: %s", e) return def on_queuebottom_activate(self, data=None): @@ -100,7 +110,10 @@ class UI: # Get the selected torrents torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: - self.core.queue_bottom(torrent_id) + try: + client.get_core().queue_queue_bottom(torrent_id) + except Exception, e: + log.debug("Unable to queue bottom torrent: %s", e) return ## Signals ## @@ -110,7 +123,6 @@ class UI: """ log.debug("torrent_queue_changed signal received..") # We only need to update the queue column -# self.torrentview.update(["#"]) - self.update_interface() + self.update() return diff --git a/deluge/plugins/testp/testp/__init__.py b/deluge/plugins/testp/testp/__init__.py index 6a417f2b3..0aeca2568 100644 --- a/deluge/plugins/testp/testp/__init__.py +++ b/deluge/plugins/testp/testp/__init__.py @@ -36,16 +36,16 @@ from deluge.log import LOG as log from deluge.plugins.init import PluginBase class CorePlugin(PluginBase): - def __init__(self, plugin_api): + def __init__(self, plugin_api, plugin_name): # Load the Core portion of the plugin try: from core import Core - self.plugin = Core(plugin_api) + self.plugin = Core(plugin_api, plugin_name) except: pass class GtkUIPlugin(PluginBase): - def __init__(self, plugin_api): + def __init__(self, plugin_api, plugin_name): # Load the GtkUI portion of the plugin try: from gtkui import GtkUI From a4b46d0607f11e60bc8c28bd456806a2a3b02cae Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 5 Nov 2007 03:05:45 +0000 Subject: [PATCH 0237/1009] Queue preferences glade file. --- .../queue/queue/glade/queueprefs.glade | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 deluge/plugins/queue/queue/glade/queueprefs.glade diff --git a/deluge/plugins/queue/queue/glade/queueprefs.glade b/deluge/plugins/queue/queue/glade/queueprefs.glade new file mode 100644 index 000000000..4817944a7 --- /dev/null +++ b/deluge/plugins/queue/queue/glade/queueprefs.glade @@ -0,0 +1,302 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue new torrents to top + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>General</b> + True + + + label_item + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 2 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + 2 + 3 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + 1 + 2 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active downloading: + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active seeding: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active torrents: + + + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Active Torrents</b> + True + + + label_item + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue newly finished torrents to bottom + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Stop seeding when share ratio reaches: + True + + + + False + False + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 0 0 100 0.10000000000000001 1 1 + 2 + True + + + False + False + 1 + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remove torrent when share ratio reached + True + + + + + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Seeding</b> + True + + + label_item + + + + + False + False + 2 + + + + + + From 094e132ace8966738ed35fbb3eb3d5645e433557 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 8 Nov 2007 07:54:56 +0000 Subject: [PATCH 0238/1009] Add Force-recheck. --- deluge/core/core.py | 4 ++ deluge/core/torrentmanager.py | 69 +++++++++++++++++++++++- deluge/plugins/queue/queue/gtkui.py | 6 ++- deluge/ui/client.py | 10 +++- deluge/ui/gtkui/glade/torrent_menu.glade | 44 +++++++++++++++ deluge/ui/gtkui/menubar.py | 27 +++++++--- deluge/ui/gtkui/pluginmanager.py | 5 +- 7 files changed, 154 insertions(+), 11 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index be86f36d7..67abf00c7 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -380,6 +380,10 @@ class Core( def export_disable_plugin(self, plugin): self.plugins.disable_plugin(plugin) return None + + def export_force_recheck(self, torrent_id): + """Forces a data recheck on torrent_id""" + return self.torrents.force_recheck(torrent_id) # Signals def torrent_added(self, torrent_id): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 222ddeebc..51e4181e1 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -235,8 +235,8 @@ class TorrentManager: try: # Remove from libtorrent session self.session.remove_torrent(self.torrents[torrent_id].handle, 0) - except RuntimeError, KeyError: - log.warning("Error removing torrent") + except (RuntimeError, KeyError), e: + log.warning("Error removing torrent: %s", e) return False try: @@ -303,6 +303,71 @@ class TorrentManager: return True + def force_recheck(self, torrent_id): + """Forces a re-check of the torrent's data""" + log.debug("Doing a forced recheck on %s", torrent_id) + paused = self.torrents[torrent_id].handle.status().paused + ### Check for .torrent file prior to removing and make a copy if needed + ### FIXME + + try: + # We start by removing it from the lt session + self.session.remove_torrent(self.torrents[torrent_id].handle, 0) + except (RuntimeError, KeyError), e: + log.warning("Error removing torrent: %s", e) + return False + # Remove the fastresume file if there + self.delete_fastresume(torrent_id) + + # Get the torrent data from the torrent file + filename = self.torrents[torrent_id].filename + try: + log.debug("Attempting to open %s for add.", filename) + _file = open( + os.path.join( + self.config["torrentfiles_location"], filename), "rb") + filedump = _file.read() + _file.close() + except IOError, e: + log.warning("Unable to open %s: e", filename, e) + return False + + # Bdecode the filedata + torrent_filedump = lt.bdecode(filedump) + handle = None + + # Next we re-add the torrent + # Make sure we have a valid download_location + if self.torrents[torrent_id].save_path is None: + self.torrents[torrent_id].save_path = \ + self.config["download_location"] + + # Make sure we are adding it with the correct allocation method. + if self.torrents[torrent_id].compact is None: + self.torrents[torrent_id].compact = \ + self.config["compact_allocation"] + + # Set the right storage_mode + if self.torrents[torrent_id].compact: + storage_mode = lt.storage_mode_t(1) + else: + storage_mode = lt.storage_mode_t(2) + + try: + handle = self.session.add_torrent( + lt.torrent_info(torrent_filedump), + str(self.torrents[torrent_id].save_path), + storage_mode=storage_mode, + paused=paused) + except RuntimeError, e: + log.warning("Error adding torrent: %s", e) + + if not handle or not handle.is_valid(): + # The torrent was not added to the session + return False + + return True + def load_state(self): """Load the state of the TorrentManager from the torrents.state file""" state = TorrentManagerState() diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index ee0ef626e..53e8ebb60 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -89,6 +89,9 @@ class GtkUI(ui.UI): tooltip=_("Queue selected torrents down"), callback=self.on_queuedown_activate) + # Add a separator before menu + self.menu_sep = self.plugin.add_torrentmenu_separator() + # Add the queue menu to the torrent menu self.queue_menuitem = gtk.ImageMenuItem("Queue") queue_image = gtk.Image() @@ -99,7 +102,8 @@ class GtkUI(ui.UI): self.plugin.add_torrentmenu_menu(self.queue_menuitem) def unload_interface(self): - self.plugin.remove_torrentmenu_menu(self.queue_menuitem) + self.plugin.remove_torrentmenu_item(self.menu_sep) + self.plugin.remove_torrentmenu_item(self.queue_menuitem) self.plugin.remove_toolbar_button(self.toolbar_sep) self.plugin.remove_toolbar_button(self.toolbutton_up) self.plugin.remove_toolbar_button(self.toolbutton_down) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 6578684ee..9b2cb393a 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -314,7 +314,15 @@ def disable_plugin(plugin): get_core().disable_plugin(plugin) except (AttributeError, socket.error): set_core_uri(None) - + +def force_recheck(torrent_ids): + """Forces a data recheck on torrent_ids""" + try: + for torrent_id in torrent_ids: + get_core().force_recheck(torrent_id) + except (AttributeError, socket.error): + set_core_uri(None) + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index deb1cecbf..00617a06e 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -4,6 +4,28 @@ True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Open Folder + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-open + 1 + + + + + + + True + + True @@ -102,5 +124,27 @@ + + + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Force Re-check + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-redo + 1 + + + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 12bea2206..0b48e1523 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -92,7 +92,8 @@ class MenuBar(component.Component): "on_menuitem_edittrackers_activate": \ self.on_menuitem_edittrackers_activate, "on_menuitem_remove_activate": self.on_menuitem_remove_activate, - + "on_menuitem_recheck_activate": self.on_menuitem_recheck_activate, + "on_menuitem_open_folder": self.on_menuitem_open_folder_activate }) self.change_sensitivity = [ @@ -121,6 +122,12 @@ class MenuBar(component.Component): self.window.main_glade.get_widget("separatormenuitem").hide() self.window.main_glade.get_widget("menuitem_quitdaemon").hide() + def add_torrentmenu_separator(self): + sep = gtk.SeparatorMenuItem() + self.torrentmenu.append(sep) + sep.show() + return sep + ### Callbacks ### ## File Menu ## @@ -162,17 +169,17 @@ class MenuBar(component.Component): def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") client.pause_torrent( - component.get("TorrentView").get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) def on_menuitem_resume_activate(self, data=None): log.debug("on_menuitem_resume_activate") client.resume_torrent( - component.get("TorrentView").get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") client.force_reannounce( - component.get("TorrentView").get_selected_torrents()) + component.get("TorrentView").get_selected_torrents()) def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") @@ -180,8 +187,16 @@ class MenuBar(component.Component): def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") client.remove_torrent( - component.get("TorrentView").get_selected_torrents()) - + component.get("TorrentView").get_selected_torrents()) + + def on_menuitem_recheck_activate(self, data=None): + log.debug("on_menuitem_recheck_activate") + client.force_recheck( + component.get("TorrentView").get_selected_torrents()) + + def on_menuitem_open_folder_activate(self, data=None): + log.debug("on_menuitem_open_folder") + ## View Menu ## def on_menuitem_toolbar_toggled(self, value): log.debug("on_menuitem_toolbar_toggled") diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index ce56e33af..e90179a29 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -74,9 +74,12 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def add_torrentmenu_menu(self, *args): return component.get("MenuBar").torrentmenu.append(*args) - def remove_torrentmenu_menu(self, *args): + def remove_torrentmenu_item(self, *args): return component.get("MenuBar").torrentmenu.remove(*args) + def add_torrentmenu_separator(self): + return component.get("MenuBar").add_torrentmenu_separator() + def update_torrent_view(self, *args): return component.get("TorrentView").update(*args) From a043858f2e2eda93020018de03bcaa078495bf2b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 8 Nov 2007 08:04:43 +0000 Subject: [PATCH 0239/1009] Update TODO --- TODO | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index b4986a4dd..757adabc6 100644 --- a/TODO +++ b/TODO @@ -1,15 +1,21 @@ * Add state saving to listview.. this includes saving column size and position. * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. -* Add plugin list and the ability to load/unload them from the preferences - dialog. -* Have the UI request a list of activated plugins on start-up so it nows which - plugins to load. * Figure out easy way for user-made plugins to add i18n support. * Change the menubar.py gtkui component to menus.py and add support for plugins to add menuitems to the torrentmenu in an easy way. * Restart daemon function * Docstrings! -* Update libtorrent and bindings for sparse_mode allocation and remove_torrent options * Implement caching in client.py * Create a new add torrent dialog +* Create edit trackers dialog +* Implement open folder +* Maybe add pop-up menus to the status bar items +* Address issue where torrents will redownload if the storage is moved outside + of deluge. +* Hide open folder if not localhost +* Modify sensitivity of torrent/tray menu based on connection state +* Add queue preferences tab +* Add classic/normal mode to preferences +* Implement 'Classic' mode + From 7d9a30a54577855923b3fc3d985aa2adbe314323 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 9 Nov 2007 12:08:15 +0000 Subject: [PATCH 0240/1009] Updates to the Connection Manager. Updates to TorrentManager. --- TODO | 1 + deluge/core/torrentmanager.py | 125 +++++++-------- deluge/ui/gtkui/connectionmanager.py | 149 +++++++++++++----- .../ui/gtkui/glade/connection_manager.glade | 8 +- 4 files changed, 177 insertions(+), 106 deletions(-) diff --git a/TODO b/TODO index 757adabc6..95e3c993c 100644 --- a/TODO +++ b/TODO @@ -18,4 +18,5 @@ * Add queue preferences tab * Add classic/normal mode to preferences * Implement 'Classic' mode +* Add remove torrent dialog and ability to remove data diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 51e4181e1..a602b58ec 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -136,16 +136,7 @@ class TorrentManager: filedump = "".join(chr(b) for b in filedump) else: # Get the data from the file - try: - log.debug("Attempting to open %s for add.", filename) - _file = open( - os.path.join( - self.config["torrentfiles_location"], filename), "rb") - filedump = _file.read() - _file.close() - except IOError: - log.warning("Unable to open %s", filename) - return None + filedump = self.load_torrent(filename) # Attempt to load fastresume data try: @@ -156,12 +147,10 @@ class TorrentManager: "rb") fastresume = lt.bdecode(_file.read()) _file.close() - except IOError: - log.debug("Unable to load .fastresume..") + except IOError, e: + log.debug("Unable to load .fastresume: %s", e) fastresume = None - # Bdecode the filedata - torrent_filedump = lt.bdecode(filedump) handle = None # Make sure we have a valid download_location @@ -180,13 +169,13 @@ class TorrentManager: try: handle = self.session.add_torrent( - lt.torrent_info(torrent_filedump), + lt.torrent_info(filedump), str(save_path), resume_data=fastresume, storage_mode=storage_mode, paused=paused) - except RuntimeError: - log.warning("Error adding torrent") + except RuntimeError, e: + log.warning("Error adding torrent: %s", e) if not handle or not handle.is_valid(): # The torrent was not added to the session @@ -202,7 +191,16 @@ class TorrentManager: # Set per-torrent limits handle.set_max_connections(self.max_connections) handle.set_max_uploads(self.max_uploads) - + + # Save the torrent file + self.save_torrent(filename, filedump) + + # Save the session state + self.save_state() + return torrent.torrent_id + + def save_torrent(self, filename, filedump): + """Saves a torrent file""" log.debug("Attempting to save torrent file: %s", filename) # Test if the torrentfiles_location is accessible if os.access( @@ -211,25 +209,37 @@ class TorrentManager: # The directory probably doesn't exist, so lets create it try: os.makedirs(os.path.join(self.config["torrentfiles_location"])) - except IOError: - log.warning("Unable to create torrent files directory..") + except IOError, e: + log.warning("Unable to create torrent files directory: %s", e) # Write the .torrent file to the torrent directory try: save_file = open(os.path.join(self.config["torrentfiles_location"], filename), "wb") - save_file.write(lt.bencode(torrent_filedump)) + save_file.write(lt.bencode(filedump)) save_file.close() - except IOError: - log.warning("Unable to save torrent file: %s", filename) - - log.debug("Torrent %s added.", handle.info_hash()) + except IOError, e: + log.warning("Unable to save torrent file: %s", e) - # Save the session state - self.save_state() - return torrent.torrent_id - + def load_torrent(self, filename): + """Load a torrent file and return it's torrent info""" + filedump = None + # Get the torrent data from the torrent file + try: + log.debug("Attempting to open %s for add.", filename) + _file = open( + os.path.join( + self.config["torrentfiles_location"], filename), + "rb") + filedump = lt.bdecode(_file.read()) + _file.close() + except IOError, e: + log.warning("Unable to open %s: e", filename, e) + return False + + return filedump + def remove(self, torrent_id): """Remove a torrent from the manager""" try: @@ -302,67 +312,52 @@ class TorrentManager: return False return True - + def force_recheck(self, torrent_id): """Forces a re-check of the torrent's data""" log.debug("Doing a forced recheck on %s", torrent_id) + torrent = self.torrents[torrent_id] paused = self.torrents[torrent_id].handle.status().paused + torrent_info = None ### Check for .torrent file prior to removing and make a copy if needed - ### FIXME - + if os.access(os.path.join(self.config["torrentfiles_location"] +\ + "/" + torrent.filename), os.F_OK) is False: + torrent_info = torrent.handle.get_torrent_info().create_torrent() + self.save_torrent(torrent.filename, torrent_info) + + # We start by removing it from the lt session try: - # We start by removing it from the lt session - self.session.remove_torrent(self.torrents[torrent_id].handle, 0) + self.session.remove_torrent(torrent.handle, 0) except (RuntimeError, KeyError), e: log.warning("Error removing torrent: %s", e) return False + # Remove the fastresume file if there self.delete_fastresume(torrent_id) - # Get the torrent data from the torrent file - filename = self.torrents[torrent_id].filename - try: - log.debug("Attempting to open %s for add.", filename) - _file = open( - os.path.join( - self.config["torrentfiles_location"], filename), "rb") - filedump = _file.read() - _file.close() - except IOError, e: - log.warning("Unable to open %s: e", filename, e) - return False - - # Bdecode the filedata - torrent_filedump = lt.bdecode(filedump) - handle = None + # Load the torrent info from file if needed + if torrent_info == None: + torrent_info = self.load_torrent(torrent.filename) # Next we re-add the torrent - # Make sure we have a valid download_location - if self.torrents[torrent_id].save_path is None: - self.torrents[torrent_id].save_path = \ - self.config["download_location"] - - # Make sure we are adding it with the correct allocation method. - if self.torrents[torrent_id].compact is None: - self.torrents[torrent_id].compact = \ - self.config["compact_allocation"] # Set the right storage_mode - if self.torrents[torrent_id].compact: + if torrent.compact: storage_mode = lt.storage_mode_t(1) else: storage_mode = lt.storage_mode_t(2) - + + # Add the torrent to the lt session try: - handle = self.session.add_torrent( - lt.torrent_info(torrent_filedump), - str(self.torrents[torrent_id].save_path), + torrent.handle = self.session.add_torrent( + lt.torrent_info(torrent_info), + str(torrent.save_path), storage_mode=storage_mode, paused=paused) except RuntimeError, e: log.warning("Error adding torrent: %s", e) - if not handle or not handle.is_valid(): + if not torrent.handle or not torrent.handle.is_valid(): # The torrent was not added to the session return False diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 6788ac452..f586e993b 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -193,10 +193,30 @@ class ConnectionManager(component.Component): self.liststore.foreach(update_row) # Update the buttons self.update_buttons() + + # See if there is any row selected + paths = self.hostlist.get_selection().get_selected_rows()[1] + if len(paths) < 1: + # And there is at least 1 row + if self.liststore.iter_n_children(None) > 0: + # Then select the first row + self.hostlist.get_selection().select_iter(self.liststore.get_iter_first()) return True def update_buttons(self): """Updates the buttons based on selection""" + if self.liststore.iter_n_children(None) < 1: + # There is nothing in the list + self.glade.get_widget("button_startdaemon").set_sensitive(True) + self.glade.get_widget("button_connect").set_sensitive(False) + self.glade.get_widget("button_removehost").set_sensitive(False) + self.glade.get_widget("image_startdaemon").set_from_stock( + gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) + self.glade.get_widget("label_startdaemon").set_text( + "_Start Daemon") + self.glade.get_widget("label_startdaemon").set_use_underline( + True) + # Get the selected row's URI paths = self.hostlist.get_selection().get_selected_rows()[1] # If nothing is selected, just return @@ -213,33 +233,46 @@ class ConnectionManager(component.Component): # Make actual URI string uri = "http://" + uri + + # Make sure buttons are sensitive at start + self.glade.get_widget("button_startdaemon").set_sensitive(True) + self.glade.get_widget("button_connect").set_sensitive(True) + self.glade.get_widget("button_removehost").set_sensitive(True) # See if this is the currently connected URI if status == HOSTLIST_STATUS.index("Connected"): # Display a disconnect button if we're connected to this host self.glade.get_widget("button_connect").set_label("gtk-disconnect") + self.glade.get_widget("button_removehost").set_sensitive(False) else: self.glade.get_widget("button_connect").set_label("gtk-connect") + if status == HOSTLIST_STATUS.index("Offline") and not localhost: + self.glade.get_widget("button_connect").set_sensitive(False) + # Check to see if the host is online + if status == HOSTLIST_STATUS.index("Connected") \ + or status == HOSTLIST_STATUS.index("Online"): + self.glade.get_widget("image_startdaemon").set_from_stock( + gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + self.glade.get_widget("label_startdaemon").set_text( + "_Stop Daemon") + # Update the start daemon button if the selected host is localhost - if localhost: - # Check to see if the host is online - if status == HOSTLIST_STATUS.index("Connected") \ - or status == HOSTLIST_STATUS.index("Online"): - self.glade.get_widget("image_startdaemon").set_from_stock( - gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - self.glade.get_widget("label_startdaemon").set_text( - "_Stop local daemon") - else: - # The localhost is not online - self.glade.get_widget("image_startdaemon").set_from_stock( - gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - self.glade.get_widget("label_startdaemon").set_text( - "_Start local daemon") - # Make sure label is displayed correctly using mnemonics - self.glade.get_widget("label_startdaemon").set_use_underline( - True) - + if localhost and status == HOSTLIST_STATUS.index("Offline"): + # The localhost is not online + self.glade.get_widget("image_startdaemon").set_from_stock( + gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) + self.glade.get_widget("label_startdaemon").set_text( + "_Start Daemon") + + if not localhost: + # An offline host + self.glade.get_widget("button_startdaemon").set_sensitive(False) + + # Make sure label is displayed correctly using mnemonics + self.glade.get_widget("label_startdaemon").set_use_underline( + True) + def save(self): """Save the current host list to file""" def append_row(model=None, path=None, row=None, columns=None): @@ -279,27 +312,29 @@ class ConnectionManager(component.Component): response = dialog.run() if response == 1: # We add the host - hostname = hostname_entry.get_text() - if hostname.startswith("http://"): - hostname = hostname[7:] + self.add_host(hostname_entry.get_text(), + port_spinbutton.get_value_as_int()) - # Check to make sure the hostname is at least 1 character long - if len(hostname) < 1: - dialog.hide() - return - - # Get the port and concatenate the hostname string - port = port_spinbutton.get_value_as_int() - hostname = hostname + ":" + str(port) - row = self.liststore.append() - self.liststore.set_value(row, HOSTLIST_COL_URI, hostname) - # Save the host list to file - self.save() - # Update the status of the hosts - self._update() - - dialog.hide() + dialog.hide() + + def add_host(self, hostname, port): + """Adds the host to the list""" + if hostname.startswith("http://"): + hostname = hostname[7:] + # Check to make sure the hostname is at least 1 character long + if len(hostname) < 1: + return + + # Get the port and concatenate the hostname string + hostname = hostname + ":" + str(port) + row = self.liststore.append() + self.liststore.set_value(row, HOSTLIST_COL_URI, hostname) + # Save the host list to file + self.save() + # Update the status of the hosts + self._update() + def on_button_removehost_clicked(self, widget): log.debug("on_button_removehost_clicked") # Get the selected rows @@ -307,12 +342,24 @@ class ConnectionManager(component.Component): for path in paths: self.liststore.remove(self.liststore.get_iter(path)) + # Update the hostlist + self._update() + # Save the host list self.save() def on_button_startdaemon_clicked(self, widget): log.debug("on_button_startdaemon_clicked") + if self.liststore.iter_n_children(None) < 1: + # There is nothing in the list, so lets create a localhost entry + self.add_host("localhost", 58846) + # ..and start the daemon. + self.start_localhost(58846) + return + paths = self.hostlist.get_selection().get_selected_rows()[1] + if len(paths) < 1: + return row = self.liststore.get_iter(paths[0]) status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) uri = self.liststore.get_value(row, HOSTLIST_COL_URI) @@ -327,10 +374,15 @@ class ConnectionManager(component.Component): # Update display to show change self.update() elif HOSTLIST_STATUS[status] == "Offline": - log.debug("Start localhost daemon..") - # Spawn a local daemon - os.popen("deluged -p %s" % port) + self.start_localhost(port) + def start_localhost(self, port): + """Starts a localhost daemon""" + port = str(port) + log.info("Starting localhost:%s daemon..", port) + # Spawn a local daemon + os.popen("deluged -p %s" % port) + def on_button_close_clicked(self, widget): log.debug("on_button_close_clicked") self.hide() @@ -341,6 +393,12 @@ class ConnectionManager(component.Component): row = self.liststore.get_iter(paths[0]) status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) uri = self.liststore.get_value(row, HOSTLIST_COL_URI) + # Determine if this is a localhost + localhost = False + port = uri.split(":")[1] + if uri.split(":")[0] == "localhost": + localhost = True + uri = "http://" + uri if status == HOSTLIST_STATUS.index("Connected"): # If we are connected to this host, then we will disconnect. @@ -352,8 +410,19 @@ class ConnectionManager(component.Component): # column information because it can be up to 5 seconds out of sync. if not self.test_online_status(uri): log.warning("Host does not appear to be online..") + # If this is an offline localhost.. lets start it and connect + if localhost: + self.start_localhost(port) + # We need to wait for the host to start before connecting + while not self.test_online_status(uri): + sleep(10) + client.set_core_uri(uri) + self._update() + self.hide() + # Update the list to show proper status self._update() + return # Status is OK, so lets change to this host diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index 4c7fb9005..53d23ac9c 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,6 +1,6 @@ - + True @@ -270,6 +270,10 @@ 0 + + False + False + @@ -283,6 +287,8 @@ + False + False 1 From 3c9209dce43e4e7b1253d69dd50b0d8bf286396e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 10 Nov 2007 00:22:11 +0000 Subject: [PATCH 0241/1009] Display Queue preferences tab when enabled. --- TODO | 1 - .../queue/queue/glade/queueprefs.glade | 12 +- deluge/plugins/queue/queue/gtkui.py | 14 +- .../ui/gtkui/glade/preferences_dialog.glade | 225 +++++++++--------- deluge/ui/gtkui/pluginmanager.py | 12 +- deluge/ui/gtkui/preferences.py | 46 +++- 6 files changed, 184 insertions(+), 126 deletions(-) diff --git a/TODO b/TODO index 95e3c993c..4dceb5879 100644 --- a/TODO +++ b/TODO @@ -15,7 +15,6 @@ of deluge. * Hide open folder if not localhost * Modify sensitivity of torrent/tray menu based on connection state -* Add queue preferences tab * Add classic/normal mode to preferences * Implement 'Classic' mode * Add remove torrent dialog and ability to remove data diff --git a/deluge/plugins/queue/queue/glade/queueprefs.glade b/deluge/plugins/queue/queue/glade/queueprefs.glade index 4817944a7..67d89aae3 100644 --- a/deluge/plugins/queue/queue/glade/queueprefs.glade +++ b/deluge/plugins/queue/queue/glade/queueprefs.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -31,6 +31,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Queue new torrents to top + 0 True @@ -80,7 +81,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 - -1 -1 9999 1 10 10 + 0 -1 9999 1 10 10 True True @@ -98,7 +99,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 - -1 -1 9999 1 10 10 + 0 -1 9999 1 10 10 True True @@ -116,7 +117,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 - -1 -1 9999 1 10 10 + 0 -1 9999 1 10 10 True True @@ -208,6 +209,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Queue newly finished torrents to bottom + 0 True @@ -222,6 +224,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Stop seeding when share ratio reaches: + 0 True @@ -266,6 +269,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Remove torrent when share ratio reached + 0 True diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 53e8ebb60..60e93293e 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -48,6 +48,9 @@ class GtkUI(ui.UI): menu_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", "glade/queuemenu.glade")) + prefs_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", + "glade/queueprefs.glade")) + menu_glade.signal_autoconnect({ "on_menuitem_queuetop_activate": \ self.on_queuetop_activate, @@ -63,11 +66,8 @@ class GtkUI(ui.UI): # Connect to the 'torrent_queue_changed' signal #self.core.connect_to_signal("torrent_queue_changed", # self.torrent_queue_changed_signal) - - # Get the torrentview component from the plugin manager - #self.torrentview = self.plugin.get_torrentview() + # Add the '#' column at the first position - #self.torrentview.add_text_column("#", self.plugin.add_torrentview_text_column("#", col_type=int, position=0, @@ -100,6 +100,11 @@ class GtkUI(ui.UI): self.queue_menuitem.set_submenu(menu) self.queue_menuitem.show_all() self.plugin.add_torrentmenu_menu(self.queue_menuitem) + + # Add preferences page + self.queue_pref_page = \ + prefs_glade.get_widget("queue_prefs_box") + self.plugin.add_preferences_page("Queue", self.queue_pref_page) def unload_interface(self): self.plugin.remove_torrentmenu_item(self.menu_sep) @@ -108,6 +113,7 @@ class GtkUI(ui.UI): self.plugin.remove_toolbar_button(self.toolbutton_up) self.plugin.remove_toolbar_button(self.toolbutton_down) self.plugin.remove_torrentview_column("#") + self.plugin.remove_preferences_page("Queue") def update(self): self.plugin.update_torrent_view(["#"]) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 6b130df42..b745de87d 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -883,40 +883,71 @@ Either 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -941,74 +972,43 @@ Either - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1052,24 +1052,29 @@ Either 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1087,24 +1092,19 @@ Either - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1165,7 +1165,6 @@ Either True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 True @@ -1357,33 +1356,15 @@ Either 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1413,20 +1394,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index e90179a29..48148df53 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -74,12 +74,18 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def add_torrentmenu_menu(self, *args): return component.get("MenuBar").torrentmenu.append(*args) + def add_torrentmenu_separator(self): + return component.get("MenuBar").add_torrentmenu_separator() + def remove_torrentmenu_item(self, *args): return component.get("MenuBar").torrentmenu.remove(*args) - def add_torrentmenu_separator(self): - return component.get("MenuBar").add_torrentmenu_separator() - + def add_preferences_page(self, *args): + return component.get("Preferences").add_page(*args) + + def remove_preferences_page(self, *args): + return component.get("Preferences").remove_page(*args) + def update_torrent_view(self, *args): return component.get("TorrentView").update(*args) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 52d098503..31aea403b 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -101,9 +101,53 @@ class Preferences(component.Component): def add_page(self, name, widget): """Add a another page to the notebook""" - index = self.notebook.append_page(widget) + # Create a header and scrolled window for the preferences tab + widget.unparent() + vbox = gtk.VBox() + label = gtk.Label() + label.set_use_markup(True) + label.set_markup("" + name + "") + label.set_alignment(0.05, 0.50) + label.set_padding(0, 10) + vbox.pack_start(label, False, True, 0) + sep = gtk.HSeparator() + vbox.pack_start(sep, False, True, 0) + align = gtk.Alignment() + align.set_padding(5, 0, 0, 0) + align.add(widget) + vbox.pack_start(align, False, False, 0) + scrolled = gtk.ScrolledWindow() + viewport = gtk.Viewport() + viewport.set_shadow_type(gtk.SHADOW_NONE) + viewport.add(vbox) + scrolled.add(viewport) + scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled.show_all() + # Add this page to the notebook + index = self.notebook.append_page(scrolled) self.liststore.append([index, name]) + return name + def remove_page(self, name): + """Removes a page from the notebook""" + self.page_num_to_remove = None + self.iter_to_remove = None + + def check_row(model, path, iter, user_data): + row_name = model.get_value(iter, 1) + if row_name == user_data: + # This is the row we need to remove + self.page_num_to_remove = model.get_value(iter, 0) + self.iter_to_remove = iter + return + + self.liststore.foreach(check_row, name) + # Remove the page and row + if self.page_num_to_remove != None: + self.notebook.remove_page(self.page_num_to_remove) + if self.iter_to_remove != None: + self.liststore.remove(self.iter_to_remove) + def show(self): # Update the preferences dialog to reflect current config settings self.core_config = client.get_config() From 4dc601c8459ead46e04b5c5a82c1108ba4ba6a3e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 10 Nov 2007 06:00:23 +0000 Subject: [PATCH 0242/1009] System tray updates. --- deluge/config.py | 1 + deluge/ui/client.py | 14 ++ deluge/ui/gtkui/connectionmanager.py | 15 ++ deluge/ui/gtkui/glade/dgtkpopups.glade | 6 +- .../ui/gtkui/glade/preferences_dialog.glade | 228 +++++++++--------- deluge/ui/gtkui/glade/tray_menu.glade | 13 +- deluge/ui/gtkui/systemtray.py | 54 ++++- deluge/ui/gtkui/toolbar.py | 1 + 8 files changed, 201 insertions(+), 131 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index c9b0ed893..6bfcf408e 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -35,6 +35,7 @@ import cPickle +import gobject import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 9b2cb393a..329a995b2 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -194,6 +194,20 @@ def pause_torrent(torrent_ids): except (AttributeError, socket.error): set_core_uri(None) +def pause_all_torrents(): + """Pauses all torrents""" + try: + get_core().pause_all_torrents() + except (AttributeError, socket.error): + set_core_uri(None) + +def resume_all_torrents(): + """Resumes all torrents""" + try: + get_core().resume_all_torrents() + except (AttributeError, socket.error): + set_core_uri(None) + def resume_torrent(torrent_ids): """Resume torrent_ids""" try: diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index f586e993b..780e05bf8 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -127,6 +127,9 @@ class ConnectionManager(component.Component): uri[7:].split(":")[0] == "127.0.0.1": # This is a localhost, so lets try to start it port = uri[7:].split(":")[1] + # First add it to the list + self.add_host("localhost", port) + os.popen("deluged -p %s" % port) # We need to wait for the host to start before connecting while not self.test_online_status(uri): @@ -328,6 +331,18 @@ class ConnectionManager(component.Component): # Get the port and concatenate the hostname string hostname = hostname + ":" + str(port) + + # Check to see if there is already an entry for this host and return + # if thats the case + self.hosts_liststore = [] + def each_row(model, path, iter, data): + self.hosts_liststore.append( + model.get_value(iter, HOSTLIST_COL_URI)) + self.liststore.foreach(each_row, None) + if hostname in self.hosts_liststore: + return + + # Host isn't in the list, so lets add it row = self.liststore.append() self.liststore.set_value(row, HOSTLIST_COL_URI, hostname) # Save the host list to file diff --git a/deluge/ui/gtkui/glade/dgtkpopups.glade b/deluge/ui/gtkui/glade/dgtkpopups.glade index cdb19036d..1022651f5 100644 --- a/deluge/ui/gtkui/glade/dgtkpopups.glade +++ b/deluge/ui/gtkui/glade/dgtkpopups.glade @@ -1,6 +1,6 @@ - + Remove Torrent @@ -239,10 +239,12 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Speed - GTK_WIN_POS_CENTER + GTK_WIN_POS_MOUSE + True GDK_WINDOW_TYPE_HINT_NORMAL True True + False False diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index b745de87d..c3025ac0e 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -640,8 +640,8 @@ True True - µTorrent Peer-Exchange - µTorrent-PeX + Peer Exchange + Peer Exchange True 0 True @@ -883,71 +883,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -972,43 +941,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1052,29 +1052,24 @@ Either 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1092,19 +1087,24 @@ Either - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1356,15 +1356,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1394,38 +1412,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - diff --git a/deluge/ui/gtkui/glade/tray_menu.glade b/deluge/ui/gtkui/glade/tray_menu.glade index d54b82ca2..eaca5a191 100644 --- a/deluge/ui/gtkui/glade/tray_menu.glade +++ b/deluge/ui/gtkui/glade/tray_menu.glade @@ -17,7 +17,6 @@ True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -38,9 +37,8 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -80,7 +78,6 @@ True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -118,11 +115,10 @@ True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Quit & Shutdown Daemon @@ -138,6 +134,11 @@ + + + True + + True diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 8edeabfa8..0159bfea4 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -45,13 +45,26 @@ class SystemTray(component.Component): component.Component.__init__(self, "SystemTray") self.window = component.get("MainWindow") self.config = ConfigManager("gtkui.conf") + # List of widgets that need to be hidden when not connected to a host + self.hide_widget_list = [ + "menuitem_add_torrent", + "menuitem_pause_all", + "menuitem_resume_all", + "menuitem_download_limit", + "menuitem_upload_limit", + "menuitem_quitdaemon", + "separatormenuitem1", + "separatormenuitem2", + "separatormenuitem3", + "separatormenuitem4" + ] self.config.register_set_function("enable_system_tray", self.on_enable_system_tray_set) def enable(self): """Enables the system tray icon.""" log.debug("Enabling the system tray icon..") - self.tray = gtk.status_icon_new_from_icon_name('deluge') + self.tray = gtk.status_icon_new_from_icon_name("deluge") self.tray.connect("activate", self.on_tray_clicked) self.tray.connect("popup-menu", self.on_tray_popup) @@ -73,18 +86,37 @@ class SystemTray(component.Component): self.on_menuitem_quitdaemon_activate }) - self.tray_menu = self.tray_glade.get_widget("tray_menu") + self.tray_menu = self.tray_glade.get_widget("tray_menu") self.tray_glade.get_widget("download-limit-image").set_from_file( deluge.common.get_pixmap("downloading16.png")) self.tray_glade.get_widget("upload-limit-image").set_from_file( deluge.common.get_pixmap("seeding16.png")) + # Hide widgets now because we're not sure if we'll be connected to a + # host + for widget in self.hide_widget_list: + self.tray_glade.get_widget(widget).hide() + def start(self): log.debug("SystemTray start..") - # Build the bandwidth speed limit menus - self.build_tray_bwsetsubmenu() - + # Show widgets in the hide list because we've connected to a host + for widget in self.hide_widget_list: + self.tray_glade.get_widget(widget).show() + + if self.config["enable_system_tray"]: + # Build the bandwidth speed limit menus + self.build_tray_bwsetsubmenu() + + def stop(self): + log.debug("SystemTray stop..") + try: + # Hide widgets in hide list because we're not connected to a host + for widget in self.hide_widget_list: + self.tray_glade.get_widget(widget).hide() + except Exception, e: + log.debug("Unable to hide system tray menu widgets: %s", e) + def build_tray_bwsetsubmenu(self): # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( @@ -111,10 +143,14 @@ class SystemTray(component.Component): def disable(self): """Disables the system tray icon.""" log.debug("Disabling the system tray icon..") - self.tray.set_visible(False) - del self.tray - del self.tray_glade - del self.tray_menu + try: + self.tray.set_visible(False) + del self.tray + del self.tray_glade + del self.tray_menu + except Exception, e: + log.warning( + "Unable to disable system tray, probably wasn't enabled: %s", e) def on_enable_system_tray_set(self, key, value): """Called whenever the 'enable_system_tray' config key is modified""" diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index a4f4bb716..8d083dedc 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -73,6 +73,7 @@ class ToolBar(component.Component): def start(self): for widget in self.change_sensitivity: self.window.main_glade.get_widget(widget).set_sensitive(True) + self.update_buttons() gobject.idle_add(self.update_buttons) def stop(self): From 5b5232eddf9746e9921af0cbfbbecf4920fb3163 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 10 Nov 2007 06:28:09 +0000 Subject: [PATCH 0243/1009] Fix adding torrents. --- deluge/core/torrentmanager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index a602b58ec..05cb8027b 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -134,6 +134,7 @@ class TorrentManager: # joined. if type(filedump) is not str: filedump = "".join(chr(b) for b in filedump) + filedump = lt.bdecode(filedump) else: # Get the data from the file filedump = self.load_torrent(filename) From c42b1465280a6262706780ccd6790b0c677bf7fe Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 10 Nov 2007 09:44:51 +0000 Subject: [PATCH 0244/1009] System tray fixes. --- deluge/ui/gtkui/systemtray.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 0159bfea4..c0be1eef7 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -93,18 +93,18 @@ class SystemTray(component.Component): self.tray_glade.get_widget("upload-limit-image").set_from_file( deluge.common.get_pixmap("seeding16.png")) - # Hide widgets now because we're not sure if we'll be connected to a - # host - for widget in self.hide_widget_list: - self.tray_glade.get_widget(widget).hide() + if client.get_core_uri() == None: + # Hide menu widgets because we're not connected to a host. + for widget in self.hide_widget_list: + self.tray_glade.get_widget(widget).hide() def start(self): log.debug("SystemTray start..") - # Show widgets in the hide list because we've connected to a host - for widget in self.hide_widget_list: - self.tray_glade.get_widget(widget).show() - - if self.config["enable_system_tray"]: + if self.config["enable_system_tray"]: + # Show widgets in the hide list because we've connected to a host + for widget in self.hide_widget_list: + self.tray_glade.get_widget(widget).show() + # Build the bandwidth speed limit menus self.build_tray_bwsetsubmenu() @@ -149,8 +149,7 @@ class SystemTray(component.Component): del self.tray_glade del self.tray_menu except Exception, e: - log.warning( - "Unable to disable system tray, probably wasn't enabled: %s", e) + log.debug("Unable to disable system tray: %s", e) def on_enable_system_tray_set(self, key, value): """Called whenever the 'enable_system_tray' config key is modified""" From 5c154c32312983e6d8bddc2667cbeb12c44695f0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 10 Nov 2007 09:54:09 +0000 Subject: [PATCH 0245/1009] Fix removing columns from ListView. --- deluge/ui/gtkui/glade/connection_manager.glade | 3 ++- deluge/ui/gtkui/listview.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index 53d23ac9c..9fd34a8d6 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,6 +1,6 @@ - + True @@ -11,6 +11,7 @@ GTK_WIN_POS_CENTER_ON_PARENT 350 300 + True GDK_WINDOW_TYPE_HINT_DIALOG False diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 1f80953cc..369d144a1 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -200,7 +200,7 @@ class ListView: def copy_row(model, path, row, user_data): new_list, columns = user_data new_row = new_list.append() - for column in range(model.get_n_columns()): + for column in range(len(columns)): # Get the current value of the column for this row value = model.get_value(row, column) # Set the value of this row and column in the new liststore From 6da931e815503f928244827d53c5c7a96ae2cae0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 10 Nov 2007 10:04:07 +0000 Subject: [PATCH 0246/1009] Do not show Torrent menu when not connected. Fix force recheck. --- deluge/core/torrentmanager.py | 4 +- deluge/ui/gtkui/glade/main_window.glade | 1103 +++++++++++----------- deluge/ui/gtkui/glade/torrent_menu.glade | 5 - deluge/ui/gtkui/menubar.py | 12 +- 4 files changed, 559 insertions(+), 565 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 05cb8027b..76217276a 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -413,8 +413,8 @@ class TorrentManager: log.debug("Deleting fastresume file: %s", path) try: os.remove(path) - except IOError: - log.warning("Unable to delete the fastresume file: %s", path) + except Exception, e: + log.warning("Unable to delete the fastresume file: %s", e) def write_fastresume(self, torrent_id): """Writes the .fastresume file for the torrent""" diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index b62b98cd7..a6d21d6ba 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -135,7 +135,6 @@ - True _Torrent True @@ -384,287 +383,6 @@ 1 2 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - True @@ -697,18 +415,277 @@ 4 5 - + + True + 0 + + + 1 + 2 + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 4 5 - @@ -735,277 +712,18 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - + 0 + True + PANGO_WRAP_WORD_CHAR - 1 - 2 + 3 + 4 4 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 + @@ -1035,6 +753,287 @@ GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 00617a06e..0e58f551c 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -29,7 +29,6 @@ True - False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Pause True @@ -45,7 +44,6 @@ True - False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Resume selected torrents. Resu_me @@ -69,7 +67,6 @@ True - False _Update Tracker True @@ -86,7 +83,6 @@ True - False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Edit Trackers True @@ -109,7 +105,6 @@ True - False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Remove Torrent True diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 0b48e1523..aa1ede056 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -52,10 +52,10 @@ class MenuBar(component.Component): "glade/torrent_menu.glade")) self.torrentmenu = torrentmenu_glade.get_widget("torrent_menu") + self.menu_torrent = self.window.main_glade.get_widget("menu_torrent") # Attach the torrent_menu to the Torrent file menu - self.window.main_glade.get_widget("menu_torrent").set_submenu( - self.torrentmenu) + self.menu_torrent.set_submenu(self.torrentmenu) ### Connect Signals ### self.window.main_glade.signal_autoconnect({ @@ -106,8 +106,8 @@ class MenuBar(component.Component): for widget in self.change_sensitivity: self.window.main_glade.get_widget(widget).set_sensitive(True) - for child in self.torrentmenu: - child.set_sensitive(True) + # Show the Torrent menu because we're connected to a host + self.menu_torrent.show() self.window.main_glade.get_widget("separatormenuitem").show() self.window.main_glade.get_widget("menuitem_quitdaemon").show() @@ -116,8 +116,8 @@ class MenuBar(component.Component): for widget in self.change_sensitivity: self.window.main_glade.get_widget(widget).set_sensitive(False) - for child in self.torrentmenu: - child.set_sensitive(False) + # Hide the Torrent menu + self.menu_torrent.hide() self.window.main_glade.get_widget("separatormenuitem").hide() self.window.main_glade.get_widget("menuitem_quitdaemon").hide() From cd0465a3a14645f57918136ef47d4a025b0f21af Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 10 Nov 2007 22:18:02 +0000 Subject: [PATCH 0247/1009] use lazy bitfield in 0.6 --- deluge/core/core.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deluge/core/core.py b/deluge/core/core.py index 67abf00c7..2c6f99e2c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -138,6 +138,9 @@ class Core( # Set the user agent self.settings = lt.session_settings() self.settings.user_agent = "Deluge %s" % deluge.common.get_version() + + # Set lazy bitfield + self.settings.lazy_bitfields = 1 self.session.set_settings(self.settings) # Load metadata extension From 4e92f3f699bc541f76984d7a1c1c2936a610954b Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 11 Nov 2007 00:22:53 +0000 Subject: [PATCH 0248/1009] lt sync 1723 --- .../include/libtorrent/aux_/session_impl.hpp | 31 +- .../include/libtorrent/bandwidth_manager.hpp | 12 +- libtorrent/include/libtorrent/bencode.hpp | 79 +++-- .../include/libtorrent/connection_queue.hpp | 1 + .../include/libtorrent/disk_io_thread.hpp | 7 +- libtorrent/include/libtorrent/entry.hpp | 27 ++ .../libtorrent/http_tracker_connection.hpp | 4 +- .../libtorrent/instantiate_connection.hpp | 4 +- .../include/libtorrent/intrusive_ptr_base.hpp | 6 +- .../include/libtorrent/peer_connection.hpp | 12 +- .../include/libtorrent/piece_picker.hpp | 5 +- libtorrent/include/libtorrent/policy.hpp | 3 + libtorrent/include/libtorrent/proxy_base.hpp | 24 +- .../include/libtorrent/session_settings.hpp | 8 +- .../include/libtorrent/socks4_stream.hpp | 1 + .../include/libtorrent/socks5_stream.hpp | 1 + libtorrent/include/libtorrent/storage.hpp | 5 +- libtorrent/include/libtorrent/torrent.hpp | 44 +-- .../include/libtorrent/torrent_handle.hpp | 7 + .../include/libtorrent/torrent_info.hpp | 4 +- .../include/libtorrent/tracker_manager.hpp | 3 +- .../libtorrent/udp_tracker_connection.hpp | 4 +- libtorrent/include/libtorrent/upnp.hpp | 15 + .../include/libtorrent/variant_stream.hpp | 172 ++++------ libtorrent/src/broadcast_socket.cpp | 2 + libtorrent/src/bt_peer_connection.cpp | 19 +- libtorrent/src/connection_queue.cpp | 13 +- libtorrent/src/disk_io_thread.cpp | 37 ++ libtorrent/src/entry.cpp | 2 + libtorrent/src/http_tracker_connection.cpp | 43 ++- libtorrent/src/instantiate_connection.cpp | 30 +- libtorrent/src/lsd.cpp | 1 + libtorrent/src/metadata_transfer.cpp | 5 +- libtorrent/src/natpmp.cpp | 4 + libtorrent/src/peer_connection.cpp | 160 ++++++--- libtorrent/src/piece_picker.cpp | 20 +- libtorrent/src/policy.cpp | 85 +++-- libtorrent/src/session_impl.cpp | 260 +++++++------- libtorrent/src/storage.cpp | 20 +- libtorrent/src/torrent.cpp | 320 ++++++++++-------- libtorrent/src/torrent_handle.cpp | 16 +- libtorrent/src/tracker_manager.cpp | 41 ++- libtorrent/src/udp_tracker_connection.cpp | 58 ++-- libtorrent/src/upnp.cpp | 99 ++++-- libtorrent/src/ut_pex.cpp | 34 +- 45 files changed, 1036 insertions(+), 712 deletions(-) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index bff8e3387..df39fabb0 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -178,9 +178,7 @@ namespace libtorrent #endif friend struct checker_impl; friend class invariant_access; - typedef std::map - , boost::intrusive_ptr > - connection_map; + typedef std::set > connection_map; typedef std::map > torrent_map; session_impl( @@ -190,7 +188,8 @@ namespace libtorrent ~session_impl(); #ifndef TORRENT_DISABLE_EXTENSIONS - void add_extension(boost::function(torrent*, void*)> ext); + void add_extension(boost::function( + torrent*, void*)> ext); #endif void operator()(); @@ -214,7 +213,7 @@ namespace libtorrent peer_id const& get_peer_id() const { return m_peer_id; } void close_connection(boost::intrusive_ptr const& p); - void connection_failed(boost::shared_ptr const& s + void connection_failed(boost::intrusive_ptr const& p , tcp::endpoint const& a, char const* message); void set_settings(session_settings const& s); @@ -342,8 +341,8 @@ namespace libtorrent for (connection_map::const_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - send_buffer_capacity += i->second->send_buffer_capacity(); - used_send_buffer += i->second->send_buffer_size(); + send_buffer_capacity += (*i)->send_buffer_capacity(); + used_send_buffer += (*i)->send_buffer_size(); } TORRENT_ASSERT(send_buffer_capacity >= used_send_buffer); m_buffer_usage_logger << log_time() << " send_buffer_size: " << send_buffer_capacity << std::endl; @@ -375,12 +374,6 @@ namespace libtorrent // buffers from. boost::pool<> m_send_buffers; - // this is where all active sockets are stored. - // the selector can sleep while there's no activity on - // them - io_service m_io_service; - asio::strand m_strand; - // the file pool that all storages in this session's // torrents uses. It sets a limit on the number of // open files by this session. @@ -392,9 +385,17 @@ namespace libtorrent // handles disk io requests asynchronously // peers have pointers into the disk buffer // pool, and must be destructed before this - // object. + // object. The disk thread relies on the file + // pool object, and must be destructed before + // m_files. disk_io_thread m_disk_thread; + // this is where all active sockets are stored. + // the selector can sleep while there's no activity on + // them + io_service m_io_service; + asio::strand m_strand; + // this is a list of half-open tcp connections // (only outgoing connections) // this has to be one of the last @@ -646,7 +647,7 @@ namespace libtorrent void debug_log(const std::string& line) { - (*m_ses.m_logger) << line << "\n"; + (*m_ses.m_logger) << time_now_string() << " " << line << "\n"; } session_impl& m_ses; }; diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 83df5b371..ef132543f 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -181,6 +181,7 @@ struct bandwidth_manager , m_current_quota(0) , m_channel(channel) , m_in_hand_out_bandwidth(false) + , m_abort(false) {} void throttle(int limit) throw() @@ -196,6 +197,12 @@ struct bandwidth_manager return m_limit; } + void close() + { + m_abort = true; + m_history_timer.cancel(); + } + // non prioritized means that, if there's a line for bandwidth, // others will cut in front of the non-prioritized peers. // this is used by web seeds @@ -275,6 +282,8 @@ private: // active that will be invoked, no need to set one up if (m_history.size() > 1) return; + if (m_abort) return; + m_history_timer.expires_at(e.expires_at); m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); #ifndef NDEBUG @@ -308,7 +317,7 @@ private: } // now, wait for the next chunk to expire - if (!m_history.empty()) + if (!m_history.empty() && !m_abort) { m_history_timer.expires_at(m_history.back().expires_at); m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); @@ -487,6 +496,7 @@ private: // to prevent recursive invocations to interfere bool m_in_hand_out_bandwidth; + bool m_abort; }; } diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index 66da191ab..1d3f1ea2b 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -135,26 +135,38 @@ namespace libtorrent } template - std::string read_until(InIt& in, InIt end, char end_token) + std::string read_until(InIt& in, InIt end, char end_token, bool& err) { - if (in == end) throw invalid_encoding(); std::string ret; + if (in == end) + { + err = true; + return ret; + } while (*in != end_token) { ret += *in; ++in; - if (in == end) throw invalid_encoding(); + if (in == end) + { + err = true; + return ret; + } } return ret; } template - void read_string(InIt& in, InIt end, int len, std::string& str) + void read_string(InIt& in, InIt end, int len, std::string& str, bool& err) { TORRENT_ASSERT(len >= 0); for (int i = 0; i < len; ++i) { - if (in == end) throw invalid_encoding(); + if (in == end) + { + err = true; + return; + } str += *in; ++in; } @@ -202,9 +214,13 @@ namespace libtorrent } template - void bdecode_recursive(InIt& in, InIt end, entry& ret) + void bdecode_recursive(InIt& in, InIt end, entry& ret, bool& err) { - if (in == end) throw invalid_encoding(); + if (in == end) + { + err = true; + return; + } switch (*in) { @@ -213,7 +229,8 @@ namespace libtorrent case 'i': { ++in; // 'i' - std::string val = read_until(in, end, 'e'); + std::string val = read_until(in, end, 'e', err); + if (err) return; TORRENT_ASSERT(*in == 'e'); ++in; // 'e' ret = entry(entry::int_t); @@ -230,8 +247,13 @@ namespace libtorrent { ret.list().push_back(entry()); entry& e = ret.list().back(); - bdecode_recursive(in, end, e); - if (in == end) throw invalid_encoding(); + bdecode_recursive(in, end, e, err); + if (err) return; + if (in == end) + { + err = true; + return; + } } TORRENT_ASSERT(*in == 'e'); ++in; // 'e' @@ -246,10 +268,16 @@ namespace libtorrent while (*in != 'e') { entry key; - bdecode_recursive(in, end, key); + bdecode_recursive(in, end, key, err); + if (err) return; entry& e = ret[key.string()]; - bdecode_recursive(in, end, e); - if (in == end) throw invalid_encoding(); + bdecode_recursive(in, end, e, err); + if (err) return; + if (in == end) + { + err = true; + return; + } } TORRENT_ASSERT(*in == 'e'); ++in; // 'e' @@ -260,16 +288,19 @@ namespace libtorrent default: if (isdigit((unsigned char)*in)) { - std::string len_s = read_until(in, end, ':'); + std::string len_s = read_until(in, end, ':', err); + if (err) return; TORRENT_ASSERT(*in == ':'); ++in; // ':' int len = std::atoi(len_s.c_str()); ret = entry(entry::string_t); - read_string(in, end, len, ret.string()); + read_string(in, end, len, ret.string(), err); + if (err) return; } else { - throw invalid_encoding(); + err = true; + return; } } } @@ -284,16 +315,18 @@ namespace libtorrent template entry bdecode(InIt start, InIt end) { - try - { - entry e; - detail::bdecode_recursive(start, end, e); - return e; - } - catch(type_error&) + entry e; + bool err = false; + detail::bdecode_recursive(start, end, e, err); + if (err) { +#ifdef BOOST_NO_EXCEPTIONS + return entry(); +#else throw invalid_encoding(); +#endif } + return e; } } diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp index b3b7cde86..c229ec217 100644 --- a/libtorrent/include/libtorrent/connection_queue.hpp +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -56,6 +56,7 @@ public: void done(int ticket); void limit(int limit); int limit() const; + void close(); #ifndef NDEBUG diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index bd6d5e1ba..b93ea8b75 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -106,6 +106,10 @@ namespace libtorrent , boost::function const& f = boost::function()); +#ifndef NDEBUG + disk_io_job find_job(boost::intrusive_ptr s + , int action, int piece) const; +#endif // keep track of the number of bytes in the job queue // at any given time. i.e. the sum of all buffer_size. // this is used to slow down the download global download @@ -120,7 +124,7 @@ namespace libtorrent private: - boost::mutex m_mutex; + mutable boost::mutex m_mutex; boost::condition m_signal; bool m_abort; std::deque m_jobs; @@ -131,6 +135,7 @@ namespace libtorrent #ifndef NDEBUG int m_block_size; + disk_io_job m_current; #endif #ifdef TORRENT_DISK_STATS diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp index 7fd6c8c53..1c25cc7c7 100755 --- a/libtorrent/include/libtorrent/entry.hpp +++ b/libtorrent/include/libtorrent/entry.hpp @@ -161,8 +161,10 @@ namespace libtorrent // is a dictionary, otherwise they will throw entry& operator[](char const* key); entry& operator[](std::string const& key); +#ifndef BOOST_NO_EXCEPTIONS const entry& operator[](char const* key) const; const entry& operator[](std::string const& key) const; +#endif entry* find_key(char const* key); entry const* find_key(char const* key) const; @@ -221,55 +223,80 @@ namespace libtorrent copy(e); } + inline entry::integer_type& entry::integer() { if (m_type == undefined_t) construct(int_t); +#ifndef BOOST_NO_EXCEPTIONS if (m_type != int_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == int_t); return *reinterpret_cast(data); } inline entry::integer_type const& entry::integer() const { +#ifndef BOOST_NO_EXCEPTIONS if (m_type != int_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == int_t); return *reinterpret_cast(data); } inline entry::string_type& entry::string() { if (m_type == undefined_t) construct(string_t); +#ifndef BOOST_NO_EXCEPTIONS if (m_type != string_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == string_t); return *reinterpret_cast(data); } inline entry::string_type const& entry::string() const { +#ifndef BOOST_NO_EXCEPTIONS if (m_type != string_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == string_t); return *reinterpret_cast(data); } inline entry::list_type& entry::list() { if (m_type == undefined_t) construct(list_t); +#ifndef BOOST_NO_EXCEPTIONS if (m_type != list_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == list_t); return *reinterpret_cast(data); } inline entry::list_type const& entry::list() const { +#ifndef BOOST_NO_EXCEPTIONS if (m_type != list_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == list_t); return *reinterpret_cast(data); } inline entry::dictionary_type& entry::dict() { if (m_type == undefined_t) construct(dictionary_t); +#ifndef BOOST_NO_EXCEPTIONS if (m_type != dictionary_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == dictionary_t); return *reinterpret_cast(data); } inline entry::dictionary_type const& entry::dict() const { +#ifndef BOOST_NO_EXCEPTIONS if (m_type != dictionary_t) throw type_error("invalid type requested from entry"); +#endif + TORRENT_ASSERT(m_type == dictionary_t); return *reinterpret_cast(data); } diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index 70be3054a..c0057dfa1 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -130,6 +130,8 @@ namespace libtorrent , proxy_settings const& ps , std::string const& password = ""); + void close(); + private: boost::intrusive_ptr self() @@ -159,7 +161,7 @@ namespace libtorrent asio::strand& m_strand; tcp::resolver m_name_lookup; int m_port; - boost::shared_ptr m_socket; + socket_type m_socket; int m_recv_pos; std::vector m_buffer; std::string m_send_buffer; diff --git a/libtorrent/include/libtorrent/instantiate_connection.hpp b/libtorrent/include/libtorrent/instantiate_connection.hpp index 49cb1fe18..71282f993 100644 --- a/libtorrent/include/libtorrent/instantiate_connection.hpp +++ b/libtorrent/include/libtorrent/instantiate_connection.hpp @@ -41,8 +41,8 @@ namespace libtorrent { struct proxy_settings; - boost::shared_ptr instantiate_connection( - asio::io_service& ios, proxy_settings const& ps); + bool instantiate_connection(asio::io_service& ios + , proxy_settings const& ps, socket_type& s); } #endif diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index c7fbe46ad..5cccdf827 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_INTRUSIVE_PTR_BASE #include +#include #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" @@ -45,7 +46,8 @@ namespace libtorrent intrusive_ptr_base(intrusive_ptr_base const&) : m_refs(0) {} - intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) {} + intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) + { return *this; } friend void intrusive_ptr_add_ref(intrusive_ptr_base const* s) { @@ -59,7 +61,7 @@ namespace libtorrent TORRENT_ASSERT(s->m_refs > 0); TORRENT_ASSERT(s != 0); if (--s->m_refs == 0) - delete static_cast(s); + boost::checked_delete(static_cast(s)); } boost::intrusive_ptr self() diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index e1581affe..805b38d9d 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -367,8 +367,12 @@ namespace libtorrent return boost::optional(); } - void send_buffer(char const* begin, int size); - buffer::interval allocate_send_buffer(int size); + // these functions are virtual to let bt_peer_connection hook into them + // and encrypt the content + virtual void send_buffer(char const* begin, int size); + virtual buffer::interval allocate_send_buffer(int size); + virtual void setup_send(); + template void append_send_buffer(char* buffer, int size, Destructor const& destructor) { @@ -378,7 +382,6 @@ namespace libtorrent m_ses.log_buffer_usage(); #endif } - void setup_send(); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void set_country(char const* c) @@ -466,9 +469,6 @@ namespace libtorrent // the peer belongs to. aux::session_impl& m_ses; - boost::intrusive_ptr self() - { return boost::intrusive_ptr(this); } - // called from the main loop when this connection has any // work to do. void on_send_data(asio::error_code const& error diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index ec6fc5bd3..ef69c3334 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -357,11 +357,12 @@ namespace libtorrent // the different priority levels switch (piece_priority) { + case 1: return prio; case 2: return prio - 1; case 3: return (std::max)(prio / 2, 1); case 4: return (std::max)(prio / 2 - 1, 1); - case 5: - case 6: return (std::min)(prio / 2 - 1, 2); + case 5: return (std::max)(prio / 3, 1); + case 6: return (std::max)(prio / 3 - 1, 1); case 7: return 1; } return prio; diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index c38bb426c..95397b49e 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -85,6 +85,7 @@ namespace libtorrent // the tracker, pex, lsd or dht. policy::peer* peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int source, char flags); + void update_peer_port(int port, policy::peer* p, int src); // called when an incoming connection is accepted void new_connection(peer_connection& c); @@ -219,6 +220,8 @@ namespace libtorrent typedef std::multimap::const_iterator const_iterator; iterator begin_peer() { return m_peers.begin(); } iterator end_peer() { return m_peers.end(); } + const_iterator begin_peer() const { return m_peers.begin(); } + const_iterator end_peer() const { return m_peers.end(); } bool connect_one_peer(); bool disconnect_one_peer(); diff --git a/libtorrent/include/libtorrent/proxy_base.hpp b/libtorrent/include/libtorrent/proxy_base.hpp index 021802dd0..f2a955958 100644 --- a/libtorrent/include/libtorrent/proxy_base.hpp +++ b/libtorrent/include/libtorrent/proxy_base.hpp @@ -104,10 +104,9 @@ public: m_sock.bind(endpoint); } - template - void bind(endpoint_type const& endpoint, Error_Handler const& error_handler) + void bind(endpoint_type const& endpoint, asio::error_code& ec) { - m_sock.bind(endpoint, error_handler); + m_sock.bind(endpoint, ec); } void open(protocol_type const& p) @@ -115,10 +114,9 @@ public: m_sock.open(p); } - template - void open(protocol_type const& p, Error_Handler const& error_handler) + void open(protocol_type const& p, asio::error_code& ec) { - m_sock.open(p, error_handler); + m_sock.open(p, ec); } void close() @@ -127,10 +125,9 @@ public: m_sock.close(); } - template - void close(Error_Handler const& error_handler) + void close(asio::error_code& ec) { - m_sock.close(error_handler); + m_sock.close(ec); } endpoint_type remote_endpoint() @@ -138,8 +135,7 @@ public: return m_remote_endpoint; } - template - endpoint_type remote_endpoint(Error_Handler const& error_handler) + endpoint_type remote_endpoint(asio::error_code& ec) { return m_remote_endpoint; } @@ -149,10 +145,9 @@ public: return m_sock.local_endpoint(); } - template - endpoint_type local_endpoint(Error_Handler const& error_handler) + endpoint_type local_endpoint(asio::error_code& ec) { - return m_sock.local_endpoint(error_handler); + return m_sock.local_endpoint(ec); } asio::io_service& io_service() @@ -168,7 +163,6 @@ public: protected: stream_socket m_sock; - // the socks5 proxy std::string m_hostname; int m_port; diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index a792e296a..7b08ec11e 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -87,7 +87,7 @@ namespace libtorrent , tracker_receive_timeout(20) , stop_tracker_timeout(5) , tracker_maximum_response_length(1024*1024) - , piece_timeout(120) + , piece_timeout(10) , request_queue_time(3.f) , max_allowed_in_request_queue(250) , max_out_request_queue(200) @@ -111,6 +111,7 @@ namespace libtorrent , initial_picker_threshold(4) , allowed_fast_set_size(10) , max_outstanding_disk_bytes_per_connection(64 * 1024) + , handshake_timeout(10) #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) #endif @@ -270,6 +271,11 @@ namespace libtorrent // to not completely disrupt normal downloads. int max_outstanding_disk_bytes_per_connection; + // the number of seconds to wait for a handshake + // response from a peer. If no response is received + // within this time, the peer is disconnected. + int handshake_timeout; + #ifndef TORRENT_DISABLE_DHT // while this is true, the dht will note be used unless the // tracker is online diff --git a/libtorrent/include/libtorrent/socks4_stream.hpp b/libtorrent/include/libtorrent/socks4_stream.hpp index 6110dbe4d..9530f9d57 100644 --- a/libtorrent/include/libtorrent/socks4_stream.hpp +++ b/libtorrent/include/libtorrent/socks4_stream.hpp @@ -89,3 +89,4 @@ private: } #endif + diff --git a/libtorrent/include/libtorrent/socks5_stream.hpp b/libtorrent/include/libtorrent/socks5_stream.hpp index 8bfc74c59..622557cd2 100644 --- a/libtorrent/include/libtorrent/socks5_stream.hpp +++ b/libtorrent/include/libtorrent/socks5_stream.hpp @@ -101,3 +101,4 @@ private: } #endif + diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 68a81c75b..a3f97b589 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -57,6 +57,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_request.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" namespace libtorrent { @@ -344,8 +345,8 @@ namespace libtorrent // used to move pieces while expanding // the storage from compact allocation // to full allocation - std::vector m_scratch_buffer; - std::vector m_scratch_buffer2; + buffer m_scratch_buffer; + buffer m_scratch_buffer2; // the piece that is in the scratch buffer int m_scratch_piece; diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 60140f2c2..5ee5ddb03 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -128,6 +128,8 @@ namespace libtorrent #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::shared_ptr); + void add_extension(boost::function(torrent*, void*)> const& ext + , void* userdata); #endif // this is called when the torrent has metadata. @@ -171,7 +173,7 @@ namespace libtorrent boost::tuples::tuple bytes_done() const; size_type quantized_bytes_done() const; - void ip_filter_updated() { m_policy->ip_filter_updated(); } + void ip_filter_updated() { m_policy.ip_filter_updated(); } void pause(); void resume(); @@ -204,7 +206,7 @@ namespace libtorrent tcp::endpoint const& get_interface() const { return m_net_interface; } void connect_to_url_seed(std::string const& url); - peer_connection* connect_to_peer(policy::peer* peerinfo); + bool connect_to_peer(policy::peer* peerinfo) throw(); void set_ratio(float ratio) { TORRENT_ASSERT(ratio >= 0.0f); m_ratio = ratio; } @@ -276,31 +278,12 @@ namespace libtorrent bool want_more_peers() const; bool try_connect_peer(); - peer_connection* connection_for(tcp::endpoint const& a) - { - peer_iterator i = m_connections.find(a); - if (i == m_connections.end()) return 0; - return i->second; - } - -#ifndef NDEBUG - void connection_for(address const& a, std::vector& pc) - { - for (peer_iterator i = m_connections.begin() - , end(m_connections.end()); i != end; ++i) - { - if (i->first.address() == a) pc.push_back(i->second); - } - return; - } -#endif - // the number of peers that belong to this torrent int num_peers() const { return (int)m_connections.size(); } int num_seeds() const; - typedef std::map::iterator peer_iterator; - typedef std::map::const_iterator const_peer_iterator; + typedef std::set::iterator peer_iterator; + typedef std::set::const_iterator const_peer_iterator; const_peer_iterator begin() const { return m_connections.begin(); } const_peer_iterator end() const { return m_connections.end(); } @@ -424,6 +407,7 @@ namespace libtorrent } int block_size() const { TORRENT_ASSERT(m_block_size > 0); return m_block_size; } + peer_request to_req(piece_block const& p); // this will tell all peers that we just got his piece // and also let the piece picker know that we have this piece @@ -494,11 +478,7 @@ namespace libtorrent { return m_picker.get() != 0; } - policy& get_policy() - { - TORRENT_ASSERT(m_policy); - return *m_policy; - } + policy& get_policy() { return m_policy; } piece_manager& filesystem(); torrent_info const& torrent_file() const { return *m_torrent_file; } @@ -549,7 +529,7 @@ namespace libtorrent // to the checker thread for initial checking // of the storage. void set_metadata(entry const&); - + private: void on_files_deleted(int ret, disk_io_job const& j); @@ -632,7 +612,7 @@ namespace libtorrent #ifndef NDEBUG public: #endif - std::map m_connections; + std::set m_connections; #ifndef NDEBUG private: #endif @@ -688,8 +668,6 @@ namespace libtorrent // ----------------------------- - boost::shared_ptr m_policy; - // a back reference to the session // this torrent belongs to. aux::session_impl& m_ses; @@ -801,6 +779,8 @@ namespace libtorrent // total_done - m_initial_done <= total_payload_download size_type m_initial_done; #endif + + policy m_policy; }; inline ptime torrent::next_announce() const diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 8ae2f7f41..7ddb218a6 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -64,6 +64,8 @@ namespace libtorrent struct checker_impl; } + struct torrent_plugin; + struct TORRENT_EXPORT duplicate_torrent: std::exception { virtual const char* what() const throw() @@ -279,6 +281,11 @@ namespace libtorrent void remove_url_seed(std::string const& url) const; std::set url_seeds() const; +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::function(torrent*, void*)> const& ext + , void* userdata = 0); +#endif + bool has_metadata() const; const torrent_info& get_torrent_info() const; bool is_valid() const; diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index 89744d0af..16eebf234 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -240,8 +240,8 @@ namespace libtorrent void parse_info_section(entry const& e); - entry extra(char const* key) const - { return m_extra_info[key]; } + entry const* extra(char const* key) const + { return m_extra_info.find_key(key); } // frees parts of the metadata that isn't // used by seeds diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index 07c377a0f..fdc3f6bbf 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -184,6 +184,7 @@ namespace libtorrent typedef boost::mutex mutex_t; mutable mutex_t m_mutex; + bool m_abort; }; struct TORRENT_EXPORT tracker_connection @@ -202,7 +203,7 @@ namespace libtorrent void fail(int code, char const* msg); void fail_timeout(); - void close(); + virtual void close(); address const& bind_interface() const { return m_bind_interface; } protected: diff --git a/libtorrent/include/libtorrent/udp_tracker_connection.hpp b/libtorrent/include/libtorrent/udp_tracker_connection.hpp index c0e6853b9..4fba505a4 100755 --- a/libtorrent/include/libtorrent/udp_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/udp_tracker_connection.hpp @@ -74,6 +74,8 @@ namespace libtorrent , boost::weak_ptr c , session_settings const& stn); + void close(); + private: enum action_t @@ -105,7 +107,7 @@ namespace libtorrent asio::strand& m_strand; udp::resolver m_name_lookup; - boost::shared_ptr m_socket; + datagram_socket m_socket; udp::endpoint m_target; udp::endpoint m_sender; diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index 2c819df5f..0b799d1e9 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -148,7 +148,18 @@ private: { mapping[0].protocol = 0; mapping[1].protocol = 1; +#ifndef NDEBUG + magic = 1337; +#endif } + +#ifndef NDEBUG + ~rootdevice() + { + TORRENT_ASSERT(magic == 1337); + magic = 0; + } +#endif // the interface url, through which the list of // supported interfaces are fetched @@ -174,8 +185,12 @@ private: mutable boost::shared_ptr upnp_connection; +#ifndef NDEBUG + int magic; +#endif void close() const { + TORRENT_ASSERT(magic == 1337); if (!upnp_connection) return; upnp_connection->close(); upnp_connection.reset(); diff --git a/libtorrent/include/libtorrent/variant_stream.hpp b/libtorrent/include/libtorrent/variant_stream.hpp index 4f45f5e01..bbe3d964d 100644 --- a/libtorrent/include/libtorrent/variant_stream.hpp +++ b/libtorrent/include/libtorrent/variant_stream.hpp @@ -48,8 +48,8 @@ namespace aux template struct io_control_visitor_ec: boost::static_visitor<> { - io_control_visitor_ec(IO_Control_Command& ioc, asio::error_code& ec) - : ioc(ioc), ec(ec) {} + io_control_visitor_ec(IO_Control_Command& ioc, asio::error_code& ec_) + : ioc(ioc), ec(ec_) {} template void operator()(T* p) const @@ -108,30 +108,27 @@ namespace aux // -------------- bind ----------- - template - struct bind_visitor + template + struct bind_visitor_ec : boost::static_visitor<> { - bind_visitor(EndpointType const& ep, Error_Handler const& error_handler) + bind_visitor_ec(EndpointType const& ep, asio::error_code& ec_) : endpoint(ep) - , error_handler(error_handler) + , ec(ec_) {} template void operator()(T* p) const - { - p->bind(endpoint, error_handler); - } + { p->bind(endpoint, ec); } - void operator()(boost::blank) const - {} + void operator()(boost::blank) const {} EndpointType const& endpoint; - Error_Handler const& error_handler; + asio::error_code& ec; }; template - struct bind_visitor + struct bind_visitor : boost::static_visitor<> { bind_visitor(EndpointType const& ep) @@ -140,42 +137,36 @@ namespace aux template void operator()(T* p) const - { - p->bind(endpoint); - } + { p->bind(endpoint); } - void operator()(boost::blank) const - {} + void operator()(boost::blank) const {} EndpointType const& endpoint; }; // -------------- open ----------- - template - struct open_visitor + template + struct open_visitor_ec : boost::static_visitor<> { - open_visitor(Protocol const& p, Error_Handler const& error_handler) + open_visitor_ec(Protocol const& p, asio::error_code& ec_) : proto(p) - , error_handler(error_handler) + , ec(ec_) {} template void operator()(T* p) const - { - p->open(proto, error_handler); - } + { p->open(proto, ec); } - void operator()(boost::blank) const - {} + void operator()(boost::blank) const {} Protocol const& proto; - Error_Handler const& error_handler; + asio::error_code& ec; }; template - struct open_visitor + struct open_visitor : boost::static_visitor<> { open_visitor(Protocol const& p) @@ -184,50 +175,39 @@ namespace aux template void operator()(T* p) const - { - p->open(proto); - } + { p->open(proto); } - void operator()(boost::blank) const - {} + void operator()(boost::blank) const {} Protocol const& proto; }; // -------------- close ----------- - template + struct close_visitor_ec + : boost::static_visitor<> + { + close_visitor_ec(asio::error_code& ec_) + : ec(ec_) + {} + + template + void operator()(T* p) const + { p->close(ec); } + + void operator()(boost::blank) const {} + + asio::error_code& ec; + }; + struct close_visitor : boost::static_visitor<> { - close_visitor(Error_Handler const& error_handler) - : error_handler(error_handler) - {} - template void operator()(T* p) const - { - p->close(error_handler); - } + { p->close(); } - void operator()(boost::blank) const - {} - - Error_Handler const& error_handler; - }; - - template <> - struct close_visitor - : boost::static_visitor<> - { - template - void operator()(T* p) const - { - p->close(); - } - - void operator()(boost::blank) const - {} + void operator()(boost::blank) const {} }; // -------------- remote_endpoint ----------- @@ -242,14 +222,10 @@ namespace aux template EndpointType operator()(T* p) const - { - return p->remote_endpoint(error_code); - } + { return p->remote_endpoint(error_code); } EndpointType operator()(boost::blank) const - { - return EndpointType(); - } + { return EndpointType(); } asio::error_code& error_code; }; @@ -260,14 +236,10 @@ namespace aux { template EndpointType operator()(T* p) const - { - return p->remote_endpoint(); - } + { return p->remote_endpoint(); } EndpointType operator()(boost::blank) const - { - return EndpointType(); - } + { return EndpointType(); } }; // -------------- local_endpoint ----------- @@ -357,9 +329,9 @@ namespace aux struct read_some_visitor_ec : boost::static_visitor { - read_some_visitor_ec(Mutable_Buffers const& buffers, asio::error_code& ec) + read_some_visitor_ec(Mutable_Buffers const& buffers, asio::error_code& ec_) : buffers(buffers) - , ec(ec) + , ec(ec_) {} template @@ -399,18 +371,17 @@ namespace aux // -------------- in_avail ----------- - template - struct in_avail_visitor + struct in_avail_visitor_ec : boost::static_visitor { - in_avail_visitor(Error_Handler const& error_handler) - : error_handler(error_handler) + in_avail_visitor_ec(asio::error_code& ec_) + : ec(ec_) {} template std::size_t operator()(T* p) const { - return p->in_avail(error_handler); + return p->in_avail(ec); } std::size_t operator()(boost::blank) const @@ -418,11 +389,10 @@ namespace aux return 0; } - Error_Handler const& error_handler; + asio::error_code& ec; }; - template <> - struct in_avail_visitor + struct in_avail_visitor : boost::static_visitor { template @@ -501,15 +471,12 @@ public: typedef typename S0::endpoint_type endpoint_type; typedef typename S0::protocol_type protocol_type; - explicit variant_stream(asio::io_service& io_service) - : m_io_service(io_service) - , m_variant(boost::blank()) - {} + explicit variant_stream() : m_variant(boost::blank()) {} template - void instantiate() + void instantiate(asio::io_service& ios) { - std::auto_ptr owned(new S(m_io_service)); + std::auto_ptr owned(new S(ios)); boost::apply_visitor(aux::delete_visitor(), m_variant); m_variant = owned.get(); owned.release(); @@ -605,12 +572,11 @@ public: boost::apply_visitor(aux::bind_visitor(endpoint), m_variant); } - template - void bind(endpoint_type const& endpoint, Error_Handler const& error_handler) + void bind(endpoint_type const& endpoint, asio::error_code& ec) { TORRENT_ASSERT(instantiated()); boost::apply_visitor( - aux::bind_visitor(endpoint, error_handler), m_variant + aux::bind_visitor_ec(endpoint, ec), m_variant ); } @@ -620,42 +586,39 @@ public: boost::apply_visitor(aux::open_visitor(p), m_variant); } - template - void open(protocol_type const& p, Error_Handler const& error_handler) + void open(protocol_type const& p, asio::error_code& ec) { TORRENT_ASSERT(instantiated()); boost::apply_visitor( - aux::open_visitor(p, error_handler), m_variant + aux::open_visitor_ec(p, ec), m_variant ); } void close() { - TORRENT_ASSERT(instantiated()); - boost::apply_visitor(aux::close_visitor<>(), m_variant); + if (!instantiated()) return; + boost::apply_visitor(aux::close_visitor(), m_variant); } - template - void close(Error_Handler const& error_handler) + void close(asio::error_code& ec) { - TORRENT_ASSERT(instantiated()); + if (!instantiated()) return; boost::apply_visitor( - aux::close_visitor(error_handler), m_variant + aux::close_visitor_ec(ec), m_variant ); } std::size_t in_avail() { TORRENT_ASSERT(instantiated()); - return boost::apply_visitor(aux::in_avail_visitor<>(), m_variant); + return boost::apply_visitor(aux::in_avail_visitor(), m_variant); } - template - std::size_t in_avail(Error_Handler const& error_handler) + std::size_t in_avail(asio::error_code& ec) { TORRENT_ASSERT(instantiated()); return boost::apply_visitor( - aux::in_avail_visitor(error_handler), m_variant + aux::in_avail_visitor_ec(ec), m_variant ); } @@ -704,7 +667,6 @@ public: } private: - asio::io_service& m_io_service; variant_type m_variant; }; diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 4c2e9397c..a9d27eff4 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -190,6 +190,8 @@ namespace libtorrent void broadcast_socket::close() { + m_on_receive.clear(); + for (std::list::iterator i = m_sockets.begin() , end(m_sockets.end()); i != end; ++i) { diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 0559aff95..d7b3226ec 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -582,9 +582,8 @@ namespace libtorrent { TORRENT_ASSERT(buf); TORRENT_ASSERT(size > 0); - TORRENT_ASSERT(!m_rc4_encrypted || m_encrypted); - if (m_rc4_encrypted) + if (m_encrypted && m_rc4_encrypted) m_RC4_handler->encrypt(buf, size); peer_connection::send_buffer(buf, size); @@ -592,9 +591,7 @@ namespace libtorrent buffer::interval bt_peer_connection::allocate_send_buffer(int size) { - TORRENT_ASSERT(!m_rc4_encrypted || m_encrypted); - - if (m_rc4_encrypted) + if (m_encrypted && m_rc4_encrypted) { TORRENT_ASSERT(m_enc_send_buffer.left() == 0); m_enc_send_buffer = peer_connection::allocate_send_buffer(size); @@ -609,9 +606,7 @@ namespace libtorrent void bt_peer_connection::setup_send() { - TORRENT_ASSERT(!m_rc4_encrypted || m_encrypted); - - if (m_rc4_encrypted && m_enc_send_buffer.left()) + if (m_encrypted && m_rc4_encrypted && m_enc_send_buffer.left()) { TORRENT_ASSERT(m_enc_send_buffer.begin); TORRENT_ASSERT(m_enc_send_buffer.end); @@ -1208,11 +1203,11 @@ namespace libtorrent // there is supposed to be a remote listen port if (entry* listen_port = root.find_key("p")) { - if (listen_port->type() == entry::int_t) + if (listen_port->type() == entry::int_t + && peer_info_struct() != 0) { - tcp::endpoint adr(remote().address() - , (unsigned short)listen_port->integer()); - t->get_policy().peer_from_tracker(adr, pid(), peer_info::incoming, 0); + t->get_policy().update_peer_port(listen_port->integer() + , peer_info_struct(), peer_info::incoming); } } // there should be a version too diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index 1caeb99fc..a48456ed5 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -86,6 +86,11 @@ namespace libtorrent try_connect(); } + void connection_queue::close() + { + m_timer.cancel(); + } + void connection_queue::limit(int limit) { m_half_open_limit = limit; } @@ -111,8 +116,14 @@ namespace libtorrent { INVARIANT_CHECK; - if (!free_slots() || m_queue.empty()) + if (!free_slots()) return; + + if (m_queue.empty()) + { + m_timer.cancel(); + return; + } std::list::iterator i = std::find_if(m_queue.begin() , m_queue.end(), boost::bind(&entry::connecting, _1) == false); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 22ee12179..a9446f664 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -70,6 +70,31 @@ namespace libtorrent m_disk_io_thread.join(); } +#ifndef NDEBUG + disk_io_job disk_io_thread::find_job(boost::intrusive_ptr s + , int action, int piece) const + { + boost::mutex::scoped_lock l(m_mutex); + for (std::deque::const_iterator i = m_jobs.begin(); + i != m_jobs.end(); ++i) + { + if (i->storage != s) + continue; + if ((i->action == action || action == -1) && i->piece == piece) + return *i; + } + if ((m_current.action == action || action == -1) + && m_current.piece == piece) + return m_current; + + disk_io_job ret; + ret.action = (disk_io_job::action_t)-1; + ret.piece = -1; + return ret; + } + +#endif + // aborts read operations void disk_io_thread::stop(boost::intrusive_ptr s) { @@ -205,12 +230,19 @@ namespace libtorrent m_log << log_time() << " idle" << std::endl; #endif boost::mutex::scoped_lock l(m_mutex); +#ifndef NDEBUG + m_current.action = (disk_io_job::action_t)-1; + m_current.piece = -1; +#endif while (m_jobs.empty() && !m_abort) m_signal.wait(l); if (m_abort && m_jobs.empty()) return; boost::function handler; handler.swap(m_jobs.front().callback); +#ifndef NDEBUG + m_current = m_jobs.front(); +#endif disk_io_job j = m_jobs.front(); m_jobs.pop_front(); m_queue_buffer_size -= j.buffer_size; @@ -309,6 +341,11 @@ namespace libtorrent // else std::cerr << "DISK THREAD: invoking callback" << std::endl; try { if (handler) handler(ret, j); } catch (std::exception&) {} + +#ifndef NDEBUG + m_current.storage = 0; + m_current.callback.clear(); +#endif if (j.buffer && free_buffer) { diff --git a/libtorrent/src/entry.cpp b/libtorrent/src/entry.cpp index 50c6967cc..88800713c 100755 --- a/libtorrent/src/entry.cpp +++ b/libtorrent/src/entry.cpp @@ -126,6 +126,7 @@ namespace libtorrent return &i->second; } +#ifndef BOOST_NO_EXCEPTIONS const entry& entry::operator[](char const* key) const { dictionary_type::const_iterator i = dict().find(key); @@ -138,6 +139,7 @@ namespace libtorrent { return (*this)[key.c_str()]; } +#endif entry::entry(dictionary_type const& v) { diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index de1783b58..ed9823b83 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -489,20 +489,39 @@ namespace libtorrent , boost::lexical_cast(m_port)); m_name_lookup.async_resolve(q, m_strand.wrap( boost::bind(&http_tracker_connection::name_lookup, self(), _1, _2))); - set_timeout(m_settings.tracker_completion_timeout + set_timeout(req.event == tracker_request::stopped + ? m_settings.stop_tracker_timeout + : m_settings.tracker_completion_timeout , m_settings.tracker_receive_timeout); } void http_tracker_connection::on_timeout() { m_timed_out = true; - m_socket.reset(); + m_socket.close(); m_name_lookup.cancel(); if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); m_connection_ticket = -1; fail_timeout(); } + void http_tracker_connection::close() + { + asio::error_code ec; + m_socket.close(ec); + m_name_lookup.cancel(); + if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + m_timed_out = true; + tracker_connection::close(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr cb = requester(); + std::stringstream msg; + msg << "http_tracker_connection::close() " << m_man.num_requests(); + if (cb) cb->debug_log(msg.str()); +#endif + } + void http_tracker_connection::name_lookup(asio::error_code const& error , tcp::resolver::iterator i) try { @@ -550,18 +569,20 @@ namespace libtorrent } if (cb) cb->m_tracker_address = target_address; - m_socket = instantiate_connection(m_name_lookup.io_service(), m_proxy); + bool ret = instantiate_connection(m_strand.io_service(), m_proxy, m_socket); + + TORRENT_ASSERT(ret); if (m_proxy.type == proxy_settings::http || m_proxy.type == proxy_settings::http_pw) { // the tracker connection will talk immediately to // the proxy, without requiring CONNECT support - m_socket->get().set_no_connect(true); + m_socket.get().set_no_connect(true); } - m_socket->open(target_address.protocol()); - m_socket->bind(tcp::endpoint(bind_interface(), 0)); + m_socket.open(target_address.protocol()); + m_socket.bind(tcp::endpoint(bind_interface(), 0)); m_cc.enqueue(bind(&http_tracker_connection::connect, self(), _1, target_address) , bind(&http_tracker_connection::on_timeout, self()) , seconds(m_settings.tracker_receive_timeout)); @@ -574,7 +595,7 @@ namespace libtorrent void http_tracker_connection::connect(int ticket, tcp::endpoint target_address) { m_connection_ticket = ticket; - m_socket->async_connect(target_address, bind(&http_tracker_connection::connected, self(), _1)); + m_socket.async_connect(target_address, bind(&http_tracker_connection::connected, self(), _1)); } void http_tracker_connection::connected(asio::error_code const& error) try @@ -595,7 +616,7 @@ namespace libtorrent #endif restart_read_timeout(); - async_write(*m_socket, asio::buffer(m_send_buffer.c_str() + async_write(m_socket, asio::buffer(m_send_buffer.c_str() , m_send_buffer.size()), bind(&http_tracker_connection::sent , self(), _1)); } @@ -620,7 +641,7 @@ namespace libtorrent #endif restart_read_timeout(); TORRENT_ASSERT(m_buffer.size() - m_recv_pos > 0); - m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] + m_socket.async_read_some(asio::buffer(&m_buffer[m_recv_pos] , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive , self(), _1, _2)); } @@ -701,7 +722,7 @@ namespace libtorrent } TORRENT_ASSERT(m_buffer.size() - m_recv_pos > 0); - m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] + m_socket.async_read_some(asio::buffer(&m_buffer[m_recv_pos] , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive , self(), _1, _2)); } @@ -757,7 +778,6 @@ namespace libtorrent if (m_parser.status_code() != 200) { fail(m_parser.status_code(), m_parser.message().c_str()); - close(); return; } @@ -819,6 +839,7 @@ namespace libtorrent TORRENT_ASSERT(false); } #endif + close(); } peer_entry http_tracker_connection::extract_peer_info(const entry& info) diff --git a/libtorrent/src/instantiate_connection.cpp b/libtorrent/src/instantiate_connection.cpp index 43b70f40d..f9c997fb1 100644 --- a/libtorrent/src/instantiate_connection.cpp +++ b/libtorrent/src/instantiate_connection.cpp @@ -42,42 +42,40 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - boost::shared_ptr instantiate_connection( - asio::io_service& ios, proxy_settings const& ps) + bool instantiate_connection(asio::io_service& ios + , proxy_settings const& ps, socket_type& s) { - boost::shared_ptr s(new socket_type(ios)); - if (ps.type == proxy_settings::none) { - s->instantiate(); + s.instantiate(ios); } else if (ps.type == proxy_settings::http || ps.type == proxy_settings::http_pw) { - s->instantiate(); - s->get().set_proxy(ps.hostname, ps.port); + s.instantiate(ios); + s.get().set_proxy(ps.hostname, ps.port); if (ps.type == proxy_settings::socks5_pw) - s->get().set_username(ps.username, ps.password); + s.get().set_username(ps.username, ps.password); } else if (ps.type == proxy_settings::socks5 || ps.type == proxy_settings::socks5_pw) { - s->instantiate(); - s->get().set_proxy(ps.hostname, ps.port); + s.instantiate(ios); + s.get().set_proxy(ps.hostname, ps.port); if (ps.type == proxy_settings::socks5_pw) - s->get().set_username(ps.username, ps.password); + s.get().set_username(ps.username, ps.password); } else if (ps.type == proxy_settings::socks4) { - s->instantiate(); - s->get().set_proxy(ps.hostname, ps.port); - s->get().set_username(ps.username); + s.instantiate(ios); + s.get().set_proxy(ps.hostname, ps.port); + s.get().set_username(ps.username); } else { - throw std::runtime_error("unsupported proxy type"); + return false; } - return s; + return true; } } diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index 44d7b19d4..6e1dcb7b3 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -184,5 +184,6 @@ void lsd::on_announce(udp::endpoint const& from, char* buffer void lsd::close() { m_socket.close(); + m_broadcast_timer.cancel(); } diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index e02a2d758..50dc57ec7 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -504,11 +504,10 @@ namespace libtorrent { namespace // extension and that has metadata int peers = 0; #ifndef TORRENT_DISABLE_EXTENSIONS - typedef std::map conn_map; - for (conn_map::iterator i = m_torrent.begin() + for (torrent::peer_iterator i = m_torrent.begin() , end(m_torrent.end()); i != end; ++i) { - bt_peer_connection* c = dynamic_cast(i->second); + bt_peer_connection* c = dynamic_cast(*i); if (c == 0) continue; metadata_peer_plugin* p = c->supports_extension(); diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index 42cf89e37..38319d18f 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -382,6 +382,8 @@ void natpmp::try_next_mapping(int i) void natpmp::close() { + asio::error_code ec; + m_socket.close(ec); if (m_disabled) return; for (int i = 0; i < num_mappings; ++i) { @@ -390,5 +392,7 @@ void natpmp::close() m_mappings[i].external_port = 0; refresh_mapping(i); } + m_refresh_timer.cancel(); + m_send_timer.cancel(); } diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 625b8eacd..9bd089234 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -390,7 +390,6 @@ namespace libtorrent TORRENT_ASSERT(m_peer_info->connection == 0); boost::shared_ptr t = m_torrent.lock(); - if (t) TORRENT_ASSERT(t->connection_for(remote()) != this); #endif } @@ -1373,21 +1372,28 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + INVARIANT_CHECK; + m_outstanding_writing_bytes -= p.length; TORRENT_ASSERT(m_outstanding_writing_bytes >= 0); -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " *** on_disk_write_complete() " << p.length << "\n"; +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << time_now_string() << " *** DISK_WRITE_COMPLETE [ p: " + << p.piece << " o: " << p.start << " ]\n"; #endif // in case the outstanding bytes just dropped down // to allow to receive more data setup_receive(); + piece_block block_finished(p.piece, p.start / t->block_size()); + if (ret == -1 || !t) { + if (t->has_picker()) t->picker().abort_download(block_finished); + if (!t) { - m_ses.connection_failed(m_socket, remote(), j.str.c_str()); + m_ses.connection_failed(self(), remote(), j.str.c_str()); return; } @@ -1406,7 +1412,6 @@ namespace libtorrent TORRENT_ASSERT(p.piece == j.piece); TORRENT_ASSERT(p.start == j.offset); - piece_block block_finished(p.piece, p.start / t->block_size()); picker.mark_as_finished(block_finished, peer_info_struct()); if (t->alerts().should_post(alert::debug)) { @@ -1414,13 +1419,6 @@ namespace libtorrent block_finished.block_index, block_finished.piece_index, "block finished")); } - if (!t->is_seed() && !m_torrent.expired()) - { - // this is a free function defined in policy.cpp - request_a_block(*t, *this); - send_block_requests(); - } - #ifndef NDEBUG try { @@ -1444,6 +1442,14 @@ namespace libtorrent TORRENT_ASSERT(false); } #endif + + if (!t->is_seed() && !m_torrent.expired()) + { + // this is a free function defined in policy.cpp + request_a_block(*t, *this); + send_block_requests(); + } + } // ----------------------------- @@ -1918,7 +1924,8 @@ namespace libtorrent "s: " << r.start << " | " "l: " << r.length << " | " "ds: " << statistics().download_rate() << " B/s | " - "qs: " << m_desired_queue_size << " ]\n"; + "qs: " << m_desired_queue_size << " " + "blk: " << (m_request_large_blocks?"large":"single") << " ]\n"; #endif } m_last_piece = time_now(); @@ -1936,7 +1943,7 @@ namespace libtorrent (*m_ses.m_logger) << "CONNECTION TIMED OUT: " << m_remote.address().to_string() << "\n"; #endif - m_ses.connection_failed(m_socket, m_remote, "timed out"); + m_ses.connection_failed(self(), m_remote, "timed out"); } void peer_connection::disconnect() @@ -2201,18 +2208,19 @@ namespace libtorrent piece_picker& picker = t->picker(); while (!m_download_queue.empty()) { - picker.abort_download(m_download_queue.back()); + piece_block const& r = m_download_queue.back(); + picker.abort_download(r); + write_cancel(t->to_req(r)); m_download_queue.pop_back(); } while (!m_request_queue.empty()) { - picker.abort_download(m_request_queue.back()); + piece_block const& r = m_request_queue.back(); + picker.abort_download(r); + write_cancel(t->to_req(r)); m_request_queue.pop_back(); } - // TODO: If we have a limited number of upload - // slots, choke this peer - m_assume_fifo = true; request_a_block(*t, *this); @@ -2281,7 +2289,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "**ERROR**: " << e.what() << "\n"; #endif - m_ses.connection_failed(m_socket, remote(), e.what()); + m_ses.connection_failed(self(), remote(), e.what()); } } @@ -2331,7 +2339,7 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); if (!t) { - m_ses.connection_failed(m_socket, remote(), j.str.c_str()); + m_ses.connection_failed(self(), remote(), j.str.c_str()); return; } @@ -2667,7 +2675,7 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); if (!t) { - m_ses.connection_failed(m_socket, remote(), e.what()); + m_ses.connection_failed(self(), remote(), e.what()); return; } @@ -2682,14 +2690,14 @@ namespace libtorrent catch (std::exception& e) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(m_socket, remote(), e.what()); + m_ses.connection_failed(self(), remote(), e.what()); } catch (...) { // all exceptions should derive from std::exception TORRENT_ASSERT(false); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(m_socket, remote(), "connection failed for unknown reason"); + m_ses.connection_failed(self(), remote(), "connection failed for unknown reason"); } bool peer_connection::can_write() const @@ -2770,7 +2778,7 @@ namespace libtorrent (*m_ses.m_logger) << "CONNECTION FAILED: " << m_remote.address().to_string() << ": " << e.message() << "\n"; #endif - m_ses.connection_failed(m_socket, m_remote, e.message().c_str()); + m_ses.connection_failed(self(), m_remote, e.message().c_str()); return; } @@ -2790,14 +2798,14 @@ namespace libtorrent catch (std::exception& ex) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(m_socket, remote(), ex.what()); + m_ses.connection_failed(self(), remote(), ex.what()); } catch (...) { // all exceptions should derive from std::exception TORRENT_ASSERT(false); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(m_socket, remote(), "connection failed for unkown reason"); + m_ses.connection_failed(self(), remote(), "connection failed for unkown reason"); } // -------------------------- @@ -2847,14 +2855,14 @@ namespace libtorrent catch (std::exception& e) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(m_socket, remote(), e.what()); + m_ses.connection_failed(self(), remote(), e.what()); } catch (...) { // all exceptions should derive from std::exception TORRENT_ASSERT(false); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(m_socket, remote(), "connection failed for unknown reason"); + m_ses.connection_failed(self(), remote(), "connection failed for unknown reason"); } @@ -2871,26 +2879,47 @@ namespace libtorrent } boost::shared_ptr t = m_torrent.lock(); - if (!t) + if (!t) return; + + if (m_peer_info) { - typedef session_impl::torrent_map torrent_map; - torrent_map& m = m_ses.m_torrents; - for (torrent_map::iterator i = m.begin(), end(m.end()); i != end; ++i) + policy::const_iterator i; + for (i = t->get_policy().begin_peer(); + i != t->get_policy().end_peer(); ++i) { - torrent& t = *i->second; - TORRENT_ASSERT(t.connection_for(m_remote) != this); + if (&i->second == m_peer_info) break; } - return; + TORRENT_ASSERT(i != t->get_policy().end_peer()); } - - TORRENT_ASSERT(t->connection_for(remote()) != 0 || m_in_constructor); - - if (!m_in_constructor && t->connection_for(remote()) != this - && !m_ses.settings().allow_multiple_connections_per_ip) + if (t->has_picker() && !t->is_aborted()) { - TORRENT_ASSERT(false); - } + // make sure that pieces that have completed the download + // of all their blocks are in the disk io thread's queue + // to be checked. + const std::vector& dl_queue + = t->picker().get_download_queue(); + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + const int blocks_per_piece = t->picker().blocks_in_piece(i->index); + bool complete = true; + for (int j = 0; j < blocks_per_piece; ++j) + { + if (i->info[j].state == piece_picker::block_info::state_finished) + continue; + complete = false; + break; + } + if (complete) + { + disk_io_job ret = m_ses.m_disk_thread.find_job( + &t->filesystem(), -1, i->index); + TORRENT_ASSERT(ret.action == disk_io_job::hash || ret.action == disk_io_job::write); + TORRENT_ASSERT(ret.piece == i->index); + } + } + } // expensive when using checked iterators /* if (t->valid_metadata()) @@ -2953,11 +2982,24 @@ namespace libtorrent // time, it is considered to have timed out time_duration d; d = now - m_last_receive; - if (d > seconds(m_timeout)) return true; + if (d > seconds(m_timeout)) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** LAST ACTIVITY [ " + << total_seconds(d) << " seconds ago ] ***\n"; +#endif + return true; + } - // if it takes more than 5 seconds to receive - // handshake, disconnect - if (in_handshake() && d > seconds(5)) return true; + // do not stall waiting for a handshake + if (in_handshake() && d > seconds(m_ses.settings().handshake_timeout)) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** NO HANDSHAKE [ waited " + << total_seconds(d) << " seconds ] ***\n"; +#endif + return true; + } // disconnect peers that we unchoked, but // they didn't send a request within 20 seconds. @@ -2968,7 +3010,14 @@ namespace libtorrent && !m_choked && m_peer_interested && t && t->is_finished() - && d > seconds(20)) return true; + && d > seconds(20)) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** NO REQUEST [ t: " + << total_seconds(d) << " ] ***\n"; +#endif + return true; + } // TODO: as long as we have less than 95% of the // global (or local) connection limit, connections should @@ -2984,11 +3033,21 @@ namespace libtorrent time_duration time_limit = seconds( m_ses.settings().inactivity_timeout); + // don't bother disconnect peers we haven't been intersted + // in (and that hasn't been interested in us) for a while + // unless we have used up all our connection slots if (!m_interesting && !m_peer_interested && d1 > time_limit - && d2 > time_limit) + && d2 > time_limit + && (m_ses.num_connections() >= m_ses.max_connections() + || (t && t->num_peers() >= t->max_connections()))) { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** MUTUAL NO INTEREST [ " + "t1: " << total_seconds(d1) << " | " + "t2: " << total_seconds(d2) << " ] ***\n"; +#endif return true; } @@ -3031,10 +3090,9 @@ namespace libtorrent if (m_writing) return; #ifdef TORRENT_VERBOSE_LOGGING - using namespace boost::posix_time; (*m_logger) << time_now_string() << " ==> KEEPALIVE\n"; #endif - + m_last_sent = time_now(); write_keepalive(); } diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index ebfe07637..831bd0986 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -962,16 +962,16 @@ namespace libtorrent int priority = p.priority(m_sequenced_download_threshold); TORRENT_ASSERT(priority < int(m_piece_info.size())); - TORRENT_ASSERT(p.downloading == 1); - TORRENT_ASSERT(!p.have()); - - std::vector::iterator i - = std::find_if(m_downloads.begin() - , m_downloads.end() - , has_index(index)); - TORRENT_ASSERT(i != m_downloads.end()); - erase_download_piece(i); - p.downloading = 0; + if (p.downloading) + { + std::vector::iterator i + = std::find_if(m_downloads.begin() + , m_downloads.end() + , has_index(index)); + TORRENT_ASSERT(i != m_downloads.end()); + erase_download_piece(i); + p.downloading = 0; + } TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(index)) == m_downloads.end()); diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 4de01d055..f203eaa56 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -82,13 +82,13 @@ namespace // want to trade it's surplus uploads for downloads itself // (and we should not consider it free). If the share diff is // negative, there's no free download to get from this peer. - size_type diff = i->second->share_diff(); + size_type diff = (*i)->share_diff(); TORRENT_ASSERT(diff < (std::numeric_limits::max)()); - if (i->second->is_peer_interested() || diff <= 0) + if ((*i)->is_peer_interested() || diff <= 0) continue; TORRENT_ASSERT(diff > 0); - i->second->add_free_upload(-diff); + (*i)->add_free_upload(-diff); accumulator += diff; TORRENT_ASSERT(accumulator > 0); } @@ -109,10 +109,10 @@ namespace size_type total_diff = 0; for (torrent::peer_iterator i = start; i != end; ++i) { - size_type d = i->second->share_diff(); + size_type d = (*i)->share_diff(); TORRENT_ASSERT(d < (std::numeric_limits::max)()); total_diff += d; - if (!i->second->is_peer_interested() || i->second->share_diff() >= 0) continue; + if (!(*i)->is_peer_interested() || (*i)->share_diff() >= 0) continue; ++num_peers; } @@ -130,7 +130,7 @@ namespace for (torrent::peer_iterator i = start; i != end; ++i) { - peer_connection* p = i->second; + peer_connection* p = *i; if (!p->is_peer_interested() || p->share_diff() >= 0) continue; p->add_free_upload(upload_share); free_upload -= upload_share; @@ -904,7 +904,7 @@ namespace libtorrent { TORRENT_ASSERT(!c.is_local()); - INVARIANT_CHECK; +// INVARIANT_CHECK; // if the connection comes from the tracker, // it's probably just a NAT-check. Ignore the @@ -932,10 +932,11 @@ namespace libtorrent if (m_torrent->settings().allow_multiple_connections_per_ip) { - i = std::find_if( - m_peers.begin() - , m_peers.end() - , match_peer_connection(c)); + tcp::endpoint remote = c.remote(); + std::pair range = m_peers.equal_range(remote.address()); + i = std::find_if(range.first, range.second, match_peer_endpoint(remote)); + + if (i == range.second) i = m_peers.end(); } else { @@ -977,8 +978,6 @@ namespace libtorrent i = m_peers.insert(std::make_pair(c.remote().address(), p)); } - TORRENT_ASSERT(m_torrent->connection_for(c.remote()) == &c); - c.set_peer_info(&i->second); TORRENT_ASSERT(i->second.connection == 0); c.add_stat(i->second.prev_amount_download, i->second.prev_amount_upload); @@ -991,7 +990,38 @@ namespace libtorrent // m_last_optimistic_disconnect = time_now(); } - policy::peer* policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid + void policy::update_peer_port(int port, policy::peer* p, int src) + { + TORRENT_ASSERT(p != 0); + if (p->ip.port() == port) return; + + if (m_torrent->settings().allow_multiple_connections_per_ip) + { + tcp::endpoint remote(p->ip.address(), port); + std::pair range = m_peers.equal_range(remote.address()); + iterator i = std::find_if(range.first, range.second + , match_peer_endpoint(remote)); + if (i != m_peers.end()) + { + policy::peer& pp = i->second; + if (pp.connection) + { + throw protocol_error("duplicate connection"); + } + if (m_torrent->has_picker()) + m_torrent->picker().clear_peer(&i->second); + m_peers.erase(i); + } + } + else + { + TORRENT_ASSERT(m_peers.count(p->ip.address()) == 1); + } + p->ip.port(port); + p->source |= src; + } + + policy::peer* policy::peer_from_tracker(tcp::endpoint const& remote, peer_id const& pid , int src, char flags) { // too expensive @@ -1022,9 +1052,7 @@ namespace libtorrent { std::pair range = m_peers.equal_range(remote.address()); i = std::find_if(range.first, range.second, match_peer_endpoint(remote)); - - if (i == range.second) - i = std::find_if(m_peers.begin(), m_peers.end(), match_peer_id(pid)); + if (i == range.second) i = m_peers.end(); } else { @@ -1066,9 +1094,6 @@ namespace libtorrent { i->second.type = peer::connectable; - // in case we got the ip from a remote connection, port is - // not known, so save it. Client may also have changed port - // for some reason. i->second.ip = remote; i->second.source |= src; @@ -1281,10 +1306,7 @@ namespace libtorrent try { - p->second.connected = time_now(); - p->second.connection = m_torrent->connect_to_peer(&p->second); - TORRENT_ASSERT(p->second.connection == m_torrent->connection_for(p->second.ip)); - if (p->second.connection == 0) + if (!m_torrent->connect_to_peer(&p->second)) { ++p->second.failcount; return false; @@ -1396,6 +1418,7 @@ namespace libtorrent void policy::check_invariant() const { if (m_torrent->is_aborted()) return; + int connected_peers = 0; int total_connections = 0; @@ -1414,20 +1437,14 @@ namespace libtorrent { TORRENT_ASSERT(unique_test.count(p.ip) == 0); unique_test.insert(p.ip); + TORRENT_ASSERT(i->first == p.ip.address()); +// TORRENT_ASSERT(p.connection == 0 || p.ip == p.connection->remote()); } ++total_connections; if (!p.connection) { continue; } - if (!m_torrent->settings().allow_multiple_connections_per_ip) - { - std::vector conns; - m_torrent->connection_for(p.ip.address(), conns); - TORRENT_ASSERT(std::find_if(conns.begin(), conns.end() - , boost::bind(std::equal_to(), _1, p.connection)) - != conns.end()); - } if (p.optimistically_unchoked) { TORRENT_ASSERT(p.connection); @@ -1444,10 +1461,10 @@ namespace libtorrent for (torrent::const_peer_iterator i = m_torrent->begin(); i != m_torrent->end(); ++i) { - if (i->second->is_disconnecting()) continue; + if ((*i)->is_disconnecting()) continue; // ignore web_peer_connections since they are not managed // by the policy class - if (dynamic_cast(i->second)) continue; + if (dynamic_cast(*i)) continue; ++num_torrent_peers; } diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 63c039010..b3eba0bf7 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -89,6 +89,10 @@ namespace } openssl_global_destructor; } +#endif +#ifdef _WIN32 +// for ERROR_SEM_TIMEOUT +#include #endif using boost::shared_ptr; @@ -543,8 +547,8 @@ namespace detail , fingerprint const& cl_fprint , char const* listen_interface) : m_send_buffers(send_buffer_size) - , m_strand(m_io_service) , m_files(40) + , m_strand(m_io_service) , m_half_open(m_io_service) , m_download_channel(m_io_service, peer_connection::download_channel) , m_upload_channel(m_io_service, peer_connection::upload_channel) @@ -671,6 +675,17 @@ namespace detail if (m_dht) m_dht->stop(); #endif m_timer.cancel(); + + // close the listen sockets + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + { + i->sock->close(); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " aborting all torrents\n"; +#endif // abort all torrents for (torrent_map::iterator i = m_torrents.begin() , end(m_torrents.end()); i != end; ++i) @@ -678,7 +693,68 @@ namespace detail i->second->abort(); } - m_io_service.stop(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " aborting all tracker requests\n"; +#endif + m_tracker_manager.abort_all_requests(); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " sending event=stopped to trackers\n"; + int counter = 0; +#endif + for (torrent_map::iterator i = m_torrents.begin(); + i != m_torrents.end(); ++i) + { + torrent& t = *i->second; + + if ((!t.is_paused() || t.should_request()) + && !t.trackers().empty()) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + ++counter; +#endif + tracker_request req = t.generate_tracker_request(); + TORRENT_ASSERT(req.event == tracker_request::stopped); + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; + req.key = m_key; + std::string login = i->second->tracker_login(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr tl(new tracker_logger(*this)); + m_tracker_loggers.push_back(tl); + m_tracker_manager.queue_request(m_strand, m_half_open, req, login + , m_listen_interface.address(), tl); +#else + m_tracker_manager.queue_request(m_strand, m_half_open, req, login + , m_listen_interface.address()); +#endif + } + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " sent " << counter << " tracker stop requests\n"; +#endif + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " aborting all connections (" << m_connections.size() << ")\n"; +#endif + // abort all connections + while (!m_connections.empty()) + { +#ifndef NDEBUG + int conn = m_connections.size(); +#endif + (*m_connections.begin())->disconnect(); + TORRENT_ASSERT(conn == int(m_connections.size()) + 1); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " shutting down connection queue\n"; +#endif + m_half_open.close(); + + m_download_channel.close(); + m_upload_channel.close(); mutex::scoped_lock l2(m_checker_impl.m_mutex); // abort the checker thread @@ -898,8 +974,8 @@ namespace detail void session_impl::async_accept(boost::shared_ptr const& listener) { - shared_ptr c(new socket_type(m_io_service)); - c->instantiate(); + shared_ptr c(new socket_type); + c->instantiate(m_io_service); listener->async_accept(c->get() , bind(&session_impl::on_incoming_connection, this, c , boost::weak_ptr(listener), _1)); @@ -924,6 +1000,15 @@ namespace detail std::string msg = "error accepting connection on '" + boost::lexical_cast(ep) + "' " + e.message(); (*m_logger) << msg << "\n"; +#endif +#ifdef _WIN32 + // Windows sometimes generates this error. It seems to be + // non-fatal and we have to do another async_accept. + if (e.value() == ERROR_SEM_TIMEOUT) + { + async_accept(listener); + return; + } #endif if (m_alerts.should_post(alert::fatal)) { @@ -964,6 +1049,17 @@ namespace detail return; } + // don't allow more connections than the max setting + if (num_connections() > max_connections()) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << "number of connections limit exceeded (conns: " + << num_connections() << ", limit: " << max_connections() + << "), connection rejected\n"; +#endif + return; + } + // check if we have any active torrents // if we don't reject the connection if (m_torrents.empty()) return; @@ -986,7 +1082,7 @@ namespace detail c->m_in_constructor = false; #endif - m_connections.insert(std::make_pair(s, c)); + m_connections.insert(c); } catch (std::exception& exc) { @@ -995,7 +1091,7 @@ namespace detail #endif }; - void session_impl::connection_failed(boost::shared_ptr const& s + void session_impl::connection_failed(boost::intrusive_ptr const& peer , tcp::endpoint const& a, char const* message) #ifndef NDEBUG try @@ -1006,7 +1102,7 @@ namespace detail // too expensive // INVARIANT_CHECK; - connection_map::iterator p = m_connections.find(s); + connection_map::iterator p = m_connections.find(peer); // the connection may have been disconnected in the receive or send phase if (p == m_connections.end()) return; @@ -1015,15 +1111,15 @@ namespace detail m_alerts.post_alert( peer_error_alert( a - , p->second->pid() + , (*p)->pid() , message)); } #if defined(TORRENT_VERBOSE_LOGGING) - (*p->second->m_logger) << "*** CONNECTION FAILED " << message << "\n"; + (*(*p)->m_logger) << "*** CONNECTION FAILED " << message << "\n"; #endif - p->second->set_failed(); - p->second->disconnect(); + (*p)->set_failed(); + (*p)->disconnect(); } #ifndef NDEBUG catch (...) @@ -1040,10 +1136,10 @@ namespace detail // INVARIANT_CHECK; TORRENT_ASSERT(p->is_disconnecting()); - connection_map::iterator i = m_connections.find(p->get_socket()); + connection_map::iterator i = m_connections.find(p); if (i != m_connections.end()) { - if (!i->second->is_choked()) --m_num_unchoked; + if (!(*i)->is_choked()) --m_num_unchoked; m_connections.erase(i); } } @@ -1102,7 +1198,7 @@ namespace detail for (connection_map::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - if (i->second->is_connecting()) + if ((*i)->is_connecting()) ++num_half_open; else ++num_complete_connections; @@ -1190,7 +1286,7 @@ namespace detail ++i; // if this socket has timed out // close it. - peer_connection& c = *j->second; + peer_connection& c = *j->get(); if (c.has_timed_out()) { if (m_alerts.should_post(alert::debug)) @@ -1257,7 +1353,7 @@ namespace detail for (connection_map::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - peer_connection* p = i->second.get(); + peer_connection* p = i->get(); torrent* t = p->associated_torrent().lock().get(); if (!p->peer_info_struct() || t == 0 @@ -1267,7 +1363,7 @@ namespace detail || (p->share_diff() < -free_upload_amount && !t->is_seed())) { - if (!i->second->is_choked() && t) + if (!(*i)->is_choked() && t) { policy::peer* pi = p->peer_info_struct(); if (pi && pi->optimistically_unchoked) @@ -1276,11 +1372,11 @@ namespace detail // force a new optimistic unchoke m_optimistic_unchoke_time_scaler = 0; } - t->choke_peer(*i->second); + t->choke_peer(*(*i)); } continue; } - peers.push_back(i->second.get()); + peers.push_back(i->get()); } // sort the peers that are eligible for unchoke by download rate and secondary @@ -1352,7 +1448,7 @@ namespace detail for (connection_map::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - peer_connection* p = i->second.get(); + peer_connection* p = i->get(); TORRENT_ASSERT(p); policy::peer* pi = p->peer_info_struct(); if (!pi) continue; @@ -1383,21 +1479,21 @@ namespace detail { if (current_optimistic_unchoke != m_connections.end()) { - torrent* t = current_optimistic_unchoke->second->associated_torrent().lock().get(); + torrent* t = (*current_optimistic_unchoke)->associated_torrent().lock().get(); TORRENT_ASSERT(t); - current_optimistic_unchoke->second->peer_info_struct()->optimistically_unchoked = false; - t->choke_peer(*current_optimistic_unchoke->second); + (*current_optimistic_unchoke)->peer_info_struct()->optimistically_unchoked = false; + t->choke_peer(*current_optimistic_unchoke->get()); } else { ++m_num_unchoked; } - torrent* t = optimistic_unchoke_candidate->second->associated_torrent().lock().get(); + torrent* t = (*optimistic_unchoke_candidate)->associated_torrent().lock().get(); TORRENT_ASSERT(t); - bool ret = t->unchoke_peer(*optimistic_unchoke_candidate->second); + bool ret = t->unchoke_peer(*optimistic_unchoke_candidate->get()); TORRENT_ASSERT(ret); - optimistic_unchoke_candidate->second->peer_info_struct()->optimistically_unchoked = true; + (*optimistic_unchoke_candidate)->peer_info_struct()->optimistically_unchoked = true; } } } @@ -1460,96 +1556,11 @@ namespace detail } while (!m_abort); - deadline_timer tracker_timer(m_io_service); - // this will remove the port mappings - if (m_natpmp.get()) - m_natpmp->close(); - if (m_upnp.get()) - m_upnp->close(); - #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " locking mutex\n"; #endif session_impl::mutex_t::scoped_lock l(m_mutex); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " aborting all tracker requests\n"; -#endif - m_tracker_manager.abort_all_requests(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " sending stopped to all torrent's trackers\n"; -#endif - for (std::map >::iterator i = - m_torrents.begin(); i != m_torrents.end(); ++i) - { - i->second->abort(); - // generate a tracker request in case the torrent is not paused - // (in which case it's not currently announced with the tracker) - // or if the torrent itself thinks we should request. Do not build - // a request in case the torrent doesn't have any trackers - if ((!i->second->is_paused() || i->second->should_request()) - && !i->second->trackers().empty()) - { - tracker_request req = i->second->generate_tracker_request(); - TORRENT_ASSERT(!m_listen_sockets.empty()); - req.listen_port = 0; - if (!m_listen_sockets.empty()) - req.listen_port = m_listen_sockets.front().external_port; - req.key = m_key; - std::string login = i->second->tracker_login(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr tl(new tracker_logger(*this)); - m_tracker_loggers.push_back(tl); - m_tracker_manager.queue_request(m_strand, m_half_open, req, login - , m_listen_interface.address(), tl); -#else - m_tracker_manager.queue_request(m_strand, m_half_open, req, login - , m_listen_interface.address()); -#endif - } - } - - // close the listen sockets - m_listen_sockets.clear(); - - ptime start(time_now()); - l.unlock(); - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " waiting for trackers to respond (" - << m_settings.stop_tracker_timeout << " seconds timeout)\n"; -#endif - - while (time_now() - start < seconds( - m_settings.stop_tracker_timeout) - && !m_tracker_manager.empty()) - { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " " << m_tracker_manager.num_requests() - << " tracker requests pending\n"; -#endif - tracker_timer.expires_from_now(milliseconds(100)); - tracker_timer.async_wait(m_strand.wrap( - bind(&io_service::stop, &m_io_service))); - - m_io_service.reset(); - m_io_service.run(); - } - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " tracker shutdown complete, locking mutex\n"; -#endif - - l.lock(); - TORRENT_ASSERT(m_abort); - m_abort = true; - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " cleaning up connections\n"; -#endif - while (!m_connections.empty()) - m_connections.begin()->second->disconnect(); - #ifndef NDEBUG for (torrent_map::iterator i = m_torrents.begin(); i != m_torrents.end(); ++i) @@ -2077,8 +2088,8 @@ namespace detail entry session_impl::dht_state() const { - TORRENT_ASSERT(m_dht); mutex_t::scoped_lock l(m_mutex); + if (!m_dht) return entry(); return m_dht->state(); } @@ -2114,16 +2125,10 @@ namespace detail session_impl::~session_impl() { - abort(); - #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << "\n\n *** shutting down session *** \n\n"; #endif - // lock the main thread and abort it - mutex_t::scoped_lock l(m_mutex); - m_abort = true; - m_io_service.stop(); - l.unlock(); + abort(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " waiting for main thread\n"; @@ -2364,19 +2369,20 @@ namespace detail for (connection_map::const_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - TORRENT_ASSERT(i->second); - boost::shared_ptr t = i->second->associated_torrent().lock(); + TORRENT_ASSERT(*i); + boost::shared_ptr t = (*i)->associated_torrent().lock(); - if (!i->second->is_choked()) ++unchokes; - if (i->second->peer_info_struct() - && i->second->peer_info_struct()->optimistically_unchoked) + peer_connection* p = i->get(); + if (!p->is_choked()) ++unchokes; + if (p->peer_info_struct() + && p->peer_info_struct()->optimistically_unchoked) { ++num_optimistic; - TORRENT_ASSERT(!i->second->is_choked()); + TORRENT_ASSERT(!p->is_choked()); } - if (t && i->second->peer_info_struct()) + if (t && p->peer_info_struct()) { - TORRENT_ASSERT(t->get_policy().has_connection(boost::get_pointer(i->second))); + TORRENT_ASSERT(t->get_policy().has_connection(p)); } } TORRENT_ASSERT(num_optimistic == 0 || num_optimistic == 1); diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 6671e38e9..0468684f3 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -99,6 +99,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include "libtorrent/utf8.hpp" +#include "libtorrent/buffer.hpp" namespace libtorrent { @@ -386,7 +387,7 @@ namespace libtorrent file_pool& m_files; // temporary storage for moving pieces - std::vector m_scratch_buffer; + buffer m_scratch_buffer; }; sha1_hash storage::hash_for_slot(int slot, partial_hash& ph, int piece_size) @@ -468,14 +469,14 @@ namespace libtorrent void storage::release_files() { m_files.release(this); - std::vector().swap(m_scratch_buffer); + buffer().swap(m_scratch_buffer); } void storage::delete_files() { // make sure we don't have the files open m_files.release(this); - std::vector().swap(m_scratch_buffer); + buffer().swap(m_scratch_buffer); // delete the files from disk std::set directories; @@ -485,11 +486,12 @@ namespace libtorrent { std::string p = (m_save_path / i->path).string(); fs::path bp = i->path.branch_path(); - std::pair ret = directories.insert(bp.string()); + std::pair ret; + ret.second = true; while (ret.second && !bp.empty()) { + std::pair ret = directories.insert((m_save_path / bp).string()); bp = bp.branch_path(); - std::pair ret = directories.insert(bp.string()); } std::remove(p.c_str()); } @@ -498,9 +500,6 @@ namespace libtorrent // subdirectories first std::for_each(directories.rbegin(), directories.rend() , bind((int(*)(char const*))&std::remove, bind(&std::string::c_str, _1))); - - std::string p = (m_save_path / m_info->name()).string(); - std::remove(p.c_str()); } void storage::write_resume_data(entry& rd) const @@ -977,6 +976,7 @@ namespace libtorrent , m_storage_mode(storage_mode_sparse) , m_info(ti) , m_save_path(complete(save_path)) + , m_state(state_none) , m_current_slot(0) , m_out_of_place(false) , m_scratch_piece(-1) @@ -1624,8 +1624,8 @@ namespace libtorrent if (m_current_slot == m_info->num_pieces()) { m_state = state_create_files; - std::vector().swap(m_scratch_buffer); - std::vector().swap(m_scratch_buffer2); + buffer().swap(m_scratch_buffer); + buffer().swap(m_scratch_buffer2); if (m_storage_mode != storage_mode_compact) { std::vector().swap(m_piece_to_slot); diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 840e488ba..84f30aa2d 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -116,11 +116,11 @@ namespace , tor(t) { TORRENT_ASSERT(t != 0); } - bool operator()(const session_impl::connection_map::value_type& c) const + bool operator()(session_impl::connection_map::value_type const& c) const { - tcp::endpoint sender = c.first->remote_endpoint(); + tcp::endpoint const& sender = c->remote(); if (sender.address() != ip.address()) return false; - if (tor != c.second->associated_torrent().lock().get()) return false; + if (tor != c->associated_torrent().lock().get()) return false; return true; } @@ -132,9 +132,9 @@ namespace { peer_by_id(const peer_id& i): pid(i) {} - bool operator()(const std::pair& p) const + bool operator()(session_impl::connection_map::value_type const& p) const { - if (p.second->pid() != pid) return false; + if (p->pid() != pid) return false; // have a special case for all zeros. We can have any number // of peers with that pid, since it's used to indicate no pid. if (std::count(pid.begin(), pid.end(), 0) == 20) return false; @@ -178,7 +178,6 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT , m_last_dht_announce(time_now() - minutes(15)) #endif - , m_policy() , m_ses(ses) , m_checker(checker) , m_picker(0) @@ -203,8 +202,8 @@ namespace libtorrent , m_max_uploads((std::numeric_limits::max)()) , m_num_uploads(0) , m_max_connections((std::numeric_limits::max)()) + , m_policy(this) { - m_policy.reset(new policy(this)); } torrent::torrent( @@ -239,7 +238,6 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT , m_last_dht_announce(time_now() - minutes(15)) #endif - , m_policy() , m_ses(ses) , m_checker(checker) , m_picker(0) @@ -263,6 +261,7 @@ namespace libtorrent , m_max_uploads((std::numeric_limits::max)()) , m_num_uploads(0) , m_max_connections((std::numeric_limits::max)()) + , m_policy(this) { INVARIANT_CHECK; @@ -273,8 +272,6 @@ namespace libtorrent m_trackers.push_back(announce_entry(tracker_url)); m_torrent_file->add_tracker(tracker_url); } - - m_policy.reset(new policy(this)); } void torrent::start() @@ -317,7 +314,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - (*i->second->m_logger) << "*** DESTRUCTING TORRENT\n"; + (*(*i)->m_logger) << "*** DESTRUCTING TORRENT\n"; } #endif @@ -326,6 +323,21 @@ namespace libtorrent disconnect_all(); } + peer_request torrent::to_req(piece_block const& p) + { + int block_offset = p.block_index * m_block_size; + int block_size = (std::min)((int)torrent_file().piece_size( + p.piece_index) - block_offset, m_block_size); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= m_block_size); + + peer_request r; + r.piece = p.piece_index; + r.start = block_offset; + r.length = block_size; + return r; + } + std::string torrent::name() const { if (valid_metadata()) return m_torrent_file->name(); @@ -334,10 +346,34 @@ namespace libtorrent } #ifndef TORRENT_DISABLE_EXTENSIONS + void torrent::add_extension(boost::shared_ptr ext) { m_extensions.push_back(ext); } + + void torrent::add_extension(boost::function(torrent*, void*)> const& ext + , void* userdata) + { + boost::shared_ptr tp(ext(this, userdata)); + if (!tp) return; + + add_extension(tp); + + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + peer_connection* p = *i; + boost::shared_ptr pp(tp->new_connection(p)); + if (pp) p->add_extension(pp); + } + + // if files are checked for this torrent, call the extension + // to let it initialize itself + if (m_connections_initialized) + tp->on_files_checked(); + } + #endif // this may not be called from a constructor because of the call to @@ -551,7 +587,7 @@ namespace libtorrent continue; } - m_policy->peer_from_tracker(a, i->pid, peer_info::tracker, 0); + m_policy.peer_from_tracker(a, i->pid, peer_info::tracker, 0); } catch (std::exception&) { @@ -599,7 +635,7 @@ namespace libtorrent return; } - m_policy->peer_from_tracker(*host, pid, peer_info::tracker, 0); + m_policy.peer_from_tracker(*host, pid, peer_info::tracker, 0); } catch (std::exception&) {}; @@ -729,7 +765,7 @@ namespace libtorrent std::map downloading_piece; for (const_peer_iterator i = begin(); i != end(); ++i) { - peer_connection* pc = i->second; + peer_connection* pc = *i; boost::optional p = pc->downloading_piece_progress(); if (p) @@ -819,6 +855,11 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << time_now_string() << " *** PIECE_FINISHED [ p: " + << index << " chk: " << (passed_hash_check?"passed":"failed") << " ]\n"; +#endif + bool was_seed = is_seed(); bool was_finished = m_picker->num_filtered() + num_pieces() == torrent_file().num_pieces(); @@ -865,7 +906,7 @@ namespace libtorrent { #endif - m_policy->piece_finished(index, passed_hash_check); + m_policy.piece_finished(index, passed_hash_check); #ifndef NDEBUG } @@ -905,7 +946,7 @@ namespace libtorrent // resets the download queue. So, we cannot do the // invariant check here since it assumes: // (total_done == m_torrent_file->total_size()) => is_seed() -// INVARIANT_CHECK; + INVARIANT_CHECK; TORRENT_ASSERT(m_storage); TORRENT_ASSERT(m_storage->refcount() > 0); @@ -944,17 +985,6 @@ namespace libtorrent { policy::peer* p = static_cast(*i); if (p == 0) continue; -#ifndef NDEBUG - if (!settings().allow_multiple_connections_per_ip) - { - std::vector conns; - connection_for(p->ip.address(), conns); - TORRENT_ASSERT(p->connection == 0 - || std::find_if(conns.begin(), conns.end() - , boost::bind(std::equal_to(), _1, p->connection)) - != conns.end()); - } -#endif if (p->connection) p->connection->received_invalid_data(index); // either, we have received too many failed hashes @@ -1019,7 +1049,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - (*i->second->m_logger) << "*** ABORTING TORRENT\n"; + (*(*i)->m_logger) << "*** ABORTING TORRENT\n"; } #endif @@ -1088,7 +1118,7 @@ namespace libtorrent m_picker->we_have(index); for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) - try { i->second->announce_piece(index); } catch (std::exception&) {} + try { (*i)->announce_piece(index); } catch (std::exception&) {} for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) @@ -1259,7 +1289,7 @@ namespace libtorrent void torrent::update_peer_interest() { for (peer_iterator i = begin(); i != end(); ++i) - i->second->update_interest(); + (*i)->update_interest(); } void torrent::filter_piece(int index, bool filter) @@ -1441,7 +1471,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - i->second->cancel_request(block); + (*i)->cancel_request(block); } } @@ -1451,7 +1481,7 @@ namespace libtorrent TORRENT_ASSERT(p != 0); - peer_iterator i = m_connections.find(p->remote()); + peer_iterator i = m_connections.find(p); if (i == m_connections.end()) { TORRENT_ASSERT(false); @@ -1489,8 +1519,9 @@ namespace libtorrent if (!p->is_choked()) --m_num_uploads; - m_policy->connection_closed(*p); + m_policy.connection_closed(*p); p->set_peer_info(0); + TORRENT_ASSERT(i != m_connections.end()); m_connections.erase(i); } catch (std::exception& e) @@ -1506,7 +1537,7 @@ namespace libtorrent INVARIANT_CHECK; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << time_now_string() << " resolving: " << url << "\n"; + (*m_ses.m_logger) << time_now_string() << " resolving web seed: " << url << "\n"; #endif m_resolving_web_seeds.insert(url); @@ -1643,16 +1674,11 @@ namespace libtorrent return; } - peer_iterator conn = m_connections.find(a); - if (conn != m_connections.end()) - { - if (dynamic_cast(conn->second) == 0 - || conn->second->is_disconnecting()) conn->second->disconnect(); - else return; - } + boost::shared_ptr s(new socket_type); + + bool ret = instantiate_connection(m_ses.m_io_service, m_ses.web_seed_proxy(), *s); + TORRENT_ASSERT(ret); - boost::shared_ptr s - = instantiate_connection(m_ses.m_io_service, m_ses.web_seed_proxy()); if (m_ses.web_seed_proxy().type == proxy_settings::http || m_ses.web_seed_proxy().type == proxy_settings::http_pw) { @@ -1679,10 +1705,8 @@ namespace libtorrent try { // add the newly connected peer to this torrent's peer list - TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); - m_connections.insert( - std::make_pair(a, boost::get_pointer(c))); - m_ses.m_connections.insert(std::make_pair(s, c)); + m_connections.insert(boost::get_pointer(c)); + m_ses.m_connections.insert(c); m_ses.m_half_open.enqueue( bind(&peer_connection::connect, c, _1) @@ -1698,7 +1722,7 @@ namespace libtorrent // TODO: post an error alert! // std::map::iterator i = m_connections.find(a); // if (i != m_connections.end()) m_connections.erase(i); - m_ses.connection_failed(s, a, e.what()); + m_ses.connection_failed(c, a, e.what()); c->disconnect(); } } @@ -1843,19 +1867,19 @@ namespace libtorrent } #endif - peer_connection* torrent::connect_to_peer(policy::peer* peerinfo) + bool torrent::connect_to_peer(policy::peer* peerinfo) throw() { INVARIANT_CHECK; TORRENT_ASSERT(peerinfo); TORRENT_ASSERT(peerinfo->connection == 0); + peerinfo->connected = time_now(); #ifndef NDEBUG // this asserts that we don't have duplicates in the policy's peer list - peer_iterator i_ = m_connections.find(peerinfo->ip); + peer_iterator i_ = std::find_if(m_connections.begin(), m_connections.end() + , bind(&peer_connection::remote, _1) == peerinfo->ip); TORRENT_ASSERT(i_ == m_connections.end() - || i_->second->is_disconnecting() - || dynamic_cast(i_->second) == 0 - || m_ses.settings().allow_multiple_connections_per_ip); + || dynamic_cast(*i_) == 0); #endif TORRENT_ASSERT(want_more_peers()); @@ -1864,8 +1888,11 @@ namespace libtorrent tcp::endpoint const& a(peerinfo->ip); TORRENT_ASSERT((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); - boost::shared_ptr s - = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy()); + boost::shared_ptr s(new socket_type); + + bool ret = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy(), *s); + TORRENT_ASSERT(ret); + boost::intrusive_ptr c(new bt_peer_connection( m_ses, shared_from_this(), s, a, peerinfo)); @@ -1873,23 +1900,20 @@ namespace libtorrent c->m_in_constructor = false; #endif -#ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { - boost::shared_ptr pp((*i)->new_connection(c.get())); - if (pp) c->add_extension(pp); - } -#endif - try { - TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr pp((*i)->new_connection(c.get())); + if (pp) c->add_extension(pp); + } +#endif // add the newly connected peer to this torrent's peer list - m_connections.insert( - std::make_pair(a, boost::get_pointer(c))); - m_ses.m_connections.insert(std::make_pair(s, c)); + m_connections.insert(boost::get_pointer(c)); + m_ses.m_connections.insert(c); int timeout = settings().peer_connect_timeout; if (peerinfo) timeout += 3 * peerinfo->failcount; @@ -1901,16 +1925,15 @@ namespace libtorrent } catch (std::exception& e) { - TORRENT_ASSERT(false); - // TODO: post an error alert! - std::map::iterator i = m_connections.find(a); + std::set::iterator i + = m_connections.find(boost::get_pointer(c)); if (i != m_connections.end()) m_connections.erase(i); - m_ses.connection_failed(s, a, e.what()); + m_ses.connection_failed(c, a, e.what()); c->disconnect(); - throw; + return false; } - if (c->is_disconnecting()) throw protocol_error("failed to connect"); - return c.get(); + peerinfo->connection = c.get(); + return true; } void torrent::set_metadata(entry const& metadata) @@ -1954,25 +1977,7 @@ namespace libtorrent TORRENT_ASSERT(p != 0); TORRENT_ASSERT(!p->is_local()); - std::map::iterator c - = m_connections.find(p->remote()); - if (c != m_connections.end()) - { - TORRENT_ASSERT(p != c->second); - // we already have a peer_connection to this ip. - // It may currently be waiting for completing a - // connection attempt that might fail. So, - // prioritize this current connection since - // it has already succeeded. - if (!c->second->is_connecting()) - { - throw protocol_error("already connected to peer"); - } - c->second->disconnect(); - } - - if (m_ses.m_connections.find(p->get_socket()) - == m_ses.m_connections.end()) + if (m_ses.m_connections.find(p) == m_ses.m_connections.end()) { throw protocol_error("peer is not properly constructed"); } @@ -1982,9 +1987,8 @@ namespace libtorrent throw protocol_error("session is closing"); } - TORRENT_ASSERT(m_connections.find(p->remote()) == m_connections.end()); - peer_iterator ci = m_connections.insert( - std::make_pair(p->remote(), p)).first; + TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); + peer_iterator ci = m_connections.insert(p).first; try { // if new_connection throws, we have to remove the @@ -1998,9 +2002,9 @@ namespace libtorrent if (pp) p->add_extension(pp); } #endif - TORRENT_ASSERT(connection_for(p->remote()) == p); - TORRENT_ASSERT(ci->second == p); - m_policy->new_connection(*ci->second); + TORRENT_ASSERT(m_connections.find(p) == ci); + TORRENT_ASSERT(*ci == p); + m_policy.new_connection(**ci); } catch (std::exception& e) { @@ -2010,7 +2014,7 @@ namespace libtorrent TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint()); #ifndef NDEBUG - m_policy->check_invariant(); + m_policy.check_invariant(); #endif } @@ -2029,19 +2033,19 @@ namespace libtorrent while (!m_connections.empty()) { - peer_connection& p = *m_connections.begin()->second; - TORRENT_ASSERT(p.associated_torrent().lock().get() == this); + peer_connection* p = *m_connections.begin(); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); #if defined(TORRENT_VERBOSE_LOGGING) if (m_abort) - (*p.m_logger) << "*** CLOSING CONNECTION 'aborting'\n"; + (*p->m_logger) << "*** CLOSING CONNECTION 'aborting'\n"; else - (*p.m_logger) << "*** CLOSING CONNECTION 'pausing'\n"; + (*p->m_logger) << "*** CLOSING CONNECTION 'pausing'\n"; #endif #ifndef NDEBUG std::size_t size = m_connections.size(); #endif - p.disconnect(); + p->disconnect(); TORRENT_ASSERT(m_connections.size() <= size); } } @@ -2112,7 +2116,8 @@ namespace libtorrent expire_bandwidth(channel, blk - amount); } - // called when torrent is finished (all interested pieces downloaded) + // called when torrent is finished (all interesting + // pieces have been downloaded) void torrent::finished() { INVARIANT_CHECK; @@ -2131,13 +2136,14 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - TORRENT_ASSERT(i->second->associated_torrent().lock().get() == this); - if (i->second->is_seed()) + peer_connection* p = *i; + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + if (p->is_seed()) { #if defined(TORRENT_VERBOSE_LOGGING) - (*i->second->m_logger) << "*** SEED, CLOSING CONNECTION\n"; + (*p->m_logger) << "*** SEED, CLOSING CONNECTION\n"; #endif - seeds.push_back(i->second); + seeds.push_back(p); } } std::for_each(seeds.begin(), seeds.end() @@ -2338,23 +2344,21 @@ namespace libtorrent m_connections_initialized = true; // all peer connections have to initialize themselves now that the metadata // is available - typedef std::map conn_map; - for (conn_map::iterator i = m_connections.begin() + for (torrent::peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end;) { try { - i->second->on_metadata(); - i->second->init(); + (*i)->on_metadata(); + (*i)->init(); ++i; } catch (std::exception& e) { // the connection failed, close it - conn_map::iterator j = i; + torrent::peer_iterator j = i; ++j; - m_ses.connection_failed(i->second->get_socket() - , i->first, e.what()); + m_ses.connection_failed(*i, (*i)->remote(), e.what()); i = j; } } @@ -2425,7 +2429,7 @@ namespace libtorrent std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) { - peer_connection const& p = *i->second; + peer_connection const& p = *(*i); for (std::deque::const_iterator i = p.request_queue().begin() , end(p.request_queue().end()); i != end; ++i) ++num_requests[*i]; @@ -2457,12 +2461,12 @@ namespace libtorrent TORRENT_ASSERT(m_abort || m_have_pieces.empty()); } -/* for (policy::const_iterator i = m_policy->begin_peer() - , end(m_policy->end_peer()); i != end; ++i) + for (policy::const_iterator i = m_policy.begin_peer() + , end(m_policy.end_peer()); i != end; ++i) { - TORRENT_ASSERT(i->connection == const_cast(this)->connection_for(i->ip)); + TORRENT_ASSERT(i->second.ip.address() == i->first); } -*/ + size_type total_done = quantized_bytes_done(); if (m_torrent_file->is_valid()) { @@ -2476,6 +2480,36 @@ namespace libtorrent TORRENT_ASSERT(total_done == 0); } + if (m_picker && !m_abort) + { + // make sure that pieces that have completed the download + // of all their blocks are in the disk io thread's queue + // to be checked. + const std::vector& dl_queue + = m_picker->get_download_queue(); + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + const int blocks_per_piece = m_picker->blocks_in_piece(i->index); + + bool complete = true; + for (int j = 0; j < blocks_per_piece; ++j) + { + if (i->info[j].state == piece_picker::block_info::state_finished) + continue; + complete = false; + break; + } + if (complete) + { + disk_io_job ret = m_ses.m_disk_thread.find_job( + m_owning_storage, -1, i->index); + TORRENT_ASSERT(ret.action == disk_io_job::hash || ret.action == disk_io_job::write); + TORRENT_ASSERT(ret.piece == i->index); + } + } + } + // This check is very expensive. TORRENT_ASSERT(m_num_pieces == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); @@ -2515,17 +2549,19 @@ namespace libtorrent void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) { TORRENT_ASSERT(limit >= -1); - peer_connection* p = connection_for(ip); - if (p == 0) return; - p->set_upload_limit(limit); + peer_iterator i = std::find_if(m_connections.begin(), m_connections.end() + , bind(&peer_connection::remote, _1) == ip); + if (i == m_connections.end()) return; + (*i)->set_upload_limit(limit); } void torrent::set_peer_download_limit(tcp::endpoint ip, int limit) { TORRENT_ASSERT(limit >= -1); - peer_connection* p = connection_for(ip); - if (p == 0) return; - p->set_download_limit(limit); + peer_iterator i = std::find_if(m_connections.begin(), m_connections.end() + , bind(&peer_connection::remote, _1) == ip); + if (i == m_connections.end()) return; + (*i)->set_download_limit(limit); } void torrent::set_upload_limit(int limit) @@ -2564,7 +2600,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - (*i->second->m_logger) << "*** DELETING FILES IN TORRENT\n"; + (*(*i)->m_logger) << "*** DELETING FILES IN TORRENT\n"; } #endif @@ -2599,7 +2635,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - (*i->second->m_logger) << "*** PAUSING TORRENT\n"; + (*(*i)->m_logger) << "*** PAUSING TORRENT\n"; } #endif @@ -2673,7 +2709,7 @@ namespace libtorrent i != m_connections.end(); ++i) { web_peer_connection* p - = dynamic_cast(i->second); + = dynamic_cast(*i); if (!p) continue; web_seeds.insert(p->url()); } @@ -2696,7 +2732,7 @@ namespace libtorrent for (peer_iterator i = m_connections.begin(); i != m_connections.end();) { - peer_connection* p = i->second; + peer_connection* p = *i; ++i; m_stat += p->statistics(); // updates the peer connection's ul/dl bandwidth @@ -2721,19 +2757,19 @@ namespace libtorrent if (m_time_scaler <= 0) { m_time_scaler = 10; - m_policy->pulse(); + m_policy.pulse(); } } bool torrent::try_connect_peer() { TORRENT_ASSERT(want_more_peers()); - return m_policy->connect_one_peer(); + return m_policy.connect_one_peer(); } void torrent::async_verify_piece(int piece_index, boost::function const& f) { - INVARIANT_CHECK; +// INVARIANT_CHECK; TORRENT_ASSERT(m_storage); TORRENT_ASSERT(m_storage->refcount() > 0); @@ -2743,6 +2779,9 @@ namespace libtorrent m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified , shared_from_this(), _1, _2, f)); +#ifndef NDEBUG + check_invariant(); +#endif } void torrent::on_piece_verified(int ret, disk_io_job const& j @@ -2809,8 +2848,7 @@ namespace libtorrent torrent_status st; st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end(), - !boost::bind(&peer_connection::is_connecting - , boost::bind(&std::map::value_type::second, _1))); + !boost::bind(&peer_connection::is_connecting, _1)); st.storage_mode = m_storage_mode; @@ -2937,9 +2975,7 @@ namespace libtorrent INVARIANT_CHECK; return (int)std::count_if(m_connections.begin(), m_connections.end() - , boost::bind(&peer_connection::is_seed - , boost::bind(&std::map::value_type::second, _1))); + , boost::bind(&peer_connection::is_seed, _1)); } void torrent::tracker_request_timed_out( @@ -2995,7 +3031,7 @@ namespace libtorrent #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) void torrent::debug_log(const std::string& line) { - (*m_ses.m_logger) << line << "\n"; + (*m_ses.m_logger) << time_now_string() << " " << line << "\n"; } #endif diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 9418d50e8..b19e05bb4 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -247,6 +247,20 @@ namespace libtorrent find_torrent(m_ses, m_chk, m_info_hash)->move_storage(save_path); } + void torrent_handle::add_extension( + boost::function(torrent*, void*)> const& ext + , void* userdata) + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + TORRENT_ASSERT(m_chk); + + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); + mutex::scoped_lock l2(m_chk->m_mutex); + return find_torrent(m_ses, m_chk, m_info_hash)->add_extension(ext, userdata); + } + bool torrent_handle::has_metadata() const { INVARIANT_CHECK; @@ -908,7 +922,7 @@ namespace libtorrent for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) { - peer_connection* peer = i->second; + peer_connection* peer = *i; // incoming peers that haven't finished the handshake should // not be included in this list diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 0118e5802..82c5cc948 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -296,18 +296,20 @@ namespace libtorrent , m_timeout(str.io_service()) , m_completion_timeout(0) , m_read_timeout(0) + , m_abort(false) {} void timeout_handler::set_timeout(int completion_timeout, int read_timeout) { m_completion_timeout = completion_timeout; m_read_timeout = read_timeout; - m_start_time = time_now(); - m_read_time = time_now(); + m_start_time = m_read_time = time_now(); - m_timeout.expires_at((std::min)( - m_read_time + seconds(m_read_timeout) - , m_start_time + seconds(m_completion_timeout))); + if (m_abort) return; + + int timeout = (std::min)( + m_read_timeout, (std::min)(m_completion_timeout, m_read_timeout)); + m_timeout.expires_at(m_read_time + seconds(timeout)); m_timeout.async_wait(m_strand.wrap(bind( &timeout_handler::timeout_callback, self(), _1))); } @@ -319,6 +321,7 @@ namespace libtorrent void timeout_handler::cancel() { + m_abort = true; m_completion_timeout = 0; m_timeout.cancel(); } @@ -341,9 +344,11 @@ namespace libtorrent return; } - m_timeout.expires_at((std::min)( - m_read_time + seconds(m_read_timeout) - , m_start_time + seconds(m_completion_timeout))); + if (m_abort) return; + + int timeout = (std::min)( + m_read_timeout, (std::min)(m_completion_timeout, m_read_timeout)); + m_timeout.expires_at(m_read_time + seconds(timeout)); m_timeout.async_wait(m_strand.wrap( bind(&timeout_handler::timeout_callback, self(), _1))); } @@ -567,12 +572,24 @@ namespace libtorrent m_abort = true; tracker_connections_t keep_connections; - for (tracker_connections_t::const_iterator i = - m_connections.begin(); i != m_connections.end(); ++i) + while (!m_connections.empty()) { - tracker_request const& req = (*i)->tracker_req(); + boost::intrusive_ptr& c = m_connections.back(); + if (!c) + { + m_connections.pop_back(); + continue; + } + tracker_request const& req = c->tracker_req(); if (req.event == tracker_request::stopped) - keep_connections.push_back(*i); + { + keep_connections.push_back(c); + m_connections.pop_back(); + continue; + } + // close will remove the entry from m_connections + // so no need to pop + c->close(); } std::swap(m_connections, keep_connections); diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index 1ba50b3c6..6d76988d3 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -86,6 +86,7 @@ namespace libtorrent , m_man(man) , m_strand(str) , m_name_lookup(m_strand.io_service()) + , m_socket(m_strand.io_service()) , m_transaction_id(0) , m_connection_id(0) , m_settings(stn) @@ -95,7 +96,9 @@ namespace libtorrent m_name_lookup.async_resolve(q , m_strand.wrap(boost::bind( &udp_tracker_connection::name_lookup, self(), _1, _2))); - set_timeout(m_settings.tracker_completion_timeout + set_timeout(req.event == tracker_request::stopped + ? m_settings.stop_tracker_timeout + : m_settings.tracker_completion_timeout , m_settings.tracker_receive_timeout); } @@ -103,7 +106,7 @@ namespace libtorrent , udp::resolver::iterator i) try { if (error == asio::error::operation_aborted) return; - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted if (error || i == udp::resolver::iterator()) { fail(-1, error.message().c_str()); @@ -143,10 +146,9 @@ namespace libtorrent if (cb) cb->m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); m_target = target_address; - m_socket.reset(new datagram_socket(m_name_lookup.io_service())); - m_socket->open(target_address.protocol()); - m_socket->bind(udp::endpoint(bind_interface(), 0)); - m_socket->connect(target_address); + m_socket.open(target_address.protocol()); + m_socket.bind(udp::endpoint(bind_interface(), 0)); + m_socket.connect(target_address); send_udp_connect(); } catch (std::exception& e) @@ -156,11 +158,20 @@ namespace libtorrent void udp_tracker_connection::on_timeout() { - m_socket.reset(); + asio::error_code ec; + m_socket.close(ec); m_name_lookup.cancel(); fail_timeout(); } + void udp_tracker_connection::close() + { + asio::error_code ec; + m_socket.close(ec); + m_name_lookup.cancel(); + tracker_connection::close(); + } + void udp_tracker_connection::send_udp_connect() { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -171,7 +182,7 @@ namespace libtorrent + lexical_cast(tracker_req().info_hash) + "]"); } #endif - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted char send_buf[16]; char* ptr = send_buf; @@ -187,10 +198,10 @@ namespace libtorrent // transaction_id detail::write_int32(m_transaction_id, ptr); - m_socket->send(asio::buffer((void*)send_buf, 16), 0); + m_socket.send(asio::buffer((void*)send_buf, 16), 0); ++m_attempts; m_buffer.resize(udp_buffer_size); - m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + m_socket.async_receive_from(asio::buffer(m_buffer), m_sender , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); } @@ -198,7 +209,7 @@ namespace libtorrent , std::size_t bytes_transferred) try { if (error == asio::error::operation_aborted) return; - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted if (error) { fail(-1, error.message().c_str()); @@ -208,7 +219,7 @@ namespace libtorrent if (m_target != m_sender) { // this packet was not received from the tracker - m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + m_socket.async_receive_from(asio::buffer(m_buffer), m_sender , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); return; } @@ -284,7 +295,7 @@ namespace libtorrent if (m_transaction_id == 0) m_transaction_id = rand() ^ (rand() << 16); - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted std::vector buf; std::back_insert_iterator > out(buf); @@ -332,10 +343,10 @@ namespace libtorrent } #endif - m_socket->send(asio::buffer(buf), 0); + m_socket.send(asio::buffer(buf), 0); ++m_attempts; - m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + m_socket.async_receive_from(asio::buffer(m_buffer), m_sender , bind(&udp_tracker_connection::announce_response, self(), _1, _2)); } @@ -344,7 +355,7 @@ namespace libtorrent if (m_transaction_id == 0) m_transaction_id = rand() ^ (rand() << 16); - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted std::vector buf; std::back_insert_iterator > out(buf); @@ -358,10 +369,10 @@ namespace libtorrent // info_hash std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end(), out); - m_socket->send(asio::buffer(&buf[0], buf.size()), 0); + m_socket.send(asio::buffer(&buf[0], buf.size()), 0); ++m_attempts; - m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + m_socket.async_receive_from(asio::buffer(m_buffer), m_sender , bind(&udp_tracker_connection::scrape_response, self(), _1, _2)); } @@ -369,7 +380,7 @@ namespace libtorrent , std::size_t bytes_transferred) try { if (error == asio::error::operation_aborted) return; - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted if (error) { fail(-1, error.message().c_str()); @@ -379,7 +390,7 @@ namespace libtorrent if (m_target != m_sender) { // this packet was not received from the tracker - m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + m_socket.async_receive_from(asio::buffer(m_buffer), m_sender , bind(&udp_tracker_connection::connect_response, self(), _1, _2)); return; } @@ -468,6 +479,7 @@ namespace libtorrent , complete, incomplete); m_man.remove_request(this); + close(); return; } catch (std::exception& e) @@ -479,7 +491,7 @@ namespace libtorrent , std::size_t bytes_transferred) try { if (error == asio::error::operation_aborted) return; - if (!m_socket) return; // the operation was aborted + if (!m_socket.is_open()) return; // the operation was aborted if (error) { fail(-1, error.message().c_str()); @@ -489,7 +501,7 @@ namespace libtorrent if (m_target != m_sender) { // this packet was not received from the tracker - m_socket->async_receive_from(asio::buffer(m_buffer), m_sender + m_socket.async_receive_from(asio::buffer(m_buffer), m_sender , bind(&udp_tracker_connection::connect_response, self(), _1, _2)); return; } @@ -543,6 +555,7 @@ namespace libtorrent if (!cb) { m_man.remove_request(this); + close(); return; } @@ -551,6 +564,7 @@ namespace libtorrent , complete, incomplete); m_man.remove_request(this); + close(); } catch (std::exception& e) { diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 4bdc20987..116eb1dfe 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -148,6 +148,7 @@ void upnp::set_mappings(int tcp, int udp) , end(m_devices.end()); i != end; ++i) { rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); if (d.mapping[0].local_port != m_tcp_local_port) { if (d.mapping[0].external_port == 0) @@ -200,8 +201,13 @@ try // we don't have a WANIP or WANPPP url for this device, // ask for it rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); try { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> connecting to " << d.url << std::endl; +#endif d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, self(), _1, _2 , boost::ref(d))))); @@ -270,7 +276,7 @@ try { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl; + << " <== (" << from << ") Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl; #endif return; } @@ -280,11 +286,11 @@ try #ifdef TORRENT_UPNP_LOGGING if (p.method().empty()) m_log << time_now_string() - << " <== Device responded with HTTP status: " << p.status_code() + << " <== (" << from << ") Device responded with HTTP status: " << p.status_code() << ". Ignoring device" << std::endl; else m_log << time_now_string() - << " <== Device with HTTP method: " << p.method() + << " <== (" << from << ") Device with HTTP method: " << p.method() << ". Ignoring device" << std::endl; #endif return; @@ -294,7 +300,7 @@ try { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with incomplete HTTP " + << " <== (" << from << ") Rootdevice responded with incomplete HTTP " "packet. Ignoring device" << std::endl; #endif return; @@ -305,7 +311,7 @@ try { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice response is missing a location header. " + << " <== (" << from << ") Rootdevice response is missing a location header. " "Ignoring device" << std::endl; #endif return; @@ -332,7 +338,7 @@ try { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice uses unsupported protocol: '" << protocol + << " <== (" << from << ") Rootdevice uses unsupported protocol: '" << protocol << "'. Ignoring device" << std::endl; #endif return; @@ -342,16 +348,27 @@ try { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with a url with port 0. " + << " <== (" << from << ") Rootdevice responded with a url with port 0. " "Ignoring device" << std::endl; #endif return; } #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Found rootdevice: " << d.url << std::endl; + << " <== (" << from << ") Found rootdevice: " << d.url + << " total: " << m_devices.size() << std::endl; #endif + if (m_devices.size() >= 50) + { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " <== (" << from << ") Too many devices (" << m_devices.size() << "), " + "ignoring: " << d.url << std::endl; +#endif + return; + } + if (m_tcp_local_port != 0) { d.mapping[0].need_update = true; @@ -390,8 +407,13 @@ try // we don't have a WANIP or WANPPP url for this device, // ask for it rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); try { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> connecting to " << d.url << std::endl; +#endif d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, self(), _1, _2 , boost::ref(d))))); @@ -420,6 +442,7 @@ catch (std::exception&) void upnp::post(upnp::rootdevice const& d, std::string const& soap , std::string const& soap_action) { + TORRENT_ASSERT(d.magic == 1337); std::stringstream header; header << "POST " << d.control_url << " HTTP/1.1\r\n" @@ -439,6 +462,7 @@ void upnp::post(upnp::rootdevice const& d, std::string const& soap void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) { + TORRENT_ASSERT(d.magic == 1337); std::string soap_action = "AddPortMapping"; std::stringstream soap; @@ -463,6 +487,7 @@ void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) void upnp::map_port(rootdevice& d, int i) { + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) return; if (!d.mapping[i].need_update) @@ -479,6 +504,10 @@ void upnp::map_port(rootdevice& d, int i) TORRENT_ASSERT(!d.upnp_connection); TORRENT_ASSERT(d.service_namespace); +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> connecting to " << d.hostname << std::endl; +#endif d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, self(), _1, _2 , boost::ref(d), i)), true @@ -490,6 +519,7 @@ void upnp::map_port(rootdevice& d, int i) void upnp::delete_port_mapping(rootdevice& d, int i) { + TORRENT_ASSERT(d.magic == 1337); std::stringstream soap; std::string soap_action = "DeletePortMapping"; @@ -510,23 +540,24 @@ void upnp::delete_port_mapping(rootdevice& d, int i) // requires the mutex to be locked void upnp::unmap_port(rootdevice& d, int i) { - if (d.mapping[i].external_port == 0) + TORRENT_ASSERT(d.magic == 1337); + if (d.mapping[i].external_port == 0 + || d.disabled) { if (i < num_mappings - 1) { unmap_port(d, i + 1); } - else - { - m_devices.erase(d); - } return; } +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> connecting to " << d.hostname << std::endl; +#endif d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, self(), _1, _2 , boost::ref(d), i)), true , bind(&upnp::delete_port_mapping, self(), boost::ref(d), i))); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) , seconds(10)); } @@ -591,6 +622,7 @@ namespace void upnp::on_upnp_xml(asio::error_code const& e , libtorrent::http_parser const& p, rootdevice& d) try { + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) { d.upnp_connection->close(); @@ -601,8 +633,10 @@ void upnp::on_upnp_xml(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== error while fetching control url: " << e.message() << std::endl; + << " <== (" << d.url << ") error while fetching control url: " + << e.message() << std::endl; #endif + d.disabled = true; return; } @@ -610,8 +644,9 @@ void upnp::on_upnp_xml(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== error while fetching control url: incomplete http message" << std::endl; + << " <== (" << d.url << ") error while fetching control url: incomplete http message" << std::endl; #endif + d.disabled = true; return; } @@ -619,8 +654,9 @@ void upnp::on_upnp_xml(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== error while fetching control url: " << p.message() << std::endl; + << " <== (" << d.url << ") error while fetching control url: " << p.message() << std::endl; #endif + d.disabled = true; return; } @@ -647,15 +683,17 @@ void upnp::on_upnp_xml(asio::error_code const& e { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice response, did not find a port mapping interface" << std::endl; + << " <== (" << d.url << ") Rootdevice response, did not find " + "a port mapping interface" << std::endl; #endif + d.disabled = true; return; } } #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice response, found control URL: " << s.control_url + << " <== (" << d.url << ") Rootdevice response, found control URL: " << s.control_url << " namespace: " << d.service_namespace << std::endl; #endif @@ -732,6 +770,7 @@ namespace void upnp::on_upnp_map_response(asio::error_code const& e , libtorrent::http_parser const& p, rootdevice& d, int mapping) try { + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) { d.upnp_connection->close(); @@ -744,7 +783,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e m_log << time_now_string() << " <== error while adding portmap: " << e.message() << std::endl; #endif - m_devices.erase(d); + d.disabled = true; return; } @@ -773,7 +812,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e m_log << time_now_string() << " <== error while adding portmap: incomplete http message" << std::endl; #endif - m_devices.erase(d); + d.disabled = true; return; } @@ -877,6 +916,7 @@ catch (std::exception&) void upnp::on_upnp_unmap_response(asio::error_code const& e , libtorrent::http_parser const& p, rootdevice& d, int mapping) try { + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) { d.upnp_connection->close(); @@ -906,7 +946,7 @@ void upnp::on_upnp_unmap_response(asio::error_code const& e m_log << time_now_string() << " <== error while deleting portmap: " << p.message() << std::endl; #endif - m_devices.erase(d); + d.disabled = true; return; } @@ -922,10 +962,6 @@ void upnp::on_upnp_unmap_response(asio::error_code const& e unmap_port(d, mapping + 1); return; } - - // the main thread is likely to be waiting for - // all the unmap operations to complete - m_devices.erase(d); } catch (std::exception&) { @@ -943,6 +979,7 @@ void upnp::on_expire(asio::error_code const& e) try , end(m_devices.end()); i != end; ++i) { rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); for (int m = 0; m < num_mappings; ++m) { if (d.mapping[m].expires != max_time()) @@ -984,15 +1021,11 @@ void upnp::close() } for (std::set::iterator i = m_devices.begin() - , end(m_devices.end()); i != end;) + , end(m_devices.end()); i != end; ++i) { rootdevice& d = const_cast(*i); - if (d.control_url.empty()) - { - m_devices.erase(i++); - continue; - } - ++i; + TORRENT_ASSERT(d.magic == 1337); + if (d.control_url.empty()) continue; unmap_port(d, 0); } } diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 071407005..d1248637c 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -72,7 +72,7 @@ namespace libtorrent { namespace struct ut_pex_plugin: torrent_plugin { - ut_pex_plugin(torrent& t): m_torrent(t), m_1_minute(0) {} + ut_pex_plugin(torrent& t): m_torrent(t), m_1_minute(55) {} virtual boost::shared_ptr new_connection(peer_connection* pc); @@ -113,18 +113,20 @@ namespace libtorrent { namespace for (torrent::peer_iterator i = m_torrent.begin() , end(m_torrent.end()); i != end; ++i) { - if (!send_peer(*i->second)) continue; + peer_connection* peer = *i; + if (!send_peer(*peer)) continue; - m_old_peers.insert(i->first); + tcp::endpoint const& remote = peer->remote(); + m_old_peers.insert(remote); - std::set::iterator di = dropped.find(i->first); + std::set::iterator di = dropped.find(remote); if (di == dropped.end()) { // don't write too big of a package if (num_added >= max_peer_entries) break; // only send proper bittorrent peers - bt_peer_connection* p = dynamic_cast(i->second); + bt_peer_connection* p = dynamic_cast(peer); if (!p) continue; // no supported flags to set yet @@ -135,14 +137,14 @@ namespace libtorrent { namespace flags |= p->supports_encryption() ? 1 : 0; #endif // i->first was added since the last time - if (i->first.address().is_v4()) + if (remote.address().is_v4()) { - detail::write_endpoint(i->first, pla_out); + detail::write_endpoint(remote, pla_out); detail::write_uint8(flags, plf_out); } else { - detail::write_endpoint(i->first, pla6_out); + detail::write_endpoint(remote, pla6_out); detail::write_uint8(flags, plf6_out); } ++num_added; @@ -156,7 +158,7 @@ namespace libtorrent { namespace } for (std::set::const_iterator i = dropped.begin() - , end(dropped.end());i != end; ++i) + , end(dropped.end()); i != end; ++i) { if (i->address().is_v4()) detail::write_endpoint(*i, pld_out); @@ -183,7 +185,7 @@ namespace libtorrent { namespace : m_torrent(t) , m_pc(pc) , m_tp(tp) - , m_1_minute(0) + , m_1_minute(55) , m_message_index(0) , m_first_time(true) {} @@ -327,13 +329,14 @@ namespace libtorrent { namespace for (torrent::peer_iterator i = m_torrent.begin() , end(m_torrent.end()); i != end; ++i) { - if (!send_peer(*i->second)) continue; + peer_connection* peer = *i; + if (!send_peer(*peer)) continue; // don't write too big of a package if (num_added >= max_peer_entries) break; // only send proper bittorrent peers - bt_peer_connection* p = dynamic_cast(i->second); + bt_peer_connection* p = dynamic_cast(peer); if (!p) continue; // no supported flags to set yet @@ -343,15 +346,16 @@ namespace libtorrent { namespace #ifndef TORRENT_DISABLE_ENCRYPTION flags |= p->supports_encryption() ? 1 : 0; #endif + tcp::endpoint const& remote = peer->remote(); // i->first was added since the last time - if (i->first.address().is_v4()) + if (remote.address().is_v4()) { - detail::write_endpoint(i->first, pla_out); + detail::write_endpoint(remote, pla_out); detail::write_uint8(flags, plf_out); } else { - detail::write_endpoint(i->first, pla6_out); + detail::write_endpoint(remote, pla6_out); detail::write_uint8(flags, plf6_out); } ++num_added; From be8a004324b336de8a38f92556f64cc1e894e148 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 11 Nov 2007 00:30:29 +0000 Subject: [PATCH 0249/1009] add lsd to bindings --- libtorrent/bindings/python/src/docstrings.cpp | 4 ++++ libtorrent/bindings/python/src/session.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index 6c8682d9c..7b6b9d640 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -169,6 +169,10 @@ char const* session_start_upnp_doc = ""; char const* session_stop_upnp_doc = ""; +char const* session_start_lsd_doc = + ""; +char const* session_stop_lsd_doc = + ""; char const* session_start_natpmp_doc = ""; char const* session_stop_natpmp_doc = diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 05ec2a497..481348730 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -51,6 +51,8 @@ extern char const* session_get_pe_settings_doc; extern char const* session_set_severity_level_doc; extern char const* session_pop_alert_doc; extern char const* session_start_upnp_doc; +extern char const* session_start_lsd_doc; +extern char const* session_stop_lsd_doc; extern char const* session_stop_upnp_doc; extern char const* session_start_natpmp_doc; extern char const* session_stop_natpmp_doc; @@ -242,6 +244,8 @@ void bind_session() #endif .def("start_upnp", allow_threads(&session::start_upnp), session_start_upnp_doc) .def("stop_upnp", allow_threads(&session::stop_upnp), session_stop_upnp_doc) + .def("start_lsd", allow_threads(&session::start_lsd), session_start_lsd_doc) + .def("stop_lsd", allow_threads(&session::stop_lsd), session_stop_lsd_doc) .def("start_natpmp", allow_threads(&session::start_natpmp), session_start_natpmp_doc) .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) ; From a8c796ba59c2dde5f4f548283b3941db0ab82ea6 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 12 Nov 2007 02:27:03 +0000 Subject: [PATCH 0250/1009] Inital import of EditTrackersDialog. --- deluge/ui/gtkui/connectionmanager.py | 2 + deluge/ui/gtkui/edittrackersdialog.py | 78 +++++++ deluge/ui/gtkui/glade/edit_trackers.glade | 238 +++++++++++++++------- deluge/ui/gtkui/glade/plugin_dialog.glade | 114 ----------- deluge/ui/gtkui/menubar.py | 3 + 5 files changed, 249 insertions(+), 186 deletions(-) create mode 100644 deluge/ui/gtkui/edittrackersdialog.py delete mode 100644 deluge/ui/gtkui/glade/plugin_dialog.glade diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 780e05bf8..17ca1a1e3 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -69,6 +69,8 @@ class ConnectionManager(component.Component): self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) self.gtkui_config = ConfigManager("gtkui.conf") self.connection_manager = self.glade.get_widget("connection_manager") + # Make the Connection Manager window a transient for the main window. + self.connection_manager.set_transient_for(self.window.window) self.hostlist = self.glade.get_widget("hostlist") self.connection_manager.set_icon(deluge.common.get_logo(32)) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py new file mode 100644 index 000000000..b25f73049 --- /dev/null +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -0,0 +1,78 @@ +# +# edittrackersdialog.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gtk, gtk.glade +import pkg_resources + +import deluge.common +import deluge.ui.client as client +import deluge.ui.component as component +from deluge.log import LOG as log + +class EditTrackersDialog: + def __init__(self, torrent_id, parent=None): + self.glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/edit_trackers.glade")) + + self.dialog = self.glade.get_widget("edit_trackers_dialog") + self.dialog.set_icon(deluge.common.get_logo(32)) + + if parent != None: + self.dialog.set_transient_for(parent) + + # Connect the signals + self.glade.signal_autoconnect({ + "on_button_up_clicked": self.on_button_up_clicked, + "on_button_add_clicked": self.on_button_add_clicked, + "on_button_remove_clicked": self.on_button_remove_clicked, + "on_button_down_clicked": self.on_button_down_clicked, + "on_button_ok_clicked": self.on_button_ok_clicked, + "on_button_cancel_clicked": self.on_button_cancel_clicked + }) + + def run(self): + self.dialog.show() + + def on_button_up_clicked(self, widget): + log.debug("on_button_up_clicked") + def on_button_add_clicked(self, widget): + log.debug("on_button_add_clicked") + def on_button_remove_clicked(self, widget): + log.debug("on_button_remove_clicked") + def on_button_down_clicked(self, widget): + log.debug("on_button_down_clicked") + def on_button_ok_clicked(self, widget): + log.debug("on_button_ok_clicked") + def on_button_cancel_clicked(self, widget): + log.debug("on_button_cancel_clicked") diff --git a/deluge/ui/gtkui/glade/edit_trackers.glade b/deluge/ui/gtkui/glade/edit_trackers.glade index 32053dcd5..800c30e5a 100644 --- a/deluge/ui/gtkui/glade/edit_trackers.glade +++ b/deluge/ui/gtkui/glade/edit_trackers.glade @@ -1,73 +1,163 @@ - + - - 300 - 200 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 Edit Trackers - - + GTK_WIN_POS_CENTER_ON_PARENT + 400 + True + GDK_WINDOW_TYPE_HINT_DIALOG + False + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 - - 36 + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Tracker Editing - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC + 5 - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - 1 + 10 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-edit + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <big><b>Edit Trackers</b></big> + True + + + False + False + 1 + + + False + False + - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel - True - 0 - + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-up + True + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 0 + + + + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + True + 0 + + + + 2 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-down + True + 0 + + + + 3 + + + + + False + False + 1 + + False @@ -75,8 +165,30 @@ 1 + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END - + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + True True True @@ -84,34 +196,16 @@ gtk-ok True 0 - + - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - 1 - - - - - - 3 + 1 False - False - 2 + GTK_PACK_END diff --git a/deluge/ui/gtkui/glade/plugin_dialog.glade b/deluge/ui/gtkui/glade/plugin_dialog.glade deleted file mode 100644 index 68fa97357..000000000 --- a/deluge/ui/gtkui/glade/plugin_dialog.glade +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - 480 - 5 - Plugin Manager - 583 - 431 - True - GDK_WINDOW_TYPE_HINT_DIALOG - True - True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - 2 - - - True - False - - - True - True - - - True - - - - - True - - - True - False - GTK_WRAP_WORD - False - - - 10 - - - - - True - GTK_BUTTONBOX_SPREAD - - - True - False - gtk-preferences - True - - - - - - False - 1 - - - - - 10 - 1 - - - - - False - - - - - True - Plugins - - - tab - False - False - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - GTK_BUTTONBOX_END - - - True - gtk-close - True - - - - - False - GTK_PACK_END - - - - - - diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index aa1ede056..079d7d762 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -183,6 +183,9 @@ class MenuBar(component.Component): def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") + from edittrackersdialog import EditTrackersDialog + dialog = EditTrackersDialog(None, component.get("MainWindow").window) + dialog.run() def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") From 190d6d94eaa8f0fd729854a2cc90e610da92dd21 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 12 Nov 2007 04:51:23 +0000 Subject: [PATCH 0251/1009] Extra debug info. --- deluge/ui/gtkui/torrentview.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 162cd824a..bed6d2be2 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -233,9 +233,9 @@ class TorrentView(listview.ListView, component.Component): model.set_value(row, column_index, status[self.columns[column].status_field[0]]) - except (TypeError, KeyError): - log.warning("Unable to update column %s", - column) + except (TypeError, KeyError), e: + log.warning("Unable to update column %s: %s", + column, e) else: # We have more than 1 liststore column to update for index in column_index: From 6bced0fb8c97561be6cee2456e8df68436f59e7e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 12 Nov 2007 13:28:56 +0000 Subject: [PATCH 0252/1009] EditTrackerDialog now works. Save trackers in torrent state. Load trackers on torrent add from state. --- TODO | 12 +- deluge/core/core.py | 4 + deluge/core/torrent.py | 22 ++- deluge/core/torrentmanager.py | 47 ++++-- deluge/ui/client.py | 7 + deluge/ui/gtkui/edittrackersdialog.py | 134 ++++++++++++++++- deluge/ui/gtkui/glade/edit_trackers.glade | 168 ++++++++++++++++++++-- deluge/ui/gtkui/menubar.py | 4 +- deluge/ui/gtkui/torrentview.py | 10 +- 9 files changed, 373 insertions(+), 35 deletions(-) diff --git a/TODO b/TODO index 4dceb5879..75c9bf8cd 100644 --- a/TODO +++ b/TODO @@ -2,20 +2,22 @@ * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. * Figure out easy way for user-made plugins to add i18n support. -* Change the menubar.py gtkui component to menus.py and add support for plugins - to add menuitems to the torrentmenu in an easy way. * Restart daemon function * Docstrings! * Implement caching in client.py * Create a new add torrent dialog -* Create edit trackers dialog * Implement open folder * Maybe add pop-up menus to the status bar items * Address issue where torrents will redownload if the storage is moved outside of deluge. * Hide open folder if not localhost -* Modify sensitivity of torrent/tray menu based on connection state * Add classic/normal mode to preferences * Implement 'Classic' mode * Add remove torrent dialog and ability to remove data - +* Tray tooltip +* Add DBUS to gtkui so we can add torrents to existing session +* Add LSD +* Add torrentfiles location config option +* Add autoload folder +* Add wizard +* Add a health indication to the statusbar diff --git a/deluge/core/core.py b/deluge/core/core.py index 2c6f99e2c..4c088e8ae 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -387,6 +387,10 @@ class Core( def export_force_recheck(self, torrent_id): """Forces a data recheck on torrent_id""" return self.torrents.force_recheck(torrent_id) + + def export_set_torrent_trackers(self, torrent_id, trackers): + """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" + return self.torrents.set_trackers(torrent_id, trackers) # Signals def torrent_added(self, torrent_id): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 5b01a1395..18139ede7 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -38,7 +38,8 @@ import deluge.common class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ - def __init__(self, filename, handle, compact, save_path, total_uploaded=0): + def __init__(self, filename, handle, compact, save_path, total_uploaded=0, + trackers=None): # Set the filename self.filename = filename # Set the libtorrent handle @@ -53,7 +54,18 @@ class Torrent: self.save_path = save_path # The tracker status self.tracker_status = "" - + # Tracker list + if trackers == None: + self.trackers = [] + # Create a list of trackers + for value in self.handle.trackers(): + tracker = {} + tracker["url"] = value.url + tracker["tier"] = value.tier + self.trackers.append(tracker) + else: + self.trackers = trackers + def set_tracker_status(self, status): """Sets the tracker status""" self.tracker_status = status @@ -62,7 +74,8 @@ class Torrent: """Returns the state of this torrent for saving to the session state""" status = self.handle.status() return (self.torrent_id, self.filename, self.compact, status.paused, - self.save_path, self.total_uploaded + status.total_payload_upload) + self.save_path, self.total_uploaded + status.total_payload_upload, + self.trackers) def get_eta(self): """Returns the ETA in seconds for this torrent""" @@ -135,7 +148,7 @@ class Torrent: distributed_copies = status.distributed_copies if distributed_copies < 0: distributed_copies = 0.0 - + full_status = { "name": self.handle.torrent_info().name(), "total_size": self.handle.torrent_info().total_size(), @@ -161,6 +174,7 @@ class Torrent: "eta": self.get_eta(), "ratio": self.get_ratio(), "tracker": status.current_tracker, + "trackers": self.trackers, "tracker_status": self.tracker_status, "save_path": self.save_path, "files": self.get_files() diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 76217276a..6fbd6e0f6 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -48,13 +48,14 @@ from deluge.log import LOG as log class TorrentState: def __init__(self, torrent_id, filename, compact, paused, save_path, - total_uploaded): + total_uploaded, trackers): self.torrent_id = torrent_id self.filename = filename self.compact = compact self.paused = paused self.save_path = save_path self.total_uploaded = total_uploaded + self.trackers = trackers class TorrentManagerState: def __init__(self): @@ -121,7 +122,7 @@ class TorrentManager: return self.torrents.keys() def add(self, filename, filedump=None, compact=None, paused=False, - save_path=None, total_uploaded=0): + save_path=None, total_uploaded=0, trackers=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) @@ -167,14 +168,14 @@ class TorrentManager: storage_mode = lt.storage_mode_t(1) else: storage_mode = lt.storage_mode_t(2) - + try: handle = self.session.add_torrent( lt.torrent_info(filedump), str(save_path), resume_data=fastresume, storage_mode=storage_mode, - paused=paused) + paused=True) except RuntimeError, e: log.warning("Error adding torrent: %s", e) @@ -184,15 +185,23 @@ class TorrentManager: # Create a Torrent object torrent = Torrent(filename, handle, compact, - save_path, total_uploaded) + save_path, total_uploaded, trackers) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent - + + # Set the trackers + if trackers != None: + self.set_trackers(str(handle.info_hash()), trackers) + # Set per-torrent limits handle.set_max_connections(self.max_connections) handle.set_max_uploads(self.max_uploads) + # Resume the torrent if needed + if paused == False: + handle.resume() + # Save the torrent file self.save_torrent(filename, filedump) @@ -304,7 +313,28 @@ class TorrentManager: log.warning("Unable to resume torrent %s", key) return torrent_was_resumed - + + def set_trackers(self, torrent_id, trackers): + """Sets trackers""" + if trackers == [] or trackers == None: + return + log.debug("Setting trackers for %s", torrent_id) + tracker_list = [] + + for tracker in trackers: + new_entry = lt.announce_entry(tracker["url"]) + new_entry.tier = tracker["tier"] + tracker_list.append(new_entry) + + self.torrents[torrent_id].handle.replace_trackers(tracker_list) + # Print out the trackers + for t in self.torrents[torrent_id].handle.trackers(): + log.debug("tier: %s tracker: %s", t.tier, t.url) + # Set the tracker list in the torrent object + self.torrents[torrent_id].trackers = trackers + # Force a reannounce + self.force_reannounce(torrent_id) + def force_reannounce(self, torrent_id): """Force a tracker reannounce""" try: @@ -381,7 +411,8 @@ class TorrentManager: for torrent_state in state.torrents: self.add(torrent_state.filename, compact=torrent_state.compact, paused=torrent_state.paused, save_path=torrent_state.save_path, - total_uploaded=torrent_state.total_uploaded) + total_uploaded=torrent_state.total_uploaded, + trackers=torrent_state.trackers) def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 329a995b2..40edfff4b 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -337,6 +337,13 @@ def force_recheck(torrent_ids): except (AttributeError, socket.error): set_core_uri(None) +def set_torrent_trackers(torrent_id, trackers): + """Sets the torrents trackers""" + try: + get_core().set_torrent_trackers(torrent_id, trackers) + except (AttributeError, socket.error): + set_core_uri(None) + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index b25f73049..87d100e92 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -41,11 +41,16 @@ from deluge.log import LOG as log class EditTrackersDialog: def __init__(self, torrent_id, parent=None): + self.torrent_id = torrent_id self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/edit_trackers.glade")) self.dialog = self.glade.get_widget("edit_trackers_dialog") + self.treeview = self.glade.get_widget("tracker_treeview") + self.add_tracker_dialog = self.glade.get_widget("add_tracker_dialog") + self.add_tracker_dialog.set_transient_for(self.dialog) + self.dialog.set_icon(deluge.common.get_logo(32)) if parent != None: @@ -58,21 +63,148 @@ class EditTrackersDialog: "on_button_remove_clicked": self.on_button_remove_clicked, "on_button_down_clicked": self.on_button_down_clicked, "on_button_ok_clicked": self.on_button_ok_clicked, - "on_button_cancel_clicked": self.on_button_cancel_clicked + "on_button_cancel_clicked": self.on_button_cancel_clicked, + "on_button_add_ok_clicked": self.on_button_add_ok_clicked, + "on_button_add_cancel_clicked": self.on_button_add_cancel_clicked }) + + # Create a liststore for tier, url + self.liststore = gtk.ListStore(int, str) + + # Create the columns + self.treeview.append_column( + gtk.TreeViewColumn(_("Tier"), gtk.CellRendererText(), text=0)) + self.treeview.append_column( + gtk.TreeViewColumn(_("Tracker"), gtk.CellRendererText(), text=1)) + self.treeview.set_model(self.liststore) + self.liststore.set_sort_column_id(0, gtk.SORT_ASCENDING) + def run(self): + # Make sure we have a torrent_id.. if not just return + if self.torrent_id == None: + return + + # Get the trackers for this torrent + trackers = client.get_torrent_status(self.torrent_id, ["trackers"]) + for tracker in trackers["trackers"]: + self.add_tracker(tracker["tier"], tracker["url"]) + self.dialog.show() + + def add_tracker(self, tier, url): + """Adds a tracker to the list""" + self.liststore.append([tier, url]) + + def get_selected(self): + """Returns the selected tracker""" + return self.treeview.get_selection().get_selected()[1] def on_button_up_clicked(self, widget): log.debug("on_button_up_clicked") + selected = self.get_selected() + num_rows = self.liststore.iter_n_children(None) + if selected != None and num_rows > 1: + tier = self.liststore.get_value(selected, 0) + new_tier = tier - 1 + # Return if the tier is already at the top + if tier == 0: + return + # Change the tier of the tracker we're surplanting + def change_tier(model, path, iter, data): + t = model.get_value(iter, 0) + if t == data: + model.set_value(iter, 0, data + 1) + self.liststore.foreach(change_tier, new_tier) + + # Now change the tier for this tracker + self.liststore.set_value(selected, 0, new_tier) + def on_button_add_clicked(self, widget): log.debug("on_button_add_clicked") + # Show the add tracker dialog + self.add_tracker_dialog.show() + self.glade.get_widget("entry_tracker").grab_focus() + def on_button_remove_clicked(self, widget): log.debug("on_button_remove_clicked") + selected = self.get_selected() + if selected != None: + self.liststore.remove(selected) + def on_button_down_clicked(self, widget): log.debug("on_button_down_clicked") + selected = self.get_selected() + num_rows = self.liststore.iter_n_children(None) + if selected != None and num_rows > 1: + tier = self.liststore.get_value(selected, 0) + new_tier = tier + 1 + # This tracker is on the bottom already + if new_tier == num_rows: + return + # Change the tier of the tracker we're surplanting + def change_tier(model, path, iter, data): + t = model.get_value(iter, 0) + if t == data: + model.set_value(iter, 0, data - 1) + self.liststore.foreach(change_tier, new_tier) + # Now change the tier for this tracker + self.liststore.set_value(selected, 0, new_tier) + def on_button_ok_clicked(self, widget): log.debug("on_button_ok_clicked") + self.trackers = [] + def each(model, path, iter, data): + tracker = {} + tracker["tier"] = model.get_value(iter, 0) + tracker["url"] = model.get_value(iter, 1) + self.trackers.append(tracker) + self.liststore.foreach(each, None) + # Set the torrens trackers + client.set_torrent_trackers(self.torrent_id, self.trackers) + self.dialog.destroy() + def on_button_cancel_clicked(self, widget): log.debug("on_button_cancel_clicked") + self.dialog.destroy() + + def on_button_add_ok_clicked(self, widget): + log.debug("on_button_add_ok_clicked") + tracker = self.glade.get_widget("entry_tracker").get_text() + if tracker[:7] != "http://" or tracker[:8] != "https://": + # Bad url.. lets prepend http:// + tracker = "http://" + tracker + + # Figure out what tier number to use.. it's going to be the highest+1 + # Also check for duplicates + self.highest_tier = 0 + self.duplicate = False + def tier_count(model, path, iter, data): + tier = model.get_value(iter, 0) + if tier > self.highest_tier: + self.highest_tier = tier + tracker = model.get_value(iter, 1) + if data == tracker: + # We already have this tracker in the list + self.duplicate = True + + # Check if there are any entries + if self.liststore.iter_n_children(None) > 0: + self.liststore.foreach(tier_count, tracker) + else: + self.highest_tier = -1 + + # If not a duplicate, then add it to the list + if not self.duplicate: + # Add the tracker to the list + self.add_tracker(self.highest_tier + 1, tracker) + + # Clear the entry widget and hide the dialog + self.glade.get_widget("entry_tracker").set_text("") + self.add_tracker_dialog.hide() + + def on_button_add_cancel_clicked(self, widget): + log.debug("on_button_add_cancel_clicked") + # Clear the entry widget and hide the dialog + self.glade.get_widget("entry_tracker").set_text("") + self.add_tracker_dialog.hide() diff --git a/deluge/ui/gtkui/glade/edit_trackers.glade b/deluge/ui/gtkui/glade/edit_trackers.glade index 800c30e5a..9e6fc8f71 100644 --- a/deluge/ui/gtkui/glade/edit_trackers.glade +++ b/deluge/ui/gtkui/glade/edit_trackers.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -70,20 +70,12 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - @@ -93,7 +85,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 - GTK_BUTTONBOX_END + GTK_BUTTONBOX_CENTER True @@ -160,8 +152,6 @@ - False - False 1 @@ -211,4 +201,152 @@ + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Add Tracker + GTK_WIN_POS_CENTER_ON_PARENT + True + GDK_WINDOW_TYPE_HINT_DIALOG + False + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Add Tracker</b> + True + + + False + False + 1 + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Tracker: + + + False + False + + + + + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + 1 + + + + + False + False + 2 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 0 + + + + 1 + + + + + False + GTK_PACK_END + + + + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 079d7d762..79a7851ae 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -184,7 +184,9 @@ class MenuBar(component.Component): def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") from edittrackersdialog import EditTrackersDialog - dialog = EditTrackersDialog(None, component.get("MainWindow").window) + dialog = EditTrackersDialog( + component.get("TorrentView").get_selected_torrent(), + component.get("MainWindow").window) dialog.run() def on_menuitem_remove_activate(self, data=None): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index bed6d2be2..5c5d9525a 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -271,7 +271,15 @@ class TorrentView(listview.ListView, component.Component): self.update() break row = self.liststore.iter_next(row) - + + def get_selected_torrent(self): + """Returns a torrent_id or None. If multiple torrents are selected, + it will return the torrent_id of the first one.""" + selected = self.get_selected_torrents() + if selected == None: + return selected + return selected[0] + def get_selected_torrents(self): """Returns a list of selected torrents or None""" torrent_ids = [] From 10456b3f992ae86d837bcf205a1c4006e02c74bd Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 13 Nov 2007 08:47:26 +0000 Subject: [PATCH 0253/1009] Add support for Dbus and adding torrents to an already running client. Minor UI tweaks. Additional error checking in update functions. --- TODO | 7 +- deluge/ui/client.py | 15 +- deluge/ui/component.py | 1 - deluge/ui/gtkui/dbusinterface.py | 109 ++ deluge/ui/gtkui/glade/main_window.glade | 1294 +++++++++++------------ deluge/ui/gtkui/gtkui.py | 5 + deluge/ui/gtkui/toolbar.py | 21 +- deluge/ui/gtkui/torrentdetails.py | 68 +- 8 files changed, 823 insertions(+), 697 deletions(-) create mode 100644 deluge/ui/gtkui/dbusinterface.py diff --git a/TODO b/TODO index 75c9bf8cd..6e9b9a488 100644 --- a/TODO +++ b/TODO @@ -15,9 +15,14 @@ * Implement 'Classic' mode * Add remove torrent dialog and ability to remove data * Tray tooltip -* Add DBUS to gtkui so we can add torrents to existing session * Add LSD * Add torrentfiles location config option * Add autoload folder * Add wizard * Add a health indication to the statusbar +* Add sidebar for labels and other things.. Plugins should be able to add their + own section to this. +* Have the dbus interface queue up torrents if we're not connected to a host. + Once connected it should prompt the user if they would like to add the + queued torrents. Maybe add an indicator to the status bar that their are + queued torrents. diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 40edfff4b..d8e2a133f 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -126,7 +126,13 @@ def is_localhost(): return True return False - + +def connected(): + """Returns True if connected to a host, and False if not.""" + if get_core_uri() != None: + return True + return False + def shutdown(): """Shutdown the core daemon""" try: @@ -146,7 +152,12 @@ def add_torrent_file(torrent_files): for torrent_file in torrent_files: # Open the .torrent file for reading because we need to send it's # contents to the core. - f = open(torrent_file, "rb") + try: + f = open(torrent_file, "rb") + except Exception, e: + log.warning("Unable to open %s: %s", torrent_file, e) + continue + # Get the filename because the core doesn't want a path. (path, filename) = os.path.split(torrent_file) fdump = xmlrpclib.Binary(f.read()) diff --git a/deluge/ui/component.py b/deluge/ui/component.py index 434c83391..23cb5838e 100644 --- a/deluge/ui/component.py +++ b/deluge/ui/component.py @@ -67,7 +67,6 @@ class Component: class ComponentRegistry: def __init__(self): self.components = {} - #self.component_state = {} self.update_timer = None def register(self, name, obj): diff --git a/deluge/ui/gtkui/dbusinterface.py b/deluge/ui/gtkui/dbusinterface.py new file mode 100644 index 000000000..0c91a28e7 --- /dev/null +++ b/deluge/ui/gtkui/dbusinterface.py @@ -0,0 +1,109 @@ +# +# dbusinterface.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import sys +import os + +# Import DBUS +#from dbus import Interface, SessionBus, version +import dbus, dbus.service + +if dbus.version >= (0,41,0) and dbus.version < (0,80,0): + import dbus.glib +elif dbus.version >= (0,80,0): + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + +import deluge.ui.component as component +import deluge.ui.client as client +import deluge.common +from deluge.log import LOG as log + +class DbusInterface(dbus.service.Object, component.Component): + def __init__(self, args, path="/org/deluge_torrent/Deluge"): + component.Component.__init__(self, "DbusInterface") + + # Check to see if the daemon is already running and if not, start it + bus = dbus.SessionBus() + obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") + iface = dbus.Interface(obj, "org.freedesktop.DBus") + if iface.NameHasOwner("org.deluge_torrent.Deluge"): + # Deluge client already running.. Lets exit. + log.info("Deluge already running..") + log.debug("args: %s", args) + # Convert the paths to absolutes + new_args = [] + for arg in args: + if not deluge.common.is_url(arg): + new_args.append(os.path.abspath(arg)) + args = new_args + + # Send the args to the running session + if args != [] and args != None: + bus = dbus.SessionBus() + proxy = bus.get_object("org.deluge_torrent.Deluge", + "/org/deluge_torrent/Deluge") + ui = dbus.Interface(proxy, "org.deluge_torrent.Deluge") + ui.process_args(args) + # Exit + log.debug("Exiting..") + sys.exit(0) + + # Register Deluge with Dbus + log.info("Registering with DBUS..") + bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", + bus=dbus.SessionBus()) + dbus.service.Object.__init__(self, bus_name, path) + + @dbus.service.method("org.deluge_torrent.Deluge", in_signature="as") + def process_args(self, args): + """Process arguments sent to already running Deluge""" + # Pythonize the values from Dbus + dbus_args = args + args = [] + for arg in dbus_args: + args.append(str(arg)) + log.debug("Processing args from other process: %s", args) + for arg in args: + # Check to see if we're connected to a host first + if client.connected(): + if deluge.common.is_url(arg): + log.debug("Attempting to add %s from external source..", + arg) + client.add_torrent_url(arg) + else: + # Just a file + log.debug("Attempting to add %s from external source..", + os.path.abspath(arg)) + client.add_torrent_file([os.path.abspath(arg)]) + diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index a6d21d6ba..ab7c5a27f 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -204,7 +204,7 @@ True - False + GTK_ICON_SIZE_SMALL_TOOLBAR True @@ -329,24 +329,19 @@ True - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT - + True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - True - True - False - - + True + True + True + False @@ -356,706 +351,695 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False - + True - False - True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE + 1 + 2 + 10 - + True - 1 - 2 - 10 + 0 - + True - 0 + 10 + 10 + 15 + 15 - + True - 10 - 10 - 15 - 15 + 5 - + True - 5 + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 - + True - 0.10000000149 + 0 - False - False + 1 + 2 - + True - 5 - 4 - 5 + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 - + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + True 0 - - 1 - 2 - + + + 1 + 2 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - + True 0 1 - <b>Pieces:</b> + <b>Availability:</b> True - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - False - 1 + 2 + 3 + 4 + 5 + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + False + 1 + - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - GTK_FILL - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - + <b>Statistics</b> + True - 1 - 2 - GTK_FILL + label_item + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + - - False - - - - - True - Details - - - tab - False - False - + + + True + Details + + + tab + False + + False diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 9a5b0ce27..94a2ac5b2 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -51,6 +51,7 @@ from statusbar import StatusBar from connectionmanager import ConnectionManager from signals import Signals from pluginmanager import PluginManager +from dbusinterface import DbusInterface from deluge.configmanager import ConfigManager from deluge.log import LOG as log import deluge.configmanager @@ -86,6 +87,10 @@ DEFAULT_PREFS = { class GtkUI: def __init__(self, args): + # Start the Dbus Interface before anything else.. Just in case we are + # already running. + self.dbusinterface = DbusInterface(args) + # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') locale.bindtextdomain("deluge", diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 8d083dedc..7dd7c858e 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -175,11 +175,16 @@ class ToolBar(component.Component): # Disable the 'Clear Seeders' button if there's no finished torrent finished = False - selecteds = component.get('TorrentView').get_selected_torrents() - if not selecteds : selecteds = [] + selected = component.get('TorrentView').get_selected_torrents() + if not selected: + selected = [] - for torrent in selecteds : - status = client.get_torrent_status(torrent, ['state'])['state'] + for torrent in selected: + try: + status = client.get_torrent_status(torrent, ['state'])['state'] + except KeyError, e: + log.debug("Error getting torrent state: %s", e) + continue if status == self.STATE_PAUSED: resume = True elif status in [self.STATE_FINISHED, self.STATE_SEEDING]: @@ -187,16 +192,18 @@ class ToolBar(component.Component): pause = True else: pause = True - if pause and resume and finished: break + if pause and resume and finished: + break # Enable the 'Remove Torrent' button only if there's some selected # torrent. - remove = (len(selecteds ) > 0) + remove = (len(selected) > 0) if not finished: torrents = client.get_session_state() for torrent in torrents: - if torrent in selecteds: continue + if torrent in selected: + continue status = client.get_torrent_status(torrent, ['state'])['state'] if status in [self.STATE_FINISHED, self.STATE_SEEDING]: finished = True diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index b881a8153..4204a110f 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -118,37 +118,43 @@ class TorrentDetails(component.Component): return # We need to adjust the value core gives us for progress - progress = status["progress"]/100 - self.progress_bar.set_fraction(progress) - self.progress_bar.set_text(deluge.common.fpcnt(progress)) - - self.name.set_text(status["name"]) - self.total_size.set_text(deluge.common.fsize(status["total_size"])) - self.num_files.set_text(str(status["num_files"])) - self.pieces.set_text("%s (%s)" % (status["num_pieces"], - deluge.common.fsize(status["piece_length"]))) - self.availability.set_text("%.3f" % status["distributed_copies"]) - self.total_downloaded.set_text("%s (%s)" % \ - (deluge.common.fsize(status["total_done"]), - deluge.common.fsize(status["total_payload_download"]))) - self.total_uploaded.set_text("%s (%s)" % \ - (deluge.common.fsize(status["total_uploaded"]), - deluge.common.fsize(status["total_payload_upload"]))) - self.download_speed.set_text( - deluge.common.fspeed(status["download_payload_rate"])) - self.upload_speed.set_text( - deluge.common.fspeed(status["upload_payload_rate"])) - self.seeders.set_text(deluge.common.fpeer(status["num_seeds"], - status["total_seeds"])) - self.peers.set_text(deluge.common.fpeer(status["num_peers"], - status["total_peers"])) - self.eta.set_text(deluge.common.ftime(status["eta"])) - self.share_ratio.set_text("%.3f" % status["ratio"]) - self.tracker.set_text(status["tracker"]) - self.tracker_status.set_text(status["tracker_status"]) - self.next_announce.set_text( - deluge.common.ftime(status["next_announce"])) - self.torrent_path.set_text(status["save_path"]) + try: + progress = status["progress"]/100 + + self.progress_bar.set_fraction(progress) + self.progress_bar.set_text(deluge.common.fpcnt(progress)) + + self.name.set_text(status["name"]) + self.total_size.set_text( + deluge.common.fsize(status["total_size"])) + self.num_files.set_text(str(status["num_files"])) + self.pieces.set_text("%s (%s)" % (status["num_pieces"], + deluge.common.fsize(status["piece_length"]))) + self.availability.set_text( + "%.3f" % status["distributed_copies"]) + self.total_downloaded.set_text("%s (%s)" % \ + (deluge.common.fsize(status["total_done"]), + deluge.common.fsize(status["total_payload_download"]))) + self.total_uploaded.set_text("%s (%s)" % \ + (deluge.common.fsize(status["total_uploaded"]), + deluge.common.fsize(status["total_payload_upload"]))) + self.download_speed.set_text( + deluge.common.fspeed(status["download_payload_rate"])) + self.upload_speed.set_text( + deluge.common.fspeed(status["upload_payload_rate"])) + self.seeders.set_text(deluge.common.fpeer(status["num_seeds"], + status["total_seeds"])) + self.peers.set_text(deluge.common.fpeer(status["num_peers"], + status["total_peers"])) + self.eta.set_text(deluge.common.ftime(status["eta"])) + self.share_ratio.set_text("%.3f" % status["ratio"]) + self.tracker.set_text(status["tracker"]) + self.tracker_status.set_text(status["tracker_status"]) + self.next_announce.set_text( + deluge.common.ftime(status["next_announce"])) + self.torrent_path.set_text(status["save_path"]) + except KeyError, e: + log.debug(e) def clear(self): # Only update if this page is showing From 80ef895a51a66d38dc3d429e865bd6bafbfca8af Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 14 Nov 2007 11:29:06 +0000 Subject: [PATCH 0254/1009] Add dependency support to the components. --- deluge/ui/component.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/deluge/ui/component.py b/deluge/ui/component.py index 23cb5838e..e5055c827 100644 --- a/deluge/ui/component.py +++ b/deluge/ui/component.py @@ -40,9 +40,9 @@ COMPONENT_STATE = [ ] class Component: - def __init__(self, name): + def __init__(self, name, depend=None): # Register with the ComponentRegistry - register(name, self) + register(name, self, depend) self._state = COMPONENT_STATE.index("Stopped") def get_state(self): @@ -67,12 +67,15 @@ class Component: class ComponentRegistry: def __init__(self): self.components = {} + self.depend = {} self.update_timer = None - def register(self, name, obj): - """Registers a component""" + def register(self, name, obj, depend): + """Registers a component.. depend must be list or None""" log.debug("Registered %s with ComponentRegistry..", name) self.components[name] = obj + if depend != None: + self.depend[name] = depend def get(self, name): """Returns a reference to the component 'name'""" @@ -81,13 +84,25 @@ class ComponentRegistry: def start(self, update_interval=1000): """Starts all components""" for component in self.components.keys(): - self.components[component].start() - self.components[component]._start() + self.start_component(component) # Start the update timer self.update_timer = gobject.timeout_add(update_interval, self.update) # Do an update right away self.update() + + def start_component(self, name): + """Starts a component""" + # Check to see if this component has any dependencies + if self.depend.has_key(name): + for depend in self.depend[name]: + self.start_component(depend) + # Only start if the component is stopped. + if self.components[name].get_state() == \ + COMPONENT_STATE.index("Stopped"): + self.components[name].start() + self.components[name]._start() + def stop(self): """Stops all components""" @@ -109,9 +124,9 @@ class ComponentRegistry: _ComponentRegistry = ComponentRegistry() -def register(name, obj): +def register(name, obj, depend=None): """Registers a UI component with the registry""" - _ComponentRegistry.register(name, obj) + _ComponentRegistry.register(name, obj, depend) def start(): """Starts all components""" From eac95882d4bfa179ff11f8bbcfd0fdba53561785 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 14 Nov 2007 15:51:22 +0000 Subject: [PATCH 0255/1009] Queued torrents update. --- deluge/ui/gtkui/dbusinterface.py | 90 +++++++-- deluge/ui/gtkui/glade/queuedtorrents.glade | 210 +++++++++++++++++++++ deluge/ui/gtkui/statusbar.py | 36 +++- 3 files changed, 320 insertions(+), 16 deletions(-) create mode 100644 deluge/ui/gtkui/glade/queuedtorrents.glade diff --git a/deluge/ui/gtkui/dbusinterface.py b/deluge/ui/gtkui/dbusinterface.py index 0c91a28e7..719489210 100644 --- a/deluge/ui/gtkui/dbusinterface.py +++ b/deluge/ui/gtkui/dbusinterface.py @@ -34,8 +34,9 @@ import sys import os +import gtk +import gobject # Import DBUS -#from dbus import Interface, SessionBus, version import dbus, dbus.service if dbus.version >= (0,41,0) and dbus.version < (0,80,0): @@ -51,8 +52,9 @@ from deluge.log import LOG as log class DbusInterface(dbus.service.Object, component.Component): def __init__(self, args, path="/org/deluge_torrent/Deluge"): - component.Component.__init__(self, "DbusInterface") - + component.Component.__init__(self, "DbusInterface", ["StatusBar"]) + self.queue = [] + self.widgets = None # Check to see if the daemon is already running and if not, start it bus = dbus.SessionBus() obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") @@ -79,12 +81,66 @@ class DbusInterface(dbus.service.Object, component.Component): log.debug("Exiting..") sys.exit(0) + # Process the args if any + self.process_args(args) # Register Deluge with Dbus log.info("Registering with DBUS..") bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", bus=dbus.SessionBus()) dbus.service.Object.__init__(self, bus_name, path) + def start(self): + """Called when we connect to a host""" + log.debug("DbusInterface start..") + if len(self.queue) == 0: + return + # Make sure status bar info is showing + self.widgets = None + self.update_status_bar() + + def add_to_queue(self, torrents): + """Adds the list of torrents to the queue""" + # Add to the queue while removing duplicates + self.queue = list(set(self.queue + torrents)) + + # Update the status bar + self.update_status_bar() + + def update_status_bar(self): + """Attempts to update status bar""" + # If there are no queued torrents.. remove statusbar widgets and return + if len(self.queue) == 0: + if self.widgets != None: + for widget in self.widgets: + component.get("StatusBar").remove(widget) + return False + + try: + statusbar = component.get("StatusBar") + except Exception, e: + # The statusbar hasn't been loaded yet, so we'll add a timer to + # update it later. + gobject.timeout_add(100, self.update_status_bar) + return False + + # Set the label text for statusbar + if len(self.queue) > 1: + label = str(len(self.queue)) + _(" Torrents Queued") + else: + label = str(len(self.queue)) + _(" Torrent Queued") + + # Add the statusbar items if needed, or just modify the label if they + # have already been added. + if self.widgets == None: + self.widgets = component.get("StatusBar").add_item( + stock=gtk.STOCK_SORT_DESCENDING, + text=label) + else: + self.widgets[1].set_text(label) + + # We return False so the timer stops + return False + @dbus.service.method("org.deluge_torrent.Deluge", in_signature="as") def process_args(self, args): """Process arguments sent to already running Deluge""" @@ -94,16 +150,20 @@ class DbusInterface(dbus.service.Object, component.Component): for arg in dbus_args: args.append(str(arg)) log.debug("Processing args from other process: %s", args) + if not client.connected(): + # We're not connected so add these to the queue + log.debug("Not connected to host.. Adding to queue.") + self.add_to_queue(args) + return + for arg in args: - # Check to see if we're connected to a host first - if client.connected(): - if deluge.common.is_url(arg): - log.debug("Attempting to add %s from external source..", - arg) - client.add_torrent_url(arg) - else: - # Just a file - log.debug("Attempting to add %s from external source..", - os.path.abspath(arg)) - client.add_torrent_file([os.path.abspath(arg)]) - + if deluge.common.is_url(arg): + log.debug("Attempting to add %s from external source..", + arg) + client.add_torrent_url(arg) + else: + # Just a file + log.debug("Attempting to add %s from external source..", + os.path.abspath(arg)) + client.add_torrent_file([os.path.abspath(arg)]) + diff --git a/deluge/ui/gtkui/glade/queuedtorrents.glade b/deluge/ui/gtkui/glade/queuedtorrents.glade new file mode 100644 index 000000000..dc59ada44 --- /dev/null +++ b/deluge/ui/gtkui/glade/queuedtorrents.glade @@ -0,0 +1,210 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + GTK_WIN_POS_CENTER_ON_PARENT + True + . + GDK_WINDOW_TYPE_HINT_DIALOG + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <big><b>Add Queued Torrents</b></big> + True + + + False + False + 1 + + + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_START + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + True + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-clear + True + 0 + + + + 1 + + + + + False + False + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 11 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrents on connect + 0 + True + + + False + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Options + + + label_item + + + + + 3 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 0 + + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index ffe36710d..3914a3075 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -86,10 +86,44 @@ class StatusBar(component.Component): image = gtk.Image() image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) self.hbox.pack_start(image, expand=False, fill=False) - label = gtk.Label("Not connected to daemon..") + label = gtk.Label(_("Not Connected")) self.hbox.pack_start(label, expand=False, fill=False) self.statusbar.show_all() + def add_item(self, image=None, stock=None, text=None): + """Adds an item to the status bar""" + # The return tuple.. we return whatever widgets we add + ret = [] + # Add image from file or stock + if image != None or stock != None: + _image = gtk.Image() + if image != None: + _image.set_from_file(image) + if stock != None: + _image.set_from_stock(stock, gtk.ICON_SIZE_MENU) + self.hbox.pack_start(_image, expand=False, fill=False) + ret.append(_image) + + # Add text + if text != None: + label = gtk.Label(text) + self.hbox.pack_start(label, expand=False, fill=False) + ret.append(label) + + # Show the widgets + for widget in ret: + widget.show() + + # Return the widgets + return tuple(ret) + + def remove_item(self, widget): + """Removes an item from the statusbar""" + try: + self.hbox.remove(widget) + except Exception, e: + log.debug("Unable to remove widget: %s", e) + def clear_statusbar(self): def remove(child): self.hbox.remove(child) From b871ae81524cd5a76ae30bcf6fe9a57ca5a717b9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 17 Nov 2007 02:13:43 +0000 Subject: [PATCH 0256/1009] Do not force_reannounce if 0 trackers. --- deluge/core/torrentmanager.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 6fbd6e0f6..8d83efbfb 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -316,9 +316,10 @@ class TorrentManager: def set_trackers(self, torrent_id, trackers): """Sets trackers""" - if trackers == [] or trackers == None: - return - log.debug("Setting trackers for %s", torrent_id) + if trackers == None: + trackers = [] + + log.debug("Setting trackers for %s: %s", torrent_id, trackers) tracker_list = [] for tracker in trackers: @@ -332,8 +333,9 @@ class TorrentManager: log.debug("tier: %s tracker: %s", t.tier, t.url) # Set the tracker list in the torrent object self.torrents[torrent_id].trackers = trackers - # Force a reannounce - self.force_reannounce(torrent_id) + if len(trackers) > 0: + # Force a reannounce if there is at least 1 tracker + self.force_reannounce(torrent_id) def force_reannounce(self, torrent_id): """Force a tracker reannounce""" From 3c78e41fc2ec78ec26f3ae6314716b228850d527 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 17 Nov 2007 10:17:51 +0000 Subject: [PATCH 0257/1009] Add missing file. --- deluge/plugins/corepluginbase.py | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 deluge/plugins/corepluginbase.py diff --git a/deluge/plugins/corepluginbase.py b/deluge/plugins/corepluginbase.py new file mode 100644 index 000000000..188dc5053 --- /dev/null +++ b/deluge/plugins/corepluginbase.py @@ -0,0 +1,47 @@ +# +# core.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + +class CorePluginBase: + def __init__(self, plugin_api, plugin_name): + self.plugin = plugin_api + # Register all export_* functions + for func in dir(self): + if func.startswith("export_"): + log.debug("Registering export function %s as %s", func, + plugin_name.lower() + "_" + func[7:]) + self.plugin.get_core().register_function( + getattr(self, "%s" % func), plugin_name.lower()\ + + "_" + func[7:]) + log.debug("CorePlugin initialized..") From 35fe99eee3fadd43d4466bd07fb09685cc21eec4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 17 Nov 2007 11:36:29 +0000 Subject: [PATCH 0258/1009] Updates to the QueuedTorrents dialog stuff. Updates to StatusBar. --- TODO | 6 +- deluge/core/torrentmanager.py | 3 +- deluge/ui/gtkui/dbusinterface.py | 58 +------ deluge/ui/gtkui/glade/queuedtorrents.glade | 70 +++------ deluge/ui/gtkui/gtkui.py | 12 +- deluge/ui/gtkui/queuedtorrents.py | 166 +++++++++++++++++++++ deluge/ui/gtkui/statusbar.py | 83 +++++++---- 7 files changed, 260 insertions(+), 138 deletions(-) create mode 100644 deluge/ui/gtkui/queuedtorrents.py diff --git a/TODO b/TODO index 6e9b9a488..6e590d209 100644 --- a/TODO +++ b/TODO @@ -22,7 +22,5 @@ * Add a health indication to the statusbar * Add sidebar for labels and other things.. Plugins should be able to add their own section to this. -* Have the dbus interface queue up torrents if we're not connected to a host. - Once connected it should prompt the user if they would like to add the - queued torrents. Maybe add an indicator to the status bar that their are - queued torrents. +* Finish queuedtorrents dialog +* Update statusbar to use new statusbaritems diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 8d83efbfb..6bde4360b 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -341,7 +341,8 @@ class TorrentManager: """Force a tracker reannounce""" try: self.torrents[torrent_id].handle.force_reannounce() - except: + except Exception, e: + log.debug("Unable to force reannounce: %s", e) return False return True diff --git a/deluge/ui/gtkui/dbusinterface.py b/deluge/ui/gtkui/dbusinterface.py index 719489210..d4699f526 100644 --- a/deluge/ui/gtkui/dbusinterface.py +++ b/deluge/ui/gtkui/dbusinterface.py @@ -52,9 +52,7 @@ from deluge.log import LOG as log class DbusInterface(dbus.service.Object, component.Component): def __init__(self, args, path="/org/deluge_torrent/Deluge"): - component.Component.__init__(self, "DbusInterface", ["StatusBar"]) - self.queue = [] - self.widgets = None + component.Component.__init__(self, "DbusInterface") # Check to see if the daemon is already running and if not, start it bus = dbus.SessionBus() obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") @@ -89,58 +87,6 @@ class DbusInterface(dbus.service.Object, component.Component): bus=dbus.SessionBus()) dbus.service.Object.__init__(self, bus_name, path) - def start(self): - """Called when we connect to a host""" - log.debug("DbusInterface start..") - if len(self.queue) == 0: - return - # Make sure status bar info is showing - self.widgets = None - self.update_status_bar() - - def add_to_queue(self, torrents): - """Adds the list of torrents to the queue""" - # Add to the queue while removing duplicates - self.queue = list(set(self.queue + torrents)) - - # Update the status bar - self.update_status_bar() - - def update_status_bar(self): - """Attempts to update status bar""" - # If there are no queued torrents.. remove statusbar widgets and return - if len(self.queue) == 0: - if self.widgets != None: - for widget in self.widgets: - component.get("StatusBar").remove(widget) - return False - - try: - statusbar = component.get("StatusBar") - except Exception, e: - # The statusbar hasn't been loaded yet, so we'll add a timer to - # update it later. - gobject.timeout_add(100, self.update_status_bar) - return False - - # Set the label text for statusbar - if len(self.queue) > 1: - label = str(len(self.queue)) + _(" Torrents Queued") - else: - label = str(len(self.queue)) + _(" Torrent Queued") - - # Add the statusbar items if needed, or just modify the label if they - # have already been added. - if self.widgets == None: - self.widgets = component.get("StatusBar").add_item( - stock=gtk.STOCK_SORT_DESCENDING, - text=label) - else: - self.widgets[1].set_text(label) - - # We return False so the timer stops - return False - @dbus.service.method("org.deluge_torrent.Deluge", in_signature="as") def process_args(self, args): """Process arguments sent to already running Deluge""" @@ -153,7 +99,7 @@ class DbusInterface(dbus.service.Object, component.Component): if not client.connected(): # We're not connected so add these to the queue log.debug("Not connected to host.. Adding to queue.") - self.add_to_queue(args) + component.get("QueuedTorrents").add_to_queue(args) return for arg in args: diff --git a/deluge/ui/gtkui/glade/queuedtorrents.glade b/deluge/ui/gtkui/glade/queuedtorrents.glade index dc59ada44..2428c89e9 100644 --- a/deluge/ui/gtkui/glade/queuedtorrents.glade +++ b/deluge/ui/gtkui/glade/queuedtorrents.glade @@ -1,14 +1,17 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 + Queued Torrents GTK_WIN_POS_CENTER_ON_PARENT + 450 + 300 True - . GDK_WINDOW_TYPE_HINT_DIALOG + False True @@ -23,7 +26,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 + 10 True @@ -39,6 +42,8 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 <big><b>Add Queued Torrents</b></big> True @@ -63,11 +68,11 @@ GTK_POLICY_AUTOMATIC GTK_SHADOW_IN - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True + False @@ -115,49 +120,17 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 11 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrents on connect - 0 - True - - - False - False - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Options - - - label_item - - + Automatically add torrents on connect + 0 + True + False + False 3 @@ -172,21 +145,26 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel + gtk-close True 0 - + True + False True + True + True + True + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-add diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 94a2ac5b2..5757b80fb 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -52,6 +52,7 @@ from connectionmanager import ConnectionManager from signals import Signals from pluginmanager import PluginManager from dbusinterface import DbusInterface +from queuedtorrents import QueuedTorrents from deluge.configmanager import ConfigManager from deluge.log import LOG as log import deluge.configmanager @@ -87,10 +88,6 @@ DEFAULT_PREFS = { class GtkUI: def __init__(self, args): - # Start the Dbus Interface before anything else.. Just in case we are - # already running. - self.dbusinterface = DbusInterface(args) - # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') locale.bindtextdomain("deluge", @@ -107,7 +104,12 @@ class GtkUI: # Make sure gtkui.conf has at least the defaults set config = ConfigManager("gtkui.conf", DEFAULT_PREFS) - + + # Start the Dbus Interface before anything else.. Just in case we are + # already running. + self.queuedtorrents = QueuedTorrents() + self.dbusinterface = DbusInterface(args) + # We make sure that the UI components start once we get a core URI client.connect_on_new_core(component.start) client.connect_on_no_core(component.stop) diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py new file mode 100644 index 000000000..face40986 --- /dev/null +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -0,0 +1,166 @@ +# +# queuedtorrents.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import os.path + +import gtk, gtk.glade +import pkg_resources + +import deluge.ui.component as component +import deluge.ui.client as client +import deluge.common +from deluge.log import LOG as log + +class QueuedTorrents(component.Component): + def __init__(self): + component.Component.__init__(self, "QueuedTorrents", ["StatusBar"]) + self.queue = [] + self.status_item = None + + self.glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/queuedtorrents.glade")) + + self.dialog = self.glade.get_widget("queued_torrents_dialog") + self.dialog.set_icon(deluge.common.get_logo(32)) + + self.glade.signal_autoconnect({ + "on_button_remove_clicked": self.on_button_remove_clicked, + "on_button_clear_clicked": self.on_button_clear_clicked, + "on_button_close_clicked": self.on_button_close_clicked, + "on_button_add_clicked": self.on_button_add_clicked + }) + + self.treeview = self.glade.get_widget("treeview") + self.treeview.append_column( + gtk.TreeViewColumn(_("Torrent"), gtk.CellRendererText(), text=0)) + + self.liststore = gtk.ListStore(str, str) + self.treeview.set_model(self.liststore) + + def run(self): + self.dialog.set_transient_for(component.get("MainWindow").window) + self.dialog.show() + + def start(self): + if len(self.queue) == 0: + return + # Make sure status bar info is showing + self.status_item = None + self.update_status_bar() + # We only want the add button sensitive if we're connected to a host + self.glade.get_widget("button_add").set_sensitive(True) + self.run() + + def stop(self): + # We only want the add button sensitive if we're connected to a host + self.glade.get_widget("button_add").set_sensitive(False) + + def add_to_queue(self, torrents): + """Adds the list of torrents to the queue""" + # Add to the queue while removing duplicates + self.queue = list(set(self.queue + torrents)) + + # Update the liststore + self.liststore.clear() + for torrent in self.queue: + self.liststore.append([os.path.split(torrent)[1], torrent]) + + # Update the status bar + self.update_status_bar() + + def update_status_bar(self): + """Attempts to update status bar""" + # If there are no queued torrents.. remove statusbar widgets and return + if len(self.queue) == 0: + if self.status_item != None: + component.get("StatusBar").remove_item(self.status_item) + self.status_item = None + return False + + try: + statusbar = component.get("StatusBar") + except Exception, e: + # The statusbar hasn't been loaded yet, so we'll add a timer to + # update it later. + gobject.timeout_add(100, self.update_status_bar) + return False + + # Set the label text for statusbar + if len(self.queue) > 1: + label = str(len(self.queue)) + _(" Torrents Queued") + else: + label = str(len(self.queue)) + _(" Torrent Queued") + + # Add the statusbar items if needed, or just modify the label if they + # have already been added. + if self.status_item == None: + self.status_item = component.get("StatusBar").add_item( + stock=gtk.STOCK_SORT_DESCENDING, + text=label, + callback=self.on_statusbar_click) + else: + self.status_item.set_text(label) + + # We return False so the timer stops + return False + + def on_statusbar_click(self, widget, event): + log.debug("on_statusbar_click") + self.run() + + def on_button_remove_clicked(self, widget): + selected = self.treeview.get_selection().get_selected()[1] + if selected != None: + path = self.liststore.get_value(selected, 1) + self.liststore.remove(selected) + self.queue.remove(path) + self.update_status_bar() + + def on_button_clear_clicked(self, widget): + self.liststore.clear() + self.update_status_bar() + + def on_button_close_clicked(self, widget): + self.dialog.hide() + + def on_button_add_clicked(self, widget): + # Add all the torrents in the liststore + def add_torrent(model, path, iter, data): + torrent_path = model.get_value(iter, 1) + client.add_torrent_file([torrent_path]) + + self.liststore.foreach(add_torrent, None) + del self.queue[:] + self.dialog.hide() + self.update_status_bar() diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 3914a3075..f83a9ea3a 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -38,6 +38,57 @@ import deluge.common import deluge.ui.client as client from deluge.log import LOG as log +class StatusBarItem: + def __init__(self, image=None, stock=None, text=None, callback=None): + self._widgets = [] + self._ebox = gtk.EventBox() + self._hbox = gtk.HBox() + self._image = gtk.Image() + self._label = gtk.Label() + self._hbox.add(self._image) + self._hbox.add(self._label) + self._ebox.add(self._hbox) + + # Add image from file or stock + if image != None or stock != None: + if image != None: + self.set_image_from_file(image) + if stock != None: + self.set_image_from_stock(stock) + + # Add text + if text != None: + self.set_text(text) + + if callback != None: + self.set_callback(callback) + + self.show_all() + + def set_callback(self, callback): + self._ebox.connect("button-press-event", callback) + + def show_all(self): + self._ebox.show() + self._hbox.show() + self._image.show() + self._label.show() + + def set_image_from_file(self, image): + self._image.set_from_file(image) + + def set_image_from_stock(self, stock): + self._image.set_from_stock(stock, gtk.ICON_SIZE_MENU) + + def set_text(self, text): + self._label.set_text(text) + + def get_widgets(self): + return self._widgets() + + def get_eventbox(self): + return self._ebox + class StatusBar(component.Component): def __init__(self): component.Component.__init__(self, "StatusBar") @@ -90,37 +141,17 @@ class StatusBar(component.Component): self.hbox.pack_start(label, expand=False, fill=False) self.statusbar.show_all() - def add_item(self, image=None, stock=None, text=None): + def add_item(self, image=None, stock=None, text=None, callback=None): """Adds an item to the status bar""" # The return tuple.. we return whatever widgets we add - ret = [] - # Add image from file or stock - if image != None or stock != None: - _image = gtk.Image() - if image != None: - _image.set_from_file(image) - if stock != None: - _image.set_from_stock(stock, gtk.ICON_SIZE_MENU) - self.hbox.pack_start(_image, expand=False, fill=False) - ret.append(_image) - - # Add text - if text != None: - label = gtk.Label(text) - self.hbox.pack_start(label, expand=False, fill=False) - ret.append(label) - - # Show the widgets - for widget in ret: - widget.show() - - # Return the widgets - return tuple(ret) + item = StatusBarItem(image, stock, text, callback) + self.hbox.pack_start(item.get_eventbox(), expand=False, fill=False) + return item - def remove_item(self, widget): + def remove_item(self, item): """Removes an item from the statusbar""" try: - self.hbox.remove(widget) + self.hbox.remove(item.get_eventbox()) except Exception, e: log.debug("Unable to remove widget: %s", e) From b4b7b154ae22962c7060edbaaed4655266203ff7 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 18 Nov 2007 08:32:15 +0000 Subject: [PATCH 0259/1009] paused/active state on add to todo --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 6e590d209..6dd12fdb6 100644 --- a/TODO +++ b/TODO @@ -24,3 +24,4 @@ own section to this. * Finish queuedtorrents dialog * Update statusbar to use new statusbaritems +* Option for adding torrents in paused/active state From de2e230e4f050989cba6c26e8e1a5efed4cf8d7f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 19 Nov 2007 03:51:33 +0000 Subject: [PATCH 0260/1009] Enable auto add queued torrents. --- deluge/ui/gtkui/glade/queuedtorrents.glade | 3 ++- deluge/ui/gtkui/gtkui.py | 3 ++- deluge/ui/gtkui/queuedtorrents.py | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/glade/queuedtorrents.glade b/deluge/ui/gtkui/glade/queuedtorrents.glade index 2428c89e9..ed59466bb 100644 --- a/deluge/ui/gtkui/glade/queuedtorrents.glade +++ b/deluge/ui/gtkui/glade/queuedtorrents.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -127,6 +127,7 @@ Automatically add torrents on connect 0 True + False diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 5757b80fb..12a6e47bd 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -83,7 +83,8 @@ DEFAULT_PREFS = { "show_connection_manager_on_start": True, "autoconnect": False, "autoconnect_host_uri": None, - "autostart_localhost": False + "autostart_localhost": False, + "autoadd_queued": False } class GtkUI: diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index face40986..94570997e 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -39,6 +39,7 @@ import pkg_resources import deluge.ui.component as component import deluge.ui.client as client import deluge.common +from deluge.configmanager import ConfigManager from deluge.log import LOG as log class QueuedTorrents(component.Component): @@ -47,10 +48,12 @@ class QueuedTorrents(component.Component): self.queue = [] self.status_item = None + self.config = ConfigManager("gtkui.conf") self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/queuedtorrents.glade")) - + self.glade.get_widget("chk_autoadd").set_active( + self.config["autoadd_queued"]) self.dialog = self.glade.get_widget("queued_torrents_dialog") self.dialog.set_icon(deluge.common.get_logo(32)) @@ -58,7 +61,8 @@ class QueuedTorrents(component.Component): "on_button_remove_clicked": self.on_button_remove_clicked, "on_button_clear_clicked": self.on_button_clear_clicked, "on_button_close_clicked": self.on_button_close_clicked, - "on_button_add_clicked": self.on_button_add_clicked + "on_button_add_clicked": self.on_button_add_clicked, + "on_chk_autoadd_toggled": self.on_chk_autoadd_toggled }) self.treeview = self.glade.get_widget("treeview") @@ -75,8 +79,13 @@ class QueuedTorrents(component.Component): def start(self): if len(self.queue) == 0: return + + if self.config["autoadd_queued"]: + self.on_button_add_clicked(None) + return # Make sure status bar info is showing self.status_item = None + self.update_status_bar() # We only want the add button sensitive if we're connected to a host self.glade.get_widget("button_add").set_sensitive(True) @@ -85,6 +94,7 @@ class QueuedTorrents(component.Component): def stop(self): # We only want the add button sensitive if we're connected to a host self.glade.get_widget("button_add").set_sensitive(False) + self.update_status_bar() def add_to_queue(self, torrents): """Adds the list of torrents to the queue""" @@ -164,3 +174,8 @@ class QueuedTorrents(component.Component): del self.queue[:] self.dialog.hide() self.update_status_bar() + + def on_chk_autoadd_toggled(self, widget): + self.config["autoadd_queued"] = widget.get_active() + + From d8680af277449f7baef163f1f6105760bb09b531 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 19 Nov 2007 07:33:43 +0000 Subject: [PATCH 0261/1009] Clean up StatusBar and use new StatusBarItems. --- deluge/ui/gtkui/queuedtorrents.py | 3 +- deluge/ui/gtkui/statusbar.py | 59 ++++++++++++++----------------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index 94570997e..9ea4efe3b 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -84,9 +84,8 @@ class QueuedTorrents(component.Component): self.on_button_add_clicked(None) return # Make sure status bar info is showing - self.status_item = None - self.update_status_bar() + # We only want the add button sensitive if we're connected to a host self.glade.get_widget("button_add").set_sensitive(True) self.run() diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index f83a9ea3a..b0ede5e55 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -43,6 +43,7 @@ class StatusBarItem: self._widgets = [] self._ebox = gtk.EventBox() self._hbox = gtk.HBox() + self._hbox.set_spacing(5) self._image = gtk.Image() self._label = gtk.Label() self._hbox.add(self._image) @@ -101,45 +102,39 @@ class StatusBar(component.Component): frame = self.statusbar.get_children()[0] frame.remove(frame.get_children()[0]) frame.add(self.hbox) + self.statusbar.show_all() # Show the not connected status bar self.show_not_connected() def start(self): log.debug("StatusBar start..") # Add in images and labels - self.clear_statusbar() - image = gtk.Image() - image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) - self.hbox.pack_start(image, expand=False, fill=False) - self.label_connections = gtk.Label() - self.hbox.pack_start(self.label_connections, expand=False, fill=False) - image = gtk.Image() - image.set_from_file(deluge.common.get_pixmap("downloading16.png")) - self.hbox.pack_start(image, expand=False, fill=False) - self.label_download_speed = gtk.Label() - self.hbox.pack_start(self.label_download_speed, - expand=False, fill=False) - image = gtk.Image() - image.set_from_file(deluge.common.get_pixmap("seeding16.png")) - self.hbox.pack_start(image, expand=False, fill=False) - self.label_upload_speed = gtk.Label() - self.hbox.pack_start(self.label_upload_speed, - expand=False, fill=False) - - self.statusbar.show_all() + self.remove_item(self.not_connected_item) + self.connections_item = StatusBarItem( + stock=gtk.STOCK_NETWORK) + self.hbox.pack_start( + self.connections_item.get_eventbox(), expand=False, fill=False) + self.download_item = StatusBarItem( + image=deluge.common.get_pixmap("downloading16.png")) + self.hbox.pack_start( + self.download_item.get_eventbox(), expand=False, fill=False) + self.upload_item = StatusBarItem( + image=deluge.common.get_pixmap("seeding16.png")) + self.hbox.pack_start( + self.upload_item.get_eventbox(), expand=False, fill=False) def stop(self): # When stopped, we just show the not connected thingy + self.remove_item(self.connections_item) + self.remove_item(self.download_item) + self.remove_item(self.upload_item) self.show_not_connected() def show_not_connected(self): - self.clear_statusbar() - image = gtk.Image() - image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - self.hbox.pack_start(image, expand=False, fill=False) - label = gtk.Label(_("Not Connected")) - self.hbox.pack_start(label, expand=False, fill=False) - self.statusbar.show_all() + self.not_connected_item = StatusBarItem( + stock=gtk.STOCK_STOP, text=_("Not Connected")) + self.hbox.pack_start( + self.not_connected_item.get_eventbox(), expand=False, fill=False) def add_item(self, image=None, stock=None, text=None, callback=None): """Adds an item to the status bar""" @@ -165,8 +160,8 @@ class StatusBar(component.Component): max_connections = client.get_config_value("max_connections_global") if max_connections < 0: max_connections = _("Unlimited") - - self.label_connections.set_text("%s (%s)" % ( + + self.connections_item.set_text("%s (%s)" % ( client.get_num_connections(), max_connections)) # Set the download speed label @@ -176,10 +171,10 @@ class StatusBar(component.Component): else: max_download_speed = "%s %s" % (max_download_speed, _("KiB/s")) - self.label_download_speed.set_text("%s/s (%s)" % ( + self.download_item.set_text("%s/s (%s)" % ( deluge.common.fsize(client.get_download_rate()), max_download_speed)) - + # Set the upload speed label max_upload_speed = client.get_config_value("max_upload_speed") if max_upload_speed < 0: @@ -187,7 +182,7 @@ class StatusBar(component.Component): else: max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s")) - self.label_upload_speed.set_text("%s/s (%s)" % ( + self.upload_item.set_text("%s/s (%s)" % ( deluge.common.fsize(client.get_upload_rate()), max_upload_speed)) From 1aec790ad605e2f7ba84089b20dd832efdce2246 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 23 Nov 2007 06:39:11 +0000 Subject: [PATCH 0262/1009] Update Preferences dialog glade file to support new options. --- TODO | 2 - .../ui/gtkui/glade/preferences_dialog.glade | 477 +++++++++++++----- deluge/ui/gtkui/preferences.py | 4 +- setup.py | 4 +- 4 files changed, 343 insertions(+), 144 deletions(-) diff --git a/TODO b/TODO index 6dd12fdb6..1e2a4baed 100644 --- a/TODO +++ b/TODO @@ -22,6 +22,4 @@ * Add a health indication to the statusbar * Add sidebar for labels and other things.. Plugins should be able to add their own section to this. -* Finish queuedtorrents dialog -* Update statusbar to use new statusbaritems * Option for adding torrents in paused/active state diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index c3025ac0e..3fee5dcda 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,11 +1,11 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - Deluge Preferences + Preferences GTK_WIN_POS_CENTER_ON_PARENT 510 520 @@ -102,6 +102,7 @@ True + 2 True @@ -173,6 +174,135 @@ 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Save .torrent files to: + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder + + + False + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Files Location</b> + True + + + label_item + + + + + False + False + 5 + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrent files that are placed in this folder. + Auto Add Folder: + 0 + True + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + False + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Auto Add Folder</b> + True + + + label_item + + + + + False + False + 5 + 4 + + True @@ -241,7 +371,7 @@ False False 5 - 3 + 5 @@ -306,7 +436,7 @@ False False 5 - 4 + 6 @@ -314,14 +444,16 @@ - - False - - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + page 6 + tab + False @@ -653,6 +785,22 @@ 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Local Service Discovery finds local peers on your network. + LSD + 0 + True + + + False + False + 3 + + @@ -815,7 +963,6 @@ Either 1 - False @@ -883,40 +1030,71 @@ Either 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -941,74 +1119,43 @@ Either - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1052,24 +1199,29 @@ Either 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1087,24 +1239,19 @@ Either - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1139,7 +1286,6 @@ Either 2 - False @@ -1171,7 +1317,7 @@ Either GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0.05000000074505806 10 - <i><b><big>Other</big></b></i> + <i><b><big>Interface</big></b></i> True @@ -1356,33 +1502,15 @@ Either 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1412,20 +1540,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + @@ -1449,24 +1595,79 @@ Thunar 3 + + + + + + + 3 + + + + + + tab + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Other</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1491,7 +1692,7 @@ Thunar - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Updates</b> @@ -1506,27 +1707,27 @@ Thunar False False 5 - 4 + 2 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 @@ -1540,7 +1741,7 @@ Thunar - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 @@ -1565,7 +1766,7 @@ Thunar - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>System Information</b> @@ -1580,7 +1781,7 @@ Thunar False False 5 - 5 + 3 @@ -1589,8 +1790,7 @@ Thunar - 3 - False + 5 @@ -1682,8 +1882,7 @@ Thunar - 4 - False + 5 diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 31aea403b..4b1b9b3ec 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -62,8 +62,8 @@ class Preferences(component.Component): self.treeview.append_column(column) # Add the default categories i = 0 - for category in ["Downloads", "Network", "Bandwidth", "Other", - "Plugins"]: + for category in ["Downloads", "Network", "Bandwidth", "Interface", + "Other", "Plugins"]: self.liststore.append([i, category]) i += 1 diff --git a/setup.py b/setup.py index 77a648e53..8fa470385 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,9 @@ _extra_compile_args = [ "-DHAVE_INCLUDE_LIBTORRENT_ASIO_IP_TCP_HPP=1", "-DHAVE_PTHREAD=1", "-DTORRENT_USE_OPENSSL=1", - "-DHAVE_SSL=1" + "-DHAVE_SSL=1", + "-g", + "-p" ] _include_dirs = [ From ae4d85d5d8565c52019c2cc2bda063b0efba5b8e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 23 Nov 2007 08:52:00 +0000 Subject: [PATCH 0263/1009] Add LSD support. Initial import of remove torrent dialog. --- TODO | 1 - deluge/core/core.py | 11 +- .../gtkui/glade/remove_torrent_dialog.glade | 138 ++++++++++++++++++ deluge/ui/gtkui/preferences.py | 10 +- 4 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 deluge/ui/gtkui/glade/remove_torrent_dialog.glade diff --git a/TODO b/TODO index 1e2a4baed..f93910f83 100644 --- a/TODO +++ b/TODO @@ -15,7 +15,6 @@ * Implement 'Classic' mode * Add remove torrent dialog and ability to remove data * Tray tooltip -* Add LSD * Add torrentfiles location config option * Add autoload folder * Add wizard diff --git a/deluge/core/core.py b/deluge/core/core.py index 4c088e8ae..fa96b0bb8 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -65,6 +65,7 @@ DEFAULT_PREFS = { "upnp": False, "natpmp": False, "utpex": False, + "lsd": False, "enc_in_policy": 1, "enc_out_policy": 1, "enc_level": 2, @@ -155,6 +156,7 @@ class Core( self.config.register_set_function("upnp", self._on_set_upnp) self.config.register_set_function("natpmp", self._on_set_natpmp) self.config.register_set_function("utpex", self._on_set_utpex) + self.config.register_set_function("lsd", self._on_set_lsd) self.config.register_set_function("enc_in_policy", self._on_set_encryption) self.config.register_set_function("enc_out_policy", @@ -468,7 +470,14 @@ class Core( self.session.start_natpmp() else: self.session.stop_natpmp() - + + def _on_set_lsd(self, key, value): + log.debug("lsd value set to %s", value) + if value: + self.session.start_lsd() + else: + self.session.stop_lsd() + def _on_set_utpex(self, key, value): log.debug("utpex value set to %s", value) if value: diff --git a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade new file mode 100644 index 000000000..6591dba4d --- /dev/null +++ b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade @@ -0,0 +1,138 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Remove Torrents + False + GTK_WIN_POS_CENTER_ON_PARENT + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-dialog-warning + 6 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <big><b>Remove Torrents?</b></big> + True + + + False + False + 1 + + + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete .torrent files + 0 + True + + + False + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete saved data + 0 + True + + + False + False + 2 + + + + + False + False + 5 + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 0 + + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 4b1b9b3ec..42ad1093f 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -171,6 +171,7 @@ class Preferences(component.Component): "chk_upnp": ("active", self.core_config["upnp"]), "chk_natpmp": ("active", self.core_config["natpmp"]), "chk_utpex": ("active", self.core_config["utpex"]), + "chk_lsd": ("active", self.core_config["lsd"]), "combo_encin": ("active", self.core_config["enc_in_policy"]), "combo_encout": ("active", self.core_config["enc_out_policy"]), "combo_enclevel": ("active", self.core_config["enc_level"]), @@ -223,6 +224,7 @@ class Preferences(component.Component): "chk_upnp", "chk_natpmp", "chk_utpex", + "chk_lsd", "combo_encin", "combo_encout", "combo_enclevel", @@ -323,8 +325,12 @@ class Preferences(component.Component): self.glade.get_widget("chk_random_port").get_active() new_core_config["dht"] = self.glade.get_widget("chk_dht").get_active() new_core_config["upnp"] = self.glade.get_widget("chk_upnp").get_active() - new_core_config["natpmp"] = self.glade.get_widget("chk_natpmp").get_active() - new_core_config["utpex"] = self.glade.get_widget("chk_utpex").get_active() + new_core_config["natpmp"] = \ + self.glade.get_widget("chk_natpmp").get_active() + new_core_config["utpex"] = \ + self.glade.get_widget("chk_utpex").get_active() + new_core_config["lsd"] = \ + self.glade.get_widget("chk_lsd").get_active() new_core_config["enc_in_policy"] = \ self.glade.get_widget("combo_encin").get_active() new_core_config["enc_out_policy"] = \ From 8a833af73899fbab4c19b53a473cce172cba098c Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 23 Nov 2007 23:32:47 +0000 Subject: [PATCH 0264/1009] lt sync 1757 --- libtorrent/include/libtorrent/alert_types.hpp | 29 +++++ .../include/libtorrent/aux_/session_impl.hpp | 9 +- libtorrent/include/libtorrent/debug.hpp | 4 +- .../include/libtorrent/http_connection.hpp | 2 + .../libtorrent/http_tracker_connection.hpp | 3 + libtorrent/include/libtorrent/peer_id.hpp | 1 + libtorrent/include/libtorrent/proxy_base.hpp | 2 + libtorrent/include/libtorrent/session.hpp | 12 +- .../include/libtorrent/session_settings.hpp | 7 ++ libtorrent/include/libtorrent/socket.hpp | 15 +++ libtorrent/include/libtorrent/torrent.hpp | 3 + .../include/libtorrent/torrent_handle.hpp | 26 +++- .../include/libtorrent/tracker_manager.hpp | 4 +- libtorrent/src/broadcast_socket.cpp | 2 +- libtorrent/src/bt_peer_connection.cpp | 13 +- libtorrent/src/entry.cpp | 1 + libtorrent/src/file.cpp | 11 +- libtorrent/src/http_connection.cpp | 66 +++++----- libtorrent/src/http_tracker_connection.cpp | 34 ++++-- libtorrent/src/identify_client.cpp | 1 + libtorrent/src/kademlia/dht_tracker.cpp | 1 + libtorrent/src/kademlia/node_id.cpp | 2 +- libtorrent/src/lsd.cpp | 2 + libtorrent/src/session.cpp | 24 +++- libtorrent/src/session_impl.cpp | 34 +++++- libtorrent/src/storage.cpp | 16 ++- libtorrent/src/torrent.cpp | 113 ++++++++++++++---- libtorrent/src/torrent_handle.cpp | 14 +++ libtorrent/src/tracker_manager.cpp | 2 +- libtorrent/src/udp_tracker_connection.cpp | 7 +- 30 files changed, 353 insertions(+), 107 deletions(-) diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index 6f41758fa..a1dcf4364 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -82,6 +82,35 @@ namespace libtorrent { return std::auto_ptr(new tracker_warning_alert(*this)); } }; + struct TORRENT_EXPORT scrape_reply_alert: torrent_alert + { + scrape_reply_alert(torrent_handle const& h + , int incomplete_ + , int complete_ + , std::string const& msg) + : torrent_alert(h, alert::info, msg) + , incomplete(incomplete_) + , complete(complete_) + {} + + int incomplete; + int complete; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new scrape_reply_alert(*this)); } + }; + + struct TORRENT_EXPORT scrape_failed_alert: torrent_alert + { + scrape_failed_alert(torrent_handle const& h + , std::string const& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new scrape_failed_alert(*this)); } + }; + struct TORRENT_EXPORT tracker_reply_alert: torrent_alert { tracker_reply_alert(torrent_handle const& h diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index df39fabb0..95089b649 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -184,7 +184,11 @@ namespace libtorrent session_impl( std::pair listen_port_range , fingerprint const& cl_fprint - , char const* listen_interface = "0.0.0.0"); + , char const* listen_interface +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , fs::path const& logpath +#endif + ); ~session_impl(); #ifndef TORRENT_DISABLE_EXTENSIONS @@ -469,7 +473,7 @@ namespace libtorrent // we might need more than one listen socket std::list m_listen_sockets; - listen_socket_t setup_listener(tcp::endpoint ep, int retries); + listen_socket_t setup_listener(tcp::endpoint ep, int retries, bool v6_only = false); // the settings for the client session_settings m_settings; @@ -575,6 +579,7 @@ namespace libtorrent // whe shutting down process std::list > m_tracker_loggers; + fs::path m_logpath; public: boost::shared_ptr m_logger; private: diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp index 0d3158417..96c2c9dc7 100755 --- a/libtorrent/include/libtorrent/debug.hpp +++ b/libtorrent/include/libtorrent/debug.hpp @@ -58,11 +58,11 @@ namespace libtorrent struct logger { - logger(fs::path const& filename, int instance, bool append = true) + logger(fs::path const& logpath, fs::path const& filename, int instance, bool append = true) { try { - fs::path dir(fs::complete("libtorrent_logs" + boost::lexical_cast(instance))); + fs::path dir(fs::complete(logpath / ("libtorrent_logs" + boost::lexical_cast(instance)))); if (!fs::exists(dir)) fs::create_directories(dir); m_file.open((dir / filename).string().c_str(), std::ios_base::out | (append ? std::ios_base::app : std::ios_base::out)); *this << "\n\n\n*** starting log ***\n"; diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index 4a70a902c..9d32af2e9 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -115,6 +115,8 @@ private: , asio::error_code const& e); void on_assign_bandwidth(asio::error_code const& e); + void callback(asio::error_code const& e, char const* data = 0, int size = 0); + std::vector m_recvbuffer; tcp::socket m_sock; int m_read_pos; diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index c0057dfa1..41df4c953 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -91,6 +91,9 @@ namespace libtorrent int content_length() const { return m_content_length; } void reset(); + + std::map const& headers() const { return m_header; } + private: int m_recv_pos; int m_status_code; diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp index 13d857f99..a546c6e08 100755 --- a/libtorrent/include/libtorrent/peer_id.hpp +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" diff --git a/libtorrent/include/libtorrent/proxy_base.hpp b/libtorrent/include/libtorrent/proxy_base.hpp index f2a955958..037a1c2d4 100644 --- a/libtorrent/include/libtorrent/proxy_base.hpp +++ b/libtorrent/include/libtorrent/proxy_base.hpp @@ -123,11 +123,13 @@ public: { m_remote_endpoint = endpoint_type(); m_sock.close(); + m_resolver.cancel(); } void close(asio::error_code& ec) { m_sock.close(ec); + m_resolver.cancel(); } endpoint_type remote_endpoint() diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 5093e2336..1d29e03b3 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -121,11 +121,19 @@ namespace libtorrent public: session(fingerprint const& print = fingerprint("LT" - , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0)); + , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , fs::path logpath = "." +#endif + ); session( fingerprint const& print , std::pair listen_port_range - , char const* listen_interface = "0.0.0.0"); + , char const* listen_interface = "0.0.0.0" +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , fs::path logpath = "." +#endif + ); ~session(); diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index 7b08ec11e..f1f9d190c 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -115,6 +115,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT , use_dht_as_fallback(true) #endif + , free_torrent_hashes(true) {} // this is the user agent that will be sent to the tracker @@ -281,6 +282,12 @@ namespace libtorrent // tracker is online bool use_dht_as_fallback; #endif + + // if this is true, the piece hashes will be freed, in order + // to save memory, once the torrent is seeding. This will + // make the get_torrent_info() function to return an incomplete + // torrent object that cannot be passed back to add_torrent() + bool free_torrent_hashes; }; #ifndef TORRENT_DISABLE_DHT diff --git a/libtorrent/include/libtorrent/socket.hpp b/libtorrent/include/libtorrent/socket.hpp index 514be256c..499842dd7 100755 --- a/libtorrent/include/libtorrent/socket.hpp +++ b/libtorrent/include/libtorrent/socket.hpp @@ -171,6 +171,21 @@ namespace libtorrent return Endpoint(addr, port); } } + + struct v6only + { + v6only(bool enable): m_value(enable) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_V6ONLY; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + } #endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 5ee5ddb03..7aa779081 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -309,6 +309,8 @@ namespace libtorrent virtual void tracker_request_error(tracker_request const& r , int response_code, const std::string& str); virtual void tracker_warning(std::string const& msg); + virtual void tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded); // generates a request string for sending // to the tracker @@ -332,6 +334,7 @@ namespace libtorrent // forcefully sets next_announce to the current time void force_tracker_request(); void force_tracker_request(ptime); + void scrape_tracker(); // sets the username and password that will be sent to // the tracker diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 7ddb218a6..48a17e2ec 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -94,15 +94,17 @@ namespace libtorrent , upload_rate(0) , download_payload_rate(0) , upload_payload_rate(0) + , num_seeds(0) , num_peers(0) , num_complete(-1) , num_incomplete(-1) + , list_seeds(0) + , list_peers(0) , pieces(0) , num_pieces(0) , total_done(0) , total_wanted_done(0) , total_wanted(0) - , num_seeds(0) , distributed_copies(0.f) , block_size(0) , num_uploads(0) @@ -159,8 +161,12 @@ namespace libtorrent float download_payload_rate; float upload_payload_rate; + // the number of peers this torrent is connected to + // that are seeding. + int num_seeds; + // the number of peers this torrent - // is connected to. + // is connected to (including seeds). int num_peers; // if the tracker sends scrape info in its @@ -171,6 +177,15 @@ namespace libtorrent int num_complete; int num_incomplete; + // this is the number of seeds whose IP we know + // but are not necessarily connected to + int list_seeds; + + // this is the number of peers whose IP we know + // (including seeds), but are not necessarily + // connected to + int list_peers; + const std::vector* pieces; // this is the number of pieces the client has @@ -193,10 +208,6 @@ namespace libtorrent // in case any pieces are filtered as not wanted size_type total_wanted; - // the number of peers this torrent is connected to - // that are seeding. - int num_seeds; - // the number of distributed copies of the file. // note that one copy may be spread out among many peers. // @@ -345,6 +356,9 @@ namespace libtorrent // timed out. void force_reannounce(boost::posix_time::time_duration) const; + // performs a scrape request + void scrape_tracker() const; + // returns the name of this torrent, in case it doesn't // have metadata it returns the name assigned to it // when it was added. diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index fdc3f6bbf..8fec9563c 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -122,6 +122,8 @@ namespace libtorrent request_callback(): m_manager(0) {} virtual ~request_callback() {} virtual void tracker_warning(std::string const& msg) = 0; + virtual void tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloads) {} virtual void tracker_response( tracker_request const& , std::vector& peers @@ -191,7 +193,7 @@ namespace libtorrent : timeout_handler { tracker_connection(tracker_manager& man - , tracker_request req + , tracker_request const& req , asio::strand& str , address bind_interface , boost::weak_ptr r); diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index a9d27eff4..e03ad2274 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -182,7 +182,7 @@ namespace libtorrent void broadcast_socket::on_receive(socket_entry* s, asio::error_code const& ec , std::size_t bytes_transferred) { - if (ec || bytes_transferred == 0) return; + if (ec || bytes_transferred == 0 || !m_on_receive) return; m_on_receive(s->remote, s->buffer, bytes_transferred); s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer)) , s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2)); diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index d7b3226ec..384bc2375 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -1272,7 +1272,13 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); + // Don't require the bitfield to have been sent at this point + // the case where m_sent_bitfield may not be true is if the + // torrent doesn't have any metadata, and a peer is timimg out. + // then the keep-alive message will be sent before the bitfield + // this is a violation to the original protocol, but necessary + // for the metadata extension. + TORRENT_ASSERT(m_sent_handshake); char msg[] = {0,0,0,0}; send_buffer(msg, sizeof(msg)); @@ -2477,6 +2483,11 @@ namespace libtorrent TORRENT_ASSERT(!m_rc4_encrypted || m_RC4_handler.get()); #endif + if (!in_handshake()) + { + TORRENT_ASSERT(m_sent_handshake); + } + if (!m_in_constructor) peer_connection::check_invariant(); diff --git a/libtorrent/src/entry.cpp b/libtorrent/src/entry.cpp index 88800713c..8219ecc06 100755 --- a/libtorrent/src/entry.cpp +++ b/libtorrent/src/entry.cpp @@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include +#include #include #include "libtorrent/entry.hpp" #include "libtorrent/config.hpp" diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 67024cf81..5e01e1ae5 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -68,6 +68,7 @@ BOOST_STATIC_ASSERT(sizeof(lseek(0, 0, 0)) >= 8); #include #include "libtorrent/file.hpp" #include +#include #ifndef O_BINARY #define O_BINARY 0 @@ -184,7 +185,7 @@ namespace libtorrent { std::stringstream msg; msg << "open failed: '" << path.native_file_string() << "'. " - << strerror(errno); + << std::strerror(errno); throw file_error(msg.str()); } m_open_mode = mode; @@ -216,7 +217,7 @@ namespace libtorrent if (ret == -1) { std::stringstream msg; - msg << "read failed: " << strerror(errno); + msg << "read failed: " << std::strerror(errno); throw file_error(msg.str()); } return ret; @@ -240,7 +241,7 @@ namespace libtorrent if (ret == -1) { std::stringstream msg; - msg << "write failed: " << strerror(errno); + msg << "write failed: " << std::strerror(errno); throw file_error(msg.str()); } return ret; @@ -254,7 +255,7 @@ namespace libtorrent if (ftruncate(m_fd, s) < 0) { std::stringstream msg; - msg << "ftruncate failed: '" << strerror(errno); + msg << "ftruncate failed: '" << std::strerror(errno); throw file_error(msg.str()); } #endif @@ -278,7 +279,7 @@ namespace libtorrent if (ret == -1) { std::stringstream msg; - msg << "seek failed: '" << strerror(errno) + msg << "seek failed: '" << std::strerror(errno) << "' fd: " << m_fd << " offset: " << offset << " seekdir: " << seekdir; diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 08be387cc..a0d8e3fa3 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -96,9 +96,7 @@ void http_connection::on_connect_timeout() if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); m_connection_ticket = -1; - if (m_bottled && m_called) return; - m_called = true; - m_handler(asio::error::timed_out, m_parser, 0, 0); + callback(asio::error::timed_out); close(); } @@ -112,15 +110,15 @@ void http_connection::on_timeout(boost::weak_ptr p if (e == asio::error::operation_aborted) return; - if (c->m_bottled && c->m_called) return; - if (c->m_last_receive + c->m_timeout < time_now()) { - c->m_called = true; - c->m_handler(asio::error::timed_out, c->m_parser, 0, 0); + c->callback(asio::error::timed_out); + c->close(); return; } + if (!c->m_sock.is_open()) return; + c->m_timer.expires_at(c->m_last_receive + c->m_timeout); c->m_timer.async_wait(bind(&http_connection::on_timeout, p, _1)); } @@ -135,6 +133,8 @@ void http_connection::close() if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); m_connection_ticket = -1; + + m_handler.clear(); } void http_connection::on_resolve(asio::error_code const& e @@ -142,10 +142,8 @@ void http_connection::on_resolve(asio::error_code const& e { if (e) { + callback(e); close(); - if (m_bottled && m_called) return; - m_called = true; - m_handler(e, m_parser, 0, 0); return; } TORRENT_ASSERT(i != tcp::resolver::iterator()); @@ -181,10 +179,17 @@ void http_connection::on_connect(asio::error_code const& e } */ else { + callback(e); close(); - if (m_bottled && m_called) return; + } +} + +void http_connection::callback(asio::error_code const& e, char const* data, int size) +{ + if (!m_bottled || !m_called) + { m_called = true; - m_handler(e, m_parser, 0, 0); + if (m_handler) m_handler(e, m_parser, data, size); } } @@ -192,10 +197,8 @@ void http_connection::on_write(asio::error_code const& e) { if (e) { + callback(e); close(); - if (m_bottled && m_called) return; - m_called = true; - m_handler(e, m_parser, 0, 0); return; } @@ -230,9 +233,6 @@ void http_connection::on_read(asio::error_code const& e if (e == asio::error::eof) { - close(); - if (m_bottled && m_called) return; - m_called = true; char const* data = 0; std::size_t size = 0; if (m_bottled && m_parser.header_finished()) @@ -240,16 +240,15 @@ void http_connection::on_read(asio::error_code const& e data = m_parser.get_body().begin; size = m_parser.get_body().left(); } - m_handler(e, m_parser, data, size); + callback(e, data, size); + close(); return; } if (e) { + callback(e); close(); - if (m_bottled && m_called) return; - m_called = true; - m_handler(e, m_parser, 0, 0); return; } @@ -267,9 +266,7 @@ void http_connection::on_read(asio::error_code const& e if (url.empty()) { // missing location header - if (m_bottled && m_called) return; - m_called = true; - m_handler(e, m_parser, 0, 0); + callback(e); return; } @@ -291,7 +288,7 @@ void http_connection::on_read(asio::error_code const& e if (!m_bottled && m_parser.header_finished()) { if (m_read_pos > m_parser.body_start()) - m_handler(e, m_parser, &m_recvbuffer[0] + m_parser.body_start() + callback(e, &m_recvbuffer[0] + m_parser.body_start() , m_read_pos - m_parser.body_start()); m_read_pos = 0; m_last_receive = time_now(); @@ -299,15 +296,13 @@ void http_connection::on_read(asio::error_code const& e else if (m_bottled && m_parser.finished()) { m_timer.cancel(); - if (m_bottled && m_called) return; - m_called = true; - m_handler(e, m_parser, m_parser.get_body().begin, m_parser.get_body().left()); + callback(e, m_parser.get_body().begin, m_parser.get_body().left()); } } else { TORRENT_ASSERT(!m_bottled); - m_handler(e, m_parser, &m_recvbuffer[0], m_read_pos); + callback(e, &m_recvbuffer[0], m_read_pos); m_read_pos = 0; m_last_receive = time_now(); } @@ -316,10 +311,8 @@ void http_connection::on_read(asio::error_code const& e m_recvbuffer.resize((std::min)(m_read_pos + 2048, int(max_bottled_buffer))); if (m_read_pos == max_bottled_buffer) { + callback(asio::error::eof); close(); - if (m_bottled && m_called) return; - m_called = true; - m_handler(asio::error::eof, m_parser, 0, 0); return; } int amount_to_read = m_recvbuffer.size() - m_read_pos; @@ -345,8 +338,7 @@ void http_connection::on_assign_bandwidth(asio::error_code const& e) && m_limiter_timer_active) || !m_sock.is_open()) { - if (!m_bottled || !m_called) - m_handler(e, m_parser, 0, 0); + callback(asio::error::eof); return; } m_limiter_timer_active = false; @@ -360,6 +352,8 @@ void http_connection::on_assign_bandwidth(asio::error_code const& e) if (amount_to_read > m_download_quota) amount_to_read = m_download_quota; + if (!m_sock.is_open()) return; + m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos , amount_to_read) , bind(&http_connection::on_read @@ -373,6 +367,8 @@ void http_connection::on_assign_bandwidth(asio::error_code const& e) void http_connection::rate_limit(int limit) { + if (!m_sock.is_open()) return; + if (!m_limiter_timer_active) { m_limiter_timer_active = true; diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index ed9823b83..86d21e494 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -797,7 +797,7 @@ namespace libtorrent return; } m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_parser.body_start()); - if (inflate_gzip(m_buffer, tracker_request(), cb.get(), + if (inflate_gzip(m_buffer, tracker_req(), cb.get(), m_settings.tracker_maximum_response_length)) { close(); @@ -897,21 +897,35 @@ namespace libtorrent } catch(type_error const&) {} - std::vector peer_list; - if (tracker_req().kind == tracker_request::scrape_request) { std::string ih; std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end() , std::back_inserter(ih)); entry scrape_data = e["files"][ih]; - int complete = scrape_data["complete"].integer(); - int incomplete = scrape_data["incomplete"].integer(); - cb->tracker_response(tracker_request(), peer_list, 0, complete - , incomplete); + + int complete = -1; + int incomplete = -1; + int downloaded = -1; + + entry const* complete_ent = scrape_data.find_key("complete"); + if (complete_ent && complete_ent->type() == entry::int_t) + complete = complete_ent->integer(); + + entry const* incomplete_ent = scrape_data.find_key("incomplete"); + if (incomplete_ent && incomplete_ent->type() == entry::int_t) + incomplete = incomplete_ent->integer(); + + entry const* downloaded_ent = scrape_data.find_key("downloaded"); + if (downloaded_ent && downloaded_ent->type() == entry::int_t) + downloaded = downloaded_ent->integer(); + + cb->tracker_scrape_response(tracker_req(), complete + , incomplete, downloaded); return; } + std::vector peer_list; int interval = (int)e["interval"].integer(); if (e["peers"].type() == entry::string_t) @@ -965,16 +979,16 @@ namespace libtorrent try { incomplete = e["incomplete"].integer(); } catch(type_error&) {} - cb->tracker_response(tracker_request(), peer_list, interval, complete + cb->tracker_response(tracker_req(), peer_list, interval, complete , incomplete); } catch(type_error& e) { - cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + cb->tracker_request_error(tracker_req(), m_parser.status_code(), e.what()); } catch(std::runtime_error& e) { - cb->tracker_request_error(tracker_request(), m_parser.status_code(), e.what()); + cb->tracker_request_error(tracker_req(), m_parser.status_code(), e.what()); } } diff --git a/libtorrent/src/identify_client.cpp b/libtorrent/src/identify_client.cpp index 4888f4a95..73221de66 100755 --- a/libtorrent/src/identify_client.cpp +++ b/libtorrent/src/identify_client.cpp @@ -174,6 +174,7 @@ namespace , {"ML", "MLDonkey"} , {"MO", "Mono Torrent"} , {"MP", "MooPolice"} + , {"MR", "Miro"} , {"MT", "Moonlight Torrent"} , {"O", "Osprey Permaseed"} , {"PD", "Pando"} diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index b2981f7cd..f32db06d4 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -224,6 +224,7 @@ namespace libtorrent { namespace dht m_connection_timer.cancel(); m_refresh_timer.cancel(); m_socket.close(); + m_host_resolver.cancel(); } void dht_tracker::dht_status(session_status& s) diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp index 52a5c766a..99f3df219 100644 --- a/libtorrent/src/kademlia/node_id.cpp +++ b/libtorrent/src/kademlia/node_id.cpp @@ -87,7 +87,7 @@ int distance_exp(node_id const& n1, node_id const& n2) // return the bit-number of the first bit // that differs int bit = byte * 8; - for (int b = 7; b > 0; --b) + for (int b = 7; b >= 0; --b) if (t >= (1 << b)) return bit + b; return bit; } diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index 6e1dcb7b3..06e570f3c 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -185,5 +185,7 @@ void lsd::close() { m_socket.close(); m_broadcast_timer.cancel(); + m_disabled = true; + m_callback.clear(); } diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 5bdb6b07e..0b8aecff7 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -106,8 +106,16 @@ namespace libtorrent session::session( fingerprint const& id , std::pair listen_port_range - , char const* listen_interface) - : m_impl(new session_impl(listen_port_range, id, listen_interface)) + , char const* listen_interface +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , fs::path logpath +#endif + ) + : m_impl(new session_impl(listen_port_range, id, listen_interface +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , logpath +#endif + )) { // turn off the filename checking in boost.filesystem TORRENT_ASSERT(listen_port_range.first > 0); @@ -121,8 +129,16 @@ namespace libtorrent #endif } - session::session(fingerprint const& id) - : m_impl(new session_impl(std::make_pair(0, 0), id)) + session::session(fingerprint const& id +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , fs::path logpath +#endif + ) +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + : m_impl(new session_impl(std::make_pair(0, 0), id, "0.0.0.0", logpath)) +#else + : m_impl(new session_impl(std::make_pair(0, 0), id, "0.0.0.0")) +#endif { #ifndef NDEBUG boost::function0 test = boost::ref(*m_impl); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index b3eba0bf7..69f2c1bc1 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -545,7 +545,11 @@ namespace detail session_impl::session_impl( std::pair listen_port_range , fingerprint const& cl_fprint - , char const* listen_interface) + , char const* listen_interface +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , fs::path const& logpath +#endif + ) : m_send_buffers(send_buffer_size) , m_files(40) , m_strand(m_io_service) @@ -570,6 +574,9 @@ namespace detail #endif , m_timer(m_io_service) , m_next_connect_torrent(0) +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + , m_logpath(logpath) +#endif , m_checker_impl(*this) { #ifdef WIN32 @@ -807,13 +814,15 @@ namespace detail return m_ipv6_interface; } - session_impl::listen_socket_t session_impl::setup_listener(tcp::endpoint ep, int retries) + session_impl::listen_socket_t session_impl::setup_listener(tcp::endpoint ep + , int retries, bool v6_only) { asio::error_code ec; listen_socket_t s; s.sock.reset(new socket_acceptor(m_io_service)); s.sock->open(ep.protocol(), ec); s.sock->set_option(socket_acceptor::reuse_address(true), ec); + if (ep.protocol() == tcp::v6()) s.sock->set_option(v6only(v6_only), ec); s.sock->bind(ep, ec); while (ec && retries > 0) { @@ -906,7 +915,7 @@ namespace detail s = setup_listener( tcp::endpoint(address_v6::any(), m_listen_interface.port()) - , m_listen_port_retries); + , m_listen_port_retries, true); if (s.sock) { @@ -1021,7 +1030,6 @@ namespace detail async_accept(listener); // we got a connection request! - m_incoming_connection = true; tcp::endpoint endp = s->remote_endpoint(ec); if (ec) @@ -1036,6 +1044,14 @@ namespace detail #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << endp << " <== INCOMING CONNECTION\n"; #endif + + // local addresses do not count, since it's likely + // coming from our own client through local service discovery + // and it does not reflect whether or not a router is open + // for incoming connections or not. + if (!is_local(endp.address())) + m_incoming_connection = true; + if (m_ip_filter.access(endp.address()) & ip_filter::blocked) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1602,7 +1618,7 @@ namespace detail , int instance, bool append) { // current options are file_logger, cout_logger and null_logger - return boost::shared_ptr(new logger(name + ".log", instance, append)); + return boost::shared_ptr(new logger(m_logpath, name + ".log", instance, append)); } #endif @@ -2265,6 +2281,8 @@ namespace detail INVARIANT_CHECK; + if (m_lsd) return; + m_lsd = new lsd(m_io_service , m_listen_interface.address() , bind(&session_impl::on_lsd_peer, this, _1, _2)); @@ -2276,6 +2294,8 @@ namespace detail INVARIANT_CHECK; + if (m_natpmp) return; + m_natpmp = new natpmp(m_io_service , m_listen_interface.address() , bind(&session_impl::on_port_mapping @@ -2294,6 +2314,8 @@ namespace detail INVARIANT_CHECK; + if (m_upnp) return; + m_upnp = new upnp(m_io_service, m_half_open , m_listen_interface.address() , m_settings.user_agent @@ -2310,6 +2332,8 @@ namespace detail void session_impl::stop_lsd() { mutex_t::scoped_lock l(m_mutex); + if (m_lsd.get()) + m_lsd->close(); m_lsd = 0; } diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 0468684f3..cf0781e6a 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -478,6 +478,8 @@ namespace libtorrent m_files.release(this); buffer().swap(m_scratch_buffer); + std::string error; + // delete the files from disk std::set directories; typedef std::set::iterator iter_t; @@ -493,13 +495,21 @@ namespace libtorrent std::pair ret = directories.insert((m_save_path / bp).string()); bp = bp.branch_path(); } - std::remove(p.c_str()); + if (std::remove(p.c_str()) != 0 && errno != ENOENT) + error = std::strerror(errno); } // remove the directories. Reverse order to delete // subdirectories first - std::for_each(directories.rbegin(), directories.rend() - , bind((int(*)(char const*))&std::remove, bind(&std::string::c_str, _1))); + + for (std::set::reverse_iterator i = directories.rbegin() + , end(directories.rend()); i != end; ++i) + { + if (std::remove(i->c_str()) != 0 && errno != ENOENT) + error = std::strerror(errno); + } + + if (!error.empty()) throw std::runtime_error(error); } void storage::write_resume_data(entry& rd) const diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 84f30aa2d..33a73d1f3 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -278,6 +278,7 @@ namespace libtorrent { boost::weak_ptr self(shared_from_this()); if (m_torrent_file->is_valid()) init(); + if (m_abort) return; m_announce_timer.expires_from_now(seconds(1)); m_announce_timer.async_wait(m_ses.m_strand.wrap( bind(&torrent::on_announce_disp, self, _1))); @@ -421,6 +422,8 @@ namespace libtorrent try #endif { + if (m_abort) return; + boost::weak_ptr self(shared_from_this()); if (!m_torrent_file->priv()) @@ -489,13 +492,28 @@ namespace libtorrent #endif + void torrent::scrape_tracker() + { + if (m_trackers.empty()) return; + + TORRENT_ASSERT(m_currently_trying_tracker >= 0); + TORRENT_ASSERT(m_currently_trying_tracker < int(m_trackers.size())); + + tracker_request req; + req.info_hash = m_torrent_file->info_hash(); + req.kind = tracker_request::scrape_request; + req.url = m_trackers[m_currently_trying_tracker].url; + m_ses.m_tracker_manager.queue_request(m_ses.m_strand, m_ses.m_half_open, req + , tracker_login(), m_ses.m_listen_interface.address(), shared_from_this()); + } + // returns true if it is time for this torrent to make another // tracker request bool torrent::should_request() { INVARIANT_CHECK; - if (m_torrent_file->trackers().empty()) return false; + if (m_trackers.empty()) return false; if (m_just_paused) { @@ -517,8 +535,28 @@ namespace libtorrent } } + void torrent::tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + TORRENT_ASSERT(req.kind == tracker_request::scrape_request); + + if (complete >= 0) m_complete = complete; + if (incomplete >= 0) m_incomplete = incomplete; + + if (m_ses.m_alerts.should_post(alert::info)) + { + std::stringstream s; + s << "Got scrape response from tracker: " << req.url; + m_ses.m_alerts.post_alert(scrape_reply_alert( + get_handle(), m_incomplete, m_complete, s.str())); + } + } + void torrent::tracker_response( - tracker_request const& + tracker_request const& r , std::vector& peer_list , int interval , int complete @@ -527,6 +565,7 @@ namespace libtorrent session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; + TORRENT_ASSERT(r.kind == tracker_request::announce_request); m_failed_trackers = 0; // announce intervals less than 5 minutes @@ -603,8 +642,7 @@ namespace libtorrent if (m_ses.m_alerts.should_post(alert::info)) { std::stringstream s; - s << "Got response from tracker: " - << m_trackers[m_last_working_tracker].url; + s << "Got response from tracker: " << r.url; m_ses.m_alerts.post_alert(tracker_reply_alert( get_handle(), peer_list.size(), s.str())); } @@ -1060,6 +1098,7 @@ namespace libtorrent m_owning_storage = 0; m_announce_timer.cancel(); + m_host_resolver.cancel(); } void torrent::on_files_deleted(int ret, disk_io_job const& j) @@ -1068,7 +1107,14 @@ namespace libtorrent if (alerts().should_post(alert::warning)) { - alerts().post_alert(torrent_deleted_alert(get_handle(), "files deleted")); + if (ret != 0) + { + alerts().post_alert(torrent_deleted_alert(get_handle(), "delete files failed: " + j.str)); + } + else + { + alerts().post_alert(torrent_deleted_alert(get_handle(), "files deleted")); + } } } @@ -1142,7 +1188,8 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file->seed_free(); + if (m_ses.settings().free_torrent_hashes) + m_torrent_file->seed_free(); } } @@ -1402,7 +1449,6 @@ namespace libtorrent void torrent::replace_trackers(std::vector const& urls) { - TORRENT_ASSERT(!urls.empty()); m_trackers = urls; if (m_currently_trying_tracker >= (int)m_trackers.size()) m_currently_trying_tracker = (int)m_trackers.size()-1; @@ -2206,6 +2252,7 @@ namespace libtorrent // only start the announce if we want to announce with the dht if (should_announce_dht()) { + if (m_abort) return; // force the DHT to reannounce m_last_dht_announce = time_now() - minutes(15); boost::weak_ptr self(shared_from_this()); @@ -2336,7 +2383,8 @@ namespace libtorrent if (is_seed()) { m_picker.reset(); - m_torrent_file->seed_free(); + if (m_ses.settings().free_torrent_hashes) + m_torrent_file->seed_free(); } if (!m_connections_initialized) @@ -2847,8 +2895,12 @@ namespace libtorrent torrent_status st; - st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end(), - !boost::bind(&peer_connection::is_connecting, _1)); + st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end() + , !boost::bind(&peer_connection::is_connecting, _1)); + + st.list_peers = std::distance(m_policy.begin_peer(), m_policy.end_peer()); + st.list_seeds = (int)std::count_if(m_policy.begin_peer(), m_policy.end_peer() + , boost::bind(&policy::peer::seed, bind(&policy::iterator::value_type::second, _1))); st.storage_mode = m_storage_mode; @@ -2979,7 +3031,7 @@ namespace libtorrent } void torrent::tracker_request_timed_out( - tracker_request const&) + tracker_request const& r) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -2992,19 +3044,26 @@ namespace libtorrent if (m_ses.m_alerts.should_post(alert::warning)) { std::stringstream s; - s << "tracker: \"" - << m_trackers[m_currently_trying_tracker].url - << "\" timed out"; - m_ses.m_alerts.post_alert(tracker_alert(get_handle() - , m_failed_trackers + 1, 0, s.str())); + s << "tracker: \"" << r.url << "\" timed out"; + if (r.kind == tracker_request::announce_request) + { + m_ses.m_alerts.post_alert(tracker_alert(get_handle() + , m_failed_trackers + 1, 0, s.str())); + } + else if (r.kind == tracker_request::scrape_request) + { + m_ses.m_alerts.post_alert(scrape_failed_alert(get_handle(), s.str())); + } } - try_next_tracker(); + + if (r.kind == tracker_request::announce_request) + try_next_tracker(); } // TODO: with some response codes, we should just consider // the tracker as a failure and not retry // it anymore - void torrent::tracker_request_error(tracker_request const& + void torrent::tracker_request_error(tracker_request const& r , int response_code, const std::string& str) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -3017,14 +3076,20 @@ namespace libtorrent if (m_ses.m_alerts.should_post(alert::warning)) { std::stringstream s; - s << "tracker: \"" - << m_trackers[m_currently_trying_tracker].url - << "\" " << str; - m_ses.m_alerts.post_alert(tracker_alert(get_handle() - , m_failed_trackers + 1, response_code, s.str())); + s << "tracker: \"" << r.url << "\" " << str; + if (r.kind == tracker_request::announce_request) + { + m_ses.m_alerts.post_alert(tracker_alert(get_handle() + , m_failed_trackers + 1, response_code, s.str())); + } + else if (r.kind == tracker_request::scrape_request) + { + m_ses.m_alerts.post_alert(scrape_failed_alert(get_handle(), s.str())); + } } - try_next_tracker(); + if (r.kind == tracker_request::announce_request) + try_next_tracker(); } diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index b19e05bb4..aa517ac76 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -863,6 +863,20 @@ namespace libtorrent t->force_tracker_request(); } + void torrent_handle::scrape_tracker() const + { + INVARIANT_CHECK; + + if (m_ses == 0) throw_invalid_handle(); + TORRENT_ASSERT(m_chk); + + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) throw_invalid_handle(); + + t->scrape_tracker(); + } + void torrent_handle::set_ratio(float ratio) const { INVARIANT_CHECK; diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index 82c5cc948..19da33d4b 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -359,7 +359,7 @@ namespace libtorrent tracker_connection::tracker_connection( tracker_manager& man - , tracker_request req + , tracker_request const& req , asio::strand& str , address bind_interface_ , boost::weak_ptr r) diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index 6d76988d3..eb138e67a 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -548,7 +548,7 @@ namespace libtorrent } int complete = detail::read_int32(buf); - /*int downloaded = */detail::read_int32(buf); + int downloaded = detail::read_int32(buf); int incomplete = detail::read_int32(buf); boost::shared_ptr cb = requester(); @@ -559,9 +559,8 @@ namespace libtorrent return; } - std::vector peer_list; - cb->tracker_response(tracker_req(), peer_list, 0 - , complete, incomplete); + cb->tracker_scrape_response(tracker_req() + , complete, incomplete, downloaded); m_man.remove_request(this); close(); From ffa20660469b043b1b35563d1563bf810315ac45 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 23 Nov 2007 23:36:34 +0000 Subject: [PATCH 0265/1009] asio sync --- libtorrent/include/asio.hpp | 74 -- libtorrent/include/asio/COPYING | 4 + libtorrent/include/asio/LICENSE_1_0.txt | 23 + .../include/asio/basic_datagram_socket.hpp | 4 +- libtorrent/include/asio/basic_io_object.hpp | 18 +- libtorrent/include/asio/basic_resolver.hpp | 252 +++++++ libtorrent/include/asio/basic_socket.hpp | 3 +- libtorrent/include/asio/buffer.hpp | 7 +- .../include/asio/buffered_read_stream.hpp | 17 +- libtorrent/include/asio/buffered_stream.hpp | 11 +- .../include/asio/buffered_write_stream.hpp | 17 +- .../include/asio/datagram_socket_service.hpp | 3 + .../include/asio/deadline_timer_service.hpp | 6 +- .../asio/detail/deadline_timer_service.hpp | 2 +- .../include/asio/detail/dev_poll_reactor.hpp | 647 ++++++++++++++++++ .../asio/detail/dev_poll_reactor_fwd.hpp | 40 ++ .../include/asio/detail/epoll_reactor.hpp | 70 +- .../include/asio/detail/handler_queue.hpp | 219 ++++++ .../include/asio/detail/kqueue_reactor.hpp | 20 +- .../asio/detail/pipe_select_interrupter.hpp | 10 + .../include/asio/detail/posix_event.hpp | 3 +- .../asio/detail/posix_fd_set_adapter.hpp | 2 +- .../include/asio/detail/posix_mutex.hpp | 9 +- .../include/asio/detail/posix_thread.hpp | 3 +- .../include/asio/detail/posix_tss_ptr.hpp | 3 +- .../asio/detail/reactive_socket_service.hpp | 70 +- .../include/asio/detail/resolver_service.hpp | 4 +- libtorrent/include/asio/detail/socket_ops.hpp | 377 +++++++--- .../asio/detail/socket_select_interrupter.hpp | 2 +- .../include/asio/detail/socket_types.hpp | 12 +- .../include/asio/detail/strand_service.hpp | 12 +- .../include/asio/detail/task_io_service.hpp | 160 +---- libtorrent/include/asio/detail/thread.hpp | 10 +- .../include/asio/detail/timer_queue.hpp | 25 +- libtorrent/include/asio/detail/win_event.hpp | 2 +- .../asio/detail/win_iocp_io_service.hpp | 231 ++++++- .../asio/detail/win_iocp_io_service_fwd.hpp | 2 + .../asio/detail/win_iocp_socket_service.hpp | 104 +-- .../detail/win_local_free_on_block_exit.hpp | 59 ++ libtorrent/include/asio/detail/win_mutex.hpp | 6 +- libtorrent/include/asio/detail/win_thread.hpp | 6 +- .../include/asio/detail/win_tss_ptr.hpp | 10 +- .../include/asio/detail/wince_thread.hpp | 124 ++++ .../include/asio/detail/winsock_init.hpp | 3 +- libtorrent/include/asio/error.hpp | 22 +- libtorrent/include/asio/error_code.hpp | 21 +- libtorrent/include/asio/error_handler.hpp | 120 ++++ libtorrent/include/asio/impl/error_code.ipp | 8 +- libtorrent/include/asio/impl/io_service.ipp | 11 + libtorrent/include/asio/io_service.hpp | 23 +- libtorrent/include/asio/ip/address_v4.hpp | 7 +- libtorrent/include/asio/ip/address_v6.hpp | 9 +- libtorrent/include/asio/ip/basic_endpoint.hpp | 56 +- .../asio/ip/basic_resolver_iterator.hpp | 4 +- .../include/asio/ip/detail/socket_option.hpp | 104 ++- libtorrent/include/asio/ip/multicast.hpp | 2 +- libtorrent/include/asio/placeholders.hpp | 6 +- libtorrent/include/asio/resolver_service.hpp | 126 ++++ .../include/asio/socket_acceptor_service.hpp | 3 + .../asio/ssl/detail/openssl_operation.hpp | 2 +- .../ssl/detail/openssl_stream_service.hpp | 16 +- libtorrent/include/asio/ssl/stream.hpp | 20 +- libtorrent/include/asio/strand.hpp | 18 +- .../include/asio/stream_socket_service.hpp | 3 + libtorrent/include/asio/system_exception.hpp | 198 ++++++ 65 files changed, 2886 insertions(+), 579 deletions(-) delete mode 100644 libtorrent/include/asio.hpp create mode 100644 libtorrent/include/asio/COPYING create mode 100644 libtorrent/include/asio/LICENSE_1_0.txt create mode 100644 libtorrent/include/asio/basic_resolver.hpp create mode 100644 libtorrent/include/asio/detail/dev_poll_reactor.hpp create mode 100644 libtorrent/include/asio/detail/dev_poll_reactor_fwd.hpp create mode 100644 libtorrent/include/asio/detail/handler_queue.hpp create mode 100644 libtorrent/include/asio/detail/win_local_free_on_block_exit.hpp create mode 100644 libtorrent/include/asio/detail/wince_thread.hpp create mode 100644 libtorrent/include/asio/error_handler.hpp create mode 100644 libtorrent/include/asio/resolver_service.hpp create mode 100644 libtorrent/include/asio/system_exception.hpp diff --git a/libtorrent/include/asio.hpp b/libtorrent/include/asio.hpp deleted file mode 100644 index ae6455bdb..000000000 --- a/libtorrent/include/asio.hpp +++ /dev/null @@ -1,74 +0,0 @@ -// -// asio.hpp -// ~~~~~~~~ -// -// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASIO_HPP -#define ASIO_HPP - -#if defined(_MSC_VER) && (_MSC_VER >= 1200) -# pragma once -#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) - -#include "asio/basic_datagram_socket.hpp" -#include "asio/basic_deadline_timer.hpp" -#include "asio/basic_io_object.hpp" -#include "asio/basic_socket_acceptor.hpp" -#include "asio/basic_socket_iostream.hpp" -#include "asio/basic_socket_streambuf.hpp" -#include "asio/basic_stream_socket.hpp" -#include "asio/basic_streambuf.hpp" -#include "asio/buffer.hpp" -#include "asio/buffered_read_stream_fwd.hpp" -#include "asio/buffered_read_stream.hpp" -#include "asio/buffered_stream_fwd.hpp" -#include "asio/buffered_stream.hpp" -#include "asio/buffered_write_stream_fwd.hpp" -#include "asio/buffered_write_stream.hpp" -#include "asio/completion_condition.hpp" -#include "asio/datagram_socket_service.hpp" -#include "asio/deadline_timer_service.hpp" -#include "asio/deadline_timer.hpp" -#include "asio/error.hpp" -#include "asio/error_code.hpp" -#include "asio/handler_alloc_hook.hpp" -#include "asio/handler_invoke_hook.hpp" -#include "asio/io_service.hpp" -#include "asio/ip/address.hpp" -#include "asio/ip/address_v4.hpp" -#include "asio/ip/address_v6.hpp" -#include "asio/ip/basic_endpoint.hpp" -#include "asio/ip/basic_resolver.hpp" -#include "asio/ip/basic_resolver_entry.hpp" -#include "asio/ip/basic_resolver_iterator.hpp" -#include "asio/ip/basic_resolver_query.hpp" -#include "asio/ip/host_name.hpp" -#include "asio/ip/multicast.hpp" -#include "asio/ip/resolver_query_base.hpp" -#include "asio/ip/resolver_service.hpp" -#include "asio/ip/tcp.hpp" -#include "asio/ip/udp.hpp" -#include "asio/ip/unicast.hpp" -#include "asio/ip/v6_only.hpp" -#include "asio/is_read_buffered.hpp" -#include "asio/is_write_buffered.hpp" -#include "asio/placeholders.hpp" -#include "asio/read.hpp" -#include "asio/read_until.hpp" -#include "asio/socket_acceptor_service.hpp" -#include "asio/socket_base.hpp" -#include "asio/strand.hpp" -#include "asio/stream_socket_service.hpp" -#include "asio/streambuf.hpp" -#include "asio/system_error.hpp" -#include "asio/thread.hpp" -#include "asio/time_traits.hpp" -#include "asio/version.hpp" -#include "asio/write.hpp" - -#endif // ASIO_HPP diff --git a/libtorrent/include/asio/COPYING b/libtorrent/include/asio/COPYING new file mode 100644 index 000000000..b68879a1a --- /dev/null +++ b/libtorrent/include/asio/COPYING @@ -0,0 +1,4 @@ +Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) + +Distributed under the Boost Software License, Version 1.0. (See accompanying +file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/libtorrent/include/asio/LICENSE_1_0.txt b/libtorrent/include/asio/LICENSE_1_0.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/libtorrent/include/asio/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/libtorrent/include/asio/basic_datagram_socket.hpp b/libtorrent/include/asio/basic_datagram_socket.hpp index 1a521628f..64d4d7244 100644 --- a/libtorrent/include/asio/basic_datagram_socket.hpp +++ b/libtorrent/include/asio/basic_datagram_socket.hpp @@ -732,8 +732,8 @@ public: * completes. Copies will be made of the handler as required. The function * signature of the handler must be: * @code void handler( - * const asio::system_error& error, // Result of operation. - * std::size_t bytes_transferred // Number of bytes received. + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes received. * ); @endcode * Regardless of whether the asynchronous operation completes immediately or * not, the handler will not be invoked from within this function. Invocation diff --git a/libtorrent/include/asio/basic_io_object.hpp b/libtorrent/include/asio/basic_io_object.hpp index 9291ff5da..5b1f94b8e 100644 --- a/libtorrent/include/asio/basic_io_object.hpp +++ b/libtorrent/include/asio/basic_io_object.hpp @@ -34,7 +34,8 @@ public: /// The underlying implementation type of I/O object. typedef typename service_type::implementation_type implementation_type; - /// Get the io_service associated with the object. + /// (Deprecated: use get_io_service().) Get the io_service associated with + /// the object. /** * This function may be used to obtain the io_service object that the I/O * object uses to dispatch handlers for asynchronous operations. @@ -44,7 +45,20 @@ public: */ asio::io_service& io_service() { - return service.io_service(); + return service.get_io_service(); + } + + /// Get the io_service associated with the object. + /** + * This function may be used to obtain the io_service object that the I/O + * object uses to dispatch handlers for asynchronous operations. + * + * @return A reference to the io_service object that the I/O object will use + * to dispatch handlers. Ownership is not transferred to the caller. + */ + asio::io_service& get_io_service() + { + return service.get_io_service(); } protected: diff --git a/libtorrent/include/asio/basic_resolver.hpp b/libtorrent/include/asio/basic_resolver.hpp new file mode 100644 index 000000000..5df89d545 --- /dev/null +++ b/libtorrent/include/asio/basic_resolver.hpp @@ -0,0 +1,252 @@ +// +// basic_resolver.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_BASIC_RESOLVER_HPP +#define ASIO_BASIC_RESOLVER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/basic_io_object.hpp" +#include "asio/error.hpp" +#include "asio/error_handler.hpp" +#include "asio/resolver_service.hpp" + +namespace asio { + +/// Provides endpoint resolution functionality. +/** + * The basic_resolver class template provides the ability to resolve a query + * to a list of endpoints. + * + * @par Thread Safety: + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe. + * + * @par Concepts: + * Async_Object, Error_Source. + */ +template > +class basic_resolver + : public basic_io_object +{ +public: + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// The query type. + typedef typename Protocol::resolver_query query; + + /// The iterator type. + typedef typename Protocol::resolver_iterator iterator; + + /// The type used for reporting errors. + typedef asio::error error_type; + + /// Constructor. + /** + * This constructor creates a basic_resolver. + * + * @param io_service The io_service object that the resolver will use to + * dispatch handlers for any asynchronous operations performed on the timer. + */ + explicit basic_resolver(asio::io_service& io_service) + : basic_io_object(io_service) + { + } + + /// Cancel any asynchronous operations that are waiting on the resolver. + /** + * This function forces the completion of any pending asynchronous + * operations on the host resolver. The handler for each cancelled operation + * will be invoked with the asio::error::operation_aborted error code. + */ + void cancel() + { + return this->service.cancel(this->implementation); + } + + /// Resolve a query to a list of entries. + /** + * This function is used to resolve a query into a list of endpoint entries. + * + * @param q A query object that determines what endpoints will be returned. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. + * + * @throws asio::error Thrown on failure. + * + * @note A default constructed iterator represents the end of the list. + * + * @note A successful call to this function is guaranteed to return at least + * one entry. + */ + iterator resolve(const query& q) + { + return this->service.resolve(this->implementation, q, throw_error()); + } + + /// Resolve a query to a list of entries. + /** + * This function is used to resolve a query into a list of endpoint entries. + * + * @param q A query object that determines what endpoints will be returned. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. Returns a default constructed iterator if an error + * occurs. + * + * @param error_handler A handler to be called when the operation completes, + * to indicate whether or not an error has occurred. Copies will be made of + * the handler as required. The function signature of the handler must be: + * @code void error_handler( + * const asio::error& error // Result of operation. + * ); @endcode + * + * @note A default constructed iterator represents the end of the list. + * + * @note A successful call to this function is guaranteed to return at least + * one entry. + */ + template + iterator resolve(const query& q, Error_Handler error_handler) + { + return this->service.resolve(this->implementation, q, error_handler); + } + + /// Asynchronously resolve a query to a list of entries. + /** + * This function is used to asynchronously resolve a query into a list of + * endpoint entries. + * + * @param q A query object that determines what endpoints will be returned. + * + * @param handler The handler to be called when the resolve operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error& error, // Result of operation. + * resolver::iterator iterator // Forward-only iterator that can be used to + * // traverse the list of endpoint entries. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note A default constructed iterator represents the end of the list. + * + * @note A successful resolve operation is guaranteed to pass at least one + * entry to the handler. + */ + template + void async_resolve(const query& q, Handler handler) + { + return this->service.async_resolve(this->implementation, q, handler); + } + + /// Resolve an endpoint to a list of entries. + /** + * This function is used to resolve an endpoint into a list of endpoint + * entries. + * + * @param e An endpoint object that determines what endpoints will be + * returned. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. + * + * @throws asio::error Thrown on failure. + * + * @note A default constructed iterator represents the end of the list. + * + * @note A successful call to this function is guaranteed to return at least + * one entry. + */ + iterator resolve(const endpoint_type& e) + { + return this->service.resolve(this->implementation, e, throw_error()); + } + + /// Resolve an endpoint to a list of entries. + /** + * This function is used to resolve an endpoint into a list of endpoint + * entries. + * + * @param e An endpoint object that determines what endpoints will be + * returned. + * + * @returns A forward-only iterator that can be used to traverse the list + * of endpoint entries. Returns a default constructed iterator if an error + * occurs. + * + * @param error_handler A handler to be called when the operation completes, + * to indicate whether or not an error has occurred. Copies will be made of + * the handler as required. The function signature of the handler must be: + * @code void error_handler( + * const asio::error& error // Result of operation. + * ); @endcode + * + * @note A default constructed iterator represents the end of the list. + * + * @note A successful call to this function is guaranteed to return at least + * one entry. + */ + template + iterator resolve(const endpoint_type& e, Error_Handler error_handler) + { + return this->service.resolve(this->implementation, e, error_handler); + } + + /// Asynchronously resolve an endpoint to a list of entries. + /** + * This function is used to asynchronously resolve an endpoint into a list of + * endpoint entries. + * + * @param e An endpoint object that determines what endpoints will be + * returned. + * + * @param handler The handler to be called when the resolve operation + * completes. Copies will be made of the handler as required. The function + * signature of the handler must be: + * @code void handler( + * const asio::error& error, // Result of operation. + * resolver::iterator iterator // Forward-only iterator that can be used to + * // traverse the list of endpoint entries. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the handler will not be invoked from within this function. Invocation + * of the handler will be performed in a manner equivalent to using + * asio::io_service::post(). + * + * @note A default constructed iterator represents the end of the list. + * + * @note A successful resolve operation is guaranteed to pass at least one + * entry to the handler. + */ + template + void async_resolve(const endpoint_type& e, Handler handler) + { + return this->service.async_resolve(this->implementation, e, handler); + } +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_BASIC_RESOLVER_HPP diff --git a/libtorrent/include/asio/basic_socket.hpp b/libtorrent/include/asio/basic_socket.hpp index 2b2521b69..fbacf940d 100644 --- a/libtorrent/include/asio/basic_socket.hpp +++ b/libtorrent/include/asio/basic_socket.hpp @@ -564,7 +564,8 @@ public: if (this->service.open(this->implementation, peer_endpoint.protocol(), ec)) { - this->io_service().post(asio::detail::bind_handler(handler, ec)); + this->get_io_service().post( + asio::detail::bind_handler(handler, ec)); return; } } diff --git a/libtorrent/include/asio/buffer.hpp b/libtorrent/include/asio/buffer.hpp index 9fe76178c..881bc1176 100644 --- a/libtorrent/include/asio/buffer.hpp +++ b/libtorrent/include/asio/buffer.hpp @@ -27,7 +27,7 @@ #include "asio/detail/pop_options.hpp" #if defined(BOOST_MSVC) -# if defined(_HAS_ITERATOR_DEBUGGING) +# if defined(_HAS_ITERATOR_DEBUGGING) && (_HAS_ITERATOR_DEBUGGING != 0) # if !defined(ASIO_DISABLE_BUFFER_DEBUGGING) # define ASIO_ENABLE_BUFFER_DEBUGGING # endif // !defined(ASIO_DISABLE_BUFFER_DEBUGGING) @@ -390,6 +390,11 @@ public: { } + ~buffer_debug_check() + { + iter_ = Iterator(); + } + void operator()() { *iter_; diff --git a/libtorrent/include/asio/buffered_read_stream.hpp b/libtorrent/include/asio/buffered_read_stream.hpp index 5cf5d688e..bd9169b8f 100644 --- a/libtorrent/include/asio/buffered_read_stream.hpp +++ b/libtorrent/include/asio/buffered_read_stream.hpp @@ -93,10 +93,17 @@ public: return next_layer_.lowest_layer(); } - /// Get the io_service associated with the object. + /// (Deprecated: use get_io_service().) Get the io_service associated with + /// the object. asio::io_service& io_service() { - return next_layer_.io_service(); + return next_layer_.get_io_service(); + } + + /// Get the io_service associated with the object. + asio::io_service& get_io_service() + { + return next_layer_.get_io_service(); } /// Close the stream. @@ -207,7 +214,7 @@ public: buffer( storage_.data() + previous_size, storage_.size() - previous_size), - fill_handler(io_service(), + fill_handler(get_io_service(), storage_, previous_size, handler)); } @@ -295,12 +302,12 @@ public: if (storage_.empty()) { async_fill(read_some_handler( - io_service(), storage_, buffers, handler)); + get_io_service(), storage_, buffers, handler)); } else { std::size_t length = copy(buffers); - io_service().post(detail::bind_handler( + get_io_service().post(detail::bind_handler( handler, asio::error_code(), length)); } } diff --git a/libtorrent/include/asio/buffered_stream.hpp b/libtorrent/include/asio/buffered_stream.hpp index b6901a6d4..57959059e 100644 --- a/libtorrent/include/asio/buffered_stream.hpp +++ b/libtorrent/include/asio/buffered_stream.hpp @@ -83,10 +83,17 @@ public: return stream_impl_.lowest_layer(); } - /// Get the io_service associated with the object. + /// (Deprecated: use get_io_service().) Get the io_service associated with + /// the object. asio::io_service& io_service() { - return stream_impl_.io_service(); + return stream_impl_.get_io_service(); + } + + /// Get the io_service associated with the object. + asio::io_service& get_io_service() + { + return stream_impl_.get_io_service(); } /// Close the stream. diff --git a/libtorrent/include/asio/buffered_write_stream.hpp b/libtorrent/include/asio/buffered_write_stream.hpp index ffe3a2ee7..d06787091 100644 --- a/libtorrent/include/asio/buffered_write_stream.hpp +++ b/libtorrent/include/asio/buffered_write_stream.hpp @@ -94,10 +94,17 @@ public: return next_layer_.lowest_layer(); } - /// Get the io_service associated with the object. + /// (Deprecated: use get_io_service().) Get the io_service associated with + /// the object. asio::io_service& io_service() { - return next_layer_.io_service(); + return next_layer_.get_io_service(); + } + + /// Get the io_service associated with the object. + asio::io_service& get_io_service() + { + return next_layer_.get_io_service(); } /// Close the stream. @@ -165,7 +172,7 @@ public: void async_flush(WriteHandler handler) { async_write(next_layer_, buffer(storage_.data(), storage_.size()), - flush_handler(io_service(), storage_, handler)); + flush_handler(get_io_service(), storage_, handler)); } /// Write the given data to the stream. Returns the number of bytes written. @@ -253,12 +260,12 @@ public: if (storage_.size() == storage_.capacity()) { async_flush(write_some_handler( - io_service(), storage_, buffers, handler)); + get_io_service(), storage_, buffers, handler)); } else { std::size_t bytes_copied = copy(buffers); - io_service().post(detail::bind_handler( + get_io_service().post(detail::bind_handler( handler, asio::error_code(), bytes_copied)); } } diff --git a/libtorrent/include/asio/datagram_socket_service.hpp b/libtorrent/include/asio/datagram_socket_service.hpp index 1f858de61..69de5f2cc 100644 --- a/libtorrent/include/asio/datagram_socket_service.hpp +++ b/libtorrent/include/asio/datagram_socket_service.hpp @@ -64,6 +64,9 @@ private: #elif defined(ASIO_HAS_KQUEUE) typedef detail::reactive_socket_service< Protocol, detail::kqueue_reactor > service_impl_type; +#elif defined(ASIO_HAS_DEV_POLL) + typedef detail::reactive_socket_service< + Protocol, detail::dev_poll_reactor > service_impl_type; #else typedef detail::reactive_socket_service< Protocol, detail::select_reactor > service_impl_type; diff --git a/libtorrent/include/asio/deadline_timer_service.hpp b/libtorrent/include/asio/deadline_timer_service.hpp index 17b97350b..e1d1bc97e 100644 --- a/libtorrent/include/asio/deadline_timer_service.hpp +++ b/libtorrent/include/asio/deadline_timer_service.hpp @@ -29,6 +29,7 @@ #include "asio/detail/kqueue_reactor.hpp" #include "asio/detail/select_reactor.hpp" #include "asio/detail/service_base.hpp" +#include "asio/detail/win_iocp_io_service.hpp" namespace asio { @@ -62,13 +63,16 @@ private: // The type of the platform-specific implementation. #if defined(ASIO_HAS_IOCP) typedef detail::deadline_timer_service< - traits_type, detail::select_reactor > service_impl_type; + traits_type, detail::win_iocp_io_service> service_impl_type; #elif defined(ASIO_HAS_EPOLL) typedef detail::deadline_timer_service< traits_type, detail::epoll_reactor > service_impl_type; #elif defined(ASIO_HAS_KQUEUE) typedef detail::deadline_timer_service< traits_type, detail::kqueue_reactor > service_impl_type; +#elif defined(ASIO_HAS_DEV_POLL) + typedef detail::deadline_timer_service< + traits_type, detail::dev_poll_reactor > service_impl_type; #else typedef detail::deadline_timer_service< traits_type, detail::select_reactor > service_impl_type; diff --git a/libtorrent/include/asio/detail/deadline_timer_service.hpp b/libtorrent/include/asio/detail/deadline_timer_service.hpp index c22c5a7b7..06b90b5eb 100644 --- a/libtorrent/include/asio/detail/deadline_timer_service.hpp +++ b/libtorrent/include/asio/detail/deadline_timer_service.hpp @@ -180,7 +180,7 @@ public: { impl.might_have_pending_waits = true; scheduler_.schedule_timer(timer_queue_, impl.expiry, - wait_handler(this->io_service(), handler), &impl); + wait_handler(this->get_io_service(), handler), &impl); } private: diff --git a/libtorrent/include/asio/detail/dev_poll_reactor.hpp b/libtorrent/include/asio/detail/dev_poll_reactor.hpp new file mode 100644 index 000000000..15babcdb9 --- /dev/null +++ b/libtorrent/include/asio/detail/dev_poll_reactor.hpp @@ -0,0 +1,647 @@ +// +// dev_poll_reactor.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_DEV_POLL_REACTOR_HPP +#define ASIO_DETAIL_DEV_POLL_REACTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/dev_poll_reactor_fwd.hpp" + +#if defined(ASIO_HAS_DEV_POLL) + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include "asio/detail/pop_options.hpp" + +#include "asio/error.hpp" +#include "asio/io_service.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/bind_handler.hpp" +#include "asio/detail/hash_map.hpp" +#include "asio/detail/mutex.hpp" +#include "asio/detail/task_io_service.hpp" +#include "asio/detail/thread.hpp" +#include "asio/detail/reactor_op_queue.hpp" +#include "asio/detail/select_interrupter.hpp" +#include "asio/detail/service_base.hpp" +#include "asio/detail/signal_blocker.hpp" +#include "asio/detail/socket_types.hpp" +#include "asio/detail/timer_queue.hpp" + +namespace asio { +namespace detail { + +template +class dev_poll_reactor + : public asio::detail::service_base > +{ +public: + // Constructor. + dev_poll_reactor(asio::io_service& io_service) + : asio::detail::service_base< + dev_poll_reactor >(io_service), + mutex_(), + dev_poll_fd_(do_dev_poll_create()), + wait_in_progress_(false), + interrupter_(), + read_op_queue_(), + write_op_queue_(), + except_op_queue_(), + pending_cancellations_(), + stop_thread_(false), + thread_(0), + shutdown_(false) + { + // Start the reactor's internal thread only if needed. + if (Own_Thread) + { + asio::detail::signal_blocker sb; + thread_ = new asio::detail::thread( + bind_handler(&dev_poll_reactor::call_run_thread, this)); + } + + // Add the interrupter's descriptor to /dev/poll. + ::pollfd ev = { 0 }; + ev.fd = interrupter_.read_descriptor(); + ev.events = POLLIN | POLLERR; + ev.revents = 0; + ::write(dev_poll_fd_, &ev, sizeof(ev)); + } + + // Destructor. + ~dev_poll_reactor() + { + shutdown_service(); + ::close(dev_poll_fd_); + } + + // Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + asio::detail::mutex::scoped_lock lock(mutex_); + shutdown_ = true; + stop_thread_ = true; + lock.unlock(); + + if (thread_) + { + interrupter_.interrupt(); + thread_->join(); + delete thread_; + thread_ = 0; + } + + read_op_queue_.destroy_operations(); + write_op_queue_.destroy_operations(); + except_op_queue_.destroy_operations(); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->destroy_timers(); + timer_queues_.clear(); + } + + // Register a socket with the reactor. Returns 0 on success, system error + // code on failure. + int register_descriptor(socket_type descriptor) + { + return 0; + } + + // Start a new read operation. The handler object will be invoked when the + // given descriptor is ready to be read, or an error has occurred. + template + void start_read_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (!read_op_queue_.has_operation(descriptor)) + if (handler(asio::error_code())) + return; + + if (read_op_queue_.enqueue_operation(descriptor, handler)) + { + ::pollfd& ev = add_pending_event_change(descriptor); + ev.events = POLLIN | POLLERR | POLLHUP; + if (write_op_queue_.has_operation(descriptor)) + ev.events |= POLLOUT; + if (except_op_queue_.has_operation(descriptor)) + ev.events |= POLLPRI; + interrupter_.interrupt(); + } + } + + // Start a new write operation. The handler object will be invoked when the + // given descriptor is ready to be written, or an error has occurred. + template + void start_write_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (!write_op_queue_.has_operation(descriptor)) + if (handler(asio::error_code())) + return; + + if (write_op_queue_.enqueue_operation(descriptor, handler)) + { + ::pollfd& ev = add_pending_event_change(descriptor); + ev.events = POLLOUT | POLLERR | POLLHUP; + if (read_op_queue_.has_operation(descriptor)) + ev.events |= POLLIN; + if (except_op_queue_.has_operation(descriptor)) + ev.events |= POLLPRI; + interrupter_.interrupt(); + } + } + + // Start a new exception operation. The handler object will be invoked when + // the given descriptor has exception information, or an error has occurred. + template + void start_except_op(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + if (except_op_queue_.enqueue_operation(descriptor, handler)) + { + ::pollfd& ev = add_pending_event_change(descriptor); + ev.events = POLLPRI | POLLERR | POLLHUP; + if (read_op_queue_.has_operation(descriptor)) + ev.events |= POLLIN; + if (write_op_queue_.has_operation(descriptor)) + ev.events |= POLLOUT; + interrupter_.interrupt(); + } + } + + // Start new write and exception operations. The handler object will be + // invoked when the given descriptor is ready for writing or has exception + // information available, or an error has occurred. + template + void start_write_and_except_ops(socket_type descriptor, Handler handler) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + if (shutdown_) + return; + + bool need_mod = write_op_queue_.enqueue_operation(descriptor, handler); + need_mod = except_op_queue_.enqueue_operation(descriptor, handler) + && need_mod; + if (need_mod) + { + ::pollfd& ev = add_pending_event_change(descriptor); + ev.events = POLLOUT | POLLPRI | POLLERR | POLLHUP; + if (read_op_queue_.has_operation(descriptor)) + ev.events |= POLLIN; + interrupter_.interrupt(); + } + } + + // Cancel all operations associated with the given descriptor. The + // handlers associated with the descriptor will be invoked with the + // operation_aborted error. + void cancel_ops(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + cancel_ops_unlocked(descriptor); + } + + // Enqueue cancellation of all operations associated with the given + // descriptor. The handlers associated with the descriptor will be invoked + // with the operation_aborted error. This function does not acquire the + // dev_poll_reactor's mutex, and so should only be used from within a reactor + // handler. + void enqueue_cancel_ops_unlocked(socket_type descriptor) + { + pending_cancellations_.push_back(descriptor); + } + + // Cancel any operations that are running against the descriptor and remove + // its registration from the reactor. + void close_descriptor(socket_type descriptor) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Remove the descriptor from /dev/poll. + ::pollfd& ev = add_pending_event_change(descriptor); + ev.events = POLLREMOVE; + interrupter_.interrupt(); + + // Cancel any outstanding operations associated with the descriptor. + cancel_ops_unlocked(descriptor); + } + + // Add a new timer queue to the reactor. + template + void add_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + timer_queues_.push_back(&timer_queue); + } + + // Remove a timer queue from the reactor. + template + void remove_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(mutex_); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + if (timer_queues_[i] == &timer_queue) + { + timer_queues_.erase(timer_queues_.begin() + i); + return; + } + } + } + + // Schedule a timer in the given timer queue to expire at the specified + // absolute time. The handler object will be invoked when the timer expires. + template + void schedule_timer(timer_queue& timer_queue, + const typename Time_Traits::time_type& time, Handler handler, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + if (!shutdown_) + if (timer_queue.enqueue_timer(time, handler, token)) + interrupter_.interrupt(); + } + + // Cancel the timer associated with the given token. Returns the number of + // handlers that have been posted or dispatched. + template + std::size_t cancel_timer(timer_queue& timer_queue, void* token) + { + asio::detail::mutex::scoped_lock lock(mutex_); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0) + interrupter_.interrupt(); + return n; + } + +private: + friend class task_io_service >; + + // Run /dev/poll once until interrupted or events are ready to be dispatched. + void run(bool block) + { + asio::detail::mutex::scoped_lock lock(mutex_); + + // Dispatch any operation cancellations that were made while the select + // loop was not running. + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->dispatch_cancellations(); + + // Check if the thread is supposed to stop. + if (stop_thread_) + { + cleanup_operations_and_timers(lock); + return; + } + + // We can return immediately if there's no work to do and the reactor is + // not supposed to block. + if (!block && read_op_queue_.empty() && write_op_queue_.empty() + && except_op_queue_.empty() && all_timer_queues_are_empty()) + { + cleanup_operations_and_timers(lock); + return; + } + + // Write the pending event registration changes to the /dev/poll descriptor. + std::size_t events_size = sizeof(::pollfd) * pending_event_changes_.size(); + errno = 0; + int result = ::write(dev_poll_fd_, + &pending_event_changes_[0], events_size); + if (result != static_cast(events_size)) + { + for (std::size_t i = 0; i < pending_event_changes_.size(); ++i) + { + int descriptor = pending_event_changes_[i].fd; + asio::error_code ec = asio::error_code( + errno, asio::error::get_system_category()); + read_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + pending_event_changes_.clear(); + pending_event_change_index_.clear(); + + int timeout = block ? get_timeout() : 0; + wait_in_progress_ = true; + lock.unlock(); + + // Block on the /dev/poll descriptor. + ::pollfd events[128] = { { 0 } }; + ::dvpoll dp = { 0 }; + dp.dp_fds = events; + dp.dp_nfds = 128; + dp.dp_timeout = timeout; + int num_events = ::ioctl(dev_poll_fd_, DP_POLL, &dp); + + lock.lock(); + wait_in_progress_ = false; + + // Block signals while dispatching operations. + asio::detail::signal_blocker sb; + + // Dispatch the waiting events. + for (int i = 0; i < num_events; ++i) + { + int descriptor = events[i].fd; + if (descriptor == interrupter_.read_descriptor()) + { + interrupter_.reset(); + } + else + { + bool more_reads = false; + bool more_writes = false; + bool more_except = false; + asio::error_code ec; + + // Exception operations must be processed first to ensure that any + // out-of-band data is read before normal data. + if (events[i].events & (POLLPRI | POLLERR | POLLHUP)) + more_except = except_op_queue_.dispatch_operation(descriptor, ec); + else + more_except = except_op_queue_.has_operation(descriptor); + + if (events[i].events & (POLLIN | POLLERR | POLLHUP)) + more_reads = read_op_queue_.dispatch_operation(descriptor, ec); + else + more_reads = read_op_queue_.has_operation(descriptor); + + if (events[i].events & (POLLOUT | POLLERR | POLLHUP)) + more_writes = write_op_queue_.dispatch_operation(descriptor, ec); + else + more_writes = write_op_queue_.has_operation(descriptor); + + if ((events[i].events == POLLHUP) + && !more_except && !more_reads && !more_writes) + { + // If we have only an POLLHUP event and no operations associated + // with the descriptor then we need to delete the descriptor from + // /dev/poll. The poll operation might produce POLLHUP events even + // if they are not specifically requested, so if we do not remove the + // descriptor we can end up in a tight polling loop. + ::pollfd ev = { 0 }; + ev.fd = descriptor; + ev.events = POLLREMOVE; + ev.revents = 0; + ::write(dev_poll_fd_, &ev, sizeof(ev)); + } + else + { + ::pollfd ev = { 0 }; + ev.fd = descriptor; + ev.events = POLLERR | POLLHUP; + if (more_reads) + ev.events |= POLLIN; + if (more_writes) + ev.events |= POLLOUT; + if (more_except) + ev.events |= POLLPRI; + ev.revents = 0; + int result = ::write(dev_poll_fd_, &ev, sizeof(ev)); + if (result != sizeof(ev)) + { + ec = asio::error_code(errno, + asio::error::get_system_category()); + read_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } + } + } + } + read_op_queue_.dispatch_cancellations(); + write_op_queue_.dispatch_cancellations(); + except_op_queue_.dispatch_cancellations(); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + } + + // Issue any pending cancellations. + for (size_t i = 0; i < pending_cancellations_.size(); ++i) + cancel_ops_unlocked(pending_cancellations_[i]); + pending_cancellations_.clear(); + + cleanup_operations_and_timers(lock); + } + + // Run the select loop in the thread. + void run_thread() + { + asio::detail::mutex::scoped_lock lock(mutex_); + while (!stop_thread_) + { + lock.unlock(); + run(true); + lock.lock(); + } + } + + // Entry point for the select loop thread. + static void call_run_thread(dev_poll_reactor* reactor) + { + reactor->run_thread(); + } + + // Interrupt the select loop. + void interrupt() + { + interrupter_.interrupt(); + } + + // Create the /dev/poll file descriptor. Throws an exception if the descriptor + // cannot be created. + static int do_dev_poll_create() + { + int fd = ::open("/dev/poll", O_RDWR); + if (fd == -1) + { + boost::throw_exception( + asio::system_error( + asio::error_code(errno, + asio::error::get_system_category()), + "/dev/poll")); + } + return fd; + } + + // Check if all timer queues are empty. + bool all_timer_queues_are_empty() const + { + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + if (!timer_queues_[i]->empty()) + return false; + return true; + } + + // Get the timeout value for the /dev/poll DP_POLL operation. The timeout + // value is returned as a number of milliseconds. A return value of -1 + // indicates that the poll should block indefinitely. + int get_timeout() + { + if (all_timer_queues_are_empty()) + return -1; + + // By default we will wait no longer than 5 minutes. This will ensure that + // any changes to the system clock are detected after no longer than this. + boost::posix_time::time_duration minimum_wait_duration + = boost::posix_time::minutes(5); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + boost::posix_time::time_duration wait_duration + = timer_queues_[i]->wait_duration(); + if (wait_duration < minimum_wait_duration) + minimum_wait_duration = wait_duration; + } + + if (minimum_wait_duration > boost::posix_time::time_duration()) + { + int milliseconds = minimum_wait_duration.total_milliseconds(); + return milliseconds > 0 ? milliseconds : 1; + } + else + { + return 0; + } + } + + // Cancel all operations associated with the given descriptor. The do_cancel + // function of the handler objects will be invoked. This function does not + // acquire the dev_poll_reactor's mutex. + void cancel_ops_unlocked(socket_type descriptor) + { + bool interrupt = read_op_queue_.cancel_operations(descriptor); + interrupt = write_op_queue_.cancel_operations(descriptor) || interrupt; + interrupt = except_op_queue_.cancel_operations(descriptor) || interrupt; + if (interrupt) + interrupter_.interrupt(); + } + + // Clean up operations and timers. We must not hold the lock since the + // destructors may make calls back into this reactor. We make a copy of the + // vector of timer queues since the original may be modified while the lock + // is not held. + void cleanup_operations_and_timers( + asio::detail::mutex::scoped_lock& lock) + { + timer_queues_for_cleanup_ = timer_queues_; + lock.unlock(); + read_op_queue_.cleanup_operations(); + write_op_queue_.cleanup_operations(); + except_op_queue_.cleanup_operations(); + for (std::size_t i = 0; i < timer_queues_for_cleanup_.size(); ++i) + timer_queues_for_cleanup_[i]->cleanup_timers(); + } + + // Add a pending event entry for the given descriptor. + ::pollfd& add_pending_event_change(int descriptor) + { + hash_map::iterator iter + = pending_event_change_index_.find(descriptor); + if (iter == pending_event_change_index_.end()) + { + std::size_t index = pending_event_changes_.size(); + pending_event_changes_.reserve(pending_event_changes_.size() + 1); + pending_event_change_index_.insert(std::make_pair(descriptor, index)); + pending_event_changes_.push_back(::pollfd()); + pending_event_changes_[index].fd = descriptor; + pending_event_changes_[index].revents = 0; + return pending_event_changes_[index]; + } + else + { + return pending_event_changes_[iter->second]; + } + } + + // Mutex to protect access to internal data. + asio::detail::mutex mutex_; + + // The /dev/poll file descriptor. + int dev_poll_fd_; + + // Vector of /dev/poll events waiting to be written to the descriptor. + std::vector< ::pollfd> pending_event_changes_; + + // Hash map to associate a descriptor with a pending event change index. + hash_map pending_event_change_index_; + + // Whether the DP_POLL operation is currently in progress + bool wait_in_progress_; + + // The interrupter is used to break a blocking DP_POLL operation. + select_interrupter interrupter_; + + // The queue of read operations. + reactor_op_queue read_op_queue_; + + // The queue of write operations. + reactor_op_queue write_op_queue_; + + // The queue of except operations. + reactor_op_queue except_op_queue_; + + // The timer queues. + std::vector timer_queues_; + + // A copy of the timer queues, used when cleaning up timers. The copy is + // stored as a class data member to avoid unnecessary memory allocation. + std::vector timer_queues_for_cleanup_; + + // The descriptors that are pending cancellation. + std::vector pending_cancellations_; + + // Does the reactor loop thread need to stop. + bool stop_thread_; + + // The thread that is running the reactor loop. + asio::detail::thread* thread_; + + // Whether the service has been shut down. + bool shutdown_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(ASIO_HAS_DEV_POLL) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_DEV_POLL_REACTOR_HPP diff --git a/libtorrent/include/asio/detail/dev_poll_reactor_fwd.hpp b/libtorrent/include/asio/detail/dev_poll_reactor_fwd.hpp new file mode 100644 index 000000000..230a6fd12 --- /dev/null +++ b/libtorrent/include/asio/detail/dev_poll_reactor_fwd.hpp @@ -0,0 +1,40 @@ +// +// dev_poll_reactor_fwd.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_DEV_POLL_REACTOR_FWD_HPP +#define ASIO_DETAIL_DEV_POLL_REACTOR_FWD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#if !defined(ASIO_DISABLE_DEV_POLL) +#if defined(__sun) // This service is only supported on Solaris. + +// Define this to indicate that /dev/poll is supported on the target platform. +#define ASIO_HAS_DEV_POLL 1 + +namespace asio { +namespace detail { + +template +class dev_poll_reactor; + +} // namespace detail +} // namespace asio + +#endif // defined(__sun) +#endif // !defined(ASIO_DISABLE_DEV_POLL) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_DEV_POLL_REACTOR_FWD_HPP diff --git a/libtorrent/include/asio/detail/epoll_reactor.hpp b/libtorrent/include/asio/detail/epoll_reactor.hpp index e260c5194..93d39a23c 100644 --- a/libtorrent/include/asio/detail/epoll_reactor.hpp +++ b/libtorrent/include/asio/detail/epoll_reactor.hpp @@ -155,10 +155,12 @@ public: ev.data.fd = descriptor; int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0 && errno == ENOENT) + result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev); if (result != 0) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -189,10 +191,12 @@ public: ev.data.fd = descriptor; int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0 && errno == ENOENT) + result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev); if (result != 0) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -219,10 +223,12 @@ public: ev.data.fd = descriptor; int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0 && errno == ENOENT) + result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev); if (result != 0) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -251,10 +257,12 @@ public: ev.data.fd = descriptor; int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0 && errno == ENOENT) + result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev); if (result != 0) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); write_op_queue_.dispatch_all_operations(descriptor, ec); except_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -419,23 +427,40 @@ private: else more_writes = write_op_queue_.has_operation(descriptor); - epoll_event ev = { 0, { 0 } }; - ev.events = EPOLLERR | EPOLLHUP; - if (more_reads) - ev.events |= EPOLLIN; - if (more_writes) - ev.events |= EPOLLOUT; - if (more_except) - ev.events |= EPOLLPRI; - ev.data.fd = descriptor; - int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); - if (result != 0) + if ((events[i].events == EPOLLHUP) + && !more_except && !more_reads && !more_writes) { - ec = asio::error_code(errno, - asio::error::system_category); - read_op_queue_.dispatch_all_operations(descriptor, ec); - write_op_queue_.dispatch_all_operations(descriptor, ec); - except_op_queue_.dispatch_all_operations(descriptor, ec); + // If we have only an EPOLLHUP event and no operations associated + // with the descriptor then we need to delete the descriptor from + // epoll. The epoll_wait system call will produce EPOLLHUP events + // even if they are not specifically requested, so if we do not + // remove the descriptor we can end up in a tight loop of repeated + // calls to epoll_wait. + epoll_event ev = { 0, { 0 } }; + epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, descriptor, &ev); + } + else + { + epoll_event ev = { 0, { 0 } }; + ev.events = EPOLLERR | EPOLLHUP; + if (more_reads) + ev.events |= EPOLLIN; + if (more_writes) + ev.events |= EPOLLOUT; + if (more_except) + ev.events |= EPOLLPRI; + ev.data.fd = descriptor; + int result = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev); + if (result != 0 && errno == ENOENT) + result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev); + if (result != 0) + { + ec = asio::error_code(errno, + asio::error::get_system_category()); + read_op_queue_.dispatch_all_operations(descriptor, ec); + write_op_queue_.dispatch_all_operations(descriptor, ec); + except_op_queue_.dispatch_all_operations(descriptor, ec); + } } } } @@ -493,7 +518,7 @@ private: boost::throw_exception( asio::system_error( asio::error_code(errno, - asio::error::system_category), + asio::error::get_system_category()), "epoll")); } return fd; @@ -531,7 +556,8 @@ private: if (minimum_wait_duration > boost::posix_time::time_duration()) { - return minimum_wait_duration.total_milliseconds(); + int milliseconds = minimum_wait_duration.total_milliseconds(); + return milliseconds > 0 ? milliseconds : 1; } else { diff --git a/libtorrent/include/asio/detail/handler_queue.hpp b/libtorrent/include/asio/detail/handler_queue.hpp new file mode 100644 index 000000000..cd9870279 --- /dev/null +++ b/libtorrent/include/asio/detail/handler_queue.hpp @@ -0,0 +1,219 @@ +// +// handler_queue.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_HANDLER_QUEUE_HPP +#define ASIO_DETAIL_HANDLER_QUEUE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/handler_alloc_helpers.hpp" +#include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/noncopyable.hpp" + +namespace asio { +namespace detail { + +class handler_queue + : private noncopyable +{ +public: + // Base class for handlers in the queue. + class handler + : private noncopyable + { + public: + void invoke() + { + invoke_func_(this); + } + + void destroy() + { + destroy_func_(this); + } + + protected: + typedef void (*invoke_func_type)(handler*); + typedef void (*destroy_func_type)(handler*); + + handler(invoke_func_type invoke_func, + destroy_func_type destroy_func) + : next_(0), + invoke_func_(invoke_func), + destroy_func_(destroy_func) + { + } + + ~handler() + { + } + + private: + friend class handler_queue; + handler* next_; + invoke_func_type invoke_func_; + destroy_func_type destroy_func_; + }; + + // Smart point to manager handler lifetimes. + class scoped_ptr + : private noncopyable + { + public: + explicit scoped_ptr(handler* h) + : handler_(h) + { + } + + ~scoped_ptr() + { + if (handler_) + handler_->destroy(); + } + + handler* get() const + { + return handler_; + } + + handler* release() + { + handler* tmp = handler_; + handler_ = 0; + return tmp; + } + + private: + handler* handler_; + }; + + // Constructor. + handler_queue() + : front_(0), + back_(0) + { + } + + // Wrap a handler to be pushed into the queue. + template + static handler* wrap(Handler h) + { + // Allocate and construct an object to wrap the handler. + typedef handler_wrapper value_type; + typedef handler_alloc_traits alloc_traits; + raw_handler_ptr raw_ptr(h); + handler_ptr ptr(raw_ptr, h); + return ptr.release(); + } + + // Get the handler at the front of the queue. + handler* front() + { + return front_; + } + + // Pop a handler from the front of the queue. + void pop() + { + if (front_) + { + handler* tmp = front_; + front_ = front_->next_; + if (front_ == 0) + back_ = 0; + tmp->next_= 0; + } + } + + // Push a handler on to the back of the queue. + void push(handler* h) + { + h->next_ = 0; + if (back_) + { + back_->next_ = h; + back_ = h; + } + else + { + front_ = back_ = h; + } + } + + // Whether the queue is empty. + bool empty() const + { + return front_ == 0; + } + +private: + // Template wrapper for handlers. + template + class handler_wrapper + : public handler + { + public: + handler_wrapper(Handler h) + : handler( + &handler_wrapper::do_call, + &handler_wrapper::do_destroy), + handler_(h) + { + } + + static void do_call(handler* base) + { + // Take ownership of the handler object. + typedef handler_wrapper this_type; + this_type* h(static_cast(base)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(h->handler_, h); + + // Make a copy of the handler so that the memory can be deallocated before + // the upcall is made. + Handler handler(h->handler_); + + // Free the memory associated with the handler. + ptr.reset(); + + // Make the upcall. + asio_handler_invoke_helpers::invoke(handler, &handler); + } + + static void do_destroy(handler* base) + { + // Take ownership of the handler object. + typedef handler_wrapper this_type; + this_type* h(static_cast(base)); + typedef handler_alloc_traits alloc_traits; + handler_ptr ptr(h->handler_, h); + } + + private: + Handler handler_; + }; + + // The front of the queue. + handler* front_; + + // The back of the queue. + handler* back_; +}; + +} // namespace detail +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_HANDLER_QUEUE_HPP diff --git a/libtorrent/include/asio/detail/kqueue_reactor.hpp b/libtorrent/include/asio/detail/kqueue_reactor.hpp index 5fffc6788..896ff5403 100644 --- a/libtorrent/include/asio/detail/kqueue_reactor.hpp +++ b/libtorrent/include/asio/detail/kqueue_reactor.hpp @@ -151,7 +151,7 @@ public: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); read_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -178,7 +178,7 @@ public: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -204,7 +204,7 @@ public: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); except_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -228,7 +228,7 @@ public: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); write_op_queue_.dispatch_all_operations(descriptor, ec); } } @@ -243,7 +243,7 @@ public: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code ec(errno, - asio::error::system_category); + asio::error::get_system_category()); except_op_queue_.dispatch_all_operations(descriptor, ec); write_op_queue_.dispatch_all_operations(descriptor, ec); } @@ -397,7 +397,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::error::system_category); + events[i].data, asio::error::get_system_category()); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -428,7 +428,7 @@ private: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code error(errno, - asio::error::system_category); + asio::error::get_system_category()); except_op_queue_.dispatch_all_operations(descriptor, error); read_op_queue_.dispatch_all_operations(descriptor, error); } @@ -440,7 +440,7 @@ private: if (events[i].flags & EV_ERROR) { asio::error_code error( - events[i].data, asio::error::system_category); + events[i].data, asio::error::get_system_category()); write_op_queue_.dispatch_all_operations(descriptor, error); } else @@ -458,7 +458,7 @@ private: if (::kevent(kqueue_fd_, &event, 1, 0, 0, 0) == -1) { asio::error_code error(errno, - asio::error::system_category); + asio::error::get_system_category()); write_op_queue_.dispatch_all_operations(descriptor, error); } } @@ -515,7 +515,7 @@ private: boost::throw_exception( asio::system_error( asio::error_code(errno, - asio::error::system_category), + asio::error::get_system_category()), "kqueue")); } return fd; diff --git a/libtorrent/include/asio/detail/pipe_select_interrupter.hpp b/libtorrent/include/asio/detail/pipe_select_interrupter.hpp index e203669cb..c0ac2aab1 100644 --- a/libtorrent/include/asio/detail/pipe_select_interrupter.hpp +++ b/libtorrent/include/asio/detail/pipe_select_interrupter.hpp @@ -19,6 +19,7 @@ #include "asio/detail/push_options.hpp" #include +#include #include "asio/detail/pop_options.hpp" #if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) @@ -27,6 +28,8 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" +#include "asio/system_error.hpp" #include "asio/detail/socket_types.hpp" namespace asio { @@ -46,6 +49,13 @@ public: write_descriptor_ = pipe_fds[1]; ::fcntl(write_descriptor_, F_SETFL, O_NONBLOCK); } + else + { + asio::error_code ec(errno, + asio::error::get_system_category()); + asio::system_error e(ec, "pipe_select_interrupter"); + boost::throw_exception(e); + } } // Destructor. diff --git a/libtorrent/include/asio/detail/posix_event.hpp b/libtorrent/include/asio/detail/posix_event.hpp index b48586f15..305ed873b 100644 --- a/libtorrent/include/asio/detail/posix_event.hpp +++ b/libtorrent/include/asio/detail/posix_event.hpp @@ -48,7 +48,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "event"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp b/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp index ae5bf6642..c43904f4d 100644 --- a/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp +++ b/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp @@ -58,7 +58,7 @@ public: } private: - fd_set fd_set_; + mutable fd_set fd_set_; socket_type max_descriptor_; }; diff --git a/libtorrent/include/asio/detail/posix_mutex.hpp b/libtorrent/include/asio/detail/posix_mutex.hpp index 6067880fb..c2a01d0b0 100644 --- a/libtorrent/include/asio/detail/posix_mutex.hpp +++ b/libtorrent/include/asio/detail/posix_mutex.hpp @@ -51,7 +51,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "mutex"); boost::throw_exception(e); } @@ -70,7 +71,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "mutex"); boost::throw_exception(e); } @@ -83,7 +85,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "mutex"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/posix_thread.hpp b/libtorrent/include/asio/detail/posix_thread.hpp index 6e5815426..a4a3da23c 100644 --- a/libtorrent/include/asio/detail/posix_thread.hpp +++ b/libtorrent/include/asio/detail/posix_thread.hpp @@ -53,7 +53,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "thread"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/posix_tss_ptr.hpp b/libtorrent/include/asio/detail/posix_tss_ptr.hpp index dda329c40..1becb8dc4 100644 --- a/libtorrent/include/asio/detail/posix_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/posix_tss_ptr.hpp @@ -47,7 +47,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/reactive_socket_service.hpp b/libtorrent/include/asio/detail/reactive_socket_service.hpp index 9c0075821..df70f8863 100644 --- a/libtorrent/include/asio/detail/reactive_socket_service.hpp +++ b/libtorrent/include/asio/detail/reactive_socket_service.hpp @@ -157,7 +157,8 @@ public: if (int err = reactor_.register_descriptor(sock.get())) { - ec = asio::error_code(err, asio::error::system_category); + ec = asio::error_code(err, + asio::error::get_system_category()); return ec; } @@ -181,7 +182,8 @@ public: if (int err = reactor_.register_descriptor(native_socket)) { - ec = asio::error_code(err, asio::error::system_category); + ec = asio::error_code(err, + asio::error::get_system_category()); return ec; } @@ -450,7 +452,7 @@ public: } endpoint_type endpoint; - socket_addr_len_type addr_len = endpoint.capacity(); + std::size_t addr_len = endpoint.capacity(); if (socket_ops::getsockname(impl.socket_, endpoint.data(), &addr_len, ec)) return endpoint_type(); endpoint.resize(addr_len); @@ -468,7 +470,7 @@ public: } endpoint_type endpoint; - socket_addr_len_type addr_len = endpoint.capacity(); + std::size_t addr_len = endpoint.capacity(); if (socket_ops::getpeername(impl.socket_, endpoint.data(), &addr_len, ec)) return endpoint_type(); endpoint.resize(addr_len); @@ -624,7 +626,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); } else @@ -645,7 +647,7 @@ public: // A request to receive 0 bytes on a stream socket is a no-op. if (total_buffer_size == 0) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error_code(), 0)); return; } @@ -658,7 +660,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec, 0)); + this->get_io_service().post(bind_handler(handler, ec, 0)); return; } impl.flags_ |= implementation_type::internal_non_blocking; @@ -666,7 +668,7 @@ public: reactor_.start_write_op(impl.socket_, send_handler( - impl.socket_, this->io_service(), buffers, flags, handler)); + impl.socket_, this->get_io_service(), buffers, flags, handler)); } } @@ -804,7 +806,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); } else @@ -816,7 +818,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec, 0)); + this->get_io_service().post(bind_handler(handler, ec, 0)); return; } impl.flags_ |= implementation_type::internal_non_blocking; @@ -824,7 +826,7 @@ public: reactor_.start_write_op(impl.socket_, send_to_handler( - impl.socket_, this->io_service(), buffers, + impl.socket_, this->get_io_service(), buffers, destination, flags, handler)); } } @@ -975,7 +977,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); } else @@ -996,7 +998,7 @@ public: // A request to receive 0 bytes on a stream socket is a no-op. if (total_buffer_size == 0) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error_code(), 0)); return; } @@ -1009,7 +1011,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec, 0)); + this->get_io_service().post(bind_handler(handler, ec, 0)); return; } impl.flags_ |= implementation_type::internal_non_blocking; @@ -1019,13 +1021,13 @@ public: { reactor_.start_except_op(impl.socket_, receive_handler( - impl.socket_, this->io_service(), buffers, flags, handler)); + impl.socket_, this->get_io_service(), buffers, flags, handler)); } else { reactor_.start_read_op(impl.socket_, receive_handler( - impl.socket_, this->io_service(), buffers, flags, handler)); + impl.socket_, this->get_io_service(), buffers, flags, handler)); } } } @@ -1073,7 +1075,7 @@ public: for (;;) { // Try to complete the operation without blocking. - socket_addr_len_type addr_len = sender_endpoint.capacity(); + std::size_t addr_len = sender_endpoint.capacity(); int bytes_recvd = socket_ops::recvfrom(impl.socket_, bufs, i, flags, sender_endpoint.data(), &addr_len, ec); @@ -1144,7 +1146,7 @@ public: } // Receive some data. - socket_addr_len_type addr_len = sender_endpoint_.capacity(); + std::size_t addr_len = sender_endpoint_.capacity(); asio::error_code ec; int bytes = socket_ops::recvfrom(socket_, bufs, i, flags_, sender_endpoint_.data(), &addr_len, ec); @@ -1181,7 +1183,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); } else @@ -1193,7 +1195,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec, 0)); + this->get_io_service().post(bind_handler(handler, ec, 0)); return; } impl.flags_ |= implementation_type::internal_non_blocking; @@ -1201,7 +1203,7 @@ public: reactor_.start_read_op(impl.socket_, receive_from_handler( - impl.socket_, this->io_service(), buffers, + impl.socket_, this->get_io_service(), buffers, sender_endpoint, flags, handler)); } } @@ -1242,7 +1244,7 @@ public: // Try to complete the operation without blocking. asio::error_code ec; socket_holder new_socket; - socket_addr_len_type addr_len = 0; + std::size_t addr_len = 0; if (peer_endpoint) { addr_len = peer_endpoint->capacity(); @@ -1327,7 +1329,7 @@ public: // Accept the waiting connection. asio::error_code ec; socket_holder new_socket; - socket_addr_len_type addr_len = 0; + std::size_t addr_len = 0; if (peer_endpoint_) { addr_len = peer_endpoint_->capacity(); @@ -1384,12 +1386,12 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor)); } else if (peer.is_open()) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::already_open)); } else @@ -1401,7 +1403,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); return; } impl.flags_ |= implementation_type::internal_non_blocking; @@ -1409,7 +1411,7 @@ public: reactor_.start_read_op(impl.socket_, accept_handler( - impl.socket_, this->io_service(), + impl.socket_, this->get_io_service(), peer, impl.protocol_, peer_endpoint, (impl.flags_ & implementation_type::enable_connection_aborted) != 0, handler)); @@ -1489,7 +1491,7 @@ public: if (connect_error) { ec = asio::error_code(connect_error, - asio::error::system_category); + asio::error::get_system_category()); io_service_.post(bind_handler(handler_, ec)); return true; } @@ -1515,7 +1517,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor)); return; } @@ -1527,7 +1529,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); return; } impl.flags_ |= implementation_type::internal_non_blocking; @@ -1541,7 +1543,7 @@ public: { // The connect operation has finished successfully so we need to post the // handler immediately. - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error_code())); } else if (ec == asio::error::in_progress @@ -1551,13 +1553,13 @@ public: // until the socket becomes writeable. boost::shared_ptr completed(new bool(false)); reactor_.start_write_and_except_ops(impl.socket_, - connect_handler( - impl.socket_, completed, this->io_service(), reactor_, handler)); + connect_handler(impl.socket_, completed, + this->get_io_service(), reactor_, handler)); } else { // The connect operation has failed, so post the handler immediately. - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); } } diff --git a/libtorrent/include/asio/detail/resolver_service.hpp b/libtorrent/include/asio/detail/resolver_service.hpp index c820b75f3..dab6340a5 100644 --- a/libtorrent/include/asio/detail/resolver_service.hpp +++ b/libtorrent/include/asio/detail/resolver_service.hpp @@ -213,7 +213,7 @@ public: start_work_thread(); work_io_service_->post( resolve_query_handler( - impl, query, this->io_service(), handler)); + impl, query, this->get_io_service(), handler)); } } @@ -309,7 +309,7 @@ public: start_work_thread(); work_io_service_->post( resolve_endpoint_handler( - impl, endpoint, this->io_service(), handler)); + impl, endpoint, this->get_io_service(), handler)); } } diff --git a/libtorrent/include/asio/detail/socket_ops.hpp b/libtorrent/include/asio/detail/socket_ops.hpp index 98f3b0f64..ee0b4b582 100644 --- a/libtorrent/include/asio/detail/socket_ops.hpp +++ b/libtorrent/include/asio/detail/socket_ops.hpp @@ -26,9 +26,6 @@ #include #include #include -#if defined(__MACH__) && defined(__APPLE__) -# include -#endif // defined(__MACH__) && defined(__APPLE__) #include "asio/detail/pop_options.hpp" #include "asio/error.hpp" @@ -38,12 +35,24 @@ namespace asio { namespace detail { namespace socket_ops { +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +struct msghdr { int msg_namelen; }; +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#if defined(__hpux) +// HP-UX doesn't declare these functions extern "C", so they are declared again +// here to avoid linker errors about undefined symbols. +extern "C" char* if_indextoname(unsigned int, char*); +extern "C" unsigned int if_nametoindex(const char*); +#endif // defined(__hpux) + inline void clear_error(asio::error_code& ec) { - errno = 0; #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) WSASetLastError(0); -#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) +#else + errno = 0; +#endif ec = asio::error_code(); } @@ -53,22 +62,36 @@ inline ReturnType error_wrapper(ReturnType return_value, { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) ec = asio::error_code(WSAGetLastError(), - asio::error::system_category); + asio::error::get_system_category()); #else - ec = asio::error_code(errno, asio::error::system_category); + ec = asio::error_code(errno, + asio::error::get_system_category()); #endif return return_value; } +template +inline socket_type call_accept(SockLenType msghdr::*, + socket_type s, socket_addr_type* addr, std::size_t* addrlen) +{ + SockLenType tmp_addrlen = addrlen ? (SockLenType)*addrlen : 0; + socket_type result = ::accept(s, addr, addrlen ? &tmp_addrlen : 0); + if (addrlen) + *addrlen = (std::size_t)tmp_addrlen; + return result; +} + inline socket_type accept(socket_type s, socket_addr_type* addr, - socket_addr_len_type* addrlen, asio::error_code& ec) + std::size_t* addrlen, asio::error_code& ec) { clear_error(ec); -#if defined(__MACH__) && defined(__APPLE__) || defined(__FreeBSD__) - socket_type new_s = error_wrapper(::accept(s, addr, addrlen), ec); + + socket_type new_s = error_wrapper(call_accept( + &msghdr::msg_namelen, s, addr, addrlen), ec); if (new_s == invalid_socket) return new_s; +#if defined(__MACH__) && defined(__APPLE__) || defined(__FreeBSD__) int optval = 1; int result = error_wrapper(::setsockopt(new_s, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)), ec); @@ -77,25 +100,45 @@ inline socket_type accept(socket_type s, socket_addr_type* addr, ::close(new_s); return invalid_socket; } +#endif + +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + clear_error(ec); +#endif return new_s; -#else - return error_wrapper(::accept(s, addr, addrlen), ec); -#endif +} + +template +inline int call_bind(SockLenType msghdr::*, + socket_type s, const socket_addr_type* addr, std::size_t addrlen) +{ + return ::bind(s, addr, (SockLenType)addrlen); } inline int bind(socket_type s, const socket_addr_type* addr, - socket_addr_len_type addrlen, asio::error_code& ec) + std::size_t addrlen, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::bind(s, addr, addrlen), ec); + int result = error_wrapper(call_bind( + &msghdr::msg_namelen, s, addr, addrlen), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; } inline int close(socket_type s, asio::error_code& ec) { clear_error(ec); #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) - return error_wrapper(::closesocket(s), ec); + int result = error_wrapper(::closesocket(s), ec); +# if defined(UNDER_CE) + if (result == 0) + clear_error(ec); +# endif + return result; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) return error_wrapper(::close(s), ec); #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) @@ -104,20 +147,43 @@ inline int close(socket_type s, asio::error_code& ec) inline int shutdown(socket_type s, int what, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::shutdown(s, what), ec); + int result = error_wrapper(::shutdown(s, what), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; +} + +template +inline int call_connect(SockLenType msghdr::*, + socket_type s, const socket_addr_type* addr, std::size_t addrlen) +{ + return ::connect(s, addr, (SockLenType)addrlen); } inline int connect(socket_type s, const socket_addr_type* addr, - socket_addr_len_type addrlen, asio::error_code& ec) + std::size_t addrlen, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::connect(s, addr, addrlen), ec); + int result = error_wrapper(call_connect( + &msghdr::msg_namelen, s, addr, addrlen), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; } inline int listen(socket_type s, int backlog, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::listen(s, backlog), ec); + int result = error_wrapper(::listen(s, backlog), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; } #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) @@ -148,6 +214,28 @@ inline void init_buf(buf& b, const void* data, size_t size) #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) } +inline void init_msghdr_msg_name(void*& name, socket_addr_type* addr) +{ + name = addr; +} + +inline void init_msghdr_msg_name(void*& name, const socket_addr_type* addr) +{ + name = const_cast(addr); +} + +template +inline void init_msghdr_msg_name(T& name, socket_addr_type* addr) +{ + name = reinterpret_cast(addr); +} + +template +inline void init_msghdr_msg_name(T& name, const socket_addr_type* addr) +{ + name = reinterpret_cast(const_cast(addr)); +} + inline int recv(socket_type s, buf* bufs, size_t count, int flags, asio::error_code& ec) { @@ -161,22 +249,20 @@ inline int recv(socket_type s, buf* bufs, size_t count, int flags, recv_buf_count, &bytes_transferred, &recv_flags, 0, 0), ec); if (result != 0) return -1; +# if defined(UNDER_CE) + clear_error(ec); +# endif return bytes_transferred; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) - msghdr msg; - msg.msg_name = 0; - msg.msg_namelen = 0; + msghdr msg = msghdr(); msg.msg_iov = bufs; msg.msg_iovlen = count; - msg.msg_control = 0; - msg.msg_controllen = 0; - msg.msg_flags = 0; return error_wrapper(::recvmsg(s, &msg, flags), ec); #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) } inline int recvfrom(socket_type s, buf* bufs, size_t count, int flags, - socket_addr_type* addr, socket_addr_len_type* addrlen, + socket_addr_type* addr, std::size_t* addrlen, asio::error_code& ec) { clear_error(ec); @@ -185,25 +271,22 @@ inline int recvfrom(socket_type s, buf* bufs, size_t count, int flags, DWORD recv_buf_count = static_cast(count); DWORD bytes_transferred = 0; DWORD recv_flags = flags; + int tmp_addrlen = (int)*addrlen; int result = error_wrapper(::WSARecvFrom(s, bufs, recv_buf_count, - &bytes_transferred, &recv_flags, addr, addrlen, 0, 0), ec); + &bytes_transferred, &recv_flags, addr, &tmp_addrlen, 0, 0), ec); + *addrlen = (std::size_t)tmp_addrlen; if (result != 0) return -1; +# if defined(UNDER_CE) + clear_error(ec); +# endif return bytes_transferred; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) - msghdr msg; -#if defined(__MACH__) && defined(__APPLE__) \ - && (MAC_OS_X_VERSION_MAX_ALLOWED < 1040) - msg.msg_name = reinterpret_cast(addr); -#else - msg.msg_name = addr; -#endif + msghdr msg = msghdr(); + init_msghdr_msg_name(msg.msg_name, addr); msg.msg_namelen = *addrlen; msg.msg_iov = bufs; msg.msg_iovlen = count; - msg.msg_control = 0; - msg.msg_controllen = 0; - msg.msg_flags = 0; int result = error_wrapper(::recvmsg(s, &msg, flags), ec); *addrlen = msg.msg_namelen; return result; @@ -223,16 +306,14 @@ inline int send(socket_type s, const buf* bufs, size_t count, int flags, send_buf_count, &bytes_transferred, send_flags, 0, 0), ec); if (result != 0) return -1; +# if defined(UNDER_CE) + clear_error(ec); +# endif return bytes_transferred; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) - msghdr msg; - msg.msg_name = 0; - msg.msg_namelen = 0; + msghdr msg = msghdr(); msg.msg_iov = const_cast(bufs); msg.msg_iovlen = count; - msg.msg_control = 0; - msg.msg_controllen = 0; - msg.msg_flags = 0; #if defined(__linux__) flags |= MSG_NOSIGNAL; #endif // defined(__linux__) @@ -241,7 +322,7 @@ inline int send(socket_type s, const buf* bufs, size_t count, int flags, } inline int sendto(socket_type s, const buf* bufs, size_t count, int flags, - const socket_addr_type* addr, socket_addr_len_type addrlen, + const socket_addr_type* addr, std::size_t addrlen, asio::error_code& ec) { clear_error(ec); @@ -250,24 +331,20 @@ inline int sendto(socket_type s, const buf* bufs, size_t count, int flags, DWORD send_buf_count = static_cast(count); DWORD bytes_transferred = 0; int result = error_wrapper(::WSASendTo(s, const_cast(bufs), - send_buf_count, &bytes_transferred, flags, addr, addrlen, 0, 0), ec); + send_buf_count, &bytes_transferred, flags, addr, + static_cast(addrlen), 0, 0), ec); if (result != 0) return -1; +# if defined(UNDER_CE) + clear_error(ec); +# endif return bytes_transferred; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) - msghdr msg; -#if defined(__MACH__) && defined(__APPLE__) \ - && (MAC_OS_X_VERSION_MAX_ALLOWED < 1040) - msg.msg_name = reinterpret_cast(const_cast(addr)); -#else - msg.msg_name = const_cast(addr); -#endif + msghdr msg = msghdr(); + init_msghdr_msg_name(msg.msg_name, addr); msg.msg_namelen = addrlen; msg.msg_iov = const_cast(bufs); msg.msg_iovlen = count; - msg.msg_control = 0; - msg.msg_controllen = 0; - msg.msg_flags = 0; #if defined(__linux__) flags |= MSG_NOSIGNAL; #endif // defined(__linux__) @@ -295,6 +372,10 @@ inline socket_type socket(int af, int type, int protocol, reinterpret_cast(&optval), sizeof(optval)); } +# if defined(UNDER_CE) + clear_error(ec); +# endif + return s; #elif defined(__MACH__) && defined(__APPLE__) || defined(__FreeBSD__) socket_type s = error_wrapper(::socket(af, type, protocol), ec); @@ -316,8 +397,17 @@ inline socket_type socket(int af, int type, int protocol, #endif } +template +inline int call_setsockopt(SockLenType msghdr::*, + socket_type s, int level, int optname, + const void* optval, std::size_t optlen) +{ + return ::setsockopt(s, level, optname, + (const char*)optval, (SockLenType)optlen); +} + inline int setsockopt(socket_type s, int level, int optname, - const void* optval, size_t optlen, asio::error_code& ec) + const void* optval, std::size_t optlen, asio::error_code& ec) { if (level == custom_socket_option_level && optname == always_fail_option) { @@ -342,15 +432,27 @@ inline int setsockopt(socket_type s, int level, int optname, } ec = asio::error::fault; return -1; -#elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) +#else // defined(__BORLANDC__) clear_error(ec); - return error_wrapper(::setsockopt(s, level, optname, - reinterpret_cast(optval), static_cast(optlen)), ec); -#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) - clear_error(ec); - return error_wrapper(::setsockopt(s, level, optname, optval, - static_cast(optlen)), ec); -#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + int result = error_wrapper(call_setsockopt(&msghdr::msg_namelen, + s, level, optname, optval, optlen), ec); +# if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +# endif + return result; +#endif // defined(__BORLANDC__) +} + +template +inline int call_getsockopt(SockLenType msghdr::*, + socket_type s, int level, int optname, + void* optval, std::size_t* optlen) +{ + SockLenType tmp_optlen = (SockLenType)*optlen; + int result = ::getsockopt(s, level, optname, (char*)optval, &tmp_optlen); + *optlen = (std::size_t)tmp_optlen; + return result; } inline int getsockopt(socket_type s, int level, int optname, void* optval, @@ -394,10 +496,8 @@ inline int getsockopt(socket_type s, int level, int optname, void* optval, return -1; #elif defined(BOOST_WINDOWS) || defined(__CYGWIN__) clear_error(ec); - int tmp_optlen = static_cast(*optlen); - int result = error_wrapper(::getsockopt(s, level, optname, - reinterpret_cast(optval), &tmp_optlen), ec); - *optlen = static_cast(tmp_optlen); + int result = error_wrapper(call_getsockopt(&msghdr::msg_namelen, + s, level, optname, optval, optlen), ec); if (result != 0 && level == IPPROTO_IPV6 && optname == IPV6_V6ONLY && ec.value() == WSAENOPROTOOPT && *optlen == sizeof(DWORD)) { @@ -409,13 +509,15 @@ inline int getsockopt(socket_type s, int level, int optname, void* optval, *static_cast(optval) = 1; clear_error(ec); } +# if defined(UNDER_CE) + if (result == 0) + clear_error(ec); +# endif return result; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) clear_error(ec); - socklen_t tmp_optlen = static_cast(*optlen); - int result = error_wrapper(::getsockopt(s, level, optname, - optval, &tmp_optlen), ec); - *optlen = static_cast(tmp_optlen); + int result = error_wrapper(call_getsockopt(&msghdr::msg_namelen, + s, level, optname, optval, optlen), ec); #if defined(__linux__) if (result == 0 && level == SOL_SOCKET && *optlen == sizeof(int) && (optname == SO_SNDBUF || optname == SO_RCVBUF)) @@ -432,18 +534,50 @@ inline int getsockopt(socket_type s, int level, int optname, void* optval, #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) } +template +inline int call_getpeername(SockLenType msghdr::*, + socket_type s, socket_addr_type* addr, std::size_t* addrlen) +{ + SockLenType tmp_addrlen = (SockLenType)*addrlen; + int result = ::getpeername(s, addr, &tmp_addrlen); + *addrlen = (std::size_t)tmp_addrlen; + return result; +} + inline int getpeername(socket_type s, socket_addr_type* addr, - socket_addr_len_type* addrlen, asio::error_code& ec) + std::size_t* addrlen, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::getpeername(s, addr, addrlen), ec); + int result = error_wrapper(call_getpeername( + &msghdr::msg_namelen, s, addr, addrlen), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; +} + +template +inline int call_getsockname(SockLenType msghdr::*, + socket_type s, socket_addr_type* addr, std::size_t* addrlen) +{ + SockLenType tmp_addrlen = (SockLenType)*addrlen; + int result = ::getsockname(s, addr, &tmp_addrlen); + *addrlen = (std::size_t)tmp_addrlen; + return result; } inline int getsockname(socket_type s, socket_addr_type* addr, - socket_addr_len_type* addrlen, asio::error_code& ec) + std::size_t* addrlen, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::getsockname(s, addr, addrlen), ec); + int result = error_wrapper(call_getsockname( + &msghdr::msg_namelen, s, addr, addrlen), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; } inline int ioctl(socket_type s, long cmd, ioctl_arg_type* arg, @@ -451,7 +585,12 @@ inline int ioctl(socket_type s, long cmd, ioctl_arg_type* arg, { clear_error(ec); #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) - return error_wrapper(::ioctlsocket(s, cmd, arg), ec); + int result = error_wrapper(::ioctlsocket(s, cmd, arg), ec); +# if defined(UNDER_CE) + if (result == 0) + clear_error(ec); +# endif + return result; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) return error_wrapper(::ioctl(s, cmd, arg), ec); #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) @@ -482,8 +621,22 @@ inline int select(int nfds, fd_set* readfds, fd_set* writefds, && timeout->tv_usec > 0 && timeout->tv_usec < 1000) timeout->tv_usec = 1000; #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) - return error_wrapper(::select(nfds, readfds, + +#if defined(__hpux) && defined(__HP_aCC) + timespec ts; + ts.tv_sec = timeout ? timeout->tv_sec : 0; + ts.tv_nsec = timeout ? timeout->tv_usec * 1000 : 0; + return error_wrapper(::pselect(nfds, readfds, + writefds, exceptfds, timeout ? &ts : 0, 0), ec); +#else + int result = error_wrapper(::select(nfds, readfds, writefds, exceptfds, timeout), ec); +# if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result >= 0) + clear_error(ec); +# endif + return result; +#endif } inline int poll_read(socket_type s, asio::error_code& ec) @@ -493,7 +646,12 @@ inline int poll_read(socket_type s, asio::error_code& ec) FD_ZERO(&fds); FD_SET(s, &fds); clear_error(ec); - return error_wrapper(::select(s, &fds, 0, 0, 0), ec); + int result = error_wrapper(::select(s, &fds, 0, 0, 0), ec); +# if defined(UNDER_CE) + if (result >= 0) + clear_error(ec); +# endif + return result; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) pollfd fds; fds.fd = s; @@ -511,7 +669,12 @@ inline int poll_write(socket_type s, asio::error_code& ec) FD_ZERO(&fds); FD_SET(s, &fds); clear_error(ec); - return error_wrapper(::select(s, 0, &fds, 0, 0), ec); + int result = error_wrapper(::select(s, 0, &fds, 0, 0), ec); +# if defined(UNDER_CE) + if (result >= 0) + clear_error(ec); +# endif + return result; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) pollfd fds; fds.fd = s; @@ -559,9 +722,17 @@ inline const char* inet_ntop(int af, const void* src, char* dest, size_t length, } DWORD string_length = static_cast(length); +#if defined(BOOST_NO_ANSI_APIS) + LPWSTR string_buffer = (LPWSTR)_alloca(length * sizeof(WCHAR)); + int result = error_wrapper(::WSAAddressToStringW( + reinterpret_cast(&address), + address_length, 0, string_buffer, &string_length), ec); + ::WideCharToMultiByte(CP_ACP, 0, string_buffer, -1, dest, length, 0, 0); +#else int result = error_wrapper(::WSAAddressToStringA( reinterpret_cast(&address), address_length, 0, dest, &string_length), ec); +#endif // Windows may set error code on success. if (result != socket_error_retval) @@ -605,10 +776,20 @@ inline int inet_pton(int af, const char* src, void* dest, sockaddr_storage_type address; int address_length = sizeof(sockaddr_storage_type); +#if defined(BOOST_NO_ANSI_APIS) + int num_wide_chars = strlen(src) + 1; + LPWSTR wide_buffer = (LPWSTR)_alloca(num_wide_chars * sizeof(WCHAR)); + ::MultiByteToWideChar(CP_ACP, 0, src, -1, wide_buffer, num_wide_chars); + int result = error_wrapper(::WSAStringToAddressW( + wide_buffer, af, 0, + reinterpret_cast(&address), + &address_length), ec); +#else int result = error_wrapper(::WSAStringToAddressA( const_cast(src), af, 0, reinterpret_cast(&address), &address_length), ec); +#endif if (af == AF_INET) { @@ -642,6 +823,11 @@ inline int inet_pton(int af, const char* src, void* dest, if (result == socket_error_retval && !ec) ec = asio::error::invalid_argument; +#if defined(UNDER_CE) + if (result != socket_error_retval) + clear_error(ec); +#endif + return result == socket_error_retval ? -1 : 1; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__) int result = error_wrapper(::inet_pton(af, src, dest), ec); @@ -668,7 +854,12 @@ inline int inet_pton(int af, const char* src, void* dest, inline int gethostname(char* name, int namelen, asio::error_code& ec) { clear_error(ec); - return error_wrapper(::gethostname(name, namelen), ec); + int result = error_wrapper(::gethostname(name, namelen), ec); +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + if (result == 0) + clear_error(ec); +#endif + return result; } #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) \ @@ -707,6 +898,9 @@ inline hostent* gethostbyaddr(const char* addr, int length, int af, hostent* retval = error_wrapper(::gethostbyaddr(addr, length, af), ec); if (!retval) return 0; +# if defined(UNDER_CE) + clear_error(ec); +# endif *result = *retval; return retval; #elif defined(__sun) || defined(__QNX__) @@ -755,6 +949,9 @@ inline hostent* gethostbyname(const char* name, int af, struct hostent* result, hostent* retval = error_wrapper(::gethostbyname(name), ec); if (!retval) return 0; +# if defined(UNDER_CE) + clear_error(ec); +# endif *result = *retval; return result; #elif defined(__sun) || defined(__QNX__) @@ -1373,7 +1570,7 @@ inline int getaddrinfo_emulation(const char* host, const char* service, } inline asio::error_code getnameinfo_emulation( - const socket_addr_type* sa, socket_addr_len_type salen, char* host, + const socket_addr_type* sa, std::size_t salen, char* host, std::size_t hostlen, char* serv, std::size_t servlen, int flags, asio::error_code& ec) { @@ -1526,10 +1723,10 @@ inline asio::error_code translate_addrinfo_error(int error) default: // Possibly the non-portable EAI_SYSTEM. #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) return asio::error_code( - WSAGetLastError(), asio::error::system_category); + WSAGetLastError(), asio::error::get_system_category()); #else return asio::error_code( - errno, asio::error::system_category); + errno, asio::error::get_system_category()); #endif } } @@ -1540,7 +1737,7 @@ inline asio::error_code getaddrinfo(const char* host, { clear_error(ec); #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) -# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) +# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) || defined(UNDER_CE) // Building for Windows XP, Windows Server 2003, or later. int error = ::getaddrinfo(host, service, hints, result); return ec = translate_addrinfo_error(error); @@ -1571,7 +1768,7 @@ inline asio::error_code getaddrinfo(const char* host, inline void freeaddrinfo(addrinfo_type* ai) { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) -# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) +# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) || defined(UNDER_CE) // Building for Windows XP, Windows Server 2003, or later. ::freeaddrinfo(ai); # else @@ -1595,11 +1792,11 @@ inline void freeaddrinfo(addrinfo_type* ai) } inline asio::error_code getnameinfo(const socket_addr_type* addr, - socket_addr_len_type addrlen, char* host, std::size_t hostlen, + std::size_t addrlen, char* host, std::size_t hostlen, char* serv, std::size_t servlen, int flags, asio::error_code& ec) { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) -# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) +# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) || defined(UNDER_CE) // Building for Windows XP, Windows Server 2003, or later. clear_error(ec); int error = ::getnameinfo(addr, addrlen, host, static_cast(hostlen), @@ -1608,7 +1805,7 @@ inline asio::error_code getnameinfo(const socket_addr_type* addr, # else // Building for Windows 2000 or earlier. typedef int (WSAAPI *gni_t)(const socket_addr_type*, - socket_addr_len_type, char*, std::size_t, char*, std::size_t, int); + int, char*, std::size_t, char*, std::size_t, int); if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) { if (gni_t gni = (gni_t)::GetProcAddress(winsock_module, "getnameinfo")) diff --git a/libtorrent/include/asio/detail/socket_select_interrupter.hpp b/libtorrent/include/asio/detail/socket_select_interrupter.hpp index ba62b12d6..988f92e8e 100644 --- a/libtorrent/include/asio/detail/socket_select_interrupter.hpp +++ b/libtorrent/include/asio/detail/socket_select_interrupter.hpp @@ -52,7 +52,7 @@ public: using namespace std; // For memset. sockaddr_in4_type addr; - socket_addr_len_type addr_len = sizeof(addr); + std::size_t addr_len = sizeof(addr); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); diff --git a/libtorrent/include/asio/detail/socket_types.hpp b/libtorrent/include/asio/detail/socket_types.hpp index 02c3a78d5..e2b68910c 100644 --- a/libtorrent/include/asio/detail/socket_types.hpp +++ b/libtorrent/include/asio/detail/socket_types.hpp @@ -29,12 +29,12 @@ # if !defined(_WIN32_WINNT) && !defined(_WIN32_WINDOWS) # if defined(_MSC_VER) || defined(__BORLANDC__) # pragma message("Please define _WIN32_WINNT or _WIN32_WINDOWS appropriately") -# pragma message("Assuming _WIN32_WINNT=0x0500 (i.e. Windows 2000 target)") +# pragma message("Assuming _WIN32_WINNT=0x0501 (i.e. Windows XP target)") # else // defined(_MSC_VER) || defined(__BORLANDC__) # warning Please define _WIN32_WINNT or _WIN32_WINDOWS appropriately -# warning Assuming _WIN32_WINNT=0x0500 (i.e. Windows 2000 target) +# warning Assuming _WIN32_WINNT=0x0501 (i.e. Windows XP target) # endif // defined(_MSC_VER) || defined(__BORLANDC__) -# define _WIN32_WINNT 0x0500 +# define _WIN32_WINNT 0x0501 # endif // !defined(_WIN32_WINNT) && !defined(_WIN32_WINDOWS) # if defined(_MSC_VER) # if defined(_WIN32) && !defined(WIN32) @@ -80,7 +80,9 @@ # undef ASIO_WSPIAPI_H_DEFINED # endif // defined(ASIO_WSPIAPI_H_DEFINED) # if !defined(ASIO_NO_DEFAULT_LINKED_LIBS) -# if defined(_MSC_VER) || defined(__BORLANDC__) +# if defined(UNDER_CE) +# pragma comment(lib, "ws2.lib") +# elif defined(_MSC_VER) || defined(__BORLANDC__) # pragma comment(lib, "ws2_32.lib") # pragma comment(lib, "mswsock.lib") # endif // defined(_MSC_VER) || defined(__BORLANDC__) @@ -116,7 +118,6 @@ const int socket_error_retval = SOCKET_ERROR; const int max_addr_v4_str_len = 256; const int max_addr_v6_str_len = 256; typedef sockaddr socket_addr_type; -typedef int socket_addr_len_type; typedef in_addr in4_addr_type; typedef ip_mreq in4_mreq_type; typedef sockaddr_in sockaddr_in4_type; @@ -154,7 +155,6 @@ const int socket_error_retval = -1; const int max_addr_v4_str_len = INET_ADDRSTRLEN; const int max_addr_v6_str_len = INET6_ADDRSTRLEN + 1 + IF_NAMESIZE; typedef sockaddr socket_addr_type; -typedef socklen_t socket_addr_len_type; typedef in_addr in4_addr_type; typedef ip_mreq in4_mreq_type; typedef sockaddr_in sockaddr_in4_type; diff --git a/libtorrent/include/asio/detail/strand_service.hpp b/libtorrent/include/asio/detail/strand_service.hpp index d987cb98d..598b79dcc 100644 --- a/libtorrent/include/asio/detail/strand_service.hpp +++ b/libtorrent/include/asio/detail/strand_service.hpp @@ -135,9 +135,9 @@ public: handler_base* last_waiter_; // Storage for posted handlers. - typedef boost::aligned_storage<64> handler_storage_type; + typedef boost::aligned_storage<128> handler_storage_type; #if defined(__BORLANDC__) - boost::aligned_storage<64> handler_storage_; + boost::aligned_storage<128> handler_storage_; #else handler_storage_type handler_storage_; #endif @@ -235,7 +235,7 @@ public: void* do_handler_allocate(std::size_t size) { #if defined(__BORLANDC__) - BOOST_ASSERT(size <= boost::aligned_storage<64>::size); + BOOST_ASSERT(size <= boost::aligned_storage<128>::size); #else BOOST_ASSERT(size <= strand_impl::handler_storage_type::size); #endif @@ -276,7 +276,7 @@ public: if (impl_->first_waiter_ == 0) impl_->last_waiter_ = 0; lock.unlock(); - service_impl_.io_service().post( + service_impl_.get_io_service().post( invoke_current_handler(service_impl_, impl_)); } } @@ -429,7 +429,7 @@ public: // This handler now has the lock, so can be dispatched immediately. impl->current_handler_ = ptr.get(); lock.unlock(); - this->io_service().dispatch(invoke_current_handler(*this, impl)); + this->get_io_service().dispatch(invoke_current_handler(*this, impl)); ptr.release(); } else @@ -469,7 +469,7 @@ public: // This handler now has the lock, so can be dispatched immediately. impl->current_handler_ = ptr.get(); lock.unlock(); - this->io_service().post(invoke_current_handler(*this, impl)); + this->get_io_service().post(invoke_current_handler(*this, impl)); ptr.release(); } else diff --git a/libtorrent/include/asio/detail/task_io_service.hpp b/libtorrent/include/asio/detail/task_io_service.hpp index 802d7ea95..ffadb620b 100644 --- a/libtorrent/include/asio/detail/task_io_service.hpp +++ b/libtorrent/include/asio/detail/task_io_service.hpp @@ -23,6 +23,7 @@ #include "asio/detail/event.hpp" #include "asio/detail/handler_alloc_helpers.hpp" #include "asio/detail/handler_invoke_helpers.hpp" +#include "asio/detail/handler_queue.hpp" #include "asio/detail/mutex.hpp" #include "asio/detail/service_base.hpp" #include "asio/detail/task_io_service_fwd.hpp" @@ -42,12 +43,11 @@ public: task_(use_service(io_service)), task_interrupted_(true), outstanding_work_(0), - handler_queue_(&task_handler_), - handler_queue_end_(&task_handler_), stopped_(false), shutdown_(false), first_idle_thread_(0) { + handler_queue_.push(&task_handler_); } void init(size_t /*concurrency_hint*/) @@ -62,17 +62,16 @@ public: lock.unlock(); // Destroy handler objects. - while (handler_queue_) + while (!handler_queue_.empty()) { - handler_base* h = handler_queue_; - handler_queue_ = h->next_; + handler_queue::handler* h = handler_queue_.front(); + handler_queue_.pop(); if (h != &task_handler_) h->destroy(); } // Reset handler queue to initial state. - handler_queue_ = &task_handler_; - handler_queue_end_ = &task_handler_; + handler_queue_.push(&task_handler_); } // Run the event loop until interrupted or no more work. @@ -173,10 +172,7 @@ public: void post(Handler handler) { // Allocate and construct an operation to wrap the handler. - typedef handler_wrapper value_type; - typedef handler_alloc_traits alloc_traits; - raw_handler_ptr raw_ptr(handler); - handler_ptr ptr(raw_ptr, handler); + handler_queue::scoped_ptr ptr(handler_queue::wrap(handler)); asio::detail::mutex::scoped_lock lock(mutex_); @@ -185,15 +181,7 @@ public: return; // Add the handler to the end of the queue. - if (handler_queue_end_) - { - handler_queue_end_->next_ = ptr.get(); - handler_queue_end_ = ptr.get(); - } - else - { - handler_queue_ = handler_queue_end_ = ptr.get(); - } + handler_queue_.push(ptr.get()); ptr.release(); // An undelivered handler is treated as unfinished work. @@ -227,29 +215,28 @@ private: bool task_has_run = false; while (!stopped_) { - if (handler_queue_) + if (!handler_queue_.empty()) { // Prepare to execute first handler from queue. - handler_base* h = handler_queue_; - handler_queue_ = h->next_; - if (handler_queue_ == 0) - handler_queue_end_ = 0; - h->next_ = 0; + handler_queue::handler* h = handler_queue_.front(); + handler_queue_.pop(); if (h == &task_handler_) { - bool more_handlers = (handler_queue_ != 0); + bool more_handlers = (!handler_queue_.empty()); task_interrupted_ = more_handlers || polling; - lock.unlock(); // If the task has already run and we're polling then we're done. if (task_has_run && polling) { + task_interrupted_ = true; + handler_queue_.push(&task_handler_); ec = asio::error_code(); return 0; } task_has_run = true; - + + lock.unlock(); task_cleanup c(lock, *this); // Run the task. May throw an exception. Only block if the handler @@ -263,7 +250,7 @@ private: handler_cleanup c(lock, *this); // Invoke the handler. May throw an exception. - h->call(); // call() deletes the handler object + h->invoke(); // invoke() deletes the handler object ec = asio::error_code(); return 1; @@ -330,94 +317,9 @@ private: } } + // Helper class to perform task-related operations on block exit. class task_cleanup; friend class task_cleanup; - - // The base class for all handler wrappers. A function pointer is used - // instead of virtual functions to avoid the associated overhead. - class handler_base - { - public: - typedef void (*call_func_type)(handler_base*); - typedef void (*destroy_func_type)(handler_base*); - - handler_base(call_func_type call_func, destroy_func_type destroy_func) - : next_(0), - call_func_(call_func), - destroy_func_(destroy_func) - { - } - - void call() - { - call_func_(this); - } - - void destroy() - { - destroy_func_(this); - } - - protected: - // Prevent deletion through this type. - ~handler_base() - { - } - - private: - friend class task_io_service; - friend class task_cleanup; - handler_base* next_; - call_func_type call_func_; - destroy_func_type destroy_func_; - }; - - // Template wrapper for handlers. - template - class handler_wrapper - : public handler_base - { - public: - handler_wrapper(Handler handler) - : handler_base(&handler_wrapper::do_call, - &handler_wrapper::do_destroy), - handler_(handler) - { - } - - static void do_call(handler_base* base) - { - // Take ownership of the handler object. - typedef handler_wrapper this_type; - this_type* h(static_cast(base)); - typedef handler_alloc_traits alloc_traits; - handler_ptr ptr(h->handler_, h); - - // Make a copy of the handler so that the memory can be deallocated before - // the upcall is made. - Handler handler(h->handler_); - - // Free the memory associated with the handler. - ptr.reset(); - - // Make the upcall. - asio_handler_invoke_helpers::invoke(handler, &handler); - } - - static void do_destroy(handler_base* base) - { - // Take ownership of the handler object. - typedef handler_wrapper this_type; - this_type* h(static_cast(base)); - typedef handler_alloc_traits alloc_traits; - handler_ptr ptr(h->handler_, h); - } - - private: - Handler handler_; - }; - - // Helper class to perform task-related operations on block exit. class task_cleanup { public: @@ -433,20 +335,7 @@ private: // Reinsert the task at the end of the handler queue. lock_.lock(); task_io_service_.task_interrupted_ = true; - task_io_service_.task_handler_.next_ = 0; - if (task_io_service_.handler_queue_end_) - { - task_io_service_.handler_queue_end_->next_ - = &task_io_service_.task_handler_; - task_io_service_.handler_queue_end_ - = &task_io_service_.task_handler_; - } - else - { - task_io_service_.handler_queue_ - = task_io_service_.handler_queue_end_ - = &task_io_service_.task_handler_; - } + task_io_service_.handler_queue_.push(&task_io_service_.task_handler_); } private: @@ -487,11 +376,11 @@ private: // Handler object to represent the position of the task in the queue. class task_handler - : public handler_base + : public handler_queue::handler { public: task_handler() - : handler_base(0, 0) + : handler_queue::handler(0, 0) { } } task_handler_; @@ -502,11 +391,8 @@ private: // The count of unfinished work. int outstanding_work_; - // The start of a linked list of handlers that are ready to be delivered. - handler_base* handler_queue_; - - // The end of a linked list of handlers that are ready to be delivered. - handler_base* handler_queue_end_; + // The queue of handlers that are ready to be delivered. + handler_queue handler_queue_; // Flag to indicate that the dispatcher has been stopped. bool stopped_; diff --git a/libtorrent/include/asio/detail/thread.hpp b/libtorrent/include/asio/detail/thread.hpp index b334c38af..520477e37 100644 --- a/libtorrent/include/asio/detail/thread.hpp +++ b/libtorrent/include/asio/detail/thread.hpp @@ -24,7 +24,11 @@ #if !defined(BOOST_HAS_THREADS) # include "asio/detail/null_thread.hpp" #elif defined(BOOST_WINDOWS) -# include "asio/detail/win_thread.hpp" +# if defined(UNDER_CE) +# include "asio/detail/wince_thread.hpp" +# else +# include "asio/detail/win_thread.hpp" +# endif #elif defined(BOOST_HAS_PTHREADS) # include "asio/detail/posix_thread.hpp" #else @@ -37,7 +41,11 @@ namespace detail { #if !defined(BOOST_HAS_THREADS) typedef null_thread thread; #elif defined(BOOST_WINDOWS) +# if defined(UNDER_CE) +typedef wince_thread thread; +# else typedef win_thread thread; +# endif #elif defined(BOOST_HAS_PTHREADS) typedef posix_thread thread; #endif diff --git a/libtorrent/include/asio/detail/timer_queue.hpp b/libtorrent/include/asio/detail/timer_queue.hpp index 7735e87cf..472cc3759 100644 --- a/libtorrent/include/asio/detail/timer_queue.hpp +++ b/libtorrent/include/asio/detail/timer_queue.hpp @@ -162,13 +162,7 @@ public: // Destroy timers that are waiting to be cleaned up. virtual void cleanup_timers() { - while (cleanup_timers_) - { - timer_base* next_timer = cleanup_timers_->next_; - cleanup_timers_->next_ = 0; - cleanup_timers_->destroy(); - cleanup_timers_ = next_timer; - } + destroy_timer_list(cleanup_timers_); } // Destroy all timers. @@ -181,11 +175,12 @@ public: timer_base* t = i->second; typename hash_map::iterator old_i = i++; timers_.erase(old_i); - t->destroy(); + destroy_timer_list(t); } heap_.clear(); timers_.clear(); - cleanup_timers(); + destroy_timer_list(cancelled_timers_); + destroy_timer_list(cleanup_timers_); } private: @@ -367,6 +362,18 @@ private: } } + // Destroy all timers in a linked list. + void destroy_timer_list(timer_base*& t) + { + while (t) + { + timer_base* next = t->next_; + t->next_ = 0; + t->destroy(); + t = next; + } + } + // A hash of timer token to linked lists of timers. hash_map timers_; diff --git a/libtorrent/include/asio/detail/win_event.hpp b/libtorrent/include/asio/detail/win_event.hpp index c73ed56ea..ff97fbedc 100644 --- a/libtorrent/include/asio/detail/win_event.hpp +++ b/libtorrent/include/asio/detail/win_event.hpp @@ -49,7 +49,7 @@ public: DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "event"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_iocp_io_service.hpp b/libtorrent/include/asio/detail/win_iocp_io_service.hpp index 61eeb1745..46e651653 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service.hpp @@ -33,7 +33,9 @@ #include "asio/detail/handler_invoke_helpers.hpp" #include "asio/detail/service_base.hpp" #include "asio/detail/socket_types.hpp" +#include "asio/detail/timer_queue.hpp" #include "asio/detail/win_iocp_operation.hpp" +#include "asio/detail/mutex.hpp" namespace asio { namespace detail { @@ -51,7 +53,9 @@ public: iocp_(), outstanding_work_(0), stopped_(0), - shutdown_(0) + shutdown_(0), + timer_thread_(0), + timer_interrupt_issued_(false) { } @@ -64,7 +68,7 @@ public: DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "iocp"); boost::throw_exception(e); } @@ -93,6 +97,10 @@ public: if (overlapped) static_cast(overlapped)->destroy(); } + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + timer_queues_[i]->destroy_timers(); + timer_queues_.clear(); } // Register a handle with the IO completion port. @@ -175,7 +183,7 @@ public: DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "pqcs"); boost::throw_exception(e); } @@ -231,7 +239,7 @@ public: DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "pqcs"); boost::throw_exception(e); } @@ -251,20 +259,102 @@ public: DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "pqcs"); boost::throw_exception(e); } } + // Add a new timer queue to the service. + template + void add_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(timer_mutex_); + timer_queues_.push_back(&timer_queue); + } + + // Remove a timer queue from the service. + template + void remove_timer_queue(timer_queue& timer_queue) + { + asio::detail::mutex::scoped_lock lock(timer_mutex_); + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + if (timer_queues_[i] == &timer_queue) + { + timer_queues_.erase(timer_queues_.begin() + i); + return; + } + } + } + + // Schedule a timer in the given timer queue to expire at the specified + // absolute time. The handler object will be invoked when the timer expires. + template + void schedule_timer(timer_queue& timer_queue, + const typename Time_Traits::time_type& time, Handler handler, void* token) + { + // If the service has been shut down we silently discard the timer. + if (::InterlockedExchangeAdd(&shutdown_, 0) != 0) + return; + + asio::detail::mutex::scoped_lock lock(timer_mutex_); + if (timer_queue.enqueue_timer(time, handler, token)) + { + if (!timer_interrupt_issued_) + { + timer_interrupt_issued_ = true; + lock.unlock(); + ::PostQueuedCompletionStatus(iocp_.handle, + 0, steal_timer_dispatching, 0); + } + } + } + + // Cancel the timer associated with the given token. Returns the number of + // handlers that have been posted or dispatched. + template + std::size_t cancel_timer(timer_queue& timer_queue, void* token) + { + // If the service has been shut down we silently ignore the cancellation. + if (::InterlockedExchangeAdd(&shutdown_, 0) != 0) + return 0; + + asio::detail::mutex::scoped_lock lock(timer_mutex_); + std::size_t n = timer_queue.cancel_timer(token); + if (n > 0 && !timer_interrupt_issued_) + { + timer_interrupt_issued_ = true; + lock.unlock(); + ::PostQueuedCompletionStatus(iocp_.handle, + 0, steal_timer_dispatching, 0); + } + return n; + } + private: // Dequeues at most one operation from the I/O completion port, and then // executes it. Returns the number of operations that were dequeued (i.e. // either 0 or 1). size_t do_one(bool block, asio::error_code& ec) { + long this_thread_id = static_cast(::GetCurrentThreadId()); + for (;;) { + // Try to acquire responsibility for dispatching timers. + bool dispatching_timers = (::InterlockedCompareExchange( + &timer_thread_, this_thread_id, 0) == 0); + + // Calculate timeout for GetQueuedCompletionStatus call. + DWORD timeout = max_timeout; + if (dispatching_timers) + { + asio::detail::mutex::scoped_lock lock(timer_mutex_); + timer_interrupt_issued_ = false; + timeout = get_timeout(); + } + // Get the next operation from the queue. DWORD bytes_transferred = 0; #if (WINVER < 0x0500) @@ -275,18 +365,47 @@ private: LPOVERLAPPED overlapped = 0; ::SetLastError(0); BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, &bytes_transferred, - &completion_key, &overlapped, block ? 1000 : 0); + &completion_key, &overlapped, block ? timeout : 0); DWORD last_error = ::GetLastError(); + // Dispatch any pending timers. + if (dispatching_timers) + { + asio::detail::mutex::scoped_lock lock(timer_mutex_); + timer_queues_copy_ = timer_queues_; + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + timer_queues_[i]->cleanup_timers(); + } + } + if (!ok && overlapped == 0) { if (block && last_error == WAIT_TIMEOUT) + { + // Relinquish responsibility for dispatching timers. + if (dispatching_timers) + { + ::InterlockedCompareExchange(&timer_thread_, 0, this_thread_id); + } + continue; + } + + // Transfer responsibility for dispatching timers to another thread. + if (dispatching_timers && ::InterlockedCompareExchange( + &timer_thread_, 0, this_thread_id) == this_thread_id) + { + ::PostQueuedCompletionStatus(iocp_.handle, + 0, transfer_timer_dispatching, 0); + } + ec = asio::error_code(); return 0; } - - if (overlapped) + else if (overlapped) { // We may have been passed a last_error value in the completion_key. if (last_error == 0) @@ -294,6 +413,14 @@ private: last_error = completion_key; } + // Transfer responsibility for dispatching timers to another thread. + if (dispatching_timers && ::InterlockedCompareExchange( + &timer_thread_, 0, this_thread_id) == this_thread_id) + { + ::PostQueuedCompletionStatus(iocp_.handle, + 0, transfer_timer_dispatching, 0); + } + // Ensure that the io_service does not exit due to running out of work // while we make the upcall. auto_work work(*this); @@ -305,18 +432,34 @@ private: ec = asio::error_code(); return 1; } + else if (completion_key == transfer_timer_dispatching) + { + // Woken up to try to acquire responsibility for dispatching timers. + ::InterlockedCompareExchange(&timer_thread_, 0, this_thread_id); + } + else if (completion_key == steal_timer_dispatching) + { + // Woken up to steal responsibility for dispatching timers. + ::InterlockedExchange(&timer_thread_, 0); + } else { // The stopped_ flag is always checked to ensure that any leftover // interrupts from a previous run invocation are ignored. if (::InterlockedExchangeAdd(&stopped_, 0) != 0) { + // Relinquish responsibility for dispatching timers. + if (dispatching_timers) + { + ::InterlockedCompareExchange(&timer_thread_, 0, this_thread_id); + } + // Wake up next thread that is blocked on GetQueuedCompletionStatus. if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) { DWORD last_error = ::GetLastError(); ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); return 0; } @@ -327,6 +470,45 @@ private: } } + // Check if all timer queues are empty. + bool all_timer_queues_are_empty() const + { + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + if (!timer_queues_[i]->empty()) + return false; + return true; + } + + // Get the timeout value for the GetQueuedCompletionStatus call. The timeout + // value is returned as a number of milliseconds. We will wait no longer than + // 1000 milliseconds. + DWORD get_timeout() + { + if (all_timer_queues_are_empty()) + return max_timeout; + + boost::posix_time::time_duration minimum_wait_duration + = boost::posix_time::milliseconds(max_timeout); + + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + boost::posix_time::time_duration wait_duration + = timer_queues_[i]->wait_duration(); + if (wait_duration < minimum_wait_duration) + minimum_wait_duration = wait_duration; + } + + if (minimum_wait_duration > boost::posix_time::time_duration()) + { + int milliseconds = minimum_wait_duration.total_milliseconds(); + return static_cast(milliseconds > 0 ? milliseconds : 1); + } + else + { + return 0; + } + } + struct auto_work { auto_work(win_iocp_io_service& io_service) @@ -416,6 +598,37 @@ private: // Flag to indicate whether the service has been shut down. long shutdown_; + + enum + { + // Maximum GetQueuedCompletionStatus timeout, in milliseconds. + max_timeout = 1000, + + // Completion key value to indicate that responsibility for dispatching + // timers is being cooperatively transferred from one thread to another. + transfer_timer_dispatching = 1, + + // Completion key value to indicate that responsibility for dispatching + // timers should be stolen from another thread. + steal_timer_dispatching = 2 + }; + + // The thread that's currently in charge of dispatching timers. + long timer_thread_; + + // Mutex for protecting access to the timer queues. + mutex timer_mutex_; + + // Whether a thread has been interrupted to process a new timeout. + bool timer_interrupt_issued_; + + // The timer queues. + std::vector timer_queues_; + + // A copy of the timer queues, used when dispatching, cancelling and cleaning + // up timers. The copy is stored as a class data member to avoid unnecessary + // memory allocation. + std::vector timer_queues_copy_; }; } // namespace detail diff --git a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp index 26eacae2a..b3d1dec34 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service_fwd.hpp @@ -27,6 +27,7 @@ #if !defined(ASIO_DISABLE_IOCP) #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) #if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400) +#if !defined(UNDER_CE) // Define this to indicate that IOCP is supported on the target platform. #define ASIO_HAS_IOCP 1 @@ -39,6 +40,7 @@ class win_iocp_io_service; } // namespace detail } // namespace asio +#endif // !defined(UNDER_CE) #endif // defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400) #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) #endif // !defined(ASIO_DISABLE_IOCP) diff --git a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp index 17d1d5887..cb3640638 100644 --- a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp @@ -327,7 +327,7 @@ public: ec = asio::error::bad_descriptor; } else if (FARPROC cancel_io_ex_ptr = ::GetProcAddress( - ::GetModuleHandle("KERNEL32"), "CancelIoEx")) + ::GetModuleHandleA("KERNEL32"), "CancelIoEx")) { // The version of Windows supports cancellation from any thread. typedef BOOL (WINAPI* cancel_io_ex_t)(HANDLE, LPOVERLAPPED); @@ -338,7 +338,7 @@ public: { DWORD last_error = ::GetLastError(); ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); } else { @@ -360,7 +360,7 @@ public: { DWORD last_error = ::GetLastError(); ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); } else { @@ -561,7 +561,7 @@ public: } endpoint_type endpoint; - socket_addr_len_type addr_len = endpoint.capacity(); + std::size_t addr_len = endpoint.capacity(); if (socket_ops::getsockname(impl.socket_, endpoint.data(), &addr_len, ec)) return endpoint_type(); endpoint.resize(addr_len); @@ -600,7 +600,7 @@ public: else { endpoint_type endpoint; - socket_addr_len_type addr_len = endpoint.capacity(); + std::size_t addr_len = endpoint.capacity(); if (socket_ops::getpeername(impl.socket_, endpoint.data(), &addr_len, ec)) return endpoint_type(); endpoint.resize(addr_len); @@ -667,7 +667,7 @@ public: else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); return 0; } @@ -719,7 +719,7 @@ public: // Map non-portable errors to their portable counterparts. asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -767,7 +767,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); return; } @@ -783,7 +783,7 @@ public: typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, - this->io_service(), impl.cancel_token_, buffers, handler); + this->get_io_service(), impl.cancel_token_, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -803,7 +803,7 @@ public: // A request to receive 0 bytes on a stream socket is a no-op. if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code error; iocp_service_.post(bind_handler(handler, error, 0)); @@ -819,10 +819,10 @@ public: // Check if the operation completed immediately. if (result != 0 && last_error != WSA_IO_PENDING) { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -867,7 +867,7 @@ public: if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); return 0; } @@ -917,7 +917,7 @@ public: // Map non-portable errors to their portable counterparts. asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -958,7 +958,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); return; } @@ -974,7 +974,7 @@ public: typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, - this->io_service(), buffers, handler); + this->get_io_service(), buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -998,10 +998,10 @@ public: // Check if the operation completed immediately. if (result != 0 && last_error != WSA_IO_PENDING) { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1056,7 +1056,7 @@ public: else if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); return 0; } if (bytes_transferred == 0) @@ -1115,7 +1115,7 @@ public: // Map non-portable errors to their portable counterparts. asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); if (ec.value() == ERROR_NETNAME_DELETED) { if (handler_op->cancel_token_.expired()) @@ -1170,7 +1170,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); return; } @@ -1186,7 +1186,7 @@ public: typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, - this->io_service(), impl.cancel_token_, buffers, handler); + this->get_io_service(), impl.cancel_token_, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -1205,7 +1205,7 @@ public: // A request to receive 0 bytes on a stream socket is a no-op. if (impl.protocol_.type() == SOCK_STREAM && total_buffer_size == 0) { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code error; iocp_service_.post(bind_handler(handler, error, 0)); @@ -1220,10 +1220,10 @@ public: DWORD last_error = ::WSAGetLastError(); if (result != 0 && last_error != WSA_IO_PENDING) { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1261,7 +1261,7 @@ public: // Receive some data. DWORD bytes_transferred = 0; DWORD recv_flags = flags; - int endpoint_size = sender_endpoint.capacity(); + int endpoint_size = static_cast(sender_endpoint.capacity()); int result = ::WSARecvFrom(impl.socket_, bufs, i, &bytes_transferred, &recv_flags, sender_endpoint.data(), &endpoint_size, 0, 0); if (result != 0) @@ -1270,7 +1270,7 @@ public: if (last_error == ERROR_PORT_UNREACHABLE) last_error = WSAECONNREFUSED; ec = asio::error_code(last_error, - asio::error::system_category); + asio::error::get_system_category()); return 0; } if (bytes_transferred == 0) @@ -1279,7 +1279,7 @@ public: return 0; } - sender_endpoint.resize(endpoint_size); + sender_endpoint.resize(static_cast(endpoint_size)); ec = asio::error_code(); return bytes_transferred; @@ -1299,7 +1299,7 @@ public: &receive_from_operation< MutableBufferSequence, Handler>::destroy_impl), endpoint_(endpoint), - endpoint_size_(endpoint.capacity()), + endpoint_size_(static_cast(endpoint.capacity())), work_(io_service), buffers_(buffers), handler_(handler) @@ -1337,7 +1337,7 @@ public: // Map non-portable errors to their portable counterparts. asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); if (ec.value() == ERROR_PORT_UNREACHABLE) { ec = asio::error::connection_refused; @@ -1390,7 +1390,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor, 0)); return; } @@ -1406,7 +1406,7 @@ public: typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); handler_ptr ptr(raw_ptr, - this->io_service(), sender_endp, buffers, handler); + this->get_io_service(), sender_endp, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -1429,10 +1429,10 @@ public: DWORD last_error = ::WSAGetLastError(); if (result != 0 && last_error != WSA_IO_PENDING) { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); iocp_service_.post(bind_handler(handler, ec, bytes_transferred)); } else @@ -1463,7 +1463,7 @@ public: { asio::error_code ec; socket_holder new_socket; - socket_addr_len_type addr_len = 0; + std::size_t addr_len = 0; if (peer_endpoint) { addr_len = peer_endpoint->capacity(); @@ -1517,7 +1517,7 @@ public: peer_(peer), protocol_(protocol), peer_endpoint_(peer_endpoint), - work_(io_service.io_service()), + work_(io_service.get_io_service()), enable_connection_aborted_(enable_connection_aborted), handler_(handler) { @@ -1618,7 +1618,8 @@ public: GetAcceptExSockaddrs(handler_op->output_buffer(), 0, handler_op->address_length(), handler_op->address_length(), &local_addr, &local_addr_length, &remote_addr, &remote_addr_length); - if (remote_addr_length > peer_endpoint.capacity()) + if (static_cast(remote_addr_length) + > peer_endpoint.capacity()) { last_error = WSAEINVAL; } @@ -1626,7 +1627,7 @@ public: { using namespace std; // For memcpy. memcpy(peer_endpoint.data(), remote_addr, remote_addr_length); - peer_endpoint.resize(remote_addr_length); + peer_endpoint.resize(static_cast(remote_addr_length)); } } @@ -1670,7 +1671,7 @@ public: // Call the handler. asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); asio_handler_invoke_helpers::invoke( detail::bind_handler(handler, ec), &handler); } @@ -1705,7 +1706,7 @@ public: // Check whether acceptor has been initialised. if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor)); return; } @@ -1713,7 +1714,7 @@ public: // Check that peer socket has not already been opened. if (peer.is_open()) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::already_open)); return; } @@ -1730,7 +1731,7 @@ public: impl.protocol_.type(), impl.protocol_.protocol(), ec)); if (sock.get() == invalid_socket) { - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); return; } @@ -1768,10 +1769,10 @@ public: } else { - asio::io_service::work work(this->io_service()); + asio::io_service::work work(this->get_io_service()); ptr.reset(); asio::error_code ec(last_error, - asio::error::system_category); + asio::error::get_system_category()); iocp_service_.post(bind_handler(handler, ec)); } } @@ -1848,7 +1849,7 @@ public: if (connect_error) { ec = asio::error_code(connect_error, - asio::error::system_category); + asio::error::get_system_category()); io_service_.post(bind_handler(handler_, ec)); return true; } @@ -1887,7 +1888,7 @@ public: { if (!is_open(impl)) { - this->io_service().post(bind_handler(handler, + this->get_io_service().post(bind_handler(handler, asio::error::bad_descriptor)); return; } @@ -1904,7 +1905,8 @@ public: reinterpret_cast(&reactor_), 0, 0)); if (!reactor) { - reactor = &(asio::use_service(this->io_service())); + reactor = &(asio::use_service( + this->get_io_service())); interlocked_exchange_pointer( reinterpret_cast(&reactor_), reactor); } @@ -1915,7 +1917,7 @@ public: asio::error_code ec; if (socket_ops::ioctl(impl.socket_, FIONBIO, &non_blocking, ec)) { - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); return; } @@ -1932,7 +1934,7 @@ public: // The connect operation has finished successfully so we need to post the // handler immediately. - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); } else if (ec == asio::error::in_progress || ec == asio::error::would_block) @@ -1944,7 +1946,7 @@ public: connect_handler( impl.socket_, (impl.flags_ & implementation_type::user_set_non_blocking) != 0, - completed, this->io_service(), *reactor, handler)); + completed, this->get_io_service(), *reactor, handler)); } else { @@ -1957,7 +1959,7 @@ public: } // The connect operation has failed, so post the handler immediately. - this->io_service().post(bind_handler(handler, ec)); + this->get_io_service().post(bind_handler(handler, ec)); } } diff --git a/libtorrent/include/asio/detail/win_local_free_on_block_exit.hpp b/libtorrent/include/asio/detail/win_local_free_on_block_exit.hpp new file mode 100644 index 000000000..c909e1af3 --- /dev/null +++ b/libtorrent/include/asio/detail/win_local_free_on_block_exit.hpp @@ -0,0 +1,59 @@ +// +// win_local_free_on_block_exit.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WIN_LOCAL_FREE_ON_BLOCK_EXIT_HPP +#define ASIO_DETAIL_WIN_LOCAL_FREE_ON_BLOCK_EXIT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +namespace asio { +namespace detail { + +class win_local_free_on_block_exit + : private noncopyable +{ +public: + // Constructor blocks all signals for the calling thread. + explicit win_local_free_on_block_exit(void* p) + : p_(p) + { + } + + // Destructor restores the previous signal mask. + ~win_local_free_on_block_exit() + { + ::LocalFree(p_); + } + +private: + void* p_; +}; + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WIN_LOCAL_FREE_ON_BLOCK_EXIT_HPP diff --git a/libtorrent/include/asio/detail/win_mutex.hpp b/libtorrent/include/asio/detail/win_mutex.hpp index 4d1bc20c2..f937db69f 100644 --- a/libtorrent/include/asio/detail/win_mutex.hpp +++ b/libtorrent/include/asio/detail/win_mutex.hpp @@ -49,7 +49,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "mutex"); boost::throw_exception(e); } @@ -68,7 +69,8 @@ public: if (error != 0) { asio::system_error e( - asio::error_code(error, asio::error::system_category), + asio::error_code(error, + asio::error::get_system_category()), "mutex"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/win_thread.hpp b/libtorrent/include/asio/detail/win_thread.hpp index c6bd61af5..4a80d15a3 100644 --- a/libtorrent/include/asio/detail/win_thread.hpp +++ b/libtorrent/include/asio/detail/win_thread.hpp @@ -21,7 +21,7 @@ #include #include "asio/detail/pop_options.hpp" -#if defined(BOOST_WINDOWS) +#if defined(BOOST_WINDOWS) && !defined(UNDER_CE) #include "asio/error.hpp" #include "asio/system_error.hpp" @@ -56,7 +56,7 @@ public: DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "thread"); boost::throw_exception(e); } @@ -118,7 +118,7 @@ inline unsigned int __stdcall win_thread_function(void* arg) } // namespace detail } // namespace asio -#endif // defined(BOOST_WINDOWS) +#endif // defined(BOOST_WINDOWS) && !defined(UNDER_CE) #include "asio/detail/pop_options.hpp" diff --git a/libtorrent/include/asio/detail/win_tss_ptr.hpp b/libtorrent/include/asio/detail/win_tss_ptr.hpp index d84810d41..8f6eaf6cb 100644 --- a/libtorrent/include/asio/detail/win_tss_ptr.hpp +++ b/libtorrent/include/asio/detail/win_tss_ptr.hpp @@ -40,16 +40,22 @@ class win_tss_ptr : private noncopyable { public: +#if defined(UNDER_CE) + enum { out_of_indexes = 0xFFFFFFFF }; +#else + enum { out_of_indexes = TLS_OUT_OF_INDEXES }; +#endif + // Constructor. win_tss_ptr() { tss_key_ = ::TlsAlloc(); - if (tss_key_ == TLS_OUT_OF_INDEXES) + if (tss_key_ == out_of_indexes) { DWORD last_error = ::GetLastError(); asio::system_error e( asio::error_code(last_error, - asio::error::system_category), + asio::error::get_system_category()), "tss"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/detail/wince_thread.hpp b/libtorrent/include/asio/detail/wince_thread.hpp new file mode 100644 index 000000000..f65a36f0c --- /dev/null +++ b/libtorrent/include/asio/detail/wince_thread.hpp @@ -0,0 +1,124 @@ +// +// wince_thread.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2007 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_DETAIL_WINCE_THREAD_HPP +#define ASIO_DETAIL_WINCE_THREAD_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + +#include "asio/error.hpp" +#include "asio/system_error.hpp" +#include "asio/detail/noncopyable.hpp" +#include "asio/detail/socket_types.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { +namespace detail { + +DWORD WINAPI wince_thread_function(LPVOID arg); + +class wince_thread + : private noncopyable +{ +public: + // Constructor. + template + wince_thread(Function f) + { + std::auto_ptr arg(new func(f)); + DWORD thread_id = 0; + thread_ = ::CreateThread(0, 0, wince_thread_function, + arg.get(), 0, &thread_id); + if (!thread_) + { + DWORD last_error = ::GetLastError(); + asio::system_error e( + asio::error_code(last_error, + asio::error::get_system_category()), + "thread"); + boost::throw_exception(e); + } + arg.release(); + } + + // Destructor. + ~wince_thread() + { + ::CloseHandle(thread_); + } + + // Wait for the thread to exit. + void join() + { + ::WaitForSingleObject(thread_, INFINITE); + } + +private: + friend DWORD WINAPI wince_thread_function(LPVOID arg); + + class func_base + { + public: + virtual ~func_base() {} + virtual void run() = 0; + }; + + template + class func + : public func_base + { + public: + func(Function f) + : f_(f) + { + } + + virtual void run() + { + f_(); + } + + private: + Function f_; + }; + + ::HANDLE thread_; +}; + +inline DWORD WINAPI wince_thread_function(LPVOID arg) +{ + std::auto_ptr func( + static_cast(arg)); + func->run(); + return 0; +} + +} // namespace detail +} // namespace asio + +#endif // defined(BOOST_WINDOWS) && defined(UNDER_CE) + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_DETAIL_WINCE_THREAD_HPP diff --git a/libtorrent/include/asio/detail/winsock_init.hpp b/libtorrent/include/asio/detail/winsock_init.hpp index 874d2b77b..65efb8852 100644 --- a/libtorrent/include/asio/detail/winsock_init.hpp +++ b/libtorrent/include/asio/detail/winsock_init.hpp @@ -28,6 +28,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/error.hpp" #include "asio/system_error.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/socket_types.hpp" @@ -86,7 +87,7 @@ public: { asio::system_error e( asio::error_code(ref_->result(), - asio::error::system_category), + asio::error::get_system_category()), "winsock"); boost::throw_exception(e); } diff --git a/libtorrent/include/asio/error.hpp b/libtorrent/include/asio/error.hpp index a8316be2c..c65599c91 100644 --- a/libtorrent/include/asio/error.hpp +++ b/libtorrent/include/asio/error.hpp @@ -197,26 +197,40 @@ enum misc_errors not_found }; +enum ssl_errors +{ +}; + // boostify: error category definitions go here. inline asio::error_code make_error_code(basic_errors e) { - return asio::error_code(static_cast(e), system_category); + return asio::error_code( + static_cast(e), get_system_category()); } inline asio::error_code make_error_code(netdb_errors e) { - return asio::error_code(static_cast(e), netdb_category); + return asio::error_code( + static_cast(e), get_netdb_category()); } inline asio::error_code make_error_code(addrinfo_errors e) { - return asio::error_code(static_cast(e), addrinfo_category); + return asio::error_code( + static_cast(e), get_addrinfo_category()); } inline asio::error_code make_error_code(misc_errors e) { - return asio::error_code(static_cast(e), misc_category); + return asio::error_code( + static_cast(e), get_misc_category()); +} + +inline asio::error_code make_error_code(ssl_errors e) +{ + return asio::error_code( + static_cast(e), get_ssl_category()); } } // namespace error diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp index 0941a8c00..989898ce5 100644 --- a/libtorrent/include/asio/error_code.hpp +++ b/libtorrent/include/asio/error_code.hpp @@ -41,10 +41,10 @@ namespace error system_category = ASIO_WIN_OR_POSIX(0, 0), /// Error codes from NetDB functions. - netdb_category = ASIO_WIN_OR_POSIX(system_category, 1), + netdb_category = ASIO_WIN_OR_POSIX(_system_category, 1), /// Error codes from getaddrinfo. - addrinfo_category = ASIO_WIN_OR_POSIX(system_category, 2), + addrinfo_category = ASIO_WIN_OR_POSIX(_system_category, 2), /// Miscellaneous error codes. misc_category = ASIO_WIN_OR_POSIX(3, 3), @@ -52,8 +52,19 @@ namespace error /// SSL error codes. ssl_category = ASIO_WIN_OR_POSIX(4, 4) }; + + // Category getters. + inline error_category get_system_category() { return system_category; } + inline error_category get_netdb_category() { return netdb_category; } + inline error_category get_addrinfo_category() { return addrinfo_category; } + inline error_category get_misc_category() { return misc_category; } + inline error_category get_ssl_category() { return ssl_category; } + } // namespace error +/// Bring error category type into the asio namespace. +typedef asio::error::error_category error_category; + /// Class to represent an error code value. class error_code { @@ -69,7 +80,7 @@ public: } /// Construct with specific error code and category. - error_code(value_type v, error::error_category c) + error_code(value_type v, error_category c) : value_(v), category_(c) { @@ -89,7 +100,7 @@ public: } /// Get the error category. - error::error_category category() const + error_category category() const { return category_; } @@ -135,7 +146,7 @@ private: value_type value_; // The category associated with the error code. - error::error_category category_; + error_category category_; }; } // namespace asio diff --git a/libtorrent/include/asio/error_handler.hpp b/libtorrent/include/asio/error_handler.hpp new file mode 100644 index 000000000..c315c8d5e --- /dev/null +++ b/libtorrent/include/asio/error_handler.hpp @@ -0,0 +1,120 @@ +// +// error_handler.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ERROR_HANDLER_HPP +#define ASIO_ERROR_HANDLER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include "asio/detail/pop_options.hpp" + +namespace asio { + +namespace detail { + +class ignore_error_t +{ +public: + typedef void result_type; + + template + void operator()(const Error&) const + { + } +}; + +class throw_error_t +{ +public: + typedef void result_type; + + template + void operator()(const Error& err) const + { + if (err) + boost::throw_exception(err); + } +}; + +template +class assign_error_t +{ +public: + typedef void result_type; + + assign_error_t(Target& target) + : target_(&target) + { + } + + template + void operator()(const Error& err) const + { + *target_ = err; + } + +private: + Target* target_; +}; + +} // namespace detail + +/** + * @defgroup error_handler Error Handler Function Objects + * + * Function objects for custom error handling. + */ +/*@{*/ + +/// Return a function object that always ignores the error. +#if defined(GENERATING_DOCUMENTATION) +unspecified ignore_error(); +#else +inline detail::ignore_error_t ignore_error() +{ + return detail::ignore_error_t(); +} +#endif + +/// Return a function object that always throws the error. +#if defined(GENERATING_DOCUMENTATION) +unspecified throw_error(); +#else +inline detail::throw_error_t throw_error() +{ + return detail::throw_error_t(); +} +#endif + +/// Return a function object that assigns the error to a variable. +#if defined(GENERATING_DOCUMENTATION) +template +unspecified assign_error(Target& target); +#else +template +inline detail::assign_error_t assign_error(Target& target) +{ + return detail::assign_error_t(target); +} +#endif + +/*@}*/ + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ERROR_HANDLER_HPP diff --git a/libtorrent/include/asio/impl/error_code.ipp b/libtorrent/include/asio/impl/error_code.ipp index f66b6fd94..9925cb484 100644 --- a/libtorrent/include/asio/impl/error_code.ipp +++ b/libtorrent/include/asio/impl/error_code.ipp @@ -35,11 +35,11 @@ inline std::string error_code::message() const return "Already open."; if (*this == error::not_found) return "Not found."; - if (category_ == error::ssl_category) + if (category_ == error::get_ssl_category()) return "SSL error."; #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) value_type value = value_; - if (category() != error::system_category && *this != error::eof) + if (category() != error::get_system_category() && *this != error::eof) return "asio error"; if (*this == error::eof) value = ERROR_HANDLE_EOF; @@ -78,13 +78,13 @@ inline std::string error_code::message() const return "Service not found."; if (*this == error::socket_type_not_supported) return "Socket type not supported."; - if (category() != error::system_category) + if (category() != error::get_system_category()) return "asio error"; #if defined(__sun) || defined(__QNX__) return strerror(value_); #elif defined(__MACH__) && defined(__APPLE__) \ || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) \ -|| defined(_AIX) +|| defined(_AIX) || defined(__hpux) || defined(__osf__) char buf[256] = ""; strerror_r(value_, buf, sizeof(buf)); return buf; diff --git a/libtorrent/include/asio/impl/io_service.ipp b/libtorrent/include/asio/impl/io_service.ipp index f51d3697d..8ebc2643c 100644 --- a/libtorrent/include/asio/impl/io_service.ipp +++ b/libtorrent/include/asio/impl/io_service.ipp @@ -21,6 +21,7 @@ #include #include "asio/detail/pop_options.hpp" +#include "asio/detail/dev_poll_reactor.hpp" #include "asio/detail/epoll_reactor.hpp" #include "asio/detail/kqueue_reactor.hpp" #include "asio/detail/select_reactor.hpp" @@ -157,6 +158,11 @@ inline asio::io_service& io_service::work::io_service() return io_service_; } +inline asio::io_service& io_service::work::get_io_service() +{ + return io_service_; +} + inline io_service::service::service(asio::io_service& owner) : owner_(owner), type_info_(0), @@ -173,6 +179,11 @@ inline asio::io_service& io_service::service::io_service() return owner_; } +inline asio::io_service& io_service::service::get_io_service() +{ + return owner_; +} + template inline Service& use_service(io_service& ios) { diff --git a/libtorrent/include/asio/io_service.hpp b/libtorrent/include/asio/io_service.hpp index 2101e56c4..984a63e18 100644 --- a/libtorrent/include/asio/io_service.hpp +++ b/libtorrent/include/asio/io_service.hpp @@ -26,6 +26,7 @@ #include "asio/detail/pop_options.hpp" #include "asio/error_code.hpp" +#include "asio/detail/dev_poll_reactor_fwd.hpp" #include "asio/detail/epoll_reactor_fwd.hpp" #include "asio/detail/kqueue_reactor_fwd.hpp" #include "asio/detail/noncopyable.hpp" @@ -39,6 +40,11 @@ namespace asio { +class io_service; +template Service& use_service(io_service& ios); +template void add_service(io_service& ios, Service* svc); +template bool has_service(io_service& ios); + /// Provides core I/O functionality. /** * The io_service class provides the core I/O functionality for users of the @@ -106,6 +112,8 @@ private: typedef detail::task_io_service > impl_type; #elif defined(ASIO_HAS_KQUEUE) typedef detail::task_io_service > impl_type; +#elif defined(ASIO_HAS_DEV_POLL) + typedef detail::task_io_service > impl_type; #else typedef detail::task_io_service > impl_type; #endif @@ -373,7 +381,8 @@ public: private: #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) detail::winsock_init<> init_; -#elif defined(__sun) || defined(__QNX__) +#elif defined(__sun) || defined(__QNX__) || defined(__hpux) || defined(_AIX) \ + || defined(__osf__) detail::signal_init<> init_; #endif @@ -421,9 +430,13 @@ public: */ ~work(); - /// Get the io_service associated with the work. + /// (Deprecated: use get_io_service().) Get the io_service associated with the + /// work. asio::io_service& io_service(); + /// Get the io_service associated with the work. + asio::io_service& get_io_service(); + private: // Prevent assignment. void operator=(const work& other); @@ -446,9 +459,13 @@ class io_service::service : private noncopyable { public: - /// Get the io_service object that owns the service. + /// (Deprecated: use get_io_service().) Get the io_service object that owns + /// the service. asio::io_service& io_service(); + /// Get the io_service object that owns the service. + asio::io_service& get_io_service(); + protected: /// Constructor. /** diff --git a/libtorrent/include/asio/ip/address_v4.hpp b/libtorrent/include/asio/ip/address_v4.hpp index ae3891c95..c7de56b7e 100644 --- a/libtorrent/include/asio/ip/address_v4.hpp +++ b/libtorrent/include/asio/ip/address_v4.hpp @@ -268,7 +268,12 @@ std::basic_ostream& operator<<( asio::error_code ec; std::string s = addr.to_string(ec); if (ec) - os.setstate(std::ios_base::failbit); + { + if (os.exceptions() & std::ios::failbit) + asio::detail::throw_error(ec); + else + os.setstate(std::ios_base::failbit); + } else for (std::string::iterator i = s.begin(); i != s.end(); ++i) os << os.widen(*i); diff --git a/libtorrent/include/asio/ip/address_v6.hpp b/libtorrent/include/asio/ip/address_v6.hpp index f732955fa..ce5eeb00b 100644 --- a/libtorrent/include/asio/ip/address_v6.hpp +++ b/libtorrent/include/asio/ip/address_v6.hpp @@ -300,7 +300,7 @@ public: { using namespace std; // For memcmp. int memcmp_result = memcmp(&a1.addr_, &a2.addr_, - sizeof(asio::detail::in6_addr_type)) < 0; + sizeof(asio::detail::in6_addr_type)); if (memcmp_result < 0) return true; if (memcmp_result > 0) @@ -386,7 +386,12 @@ std::basic_ostream& operator<<( asio::error_code ec; std::string s = addr.to_string(ec); if (ec) - os.setstate(std::ios_base::failbit); + { + if (os.exceptions() & std::ios::failbit) + asio::detail::throw_error(ec); + else + os.setstate(std::ios_base::failbit); + } else for (std::string::iterator i = s.begin(); i != s.end(); ++i) os << os.widen(*i); diff --git a/libtorrent/include/asio/ip/basic_endpoint.hpp b/libtorrent/include/asio/ip/basic_endpoint.hpp index 3d1316e22..643df48a7 100644 --- a/libtorrent/include/asio/ip/basic_endpoint.hpp +++ b/libtorrent/include/asio/ip/basic_endpoint.hpp @@ -61,14 +61,6 @@ public: typedef asio::detail::socket_addr_type data_type; #endif - /// The type for the size of the endpoint structure. This type is dependent on - /// the underlying implementation of the socket layer. -#if defined(GENERATING_DOCUMENTATION) - typedef implementation_defined size_type; -#else - typedef asio::detail::socket_addr_len_type size_type; -#endif - /// Default constructor. basic_endpoint() : data_() @@ -190,7 +182,7 @@ public: } /// Get the underlying size of the endpoint in the native type. - size_type size() const + std::size_t size() const { if (is_v4(data_)) return sizeof(asio::detail::sockaddr_in4_type); @@ -199,9 +191,9 @@ public: } /// Set the underlying size of the endpoint in the native type. - void resize(size_type size) + void resize(std::size_t size) { - if (size > size_type(sizeof(data_))) + if (size > sizeof(data_)) { asio::system_error e(asio::error::invalid_argument); boost::throw_exception(e); @@ -209,7 +201,7 @@ public: } /// Get the capacity of the endpoint in the native type. - size_type capacity() const + std::size_t capacity() const { return sizeof(data_); } @@ -349,11 +341,23 @@ std::ostream& operator<<(std::ostream& os, const basic_endpoint& endpoint) { const address& addr = endpoint.address(); - if (addr.is_v4()) - os << addr.to_string(); + asio::error_code ec; + std::string a = addr.to_string(ec); + if (ec) + { + if (os.exceptions() & std::ios::failbit) + asio::detail::throw_error(ec); + else + os.setstate(std::ios_base::failbit); + } else - os << '[' << addr.to_string() << ']'; - os << ':' << endpoint.port(); + { + if (addr.is_v4()) + os << a; + else + os << '[' << a << ']'; + os << ':' << endpoint.port(); + } return os; } #else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) @@ -363,11 +367,23 @@ std::basic_ostream& operator<<( const basic_endpoint& endpoint) { const address& addr = endpoint.address(); - if (addr.is_v4()) - os << addr.to_string(); + asio::error_code ec; + std::string a = addr.to_string(ec); + if (ec) + { + if (os.exceptions() & std::ios::failbit) + asio::detail::throw_error(ec); + else + os.setstate(std::ios_base::failbit); + } else - os << '[' << addr.to_string() << ']'; - os << ':' << endpoint.port(); + { + if (addr.is_v4()) + os << a; + else + os << '[' << a << ']'; + os << ':' << endpoint.port(); + } return os; } #endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) diff --git a/libtorrent/include/asio/ip/basic_resolver_iterator.hpp b/libtorrent/include/asio/ip/basic_resolver_iterator.hpp index 686e4446e..81c652ce8 100644 --- a/libtorrent/include/asio/ip/basic_resolver_iterator.hpp +++ b/libtorrent/include/asio/ip/basic_resolver_iterator.hpp @@ -80,9 +80,7 @@ public: { using namespace std; // For memcpy. typename InternetProtocol::endpoint endpoint; - endpoint.resize( - static_cast( - address_info->ai_addrlen)); + endpoint.resize(static_cast(address_info->ai_addrlen)); memcpy(endpoint.data(), address_info->ai_addr, address_info->ai_addrlen); iter.values_->push_back( diff --git a/libtorrent/include/asio/ip/detail/socket_option.hpp b/libtorrent/include/asio/ip/detail/socket_option.hpp index a86307077..1d43d6059 100644 --- a/libtorrent/include/asio/ip/detail/socket_option.hpp +++ b/libtorrent/include/asio/ip/detail/socket_option.hpp @@ -32,52 +32,60 @@ namespace ip { namespace detail { namespace socket_option { -// Helper template for implementing boolean-based options. +// Helper template for implementing multicast enable loopback options. template -class boolean +class multicast_enable_loopback { public: -#if defined(__sun) - typedef unsigned char value_type; +#if defined(__sun) || defined(__osf__) + typedef unsigned char ipv4_value_type; + typedef unsigned char ipv6_value_type; +#elif defined(_AIX) || defined(__hpux) + typedef unsigned char ipv4_value_type; + typedef unsigned int ipv6_value_type; #else - typedef int value_type; + typedef int ipv4_value_type; + typedef int ipv6_value_type; #endif // Default constructor. - boolean() - : value_(0) + multicast_enable_loopback() + : ipv4_value_(0), + ipv6_value_(0) { } // Construct with a specific option value. - explicit boolean(bool v) - : value_(v ? 1 : 0) + explicit multicast_enable_loopback(bool v) + : ipv4_value_(v ? 1 : 0), + ipv6_value_(v ? 1 : 0) { } // Set the value of the boolean. - boolean& operator=(bool v) + multicast_enable_loopback& operator=(bool v) { - value_ = v ? 1 : 0; + ipv4_value_ = v ? 1 : 0; + ipv6_value_ = v ? 1 : 0; return *this; } // Get the current value of the boolean. bool value() const { - return !!value_; + return !!ipv4_value_; } // Convert to bool. operator bool() const { - return !!value_; + return !!ipv4_value_; } // Test for false. bool operator!() const { - return !value_; + return !ipv4_value_; } // Get the level of the socket option. @@ -100,35 +108,58 @@ public: // Get the address of the boolean data. template - value_type* data(const Protocol&) + void* data(const Protocol& protocol) { - return &value_; + if (protocol.family() == PF_INET6) + return &ipv6_value_; + return &ipv4_value_; } // Get the address of the boolean data. template - const value_type* data(const Protocol&) const + const void* data(const Protocol& protocol) const { - return &value_; + if (protocol.family() == PF_INET6) + return &ipv6_value_; + return &ipv4_value_; } // Get the size of the boolean data. template - std::size_t size(const Protocol&) const + std::size_t size(const Protocol& protocol) const { - return sizeof(value_); + if (protocol.family() == PF_INET6) + return sizeof(ipv6_value_); + return sizeof(ipv4_value_); } // Set the size of the boolean data. template - void resize(const Protocol&, std::size_t s) + void resize(const Protocol& protocol, std::size_t s) { - if (s != sizeof(value_)) - throw std::length_error("boolean socket option resize"); + if (protocol.family() == PF_INET6) + { + if (s != sizeof(ipv6_value_)) + { + throw std::length_error( + "multicast_enable_loopback socket option resize"); + } + ipv4_value_ = ipv6_value_ ? 1 : 0; + } + else + { + if (s != sizeof(ipv4_value_)) + { + throw std::length_error( + "multicast_enable_loopback socket option resize"); + } + ipv6_value_ = ipv4_value_ ? 1 : 0; + } } private: - value_type value_; + ipv4_value_type ipv4_value_; + ipv6_value_type ipv6_value_; }; // Helper template for implementing unicast hops options. @@ -206,6 +237,10 @@ public: { if (s != sizeof(value_)) throw std::length_error("unicast hops socket option resize"); +#if defined(__hpux) + if (value_ < 0) + value_ = value_ & 0xFF; +#endif } private: @@ -217,6 +252,13 @@ template class multicast_hops { public: +#if defined(BOOST_WINDOWS) && defined(UNDER_CE) + typedef int ipv4_value_type; +#else + typedef unsigned char ipv4_value_type; +#endif + typedef int ipv6_value_type; + // Default constructor. multicast_hops() : ipv4_value_(0), @@ -229,7 +271,7 @@ public: { if (v < 0 || v > 255) throw std::out_of_range("multicast hops value out of range"); - ipv4_value_ = static_cast(v); + ipv4_value_ = (ipv4_value_type)v; ipv6_value_ = v; } @@ -238,7 +280,7 @@ public: { if (v < 0 || v > 255) throw std::out_of_range("multicast hops value out of range"); - ipv4_value_ = static_cast(v); + ipv4_value_ = (ipv4_value_type)v; ipv6_value_ = v; return *this; } @@ -307,7 +349,7 @@ public: else if (ipv6_value_ > 255) ipv4_value_ = 255; else - ipv4_value_ = static_cast(ipv6_value_); + ipv4_value_ = (ipv4_value_type)ipv6_value_; } else { @@ -318,8 +360,8 @@ public: } private: - unsigned char ipv4_value_; - int ipv6_value_; + ipv4_value_type ipv4_value_; + ipv6_value_type ipv6_value_; }; // Helper template for implementing ip_mreq-based options. @@ -477,7 +519,7 @@ public: } // Construct with IPv6 interface. - explicit network_interface(unsigned long ipv6_interface) + explicit network_interface(unsigned int ipv6_interface) { ipv4_value_.s_addr = asio::detail::socket_ops::host_to_network_long( @@ -523,7 +565,7 @@ public: private: asio::detail::in4_addr_type ipv4_value_; - unsigned long ipv6_value_; + unsigned int ipv6_value_; }; } // namespace socket_option diff --git a/libtorrent/include/asio/ip/multicast.hpp b/libtorrent/include/asio/ip/multicast.hpp index 0d90659ca..8b48687f1 100644 --- a/libtorrent/include/asio/ip/multicast.hpp +++ b/libtorrent/include/asio/ip/multicast.hpp @@ -167,7 +167,7 @@ typedef asio::ip::detail::socket_option::multicast_hops< #if defined(GENERATING_DOCUMENTATION) typedef implementation_defined enable_loopback; #else -typedef asio::ip::detail::socket_option::boolean< +typedef asio::ip::detail::socket_option::multicast_enable_loopback< IPPROTO_IP, IP_MULTICAST_LOOP, IPPROTO_IPV6, IPV6_MULTICAST_LOOP> enable_loopback; #endif diff --git a/libtorrent/include/asio/placeholders.hpp b/libtorrent/include/asio/placeholders.hpp index 0c1b6b4a6..9309fc0b4 100644 --- a/libtorrent/include/asio/placeholders.hpp +++ b/libtorrent/include/asio/placeholders.hpp @@ -27,17 +27,17 @@ namespace placeholders { #if defined(GENERATING_DOCUMENTATION) -/// An argument placeholder, for use with @ref boost_bind, that corresponds to +/// An argument placeholder, for use with boost::bind(), that corresponds to /// the error argument of a handler for any of the asynchronous functions. unspecified error; -/// An argument placeholder, for use with @ref boost_bind, that corresponds to +/// An argument placeholder, for use with boost::bind(), that corresponds to /// the bytes_transferred argument of a handler for asynchronous functions such /// as asio::basic_stream_socket::async_write_some or /// asio::async_write. unspecified bytes_transferred; -/// An argument placeholder, for use with @ref boost_bind, that corresponds to +/// An argument placeholder, for use with boost::bind(), that corresponds to /// the iterator argument of a handler for asynchronous functions such as /// asio::basic_resolver::resolve. unspecified iterator; diff --git a/libtorrent/include/asio/resolver_service.hpp b/libtorrent/include/asio/resolver_service.hpp new file mode 100644 index 000000000..bdd8dbfbd --- /dev/null +++ b/libtorrent/include/asio/resolver_service.hpp @@ -0,0 +1,126 @@ +// +// resolver_service.hpp +// ~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_RESOLVER_SERVICE_HPP +#define ASIO_RESOLVER_SERVICE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/io_service.hpp" +#include "asio/detail/resolver_service.hpp" + +namespace asio { + +/// Default service implementation for a resolver. +template +class resolver_service + : public asio::io_service::service +{ +public: + /// The protocol type. + typedef Protocol protocol_type; + + /// The endpoint type. + typedef typename Protocol::endpoint endpoint_type; + + /// The query type. + typedef typename Protocol::resolver_query query_type; + + /// The iterator type. + typedef typename Protocol::resolver_iterator iterator_type; + +private: + // The type of the platform-specific implementation. + typedef detail::resolver_service service_impl_type; + +public: + /// The type of a resolver implementation. +#if defined(GENERATING_DOCUMENTATION) + typedef implementation_defined implementation_type; +#else + typedef typename service_impl_type::implementation_type implementation_type; +#endif + + /// Construct a new resolver service for the specified io_service. + explicit resolver_service(asio::io_service& io_service) + : asio::io_service::service(io_service), + service_impl_(asio::use_service(io_service)) + { + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() + { + } + + /// Construct a new resolver implementation. + void construct(implementation_type& impl) + { + service_impl_.construct(impl); + } + + /// Destroy a resolver implementation. + void destroy(implementation_type& impl) + { + service_impl_.destroy(impl); + } + + /// Cancel pending asynchronous operations. + void cancel(implementation_type& impl) + { + service_impl_.cancel(impl); + } + + /// Resolve a query to a list of entries. + template + iterator_type resolve(implementation_type& impl, const query_type& query, + Error_Handler error_handler) + { + return service_impl_.resolve(impl, query, error_handler); + } + + /// Asynchronously resolve a query to a list of entries. + template + void async_resolve(implementation_type& impl, const query_type& query, + Handler handler) + { + service_impl_.async_resolve(impl, query, handler); + } + + /// Resolve an endpoint to a list of entries. + template + iterator_type resolve(implementation_type& impl, + const endpoint_type& endpoint, Error_Handler error_handler) + { + return service_impl_.resolve(impl, endpoint, error_handler); + } + + /// Asynchronously resolve an endpoint to a list of entries. + template + void async_resolve(implementation_type& impl, const endpoint_type& endpoint, + Handler handler) + { + return service_impl_.async_resolve(impl, endpoint, handler); + } + +private: + // The service that provides the platform-specific implementation. + service_impl_type& service_impl_; +}; + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_RESOLVER_SERVICE_HPP diff --git a/libtorrent/include/asio/socket_acceptor_service.hpp b/libtorrent/include/asio/socket_acceptor_service.hpp index 965bff39e..e80f8c1ca 100644 --- a/libtorrent/include/asio/socket_acceptor_service.hpp +++ b/libtorrent/include/asio/socket_acceptor_service.hpp @@ -60,6 +60,9 @@ private: #elif defined(ASIO_HAS_KQUEUE) typedef detail::reactive_socket_service< Protocol, detail::kqueue_reactor > service_impl_type; +#elif defined(ASIO_HAS_DEV_POLL) + typedef detail::reactive_socket_service< + Protocol, detail::dev_poll_reactor > service_impl_type; #else typedef detail::reactive_socket_service< Protocol, detail::select_reactor > service_impl_type; diff --git a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp index 5fd3ebba4..c254aceb2 100755 --- a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp @@ -179,7 +179,7 @@ public: else { return handler_(asio::error_code( - error_code, asio::error::ssl_category), rc); + error_code, asio::error::get_ssl_category()), rc); } } diff --git a/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp b/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp index d90b588ef..3812deb80 100644 --- a/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp @@ -238,7 +238,7 @@ public: typedef handshake_handler connect_handler; connect_handler* local_handler = - new connect_handler(handler, io_service()); + new connect_handler(handler, get_io_service()); openssl_operation* op = new openssl_operation ( @@ -259,7 +259,7 @@ public: ); local_handler->set_operation(op); - io_service().post(boost::bind(&openssl_operation::start, op)); + get_io_service().post(boost::bind(&openssl_operation::start, op)); } // Shut down SSL on the stream. @@ -294,7 +294,7 @@ public: typedef shutdown_handler disconnect_handler; disconnect_handler* local_handler = - new disconnect_handler(handler, io_service()); + new disconnect_handler(handler, get_io_service()); openssl_operation* op = new openssl_operation ( @@ -313,7 +313,7 @@ public: ); local_handler->set_operation(op); - io_service().post(boost::bind(&openssl_operation::start, op)); + get_io_service().post(boost::bind(&openssl_operation::start, op)); } // Write some data to the stream. @@ -354,7 +354,7 @@ public: { typedef io_handler send_handler; - send_handler* local_handler = new send_handler(handler, io_service()); + send_handler* local_handler = new send_handler(handler, get_io_service()); boost::function send_func = boost::bind(&::SSL_write, boost::arg<1>(), @@ -378,7 +378,7 @@ public: ); local_handler->set_operation(op); - io_service().post(boost::bind(&openssl_operation::start, op)); + get_io_service().post(boost::bind(&openssl_operation::start, op)); } // Read some data from the stream. @@ -419,7 +419,7 @@ public: { typedef io_handler recv_handler; - recv_handler* local_handler = new recv_handler(handler, io_service()); + recv_handler* local_handler = new recv_handler(handler, get_io_service()); boost::function recv_func = boost::bind(&::SSL_read, boost::arg<1>(), @@ -443,7 +443,7 @@ public: ); local_handler->set_operation(op); - io_service().post(boost::bind(&openssl_operation::start, op)); + get_io_service().post(boost::bind(&openssl_operation::start, op)); } // Peek at the incoming data on the stream. diff --git a/libtorrent/include/asio/ssl/stream.hpp b/libtorrent/include/asio/ssl/stream.hpp index a6af16101..9ee48fef1 100644 --- a/libtorrent/include/asio/ssl/stream.hpp +++ b/libtorrent/include/asio/ssl/stream.hpp @@ -85,7 +85,7 @@ public: template explicit stream(Arg& arg, basic_context& context) : next_layer_(arg), - service_(asio::use_service(next_layer_.io_service())), + service_(asio::use_service(next_layer_.get_io_service())), impl_(service_.null()) { service_.create(impl_, next_layer_, context); @@ -97,7 +97,8 @@ public: service_.destroy(impl_, next_layer_); } - /// Get the io_service associated with the object. + /// (Deprecated: use get_io_service().) Get the io_service associated with + /// the object. /** * This function may be used to obtain the io_service object that the stream * uses to dispatch handlers for asynchronous operations. @@ -107,7 +108,20 @@ public: */ asio::io_service& io_service() { - return next_layer_.io_service(); + return next_layer_.get_io_service(); + } + + /// Get the io_service associated with the object. + /** + * This function may be used to obtain the io_service object that the stream + * uses to dispatch handlers for asynchronous operations. + * + * @return A reference to the io_service object that stream will use to + * dispatch handlers. Ownership is not transferred to the caller. + */ + asio::io_service& get_io_service() + { + return next_layer_.get_io_service(); } /// Get a reference to the next layer. diff --git a/libtorrent/include/asio/strand.hpp b/libtorrent/include/asio/strand.hpp index d0869d95d..ff76415a2 100644 --- a/libtorrent/include/asio/strand.hpp +++ b/libtorrent/include/asio/strand.hpp @@ -65,7 +65,8 @@ public: service_.destroy(impl_); } - /// Get the io_service associated with the strand. + /// (Deprecated: use get_io_service().) Get the io_service associated with + /// the strand. /** * This function may be used to obtain the io_service object that the strand * uses to dispatch handlers for asynchronous operations. @@ -75,7 +76,20 @@ public: */ asio::io_service& io_service() { - return service_.io_service(); + return service_.get_io_service(); + } + + /// Get the io_service associated with the strand. + /** + * This function may be used to obtain the io_service object that the strand + * uses to dispatch handlers for asynchronous operations. + * + * @return A reference to the io_service object that the strand will use to + * dispatch handlers. Ownership is not transferred to the caller. + */ + asio::io_service& get_io_service() + { + return service_.get_io_service(); } /// Request the strand to invoke the given handler. diff --git a/libtorrent/include/asio/stream_socket_service.hpp b/libtorrent/include/asio/stream_socket_service.hpp index d7915aaf4..7a04c6bf0 100644 --- a/libtorrent/include/asio/stream_socket_service.hpp +++ b/libtorrent/include/asio/stream_socket_service.hpp @@ -64,6 +64,9 @@ private: #elif defined(ASIO_HAS_KQUEUE) typedef detail::reactive_socket_service< Protocol, detail::kqueue_reactor > service_impl_type; +#elif defined(ASIO_HAS_DEV_POLL) + typedef detail::reactive_socket_service< + Protocol, detail::dev_poll_reactor > service_impl_type; #else typedef detail::reactive_socket_service< Protocol, detail::select_reactor > service_impl_type; diff --git a/libtorrent/include/asio/system_exception.hpp b/libtorrent/include/asio/system_exception.hpp new file mode 100644 index 000000000..599e22712 --- /dev/null +++ b/libtorrent/include/asio/system_exception.hpp @@ -0,0 +1,198 @@ +// +// error.hpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_SYSTEM_EXCEPTION_HPP +#define ASIO_SYSTEM_EXCEPTION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/push_options.hpp" + +#include "asio/detail/push_options.hpp" +#include +#include +#include +#include +#include +#include +#include +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +# include +#endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +#include "asio/detail/pop_options.hpp" + +#include "asio/detail/win_local_free_on_block_exit.hpp" + +namespace asio { + +/// The system_exception class is used to represent system conditions that +/// prevent the library from operating correctly. +class system_exception + : public std::exception +{ +public: + /// Construct with a specific context and error code. + system_exception(const std::string& context, int code) + : context_(context), + code_(code) + { + } + + /// Copy constructor. + system_exception(const system_exception& e) + : std::exception(e), + context_(e.context_), + code_(e.code_) + { + } + + /// Destructor. + virtual ~system_exception() throw () + { + } + + /// Assignment operator. + system_exception& operator=(const system_exception& e) + { + context_ = e.context_; + code_ = e.code_; + what_.reset(); + return *this; + } + + /// Get a string representation of the exception. + virtual const char* what() const throw () + { +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) + try + { + if (!what_) + { + char* msg = 0; + DWORD length = ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, 0, code_, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&msg, 0, 0); + detail::win_local_free_on_block_exit local_free_obj(msg); + if (length && msg[length - 1] == '\n') + msg[--length] = '\0'; + if (length && msg[length - 1] == '\r') + msg[--length] = '\0'; + if (length) + { + std::string tmp(context_); + tmp += ": "; + tmp += msg; + what_.reset(new std::string(tmp)); + } + else + { + return "asio system_exception"; + } + } + return what_->c_str(); + } + catch (std::exception&) + { + return "asio system_exception"; + } +#elif defined(__sun) || defined(__QNX__) + return strerror(code_); +#elif defined(__MACH__) && defined(__APPLE__) \ + || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) + try + { + char buf[256] = ""; + strerror_r(code_, buf, sizeof(buf)); + std::string tmp(context_); + tmp += ": "; + tmp += buf; + what_.reset(new std::string(tmp)); + return what_->c_str(); + } + catch (std::exception&) + { + return "asio system_exception"; + } +#else + try + { + char buf[256] = ""; + std::string tmp(context_); + tmp += ": "; + tmp += strerror_r(code_, buf, sizeof(buf)); + what_.reset(new std::string(tmp)); + return what_->c_str(); + } + catch (std::exception&) + { + return "asio system_exception"; + } +#endif + } + + /// Get the implementation-defined context associated with the exception. + const std::string& context() const + { + return context_; + } + + /// Get the implementation-defined code associated with the exception. + int code() const + { + return code_; + } + +private: + // The context associated with the error. + std::string context_; + + // The code associated with the error. + int code_; + + // The string representation of the error. + mutable boost::scoped_ptr what_; +}; + +/// Output the string associated with a system exception. +/** + * Used to output a human-readable string that is associated with a system + * exception. + * + * @param os The output stream to which the string will be written. + * + * @param e The exception to be written. + * + * @return The output stream. + * + * @relates asio::system_exception + */ +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +std::ostream& operator<<(std::ostream& os, const system_exception& e) +{ + os << e.what(); + return os; +} +#else // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +template +Ostream& operator<<(Ostream& os, const system_exception& e) +{ + os << e.what(); + return os; +} +#endif // BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) + +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SYSTEM_EXCEPTION_HPP From ac08425ee30b67081241d82ad5ab3d781a61f6e0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 24 Nov 2007 01:38:17 +0000 Subject: [PATCH 0266/1009] Add remove torrent dialog. --- TODO | 1 - deluge/core/core.py | 4 +- deluge/core/torrentmanager.py | 24 ++++++- deluge/ui/client.py | 4 +- .../gtkui/glade/remove_torrent_dialog.glade | 66 ++++++++++++++----- deluge/ui/gtkui/menubar.py | 7 +- 6 files changed, 82 insertions(+), 24 deletions(-) diff --git a/TODO b/TODO index f93910f83..7a6176eb2 100644 --- a/TODO +++ b/TODO @@ -13,7 +13,6 @@ * Hide open folder if not localhost * Add classic/normal mode to preferences * Implement 'Classic' mode -* Add remove torrent dialog and ability to remove data * Tray tooltip * Add torrentfiles location config option * Add autoload folder diff --git a/deluge/core/core.py b/deluge/core/core.py index fa96b0bb8..bcd721f46 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -275,9 +275,9 @@ class Core( # Add the torrent to session return self.export_add_torrent_file(filename, save_path, filedump) - def export_remove_torrent(self, torrent_id): + def export_remove_torrent(self, torrent_id, remove_torrent, remove_data): log.debug("Removing torrent %s from the core.", torrent_id) - if self.torrents.remove(torrent_id): + if self.torrents.remove(torrent_id, remove_torrent, remove_data): # Run the plugin hooks for 'post_torrent_remove' self.plugins.run_post_torrent_remove(torrent_id) # Emit the torrent_removed signal diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 6bde4360b..c10e80e36 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -250,19 +250,39 @@ class TorrentManager: return filedump - def remove(self, torrent_id): + def remove(self, torrent_id, remove_torrent, remove_data): """Remove a torrent from the manager""" try: # Remove from libtorrent session - self.session.remove_torrent(self.torrents[torrent_id].handle, 0) + option = 0 + # Remove data if set + if remove_data: + option = 1 + self.session.remove_torrent(self.torrents[torrent_id].handle, + option) except (RuntimeError, KeyError), e: log.warning("Error removing torrent: %s", e) return False + # Remove the .torrent file if requested + if remove_torrent: + try: + torrent_file = os.path.join( + self.config["torrentfiles_location"], + self.torrents[torrent_id].filename) + os.remove(torrent_file) + except Exception, e: + log.warning("Unable to remove .torrent file: %s", e) + + # Remove the .fastresume if it exists + self.delete_fastresume(torrent_id) + + # Remove the torrent from deluge's session try: del self.torrents[torrent_id] except KeyError, ValueError: return False + # Save the session state self.save_state() return True diff --git a/deluge/ui/client.py b/deluge/ui/client.py index d8e2a133f..8f0ae6290 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -188,12 +188,12 @@ def add_torrent_url(torrent_url): else: log.warning("Invalid URL %s", torrent_url) -def remove_torrent(torrent_ids): +def remove_torrent(torrent_ids, remove_torrent=False, remove_data=False): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) try: for torrent_id in torrent_ids: - get_core().remove_torrent(torrent_id) + get_core().remove_torrent(torrent_id, remove_torrent, remove_data) except (AttributeError, socket.error): set_core_uri(None) diff --git a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade index 6591dba4d..5b1fb7f2d 100644 --- a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade @@ -1,11 +1,12 @@ - + + 350 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - Remove Torrents + Remove Torrent False GTK_WIN_POS_CENTER_ON_PARENT GDK_WINDOW_TYPE_HINT_DIALOG @@ -30,7 +31,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-dialog-warning - 6 + 5 False @@ -41,7 +42,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <big><b>Remove Torrents?</b></big> + <big><b>Remove Torrent(s)?</b></big> True @@ -57,28 +58,23 @@ - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete .torrent files - 0 - True False - False 1 - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete saved data - 0 - True + 0 + 10 + <b>Options</b> + True False @@ -86,6 +82,46 @@ 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete .torrent file(s) + 0 + True + + + + + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Delete saved data + 0 + True + + + + + 4 + + False diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 79a7851ae..c13c60955 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -191,8 +191,11 @@ class MenuBar(component.Component): def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") - client.remove_torrent( - component.get("TorrentView").get_selected_torrents()) + from removetorrentdialog import RemoveTorrentDialog + RemoveTorrentDialog( + component.get("TorrentView").get_selected_torrents()).run() + #client.remove_torrent( + # component.get("TorrentView").get_selected_torrents()) def on_menuitem_recheck_activate(self, data=None): log.debug("on_menuitem_recheck_activate") From 4ca1206b2c467a2b056756b741b0d9c8f748c090 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 24 Nov 2007 02:34:21 +0000 Subject: [PATCH 0267/1009] Add 'torrentfiles_location' configuration option. --- TODO | 1 - deluge/config.py | 7 +++++++ deluge/core/core.py | 29 +++++++++++++++++++++++++++++ deluge/ui/gtkui/preferences.py | 5 +++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 7a6176eb2..cae96a993 100644 --- a/TODO +++ b/TODO @@ -14,7 +14,6 @@ * Add classic/normal mode to preferences * Implement 'Classic' mode * Tray tooltip -* Add torrentfiles location config option * Add autoload folder * Add wizard * Add a health indication to the statusbar diff --git a/deluge/config.py b/deluge/config.py index 6bfcf408e..faec54a87 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -46,6 +46,7 @@ class Config: log.debug("Config created with filename: %s", filename) log.debug("Config defaults: %s", defaults) self.config = {} + self.previous_config = {} self.set_functions = {} # If defaults is not None then we need to use "defaults". @@ -115,6 +116,8 @@ class Config: # Sets the "key" with "value" in the config dict if self.config[key] != value: log.debug("Setting '%s' to %s of %s", key, value, type(value)) + # Make a copy of the current config prior to changing it + self.previous_config = self.config.copy() self.config[key] = value # Run the set_function for this key if any try: @@ -139,6 +142,10 @@ class Config: """Returns the entire configuration as a dictionary.""" return self.config + def get_previous_config(self): + """Returns the config prior to the last set()""" + return self.previous_config + def register_set_function(self, key, function, apply_now=True): """Register a function to be run when a config value changes.""" log.debug("Registering function for %s key..", key) diff --git a/deluge/core/core.py b/deluge/core/core.py index bcd721f46..93ab345ef 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -36,6 +36,8 @@ import locale import pkg_resources import sys import pickle +import shutil +import os import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn @@ -148,6 +150,8 @@ class Core( self.session.add_extension(lt.create_metadata_plugin) # Register set functions in the Config + self.config.register_set_function("torrentfiles_location", + self._on_set_torrentfiles_location) self.config.register_set_function("listen_ports", self._on_set_listen_ports) self.config.register_set_function("random_port", @@ -426,6 +430,31 @@ class Core( self.signals.emit("torrent_all_resumed", torrent_id) # Config set functions + def _on_set_torrentfiles_location(self, key, value): + try: + old = self.config.get_previous_config()["torrentfiles_location"] + except Exception, e: + # This probably means it's not a real change but we're just loading + # the config. + log.debug("Unable to get previous torrentfiles_location: %s", e) + return + + # First try to create the new directory + try: + os.makedirs(value) + except Exception, e: + log.debug("Unable to make directory: %s", e) + + # Now copy all files in the old directory to the new one + for root, dirs, files in os.walk(old): + for dir in dirs: + os.makedirs(dir) + for file in files: + try: + shutil.copy2(os.path.join(root, file), value) + except Exception, e: + log.debug("Unable to copy file to %s: %s", value, e) + def _on_set_listen_ports(self, key, value): # Only set the listen ports if random_port is not true if self.config["random_port"] is not True: diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 42ad1093f..d03522b4d 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -156,6 +156,8 @@ class Preferences(component.Component): core_widgets = { "download_path_button": \ ("filename", self.core_config["download_location"]), + "torrent_files_button": \ + ("filename", self.core_config["torrentfiles_location"]), "radio_compact_allocation": \ ("active", self.core_config["compact_allocation"]), "radio_full_allocation": \ @@ -213,6 +215,7 @@ class Preferences(component.Component): else: core_widget_list = [ "download_path_button", + "torrent_files_button", "radio_compact_allocation", "radio_full_allocation", "chk_prioritize_first_last_pieces", @@ -306,6 +309,8 @@ class Preferences(component.Component): self.glade.get_widget("radio_ask_save").get_active() new_core_config["download_location"] = \ self.glade.get_widget("download_path_button").get_filename() + new_core_config["torrentfiles_location"] = \ + self.glade.get_widget("torrent_files_button").get_filename() new_core_config["compact_allocation"] = \ self.glade.get_widget("radio_compact_allocation").get_active() new_core_config["prioritize_first_last_pieces"] = \ From 2815d8f19ff196687031403a9885d87c669cb020 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 24 Nov 2007 03:21:37 +0000 Subject: [PATCH 0268/1009] Add option to allow remote connections to daemon. Add option to change daemon port. Added 'Daemon' tab to Preferences dialog. --- deluge/core/core.py | 8 +- deluge/ui/client.py | 7 +- .../ui/gtkui/glade/preferences_dialog.glade | 407 +++++++++++++----- deluge/ui/gtkui/preferences.py | 28 +- 4 files changed, 326 insertions(+), 124 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 93ab345ef..d60b6b53a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -56,6 +56,7 @@ from deluge.log import LOG as log DEFAULT_PREFS = { "daemon_port": 58846, + "allow_remote": False, "compact_allocation": True, "download_location": deluge.common.get_default_download_dir(), "listen_ports": [6881, 6891], @@ -95,11 +96,16 @@ class Core( if port == None: port = self.config["daemon_port"] + if self.config["allow_remote"]: + hostname = "" + else: + hostname = "localhost" + # Setup the xmlrpc server try: log.info("Starting XMLRPC server on port %s", port) SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, ("localhost", port), logRequests=False, allow_none=True) + self, (hostname, port), logRequests=False, allow_none=True) except: log.info("Daemon already running or port not available..") sys.exit(0) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 8f0ae6290..75dd8c788 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -58,14 +58,17 @@ class CoreProxy: def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) - + if uri == None and self._uri != None: for callback in self._on_no_core_callbacks: callback() self._uri = None self._core = None return - + + if uri != self._uri: + self._core = None + self._uri = uri # Get a new core self.get_core() diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 3fee5dcda..0c8266f0e 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -966,9 +966,15 @@ Either - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + page 7 + tab + 1 + False @@ -1030,71 +1036,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -1119,43 +1094,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1199,29 +1205,24 @@ Either 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1239,19 +1240,24 @@ Either - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1502,15 +1508,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1540,38 +1564,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - @@ -1799,6 +1805,179 @@ Thunar tab + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Daemon</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon port: + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 0 0 65535 1 10 10 + + + False + False + 1 + + + + + False + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Port</b> + True + + + label_item + + + + + False + False + 5 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Allow Remote Connections + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Connections</b> + True + + + label_item + + + + + False + False + 5 + 3 + + + + + + + + + 5 + + + + + + tab + + True @@ -1882,7 +2061,7 @@ Thunar - 5 + 6 diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index d03522b4d..49f797ef6 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -63,7 +63,7 @@ class Preferences(component.Component): # Add the default categories i = 0 for category in ["Downloads", "Network", "Bandwidth", "Interface", - "Other", "Plugins"]: + "Other", "Daemon", "Plugins"]: self.liststore.append([i, category]) i += 1 @@ -189,7 +189,11 @@ class Preferences(component.Component): "spin_max_connections_per_torrent": \ ("value", self.core_config["max_connections_per_torrent"]), "spin_max_upload_slots_per_torrent": \ - ("value", self.core_config["max_upload_slots_per_torrent"]) + ("value", self.core_config["max_upload_slots_per_torrent"]), + "spin_daemon_port": \ + ("value", self.core_config["daemon_port"]), + "chk_allow_remote_connections": \ + ("active", self.core_config["allow_remote"]) } # Update the widgets accordingly @@ -237,7 +241,9 @@ class Preferences(component.Component): "spin_max_upload", "spin_max_upload_slots_global", "spin_max_connections_per_torrent", - "spin_max_upload_slots_per_torrent" + "spin_max_upload_slots_per_torrent", + "spin_daemon_port", + "chk_allow_remote_connections" ] # We don't appear to be connected to a daemon for key in core_widget_list: @@ -255,7 +261,7 @@ class Preferences(component.Component): self.glade.get_widget("chk_enable_files_dialog").set_active( self.gtkui_config["enable_files_dialog"]) - ## Other tab ## + ## Interface tab ## self.glade.get_widget("chk_use_tray").set_active( self.gtkui_config["enable_system_tray"]) self.glade.get_widget("chk_min_on_close").set_active( @@ -272,7 +278,8 @@ class Preferences(component.Component): self.gtkui_config["open_folder_stock"]) self.glade.get_widget("radio_open_folder_custom").set_active( not self.gtkui_config["open_folder_stock"]) - + + ## Other tab ## self.glade.get_widget("chk_new_releases").set_active( self.gtkui_config["check_new_releases"]) @@ -363,7 +370,7 @@ class Preferences(component.Component): self.glade.get_widget( "spin_max_upload_slots_per_torrent").get_value_as_int() - ## Other tab ## + ## Interface tab ## new_gtkui_config["enable_system_tray"] = \ self.glade.get_widget("chk_use_tray").get_active() new_gtkui_config["close_to_tray"] = \ @@ -382,13 +389,20 @@ class Preferences(component.Component): self.glade.get_widget("txt_open_folder_location").get_text() new_gtkui_config["open_folder_stock"] = \ self.glade.get_widget("radio_open_folder_stock").get_active() - + + ## Other tab ## new_gtkui_config["check_new_releases"] = \ self.glade.get_widget("chk_new_releases").get_active() new_gtkui_config["send_info"] = \ self.glade.get_widget("chk_send_info").get_active() + ## Daemon tab ## + new_core_config["daemon_port"] = \ + self.glade.get_widget("spin_daemon_port").get_value_as_int() + new_core_config["allow_remote"] = \ + self.glade.get_widget("chk_allow_remote_connections").get_active() + # GtkUI for key in new_gtkui_config.keys(): # The values do not match so this needs to be updated From fe33a72022ffee554cc9ecda2e3afd8a06e113df Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 25 Nov 2007 02:16:54 +0000 Subject: [PATCH 0269/1009] Various interface improvements. Initial import of SideBar. --- deluge/ui/gtkui/aboutdialog.py | 2 +- deluge/ui/gtkui/glade/main_window.glade | 1433 ++++++++++++----------- deluge/ui/gtkui/gtkui.py | 2 + deluge/ui/gtkui/menubar.py | 23 +- deluge/ui/gtkui/sidebar.py | 73 ++ deluge/ui/gtkui/torrentdetails.py | 8 + 6 files changed, 883 insertions(+), 658 deletions(-) create mode 100644 deluge/ui/gtkui/sidebar.py diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index e53ef24f4..845664f59 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -52,7 +52,7 @@ class AboutDialog: self.about.set_name("Deluge") self.about.set_version(deluge.common.get_version()) self.about.set_authors(["Andrew Resch", "Marcos Pinto"]) - self.about.set_artists(["Andrew Wedderburn"]) + self.about.set_artists(["Andrew Wedderburn", "Andrew Resch"]) self.about.set_translator_credits(_("translator-credits")) self.about.set_license(_("Deluge is free software, you can redistribute \ it and/or\nmodify it under the terms of the GNU General Public\n License as \ diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index ab7c5a27f..3d4e61181 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -156,6 +156,16 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Sidebar + True + True + + + True @@ -183,6 +193,63 @@ True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Homepage + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-home + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Frequently Asked Questions + FAQ + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-dialog-question + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Community + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-info + 1 + + + + + + + True + + True @@ -204,6 +271,7 @@ True + GTK_TOOLBAR_ICONS GTK_ICON_SIZE_SMALL_TOOLBAR @@ -329,20 +397,57 @@ True - + True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_OUT + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True - True - True - True - False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT + + + True + True + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + True + True + False + + + + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT + + + True + True + True + True + True + False + + + + + True + False + @@ -351,695 +456,711 @@ - + True - False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE - + True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER + False - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER - + True - 1 - 2 - 10 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - - - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 + 5 + 5 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 + 1 + 2 + 10 - + True - 7 - 2 - 2 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 + 10 + 10 + 15 + 15 - + True - 0 - 1 - <b># of files:</b> - True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + - - 3 - 4 - GTK_FILL - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - + <b>Torrent Info</b> + True - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 + label_item + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - 1 - 2 - GTK_FILL - + + + True + Details + + + tab + False + + - - - True - Details - - - tab - False - - False diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 12a6e47bd..785eb05b3 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -45,6 +45,7 @@ from menubar import MenuBar from toolbar import ToolBar from torrentview import TorrentView from torrentdetails import TorrentDetails +from sidebar import SideBar from preferences import Preferences from systemtray import SystemTray from statusbar import StatusBar @@ -121,6 +122,7 @@ class GtkUI: self.toolbar = ToolBar() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() + self.sidebar = SideBar() self.preferences = Preferences() self.systemtray = SystemTray() self.statusbar = StatusBar() diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index c13c60955..f60354f5c 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -77,9 +77,14 @@ class MenuBar(component.Component): ## View Menu "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, + "on_menuitem_sidebar_toggled": self.on_menuitem_sidebar_toggled, "on_menuitem_infopane_toggled": self.on_menuitem_infopane_toggled, ## Help Menu + "on_menuitem_homepage_activate": self.on_menuitem_homepage_activate, + "on_menuitem_faq_activate": self.on_menuitem_faq_activate, + "on_menuitem_community_activate": \ + self.on_menuitem_community_activate, "on_menuitem_about_activate": self.on_menuitem_about_activate }) @@ -209,12 +214,28 @@ class MenuBar(component.Component): def on_menuitem_toolbar_toggled(self, value): log.debug("on_menuitem_toolbar_toggled") component.get("ToolBar").visible(value.get_active()) - + + def on_menuitem_sidebar_toggled(self, value): + log.debug("on_menuitem_sidebar_toggled") + component.get("SideBar").visible(value.get_active()) + def on_menuitem_infopane_toggled(self, value): log.debug("on_menuitem_infopane_toggled") component.get("TorrentDetails").visible(value.get_active()) ## Help Menu ## + def on_menuitem_homepage_activate(self, data=None): + log.debug("on_menuitem_homepage_activate") + client.open_url_in_browser("http://deluge-torrent.org") + + def on_menuitem_faq_activate(self, data=None): + log.debug("on_menuitem_faq_activate") + client.open_url_in_browser("http://deluge-torrent.org/faq") + + def on_menuitem_community_activate(self, data=None): + log.debug("on_menuitem_community_activate") + client.open_url_in_browser("http://forum.deluge-torrent.org/") + def on_menuitem_about_activate(self, data=None): log.debug("on_menuitem_about_activate") from aboutdialog import AboutDialog diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py new file mode 100644 index 000000000..c1ae198bf --- /dev/null +++ b/deluge/ui/gtkui/sidebar.py @@ -0,0 +1,73 @@ +# +# sidebar.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gtk +import gtk.glade + +import deluge.ui.component as component +from deluge.log import LOG as log + +class SideBar(component.Component): + def __init__(self): + component.Component.__init__(self, "SideBar") + self.window = component.get("MainWindow") + glade = self.window.main_glade + self.label_view = glade.get_widget("label_view") + self.hpaned = glade.get_widget("hpaned") + self.scrolled = glade.get_widget("scrolledwindow_sidebar") + self.is_visible = True + + # Create the liststore + self.liststore = gtk.ListStore(str, gtk.gdk.Pixbuf) + + # Create the column + column = gtk.TreeViewColumn(_("Labels")) + render = gtk.CellRendererPixbuf() + column.pack_start(render, expand=False) + column.add_attribute(render, 'pixbuf', 1) + render = gtk.CellRendererText() + column.pack_start(render, expand=True) + column.add_attribute(render, 'text', 0) + self.label_view.append_column(column) + + self.label_view.set_model(self.liststore) + + def visible(self, visible): + if visible: + self.scrolled.show() + else: + self.scrolled.hide() + self.hpaned.set_position(-1) + + self.is_visible = visible + diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 4204a110f..72618108e 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -52,6 +52,10 @@ class TorrentDetails(component.Component): self.notebook = glade.get_widget("torrent_info") self.details_tab = glade.get_widget("torrentdetails_tab") + # Don't show tabs if there is only 1 + if self.notebook.get_n_pages() < 2: + self.notebook.set_show_tabs(False) + self.is_visible = True # Get the labels we need to update. @@ -88,6 +92,10 @@ class TorrentDetails(component.Component): self.clear() def update(self): + # Show tabs if more than 1 page + if self.notebook.get_n_pages() > 1: + self.notebook.set_show_tabs(True) + # Only update if this page is showing if self.notebook.page_num(self.details_tab) is \ self.notebook.get_current_page() and \ From fcd70cbb44ed0c7fac43a5597c7a5b0ac90c8ed5 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 25 Nov 2007 09:58:12 +0000 Subject: [PATCH 0270/1009] Registering a signal receiver with the core is done with the clients ip address. Minor UI fix-ups. Hook up autoadd folder options in Preferences. --- TODO | 4 + deluge/common.py | 2 +- deluge/core/core.py | 22 +- deluge/core/signalmanager.py | 14 +- deluge/ui/client.py | 6 +- deluge/ui/gtkui/glade/main_window.glade | 1513 +++++++++-------- .../ui/gtkui/glade/preferences_dialog.glade | 298 ++-- deluge/ui/gtkui/gtkui.py | 4 +- deluge/ui/gtkui/listview.py | 2 +- deluge/ui/gtkui/preferences.py | 18 + deluge/ui/gtkui/sidebar.py | 10 +- deluge/ui/gtkui/torrentdetails.py | 4 +- deluge/ui/signalreceiver.py | 5 +- 13 files changed, 1001 insertions(+), 901 deletions(-) diff --git a/TODO b/TODO index cae96a993..c60a020ce 100644 --- a/TODO +++ b/TODO @@ -20,3 +20,7 @@ * Add sidebar for labels and other things.. Plugins should be able to add their own section to this. * Option for adding torrents in paused/active state +* Add filtering to torrentview to use with the sidebar +* Fix up preferences for when using a remote host.. the download folders, etc.. +* Add decay items to statusbar.. items that will disappear after X seconds + diff --git a/deluge/common.py b/deluge/common.py index e0cb2900c..a558fef52 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -90,7 +90,7 @@ def get_pixmap(fname): """Returns a pixmap file included with deluge""" return pkg_resources.resource_filename("deluge", os.path.join("data", \ "pixmaps", fname)) - + def get_logo(size): """Returns a deluge logo pixbuf based on the size parameter.""" import gtk diff --git a/deluge/core/core.py b/deluge/core/core.py index d60b6b53a..be3b3f000 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -79,7 +79,9 @@ DEFAULT_PREFS = { "max_upload_slots_global": -1, "max_connections_per_torrent": -1, "max_upload_slots_per_torrent": -1, - "enabled_plugins": [] + "enabled_plugins": [], + "autoadd_location": "", + "autoadd_enable": False } class Core( @@ -90,6 +92,8 @@ class Core( log.debug("Core init..") threading.Thread.__init__(self) + self.client_address = None + # Get config self.config = ConfigManager("core.conf", DEFAULT_PREFS) @@ -130,6 +134,14 @@ class Core( gettext.install("deluge", pkg_resources.resource_filename( "deluge", "i18n")) + + def get_request(self): + """Get the request and client address from the socket. + We override this so that we can get the ip address of the client. + """ + request, client_address = self.socket.accept() + self.client_address = client_address[0] + return (request, client_address) def run(self): """Starts the core""" @@ -230,14 +242,14 @@ class Core( # Make shutdown an async call gobject.idle_add(self._shutdown) - def export_register_client(self, uri): + def export_register_client(self, port): """Registers a client with the signal manager so that signals are sent to it.""" - self.signals.register_client(uri) + self.signals.register_client(self.client_address, port) - def export_deregister_client(self, uri): + def export_deregister_client(self): """De-registers a client with the signal manager.""" - self.signals.deregister_client(uri) + self.signals.deregister_client(self.client_address) def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index 270414193..d632f9c52 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -39,13 +39,17 @@ class SignalManager: def __init__(self): self.clients = {} - def deregister_client(self, uri): + def deregister_client(self, address): """Deregisters a client""" - log.debug("Deregistering %s as a signal reciever..", uri) - del self.clients[uri] - - def register_client(self, uri): + log.debug("Deregistering %s as a signal reciever..", address) + for client in self.clients: + if client[:len(address)] == address: + del self.clients[client] + break + + def register_client(self, address, port): """Registers a client to emit signals to.""" + uri = "http://" + str(address) + ":" + str(port) log.debug("Registering %s as a signal reciever..", uri) self.clients[uri] = xmlrpclib.ServerProxy(uri) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 75dd8c788..9d2c06b0e 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -66,9 +66,11 @@ class CoreProxy: self._core = None return - if uri != self._uri: + if uri != self._uri and self._uri != None: self._core = None - + for callback in self._on_no_core_callbacks: + callback() + self._uri = uri # Get a new core self.get_core() diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 3d4e61181..91a5fb010 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -394,30 +394,776 @@ - + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 - + True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_OUT - + True True - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - True - True - False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT + + + True + True + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + True + False + + + + + False + False + + + + + True + False + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT + + + True + True + True + True + True + False + + + + + True + False + + + + + True + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + False + False + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 5 + + + True + 1 + 2 + 10 + + + True + 0 + + + True + 10 + 10 + 15 + 15 + + + True + 5 + + + True + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 + + + True + 0 + + + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 + + + True + 0 + 1 + <b>Availability:</b> + True + + + + + 2 + 3 + 4 + 5 + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + + + + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Statistics</b> + True + + + label_item + + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + + + + + + + + + + + + True + Details + + + tab + False + + @@ -426,746 +1172,7 @@ False - - - True - False - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_OUT - - - True - True - True - True - True - False - - - - - True - False - - - - True - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - - - True - False - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 5 - - - True - 1 - 2 - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - - - - True - 0 - - - True - 10 - 10 - 15 - 15 - - - True - 5 - - - True - 0.10000000149 - - - False - False - - - - - True - 5 - 4 - 5 - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - - - - - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - - GTK_FILL - - - - - - - - - - - - - True - Details - - - tab - False - - - - - - - False - False - diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 0c8266f0e..204f8141a 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -249,34 +249,76 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrent files that are placed in this folder. - Auto Add Folder: - 0 - True + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrent files that are placed in this folder. + Client Auto Add: + 0 + True + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + False + False + 1 + + - - False - False - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon Auto Add: + 0 + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + False + 1 + + - False - False 1 @@ -1036,40 +1078,71 @@ Either 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -1094,74 +1167,43 @@ Either - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1205,24 +1247,29 @@ Either 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1240,24 +1287,19 @@ Either - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1508,33 +1550,15 @@ Either 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1564,20 +1588,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 785eb05b3..63226f6ba 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -85,7 +85,9 @@ DEFAULT_PREFS = { "autoconnect": False, "autoconnect_host_uri": None, "autostart_localhost": False, - "autoadd_queued": False + "autoadd_queued": False, + "autoadd_enable": False, + "autoadd_location": "" } class GtkUI: diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 369d144a1..7a32d054c 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -306,7 +306,7 @@ class ListView: self.columns[header].column_indices[text]) elif column_type == None: return - + column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) column.set_resizable(True) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 49f797ef6..742684a6c 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -158,6 +158,10 @@ class Preferences(component.Component): ("filename", self.core_config["download_location"]), "torrent_files_button": \ ("filename", self.core_config["torrentfiles_location"]), + "chk_autoadd_daemon": \ + ("active", self.core_config["autoadd_enable"]), + "entry_autoadd_daemon": \ + ("text", self.core_config["autoadd_location"]), "radio_compact_allocation": \ ("active", self.core_config["compact_allocation"]), "radio_full_allocation": \ @@ -220,6 +224,8 @@ class Preferences(component.Component): core_widget_list = [ "download_path_button", "torrent_files_button", + "chk_autoadd_daemon", + "entry_autoadd_daemon", "radio_compact_allocation", "radio_full_allocation", "chk_prioritize_first_last_pieces", @@ -258,6 +264,10 @@ class Preferences(component.Component): self.gtkui_config["interactive_add"]) self.glade.get_widget("radio_save_all_to").set_active( not self.gtkui_config["interactive_add"]) + self.glade.get_widget("chk_autoadd_folder").set_active( + self.gtkui_config["autoadd_enable"]) + self.glade.get_widget("autoadd_folder_button").set_filename( + self.gtkui_config["autoadd_location"]) self.glade.get_widget("chk_enable_files_dialog").set_active( self.gtkui_config["enable_files_dialog"]) @@ -318,6 +328,14 @@ class Preferences(component.Component): self.glade.get_widget("download_path_button").get_filename() new_core_config["torrentfiles_location"] = \ self.glade.get_widget("torrent_files_button").get_filename() + new_gtkui_config["autoadd_enable"] = \ + self.glade.get_widget("chk_autoadd_folder").get_active() + new_gtkui_config["autoadd_location"] = \ + self.glade.get_widget("autoadd_folder_button").get_filename() + new_core_config["autoadd_enable"] = \ + self.glade.get_widget("chk_autoadd_daemon").get_active() + new_core_config["autoadd_location"] = \ + self.glade.get_widget("entry_autoadd_daemon").get_text() new_core_config["compact_allocation"] = \ self.glade.get_widget("radio_compact_allocation").get_active() new_core_config["prioritize_first_last_pieces"] = \ diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index c1ae198bf..363527517 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -35,6 +35,7 @@ import gtk import gtk.glade import deluge.ui.component as component +import deluge.common from deluge.log import LOG as log class SideBar(component.Component): @@ -49,9 +50,16 @@ class SideBar(component.Component): # Create the liststore self.liststore = gtk.ListStore(str, gtk.gdk.Pixbuf) - + self.liststore.append([_("All"), None]) + self.liststore.append([_("Downloading"), + gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("downloading16.png"))]) + self.liststore.append([_("Seeding"), + gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("seeding16.png"))]) # Create the column column = gtk.TreeViewColumn(_("Labels")) + column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) render = gtk.CellRendererPixbuf() column.pack_start(render, expand=False) column.add_attribute(render, 'pixbuf', 1) diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 72618108e..be985887a 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -55,7 +55,9 @@ class TorrentDetails(component.Component): # Don't show tabs if there is only 1 if self.notebook.get_n_pages() < 2: self.notebook.set_show_tabs(False) - + else: + self.notebook.set_show_tabs(True) + self.is_visible = True # Get the labels we need to update. diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 6def2edbc..1141d9019 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -77,15 +77,14 @@ class SignalReceiver( # Register the signal receiver with the core # FIXME: send actual URI not localhost core = client.get_core() - core.register_client("http://localhost:" + str(port)) + core.register_client(str(port)) def shutdown(self): """Shutdowns receiver thread""" self._shutdown = True # De-register with the daemon so it doesn't try to send us more signals try: - client.get_core().deregister_client( - "http://localhost:" + str(self.port)) + client.get_core().deregister_client() except (socket.error, AttributeError): pass From d3890efaf94a1bb184d425caa53a1786ede5a1ed Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 27 Nov 2007 05:07:01 +0000 Subject: [PATCH 0271/1009] Add removetorrentdialog.py --- deluge/ui/gtkui/removetorrentdialog.py | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 deluge/ui/gtkui/removetorrentdialog.py diff --git a/deluge/ui/gtkui/removetorrentdialog.py b/deluge/ui/gtkui/removetorrentdialog.py new file mode 100644 index 000000000..839985daa --- /dev/null +++ b/deluge/ui/gtkui/removetorrentdialog.py @@ -0,0 +1,72 @@ +# +# removetorrentdialog.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gtk, gtk.glade +import pkg_resources + +import deluge.common +import deluge.ui.client as client +import deluge.ui.component as component +from deluge.log import LOG as log + +class RemoveTorrentDialog: + def __init__(self, torrent_ids): + self.torrent_ids = torrent_ids + self.glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/remove_torrent_dialog.glade")) + + self.dialog = self.glade.get_widget("remove_torrent_dialog") + self.dialog.set_icon(deluge.common.get_logo(32)) + self.dialog.set_transient_for(component.get("MainWindow").window) + + self.glade.signal_autoconnect({ + "on_button_ok_clicked": self.on_button_ok_clicked, + "on_button_cancel_clicked": self.on_button_cancel_clicked + }) + + def run(self): + if self.torrent_ids == None or self.torrent_ids == []: + self.dialog.destroy() + return + self.dialog.show() + + def on_button_ok_clicked(self, widget): + data = self.glade.get_widget("chk_data").get_active() + torrent = self.glade.get_widget("chk_torrents").get_active() + client.remove_torrent(self.torrent_ids, torrent, data) + self.dialog.destroy() + + def on_button_cancel_clicked(self, widget): + self.dialog.destroy() + From dae1472f616b448a0f567fe166f09f7b6f9d710e Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Wed, 28 Nov 2007 03:29:53 +0000 Subject: [PATCH 0272/1009] lt sync 1787 --- libtorrent/include/asio/error_code.hpp | 4 +- libtorrent/include/libtorrent/alert.hpp | 4 + .../include/libtorrent/aux_/session_impl.hpp | 2 + libtorrent/include/libtorrent/session.hpp | 3 + .../include/libtorrent/session_settings.hpp | 4 + libtorrent/include/libtorrent/time.hpp | 1 + libtorrent/include/libtorrent/torrent.hpp | 11 +- .../include/libtorrent/torrent_info.hpp | 10 +- libtorrent/src/alert.cpp | 26 + libtorrent/src/kademlia/dht_tracker.cpp | 3 + libtorrent/src/kademlia/rpc_manager.cpp | 16 +- libtorrent/src/session.cpp | 5 + libtorrent/src/session_impl.cpp | 10 + libtorrent/src/socks4_stream.cpp | 23 +- libtorrent/src/socks5_stream.cpp | 53 +- libtorrent/src/storage.cpp | 18 +- libtorrent/src/torrent.cpp | 155 ++++- libtorrent/src/torrent_handle.cpp | 637 +++++------------- libtorrent/src/torrent_info.cpp | 35 +- libtorrent/src/web_peer_connection.cpp | 44 +- 20 files changed, 510 insertions(+), 554 deletions(-) diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp index 989898ce5..516d599d7 100644 --- a/libtorrent/include/asio/error_code.hpp +++ b/libtorrent/include/asio/error_code.hpp @@ -41,10 +41,10 @@ namespace error system_category = ASIO_WIN_OR_POSIX(0, 0), /// Error codes from NetDB functions. - netdb_category = ASIO_WIN_OR_POSIX(_system_category, 1), + netdb_category = ASIO_WIN_OR_POSIX(system_category, 1), /// Error codes from getaddrinfo. - addrinfo_category = ASIO_WIN_OR_POSIX(_system_category, 2), + addrinfo_category = ASIO_WIN_OR_POSIX(system_category, 2), /// Miscellaneous error codes. misc_category = ASIO_WIN_OR_POSIX(3, 3), diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp index 954e39ef5..ab8065f1f 100755 --- a/libtorrent/include/libtorrent/alert.hpp +++ b/libtorrent/include/libtorrent/alert.hpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #include #include @@ -99,10 +100,13 @@ namespace libtorrent { void set_severity(alert::severity_t severity); bool should_post(alert::severity_t severity) const; + alert const* wait_for_alert(time_duration max_wait); + private: std::queue m_alerts; alert::severity_t m_severity; mutable boost::mutex m_mutex; + boost::condition m_condition; }; struct TORRENT_EXPORT unhandled_alert : std::exception diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 95089b649..cf627c70b 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -280,6 +280,8 @@ namespace libtorrent void set_severity_level(alert::severity_t s); std::auto_ptr pop_alert(); + alert const* wait_for_alert(time_duration max_wait); + int upload_rate_limit() const; int download_rate_limit() const; diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 1d29e03b3..d2ab6ab2e 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -60,6 +60,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/session_status.hpp" #include "libtorrent/version.hpp" #include "libtorrent/fingerprint.hpp" +#include "libtorrent/time.hpp" #include "libtorrent/storage.hpp" @@ -264,6 +265,8 @@ namespace libtorrent std::auto_ptr pop_alert(); void set_severity_level(alert::severity_t s); + alert const* wait_for_alert(time_duration max_wait); + connection_queue& get_connection_queue(); // starts/stops UPnP, NATPMP or LSD port mappers diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index f1f9d190c..df393692a 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -95,6 +95,7 @@ namespace libtorrent , peer_timeout(120) , urlseed_timeout(20) , urlseed_pipeline_size(5) + , urlseed_wait_retry(30) , file_pool_size(40) , allow_multiple_connections_per_ip(false) , max_failcount(3) @@ -185,6 +186,9 @@ namespace libtorrent // controls the pipelining size of url-seeds int urlseed_pipeline_size; + + // time to wait until a new retry takes place + int urlseed_wait_retry; // sets the upper limit on the total number of files this // session will keep open. The reason why files are diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index 1aae81d3a..4ab7a3819 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -98,6 +98,7 @@ namespace libtorrent time_duration() {} time_duration operator/(int rhs) const { return time_duration(diff / rhs); } explicit time_duration(boost::int64_t d) : diff(d) {} + time_duration& operator-=(time_duration const& c) { diff -= c.diff; return *this; } boost::int64_t diff; }; diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 7aa779081..0f508e93f 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -206,7 +206,7 @@ namespace libtorrent tcp::endpoint const& get_interface() const { return m_net_interface; } void connect_to_url_seed(std::string const& url); - bool connect_to_peer(policy::peer* peerinfo) throw(); + bool connect_to_peer(policy::peer* peerinfo); void set_ratio(float ratio) { TORRENT_ASSERT(ratio >= 0.0f); m_ratio = ratio; } @@ -254,6 +254,8 @@ namespace libtorrent void remove_url_seed(std::string const& url) { m_web_seeds.erase(url); } + void retry_url_seed(std::string const& url); + std::set url_seeds() const { return m_web_seeds; } @@ -293,6 +295,9 @@ namespace libtorrent void resolve_peer_country(boost::intrusive_ptr const& p) const; + void get_peer_info(std::vector& v); + void get_download_queue(std::vector& queue); + // -------------------------------------------- // TRACKER MANAGEMENT @@ -623,6 +628,10 @@ namespace libtorrent // The list of web seeds in this torrent. Seeds // with fatal errors are removed from the set std::set m_web_seeds; + + // a list of web seeds that have failed and are + // waiting to be retried + std::map m_web_seeds_next_retry; // urls of the web seeds that we are currently // resolving the address for diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index 16eebf234..a064936ff 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -69,9 +69,15 @@ namespace libtorrent struct TORRENT_EXPORT file_entry { + file_entry(): offset(0), size(0), file_base(0) {} + fs::path path; size_type offset; // the offset of this file inside the torrent size_type size; // the size of this file + // the offset in the file where the storage starts. + // This is always 0 unless parts of the torrent is + // compressed into a single file, such as a so-called part file. + size_type file_base; // if the path was incorrectly encoded, this is // the original corrupt encoded string. It is // preserved in order to be able to reproduce @@ -117,7 +123,7 @@ namespace libtorrent void add_file(fs::path file, size_type size); void add_url_seed(std::string const& url); - bool remap_files(std::vector > const& map); + bool remap_files(std::vector const& map); std::vector map_block(int piece, size_type offset , int size, bool storage = false) const; @@ -271,7 +277,7 @@ namespace libtorrent // this vector is typically empty. If it is not // empty, it means the user has re-mapped the - // files in this torrent to diffefrent names + // files in this torrent to different names // on disk. This is only used when reading and // writing the disk. std::vector m_remapped_files; diff --git a/libtorrent/src/alert.cpp b/libtorrent/src/alert.cpp index 1401a5e4a..cb89147da 100755 --- a/libtorrent/src/alert.cpp +++ b/libtorrent/src/alert.cpp @@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include "libtorrent/alert.hpp" +#include namespace libtorrent { @@ -77,6 +78,30 @@ namespace libtorrent { } } + alert const* alert_manager::wait_for_alert(time_duration max_wait) + { + boost::mutex::scoped_lock lock(m_mutex); + + if (!m_alerts.empty()) return m_alerts.front(); + + int secs = total_seconds(max_wait); + max_wait -= seconds(secs); + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += secs; + boost::int64_t nsec = xt.nsec + total_microseconds(max_wait) * 1000; + if (nsec > 1000000000) + { + nsec -= 1000000000; + xt.sec += 1; + } + xt.nsec = nsec; + if (!m_condition.timed_wait(lock, xt)) return 0; + TORRENT_ASSERT(!m_alerts.empty()); + if (m_alerts.empty()) return 0; + return m_alerts.front(); + } + void alert_manager::post_alert(const alert& alert_) { boost::mutex::scoped_lock lock(m_mutex); @@ -90,6 +115,7 @@ namespace libtorrent { delete result; } m_alerts.push(alert_.clone().release()); + m_condition.notify_all(); } std::auto_ptr alert_manager::get() diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index f32db06d4..3f47eb070 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -273,6 +273,9 @@ namespace libtorrent { namespace dht udp::endpoint ep(listen_interface, listen_port); m_socket.open(ep.protocol()); m_socket.bind(ep); + m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] + , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] + , m_strand.wrap(bind(&dht_tracker::on_receive, self(), _1, _2))); } void dht_tracker::tick(asio::error_code const& e) diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp index 086b8fc44..5ae448501 100644 --- a/libtorrent/src/kademlia/rpc_manager.cpp +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -226,6 +226,10 @@ bool rpc_manager::incoming(msg const& m) std::ofstream reply_stats("libtorrent_logs/round_trip_ms.log", std::ios::app); reply_stats << m.addr << "\t" << total_milliseconds(time_now() - o->sent) << std::endl; +#endif +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with transaction id: " + << tid << " from " << m.addr; #endif o->reply(m); m_transactions[tid] = 0; @@ -284,6 +288,10 @@ time_duration rpc_manager::tick() try { m_transactions[m_oldest_transaction_id] = 0; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Timing out transaction id: " + << m_oldest_transaction_id << " from " << o->target_addr; +#endif timeouts.push_back(o); } catch (std::exception) {} } @@ -309,7 +317,13 @@ unsigned int rpc_manager::new_transaction_id(observer_ptr o) // moving the observer into the set of aborted transactions // it will prevent it from spawning new requests right now, // since that would break the invariant - m_aborted_transactions.push_back(m_transactions[m_next_transaction_id]); + observer_ptr o = m_transactions[m_next_transaction_id]; + m_aborted_transactions.push_back(o); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "[new_transaction_id] Aborting message with transaction id: " + << m_next_transaction_id << " sent to " << o->target_addr + << " " << total_seconds(time_now() - o->sent) << " seconds ago"; +#endif m_transactions[m_next_transaction_id] = 0; TORRENT_ASSERT(m_oldest_transaction_id == m_next_transaction_id); } diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 0b8aecff7..331ffa377 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -422,6 +422,11 @@ namespace libtorrent return m_impl->pop_alert(); } + alert const* session::wait_for_alert(time_duration max_wait) + { + return m_impl->wait_for_alert(max_wait); + } + void session::set_severity_level(alert::severity_t s) { m_impl->set_severity_level(s); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 69f2c1bc1..833d49777 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -2251,6 +2251,11 @@ namespace detail return m_alerts.get(); return std::auto_ptr(0); } + + alert const* session_impl::wait_for_alert(time_duration max_wait) + { + return m_alerts.wait_for_alert(max_wait); + } void session_impl::set_severity_level(alert::severity_t s) { @@ -2414,6 +2419,11 @@ namespace detail { TORRENT_ASSERT(false); } + for (std::map >::const_iterator j + = m_torrents.begin(); j != m_torrents.end(); ++j) + { + TORRENT_ASSERT(boost::get_pointer(j->second)); + } } #endif diff --git a/libtorrent/src/socks4_stream.cpp b/libtorrent/src/socks4_stream.cpp index 3a31b2375..c96dde85b 100644 --- a/libtorrent/src/socks4_stream.cpp +++ b/libtorrent/src/socks4_stream.cpp @@ -43,7 +43,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -54,8 +55,8 @@ namespace libtorrent if (i == tcp::resolver::iterator()) { asio::error_code ec = asio::error::operation_not_supported; - (*h)(e); - close(); + (*h)(ec); + close(ec); return; } @@ -68,7 +69,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -93,7 +95,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -107,7 +110,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -119,8 +123,9 @@ namespace libtorrent if (reply_version != 0) { - (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec = asio::error::operation_not_supported; + (*h)(ec); + close(ec); return; } @@ -140,7 +145,7 @@ namespace libtorrent case 93: ec = asio::error::no_permission; break; } (*h)(ec); - close(); + close(ec); } } diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp index a40cd33d0..f0c41c02b 100644 --- a/libtorrent/src/socks5_stream.cpp +++ b/libtorrent/src/socks5_stream.cpp @@ -44,7 +44,8 @@ namespace libtorrent if (e || i == tcp::resolver::iterator()) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -57,7 +58,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -86,7 +88,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -100,7 +103,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -113,7 +117,8 @@ namespace libtorrent if (version < 5) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } @@ -126,7 +131,8 @@ namespace libtorrent if (m_user.empty()) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } @@ -144,7 +150,8 @@ namespace libtorrent else { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } } @@ -155,7 +162,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -170,7 +178,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -183,19 +192,21 @@ namespace libtorrent if (version != 1) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } if (status != 0) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } std::vector().swap(m_buffer); - (*h)(e); + socks_connect(h); } void socks5_stream::socks_connect(boost::shared_ptr h) @@ -222,7 +233,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -236,7 +248,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -248,7 +261,8 @@ namespace libtorrent if (version < 5) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } int response = read_uint8(p); @@ -267,7 +281,8 @@ namespace libtorrent case 8: e = asio::error::address_family_not_supported; break; } (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } p += 1; // reserved @@ -291,7 +306,8 @@ namespace libtorrent else { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } m_buffer.resize(skip_bytes); @@ -305,7 +321,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index cf0781e6a..07878e7ee 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -768,10 +768,10 @@ namespace libtorrent TORRENT_ASSERT(file_offset < file_iter->size); - TORRENT_ASSERT(slices[0].offset == file_offset); + TORRENT_ASSERT(slices[0].offset == file_offset + file_iter->file_base); - size_type new_pos = in->seek(file_offset); - if (new_pos != file_offset) + size_type new_pos = in->seek(file_offset + file_iter->file_base); + if (new_pos != file_offset + file_iter->file_base) { // the file was not big enough if (!fill_zero) @@ -782,7 +782,7 @@ namespace libtorrent #ifndef NDEBUG size_type in_tell = in->tell(); - TORRENT_ASSERT(in_tell == file_offset); + TORRENT_ASSERT(in_tell == file_offset + file_iter->file_base); #endif int left_to_read = size; @@ -846,7 +846,7 @@ namespace libtorrent file_offset = 0; in = m_files.open_file( this, path, file::in); - in->seek(0); + in->seek(file_iter->file_base); } } return result; @@ -892,11 +892,11 @@ namespace libtorrent this, p, file::out | file::in); TORRENT_ASSERT(file_offset < file_iter->size); - TORRENT_ASSERT(slices[0].offset == file_offset); + TORRENT_ASSERT(slices[0].offset == file_offset + file_iter->file_base); - size_type pos = out->seek(file_offset); + size_type pos = out->seek(file_offset + file_iter->file_base); - if (pos != file_offset) + if (pos != file_offset + file_iter->file_base) { std::stringstream s; s << "no storage for slot " << slot; @@ -962,7 +962,7 @@ namespace libtorrent out = m_files.open_file( this, p, file::out | file::in); - out->seek(0); + out->seek(file_iter->file_base); } } } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 33a73d1f3..24b9f39fe 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -287,6 +287,10 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT bool torrent::should_announce_dht() const { + if (m_ses.m_listen_sockets.empty()) return false; + + if (!m_ses.m_dht) return false; + // don't announce private torrents if (m_torrent_file->is_valid() && m_torrent_file->priv()) return false; @@ -434,7 +438,8 @@ namespace libtorrent bind(&torrent::on_announce_disp, self, _1))); // announce with the local discovery service - m_ses.announce_lsd(m_torrent_file->info_hash()); + if (!m_paused) + m_ses.announce_lsd(m_torrent_file->info_hash()); } else { @@ -444,14 +449,12 @@ namespace libtorrent } #ifndef TORRENT_DISABLE_DHT + if (m_paused) return; if (!m_ses.m_dht) return; ptime now = time_now(); if (should_announce_dht() && now - m_last_dht_announce > minutes(14)) { m_last_dht_announce = now; - // TODO: There should be a way to abort an announce operation on the dht. - // when the torrent is destructed - if (m_ses.m_listen_sockets.empty()) return; m_ses.m_dht->announce(m_torrent_file->info_hash() , m_ses.m_listen_sockets.front().external_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); @@ -726,16 +729,17 @@ namespace libtorrent return tuple(0,0); const int last_piece = m_torrent_file->num_pieces() - 1; + const int piece_size = m_torrent_file->piece_length(); if (is_seed()) return make_tuple(m_torrent_file->total_size() , m_torrent_file->total_size()); size_type wanted_done = (m_num_pieces - m_picker->num_have_filtered()) - * m_torrent_file->piece_length(); + * piece_size; size_type total_done - = m_num_pieces * m_torrent_file->piece_length(); + = m_num_pieces * piece_size; TORRENT_ASSERT(m_num_pieces < m_torrent_file->num_pieces()); // if we have the last piece, we have to correct @@ -743,11 +747,17 @@ namespace libtorrent // assumed all pieces were of equal size if (m_have_pieces[last_piece]) { + TORRENT_ASSERT(total_done >= piece_size); int corr = m_torrent_file->piece_size(last_piece) - - m_torrent_file->piece_length(); + - piece_size; + TORRENT_ASSERT(corr <= 0); + TORRENT_ASSERT(corr > -piece_size); total_done += corr; if (m_picker->piece_priority(last_piece) != 0) + { + TORRENT_ASSERT(wanted_done >= piece_size); wanted_done += corr; + } } TORRENT_ASSERT(total_done <= m_torrent_file->total_size()); @@ -757,7 +767,7 @@ namespace libtorrent = m_picker->get_download_queue(); const int blocks_per_piece = static_cast( - m_torrent_file->piece_length() / m_block_size); + piece_size / m_block_size); for (std::vector::const_iterator i = dl_queue.begin(); i != dl_queue.end(); ++i) @@ -779,6 +789,7 @@ namespace libtorrent { TORRENT_ASSERT(m_picker->is_finished(piece_block(index, j)) == (i->info[j].state == piece_picker::block_info::state_finished)); corr += (i->info[j].state == piece_picker::block_info::state_finished) * m_block_size; + TORRENT_ASSERT(corr >= 0); TORRENT_ASSERT(index != last_piece || j < m_picker->blocks_in_last_piece() || i->info[j].state != piece_picker::block_info::state_finished); } @@ -1913,7 +1924,101 @@ namespace libtorrent } #endif - bool torrent::connect_to_peer(policy::peer* peerinfo) throw() + void torrent::get_peer_info(std::vector& v) + { + v.clear(); + for (peer_iterator i = begin(); + i != end(); ++i) + { + peer_connection* peer = *i; + + // incoming peers that haven't finished the handshake should + // not be included in this list + if (peer->associated_torrent().expired()) continue; + + v.push_back(peer_info()); + peer_info& p = v.back(); + + peer->get_peer_info(p); +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + if (resolving_countries()) + resolve_peer_country(intrusive_ptr(peer)); +#endif + } + } + + void torrent::get_download_queue(std::vector& queue) + { + queue.clear(); + if (!valid_metadata() || is_seed()) return; + piece_picker const& p = picker(); + std::vector const& q + = p.get_download_queue(); + + for (std::vector::const_iterator i + = q.begin(); i != q.end(); ++i) + { + partial_piece_info pi; + pi.piece_state = (partial_piece_info::state_t)i->state; + pi.blocks_in_piece = p.blocks_in_piece(i->index); + pi.finished = (int)i->finished; + pi.writing = (int)i->writing; + pi.requested = (int)i->requested; + int piece_size = torrent_file().piece_size(i->index); + for (int j = 0; j < pi.blocks_in_piece; ++j) + { + block_info& bi = pi.blocks[j]; + bi.state = i->info[j].state; + bi.block_size = j < pi.blocks_in_piece - 1 ? m_block_size + : piece_size - (j * m_block_size); + bool complete = bi.state == block_info::writing + || bi.state == block_info::finished; + if (i->info[j].peer == 0) + { + bi.peer = tcp::endpoint(); + bi.bytes_progress = complete ? bi.block_size : 0; + } + else + { + policy::peer* p = static_cast(i->info[j].peer); + if (p->connection) + { + bi.peer = p->connection->remote(); + if (bi.state == block_info::requested) + { + boost::optional pbp + = p->connection->downloading_piece_progress(); + if (pbp && pbp->piece_index == i->index && pbp->block_index == j) + { + bi.bytes_progress = pbp->bytes_downloaded; + TORRENT_ASSERT(bi.bytes_progress <= bi.block_size); + } + else + { + bi.bytes_progress = 0; + } + } + else + { + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + else + { + bi.peer = p->ip; + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + + pi.blocks[j].num_peers = i->info[j].num_peers; + } + pi.piece_index = i->index; + queue.push_back(pi); + } + + } + + bool torrent::connect_to_peer(policy::peer* peerinfo) { INVARIANT_CHECK; @@ -2249,16 +2354,18 @@ namespace libtorrent m_next_request = time_now() + seconds(delay); #ifndef TORRENT_DISABLE_DHT + if (m_abort) return; + // only start the announce if we want to announce with the dht - if (should_announce_dht()) + ptime now = time_now(); + if (should_announce_dht() && now - m_last_dht_announce > minutes(14)) { - if (m_abort) return; // force the DHT to reannounce - m_last_dht_announce = time_now() - minutes(15); + m_last_dht_announce = now; boost::weak_ptr self(shared_from_this()); - m_announce_timer.expires_from_now(seconds(1)); - m_announce_timer.async_wait(m_ses.m_strand.wrap( - bind(&torrent::on_announce_disp, self, _1))); + m_ses.m_dht->announce(m_torrent_file->info_hash() + , m_ses.m_listen_sockets.front().external_port + , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } #endif @@ -2747,6 +2854,18 @@ namespace libtorrent // ---- WEB SEEDS ---- + // re-insert urls that are to be retries into the m_web_seeds + typedef std::map::iterator iter_t; + for (iter_t i = m_web_seeds_next_retry.begin(); i != m_web_seeds_next_retry.end();) + { + iter_t erase_element = i++; + if (erase_element->second <= time_now()) + { + m_web_seeds.insert(erase_element->first); + m_web_seeds_next_retry.erase(erase_element); + } + } + // if we have everything we want we don't need to connect to any web-seed if (!is_finished() && !m_web_seeds.empty()) { @@ -2809,6 +2928,12 @@ namespace libtorrent } } + void torrent::retry_url_seed(std::string const& url) + { + m_web_seeds_next_retry[url] = time_now() + + seconds(m_ses.settings().urlseed_wait_retry); + } + bool torrent::try_connect_peer() { TORRENT_ASSERT(want_more_peers()); diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index aa517ac76..092d77c78 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -78,32 +78,90 @@ using boost::bind; using boost::mutex; using libtorrent::aux::session_impl; +#ifdef BOOST_NO_EXCEPTIONS + +#define TORRENT_FORWARD(call) \ + if (m_ses == 0) return; \ + TORRENT_ASSERT(m_chk); \ + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ + mutex::scoped_lock l2(m_chk->m_mutex); \ + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ + if (t == 0) return; \ + t->call + +#define TORRENT_FORWARD_RETURN(call, def) \ + if (m_ses == 0) return def; \ + TORRENT_ASSERT(m_chk); \ + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ + mutex::scoped_lock l2(m_chk->m_mutex); \ + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ + if (t == 0) return def; \ + return t->call + +#define TORRENT_FORWARD_RETURN2(call, def) \ + if (m_ses == 0) return def; \ + TORRENT_ASSERT(m_chk); \ + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ + mutex::scoped_lock l2(m_chk->m_mutex); \ + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ + if (t == 0) return def; \ + t->call + +#else + +#define TORRENT_FORWARD(call) \ + if (m_ses == 0) throw_invalid_handle(); \ + TORRENT_ASSERT(m_chk); \ + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ + mutex::scoped_lock l2(m_chk->m_mutex); \ + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ + if (t == 0) throw_invalid_handle(); \ + t->call + +#define TORRENT_FORWARD_RETURN(call, def) \ + if (m_ses == 0) throw_invalid_handle(); \ + TORRENT_ASSERT(m_chk); \ + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ + mutex::scoped_lock l2(m_chk->m_mutex); \ + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ + if (t == 0) return def; \ + return t->call + +#define TORRENT_FORWARD_RETURN2(call, def) \ + if (m_ses == 0) throw_invalid_handle(); \ + TORRENT_ASSERT(m_chk); \ + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ + mutex::scoped_lock l2(m_chk->m_mutex); \ + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ + if (t == 0) return def; \ + t->call + +#endif + namespace libtorrent { namespace fs = boost::filesystem; namespace { +#ifndef BOOST_NO_EXCEPTIONS void throw_invalid_handle() { throw invalid_handle(); } +#endif - boost::shared_ptr find_torrent( + torrent* find_torrent( session_impl* ses , aux::checker_impl* chk , sha1_hash const& hash) { aux::piece_checker_data* d = chk->find_torrent(hash); - if (d != 0) return d->torrent_ptr; + if (d != 0) return d->torrent_ptr.get(); boost::shared_ptr t = ses->find_torrent(hash).lock(); - if (t) return t; - - // throwing directly instead of calling - // the throw_invalid_handle() function - // avoids a warning in gcc - throw invalid_handle(); + if (t) return t.get(); + return 0; } } @@ -119,132 +177,68 @@ namespace libtorrent void torrent_handle::set_max_uploads(int max_uploads) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - TORRENT_ASSERT(max_uploads >= 2 || max_uploads == -1); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_max_uploads(max_uploads); + TORRENT_FORWARD(set_max_uploads(max_uploads)); } void torrent_handle::use_interface(const char* net_interface) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->use_interface(net_interface); + TORRENT_FORWARD(use_interface(net_interface)); } void torrent_handle::set_max_connections(int max_connections) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - TORRENT_ASSERT(max_connections >= 2 || max_connections == -1); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_max_connections(max_connections); + TORRENT_FORWARD(set_max_connections(max_connections)); } void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const { INVARIANT_CHECK; TORRENT_ASSERT(limit >= -1); - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_peer_upload_limit(ip, limit); + TORRENT_FORWARD(set_peer_upload_limit(ip, limit)); } void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const { INVARIANT_CHECK; TORRENT_ASSERT(limit >= -1); - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_peer_download_limit(ip, limit); + TORRENT_FORWARD(set_peer_download_limit(ip, limit)); } void torrent_handle::set_upload_limit(int limit) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - TORRENT_ASSERT(limit >= -1); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_upload_limit(limit); + TORRENT_FORWARD(set_upload_limit(limit)); } int torrent_handle::upload_limit() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->upload_limit(); + TORRENT_FORWARD_RETURN(upload_limit(), 0); } void torrent_handle::set_download_limit(int limit) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - TORRENT_ASSERT(limit >= -1); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_download_limit(limit); + TORRENT_FORWARD(set_download_limit(limit)); } int torrent_handle::download_limit() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->download_limit(); + TORRENT_FORWARD_RETURN(download_limit(), 0); } void torrent_handle::move_storage( fs::path const& save_path) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->move_storage(save_path); + TORRENT_FORWARD(move_storage(save_path)); } void torrent_handle::add_extension( @@ -252,123 +246,62 @@ namespace libtorrent , void* userdata) { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->add_extension(ext, userdata); + TORRENT_FORWARD(add_extension(ext, userdata)); } bool torrent_handle::has_metadata() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->valid_metadata(); + TORRENT_FORWARD_RETURN(valid_metadata(), false); } bool torrent_handle::is_seed() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->is_seed(); + TORRENT_FORWARD_RETURN(is_seed(), false); } bool torrent_handle::is_paused() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->is_paused(); + TORRENT_FORWARD_RETURN(is_paused(), false); } void torrent_handle::pause() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->pause(); + TORRENT_FORWARD(pause()); } void torrent_handle::resume() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->resume(); + TORRENT_FORWARD(resume()); } void torrent_handle::set_tracker_login(std::string const& name , std::string const& password) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_tracker_login(name, password); + TORRENT_FORWARD(resume()); } void torrent_handle::file_progress(std::vector& progress) { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) - { - if (!d->processing) - { - torrent_info const& info = d->torrent_ptr->torrent_file(); - progress.clear(); - progress.resize(info.num_files(), 0.f); - return; - } - d->torrent_ptr->file_progress(progress); - return; - } - - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->file_progress(progress); - - throw_invalid_handle(); + TORRENT_FORWARD(file_progress(progress)); } torrent_status torrent_handle::status() const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); + if (m_ses == 0) +#ifdef BOOST_NO_EXCEPTIONS + return torrent_status(); +#else + throw_invalid_handle(); +#endif TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); @@ -396,107 +329,60 @@ namespace libtorrent boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); if (t) return t->status(); +#ifndef BOOST_NO_EXCEPTIONS throw_invalid_handle(); +#endif return torrent_status(); } void torrent_handle::set_sequenced_download_threshold(int threshold) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_sequenced_download_threshold(threshold); + TORRENT_FORWARD(set_sequenced_download_threshold(threshold)); } std::string torrent_handle::name() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->name(); + TORRENT_FORWARD_RETURN(name(), ""); } - void torrent_handle::piece_availability(std::vector& avail) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->piece_availability(avail); + TORRENT_FORWARD(piece_availability(avail)); } void torrent_handle::piece_priority(int index, int priority) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_piece_priority(index, priority); + TORRENT_FORWARD(set_piece_priority(index, priority)); } int torrent_handle::piece_priority(int index) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->piece_priority(index); + TORRENT_FORWARD_RETURN(piece_priority(index), 0); } void torrent_handle::prioritize_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->prioritize_pieces(pieces); + TORRENT_FORWARD(prioritize_pieces(pieces)); } std::vector torrent_handle::piece_priorities() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - std::vector ret; - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->piece_priorities(ret); + TORRENT_FORWARD_RETURN2(piece_priorities(ret), ret); return ret; } void torrent_handle::prioritize_files(std::vector const& files) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->prioritize_files(files); + TORRENT_FORWARD(prioritize_files(files)); } // ============ start deprecation =============== @@ -504,63 +390,33 @@ namespace libtorrent void torrent_handle::filter_piece(int index, bool filter) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filter_piece(index, filter); + TORRENT_FORWARD(filter_piece(index, filter)); } void torrent_handle::filter_pieces(std::vector const& pieces) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filter_pieces(pieces); + TORRENT_FORWARD(filter_pieces(pieces)); } bool torrent_handle::is_piece_filtered(int index) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->is_piece_filtered(index); + TORRENT_FORWARD_RETURN(is_piece_filtered(index), false); } std::vector torrent_handle::filtered_pieces() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - std::vector ret; - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filtered_pieces(ret); + TORRENT_FORWARD_RETURN2(filtered_pieces(ret), ret); return ret; } void torrent_handle::filter_files(std::vector const& files) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->filter_files(files); + TORRENT_FORWARD(filter_files(files)); } // ============ end deprecation =============== @@ -569,111 +425,91 @@ namespace libtorrent std::vector const& torrent_handle::trackers() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->trackers(); + const static std::vector empty; + TORRENT_FORWARD_RETURN(trackers(), empty); } void torrent_handle::add_url_seed(std::string const& url) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->add_url_seed(url); + TORRENT_FORWARD(add_url_seed(url)); } void torrent_handle::remove_url_seed(std::string const& url) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->remove_url_seed(url); + TORRENT_FORWARD(remove_url_seed(url)); } std::set torrent_handle::url_seeds() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->url_seeds(); + const static std::set empty; + TORRENT_FORWARD_RETURN(url_seeds(), empty); } void torrent_handle::replace_trackers( std::vector const& urls) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->replace_trackers(urls); + TORRENT_FORWARD(replace_trackers(urls)); } torrent_info const& torrent_handle::get_torrent_info() const { INVARIANT_CHECK; - +#ifdef BOOST_NO_EXCEPTIONS + const static torrent_info empty; + if (m_ses == 0) return empty; +#else if (m_ses == 0) throw_invalid_handle(); +#endif TORRENT_ASSERT(m_chk); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); - boost::shared_ptr t = find_torrent(m_ses, m_chk, m_info_hash); - if (!t->valid_metadata()) throw_invalid_handle(); + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); + if (t == 0 || !t->valid_metadata()) +#ifdef BOOST_NO_EXCEPTIONS + return empty; +#else + throw_invalid_handle(); +#endif return t->torrent_file(); } bool torrent_handle::is_valid() const { INVARIANT_CHECK; - if (m_ses == 0) return false; TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) return true; - - { - boost::weak_ptr t = m_ses->find_torrent(m_info_hash); - if (!t.expired()) return true; - } - - return false; + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); + return t; } entry torrent_handle::write_resume_data() const { INVARIANT_CHECK; - std::vector piece_index; - if (m_ses == 0) return entry(); + if (m_ses == 0) +#ifdef BOOST_NO_EXCEPTIONS + return entry(); +#else + throw_invalid_handle(); +#endif TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (!t) return entry(); + mutex::scoped_lock l2(m_chk->m_mutex); - if (!t->valid_metadata()) return entry(); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t || !t->valid_metadata()) +#ifdef BOOST_NO_EXCEPTIONS + return entry(); +#else + throw_invalid_handle(); +#endif std::vector have_pieces = t->pieces(); @@ -748,6 +584,7 @@ namespace libtorrent } } + std::vector piece_index; t->filesystem().export_piece_map(piece_index, have_pieces); entry::list_type& slots = ret["slots"].list(); std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); @@ -796,20 +633,19 @@ namespace libtorrent fs::path torrent_handle::save_path() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->save_path(); + TORRENT_FORWARD_RETURN(save_path(), fs::path()); } void torrent_handle::connect_peer(tcp::endpoint const& adr, int source) const { INVARIANT_CHECK; - if (m_ses == 0) throw_invalid_handle(); + if (m_ses == 0) +#ifdef BOOST_NO_EXCEPTIONS + return; +#else + throw_invalid_handle(); +#endif TORRENT_ASSERT(m_chk); session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); @@ -823,7 +659,12 @@ namespace libtorrent mutex::scoped_lock l2(m_chk->m_mutex); aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d == 0) throw_invalid_handle(); + if (d == 0) +#ifdef BOOST_NO_EXCEPTIONS + return; +#else + throw_invalid_handle(); +#endif d->peers.push_back(adr); return; } @@ -837,205 +678,55 @@ namespace libtorrent boost::posix_time::time_duration duration) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (!t) throw_invalid_handle(); - - t->force_tracker_request(time_now() - + seconds(duration.total_seconds())); + TORRENT_FORWARD(force_tracker_request(time_now() + seconds(duration.total_seconds()))); } void torrent_handle::force_reannounce() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (!t) throw_invalid_handle(); - - t->force_tracker_request(); + TORRENT_FORWARD(force_tracker_request()); } void torrent_handle::scrape_tracker() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (!t) throw_invalid_handle(); - - t->scrape_tracker(); + TORRENT_FORWARD(scrape_tracker()); } void torrent_handle::set_ratio(float ratio) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - + TORRENT_ASSERT(ratio >= 0.f); if (ratio < 1.f && ratio > 0.f) ratio = 1.f; - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->set_ratio(ratio); + TORRENT_FORWARD(set_ratio(ratio)); } #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void torrent_handle::resolve_countries(bool r) { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - find_torrent(m_ses, m_chk, m_info_hash)->resolve_countries(r); + TORRENT_FORWARD(resolve_countries(r)); } bool torrent_handle::resolve_countries() const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - return find_torrent(m_ses, m_chk, m_info_hash)->resolving_countries(); + TORRENT_FORWARD_RETURN(resolving_countries(), false); } #endif void torrent_handle::get_peer_info(std::vector& v) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - v.clear(); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (!t) return; - - for (torrent::const_peer_iterator i = t->begin(); - i != t->end(); ++i) - { - peer_connection* peer = *i; - - // incoming peers that haven't finished the handshake should - // not be included in this list - if (peer->associated_torrent().expired()) continue; - - v.push_back(peer_info()); - peer_info& p = v.back(); - - peer->get_peer_info(p); -#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES - if (t->resolving_countries()) - t->resolve_peer_country(intrusive_ptr(peer)); -#endif - } + TORRENT_FORWARD(get_peer_info(v)); } void torrent_handle::get_download_queue(std::vector& queue) const { INVARIANT_CHECK; - - if (m_ses == 0) throw_invalid_handle(); - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - - queue.clear(); - if (!t) return; - if (!t->valid_metadata()) return; - // if we're a seed, the piece picker has been removed - if (t->is_seed()) return; - - const piece_picker& p = t->picker(); - - const std::vector& q - = p.get_download_queue(); - - int block_size = t->block_size(); - - for (std::vector::const_iterator i - = q.begin(); i != q.end(); ++i) - { - partial_piece_info pi; - pi.piece_state = (partial_piece_info::state_t)i->state; - pi.blocks_in_piece = p.blocks_in_piece(i->index); - pi.finished = (int)i->finished; - pi.writing = (int)i->writing; - pi.requested = (int)i->requested; - int piece_size = t->torrent_file().piece_size(i->index); - for (int j = 0; j < pi.blocks_in_piece; ++j) - { - block_info& bi = pi.blocks[j]; - bi.state = i->info[j].state; - bi.block_size = j < pi.blocks_in_piece - 1 ? block_size - : piece_size - (j * block_size); - bool complete = bi.state == block_info::writing - || bi.state == block_info::finished; - if (i->info[j].peer == 0) - { - bi.peer = tcp::endpoint(); - bi.bytes_progress = complete ? bi.block_size : 0; - } - else - { - policy::peer* p = static_cast(i->info[j].peer); - if (p->connection) - { - bi.peer = p->connection->remote(); - if (bi.state == block_info::requested) - { - boost::optional pbp - = p->connection->downloading_piece_progress(); - if (pbp && pbp->piece_index == i->index && pbp->block_index == j) - { - bi.bytes_progress = pbp->bytes_downloaded; - TORRENT_ASSERT(bi.bytes_progress <= bi.block_size); - } - else - { - bi.bytes_progress = 0; - } - } - else - { - bi.bytes_progress = complete ? bi.block_size : 0; - } - } - else - { - bi.peer = p->ip; - bi.bytes_progress = complete ? bi.block_size : 0; - } - } - - pi.blocks[j].num_peers = i->info[j].num_peers; - } - pi.piece_index = i->index; - queue.push_back(pi); - } + TORRENT_FORWARD(get_download_queue(queue)); } } diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index 3d30dadd5..b89510f9f 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -165,7 +165,7 @@ namespace { target.size = dict["length"].integer(); target.path = root_dir; - + target.file_base = 0; // prefer the name.utf-8 // because if it exists, it is more @@ -824,20 +824,19 @@ namespace libtorrent m_nodes.push_back(node); } - bool torrent_info::remap_files(std::vector > const& map) + bool torrent_info::remap_files(std::vector const& map) { - typedef std::vector > files_t; - size_type offset = 0; m_remapped_files.resize(map.size()); for (int i = 0; i < int(map.size()); ++i) { file_entry& fe = m_remapped_files[i]; - fe.path = map[i].first; + fe.path = map[i].path; fe.offset = offset; - fe.size = map[i].second; + fe.size = map[i].size; + fe.file_base = map[i].file_base; + fe.orig_path.reset(); offset += fe.size; } if (offset != total_size()) @@ -846,6 +845,26 @@ namespace libtorrent return false; } +#ifndef NDEBUG + std::vector map2(m_remapped_files); + std::sort(map2.begin(), map2.end() + , bind(&file_entry::file_base, _1) < bind(&file_entry::file_base, _2)); + std::stable_sort(map2.begin(), map2.end() + , bind(&file_entry::path, _1) < bind(&file_entry::path, _2)); + fs::path last_path; + size_type last_end = 0; + for (std::vector::iterator i = map2.begin(), end(map2.end()); + i != end; ++i) + { + if (last_path == i->path) + { + assert(last_end <= i->file_base); + } + last_end = i->file_base + i->size; + last_path = i->path; + } +#endif + return true; } @@ -871,7 +890,7 @@ namespace libtorrent { file_slice f; f.file_index = counter; - f.offset = file_offset; + f.offset = file_offset + file_iter->file_base; f.size = (std::min)(file_iter->size - file_offset, (size_type)size); size -= f.size; file_offset += f.size; diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index 21208454e..71ce2d430 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -306,10 +306,12 @@ namespace libtorrent namespace { - bool range_contains(peer_request const& range, peer_request const& req) + bool range_contains(peer_request const& range, peer_request const& req, int piece_size) { - return range.start <= req.start - && range.start + range.length >= req.start + req.length; + size_type range_start = size_type(range.piece) * piece_size + range.start; + size_type req_start = size_type(req.piece) * piece_size + req.start; + return range_start <= req_start + && range_start + range.length >= req_start + req.length; } } @@ -358,7 +360,11 @@ namespace libtorrent && !(m_parser.status_code() >= 300 // redirect && m_parser.status_code() < 400)) { - // we should not try this server again. + if (m_parser.status_code() == 503) + { + // temporarily unavailable, retry later + t->retry_url_seed(m_url); + } t->remove_url_seed(m_url); std::string error_msg = boost::lexical_cast(m_parser.status_code()) + " " + m_parser.message(); @@ -470,6 +476,9 @@ namespace libtorrent } } +// std::cerr << "REQUESTS: m_requests: " << m_requests.size() +// << " file_requests: " << m_file_requests.size() << std::endl; + torrent_info const& info = t->torrent_file(); if (m_requests.empty() || m_file_requests.empty()) @@ -484,16 +493,13 @@ namespace libtorrent size_type rs = size_type(in_range.piece) * info.piece_length() + in_range.start; size_type re = rs + in_range.length; size_type fs = size_type(front_request.piece) * info.piece_length() + front_request.start; +/* size_type fe = fs + front_request.length; - if (fs < rs || fe > re) - { - throw std::runtime_error("invalid range in HTTP response"); - } - // skip the http header and the blocks we've already read. The - // http_body.begin is now in sync with the request at the front - // of the request queue -// TORRENT_ASSERT(in_range.start - int(m_piece.size()) <= front_request.start); + std::cerr << "RANGE: r = (" << rs << ", " << re << " ) " + "f = (" << fs << ", " << fe << ") " + "file_index = " << file_index << " received_body = " << m_received_body << std::endl; +*/ // the http response body consists of 3 parts // 1. the middle of a block or the ending of a block @@ -501,14 +507,20 @@ namespace libtorrent // 3. the start of a block // in that order, these parts are parsed. - bool range_overlaps_request = in_range.start + in_range.length - > front_request.start + int(m_piece.size()); + bool range_overlaps_request = re > fs + int(m_piece.size()); + + if (!range_overlaps_request) + { + // this means the end of the incoming request ends _before_ the + // first expected byte (fs + m_piece.size()) + throw std::runtime_error("invalid range in HTTP response"); + } // if the request is contained in the range (i.e. the entire request // fits in the range) we should not start a partial piece, since we soon // will receive enough to call incoming_piece() and pass the read buffer // directly (in the next loop below). - if (range_overlaps_request && !range_contains(in_range, front_request)) + if (range_overlaps_request && !range_contains(in_range, front_request, info.piece_length())) { // the start of the next block to receive is stored // in m_piece. We need to append the rest of that @@ -549,7 +561,7 @@ namespace libtorrent // report all received blocks to the bittorrent engine while (!m_requests.empty() - && range_contains(in_range, m_requests.front()) + && range_contains(in_range, m_requests.front(), info.piece_length()) && recv_buffer.left() >= m_requests.front().length) { peer_request r = m_requests.front(); From a7e6ec4b06648f9bdcfda3f5510dbc856dc451a1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 29 Nov 2007 06:02:33 +0000 Subject: [PATCH 0273/1009] Sync libtorrent to 0.13RC Update python bindings to support wait_for_alert() and scrape_tracker() plus associated alerts. This modifies lt's wait_for_alert() to return a std::auto_ptr. --- TODO | 2 ++ libtorrent/bindings/python/src/alert.cpp | 13 +++++++++++ libtorrent/bindings/python/src/docstrings.cpp | 13 +++++++++++ libtorrent/bindings/python/src/session.cpp | 3 +++ .../bindings/python/src/torrent_handle.cpp | 1 + libtorrent/include/libtorrent/alert.hpp | 2 +- .../include/libtorrent/aux_/session_impl.hpp | 2 +- libtorrent/include/libtorrent/session.hpp | 2 +- libtorrent/src/alert.cpp | 10 ++++----- libtorrent/src/session.cpp | 2 +- libtorrent/src/session_impl.cpp | 22 ++++++++++++++++++- 11 files changed, 62 insertions(+), 10 deletions(-) diff --git a/TODO b/TODO index c60a020ce..252720714 100644 --- a/TODO +++ b/TODO @@ -23,4 +23,6 @@ * Add filtering to torrentview to use with the sidebar * Fix up preferences for when using a remote host.. the download folders, etc.. * Add decay items to statusbar.. items that will disappear after X seconds +* Do not update UI when minimized or hidden +* Add command line option to change config dir.. --config diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index 722cee461..aec3884e0 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -39,6 +39,8 @@ extern char const* portmap_error_alert_doc; extern char const* portmap_alert_doc; extern char const* fastresume_rejected_alert_doc; extern char const* peer_blocked_alert_doc; +extern char const* scrape_reply_alert_doc; +extern char const* scrape_failed_alert_doc; void bind_alert() { @@ -210,4 +212,15 @@ void bind_alert() ) .def_readonly("ip", &peer_blocked_alert::ip) ; + + class_, noncopyable>( + "scrape_reply_alert", scrape_reply_alert_doc, no_init + ) + .def_readonly("incomplete", &scrape_reply_alert::incomplete) + .def_readonly("complete", &scrape_reply_alert::complete) + ; + + class_, noncopyable>( + "scrape_failed_alert", scrape_failed_alert_doc, no_init + ); } diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index 7b6b9d640..f2aa7bfd0 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -177,6 +177,9 @@ char const* session_start_natpmp_doc = ""; char const* session_stop_natpmp_doc = ""; +char const* session_wait_for_alert_doc = + ""; + // -- alert ----------------------------------------------------------------- char const* alert_doc = @@ -321,3 +324,13 @@ char const* fastresume_rejected_alert_doc = char const* peer_blocked_alert_doc = ""; + +char const* scrape_reply_alert_doc = + "This alert is generated when a scrape request succeeds.\n" + "incomplete and complete is the data returned in the scrape\n" + "response. These numbers may be -1 if the reponse was malformed."; + +char const* scrape_failed_alert_doc = + "If a scrape request fails, this alert is generated. This might\n" + "be due to the tracker timing out, refusing connection or returning\n" + "an http response code indicating an error."; diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 481348730..1b9f0ffc3 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "gil.hpp" @@ -56,6 +57,7 @@ extern char const* session_stop_lsd_doc; extern char const* session_stop_upnp_doc; extern char const* session_start_natpmp_doc; extern char const* session_stop_natpmp_doc; +extern char const* session_wait_for_alert_doc; namespace { @@ -248,6 +250,7 @@ void bind_session() .def("stop_lsd", allow_threads(&session::stop_lsd), session_stop_lsd_doc) .def("start_natpmp", allow_threads(&session::start_natpmp), session_start_natpmp_doc) .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) + .def("wait_for_alert", allow_threads(&session::wait_for_alert), session_wait_for_alert_doc) ; register_ptr_to_python >(); diff --git a/libtorrent/bindings/python/src/torrent_handle.cpp b/libtorrent/bindings/python/src/torrent_handle.cpp index 98540a5f8..b199e86c8 100755 --- a/libtorrent/bindings/python/src/torrent_handle.cpp +++ b/libtorrent/bindings/python/src/torrent_handle.cpp @@ -190,6 +190,7 @@ void bind_torrent_handle() .def("prioritize_files", prioritize_files) .def("get_peer_info", get_peer_info) .def("get_download_queue", get_download_queue) + .def("scrape_tracker", (&torrent_handle::scrape_tracker)) ; } diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp index ab8065f1f..896443aaa 100755 --- a/libtorrent/include/libtorrent/alert.hpp +++ b/libtorrent/include/libtorrent/alert.hpp @@ -100,7 +100,7 @@ namespace libtorrent { void set_severity(alert::severity_t severity); bool should_post(alert::severity_t severity) const; - alert const* wait_for_alert(time_duration max_wait); + std::auto_ptr wait_for_alert(time_duration max_wait); private: std::queue m_alerts; diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index cf627c70b..803d78e00 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -280,7 +280,7 @@ namespace libtorrent void set_severity_level(alert::severity_t s); std::auto_ptr pop_alert(); - alert const* wait_for_alert(time_duration max_wait); + std::auto_ptr wait_for_alert(time_duration max_wait); int upload_rate_limit() const; int download_rate_limit() const; diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index d2ab6ab2e..b9a726fb8 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -265,7 +265,7 @@ namespace libtorrent std::auto_ptr pop_alert(); void set_severity_level(alert::severity_t s); - alert const* wait_for_alert(time_duration max_wait); + std::auto_ptr wait_for_alert(time_duration max_wait); connection_queue& get_connection_queue(); diff --git a/libtorrent/src/alert.cpp b/libtorrent/src/alert.cpp index cb89147da..5b0f892f8 100755 --- a/libtorrent/src/alert.cpp +++ b/libtorrent/src/alert.cpp @@ -78,11 +78,11 @@ namespace libtorrent { } } - alert const* alert_manager::wait_for_alert(time_duration max_wait) + std::auto_ptr alert_manager::wait_for_alert(time_duration max_wait) { boost::mutex::scoped_lock lock(m_mutex); - if (!m_alerts.empty()) return m_alerts.front(); + if (!m_alerts.empty()) return std::auto_ptr(m_alerts.front()); int secs = total_seconds(max_wait); max_wait -= seconds(secs); @@ -96,10 +96,10 @@ namespace libtorrent { xt.sec += 1; } xt.nsec = nsec; - if (!m_condition.timed_wait(lock, xt)) return 0; + if (!m_condition.timed_wait(lock, xt)) return std::auto_ptr(NULL); TORRENT_ASSERT(!m_alerts.empty()); - if (m_alerts.empty()) return 0; - return m_alerts.front(); + if (m_alerts.empty()) return std::auto_ptr(NULL); + return std::auto_ptr(m_alerts.front()); } void alert_manager::post_alert(const alert& alert_) diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 331ffa377..8efad6b0d 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -422,7 +422,7 @@ namespace libtorrent return m_impl->pop_alert(); } - alert const* session::wait_for_alert(time_duration max_wait) + std::auto_ptr session::wait_for_alert(time_duration max_wait) { return m_impl->wait_for_alert(max_wait); } diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 833d49777..fdaacfabc 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -766,6 +766,8 @@ namespace detail mutex::scoped_lock l2(m_checker_impl.m_mutex); // abort the checker thread m_checker_impl.m_abort = true; + + m_io_service.stop(); } void session_impl::set_port_filter(port_filter const& f) @@ -1572,6 +1574,24 @@ namespace detail } while (!m_abort); + ptime end = time_now() + seconds(m_settings.stop_tracker_timeout); + while (time_now() < end && !m_tracker_manager.empty()) + { + m_io_service.reset(); + m_io_service.poll(); + // sleep 200 milliseconds + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + boost::int64_t nsec = xt.nsec + 200 * 1000000; + if (nsec > 1000000000) + { + nsec -= 1000000000; + xt.sec += 1; + } + xt.nsec = nsec; + boost::thread::sleep(xt); + } + #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " locking mutex\n"; #endif @@ -2252,7 +2272,7 @@ namespace detail return std::auto_ptr(0); } - alert const* session_impl::wait_for_alert(time_duration max_wait) + std::auto_ptr session_impl::wait_for_alert(time_duration max_wait) { return m_alerts.wait_for_alert(max_wait); } From aa019231ba956c1b6be064869af0002bbb4d9148 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 29 Nov 2007 07:09:18 +0000 Subject: [PATCH 0274/1009] Scrape tracker if we do not receive any peer information on announce. Minor UI tweaks. --- deluge/core/torrentmanager.py | 20 +- deluge/ui/gtkui/glade/main_window.glade | 1274 +++++++++++------------ deluge/ui/gtkui/sidebar.py | 3 + 3 files changed, 655 insertions(+), 642 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c10e80e36..33d76f503 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -366,7 +366,17 @@ class TorrentManager: return False return True - + + def scrape_tracker(self, torrent_id): + """Scrape the tracker""" + try: + self.torrents[torrent_id].handle.scrape_tracker() + except Exception, e: + log.debug("Unable to scrape tracker: %s", e) + return False + + return True + def force_recheck(self, torrent_id): """Forces a re-check of the torrent's data""" log.debug("Doing a forced recheck on %s", torrent_id) @@ -514,7 +524,7 @@ class TorrentManager: torrent_id = str(alert.handle.info_hash()) # Write the fastresume file self.write_fastresume(torrent_id) - + def on_alert_tracker_reply(self, alert): log.debug("on_alert_tracker_reply") # Get the torrent_id @@ -524,6 +534,12 @@ class TorrentManager: self.torrents[torrent_id].set_tracker_status(_("Announce OK")) except KeyError: log.debug("torrent_id doesn't exist.") + + # Check to see if we got any peer information from the tracker + if alert.handle.status().num_complete == -1 or \ + alert.handle.status().num_incomplete == -1: + # We didn't get peer information, so lets send a scrape request + self.scrape_tracker(torrent_id) def on_alert_tracker_announce(self, alert): log.debug("on_alert_tracker_announce") diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 91a5fb010..c274d425d 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -408,6 +408,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 110 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -460,693 +461,686 @@ - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE + False + False - + True - False - False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_NEVER - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_NEVER + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE + 5 + 5 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 5 + 1 + 2 + 10 - + True - 1 - 2 - 10 + 0 - + True - 0 + 10 + 10 + 15 + 15 - + True - 10 - 10 - 15 - 15 + 5 - + True - 5 + 0.10000000149 + + + False + False + + + + + True + 5 + 4 + 5 - + True - 0.10000000149 + 0 - False - False + 1 + 2 - + True - 5 - 4 - 5 + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 3 + 4 + + + + + True + 5 - + + True + 0 + <b>Downloaded:</b> + True + + + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + True 0 - - 1 - 2 - + + + 1 + 2 + 4 + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 15 + 5 - - True - 0 - - - 3 - 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - + True 0 1 - <b>Pieces:</b> + <b>Availability:</b> True - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 15 - 5 - - - True - 0 - 1 - <b>Availability:</b> - True - - - - - 2 - 3 - 4 - 5 - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 3 - 4 - 4 - 5 - - - False - 1 + 2 + 3 + 4 + 5 + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 3 + 4 + 4 + 5 + + + False + 1 + - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Statistics</b> - True - - - label_item - - - - - GTK_FILL - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - + <b>Statistics</b> + True - 1 - 2 - GTK_FILL + label_item + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + @@ -1154,18 +1148,18 @@ - - - True - Details - - - tab - False - - + + + True + Details + + + tab + False + + False diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 363527517..dcd61c580 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -57,6 +57,9 @@ class SideBar(component.Component): self.liststore.append([_("Seeding"), gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("seeding16.png"))]) + self.liststore.append([_("Paused"), + gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("inactive16.png"))]) # Create the column column = gtk.TreeViewColumn(_("Labels")) column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) From f817de3575f4888086ba9244063c8ab5ca4d2729 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 30 Nov 2007 04:48:00 +0000 Subject: [PATCH 0275/1009] fix faq url --- deluge/ui/gtkui/menubar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index f60354f5c..bb7c42ed8 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -230,7 +230,7 @@ class MenuBar(component.Component): def on_menuitem_faq_activate(self, data=None): log.debug("on_menuitem_faq_activate") - client.open_url_in_browser("http://deluge-torrent.org/faq") + client.open_url_in_browser("http://deluge-torrent.org/faq.php") def on_menuitem_community_activate(self, data=None): log.debug("on_menuitem_community_activate") From e32f60310352a0e018b814a4b958f6a87fe0aab7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 30 Nov 2007 06:20:47 +0000 Subject: [PATCH 0276/1009] UI tweaks. --- deluge/common.py | 5 +- deluge/core/torrent.py | 15 +- deluge/core/torrentmanager.py | 9 +- deluge/ui/gtkui/glade/main_window.glade | 16 +- .../ui/gtkui/glade/preferences_dialog.glade | 230 +++++++++--------- deluge/ui/gtkui/listview.py | 11 +- deluge/ui/gtkui/preferences.py | 1 + deluge/ui/gtkui/torrentview.py | 34 ++- 8 files changed, 171 insertions(+), 150 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index a558fef52..e437cf298 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -127,7 +127,10 @@ def fspeed(bps): def fpeer(num_peers, total_peers): """Returns a formatted string num_peers (total_peers)""" - return str(str(num_peers) + " (" + str(total_peers) + ")") + if total_peers > -1: + return str(str(num_peers) + " (" + str(total_peers) + ")") + else: + return str(num_peers) def ftime(seconds): """Returns a formatted time string""" diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 18139ede7..92728ce5d 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -128,17 +128,6 @@ class Torrent: # Adjust progress to be 0-100 value progress = status.progress*100 - # Get the total number of seeds and peers - if status.num_complete == -1: - total_seeds = status.num_seeds - else: - total_seeds = status.num_complete - - if status.num_incomplete == -1: - total_peers = status.num_peers - status.num_seeds - else: - total_peers = status.num_incomplete - # Set the state to 'Paused' if the torrent is paused. state = status.state if status.paused: @@ -168,8 +157,8 @@ class Torrent: "upload_payload_rate": status.upload_payload_rate, "num_peers": status.num_peers - status.num_seeds, "num_seeds": status.num_seeds, - "total_peers": total_peers, - "total_seeds": total_seeds, + "total_peers": status.num_incomplete, + "total_seeds": status.num_complete, "total_wanted": status.total_wanted, "eta": self.get_eta(), "ratio": self.get_ratio(), diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 33d76f503..a1d4476f1 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -531,7 +531,8 @@ class TorrentManager: torrent_id = str(alert.handle.info_hash()) # Set the tracker status for the torrent try: - self.torrents[torrent_id].set_tracker_status(_("Announce OK")) + if alert.msg != "Got peers from DHT": + self.torrents[torrent_id].set_tracker_status(_("Announce OK")) except KeyError: log.debug("torrent_id doesn't exist.") @@ -555,10 +556,8 @@ class TorrentManager: log.debug("on_alert_tracker") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) - tracker_status = "%s: %s (%s=%s, %s=%s)" % \ - (_("Alert"), str(alert.msg()), - _("HTTP code"), alert.status_code, - _("times in a row"), alert.times_in_row) + tracker_status = "%s: %s" % \ + (_("Alert"), str(alert.msg()).strip('"')[8:]) # Set the tracker status for the torrent try: self.torrents[torrent_id].set_tracker_status(tracker_status) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index c274d425d..e6fa3cf18 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -904,6 +904,7 @@ True 0 + True 1 @@ -917,6 +918,7 @@ True 0 + True 1 @@ -931,7 +933,8 @@ True 0 True - PANGO_WRAP_WORD_CHAR + PANGO_WRAP_CHAR + True 1 @@ -1048,7 +1051,8 @@ True 0 True - PANGO_WRAP_WORD_CHAR + PANGO_WRAP_CHAR + True 1 @@ -1062,6 +1066,9 @@ True 0 + True + PANGO_WRAP_CHAR + True 1 @@ -1075,6 +1082,8 @@ True 0 + True + True 1 @@ -1111,7 +1120,8 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 True - PANGO_WRAP_WORD_CHAR + PANGO_WRAP_CHAR + True 1 diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 204f8141a..2acc7d4d0 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -263,7 +263,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Automatically add torrent files that are placed in this folder. - Client Auto Add: + Client Folder: 0 True @@ -296,7 +296,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Daemon Auto Add: + Daemon Folder: 0 True @@ -568,6 +568,7 @@ True True + False 5 1 0 0 65535 1 10 10 @@ -597,6 +598,7 @@ True True + False 5 1 0 0 65535 1 10 10 @@ -1078,71 +1080,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -1167,43 +1138,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1247,29 +1249,24 @@ Either 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1287,19 +1284,24 @@ Either - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1550,15 +1552,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1588,38 +1608,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 7a32d054c..c432793ed 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -43,7 +43,10 @@ from deluge.log import LOG as log def cell_data_speed(column, cell, model, row, data): """Display value as a speed, eg. 2 KiB/s""" speed = int(model.get_value(row, data)) - speed_str = deluge.common.fspeed(speed) + speed_str = "" + if speed > 0: + speed_str = deluge.common.fspeed(speed) + cell.set_property('text', speed_str) def cell_data_size(column, cell, model, row, data): @@ -57,7 +60,11 @@ def cell_data_peer(column, cell, model, row, data): column1, column2 = data first = int(model.get_value(row, column1)) second = int(model.get_value(row, column2)) - cell.set_property('text', '%d (%d)' % (first, second)) + # Only display a (total) if second is greater than -1 + if second > -1: + cell.set_property('text', '%d (%d)' % (first, second)) + else: + cell.set_property('text', '%d' % first) def cell_data_time(column, cell, model, row, data): """Display value as time, eg 1m10s""" diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 742684a6c..44d5e581c 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -465,6 +465,7 @@ class Preferences(component.Component): # Disable the port spinners if random ports is selected. if widget == self.glade.get_widget("chk_random_port"): + log.debug("chk_random_port set to: %s", value) self.glade.get_widget("spin_port_min").set_sensitive(not value) self.glade.get_widget("spin_port_max").set_sensitive(not value) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 5c5d9525a..593378140 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -45,30 +45,40 @@ import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview +# Status icons.. Create them from file only once to avoid constantly +# re-creating them. +icon_downloading = gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("downloading16.png")) +icon_seeding = gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("seeding16.png")) +icon_inactive = gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("inactive16.png")) + def cell_data_statusicon(column, cell, model, row, data): """Display text with an icon""" state = model.get_value(row, data) + icon = None if state == deluge.common.TORRENT_STATE.index("Connecting"): - fname = "downloading16.png" + icon = icon_downloading if state == deluge.common.TORRENT_STATE.index("Downloading"): - fname = "downloading16.png" + icon = icon_downloading if state == deluge.common.TORRENT_STATE.index("Downloading Metadata"): - fname = "downloading16.png" + icon = icon_downloading if state == deluge.common.TORRENT_STATE.index("Queued"): - fname = "inactive16.png" + icon = icon_inactive if state == deluge.common.TORRENT_STATE.index("Paused"): - fname = "inactive16.png" + icon = icon_inactive if state == deluge.common.TORRENT_STATE.index("Checking"): - fname = "downloading16.png" + icon = icon_downloading if state == deluge.common.TORRENT_STATE.index("Allocating"): - fname = "downloading16.png" + icon = icon_downloading if state == deluge.common.TORRENT_STATE.index("Finished"): - fname = "seeding16.png" + icon = icon_seeding if state == deluge.common.TORRENT_STATE.index("Seeding"): - fname = "seeding16.png" - - icon = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap(fname)) - cell.set_property("pixbuf", icon) + icon = icon_seeding + + if icon != None: + cell.set_property("pixbuf", icon) def cell_data_progress(column, cell, model, row, data): """Display progress bar with text""" From 6953e280ad1bbf0131626ba8552cb8e89e9f26f9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 30 Nov 2007 06:39:12 +0000 Subject: [PATCH 0277/1009] total_done overflow fix in libtorrent --- libtorrent/src/torrent.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 24b9f39fe..d28c97d47 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -704,7 +704,7 @@ namespace libtorrent const int last_piece = m_torrent_file->num_pieces() - 1; size_type total_done - = m_num_pieces * m_torrent_file->piece_length(); + = size_type(m_num_pieces) * m_torrent_file->piece_length(); // if we have the last piece, we have to correct // the amount we have, since the first calculation @@ -735,11 +735,11 @@ namespace libtorrent return make_tuple(m_torrent_file->total_size() , m_torrent_file->total_size()); - size_type wanted_done = (m_num_pieces - m_picker->num_have_filtered()) + size_type wanted_done = size_type(m_num_pieces - m_picker->num_have_filtered()) * piece_size; size_type total_done - = m_num_pieces * piece_size; + = size_type(m_num_pieces) * piece_size; TORRENT_ASSERT(m_num_pieces < m_torrent_file->num_pieces()); // if we have the last piece, we have to correct From 622080de50ba5dc6247206244290438f80c66360 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 30 Nov 2007 06:50:48 +0000 Subject: [PATCH 0278/1009] Fix signalreceiver to work when connected to a remote daemon. --- deluge/ui/gtkui/signals.py | 6 +++++- deluge/ui/signalreceiver.py | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index a648710c5..9f8b948f3 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -32,6 +32,7 @@ # statement from all source files in the program, then also delete it here. import deluge.ui.component as component +import deluge.ui.client as client from deluge.ui.signalreceiver import SignalReceiver from deluge.log import LOG as log @@ -40,7 +41,10 @@ class Signals(component.Component): component.Component.__init__(self, "Signals") def start(self): - self.receiver = SignalReceiver(6667) + remote = False + if not client.is_localhost(): + remote = True + self.receiver = SignalReceiver(6667, remote) self.receiver.start() self.receiver.connect_to_signal("torrent_added", self.torrent_added_signal) diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 1141d9019..faeacbcc2 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -49,7 +49,7 @@ class SignalReceiver( ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): - def __init__(self, port): + def __init__(self, port, remote=False): log.debug("SignalReceiver init..") gobject.threads_init() threading.Thread.__init__(self) @@ -61,10 +61,14 @@ class SignalReceiver( # Daemonize the thread so it exits when the main program does self.setDaemon(True) + host = "localhost" + if remote == True: + host = "" + # Setup the xmlrpc server try: SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, ("localhost", port), logRequests=False, allow_none=True) + self, (host, port), logRequests=False, allow_none=True) except: log.info("SignalReceiver already running or port not available..") sys.exit(0) From 3b9983f110f658a2d5425dbf036464f15fa06649 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 5 Dec 2007 05:21:34 +0000 Subject: [PATCH 0279/1009] Add filtering to torrentview for use with sidebar labels. Implement sidebar filtering. --- TODO | 6 ++--- deluge/ui/gtkui/listview.py | 18 +++++++++++-- deluge/ui/gtkui/sidebar.py | 32 ++++++++++++++++++++++- deluge/ui/gtkui/torrentview.py | 48 ++++++++++++++++++++++++++++++---- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/TODO b/TODO index 252720714..fda11fe1c 100644 --- a/TODO +++ b/TODO @@ -17,12 +17,10 @@ * Add autoload folder * Add wizard * Add a health indication to the statusbar -* Add sidebar for labels and other things.. Plugins should be able to add their - own section to this. * Option for adding torrents in paused/active state -* Add filtering to torrentview to use with the sidebar * Fix up preferences for when using a remote host.. the download folders, etc.. * Add decay items to statusbar.. items that will disappear after X seconds * Do not update UI when minimized or hidden * Add command line option to change config dir.. --config - +* Add method for plugins to add labels +* Add context menus for labels.. ie. setting options for all torrents in label diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index c432793ed..54ccb5c31 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -273,7 +273,6 @@ class ListView: # Create a new column object and add it to the list self.columns[header] = self.ListViewColumn(header, column_indices) - self.columns[header].status_field = status_field @@ -285,6 +284,10 @@ class ListView: column.pack_start(render) column.add_attribute(render, "text", self.columns[header].column_indices[text]) + elif column_type == "bool": + column.pack_start(render) + column.add_attribute(render, "active", + self.columns[header].column_indices[0]) elif column_type == "func": column.pack_start(render, True) if len(self.columns[header].column_indices) > 1: @@ -345,7 +348,18 @@ class ListView: status_field, sortid, column_type=column_type) return True - + + def add_bool_column(self, header, col_type=bool, hidden=False, + position=None, + status_field=None, + sortid=0, + column_type="bool"): + + """Add a bool column to the listview""" + render = gtk.CellRendererToggle() + self.add_column(header, render, col_type, hidden, position, + status_field, sortid, column_type=column_type) + def add_func_column(self, header, function, col_types, sortid=0, hidden=False, position=None, status_field=None, column_type="func"): diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index dcd61c580..9f0b37391 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -73,6 +73,13 @@ class SideBar(component.Component): self.label_view.set_model(self.liststore) + self.label_view.get_selection().connect("changed", + self.on_selection_changed) + + # Select the 'All' label on init + self.label_view.get_selection().select_iter( + self.liststore.get_iter_first()) + def visible(self, visible): if visible: self.scrolled.show() @@ -81,4 +88,27 @@ class SideBar(component.Component): self.hpaned.set_position(-1) self.is_visible = visible - + + def on_selection_changed(self, selection): + try: + (model, row) = self.label_view.get_selection().get_selected() + except Exception, e: + log.debug(e) + # paths is likely None .. so lets return None + return None + + value = model.get_value(row, 0) + if value == "All": + component.get("TorrentView").set_filter(None, None) + if value == "Downloading": + component.get("TorrentView").set_filter("state", + deluge.common.TORRENT_STATE.index("Downloading")) + + if value == "Seeding": + component.get("TorrentView").set_filter("state", + deluge.common.TORRENT_STATE.index("Seeding")) + + if value == "Paused": + component.get("TorrentView").set_filter("state", + deluge.common.TORRENT_STATE.index("Paused")) + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 593378140..42004ed35 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -124,6 +124,7 @@ class TorrentView(listview.ListView, component.Component): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) + self.add_bool_column("filter", hidden=True) self.add_texticon_column(_("Name"), status_field=["state", "name"], function=cell_data_statusicon) self.add_func_column(_("Size"), @@ -164,7 +165,16 @@ class TorrentView(listview.ListView, component.Component): listview.cell_data_ratio, [float], status_field=["distributed_copies"]) - + + # Set filter to None for now + self.filter = (None, None) + + # Set the liststore filter column + self.model_filter = self.liststore.filter_new() + self.model_filter.set_visible_column( + self.columns["filter"].column_indices[0]) + self.treeview.set_model(self.model_filter) + ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the # torrent menu popup. @@ -187,21 +197,49 @@ class TorrentView(listview.ListView, component.Component): """Stops the torrentview""" # We need to clear the liststore self.liststore.clear() - + + def set_filter(self, field, condition): + """Sets filters for the torrentview..""" + self.filter = (field, condition) + self.update() + def update(self, columns=None): """Update the view. If columns is not None, it will attempt to only update those columns selected. """ - # Iterates through every row and updates them accordingly - if self.liststore is not None: + def foreachrow(model, path, row, data): + filter_column = self.columns["filter"].column_indices[0] + # Create a function to create a new liststore with only the + # desired rows based on the filter. + field, condition = data + if field == None and condition == None: + model.set_value(row, filter_column, True) + return + + torrent_id = model.get_value(row, 0) + value = client.get_torrent_status(torrent_id, [field])[field] + # Condition is True, so lets show this row, if not we hide it + if value == condition: + model.set_value(row, filter_column, True) + else: + model.set_value(row, filter_column, False) + + self.liststore.foreach(foreachrow, self.filter) + if self.liststore != None: self.liststore.foreach(self.update_row, columns) def update_row(self, model=None, path=None, row=None, columns=None): """Updates the column values for 'row'. If columns is None it will update all visible columns.""" - + + # Check to see if this row is visible and return if not + if not model.get_value(row, self.columns["filter"].column_indices[0]): + return + + # Get the torrent_id from the liststore torrent_id = model.get_value(row, self.columns["torrent_id"].column_indices[0]) + # Store the 'status_fields' we need to send to core status_keys = [] # Store the actual columns we will be updating From 4a8f2c46f222b93f909d95e65c337296dd1537d0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Dec 2007 02:15:17 +0000 Subject: [PATCH 0280/1009] Add Classic mode to the preferences dialog. --- TODO | 1 - .../ui/gtkui/glade/preferences_dialog.glade | 267 ++++++++++-------- 2 files changed, 155 insertions(+), 113 deletions(-) diff --git a/TODO b/TODO index fda11fe1c..3e0041e72 100644 --- a/TODO +++ b/TODO @@ -11,7 +11,6 @@ * Address issue where torrents will redownload if the storage is moved outside of deluge. * Hide open folder if not localhost -* Add classic/normal mode to preferences * Implement 'Classic' mode * Tray tooltip * Add autoload folder diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 2acc7d4d0..e2c8fb9e8 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1080,40 +1080,71 @@ Either 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 3 4 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 2 - 3 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 1 + 2 GTK_FILL @@ -1138,74 +1169,43 @@ Either - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 1 - 2 + 1 + 2 + 2 + 3 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 3 4 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - @@ -1249,24 +1249,29 @@ Either 2 15 - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1284,24 +1289,19 @@ Either - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1552,33 +1552,15 @@ Either 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1608,20 +1590,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + @@ -1645,6 +1645,49 @@ Thunar 3 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + 12 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Enable + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Classic Mode</b> + True + + + label_item + + + + + False + False + 4 + + From 7522ecf7195463d040f549e2d2b670f280e9086e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Dec 2007 03:48:52 +0000 Subject: [PATCH 0281/1009] Fix sorting in TorrentView. --- deluge/ui/gtkui/torrentview.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 42004ed35..5a5766632 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -170,9 +170,10 @@ class TorrentView(listview.ListView, component.Component): self.filter = (None, None) # Set the liststore filter column - self.model_filter = self.liststore.filter_new() - self.model_filter.set_visible_column( + model_filter = self.liststore.filter_new() + model_filter.set_visible_column( self.columns["filter"].column_indices[0]) + self.model_filter = gtk.TreeModelSort(model_filter) self.treeview.set_model(self.model_filter) ### Connect Signals ### @@ -192,7 +193,7 @@ class TorrentView(listview.ListView, component.Component): session_state = client.get_session_state() for torrent_id in session_state: self.add_row(torrent_id) - + def stop(self): """Stops the torrentview""" # We need to clear the liststore From 589df97add74e7da4a6e56be699af18023759eb9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Dec 2007 05:02:29 +0000 Subject: [PATCH 0282/1009] Add ListView state saving. Have TorrentView save and load state on startup/shutdown. Add new method to components shutdown() which is called when the UI is exiting to allow components to clean-up. Clean up some debug output. --- TODO | 1 - deluge/core/core.py | 1 + deluge/pluginmanagerbase.py | 1 - deluge/ui/component.py | 20 +++++++++ deluge/ui/gtkui/gtkui.py | 4 ++ deluge/ui/gtkui/listview.py | 75 +++++++++++++++++++++++++++++++++- deluge/ui/gtkui/statusbar.py | 1 - deluge/ui/gtkui/systemtray.py | 2 - deluge/ui/gtkui/torrentview.py | 12 ++++-- 9 files changed, 107 insertions(+), 10 deletions(-) diff --git a/TODO b/TODO index 3e0041e72..d75e19416 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* Add state saving to listview.. this includes saving column size and position. * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. * Figure out easy way for user-made plugins to add i18n support. diff --git a/deluge/core/core.py b/deluge/core/core.py index be3b3f000..0fa3591f6 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -55,6 +55,7 @@ from deluge.core.signalmanager import SignalManager from deluge.log import LOG as log DEFAULT_PREFS = { + "config_location": deluge.common.get_config_dir(), "daemon_port": 58846, "allow_remote": False, "compact_allocation": True, diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 8746203da..a66d9543b 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -63,7 +63,6 @@ class PluginManagerBase: self.enable_plugin(name) def shutdown(self): - log.debug("PluginManager shutting down..") for plugin in self.plugins.values(): plugin.disable() del self.plugins diff --git a/deluge/ui/component.py b/deluge/ui/component.py index e5055c827..d824ec244 100644 --- a/deluge/ui/component.py +++ b/deluge/ui/component.py @@ -60,6 +60,9 @@ class Component: def _stop(self): self._state = COMPONENT_STATE.index("Stopped") + def shutdown(self): + pass + def update(self): pass @@ -100,6 +103,7 @@ class ComponentRegistry: # Only start if the component is stopped. if self.components[name].get_state() == \ COMPONENT_STATE.index("Stopped"): + log.debug("Starting component %s..", name) self.components[name].start() self.components[name]._start() @@ -107,6 +111,7 @@ class ComponentRegistry: def stop(self): """Stops all components""" for component in self.components.keys(): + log.debug("Stopping component %s..", component) self.components[component].stop() self.components[component]._stop() # Stop the update timer @@ -122,6 +127,17 @@ class ComponentRegistry: return True + def shutdown(self): + """Shuts down all components. This should be called when the program + exits so that components can do any necessary clean-up.""" + for component in self.components.keys(): + log.debug("Shutting down component %s..", component) + try: + self.components[component].shutdown() + except Exception, e: + log.debug("Unable to call shutdown(): %s", e) + + _ComponentRegistry = ComponentRegistry() def register(name, obj, depend=None): @@ -140,6 +156,10 @@ def update(): """Updates all components""" _ComponentRegistry.update() +def shutdown(): + """Shutdowns all components""" + _ComponentRegistry.shutdown() + def get(component): """Return a reference to the component""" return _ComponentRegistry.get(component) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 63226f6ba..6b1fe4d1b 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -55,10 +55,12 @@ from pluginmanager import PluginManager from dbusinterface import DbusInterface from queuedtorrents import QueuedTorrents from deluge.configmanager import ConfigManager +import deluge.common from deluge.log import LOG as log import deluge.configmanager DEFAULT_PREFS = { + "config_location": deluge.common.get_config_dir(), "interactive_add": False, "enable_files_dialog": False, "enable_system_tray": True, @@ -151,6 +153,8 @@ class GtkUI: del config # Clean-up + # Shutdown all components + component.shutdown() del self.mainwindow del self.systemtray del self.menubar diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 54ccb5c31..bb6fbcf90 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -31,11 +31,15 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import cPickle +import os.path + import pygtk pygtk.require('2.0') import gtk import gettext +from deluge.configmanager import ConfigManager import deluge.common from deluge.log import LOG as log @@ -84,6 +88,16 @@ def cell_data_ratio(column, cell, model, row, data): ratio_str = "%.3f" % ratio cell.set_property('text', ratio_str) +class ListViewColumnState: + """Used for saving/loading column state""" + def __init__(self, name, position, width, visible, sort, sort_order): + self.name = name + self.position = position + self.width = width + self.visible = visible + self.sort = sort + self.sort_order = sort_order + class ListView: """ListView is used to make custom GtkTreeViews. It supports the adding and removing of columns, creating a menu for a column toggle list and @@ -106,8 +120,7 @@ class ListView: # If column is 'hidden' then it will not be visible and will not # show up in any menu listing; it cannot be shown ever. self.hidden = False - - + def __init__(self, treeview_widget=None): log.debug("ListView initialized..") @@ -138,6 +151,50 @@ class ListView: # created. self.checklist_menus = [] + def save_state(self, filename): + """Saves the listview state (column positions and visibility) to + filename.""" + # A list of ListViewColumnStates + state = [] + + # Get the list of TreeViewColumns from the TreeView + treeview_columns = self.treeview.get_columns() + counter = 0 + for column in treeview_columns: + # Append a new column state to the state list + state.append(ListViewColumnState(column.get_title(), counter, + column.get_width(), column.get_visible(), + column.get_sort_indicator(), int(column.get_sort_order()))) + # Increase the counter because this is how we determine position + counter += 1 + + # Get the config location for saving the state file + config_location = ConfigManager("gtkui.conf")["config_location"] + + try: + log.debug("Saving ListView state file: %s", filename) + state_file = open(os.path.join(config_location, filename), "wb") + cPickle.dump(state, state_file) + state_file.close() + except IOError, e: + log.warning("Unable to save state file: %s", e) + + def load_state(self, filename): + """Load the listview state from filename.""" + # Get the config location for loading the state file + config_location = ConfigManager("gtkui.conf")["config_location"] + + try: + log.debug("Loading ListView state file: %s", filename) + state_file = open(os.path.join(config_location, filename), "rb") + state = cPickle.load(state_file) + state_file.close() + except IOError: + log.warning("Unable to load state file: %s", e) + + # Keep the state in self.state so we can access it as we add new columns + self.state = state + def set_treeview(self, treeview_widget): """Set the treeview widget that this listview uses.""" self.treeview = treeview_widget @@ -317,6 +374,7 @@ class ListView: elif column_type == None: return + column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) column.set_resizable(True) @@ -324,10 +382,23 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) column.set_visible(not hidden) + + # Check for loaded state and apply + for column_state in self.state: + if header == column_state.name: + # We found a loaded state + if column_state.width > 0: + column.set_fixed_width(column_state.width) + column.set_sort_indicator(column_state.sort) + column.set_sort_order(column_state.sort_order) + column.set_visible(column_state.visible) + position = column_state.position + if position is not None: self.treeview.insert_column(column, position) else: self.treeview.append_column(column) + # Set hidden in the column self.columns[header].hidden = hidden self.columns[header].column = column diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index b0ede5e55..30378b0d4 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -107,7 +107,6 @@ class StatusBar(component.Component): self.show_not_connected() def start(self): - log.debug("StatusBar start..") # Add in images and labels self.remove_item(self.not_connected_item) self.connections_item = StatusBarItem( diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index c0be1eef7..326fd149f 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -99,7 +99,6 @@ class SystemTray(component.Component): self.tray_glade.get_widget(widget).hide() def start(self): - log.debug("SystemTray start..") if self.config["enable_system_tray"]: # Show widgets in the hide list because we've connected to a host for widget in self.hide_widget_list: @@ -109,7 +108,6 @@ class SystemTray(component.Component): self.build_tray_bwsetsubmenu() def stop(self): - log.debug("SystemTray stop..") try: # Hide widgets in hide list because we're not connected to a host for widget in self.hide_widget_list: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 5a5766632..170c5c2b7 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -116,7 +116,9 @@ class TorrentView(listview.ListView, component.Component): listview.ListView.__init__(self, self.window.main_glade.get_widget("torrent_view")) log.debug("TorrentView Init..") - + # Try to load the state file if available + self.load_state("torrentview.state") + # Register the columns menu with the listview so it gets updated # accordingly. self.register_checklist_menu( @@ -185,7 +187,7 @@ class TorrentView(listview.ListView, component.Component): # changes. self.treeview.get_selection().connect("changed", self.on_selection_changed) - + def start(self): """Start the torrentview""" # We need to get the core session state to know which torrents are in @@ -198,7 +200,11 @@ class TorrentView(listview.ListView, component.Component): """Stops the torrentview""" # We need to clear the liststore self.liststore.clear() - + + def shutdown(self): + """Called when GtkUi is exiting""" + self.save_state("torrentview.state") + def set_filter(self, field, condition): """Sets filters for the torrentview..""" self.filter = (field, condition) From 26350c50999fb089d1e77e50898158b7e6c29a14 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Dec 2007 21:23:08 +0000 Subject: [PATCH 0283/1009] Fix listview. --- deluge/ui/gtkui/listview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index bb6fbcf90..a21fce352 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -189,7 +189,7 @@ class ListView: state_file = open(os.path.join(config_location, filename), "rb") state = cPickle.load(state_file) state_file.close() - except IOError: + except IOError, e: log.warning("Unable to load state file: %s", e) # Keep the state in self.state so we can access it as we add new columns From 9e9eee5d2c9f7b65092873c019aabb75842f48aa Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 8 Dec 2007 21:39:19 +0000 Subject: [PATCH 0284/1009] Fix loading when no .state file exists. --- deluge/ui/gtkui/listview.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index a21fce352..40895cdc0 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -183,6 +183,7 @@ class ListView: """Load the listview state from filename.""" # Get the config location for loading the state file config_location = ConfigManager("gtkui.conf")["config_location"] + state = None try: log.debug("Loading ListView state file: %s", filename) @@ -374,7 +375,6 @@ class ListView: elif column_type == None: return - column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) column.set_resizable(True) @@ -384,15 +384,17 @@ class ListView: column.set_visible(not hidden) # Check for loaded state and apply - for column_state in self.state: - if header == column_state.name: - # We found a loaded state - if column_state.width > 0: - column.set_fixed_width(column_state.width) - column.set_sort_indicator(column_state.sort) - column.set_sort_order(column_state.sort_order) - column.set_visible(column_state.visible) - position = column_state.position + if self.state != None: + for column_state in self.state: + if header == column_state.name: + # We found a loaded state + if column_state.width > 0: + column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + column.set_fixed_width(column_state.width) + column.set_sort_indicator(column_state.sort) + column.set_sort_order(column_state.sort_order) + column.set_visible(column_state.visible) + position = column_state.position if position is not None: self.treeview.insert_column(column, position) From 559fcdf51ca189ce24e87331393697ec62a1c952 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 10 Dec 2007 10:26:44 +0000 Subject: [PATCH 0285/1009] Move component.py up one level. --- deluge/{ui => }/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename deluge/{ui => }/component.py (99%) diff --git a/deluge/ui/component.py b/deluge/component.py similarity index 99% rename from deluge/ui/component.py rename to deluge/component.py index d824ec244..85e4f3cb8 100644 --- a/deluge/ui/component.py +++ b/deluge/component.py @@ -141,7 +141,7 @@ class ComponentRegistry: _ComponentRegistry = ComponentRegistry() def register(name, obj, depend=None): - """Registers a UI component with the registry""" + """Registers a component with the registry""" _ComponentRegistry.register(name, obj, depend) def start(): From 78b78ce8b3bbc24e04814c17da5796ce62835c7a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 10 Dec 2007 10:49:42 +0000 Subject: [PATCH 0286/1009] Use deluge.component for core components. --- deluge/common.py | 4 ++++ deluge/core/alertmanager.py | 16 ++++++++++++++-- deluge/core/core.py | 6 ++++-- deluge/core/pluginmanager.py | 5 ++++- deluge/core/signalmanager.py | 7 ++++++- deluge/core/torrentmanager.py | 24 +++++++++++++++--------- deluge/ui/gtkui/connectionmanager.py | 2 +- deluge/ui/gtkui/dbusinterface.py | 2 +- deluge/ui/gtkui/gtkui.py | 2 +- deluge/ui/gtkui/mainwindow.py | 2 +- deluge/ui/gtkui/menubar.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 2 +- deluge/ui/gtkui/preferences.py | 2 +- deluge/ui/gtkui/queuedtorrents.py | 2 +- deluge/ui/gtkui/removetorrentdialog.py | 2 +- deluge/ui/gtkui/sidebar.py | 2 +- deluge/ui/gtkui/signals.py | 2 +- deluge/ui/gtkui/statusbar.py | 2 +- deluge/ui/gtkui/systemtray.py | 2 +- deluge/ui/gtkui/toolbar.py | 2 +- deluge/ui/gtkui/torrentdetails.py | 2 +- deluge/ui/gtkui/torrentview.py | 2 +- 22 files changed, 63 insertions(+), 31 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index e437cf298..52dba9414 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -101,6 +101,10 @@ def get_logo(size): return gtk.gdk.pixbuf_new_from_file_at_size(get_pixmap("deluge.svg"), \ size, size) +def open_file(path): + """Opens a file or folder.""" + os.popen("xdg-open %s" % path) + ## Formatting text functions def fsize(fsize_b): diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index 4df546005..4d1be6e03 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -35,19 +35,31 @@ import gobject +import deluge.component as component import deluge.libtorrent as lt from deluge.log import LOG as log -class AlertManager: +class AlertManager(component.Component): def __init__(self, session): log.debug("AlertManager initialized..") + component.Component.__init__(self, "AlertManager") self.session = session self.session.set_severity_level(lt.alert.severity_levels.info) # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} self.handlers = {} + + def start(self): # Handle the alerts every 50 milliseconds - gobject.timeout_add(50, self.handle_alerts) + self.timer = gobject.timeout_add(50, self.handle_alerts) + def stop(self): + gobject.source_remove(self.timer) + + def shutdown(self): + self.stop() + del self.session + del self.handlers + def register_handler(self, alert_type, handler): """Registers a function that will be called when 'alert_type' is pop'd in handle_alerts. The handler function should look like: diff --git a/deluge/core/core.py b/deluge/core/core.py index 0fa3591f6..9cfbc24f9 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -48,6 +48,7 @@ import threading import deluge.libtorrent as lt from deluge.configmanager import ConfigManager import deluge.common +import deluge.component as component from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager from deluge.core.alertmanager import AlertManager @@ -213,6 +214,8 @@ class Core( self.alerts.register_handler("torrent_paused_alert", self._on_alert_torrent_paused) + component.start() + t = threading.Thread(target=self.serve_forever) t.setDaemon(True) t.start() @@ -224,8 +227,7 @@ class Core( def _shutdown(self): """This is called by a thread from shutdown()""" log.info("Shutting down core..") - self.plugins.shutdown() - self.torrents.shutdown() + component.shutdown() # Make sure the config file has been saved self.config.save() del self.config diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index c4b3e97da..b57877754 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -34,13 +34,16 @@ """PluginManager for Core""" import deluge.pluginmanagerbase +import deluge.component as component from deluge.log import LOG as log -class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): +class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, + component.Component): """PluginManager handles the loading of plugins and provides plugins with functions to access parts of the core.""" def __init__(self, core): + component.Component.__init__(self, "PluginManager") self.core = core # Set up the hooks dictionary self.hooks = { diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index d632f9c52..ed13be23f 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -33,12 +33,17 @@ import xmlrpclib +import deluge.component as component from deluge.log import LOG as log -class SignalManager: +class SignalManager(component.Component): def __init__(self): + component.Component.__init__(self, "SignalManager") self.clients = {} + def shutdown(self): + del self.clients + def deregister_client(self, address): """Deregisters a client""" log.debug("Deregistering %s as a signal reciever..", address) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index a1d4476f1..26fe9aed7 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -42,6 +42,7 @@ import gobject import deluge.libtorrent as lt import deluge.common +import deluge.component as component from deluge.configmanager import ConfigManager from deluge.core.torrent import Torrent from deluge.log import LOG as log @@ -61,12 +62,13 @@ class TorrentManagerState: def __init__(self): self.torrents = [] -class TorrentManager: +class TorrentManager(component.Component): """TorrentManager contains a list of torrents in the current libtorrent session. This object is also responsible for saving the state of the session for use on restart.""" def __init__(self, session, alerts): + component.Component.__init__(self, "TorrentManager") log.debug("TorrentManager init..") # Set the libtorrent session self.session = session @@ -79,11 +81,6 @@ class TorrentManager: self.max_uploads = -1 # Create the torrents dict { torrent_id: Torrent } self.torrents = {} - # Try to load the state from file - self.load_state() - - # Save the state every 5 minutes - self.save_state_timer = gobject.timeout_add(300000, self.save_state) # Register set functions self.config.register_set_function("max_connections_per_torrent", @@ -103,9 +100,15 @@ class TorrentManager: self.alerts.register_handler("tracker_alert", self.on_alert_tracker) self.alerts.register_handler("tracker_warning_alert", self.on_alert_tracker_warning) - - def shutdown(self): - log.debug("TorrentManager shutting down..") + + def start(self): + # Try to load the state from file + self.load_state() + + # Save the state every 5 minutes + self.save_state_timer = gobject.timeout_add(300000, self.save_state) + + def stop(self): # Save state on shutdown self.save_state() # Pause all torrents and save the .fastresume files @@ -113,6 +116,9 @@ class TorrentManager: for key in self.torrents.keys(): self.write_fastresume(key) + def shutdown(self): + self.stop() + def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" return self.torrents[torrent_id] diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 17ca1a1e3..bad92d6cc 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -37,7 +37,7 @@ import gobject import socket import os -import deluge.ui.component as component +import deluge.component as component import deluge.xmlrpclib as xmlrpclib import deluge.common import deluge.ui.client as client diff --git a/deluge/ui/gtkui/dbusinterface.py b/deluge/ui/gtkui/dbusinterface.py index d4699f526..ff44a70c1 100644 --- a/deluge/ui/gtkui/dbusinterface.py +++ b/deluge/ui/gtkui/dbusinterface.py @@ -45,7 +45,7 @@ elif dbus.version >= (0,80,0): from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 6b1fe4d1b..48ccd6ffa 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -38,7 +38,7 @@ import gettext import locale import pkg_resources -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client from mainwindow import MainWindow from menubar import MenuBar diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index df95d0df1..912f240d1 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -38,7 +38,7 @@ import gobject import pkg_resources import deluge.ui.client as client -import deluge.ui.component as component +import deluge.component as component from deluge.configmanager import ConfigManager import deluge.common diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index bb7c42ed8..49d572c57 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -36,7 +36,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 48148df53..41bf263b0 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import deluge.ui.component as component +import deluge.component as component import deluge.pluginmanagerbase import deluge.ui.client as client from deluge.configmanager import ConfigManager diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 44d5e581c..192fc75de 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -36,7 +36,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import deluge.ui.component as component +import deluge.component as component from deluge.log import LOG as log import deluge.ui.client as client import deluge.common diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index 9ea4efe3b..eac987d9c 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -36,7 +36,7 @@ import os.path import gtk, gtk.glade import pkg_resources -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager diff --git a/deluge/ui/gtkui/removetorrentdialog.py b/deluge/ui/gtkui/removetorrentdialog.py index 839985daa..b9de23bed 100644 --- a/deluge/ui/gtkui/removetorrentdialog.py +++ b/deluge/ui/gtkui/removetorrentdialog.py @@ -36,7 +36,7 @@ import pkg_resources import deluge.common import deluge.ui.client as client -import deluge.ui.component as component +import deluge.component as component from deluge.log import LOG as log class RemoveTorrentDialog: diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 9f0b37391..3e67c1d53 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -34,7 +34,7 @@ import gtk import gtk.glade -import deluge.ui.component as component +import deluge.component as component import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 9f8b948f3..aaed607a3 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client from deluge.ui.signalreceiver import SignalReceiver from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 30378b0d4..945d0c00f 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -33,7 +33,7 @@ import gtk -import deluge.ui.component as component +import deluge.component as component import deluge.common import deluge.ui.client as client from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 326fd149f..e93e0a91c 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -34,7 +34,7 @@ import gtk import pkg_resources -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 7dd7c858e..e776de875 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -36,7 +36,7 @@ pygtk.require('2.0') import gtk, gtk.glade import gobject -import deluge.ui.component as component +import deluge.component as component from deluge.log import LOG as log from deluge.common import TORRENT_STATE import deluge.ui.client as client diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index be985887a..580cf360f 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -38,7 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 170c5c2b7..77360b09a 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,7 +40,7 @@ import gettext import gobject import deluge.common -import deluge.ui.component as component +import deluge.component as component import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview From f3cdfa835103e4b0aa9c9e7c22f0e3d16f41230e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 11 Dec 2007 01:35:36 +0000 Subject: [PATCH 0287/1009] Fix Queue plugin loading when state file is wrong. --- deluge/core/pluginmanager.py | 5 ++++- deluge/plugins/queue/queue/core.py | 6 ++++-- deluge/plugins/queue/queue/torrentqueue.py | 23 ++++++++++++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index b57877754..067f63056 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -112,4 +112,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, log.debug("run_post_torrent_remove") for function in self.hooks["post_torrent_remove"]: function(torrent_id) - + + def get_torrent_list(self): + """Returns a list of torrent_id's in the current session.""" + return component.get("TorrentManager").get_torrent_list() diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 12f354b02..992ee7454 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -37,8 +37,10 @@ from deluge.plugins.corepluginbase import CorePluginBase class Core(CorePluginBase): def enable(self): - # Instantiate the TorrentQueue object - self.queue = TorrentQueue() + # Get a list of torrent_ids in the session + # We give this to the TorrentQueue to compare with the saved state + # and create the queuing order. + self.queue = TorrentQueue(self.plugin.get_torrent_list()) # Register core hooks self.plugin.register_hook("post_torrent_add", self._post_torrent_add) diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py index 2677240fe..a07df8a91 100644 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -37,10 +37,19 @@ import deluge.common from deluge.log import LOG as log class TorrentQueue: - def __init__(self): - self.queue = [] + def __init__(self, torrent_list): # Try to load the queue state from file - self.load_state() + self.queue = self.load_state() + # First remove any torrent_ids in self.queue that are not in the current + # session list. + for torrent_id in self.queue: + if torrent_id not in torrent_list: + self.queue.remove(torrent_id) + + # Next we append any torrents in the session list to self.queue + for torrent_id in torrent_list: + if torrent_id not in self.queue: + self.queue.append(torrent_id) def __getitem__(self, torrent_id): """Return the queue position of the torrent_id""" @@ -57,9 +66,11 @@ class TorrentQueue: "rb") state = pickle.load(state_file) state_file.close() - self.queue = state - except IOError: - log.warning("Unable to load queue state file.") + return state + except IOError, e: + log.warning("Unable to load queue state file: %s", e) + + return [] def save_state(self): """Save the queue state""" From 8271d70ec7adc1bac5158bad875283d4ac063323 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 11 Dec 2007 04:45:47 +0000 Subject: [PATCH 0288/1009] Plugin system changes in regards to enabling/disabling plugins. --- deluge/core/pluginmanager.py | 28 +++++++++++++++++++++- deluge/pluginmanagerbase.py | 13 ++++++---- deluge/plugins/corepluginbase.py | 1 + deluge/plugins/queue/queue/core.py | 3 +++ deluge/plugins/queue/queue/torrentqueue.py | 3 ++- deluge/ui/gtkui/pluginmanager.py | 12 +++++++--- 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 067f63056..eb69f4e18 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -33,6 +33,8 @@ """PluginManager for Core""" +import gobject + import deluge.pluginmanagerbase import deluge.component as component from deluge.log import LOG as log @@ -52,11 +54,35 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, } self.status_fields = {} - + # Call the PluginManagerBase constructor deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "core.conf", "deluge.plugin.core") + def start(self): + # Enable plugins that are enabled in the config + self.enable_plugins() + + # Set update timer to call update() in plugins every second + self.update_timer = gobject.timeout_add(1000, self.update_plugins) + + def stop(self): + # Disable all enabled plugins + self.disable_plugins() + # Stop the update timer + gobject.source_remove(self.update_timer) + + def shutdown(self): + self.stop() + + def update_plugins(self): + for plugin in self.plugins.keys(): + try: + self.plugins[plugin].update() + except AttributeError: + # We don't care if this doesn't work + pass + def get_core(self): """Returns a reference to the core""" return self.core diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index a66d9543b..774001a2c 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -58,14 +58,15 @@ class PluginManagerBase: # Scan the plugin folders for plugins self.scan_for_plugins() + def enable_plugins(self): # Load plugins that are enabled in the config. for name in self.config["enabled_plugins"]: self.enable_plugin(name) - - def shutdown(self): - for plugin in self.plugins.values(): - plugin.disable() - del self.plugins + + def disable_plugins(self): + # Disable all plugins that are enabled + for key in self.plugins.keys(): + self.plugins[key].disable() def __getitem__(self, key): return self.plugins[key] @@ -111,6 +112,8 @@ class PluginManagerBase: plugin_name = plugin_name.replace("-", " ") self.plugins[plugin_name] = instance if plugin_name not in self.config["enabled_plugins"]: + log.debug("Adding %s to enabled_plugins list in config", + plugin_name) self.config["enabled_plugins"].append(plugin_name) log.info("Plugin %s enabled..", plugin_name) diff --git a/deluge/plugins/corepluginbase.py b/deluge/plugins/corepluginbase.py index 188dc5053..e6f393a17 100644 --- a/deluge/plugins/corepluginbase.py +++ b/deluge/plugins/corepluginbase.py @@ -45,3 +45,4 @@ class CorePluginBase: getattr(self, "%s" % func), plugin_name.lower()\ + "_" + func[7:]) log.debug("CorePlugin initialized..") + diff --git a/deluge/plugins/queue/queue/core.py b/deluge/plugins/queue/queue/core.py index 992ee7454..c8ea98659 100644 --- a/deluge/plugins/queue/queue/core.py +++ b/deluge/plugins/queue/queue/core.py @@ -66,6 +66,9 @@ class Core(CorePluginBase): # De-register status fields self.plugin.deregister_status_field("queue") + def update(self): + pass + ## Hooks for core ## def _post_torrent_add(self, torrent_id): if torrent_id is not None: diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py index a07df8a91..87ddd6281 100644 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ b/deluge/plugins/queue/queue/torrentqueue.py @@ -40,6 +40,7 @@ class TorrentQueue: def __init__(self, torrent_list): # Try to load the queue state from file self.queue = self.load_state() + # First remove any torrent_ids in self.queue that are not in the current # session list. for torrent_id in self.queue: @@ -50,7 +51,7 @@ class TorrentQueue: for torrent_id in torrent_list: if torrent_id not in self.queue: self.queue.append(torrent_id) - + def __getitem__(self, torrent_id): """Return the queue position of the torrent_id""" try: diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 41bf263b0..8a545faaf 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -42,7 +42,9 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def __init__(self): component.Component.__init__(self, "PluginManager") self.config = ConfigManager("gtkui.conf") - + deluge.pluginmanagerbase.PluginManagerBase.__init__( + self, "gtkui.conf", "deluge.plugin.gtkui") + def start(self): """Start the plugin manager""" # Update the enabled_plugins from the core @@ -51,9 +53,13 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, enabled_plugins = list(set(enabled_plugins)) self.config["enabled_plugins"] = enabled_plugins - deluge.pluginmanagerbase.PluginManagerBase.__init__( - self, "gtkui.conf", "deluge.plugin.gtkui") + # Enable the plugins that are enabled in the config and core + self.enable_plugins() + def stop(self): + # Disable the plugins + self.disable_plugins() + ## Plugin functions.. will likely move to own class.. def add_torrentview_text_column(self, *args, **kwargs): From 83be470f1eab6c5f570220dd89a3bb9c46b92aa4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 13 Dec 2007 06:30:39 +0000 Subject: [PATCH 0289/1009] Libtorrent sync 1814 --- libtorrent/bindings/python/src/docstrings.cpp | 2 - libtorrent/bindings/python/src/session.cpp | 3 -- libtorrent/include/libtorrent/alert.hpp | 2 +- .../include/libtorrent/aux_/session_impl.hpp | 2 +- .../include/libtorrent/bandwidth_manager.hpp | 13 +++-- .../include/libtorrent/disk_io_thread.hpp | 3 +- .../libtorrent/kademlia/dht_tracker.hpp | 15 +++++- libtorrent/include/libtorrent/session.hpp | 2 +- .../include/libtorrent/session_settings.hpp | 2 +- libtorrent/include/libtorrent/time.hpp | 1 + libtorrent/src/alert.cpp | 10 ++-- libtorrent/src/disk_io_thread.cpp | 34 +++++-------- libtorrent/src/http_connection.cpp | 12 ++++- libtorrent/src/http_tracker_connection.cpp | 6 ++- libtorrent/src/kademlia/dht_tracker.cpp | 48 +++++++++++++++++++ libtorrent/src/kademlia/rpc_manager.cpp | 4 +- libtorrent/src/session.cpp | 2 +- libtorrent/src/session_impl.cpp | 4 +- libtorrent/src/torrent.cpp | 6 +++ 19 files changed, 122 insertions(+), 49 deletions(-) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index f2aa7bfd0..0fa001f39 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -177,8 +177,6 @@ char const* session_start_natpmp_doc = ""; char const* session_stop_natpmp_doc = ""; -char const* session_wait_for_alert_doc = - ""; // -- alert ----------------------------------------------------------------- diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 1b9f0ffc3..481348730 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "gil.hpp" @@ -57,7 +56,6 @@ extern char const* session_stop_lsd_doc; extern char const* session_stop_upnp_doc; extern char const* session_start_natpmp_doc; extern char const* session_stop_natpmp_doc; -extern char const* session_wait_for_alert_doc; namespace { @@ -250,7 +248,6 @@ void bind_session() .def("stop_lsd", allow_threads(&session::stop_lsd), session_stop_lsd_doc) .def("start_natpmp", allow_threads(&session::start_natpmp), session_start_natpmp_doc) .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) - .def("wait_for_alert", allow_threads(&session::wait_for_alert), session_wait_for_alert_doc) ; register_ptr_to_python >(); diff --git a/libtorrent/include/libtorrent/alert.hpp b/libtorrent/include/libtorrent/alert.hpp index 896443aaa..ab8065f1f 100755 --- a/libtorrent/include/libtorrent/alert.hpp +++ b/libtorrent/include/libtorrent/alert.hpp @@ -100,7 +100,7 @@ namespace libtorrent { void set_severity(alert::severity_t severity); bool should_post(alert::severity_t severity) const; - std::auto_ptr wait_for_alert(time_duration max_wait); + alert const* wait_for_alert(time_duration max_wait); private: std::queue m_alerts; diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 803d78e00..cf627c70b 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -280,7 +280,7 @@ namespace libtorrent void set_severity_level(alert::severity_t s); std::auto_ptr pop_alert(); - std::auto_ptr wait_for_alert(time_duration max_wait); + alert const* wait_for_alert(time_duration max_wait); int upload_rate_limit() const; int download_rate_limit() const; diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index ef132543f..da251cf4b 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -111,7 +111,7 @@ struct bandwidth_limit void assign(int amount) throw() { - TORRENT_ASSERT(amount > 0); + TORRENT_ASSERT(amount >= 0); m_current_rate += amount; m_quota_left += amount; } @@ -225,8 +225,14 @@ struct bandwidth_manager } #endif - TORRENT_ASSERT(peer->max_assignable_bandwidth(m_channel) > 0); boost::shared_ptr t = peer->associated_torrent().lock(); + + if (peer->max_assignable_bandwidth(m_channel) == 0) + { + t->expire_bandwidth(m_channel, blk); + peer->assign_bandwidth(m_channel, 0); + return; + } m_queue.push_back(bw_queue_entry(peer, blk, non_prioritized)); if (!non_prioritized) { @@ -389,6 +395,7 @@ private: if (max_assignable == 0) { t->expire_bandwidth(m_channel, qe.max_block_size); + qe.peer->assign_bandwidth(m_channel, 0); TORRENT_ASSERT(amount == limit - m_current_quota); continue; } @@ -430,7 +437,7 @@ private: if (block_size > qe.max_block_size) block_size = qe.max_block_size; #ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; + std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; #endif if (amount < block_size / 2) { diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index b93ea8b75..a77522356 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -124,7 +124,8 @@ namespace libtorrent private: - mutable boost::mutex m_mutex; + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; boost::condition m_signal; bool m_abort; std::deque m_jobs; diff --git a/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp b/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp index f61364707..c032c19d1 100644 --- a/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp +++ b/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp @@ -127,7 +127,20 @@ namespace libtorrent { namespace dht // used to resolve hostnames for nodes udp::resolver m_host_resolver; - + + // used to ignore abusive dht nodes + struct node_ban_entry + { + node_ban_entry(): count(0) {} + udp::endpoint src; + ptime limit; + int count; + }; + + enum { num_ban_nodes = 20 }; + + node_ban_entry m_ban_nodes[num_ban_nodes]; + // reference counter for intrusive_ptr mutable boost::detail::atomic_count m_refs; diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index b9a726fb8..d2ab6ab2e 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -265,7 +265,7 @@ namespace libtorrent std::auto_ptr pop_alert(); void set_severity_level(alert::severity_t s); - std::auto_ptr wait_for_alert(time_duration max_wait); + alert const* wait_for_alert(time_duration max_wait); connection_queue& get_connection_queue(); diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index df393692a..7cc7d26da 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -84,7 +84,7 @@ namespace libtorrent LIBTORRENT_VERSION) : user_agent(user_agent_) , tracker_completion_timeout(60) - , tracker_receive_timeout(20) + , tracker_receive_timeout(40) , stop_tracker_timeout(5) , tracker_maximum_response_length(1024*1024) , piece_timeout(10) diff --git a/libtorrent/include/libtorrent/time.hpp b/libtorrent/include/libtorrent/time.hpp index 4ab7a3819..ec1c0bce9 100644 --- a/libtorrent/include/libtorrent/time.hpp +++ b/libtorrent/include/libtorrent/time.hpp @@ -99,6 +99,7 @@ namespace libtorrent time_duration operator/(int rhs) const { return time_duration(diff / rhs); } explicit time_duration(boost::int64_t d) : diff(d) {} time_duration& operator-=(time_duration const& c) { diff -= c.diff; return *this; } + time_duration operator+(time_duration const& c) { return time_duration(diff + c.diff); } boost::int64_t diff; }; diff --git a/libtorrent/src/alert.cpp b/libtorrent/src/alert.cpp index 5b0f892f8..cb89147da 100755 --- a/libtorrent/src/alert.cpp +++ b/libtorrent/src/alert.cpp @@ -78,11 +78,11 @@ namespace libtorrent { } } - std::auto_ptr alert_manager::wait_for_alert(time_duration max_wait) + alert const* alert_manager::wait_for_alert(time_duration max_wait) { boost::mutex::scoped_lock lock(m_mutex); - if (!m_alerts.empty()) return std::auto_ptr(m_alerts.front()); + if (!m_alerts.empty()) return m_alerts.front(); int secs = total_seconds(max_wait); max_wait -= seconds(secs); @@ -96,10 +96,10 @@ namespace libtorrent { xt.sec += 1; } xt.nsec = nsec; - if (!m_condition.timed_wait(lock, xt)) return std::auto_ptr(NULL); + if (!m_condition.timed_wait(lock, xt)) return 0; TORRENT_ASSERT(!m_alerts.empty()); - if (m_alerts.empty()) return std::auto_ptr(NULL); - return std::auto_ptr(m_alerts.front()); + if (m_alerts.empty()) return 0; + return m_alerts.front(); } void alert_manager::post_alert(const alert& alert_) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index a9446f664..e0fa982bc 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -62,7 +62,7 @@ namespace libtorrent disk_io_thread::~disk_io_thread() { - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); m_abort = true; m_signal.notify_all(); l.unlock(); @@ -74,7 +74,7 @@ namespace libtorrent disk_io_job disk_io_thread::find_job(boost::intrusive_ptr s , int action, int piece) const { - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); for (std::deque::const_iterator i = m_jobs.begin(); i != m_jobs.end(); ++i) { @@ -98,7 +98,7 @@ namespace libtorrent // aborts read operations void disk_io_thread::stop(boost::intrusive_ptr s) { - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); // read jobs are aborted, write and move jobs are syncronized for (std::deque::iterator i = m_jobs.begin(); i != m_jobs.end();) @@ -151,7 +151,7 @@ namespace libtorrent { TORRENT_ASSERT(!j.callback); TORRENT_ASSERT(j.storage); - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); std::deque::reverse_iterator i = m_jobs.rbegin(); if (j.action == disk_io_job::read) @@ -206,7 +206,7 @@ namespace libtorrent char* disk_io_thread::allocate_buffer() { - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); #ifdef TORRENT_STATS ++m_allocations; #endif @@ -215,7 +215,7 @@ namespace libtorrent void disk_io_thread::free_buffer(char* buf) { - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); #ifdef TORRENT_STATS --m_allocations; #endif @@ -229,7 +229,7 @@ namespace libtorrent #ifdef TORRENT_DISK_STATS m_log << log_time() << " idle" << std::endl; #endif - boost::mutex::scoped_lock l(m_mutex); + mutex_t::scoped_lock l(m_mutex); #ifndef NDEBUG m_current.action = (disk_io_job::action_t)-1; m_current.piece = -1; @@ -250,7 +250,7 @@ namespace libtorrent int ret = 0; - bool free_buffer = true; + bool free_current_buffer = true; try { TORRENT_ASSERT(j.storage); @@ -264,15 +264,10 @@ namespace libtorrent #ifdef TORRENT_DISK_STATS m_log << log_time() << " read " << j.buffer_size << std::endl; #endif - free_buffer = false; + free_current_buffer = false; if (j.buffer == 0) { - l.lock(); - j.buffer = (char*)m_pool.ordered_malloc(); -#ifdef TORRENT_STATS - ++m_allocations; -#endif - l.unlock(); + j.buffer = allocate_buffer(); TORRENT_ASSERT(j.buffer_size <= m_block_size); if (j.buffer == 0) { @@ -347,14 +342,7 @@ namespace libtorrent m_current.callback.clear(); #endif - if (j.buffer && free_buffer) - { - l.lock(); - m_pool.ordered_free(j.buffer); -#ifdef TORRENT_STATS - --m_allocations; -#endif - } + if (j.buffer && free_current_buffer) free_buffer(j.buffer); } } } diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index a0d8e3fa3..a73d0a9af 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -284,7 +284,17 @@ void http_connection::on_read(asio::error_code const& e { libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0] , &m_recvbuffer[0] + m_read_pos); - m_parser.incoming(rcv_buf); + try + { + m_parser.incoming(rcv_buf); + } + catch (std::exception& e) + { + m_timer.cancel(); + m_handler(asio::error::fault, m_parser, 0, 0); + m_handler.clear(); + return; + } if (!m_bottled && m_parser.header_finished()) { if (m_read_pos > m_parser.body_start()) diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index 86d21e494..b9581e862 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -217,7 +217,11 @@ namespace libtorrent char dummy; std::string bytes; size_type range_start, range_end; - range_str >> bytes >> range_start >> dummy >> range_end; + // apparently some web servers do not send the "bytes" + // in their content-range + if (value.find(' ') != std::string::npos) + range_str >> bytes; + range_str >> range_start >> dummy >> range_end; if (!range_str || range_end < range_start) { throw std::runtime_error("invalid content-range in HTTP response: " + range_str.str()); diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index 3f47eb070..1a074bc20 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -405,6 +405,54 @@ namespace libtorrent { namespace dht if (error) return; + node_ban_entry* match = 0; + node_ban_entry* min = m_ban_nodes; + ptime now = time_now(); + for (node_ban_entry* i = m_ban_nodes; i < m_ban_nodes + num_ban_nodes; ++i) + { + if (i->src == m_remote_endpoint[current_buffer]) + { + match = i; + break; + } + if (i->count < min->count) min = i; + } + + if (match) + { + ++match->count; + if (match->count >= 20) + { + if (now < match->limit) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + if (match->count == 20) + { + TORRENT_LOG(dht_tracker) << time_now_string() << " BANNING PEER [ ip: " + << m_remote_endpoint[current_buffer] << " | " + "time: " << total_seconds((now - match->limit) + seconds(5)) + << " | count: " << match->count << " ]"; + } +#endif + // we've received 20 messages in less than 5 seconds from + // this node. Ignore it until it's silent for 5 minutes + match->limit = now + minutes(5); + return; + } + + // we got 50 messages from this peer, but it was in + // more than 5 seconds. Reset the counter and the timer + match->count = 0; + match->limit = now + seconds(5); + } + } + else + { + min->count = 1; + min->limit = now + seconds(5); + min->src = m_remote_endpoint[current_buffer]; + } + #ifdef TORRENT_DHT_VERBOSE_LOGGING ++m_total_message_input; m_total_in_bytes += bytes_transferred; diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp index 5ae448501..e4019bab8 100644 --- a/libtorrent/src/kademlia/rpc_manager.cpp +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -213,11 +213,11 @@ bool rpc_manager::incoming(msg const& m) return false; } - if (m.addr != o->target_addr) + if (m.addr.address() != o->target_addr.address()) { #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(rpc) << "Reply with incorrect address and valid transaction id: " - << tid << " from " << m.addr; + << tid << " from " << m.addr << " expected: " << o->target_addr; #endif return false; } diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 8efad6b0d..331ffa377 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -422,7 +422,7 @@ namespace libtorrent return m_impl->pop_alert(); } - std::auto_ptr session::wait_for_alert(time_duration max_wait) + alert const* session::wait_for_alert(time_duration max_wait) { return m_impl->wait_for_alert(max_wait); } diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index fdaacfabc..c1969d523 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -1836,7 +1836,7 @@ namespace detail t.abort(); if ((!t.is_paused() || t.should_request()) - && !t.torrent_file().trackers().empty()) + && !t.trackers().empty()) { tracker_request req = t.generate_tracker_request(); TORRENT_ASSERT(req.event == tracker_request::stopped); @@ -2272,7 +2272,7 @@ namespace detail return std::auto_ptr(0); } - std::auto_ptr session_impl::wait_for_alert(time_duration max_wait) + alert const* session_impl::wait_for_alert(time_duration max_wait) { return m_alerts.wait_for_alert(max_wait); } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index d28c97d47..557289d60 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -735,8 +735,10 @@ namespace libtorrent return make_tuple(m_torrent_file->total_size() , m_torrent_file->total_size()); + TORRENT_ASSERT(m_num_pieces >= m_picker->num_have_filtered()); size_type wanted_done = size_type(m_num_pieces - m_picker->num_have_filtered()) * piece_size; + TORRENT_ASSERT(wanted_done >= 0); size_type total_done = size_type(m_num_pieces) * piece_size; @@ -762,6 +764,7 @@ namespace libtorrent TORRENT_ASSERT(total_done <= m_torrent_file->total_size()); TORRENT_ASSERT(wanted_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(total_done >= wanted_done); const std::vector& dl_queue = m_picker->get_download_queue(); @@ -2760,6 +2763,8 @@ namespace libtorrent #endif disconnect_all(); + if (!m_paused) + m_just_paused = true; m_paused = true; // tell the tracker that we stopped m_event = tracker_request::stopped; @@ -3033,6 +3038,7 @@ namespace libtorrent st.num_incomplete = m_incomplete; st.paused = m_paused; boost::tie(st.total_done, st.total_wanted_done) = bytes_done(); + TORRENT_ASSERT(st.total_done >= st.total_wanted_done); // payload transfer st.total_payload_download = m_stat.total_payload_download(); From f133704905d5c497ec053e8c8d10a0b86d0ea4f6 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 14 Dec 2007 03:48:16 +0000 Subject: [PATCH 0290/1009] home env doesnt exist in windows, so work around that --- deluge/common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/common.py b/deluge/common.py index 52dba9414..ed5f8fdf3 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -67,7 +67,10 @@ def get_config_dir(filename=None): def get_default_download_dir(): """Returns the default download directory""" - return os.environ.get("HOME") + if common.windows_check(): + return os.path.expanduser("~") + else: + return os.environ.get("HOME") def get_default_torrent_dir(): """Returns the default torrent directory""" From 62bd4a86f8b35de690347dc072b81902a3bfeca1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 14 Dec 2007 06:38:11 +0000 Subject: [PATCH 0291/1009] Fix EditTrackersDialog to import deluge.component correctly. --- deluge/ui/gtkui/edittrackersdialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index 87d100e92..0784e21f9 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -36,7 +36,7 @@ import pkg_resources import deluge.common import deluge.ui.client as client -import deluge.ui.component as component +import deluge.component as component from deluge.log import LOG as log class EditTrackersDialog: From 6733ce38d2d35defc24c424579c82bb75aa8c9f3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 14 Dec 2007 07:44:53 +0000 Subject: [PATCH 0292/1009] Add removals to the setup script. Do not display the Open Folder menu item if not connected to a localhost. --- TODO | 1 - deluge/ui/gtkui/menubar.py | 20 +++++++++++++++++--- setup.py | 19 +++++++++++++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index d75e19416..c55352416 100644 --- a/TODO +++ b/TODO @@ -9,7 +9,6 @@ * Maybe add pop-up menus to the status bar items * Address issue where torrents will redownload if the storage is moved outside of deluge. -* Hide open folder if not localhost * Implement 'Classic' mode * Tray tooltip * Add autoload folder diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 49d572c57..c2bc775eb 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -47,11 +47,11 @@ class MenuBar(component.Component): component.Component.__init__(self, "MenuBar") self.window = component.get("MainWindow") # Get the torrent menu from the glade file - torrentmenu_glade = gtk.glade.XML( + self.torrentmenu_glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/torrent_menu.glade")) - self.torrentmenu = torrentmenu_glade.get_widget("torrent_menu") + self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu") self.menu_torrent = self.window.main_glade.get_widget("menu_torrent") # Attach the torrent_menu to the Torrent file menu @@ -88,7 +88,7 @@ class MenuBar(component.Component): "on_menuitem_about_activate": self.on_menuitem_about_activate }) - torrentmenu_glade.signal_autoconnect({ + self.torrentmenu_glade.signal_autoconnect({ ## Torrent Menu "on_menuitem_pause_activate": self.on_menuitem_pause_activate, "on_menuitem_resume_activate": self.on_menuitem_resume_activate, @@ -111,6 +111,20 @@ class MenuBar(component.Component): for widget in self.change_sensitivity: self.window.main_glade.get_widget(widget).set_sensitive(True) + # Hide the Open Folder menuitem and separator if not connected to a + # localhost. + non_remote_items = [ + "menuitem_open_folder", + "separator4" + ] + if not client.is_localhost(): + for widget in non_remote_items: + self.torrentmenu_glade.get_widget(widget).hide() + self.torrentmenu_glade.get_widget(widget).set_no_show_all(True) + else: + for widget in non_remote_items: + self.torrentmenu_glade.get_widget(widget).set_no_show_all(False) + # Show the Torrent menu because we're connected to a host self.menu_torrent.show() diff --git a/setup.py b/setup.py index 8fa470385..51a3a913c 100644 --- a/setup.py +++ b/setup.py @@ -32,13 +32,12 @@ import ez_setup ez_setup.use_setuptools() from setuptools import setup, find_packages, Extension -from distutils import cmd +from distutils import cmd, sysconfig from distutils.command.build import build as _build from distutils.command.install import install as _install from distutils.command.install_data import install_data as _install_data import msgfmt - import platform import glob import os @@ -54,10 +53,22 @@ _extra_compile_args = [ "-DHAVE_PTHREAD=1", "-DTORRENT_USE_OPENSSL=1", "-DHAVE_SSL=1", - "-g", - "-p" + "-O2" ] +removals = ["-Wstrict-prototypes"] + +if python_version == '2.5': + cv_opt = sysconfig.get_config_vars()["CFLAGS"] + for removal in removals: + cv_opt = cv_opt.replace(removal, " ") + sysconfig.get_config_vars()["CFLAGS"] = " ".join(cv_opt.split()) +else: + cv_opt = sysconfig.get_config_vars()["OPT"] + for removal in removals: + cv_opt = cv_opt.replace(removal, " ") + sysconfig.get_config_vars()["OPT"] = " ".join(cv_opt.split()) + _include_dirs = [ './libtorrent', './libtorrent/include', From 6dff50c3f5a0fe0b6bedaf60a2d74ff08c15f523 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Dec 2007 05:36:51 +0000 Subject: [PATCH 0293/1009] Import gobject now. Oops. --- deluge/ui/gtkui/queuedtorrents.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index eac987d9c..349755886 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -34,6 +34,7 @@ import os.path import gtk, gtk.glade +import gobject import pkg_resources import deluge.component as component From 58a0506b98aff5227f6e7220e1b29bc8a1a44b41 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Dec 2007 10:15:32 +0000 Subject: [PATCH 0294/1009] Sync lt 1825 --- libtorrent/include/libtorrent/torrent.hpp | 4 +++ libtorrent/src/piece_picker.cpp | 30 ++++++++++++++++------- libtorrent/src/torrent.cpp | 11 ++++++++- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 0f508e93f..4731da6b2 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -779,6 +779,10 @@ namespace libtorrent // the maximum number of connections for this torrent int m_max_connections; + +#ifndef NDEBUG + bool m_files_checked; +#endif #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list > extension_list_t; diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 831bd0986..dbdba17f0 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -80,10 +80,10 @@ namespace libtorrent TORRENT_ASSERT(m_blocks_in_last_piece <= m_blocks_per_piece); // allocate the piece_map to cover all pieces - // and make them invalid (as if though we already had every piece) + // and make them invalid (as if we don't have a single piece) std::fill(m_piece_map.begin(), m_piece_map.end() - , piece_pos(0, piece_pos::we_have_index)); - m_num_have = m_piece_map.size(); + , piece_pos(0, 0)); + m_num_have = 0; } // pieces is a bitmask with the pieces we have @@ -92,20 +92,29 @@ namespace libtorrent , std::vector const& unfinished , std::vector& verify_pieces) { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; #ifndef NDEBUG m_files_checked_called = true; #endif for (std::vector::const_iterator i = pieces.begin(); i != pieces.end(); ++i) { - if (*i) continue; int index = static_cast(i - pieces.begin()); - m_piece_map[index].index = 0; - --m_num_have; - if (m_piece_map[index].filtered()) + piece_pos& p = m_piece_map[index]; + if (*i) { - ++m_num_filtered; - --m_num_have_filtered; + ++m_num_have; + p.set_have(); + if (p.filtered()) + { + ++m_num_have_filtered; + TORRENT_ASSERT(m_num_filtered > 0); + --m_num_filtered; + } + } + else + { + p.index = 0; } } @@ -283,6 +292,9 @@ namespace libtorrent void piece_picker::check_invariant(const torrent* t) const { TORRENT_ASSERT(sizeof(piece_pos) == 4); + TORRENT_ASSERT(m_num_have >= 0); + TORRENT_ASSERT(m_num_have_filtered >= 0); + TORRENT_ASSERT(m_num_filtered >= 0); TORRENT_ASSERT(m_piece_info.empty() || m_piece_info[0].empty()); diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 557289d60..e8c1f2247 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -204,6 +204,9 @@ namespace libtorrent , m_max_connections((std::numeric_limits::max)()) , m_policy(this) { +#ifndef NDEBUG + m_files_checked = false; +#endif } torrent::torrent( @@ -263,6 +266,9 @@ namespace libtorrent , m_max_connections((std::numeric_limits::max)()) , m_policy(this) { +#ifndef NDEBUG + m_files_checked = false; +#endif INVARIANT_CHECK; if (name) m_name.reset(new std::string(name)); @@ -2521,6 +2527,9 @@ namespace libtorrent } } } +#ifndef NDEBUG + m_files_checked = true; +#endif } alert_manager& torrent::alerts() const @@ -2658,7 +2667,7 @@ namespace libtorrent complete = false; break; } - if (complete) + if (complete && m_files_checked) { disk_io_job ret = m_ses.m_disk_thread.find_job( m_owning_storage, -1, i->index); From 02fe961a8a973f8b1ef6b712747c51e472cd032b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Dec 2007 10:40:25 +0000 Subject: [PATCH 0295/1009] Fix deluge.common. --- deluge/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/common.py b/deluge/common.py index ed5f8fdf3..8ea1438fd 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -67,7 +67,7 @@ def get_config_dir(filename=None): def get_default_download_dir(): """Returns the default download directory""" - if common.windows_check(): + if windows_check(): return os.path.expanduser("~") else: return os.environ.get("HOME") From 419333b9cb520ae7028c018935a994fac5636584 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 16 Dec 2007 11:10:12 +0000 Subject: [PATCH 0296/1009] Only enable plugins that are enabled in the core. --- deluge/pluginmanagerbase.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 774001a2c..4f5108838 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -87,7 +87,7 @@ class PluginManagerBase: pkg_resources.working_set.add_entry(plugin_dir) pkg_resources.working_set.add_entry(user_plugin_dir) self.pkg_env = pkg_resources.Environment([plugin_dir, user_plugin_dir]) - + self.available_plugins = [] for name in self.pkg_env: pkg_name = str(self.pkg_env[name][0]).split()[0].replace("-", " ") diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 8a545faaf..b3e2416a9 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -49,8 +49,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, """Start the plugin manager""" # Update the enabled_plugins from the core enabled_plugins = client.get_enabled_plugins() - enabled_plugins += self.config["enabled_plugins"] - enabled_plugins = list(set(enabled_plugins)) + log.debug("Core has these plugins enabled: %s", enabled_plugins) self.config["enabled_plugins"] = enabled_plugins # Enable the plugins that are enabled in the config and core From 60fc0d24fef1fde8c9b744d469d60d91fecbe32d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Dec 2007 04:00:39 +0000 Subject: [PATCH 0297/1009] Start of caching for client.py. --- deluge/ui/client.py | 62 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 9d2c06b0e..7a52c57ea 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -34,12 +34,52 @@ import os.path import pickle import socket +import time import deluge.xmlrpclib as xmlrpclib import deluge.common from deluge.log import LOG as log +CACHE_TTL = 0.5 # seconds + +class cache: + def __init__(self, func): + self.func = func + + #self.cache_values = {(args, kwargs): (time, ret)} + self.cache_values = {} + + self.args = None + self.kwargs = None + self.ret = None + self.time = None + + def __call__(self, *__args, **__kw): + # Turn the arguments into hashable values + if __args == (): + args = None + else: + args = __args + + if __kw == {}: + kw = None + else: + kw = __kw + + # See if there is a cached return value for this call + if self.cache_values.has_key((args, kw)): + # Check the timestamp on the value to ensure it's still valid + if time.time() - self.cache_values[(args, kw)][0] < CACHE_TTL: + return self.cache_values[(args, kw)][1] + + # No return value in cache + ret = self.func(*__args, **__kw) + self.cache_values[(args, kw)] = [None, None] + self.cache_values[(args, kw)][1] = ret + self.cache_values[(args, kw)][0] = time.time() + return ret + class CoreProxy: def __init__(self): self._uri = None @@ -252,7 +292,8 @@ def get_torrent_status(torrent_id, keys): return {} return pickle.loads(status.data) - + +@cache def get_session_state(): try: state = get_core().get_session_state() @@ -260,7 +301,8 @@ def get_session_state(): set_core_uri(None) state = [] return state - + +@cache def get_config(): try: config = get_core().get_config() @@ -268,7 +310,8 @@ def get_config(): set_core_uri(None) config = {} return config - + +@cache def get_config_value(key): try: config_value = get_core().get_config_value(key) @@ -284,7 +327,7 @@ def set_config(config): get_core().set_config(config) except (AttributeError, socket.error): set_core_uri(None) - +@cache def get_listen_port(): try: port = get_core().get_listen_port() @@ -293,6 +336,7 @@ def get_listen_port(): port = 0 return int(port) +@cache def get_available_plugins(): try: available = get_core().get_available_plugins() @@ -300,7 +344,8 @@ def get_available_plugins(): set_core_uri(None) available = [] return available - + +@cache def get_enabled_plugins(): try: enabled = get_core().get_enabled_plugins() @@ -308,7 +353,8 @@ def get_enabled_plugins(): set_core_uri(None) enabled = [] return enabled - + +@cache def get_download_rate(): try: rate = get_core().get_download_rate() @@ -316,7 +362,8 @@ def get_download_rate(): set_core_uri(None) rate = -1 return rate - + +@cache def get_upload_rate(): try: rate = get_core().get_upload_rate() @@ -325,6 +372,7 @@ def get_upload_rate(): rate = -1 return rate +@cache def get_num_connections(): try: num_connections = get_core().get_num_connections() From 68642443a529dd925b3e4d1367e7cc4dff359068 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Dec 2007 11:14:22 +0000 Subject: [PATCH 0298/1009] Make CoreProxy a GObject. --- deluge/ui/client.py | 47 +++++++++++++++------------------------- deluge/ui/gtkui/gtkui.py | 11 ++++++++-- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 7a52c57ea..8495a0575 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -36,6 +36,8 @@ import pickle import socket import time +import gobject + import deluge.xmlrpclib as xmlrpclib import deluge.common @@ -50,11 +52,6 @@ class cache: #self.cache_values = {(args, kwargs): (time, ret)} self.cache_values = {} - self.args = None - self.kwargs = None - self.ret = None - self.time = None - def __call__(self, *__args, **__kw): # Turn the arguments into hashable values if __args == (): @@ -75,41 +72,34 @@ class cache: # No return value in cache ret = self.func(*__args, **__kw) - self.cache_values[(args, kw)] = [None, None] - self.cache_values[(args, kw)][1] = ret - self.cache_values[(args, kw)][0] = time.time() + self.cache_values[(args, kw)] = [time.time(), ret] return ret -class CoreProxy: +class CoreProxy(gobject.GObject): + __gsignals__ = { + "new_core" : ( + gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), + "no_core" : ( + gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), + } def __init__(self): + log.debug("CoreProxy init..") + gobject.GObject.__init__(self) self._uri = None self._core = None - self._on_new_core_callbacks = [] - self._on_no_core_callbacks = [] - - def connect_on_new_core(self, callback): - """Connect a callback to be called when a new core is connected to.""" - self._on_new_core_callbacks.append(callback) - - def connect_on_no_core(self, callback): - """Connect a callback to be called when the current core is disconnected - from.""" - self._on_no_core_callbacks.append(callback) def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) if uri == None and self._uri != None: - for callback in self._on_no_core_callbacks: - callback() + self.emit("no_core") self._uri = None self._core = None return if uri != self._uri and self._uri != None: self._core = None - for callback in self._on_no_core_callbacks: - callback() + self.emit("no_core") self._uri = uri # Get a new core @@ -124,8 +114,7 @@ class CoreProxy: log.debug("Creating ServerProxy..") self._core = xmlrpclib.ServerProxy(self._uri) # Call any callbacks registered - for callback in self._on_new_core_callbacks: - callback() + self.emit("new_core") return self._core @@ -146,12 +135,12 @@ def get_core_plugin(plugin): def connect_on_new_core(callback): """Connect a callback whenever a new core is connected to.""" - return _core.connect_on_new_core(callback) + return _core.connect("new_core", callback) def connect_on_no_core(callback): """Connect a callback whenever the core is disconnected from.""" - return _core.connect_on_no_core(callback) - + return _core.connect("no_core", callback) + def set_core_uri(uri): """Sets the core uri""" return _core.set_core_uri(uri) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 48ccd6ffa..b90e27880 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -117,8 +117,8 @@ class GtkUI: self.dbusinterface = DbusInterface(args) # We make sure that the UI components start once we get a core URI - client.connect_on_new_core(component.start) - client.connect_on_no_core(component.stop) + client.connect_on_new_core(self._on_new_core) + client.connect_on_no_core(self._on_no_core) # Initialize various components of the gtkui self.mainwindow = MainWindow() @@ -165,3 +165,10 @@ class GtkUI: del self.signal_receiver del self.plugins del deluge.configmanager + + def _on_new_core(self, data): + component.start() + + def _on_no_core(self, data): + component.stop() + From 4d6e4b1a06753ddfb8b90fe99cb59af55484cf98 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 17 Dec 2007 23:55:42 +0000 Subject: [PATCH 0299/1009] Create a specialized decorator for caching get_torrent_status(). --- deluge/common.py | 13 +++++++++- deluge/ui/client.py | 59 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 8ea1438fd..d3e1827e6 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -107,7 +107,18 @@ def get_logo(size): def open_file(path): """Opens a file or folder.""" os.popen("xdg-open %s" % path) - + +def open_url_in_browser(url): + """Opens link in the desktop's default browser""" + def start_browser(): + import webbrowser + log.debug("Opening webbrowser with url: %s", url) + webbrowser.open(url) + return False + + import gobject + gobject.idle_add(start_browser) + ## Formatting text functions def fsize(fsize_b): diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 8495a0575..9520cf3e1 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -74,7 +74,52 @@ class cache: ret = self.func(*__args, **__kw) self.cache_values[(args, kw)] = [time.time(), ret] return ret - + +class cache_dict: + """Special cache decorator for get_torrent_status and the like.. It expects + passing a str, list and returns a dict""" + def __init__(self, func): + self.func = func + self.cache_values = {} + + def __call__(self, *__args, **__kw): + if __args == (): + return + # Check for a cache value for these parameters + if self.cache_values.has_key(__args[0]): + # Check the timestamp on the value to ensure it's still valid + if time.time() - self.cache_values[__args[0]][0] < CACHE_TTL: + # Check to see if we have the right keys in cache + cache_dict = self.cache_values[__args[0]][1] + if cache_dict == None: + cache_dict = {} + keys = __args[1] + non_cached = [] + ret_dict = {} + for key in keys: + if key in cache_dict.keys(): + ret_dict[key] = cache_dict[key] + else: + non_cached.append(key) + + # If there aren't any non_cached keys then lets just return + # cached values + if len(non_cached) == 0: + return ret_dict + + # We need to request the remaining non-cached keys from the func + ret = self.func(*(__args[0], non_cached), **__kw) + if ret == None: + return ret + ret.update(ret_dict) + self.cache_values[__args[0]] = [time.time(), ret] + return ret + + # Not cached + ret = self.func(*__args, **__kw) + self.cache_values[__args[0]] = [time.time(), ret] + return ret + class CoreProxy(gobject.GObject): __gsignals__ = { "new_core" : ( @@ -269,6 +314,7 @@ def force_reannounce(torrent_ids): except (AttributeError, socket.error): set_core_uri(None) +@cache_dict def get_torrent_status(torrent_id, keys): """Builds the status dictionary and returns it""" try: @@ -396,14 +442,3 @@ def set_torrent_trackers(torrent_id, trackers): get_core().set_torrent_trackers(torrent_id, trackers) except (AttributeError, socket.error): set_core_uri(None) - -def open_url_in_browser(url): - """Opens link in the desktop's default browser""" - def start_browser(): - import webbrowser - log.debug("Opening webbrowser with url: %s", url) - webbrowser.open(url) - return False - - import gobject - gobject.idle_add(start_browser) From 473110b7feb230d3c258582e68c6d06e04665244 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Dec 2007 00:03:34 +0000 Subject: [PATCH 0300/1009] Fix up calls to open_url_in_browser() because it's been moved to deluge.common. --- deluge/common.py | 1 - deluge/ui/gtkui/menubar.py | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index d3e1827e6..47f53c371 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -112,7 +112,6 @@ def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): import webbrowser - log.debug("Opening webbrowser with url: %s", url) webbrowser.open(url) return False diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index c2bc775eb..07b62ed99 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -38,6 +38,7 @@ import pkg_resources import deluge.component as component import deluge.ui.client as client +import deluge.common as common from deluge.log import LOG as log @@ -240,15 +241,15 @@ class MenuBar(component.Component): ## Help Menu ## def on_menuitem_homepage_activate(self, data=None): log.debug("on_menuitem_homepage_activate") - client.open_url_in_browser("http://deluge-torrent.org") + common.open_url_in_browser("http://deluge-torrent.org") def on_menuitem_faq_activate(self, data=None): log.debug("on_menuitem_faq_activate") - client.open_url_in_browser("http://deluge-torrent.org/faq.php") + common.open_url_in_browser("http://deluge-torrent.org/faq.php") def on_menuitem_community_activate(self, data=None): log.debug("on_menuitem_community_activate") - client.open_url_in_browser("http://forum.deluge-torrent.org/") + common.open_url_in_browser("http://forum.deluge-torrent.org/") def on_menuitem_about_activate(self, data=None): log.debug("on_menuitem_about_activate") From 58a240a99834782c71f9d7a83e0c3a510a86fd0c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 22 Dec 2007 20:35:43 +0000 Subject: [PATCH 0301/1009] Added tray tooltip. Increased client cache time to 1.5 seconds. --- TODO | 3 +-- deluge/ui/client.py | 4 ++-- deluge/ui/gtkui/systemtray.py | 18 ++++++++++++++++++ setup.py | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index c55352416..c6faf383f 100644 --- a/TODO +++ b/TODO @@ -3,14 +3,12 @@ * Figure out easy way for user-made plugins to add i18n support. * Restart daemon function * Docstrings! -* Implement caching in client.py * Create a new add torrent dialog * Implement open folder * Maybe add pop-up menus to the status bar items * Address issue where torrents will redownload if the storage is moved outside of deluge. * Implement 'Classic' mode -* Tray tooltip * Add autoload folder * Add wizard * Add a health indication to the statusbar @@ -21,3 +19,4 @@ * Add command line option to change config dir.. --config * Add method for plugins to add labels * Add context menus for labels.. ie. setting options for all torrents in label +* Create asynchronous batch torrent status method diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 9520cf3e1..bbbf4d272 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -43,7 +43,7 @@ import deluge.xmlrpclib as xmlrpclib import deluge.common from deluge.log import LOG as log -CACHE_TTL = 0.5 # seconds +CACHE_TTL = 1.5 # seconds class cache: def __init__(self, func): @@ -119,7 +119,7 @@ class cache_dict: ret = self.func(*__args, **__kw) self.cache_values[__args[0]] = [time.time(), ret] return ret - + class CoreProxy(gobject.GObject): __gsignals__ = { "new_core" : ( diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index e93e0a91c..3d313af2e 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -114,7 +114,25 @@ class SystemTray(component.Component): self.tray_glade.get_widget(widget).hide() except Exception, e: log.debug("Unable to hide system tray menu widgets: %s", e) + + def update(self): + # Set the tool tip text + max_download_speed = client.get_config_value("max_download_speed") + max_upload_speed = client.get_config_value("max_upload_speed") + + if max_download_speed == -1: + max_download_speed = _("Unlimited") + if max_upload_speed == -1: + max_upload_speed = _("Unlimited") + msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (\ + _("Deluge Bittorrent Client"), _("Down Speed"), \ + client.get_download_rate(), max_download_speed, _("Up Speed"), \ + client.get_upload_rate(), max_upload_speed) + + # Set the tooltip + self.tray.set_tooltip(msg) + def build_tray_bwsetsubmenu(self): # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( diff --git a/setup.py b/setup.py index 51a3a913c..c9a29caa4 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ _extra_compile_args = [ "-O2" ] -removals = ["-Wstrict-prototypes"] +removals = ["-g", "-p", "-Wstrict-prototypes"] if python_version == '2.5': cv_opt = sysconfig.get_config_vars()["CFLAGS"] From 91c55522b8219c391b5262da9a4be30e5defffd5 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 27 Dec 2007 10:54:05 +0000 Subject: [PATCH 0302/1009] Optimize torrent.get_status() a bit. Emit signals with gobject.idle_add(). --- deluge/core/signalmanager.py | 13 +++-- deluge/core/torrent.py | 94 ++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 40 deletions(-) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index ed13be23f..5ba3a0aa1 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -33,6 +33,8 @@ import xmlrpclib +import gobject + import deluge.component as component from deluge.log import LOG as log @@ -60,8 +62,11 @@ class SignalManager(component.Component): def emit(self, signal, data): for client in self.clients.values(): - try: - client.emit_signal(signal, data) - except: - log.warning("Unable to emit signal to client %s", client) + gobject.idle_add(self._emit, client, signal, data) + def _emit(self, client, signal, data): + try: + client.emit_signal(signal, data) + except: + log.warning("Unable to emit signal to client %s", client) + diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 92728ce5d..63eecbbb5 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -65,7 +65,11 @@ class Torrent: self.trackers.append(tracker) else: self.trackers = trackers - + + # Holds status info so that we don't need to keep getting it from lt + self.status = None + self.torrent_info = None + def set_tracker_status(self, status): """Sets the tracker status""" self.tracker_status = status @@ -76,26 +80,35 @@ class Torrent: return (self.torrent_id, self.filename, self.compact, status.paused, self.save_path, self.total_uploaded + status.total_payload_upload, self.trackers) - + def get_eta(self): """Returns the ETA in seconds for this torrent""" - left = self.handle.status().total_wanted \ - - self.handle.status().total_done + if self.status == None: + status = self.handle.status() + else: + status = self.status - if left == 0 or self.handle.status().download_payload_rate == 0: + left = status.total_wanted - status.total_done + + if left == 0 or status.download_payload_rate == 0: return 0 try: - eta = left / self.handle.status().download_payload_rate + eta = left / status.download_payload_rate except ZeroDivisionError: eta = 0 return eta - + def get_ratio(self): """Returns the ratio for this torrent""" - up = self.total_uploaded + self.handle.status().total_payload_upload - down = self.handle.status().total_done + if self.status == None: + status = self.handle.status() + else: + status = self.status + + up = self.total_uploaded + status.total_payload_upload + down = status.total_done # Convert 'up' and 'down' to floats for proper calculation up = float(up) @@ -110,8 +123,13 @@ class Torrent: def get_files(self): """Returns a list of files this torrent contains""" + if self.torrent_info == None: + torrent_info = self.handle.torrent_info() + else: + torrent_info = self.torrent_info + ret = [] - files = self.handle.torrent_info().files() + files = torrent_info.files() for file in files: ret.append({ 'path': file.path, @@ -119,58 +137,62 @@ class Torrent: 'offset': file.offset }) return ret - + + @tit def get_status(self, keys): """Returns the status of the torrent based on the keys provided""" # Create the full dictionary - status = self.handle.status() + self.status = self.handle.status() + self.torrent_info = self.handle.torrent_info() # Adjust progress to be 0-100 value - progress = status.progress*100 + progress = self.status.progress * 100 # Set the state to 'Paused' if the torrent is paused. - state = status.state - if status.paused: + state = self.status.state + if self.status.paused: state = deluge.common.TORRENT_STATE.index("Paused") # Adjust status.distributed_copies to return a non-negative value - distributed_copies = status.distributed_copies + distributed_copies = self.status.distributed_copies if distributed_copies < 0: distributed_copies = 0.0 full_status = { - "name": self.handle.torrent_info().name(), - "total_size": self.handle.torrent_info().total_size(), - "num_files": self.handle.torrent_info().num_files(), - "num_pieces": self.handle.torrent_info().num_pieces(), - "piece_length": self.handle.torrent_info().piece_length(), + "name": self.torrent_info.name(), + "total_size": self.torrent_info.total_size(), + "num_files": self.torrent_info.num_files(), + "num_pieces": self.torrent_info.num_pieces(), + "piece_length": self.torrent_info.piece_length(), "distributed_copies": distributed_copies, - "total_done": status.total_done, - "total_uploaded": self.total_uploaded + status.total_payload_upload, + "total_done": self.status.total_done, + "total_uploaded": self.total_uploaded + self.status.total_payload_upload, "state": int(state), - "paused": status.paused, + "paused": self.status.paused, "progress": progress, - "next_announce": status.next_announce.seconds, - "total_payload_download": status.total_payload_download, - "total_payload_upload": status.total_payload_upload, - "download_payload_rate": status.download_payload_rate, - "upload_payload_rate": status.upload_payload_rate, - "num_peers": status.num_peers - status.num_seeds, - "num_seeds": status.num_seeds, - "total_peers": status.num_incomplete, - "total_seeds": status.num_complete, - "total_wanted": status.total_wanted, + "next_announce": self.status.next_announce.seconds, + "total_payload_download": self.status.total_payload_download, + "total_payload_upload": self.status.total_payload_upload, + "download_payload_rate": self.status.download_payload_rate, + "upload_payload_rate": self.status.upload_payload_rate, + "num_peers": self.status.num_peers - self.status.num_seeds, + "num_seeds": self.status.num_seeds, + "total_peers": self.status.num_incomplete, + "total_seeds": self.status.num_complete, + "total_wanted": self.status.total_wanted, "eta": self.get_eta(), "ratio": self.get_ratio(), - "tracker": status.current_tracker, + "tracker": self.status.current_tracker, "trackers": self.trackers, "tracker_status": self.tracker_status, "save_path": self.save_path, "files": self.get_files() } + self.status = None + self.torrent_info = None # Create the desired status dictionary and return it - status_dict = {} + status_dict = {}.fromkeys(keys) if len(keys) == 0: status_dict = full_status From e2e3073f23e493246f5bb92d33c672fbd0d7fb77 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 27 Dec 2007 10:56:18 +0000 Subject: [PATCH 0303/1009] Fix last. --- deluge/core/torrent.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 63eecbbb5..8d1505122 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -137,8 +137,7 @@ class Torrent: 'offset': file.offset }) return ret - - @tit + def get_status(self, keys): """Returns the status of the torrent based on the keys provided""" # Create the full dictionary From c8508c8d150176f35be2354ea4e61956f50eee7c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Dec 2007 05:29:54 +0000 Subject: [PATCH 0304/1009] Use an asynchronous batch torrent status method for updating the torrentview list. Other various minor stuff. --- TODO | 4 +- deluge/core/core.py | 39 ++++++++++-- deluge/core/signalmanager.py | 5 +- deluge/ui/client.py | 13 +++- deluge/ui/gtkui/signals.py | 7 +- deluge/ui/gtkui/torrentview.py | 113 ++++++++++++++++++++------------- deluge/ui/signalreceiver.py | 2 - setup.py | 2 +- 8 files changed, 126 insertions(+), 59 deletions(-) diff --git a/TODO b/TODO index c6faf383f..289f8dec3 100644 --- a/TODO +++ b/TODO @@ -19,4 +19,6 @@ * Add command line option to change config dir.. --config * Add method for plugins to add labels * Add context menus for labels.. ie. setting options for all torrents in label -* Create asynchronous batch torrent status method +* Implement caching in core +* Use the batch torrent status info as a cache for other torrent status requests +* Don't save fastresume files on exit for finished or paused torrents diff --git a/deluge/core/core.py b/deluge/core/core.py index 9cfbc24f9..22653a128 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -35,7 +35,7 @@ import gettext import locale import pkg_resources import sys -import pickle +import cPickle as pickle import shutil import os @@ -54,7 +54,7 @@ from deluge.core.pluginmanager import PluginManager from deluge.core.alertmanager import AlertManager from deluge.core.signalmanager import SignalManager from deluge.log import LOG as log - + DEFAULT_PREFS = { "config_location": deluge.common.get_config_dir(), "daemon_port": 58846, @@ -332,10 +332,8 @@ class Core( log.debug("Resuming torrent %s", torrent_id) if self.torrents.resume(torrent_id): self.torrent_resumed(torrent_id) - + def export_get_torrent_status(self, torrent_id, keys): - # Convert the array of strings to a python list of strings - keys = deluge.common.pythonize(keys) # Build the status dictionary try: status = self.torrents[torrent_id].get_status(keys) @@ -347,8 +345,33 @@ class Core( leftover_fields = list(set(keys) - set(status.keys())) if len(leftover_fields) > 0: status.update(self.plugins.get_status(torrent_id, leftover_fields)) - return xmlrpclib.Binary(pickle.dumps(status)) + return pickle.dumps(status) + def export_get_torrents_status(self, torrent_ids, keys): + """Returns dictionary of statuses for torrent_ids""" + # This is an async command, so we want to return right away + gobject.idle_add(self._get_torrents_status, torrent_ids, keys) + + def _get_torrents_status(self, torrent_ids, keys): + status_dict = {}.fromkeys(torrent_ids) + + # Get the torrent status for each torrent_id + for torrent_id in torrent_ids: + try: + status = self.torrents[torrent_id].get_status(keys) + except KeyError: + return None + # Get the leftover fields and ask the plugin manager to fill them + leftover_fields = list(set(keys) - set(status.keys())) + if len(leftover_fields) > 0: + status.update( + self.plugins.get_status(torrent_id, leftover_fields)) + + status_dict[torrent_id] = status + # Emit the torrent_status signal to the clients + self.torrent_status(pickle.dumps(status_dict)) + return False + def export_get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager @@ -449,6 +472,10 @@ class Core( """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") self.signals.emit("torrent_all_resumed", torrent_id) + + def torrent_status(self, status): + """Emitted when the torrent statuses are ready to be sent""" + self.signals.emit("torrent_status", status) # Config set functions def _on_set_torrentfiles_location(self, key, value): diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index 5ba3a0aa1..d47e01fad 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -32,6 +32,7 @@ # statement from all source files in the program, then also delete it here. import xmlrpclib +import socket import gobject @@ -67,6 +68,6 @@ class SignalManager(component.Component): def _emit(self, client, signal, data): try: client.emit_signal(signal, data) - except: - log.warning("Unable to emit signal to client %s", client) + except (socket.error, Exception), e: + log.warning("Unable to emit signal to client %s: %s", client, e) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index bbbf4d272..48c076fa8 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -326,8 +326,19 @@ def get_torrent_status(torrent_id, keys): if status == None: return {} - return pickle.loads(status.data) + return pickle.loads(status) +def get_torrents_status(torrent_ids, keys): + """Builds a dictionary of torrent_ids status. Expects 2 lists. This is + asynchronous so the return value will be sent as the signal + 'torrent_status'""" + try: + get_core().get_torrents_status(torrent_ids, keys) + except (AttributeError, socket.error): + set_core_uri(None) + + return None + @cache def get_session_state(): try: diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index aaed607a3..33022d3c4 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -56,7 +56,9 @@ class Signals(component.Component): self.receiver.connect_to_signal("torrent_all_paused", self.torrent_all_paused) self.receiver.connect_to_signal("torrent_all_resumed", - self.torrent_all_resumed) + self.torrent_all_resumed) + self.receiver.connect_to_signal("torrent_status", + self.torrent_status) def stop(self): self.receiver.shutdown() @@ -94,3 +96,6 @@ class Signals(component.Component): log.debug("torrent_all_resumed signal received..") component.get("TorrentView").update() component.get("ToolBar").update_buttons() + + def torrent_status(self, status): + component.get("TorrentView").on_torrent_status_signal(status) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 77360b09a..81b48cfcb 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -38,6 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext import gobject +import cPickle as pickle import deluge.common import deluge.component as component @@ -119,6 +120,8 @@ class TorrentView(listview.ListView, component.Component): # Try to load the state file if available self.load_state("torrentview.state") + self.status_signal_received = True + # Register the columns menu with the listview so it gets updated # accordingly. self.register_checklist_menu( @@ -230,27 +233,18 @@ class TorrentView(listview.ListView, component.Component): model.set_value(row, filter_column, True) else: model.set_value(row, filter_column, False) - + self.liststore.foreach(foreachrow, self.filter) - if self.liststore != None: - self.liststore.foreach(self.update_row, columns) - - def update_row(self, model=None, path=None, row=None, columns=None): - """Updates the column values for 'row'. If columns is None it will - update all visible columns.""" - # Check to see if this row is visible and return if not - if not model.get_value(row, self.columns["filter"].column_indices[0]): + # We will only send another status request if we have received the + # previous. This is to prevent things from going out of sync. + if not self.status_signal_received: return - - # Get the torrent_id from the liststore - torrent_id = model.get_value(row, - self.columns["torrent_id"].column_indices[0]) - + # Store the 'status_fields' we need to send to core status_keys = [] # Store the actual columns we will be updating - columns_to_update = [] + self.columns_to_update = [] if columns is None: # We need to iterate through all columns @@ -265,10 +259,10 @@ class TorrentView(listview.ListView, component.Component): and self.columns[column].status_field is not None: for field in self.columns[column].status_field: status_keys.append(field) - columns_to_update.append(column) + self.columns_to_update.append(column) # Remove duplicate keys - columns_to_update = list(set(columns_to_update)) + self.columns_to_update = list(set(self.columns_to_update)) # If there is nothing in status_keys then we must not continue if status_keys is []: @@ -276,32 +270,63 @@ class TorrentView(listview.ListView, component.Component): # Remove duplicates from status_key list status_keys = list(set(status_keys)) - status = client.get_torrent_status(torrent_id, - status_keys) - - # Set values for each column in the row - for column in columns_to_update: - column_index = self.get_column_index(column) - if type(column_index) is not list: - # We only have a single list store column we need to update - try: - model.set_value(row, - column_index, - status[self.columns[column].status_field[0]]) - except (TypeError, KeyError), e: - log.warning("Unable to update column %s: %s", - column, e) - else: - # We have more than 1 liststore column to update - for index in column_index: - # Only update the column if the status field exists - try: - model.set_value(row, - index, - status[self.columns[column].status_field[ - column_index.index(index)]]) - except: - pass + + # Create list of torrent_ids in need of status updates + torrent_ids = [] + row = self.liststore.get_iter_first() + while row != None: + # Only add this torrent_id if it's not filtered + if self.liststore.get_value( + row, self.columns["filter"].column_indices[0]) == True: + torrent_ids.append(self.liststore.get_value( + row, self.columns["torrent_id"].column_indices[0])) + row = self.liststore.iter_next(row) + + if torrent_ids == []: + return + + # Request the statuses for all these torrent_ids, this is async so we + # will deal with the return in a signal callback. + self.status_signal_received = False + client.get_torrents_status(torrent_ids, status_keys) + + def on_torrent_status_signal(self, status): + """Callback function for get_torrents_status(). 'status' should be a + dictionary of {torrent_id: {key, value}}.""" + status = pickle.loads(status) + row = self.liststore.get_iter_first() + while row != None: + torrent_id = self.liststore.get_value( + row, self.columns["torrent_id"].column_indices[0]) + if torrent_id in status.keys(): + # Set values for each column in the row + for column in self.columns_to_update: + column_index = self.get_column_index(column) + if type(column_index) is not list: + # We only have a single list store column we need to + # update + try: + self.liststore.set_value(row, + column_index, + status[torrent_id][ + self.columns[column].status_field[0]]) + except (TypeError, KeyError), e: + log.warning("Unable to update column %s: %s", + column, e) + else: + # We have more than 1 liststore column to update + for index in column_index: + # Only update the column if the status field exists + try: + self.liststore.set_value(row, + index, + status[torrent_id][ + self.columns[column].status_field[ + column_index.index(index)]]) + except: + pass + row = self.liststore.iter_next(row) + self.status_signal_received = True def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" @@ -312,8 +337,6 @@ class TorrentView(listview.ListView, component.Component): row, self.columns["torrent_id"].column_indices[0], torrent_id) - # Update the new row so - self.update_row(model=self.liststore, row=row) def remove_row(self, torrent_id): """Removes a row with torrent_id""" diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index faeacbcc2..60689d474 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -79,7 +79,6 @@ class SignalReceiver( self.register_function(self.emit_signal) # Register the signal receiver with the core - # FIXME: send actual URI not localhost core = client.get_core() core.register_client(str(port)) @@ -115,7 +114,6 @@ class SignalReceiver( def emit_signal(self, signal, data): """Exported method used by the core to emit a signal to the client""" - log.debug("Received signal %s with data %s from core..", signal, data) try: if data != None: for callback in self.signals[signal]: diff --git a/setup.py b/setup.py index c9a29caa4..51a3a913c 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ _extra_compile_args = [ "-O2" ] -removals = ["-g", "-p", "-Wstrict-prototypes"] +removals = ["-Wstrict-prototypes"] if python_version == '2.5': cv_opt = sysconfig.get_config_vars()["CFLAGS"] From 44c82db9a85dac04ecf74e2de2026091dbdb8566 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Dec 2007 08:22:59 +0000 Subject: [PATCH 0305/1009] Fix display of speeds in the systemtray tooltip. --- deluge/ui/gtkui/systemtray.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 3d313af2e..a5c915579 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -127,8 +127,9 @@ class SystemTray(component.Component): msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (\ _("Deluge Bittorrent Client"), _("Down Speed"), \ - client.get_download_rate(), max_download_speed, _("Up Speed"), \ - client.get_upload_rate(), max_upload_speed) + deluge.common.fspeed(client.get_download_rate()), + max_download_speed, _("Up Speed"), \ + deluge.common.fspeed(client.get_upload_rate()), max_upload_speed) # Set the tooltip self.tray.set_tooltip(msg) From dd395b8c9c009c161962e18314e367c87072f6e0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Dec 2007 08:48:33 +0000 Subject: [PATCH 0306/1009] Attempt to reduce the number of updates to the TorrentView by caching the previous status batch dictionary and only updating the difference. --- deluge/ui/gtkui/torrentview.py | 61 ++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 81b48cfcb..3743afbe2 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -121,6 +121,7 @@ class TorrentView(listview.ListView, component.Component): self.load_state("torrentview.state") self.status_signal_received = True + self.previous_batched_status = {} # Register the columns menu with the listview so it gets updated # accordingly. @@ -294,37 +295,63 @@ class TorrentView(listview.ListView, component.Component): """Callback function for get_torrents_status(). 'status' should be a dictionary of {torrent_id: {key, value}}.""" status = pickle.loads(status) + # Extract differences in this batch against the previous one. + # This is to prevent updating stuff we don't need to and should save + # GTK from redrawing needlessly. + new_status = {} + for torrent_id in status.keys(): + if torrent_id in self.previous_batched_status.keys(): + old = self.previous_batched_status[torrent_id] + new = status[torrent_id] + diff = {} + for key in new.keys(): + # There is a difference, so lets add it to our new dict + if new[key] != old[key]: + diff[key] = new[key] + if len(diff.keys()) > 0: + new_status[torrent_id] = diff + else: + # The torrent_id is not in the previous status + new_status[torrent_id] = status[torrent_id] + + self.previous_batched_status = status + row = self.liststore.get_iter_first() while row != None: torrent_id = self.liststore.get_value( row, self.columns["torrent_id"].column_indices[0]) - if torrent_id in status.keys(): + if torrent_id in new_status.keys(): # Set values for each column in the row for column in self.columns_to_update: column_index = self.get_column_index(column) if type(column_index) is not list: # We only have a single list store column we need to # update - try: - self.liststore.set_value(row, - column_index, - status[torrent_id][ - self.columns[column].status_field[0]]) - except (TypeError, KeyError), e: - log.warning("Unable to update column %s: %s", - column, e) + if self.columns[column].status_field[0] in \ + new_status[torrent_id]: + try: + self.liststore.set_value(row, + column_index, + new_status[torrent_id][ + self.columns[column].status_field[0]]) + except (TypeError, KeyError), e: + log.warning("Unable to update column %s: %s", + column, e) else: # We have more than 1 liststore column to update for index in column_index: # Only update the column if the status field exists - try: - self.liststore.set_value(row, - index, - status[torrent_id][ - self.columns[column].status_field[ - column_index.index(index)]]) - except: - pass + if self.columns[column].status_field[ + column_index.index(index)] in \ + new_status[torrent_id]: + try: + self.liststore.set_value(row, + index, + new_status[torrent_id][ + self.columns[column].status_field[ + column_index.index(index)]]) + except: + pass row = self.liststore.iter_next(row) self.status_signal_received = True From 5c38bfdbe7e02b9c557e18e94992e29323e86441 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Dec 2007 13:19:50 +0000 Subject: [PATCH 0307/1009] Fix batch status caching for when a key doesn't exist in the cache. --- deluge/ui/gtkui/torrentview.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 3743afbe2..42aec4490 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -305,6 +305,8 @@ class TorrentView(listview.ListView, component.Component): new = status[torrent_id] diff = {} for key in new.keys(): + if not key in old.keys(): + continue # There is a difference, so lets add it to our new dict if new[key] != old[key]: diff[key] = new[key] From e069542e3af1ebf89e27a31614cba2e5e412f8ea Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Dec 2007 13:20:34 +0000 Subject: [PATCH 0308/1009] Fix allocation modes. They were reversed. --- deluge/core/torrentmanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 26fe9aed7..211c30f40 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -171,9 +171,9 @@ class TorrentManager(component.Component): # Set the right storage_mode if compact: - storage_mode = lt.storage_mode_t(1) - else: storage_mode = lt.storage_mode_t(2) + else: + storage_mode = lt.storage_mode_t(1) try: handle = self.session.add_torrent( From 24471624c26b6eee1721871faf5f768814ad4717 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 28 Dec 2007 13:39:47 +0000 Subject: [PATCH 0309/1009] Fix previous fix for non-present key in cache. --- deluge/ui/gtkui/torrentview.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 42aec4490..bbc6eaa06 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -299,14 +299,18 @@ class TorrentView(listview.ListView, component.Component): # This is to prevent updating stuff we don't need to and should save # GTK from redrawing needlessly. new_status = {} + for torrent_id in status.keys(): if torrent_id in self.previous_batched_status.keys(): old = self.previous_batched_status[torrent_id] new = status[torrent_id] + diff = {} for key in new.keys(): if not key in old.keys(): + diff[key] = new[key] continue + # There is a difference, so lets add it to our new dict if new[key] != old[key]: diff[key] = new[key] @@ -317,7 +321,7 @@ class TorrentView(listview.ListView, component.Component): new_status[torrent_id] = status[torrent_id] self.previous_batched_status = status - + row = self.liststore.get_iter_first() while row != None: torrent_id = self.liststore.get_value( From 5a175587f48db4a184f978f6b2aac1b5e3f06017 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 29 Dec 2007 13:18:31 +0000 Subject: [PATCH 0310/1009] Initial commit of the new Add Torrent dialog. --- .../ui/gtkui/glade/add_torrent_dialog.glade | 831 ++++++++++++++++++ 1 file changed, 831 insertions(+) create mode 100644 deluge/ui/gtkui/glade/add_torrent_dialog.glade diff --git a/deluge/ui/gtkui/glade/add_torrent_dialog.glade b/deluge/ui/gtkui/glade/add_torrent_dialog.glade new file mode 100644 index 000000000..d5968fb5d --- /dev/null +++ b/deluge/ui/gtkui/glade/add_torrent_dialog.glade @@ -0,0 +1,831 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + GTK_WIN_POS_CENTER_ON_PARENT + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + 5 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <big><b>Add Torrents</b></big> + True + + + False + False + 1 + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_CENTER + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-open + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From File + + + False + False + 1 + + + + + + + False + False + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-network + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From URL + + + False + False + 1 + + + + + + + False + False + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-revert-to-saved + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From Hash + + + False + False + 1 + + + + + + + False + False + 2 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remove + + + False + False + 1 + + + + + + + False + False + 3 + + + + + False + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrents</b> + True + + + label_item + + + + + False + False + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Full + 0 + True + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Compact + 0 + True + True + radiobutton1 + + + False + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Allocation</b> + True + + + label_item + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 2 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 0 100 1 10 10 + + + 1 + 2 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Down Speed: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Up Speed: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Connections: + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Upload Slots: + + + 3 + 4 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 0 100 1 10 10 + + + 1 + 2 + 1 + 2 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 0 100 1 10 10 + + + 1 + 2 + 2 + 3 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 0 100 1 10 10 + + + 1 + 2 + 3 + 4 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Bandwidth</b> + True + + + label_item + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Add In Paused State + 0 + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Prioritize First/Last Pieces + 0 + True + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_START + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-edit + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Edit Trackers + + + False + False + 1 + + + + + + + False + False + + + + + False + False + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>General</b> + True + + + label_item + + + + + False + False + 2 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Download Location</b> + True + + + label_item + + + + + False + False + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Options</b> + True + + + label_item + + + + + False + False + 3 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Files</b> + True + + + label_item + + + + + 4 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 0 + + + 1 + + + + + False + GTK_PACK_END + + + + + + From aa932b56a74417335dd50d065a04884b20dad857 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 30 Dec 2007 04:00:53 +0000 Subject: [PATCH 0311/1009] Fix crash when trying to add a bad .torrent. --- deluge/core/torrentmanager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 211c30f40..a47c2dcf7 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -141,7 +141,11 @@ class TorrentManager(component.Component): # joined. if type(filedump) is not str: filedump = "".join(chr(b) for b in filedump) - filedump = lt.bdecode(filedump) + try: + filedump = lt.bdecode(filedump) + except RuntimeError, e: + log.warn("Unable to decode torrent file: %s", e) + return None else: # Get the data from the file filedump = self.load_torrent(filename) From 74d382dc86f06ced26776e442995a8b54b8fa8e1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 30 Dec 2007 04:01:33 +0000 Subject: [PATCH 0312/1009] Tweak the add torrent glade file. --- .../ui/gtkui/glade/add_torrent_dialog.glade | 155 ++++++++++-------- 1 file changed, 91 insertions(+), 64 deletions(-) diff --git a/deluge/ui/gtkui/glade/add_torrent_dialog.glade b/deluge/ui/gtkui/glade/add_torrent_dialog.glade index d5968fb5d..f70d292e9 100644 --- a/deluge/ui/gtkui/glade/add_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/add_torrent_dialog.glade @@ -1,13 +1,7 @@ - + - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 @@ -80,6 +74,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 12 12 @@ -95,7 +90,7 @@ GTK_POLICY_AUTOMATIC GTK_SHADOW_IN - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -114,12 +109,13 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_CENTER - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True @@ -159,12 +155,13 @@ - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True @@ -205,12 +202,13 @@ - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True @@ -251,12 +249,13 @@ - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True @@ -348,13 +347,14 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 12 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -369,7 +369,7 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -377,7 +377,7 @@ 0 True True - radiobutton1 + radio_full False @@ -416,6 +416,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 12 @@ -425,39 +426,64 @@ 2 10 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 0 100 1 10 10 + 1 + -1 -1 9999 1 10 10 1 2 + 3 + 4 - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Down Speed: + 1 + -1 -1 9999 1 10 10 - GTK_FILL + 1 + 2 + 2 + 3 + - + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + GTK_UPDATE_IF_VALID + + + 1 + 2 + 1 + 2 + + + + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 - Max Up Speed: + Max Upload Slots: - 1 - 2 + 3 + 4 GTK_FILL @@ -475,60 +501,40 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 - Max Upload Slots: + Max Up Speed: - 3 - 4 + 1 + 2 GTK_FILL - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 0 100 1 10 10 + 0 + Max Down Speed: - 1 - 2 - 1 - 2 - + GTK_FILL - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 0 100 1 10 10 + 1 + -1 -1 9999 1 10 10 1 2 - 2 - 3 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 0 100 1 10 10 - - - 1 - 2 - 3 - 4 @@ -564,6 +570,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 12 @@ -571,7 +578,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -585,7 +592,7 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -599,18 +606,34 @@ 1 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Set Private Flag + 0 + True + + + False + False + 2 + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_START - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True @@ -651,7 +674,7 @@ False False - 2 + 3 @@ -688,9 +711,10 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER @@ -747,6 +771,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 12 @@ -757,7 +782,7 @@ GTK_POLICY_AUTOMATIC GTK_SHADOW_OUT - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -795,7 +820,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END - + True True True @@ -803,10 +828,11 @@ gtk-cancel True 0 + - + True True True @@ -814,6 +840,7 @@ gtk-add True 0 + 1 From 24fc4f0a49ec9e3342f53479a340aefaa051e3b2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 1 Jan 2008 01:03:15 +0000 Subject: [PATCH 0313/1009] Start of work integrating new Add Torrent dialog. --- deluge/ui/gtkui/addtorrentdialog.py | 213 ++- .../ui/gtkui/glade/add_torrent_dialog.glade | 1463 ++++++++++------- deluge/ui/gtkui/menubar.py | 3 +- 3 files changed, 1078 insertions(+), 601 deletions(-) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index db1de8e54..5b2b184ee 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -36,52 +36,225 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext +import pkg_resources + +import deluge.component as component +import deluge.ui.gtkui.listview as listview from deluge.configmanager import ConfigManager from deluge.log import LOG as log import deluge.common class AddTorrentDialog: def __init__(self, parent=None): + self.glade = gtk.glade.XML( + pkg_resources.resource_filename( + "deluge.ui.gtkui", "glade/add_torrent_dialog.glade")) + + self.dialog = self.glade.get_widget("dialog_add_torrent") + + self.dialog.set_transient_for(component.get("MainWindow").window) + + self.glade.signal_autoconnect({ + "on_button_file_clicked": self._on_button_file_clicked, + "on_button_url_clicked": self._on_button_url_clicked, + "on_button_hash_clicked": self._on_button_hash_clicked, + "on_button_remove_clicked": self._on_button_remove_clicked, + "on_button_trackers_clicked": self._on_button_trackers_clicked, + "on_button_cancel_clicked": self._on_button_cancel_clicked, + "on_button_add_clicked": self._on_button_add_clicked + }) + + + self.torrent_liststore = gtk.ListStore(str, str) + self.files_liststore = gtk.ListStore(bool, str, int) + # Holds the files info + self.files = {} + + self.listview_torrents = self.glade.get_widget("listview_torrents") + self.listview_files = self.glade.get_widget("listview_files") + + render = gtk.CellRendererText() + column = gtk.TreeViewColumn(_("Torrent"), render, text=1) + self.listview_torrents.append_column(column) + + render = gtk.CellRendererToggle() + render.connect("toggled", self._on_file_toggled) + column = gtk.TreeViewColumn(None, render, active=0) + self.listview_files.append_column(column) + + render = gtk.CellRendererText() + column = gtk.TreeViewColumn(_("Filename"), render, text=1) + column.set_expand(True) + self.listview_files.append_column(column) + + render = gtk.CellRendererText() + column = gtk.TreeViewColumn(_("Size")) + column.pack_start(render) + column.set_cell_data_func(render, listview.cell_data_size, 2) + self.listview_files.append_column(column) + + self.listview_torrents.set_model(self.torrent_liststore) + self.listview_files.set_model(self.files_liststore) + + self.listview_torrents.get_selection().connect("changed", + self._on_torrent_changed) + + def show(self): + self.dialog.show_all() + return None + + def hide(self): + self.dialog.destroy() + return None + + def add_to_torrent_list(self, filenames): + import deluge.libtorrent as lt + import os.path + + for filename in filenames: + # Get the torrent data from the torrent file + try: + log.debug("Attempting to open %s for add.", filename) + _file = open(filename, "rb") + filedump = lt.bdecode(_file.read()) + _file.close() + except IOError, e: + log.warning("Unable to open %s: e", filename, e) + continue + + info = lt.torrent_info(filedump) + + # Get list of files from torrent info + files = [] + for f in info.files(): + files.append({ + 'path': f.path, + 'size': f.size, + 'download': True + }) + + name = os.path.split(filename)[-1] + ": " + info.name() + self.torrent_liststore.append([str(info.info_hash()), name]) + self.files[str(info.info_hash())] = files + + + def _on_torrent_changed(self, treeselection): + (model, row) = treeselection.get_selected() + self.files_liststore.clear() + + if row is None: + return + + files_list = self.files[model.get_value(row, 0)] + + for file_dict in files_list: + self.files_liststore.append([ + file_dict["download"], + file_dict["path"], + file_dict["size"] + ]) + + def _on_file_toggled(self, render, path): + row = self.files_liststore.get_iter(path) + self.files_liststore.set_value( + row, 0, not self.files_liststore.get_value(row, 0)) + + def _on_button_file_clicked(self, widget): + log.debug("_on_button_file_clicked") # Setup the filechooserdialog - self.chooser = gtk.FileChooserDialog(_("Choose a .torrent file"), - parent, + chooser = gtk.FileChooserDialog(_("Choose a .torrent file"), + None, gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) - self.chooser.set_icon(deluge.common.get_logo(32)) - self.chooser.set_select_multiple(True) - self.chooser.set_property("skip-taskbar-hint", True) + chooser.set_transient_for(self.dialog) + chooser.set_select_multiple(True) + chooser.set_property("skip-taskbar-hint", True) # Add .torrent and * file filters file_filter = gtk.FileFilter() file_filter.set_name(_("Torrent files")) file_filter.add_pattern("*." + "torrent") - self.chooser.add_filter(file_filter) + chooser.add_filter(file_filter) file_filter = gtk.FileFilter() file_filter.set_name(_("All files")) file_filter.add_pattern("*") - self.chooser.add_filter(file_filter) + chooser.add_filter(file_filter) # Load the 'default_load_path' from the config self.config = ConfigManager("gtkui.conf") if self.config.get("default_load_path") is not None: - self.chooser.set_current_folder( - self.config.get("default_load_path")) + chooser.set_current_folder(self.config.get("default_load_path")) - def run(self): - """Returns a list of selected files or None if no files were selected. - """ # Run the dialog - response = self.chooser.run() + response = chooser.run() if response == gtk.RESPONSE_OK: - result = self.chooser.get_filenames() - self.config.set("default_load_path", - self.chooser.get_current_folder()) + result = chooser.get_filenames() + self.config.set("default_load_path", chooser.get_current_folder()) else: - result = None + chooser.destroy() + return + + chooser.destroy() + self.add_to_torrent_list(result) + + def _on_button_url_clicked(self, widget): + log.debug("_on_button_url_clicked") + dialog = self.glade.get_widget("url_dialog") + entry = self.glade.get_widget("entry_url") + + dialog.set_default_response(gtk.RESPONSE_OK) + dialog.set_transient_for(self.dialog) + + if deluge.common.windows_check(): + import win32clipboard as clip + import win32con + clip.OpenClipboard() + text = clip.GetClipboardData(win32con.CF_UNICODETEXT) + clip.CloseClipboard() + else: + clip = gtk.clipboard_get(selection='PRIMARY') + text = clip.wait_for_text() + if text: + text = text.strip() + if deluge.common.is_url(text): + entry.set_text(text) + + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + url = entry.get_text().decode("utf_8") + else: + url = None + + log.debug("url: %s", url) + dialog.hide() + + + def _on_button_hash_clicked(self, widget): + log.debug("_on_button_hash_clicked") + + def _on_button_remove_clicked(self, widget): + log.debug("_on_button_remove_clicked") + (model, row) = self.listview_torrents.get_selection().get_selected() + if row is None: + return + + torrent_id = model.get_value(row, 0) + + model.remove(row) + del self.files[torrent_id] + + def _on_button_trackers_clicked(self, widget): + log.debug("_on_button_trackers_clicked") + + def _on_button_cancel_clicked(self, widget): + log.debug("_on_button_cancel_clicked") + self.hide() + + def _on_button_add_clicked(self, widget): + log.debug("_on_button_add_clicked") - self.chooser.destroy() - del self.config - return result diff --git a/deluge/ui/gtkui/glade/add_torrent_dialog.glade b/deluge/ui/gtkui/glade/add_torrent_dialog.glade index f70d292e9..bb4764272 100644 --- a/deluge/ui/gtkui/glade/add_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/add_torrent_dialog.glade @@ -1,13 +1,15 @@ - + - + + 560 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 + Add Torrents GTK_WIN_POS_CENTER_ON_PARENT + True GDK_WINDOW_TYPE_HINT_DIALOG - False True @@ -65,38 +67,356 @@ - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - 12 + 0 + GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + 12 - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + 100 + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_CENTER + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-open + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From File + + + False + False + 1 + + + + + + + False + False + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-network + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From URL + + + False + False + 1 + + + + + + + False + False + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-revert-to-saved + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From Hash + + + False + False + 1 + + + + + + + False + False + 2 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remove + + + False + False + 1 + + + + + + + False + False + 3 + + + + + False + False + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrents</b> + True + + + label_item + + + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_OUT + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-open + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Files + + + 5 + 1 + + + + + tab + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Download Location</b> + True + + + label_item + + False @@ -104,50 +424,73 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_CENTER + 10 - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - + 0 + GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 4 + 5 + 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-open - 1 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Full + 0 + True + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Compact + 0 + True + True + radio_full + + + False + False + 1 + + - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - From File - - - False - False - 1 - + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Allocation</b> + True + + + label_item + + False @@ -155,45 +498,160 @@ - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - + 0 + GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 4 + 5 + 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-network - 1 + 4 + 2 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + + + 1 + 2 + 3 + 4 + + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + + + 1 + 2 + 2 + 3 + + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + GTK_UPDATE_IF_VALID + + + 1 + 2 + 1 + 2 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Upload Slots: + + + 3 + 4 + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Connections: + + + 2 + 3 + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Up Speed: + + + 1 + 2 + GTK_FILL + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Max Down Speed: + + + GTK_FILL + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + + + 1 + 2 + + + + - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - From URL - - - False - False - 1 - + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Bandwidth</b> + True + + + label_item + + False @@ -202,45 +660,137 @@ - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - + 0 + GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 4 + 5 + 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-revert-to-saved - 1 + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Add In Paused State + 0 + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Prioritize First/Last Pieces + 0 + True + + + False + False + 1 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Set Private Flag + 0 + True + + + False + False + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_START + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-edit + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Edit Trackers + + + False + False + 5 + 1 + + + + + + + False + False + + + + + False + False + 3 + + - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - From Hash - - - False - False - 1 - + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>General</b> + True + + + label_item + + False @@ -248,41 +798,58 @@ 2 + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - + True + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 + 0 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-remove + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-revert-to-saved + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Revert To Defaults + + + False + False + 5 + 1 + + - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Remove - - - False - False - 1 - @@ -290,406 +857,60 @@ False False - 3 + GTK_PACK_END + 1 - - - False - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrents</b> - True - - - label_item - - - - - False - False - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - + True + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Full - 0 - True - True - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Compact - 0 - True - True - radio_full - - - False - False - 1 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Allocation</b> - True - - - label_item - - - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 - 2 - 10 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - - - 1 - 2 - 3 - 4 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - - - 1 - 2 - 2 - 3 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - GTK_UPDATE_IF_VALID - - - 1 - 2 - 1 - 2 - - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Upload Slots: - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Connections: - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Up Speed: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Max Down Speed: - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - - - 1 - 2 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Bandwidth</b> - True - - - label_item - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Add In Paused State - 0 - True - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Prioritize First/Last Pieces - 0 - True - - - False - False - 1 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Set Private Flag - 0 - True - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_START - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 4 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-edit - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Edit Trackers - - - False - False - 1 - - - - + 1 + gtk-apply False False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Apply To All + + + False + False + 5 + 1 + + - - False - False - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>General</b> - True - - label_item + False + False + GTK_PACK_END @@ -700,113 +921,48 @@ + + 1 + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - Select A Folder - - + gtk-properties - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Download Location</b> - True + Options - label_item + 5 + 1 - False - False + tab 1 + False - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Options</b> - True - - label_item + True + True - False - False - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_OUT - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Files</b> - True - - - label_item - - - - - 4 + 2 @@ -855,4 +1011,151 @@ + + 462 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Add Tracker + GTK_WIN_POS_CENTER_ON_PARENT + True + GDK_WINDOW_TYPE_HINT_DIALOG + False + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>From URL</b> + True + + + False + False + 1 + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + URL: + + + False + False + + + + + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + 1 + + + + + False + False + 2 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + -6 + + + + + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + -5 + + + 1 + + + + + False + GTK_PACK_END + + + + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 07b62ed99..0df52ba71 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -154,7 +154,8 @@ class MenuBar(component.Component): def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") from addtorrentdialog import AddTorrentDialog - client.add_torrent_file(AddTorrentDialog().run()) + #client.add_torrent_file(AddTorrentDialog().run()) + AddTorrentDialog().show() def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") From df9fd2af09f3957f3ff84f3a1f34a93e5c4b48ec Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 2 Jan 2008 11:45:36 +0000 Subject: [PATCH 0314/1009] Fix sorting of name column. --- deluge/ui/gtkui/listview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 40895cdc0..0d78332ff 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -464,7 +464,7 @@ class ListView: return True def add_texticon_column(self, header, col_types=[int, str], - sortid=0, + sortid=1, hidden=False, position=None, status_field=None, From a3658e352b0203e858ea8ba05c4b33a3b7d18d76 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 2 Jan 2008 11:46:08 +0000 Subject: [PATCH 0315/1009] Fix undefined sleep. --- deluge/ui/gtkui/connectionmanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index bad92d6cc..3d7182f74 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -135,7 +135,7 @@ class ConnectionManager(component.Component): os.popen("deluged -p %s" % port) # We need to wait for the host to start before connecting while not self.test_online_status(uri): - sleep(10) + time.sleep(0.01) client.set_core_uri(uri) self.hide() @@ -432,7 +432,7 @@ class ConnectionManager(component.Component): self.start_localhost(port) # We need to wait for the host to start before connecting while not self.test_online_status(uri): - sleep(10) + time.sleep(0.01) client.set_core_uri(uri) self._update() self.hide() From 39e987d5da9c489960b15efa7bd7eb2633eaf70f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 2 Jan 2008 11:54:42 +0000 Subject: [PATCH 0316/1009] Improve get_torrent_status() speed by only building the files dictionary when the Torrent object is created. --- deluge/core/torrent.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 8d1505122..4ac0c7bca 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -65,10 +65,13 @@ class Torrent: self.trackers.append(tracker) else: self.trackers = trackers - + # Holds status info so that we don't need to keep getting it from lt self.status = None self.torrent_info = None + + # Files dictionary + self.files = self.get_files() def set_tracker_status(self, status): """Sets the tracker status""" @@ -185,7 +188,7 @@ class Torrent: "trackers": self.trackers, "tracker_status": self.tracker_status, "save_path": self.save_path, - "files": self.get_files() + "files": self.files } self.status = None self.torrent_info = None From 1efe0f7778454224576f44dd0aa2a2973fe6725c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 3 Jan 2008 02:33:38 +0000 Subject: [PATCH 0317/1009] Improve performance of the cell data functions. --- deluge/ui/gtkui/listview.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 0d78332ff..b93689136 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -46,7 +46,7 @@ from deluge.log import LOG as log # Cell data functions to pass to add_func_column() def cell_data_speed(column, cell, model, row, data): """Display value as a speed, eg. 2 KiB/s""" - speed = int(model.get_value(row, data)) + speed = model.get_value(row, data) speed_str = "" if speed > 0: speed_str = deluge.common.fspeed(speed) @@ -55,15 +55,13 @@ def cell_data_speed(column, cell, model, row, data): def cell_data_size(column, cell, model, row, data): """Display value in terms of size, eg. 2 MB""" - size = long(model.get_value(row, data)) + size = model.get_value(row, data) size_str = deluge.common.fsize(size) cell.set_property('text', size_str) def cell_data_peer(column, cell, model, row, data): """Display values as 'value1 (value2)'""" - column1, column2 = data - first = int(model.get_value(row, column1)) - second = int(model.get_value(row, column2)) + (first, second) = model.get(row, *data) # Only display a (total) if second is greater than -1 if second > -1: cell.set_property('text', '%d (%d)' % (first, second)) @@ -72,8 +70,8 @@ def cell_data_peer(column, cell, model, row, data): def cell_data_time(column, cell, model, row, data): """Display value as time, eg 1m10s""" - time = int(model.get_value(row, data)) - if time < 0 or time == 0: + time = model.get_value(row, data) + if time <= 0: time_str = _("Infinity") else: time_str = deluge.common.ftime(time) @@ -81,12 +79,13 @@ def cell_data_time(column, cell, model, row, data): def cell_data_ratio(column, cell, model, row, data): """Display value as a ratio with a precision of 3.""" - ratio = float(model.get_value(row, data)) + ratio = model.get_value(row, data) if ratio == -1: ratio_str = _("Unknown") else: ratio_str = "%.3f" % ratio - cell.set_property('text', ratio_str) + + cell.set_property('text', ratio_str) class ListViewColumnState: """Used for saving/loading column state""" From 7477a0f1594ce4a62a098fe6af52b11b85d7a1e4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 3 Jan 2008 02:35:37 +0000 Subject: [PATCH 0318/1009] Improve performance of some common functions. --- deluge/common.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 47f53c371..21bd7c94d 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -125,13 +125,13 @@ def fsize(fsize_b): fsize_b should be in bytes Returned value will be in either KB, MB, or GB """ - fsize_kb = float (fsize_b / 1024.0) + fsize_kb = fsize_b / 1024.0 if fsize_kb < 1000: return "%.1f KiB" % fsize_kb - fsize_mb = float (fsize_kb / 1024.0) + fsize_mb = fsize_kb / 1024.0 if fsize_mb < 1000: return "%.1f MiB" % fsize_mb - fsize_gb = float (fsize_mb / 1024.0) + fsize_gb = fsize_mb / 1024.0 return "%.1f GiB" % fsize_gb def fpcnt(dec): @@ -145,29 +145,29 @@ def fspeed(bps): def fpeer(num_peers, total_peers): """Returns a formatted string num_peers (total_peers)""" if total_peers > -1: - return str(str(num_peers) + " (" + str(total_peers) + ")") + return "%d (%d)" % (num_peers, total_peers) else: - return str(num_peers) + return "%d" % num_peers def ftime(seconds): """Returns a formatted time string""" - if seconds is 0: + if seconds == 0: return "Infinity" if seconds < 60: return '%ds' % (seconds) - minutes = int(seconds/60) + minutes = seconds / 60 seconds = seconds % 60 if minutes < 60: return '%dm %ds' % (minutes, seconds) - hours = int(minutes/60) + hours = minutes / 60 minutes = minutes % 60 if hours < 24: return '%dh %dm' % (hours, minutes) - days = int(hours/24) + days = hours / 24 hours = hours % 24 if days < 7: return '%dd %dh' % (days, hours) - weeks = int(days/7) + weeks = days / 7 days = days % 7 if weeks < 10: return '%dw %dd' % (weeks, days) From 34eec4ab93debf46dd02d72705d9dc2c160dd81b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 3 Jan 2008 03:22:08 +0000 Subject: [PATCH 0319/1009] Improve performance of the statusicon and progress cell functions. --- deluge/ui/gtkui/addtorrentdialog.py | 101 ++++++++++++++++-- .../ui/gtkui/glade/add_torrent_dialog.glade | 8 +- deluge/ui/gtkui/torrentview.py | 57 ++++------ 3 files changed, 122 insertions(+), 44 deletions(-) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 5b2b184ee..43be6dafe 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -38,6 +38,7 @@ import gettext import pkg_resources +import deluge.ui.client as client import deluge.component as component import deluge.ui.gtkui.listview as listview from deluge.configmanager import ConfigManager @@ -64,11 +65,15 @@ class AddTorrentDialog: "on_button_add_clicked": self._on_button_add_clicked }) - self.torrent_liststore = gtk.ListStore(str, str) self.files_liststore = gtk.ListStore(bool, str, int) # Holds the files info self.files = {} + self.infos = {} + self.core_config = {} + self.options = {} + self.previous_selected_torrent = None + self.listview_torrents = self.glade.get_widget("listview_torrents") self.listview_files = self.glade.get_widget("listview_files") @@ -96,9 +101,28 @@ class AddTorrentDialog: self.listview_torrents.set_model(self.torrent_liststore) self.listview_files.set_model(self.files_liststore) + self.listview_files.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.listview_torrents.get_selection().connect("changed", self._on_torrent_changed) + # Get default config values from the core + self.core_keys = [ + "compact_allocation", + "max_connections_per_torrent", + "max_upload_slots_per_torrent", + "max_upload_speed_per_torrent", + "max_download_speed_per_torrent", + "prioritize_first_last_pieces", + "download_location", + "add_paused", + "default_private" + ] + + for key in self.core_keys: + self.core_config[key] = client.get_config_value(key) + + self.set_default_options() + def show(self): self.dialog.show_all() return None @@ -136,7 +160,7 @@ class AddTorrentDialog: name = os.path.split(filename)[-1] + ": " + info.name() self.torrent_liststore.append([str(info.info_hash()), name]) self.files[str(info.info_hash())] = files - + self.infos[str(info.info_hash())] = info def _on_torrent_changed(self, treeselection): (model, row) = treeselection.get_selected() @@ -144,7 +168,8 @@ class AddTorrentDialog: if row is None: return - + + # Update files list files_list = self.files[model.get_value(row, 0)] for file_dict in files_list: @@ -153,11 +178,73 @@ class AddTorrentDialog: file_dict["path"], file_dict["size"] ]) - + + # Update the options frame + self.update_torrent_options() + self.set_default_options() + + self.previous_selected_torrent = row + + def update_torrent_options(self): + # Keeps the torrent options dictionary up-to-date with what the user has + # selected. + row = self.previous_selected_torrent + if row is None or not self.torrent_liststore.iter_is_valid(row): + return + + torrent_id = self.torrent_liststore.get_value(row, 0) + + options = {} + options["download_location"] = \ + self.glade.get_widget("button_location").get_current_folder() + options["compact_allocation"] = \ + self.glade.get_widget("radio_compact").get_active() + options["max_download_speed_per_torrent"] = \ + self.glade.get_widget("spin_maxdown").get_value() + options["max_upload_speed_per_torrent"] = \ + self.glade.get_widget("spin_maxup").get_value() + options["max_connections_per_torrent"] = \ + self.glade.get_widget("spin_maxconnections").get_value() + options["max_upload_slots_per_torrent"] = \ + self.glade.get_widget("spin_maxupslots").get_value() + options["add_paused"] = \ + self.glade.get_widget("chk_paused").get_active() + options["prioritize_first_last_pieces"] = \ + self.glade.get_widget("chk_prioritize").get_active() + options["default_private"] = \ + self.glade.get_widget("chk_private") + + self.options[torrent_id] = options + + def set_default_options(self): + # FIXME: does not account for remote core + self.glade.get_widget("button_location").set_current_folder( + self.core_config["download_location"]) + self.glade.get_widget("radio_compact").set_active( + self.core_config["compact_allocation"]) + self.glade.get_widget("spin_maxdown").set_value( + self.core_config["max_download_speed_per_torrent"]) + self.glade.get_widget("spin_maxup").set_value( + self.core_config["max_upload_speed_per_torrent"]) + self.glade.get_widget("spin_maxconnections").set_value( + self.core_config["max_connections_per_torrent"]) + self.glade.get_widget("spin_maxupslots").set_value( + self.core_config["max_upload_slots_per_torrent"]) + self.glade.get_widget("chk_paused").set_active( + self.core_config["add_paused"]) + self.glade.get_widget("chk_prioritize").set_active( + self.core_config["prioritize_first_last_pieces"]) + self.glade.get_widget("chk_private").set_active( + self.core_config["default_private"]) + #self.infos[model.get_value(row, 0)].priv()) + + + def _on_file_toggled(self, render, path): - row = self.files_liststore.get_iter(path) - self.files_liststore.set_value( - row, 0, not self.files_liststore.get_value(row, 0)) + (model, paths) = self.listview_files.get_selection().get_selected_rows() + for path in paths: + row = model.get_iter(path) + model.set_value(row, 0, not model.get_value(row, 0)) def _on_button_file_clicked(self, widget): log.debug("_on_button_file_clicked") diff --git a/deluge/ui/gtkui/glade/add_torrent_dialog.glade b/deluge/ui/gtkui/glade/add_torrent_dialog.glade index bb4764272..857efb6f3 100644 --- a/deluge/ui/gtkui/glade/add_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/add_torrent_dialog.glade @@ -1,6 +1,6 @@ - + 560 @@ -815,12 +815,13 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True @@ -866,12 +867,13 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 + True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index bbc6eaa06..590ba5e9c 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -54,31 +54,24 @@ icon_seeding = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("seeding16.png")) icon_inactive = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("inactive16.png")) - + +# Holds the info for which status icon to display based on state +ICON_STATE = [ + icon_inactive, + icon_downloading, + icon_downloading, + icon_downloading, + icon_downloading, + icon_seeding, + icon_seeding, + icon_downloading, + icon_inactive +] + def cell_data_statusicon(column, cell, model, row, data): """Display text with an icon""" - state = model.get_value(row, data) - icon = None - if state == deluge.common.TORRENT_STATE.index("Connecting"): - icon = icon_downloading - if state == deluge.common.TORRENT_STATE.index("Downloading"): - icon = icon_downloading - if state == deluge.common.TORRENT_STATE.index("Downloading Metadata"): - icon = icon_downloading - if state == deluge.common.TORRENT_STATE.index("Queued"): - icon = icon_inactive - if state == deluge.common.TORRENT_STATE.index("Paused"): - icon = icon_inactive - if state == deluge.common.TORRENT_STATE.index("Checking"): - icon = icon_downloading - if state == deluge.common.TORRENT_STATE.index("Allocating"): - icon = icon_downloading - if state == deluge.common.TORRENT_STATE.index("Finished"): - icon = icon_seeding - if state == deluge.common.TORRENT_STATE.index("Seeding"): - icon = icon_seeding - - if icon != None: + icon = ICON_STATE[model.get_value(row, data)] + if cell.get_property("pixbuf") != icon: cell.set_property("pixbuf", icon) def cell_data_progress(column, cell, model, row, data): @@ -95,18 +88,14 @@ def cell_data_progress(column, cell, model, row, data): _("Allocating"), _("Paused") ] - column1, column2 = data - value = model.get_value(row, column1) - text = model.get_value(row, column2) - cell.set_property("value", value) + (value, text) = model.get(row, *data) + if cell.get_property("value") != value: + cell.set_property("value", value) textstr = "%s" % TORRENT_STATE[text] - if TORRENT_STATE[text] == "Downloading" or\ - TORRENT_STATE[text] == "Downloading Metadata" or\ - TORRENT_STATE[text] == "Checking" or\ - TORRENT_STATE[text] == "Allocating" or\ - (TORRENT_STATE[text] == "Paused" and value < 100): - textstr = textstr + " %.2f%%" % value - cell.set_property("text", textstr) + if TORRENT_STATE[text] != "Seeding" and TORRENT_STATE[text] != "Finished": + textstr = textstr + " %.2f%%" % value + if cell.get_property("text") != textstr: + cell.set_property("text", textstr) class TorrentView(listview.ListView, component.Component): """TorrentView handles the listing of torrents.""" From 5c485afe5ea8353f99f6ef0c4645fae0bdfdbf0a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 7 Jan 2008 02:18:40 +0000 Subject: [PATCH 0320/1009] Make new add torrent dialog actually work, but still needs work. --- TODO | 3 +- deluge/core/core.py | 10 ++- deluge/core/torrent.py | 31 ++++++++ deluge/core/torrentmanager.py | 45 +++++++++--- deluge/ui/client.py | 18 ++++- deluge/ui/gtkui/addtorrentdialog.py | 109 ++++++++++++++++++++++++---- 6 files changed, 185 insertions(+), 31 deletions(-) diff --git a/TODO b/TODO index 289f8dec3..574ba08b4 100644 --- a/TODO +++ b/TODO @@ -3,7 +3,6 @@ * Figure out easy way for user-made plugins to add i18n support. * Restart daemon function * Docstrings! -* Create a new add torrent dialog * Implement open folder * Maybe add pop-up menus to the status bar items * Address issue where torrents will redownload if the storage is moved outside @@ -22,3 +21,5 @@ * Implement caching in core * Use the batch torrent status info as a cache for other torrent status requests * Don't save fastresume files on exit for finished or paused torrents +* Clean-up TorrentManager and state saving.. Maybe use an 'options' dictionary + similar to one used when adding new torrents. diff --git a/deluge/core/core.py b/deluge/core/core.py index 22653a128..fa0164361 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -81,9 +81,13 @@ DEFAULT_PREFS = { "max_upload_slots_global": -1, "max_connections_per_torrent": -1, "max_upload_slots_per_torrent": -1, + "max_upload_speed_per_torrent": -1, + "max_download_speed_per_torrent": -1, "enabled_plugins": [], "autoadd_location": "", - "autoadd_enable": False + "autoadd_enable": False, + "add_paused": False, + "default_private": False } class Core( @@ -254,7 +258,7 @@ class Core( """De-registers a client with the signal manager.""" self.signals.deregister_client(self.client_address) - def export_add_torrent_file(self, filename, save_path, filedump): + def export_add_torrent_file(self, filename, save_path, filedump, options): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content """ @@ -266,7 +270,7 @@ class Core( filedump = filedump.data torrent_id = self.torrents.add(filename, filedump=filedump, - save_path=save_path) + save_path=save_path, options=options) # Run the plugin hooks for 'post_torrent_add' self.plugins.run_post_torrent_add(torrent_id) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 4ac0c7bca..7b5f7f9f9 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -52,6 +52,14 @@ class Torrent: self.compact = compact # Where the torrent is being saved to self.save_path = save_path + + self.max_connections = -1 + self.max_upload_slots = -1 + self.max_upload_speed = -1 + self.max_download_speed = -1 + self.private = False + self.prioritize = False + # The tracker status self.tracker_status = "" # Tracker list @@ -76,7 +84,30 @@ class Torrent: def set_tracker_status(self, status): """Sets the tracker status""" self.tracker_status = status + + def set_max_connections(self, max_connections): + self.max_connections = max_connections + self.handle.set_max_connections(max_connections) + + def set_max_upload_slots(self, max_slots): + self.max_upload_slots = max_slots + self.handle.set_max_uploads(max_slots) + def set_max_upload_speed(self, m_up_speed): + self.set_max_upload_speed = m_up_speed + self.handle.set_upload_limit(int(m_up_speed * 1024)) + + def set_max_download_speed(self, m_down_speed): + self.set_max_download_speed = m_down_speed + self.handle.set_download_limit(int(m_down_speed * 1024)) + + def set_private_flag(self, private): + self.private = private + self.handle.torrent_info().set_priv(private) + + def set_prioritize_first_last(self, prioritize): + pass + def get_state(self): """Returns the state of this torrent for saving to the session state""" status = self.handle.status() diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index a47c2dcf7..edbed0eb4 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -127,8 +127,9 @@ class TorrentManager(component.Component): """Returns a list of torrent_ids""" return self.torrents.keys() - def add(self, filename, filedump=None, compact=None, paused=False, - save_path=None, total_uploaded=0, trackers=None): + def add(self, filename, filedump=None, options=None, + compact=None, paused=None, save_path=None, total_uploaded=0, + trackers=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) @@ -165,13 +166,33 @@ class TorrentManager(component.Component): handle = None + # Check if options is None and load defaults + if options == None: + options_keys = [ + "compact_allocation", + "max_connections_per_torrent", + "max_upload_slots_per_torrent", + "max_upload_speed_per_torrent", + "max_download_speed_per_torrent", + "prioritize_first_last_pieces", + "download_location", + "add_paused", + "default_private" + ] + options = {} + for key in options_keys: + options[key] = self.config[key] + + if paused is None: + paused = options["add_paused"] + # Make sure we have a valid download_location if save_path is None: - save_path = self.config["download_location"] + save_path = options["download_location"] # Make sure we are adding it with the correct allocation method. if compact is None: - compact = self.config["compact_allocation"] + compact = options["compact_allocation"] # Set the right storage_mode if compact: @@ -204,9 +225,15 @@ class TorrentManager(component.Component): if trackers != None: self.set_trackers(str(handle.info_hash()), trackers) - # Set per-torrent limits - handle.set_max_connections(self.max_connections) - handle.set_max_uploads(self.max_uploads) + # Set per-torrent options + torrent.set_max_connections(options["max_connections_per_torrent"]) + torrent.set_max_upload_slots(options["max_upload_slots_per_torrent"]) + torrent.set_max_upload_speed(options["max_upload_speed_per_torrent"]) + torrent.set_max_download_speed( + options["max_download_speed_per_torrent"]) + torrent.set_prioritize_first_last( + options["prioritize_first_last_pieces"]) + torrent.set_private_flag(options["default_private"]) # Resume the torrent if needed if paused == False: @@ -510,14 +537,14 @@ class TorrentManager(component.Component): log.debug("max_connections_per_torrent set to %s..", value) self.max_connections = value for key in self.torrents.keys(): - self.torrents[key].handle.set_max_connections(value) + self.torrents[key].set_max_connections(value) def on_set_max_upload_slots_per_torrent(self, key, value): """Sets the per-torrent upload slot limit""" log.debug("max_upload_slots_per_torrent set to %s..", value) self.max_uploads = value for key in self.torrents.keys(): - self.torrents[key].handle.set_max_uploads(value) + self.torrents[key].set_max_uploads(value) ## Alert handlers ## def on_alert_torrent_finished(self, alert): diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 48c076fa8..a7377a2db 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -157,7 +157,7 @@ class CoreProxy(gobject.GObject): def get_core(self): if self._core is None and self._uri is not None: log.debug("Creating ServerProxy..") - self._core = xmlrpclib.ServerProxy(self._uri) + self._core = xmlrpclib.ServerProxy(self._uri, allow_none=True) # Call any callbacks registered self.emit("new_core") @@ -220,9 +220,10 @@ def shutdown(): # Ignore everything set_core_uri(None) -def add_torrent_file(torrent_files): +def add_torrent_file(torrent_files, torrent_options=None): """Adds torrent files to the core Expects a list of torrent files + A list of torrent_option dictionaries in the same order of torrent_files """ if torrent_files is None: log.debug("No torrent files selected..") @@ -241,8 +242,19 @@ def add_torrent_file(torrent_files): (path, filename) = os.path.split(torrent_file) fdump = xmlrpclib.Binary(f.read()) f.close() + + # Get the options for the torrent + if torrent_options != None: + try: + options = torrent_options[torrent_files.index(torrent_file)] + except: + options = None + else: + options = None + try: - result = get_core().add_torrent_file(filename, str(), fdump) + result = get_core().add_torrent_file( + filename, str(), fdump, options) except (AttributeError, socket.error): set_core_uri(None) result = False diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 43be6dafe..49fdc1e66 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -62,10 +62,12 @@ class AddTorrentDialog: "on_button_remove_clicked": self._on_button_remove_clicked, "on_button_trackers_clicked": self._on_button_trackers_clicked, "on_button_cancel_clicked": self._on_button_cancel_clicked, - "on_button_add_clicked": self._on_button_add_clicked + "on_button_add_clicked": self._on_button_add_clicked, + "on_button_apply_clicked": self._on_button_apply_clicked, + "on_button_revert_clicked": self._on_button_revert_clicked }) - self.torrent_liststore = gtk.ListStore(str, str) + self.torrent_liststore = gtk.ListStore(str, str, str) self.files_liststore = gtk.ListStore(bool, str, int) # Holds the files info self.files = {} @@ -157,8 +159,9 @@ class AddTorrentDialog: 'download': True }) - name = os.path.split(filename)[-1] + ": " + info.name() - self.torrent_liststore.append([str(info.info_hash()), name]) + name = "%s (%s)" % (info.name(), os.path.split(filename)[-1]) + self.torrent_liststore.append( + [str(info.info_hash()), name, filename]) self.files[str(info.info_hash())] = files self.infos[str(info.info_hash())] = info @@ -179,16 +182,44 @@ class AddTorrentDialog: file_dict["size"] ]) + # Save the previous torrents options + self.save_torrent_options() # Update the options frame - self.update_torrent_options() - self.set_default_options() - + self.update_torrent_options(model.get_value(row, 0)) + self.previous_selected_torrent = row - def update_torrent_options(self): + def update_torrent_options(self, torrent_id): + if torrent_id not in self.options: + self.set_default_options() + return + + options = self.options[torrent_id] + + self.glade.get_widget("button_location").set_current_folder( + options["download_location"]) + self.glade.get_widget("radio_compact").set_active( + options["compact_allocation"]) + self.glade.get_widget("spin_maxdown").set_value( + options["max_download_speed_per_torrent"]) + self.glade.get_widget("spin_maxup").set_value( + options["max_upload_speed_per_torrent"]) + self.glade.get_widget("spin_maxconnections").set_value( + options["max_connections_per_torrent"]) + self.glade.get_widget("spin_maxupslots").set_value( + options["max_upload_slots_per_torrent"]) + self.glade.get_widget("chk_paused").set_active( + options["add_paused"]) + self.glade.get_widget("chk_prioritize").set_active( + options["prioritize_first_last_pieces"]) + self.glade.get_widget("chk_private").set_active( + options["default_private"]) + + def save_torrent_options(self, row=None): # Keeps the torrent options dictionary up-to-date with what the user has # selected. - row = self.previous_selected_torrent + if row is None: + row = self.previous_selected_torrent if row is None or not self.torrent_liststore.iter_is_valid(row): return @@ -204,15 +235,15 @@ class AddTorrentDialog: options["max_upload_speed_per_torrent"] = \ self.glade.get_widget("spin_maxup").get_value() options["max_connections_per_torrent"] = \ - self.glade.get_widget("spin_maxconnections").get_value() + self.glade.get_widget("spin_maxconnections").get_value_as_int() options["max_upload_slots_per_torrent"] = \ - self.glade.get_widget("spin_maxupslots").get_value() + self.glade.get_widget("spin_maxupslots").get_value_as_int() options["add_paused"] = \ self.glade.get_widget("chk_paused").get_active() options["prioritize_first_last_pieces"] = \ self.glade.get_widget("chk_prioritize").get_active() options["default_private"] = \ - self.glade.get_widget("chk_private") + self.glade.get_widget("chk_private").get_active() self.options[torrent_id] = options @@ -236,9 +267,6 @@ class AddTorrentDialog: self.core_config["prioritize_first_last_pieces"]) self.glade.get_widget("chk_private").set_active( self.core_config["default_private"]) - #self.infos[model.get_value(row, 0)].priv()) - - def _on_file_toggled(self, render, path): (model, paths) = self.listview_files.get_selection().get_selected_rows() @@ -317,6 +345,8 @@ class AddTorrentDialog: else: url = None + # This is where we need to fetch the .torrent file from the URL and + # add it to the list. log.debug("url: %s", url) dialog.hide() @@ -344,4 +374,53 @@ class AddTorrentDialog: def _on_button_add_clicked(self, widget): log.debug("_on_button_add_clicked") + # Save the options for selected torrent prior to adding + (model, row) = self.listview_torrents.get_selection().get_selected() + if row is not None: + self.save_torrent_options(row) + torrent_filenames = [] + torrent_options = [] + + row = self.torrent_liststore.get_iter_first() + while row != None: + filename = self.torrent_liststore.get_value(row, 2) + try: + options = self.options[ + self.torrent_liststore.get_value(row, 0)] + except: + options = None + + torrent_filenames.append(filename) + torrent_options.append(options) + + row = self.torrent_liststore.iter_next(row) + + client.add_torrent_file(torrent_filenames, torrent_options) + + def _on_button_apply_clicked(self, widget): + log.debug("_on_button_apply_clicked") + (model, row) = self.listview_torrents.get_selection().get_selected() + if row is None: + return + + self.save_torrent_options(row) + + # The options we want all the torrents to have + options = self.options[model.get_value(row, 0)] + + # Set all the torrent options + row = model.get_iter_first() + while row != None: + torrent_id = model.get_value(row, 0) + self.options[torrent_id] = options + row = model.iter_next(row) + + def _on_button_revert_clicked(self, widget): + log.debug("_on_button_revert_clicked") + (model, row) = self.listview_torrents.get_selection().get_selected() + if row is None: + return + + del self.options[model.get_value(row, 0)] + self.set_default_options() From e491ecf0df59213038dda62bb2a03cc02c9c46b7 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Wed, 9 Jan 2008 06:35:48 +0000 Subject: [PATCH 0321/1009] lt sync 1912 --- libtorrent/include/libtorrent/assert.hpp | 1 + .../include/libtorrent/aux_/session_impl.hpp | 1 + .../include/libtorrent/bandwidth_limit.hpp | 120 ++++++++++++++ .../include/libtorrent/bandwidth_manager.hpp | 154 ++++++------------ .../libtorrent/bandwidth_queue_entry.hpp | 54 ++++++ .../include/libtorrent/disk_io_thread.hpp | 9 +- libtorrent/include/libtorrent/ip_filter.hpp | 37 +++-- libtorrent/include/libtorrent/pe_crypto.hpp | 10 +- .../include/libtorrent/peer_connection.hpp | 2 +- .../include/libtorrent/session_settings.hpp | 6 + libtorrent/include/libtorrent/torrent.hpp | 3 +- libtorrent/include/libtorrent/upnp.hpp | 6 +- .../libtorrent/web_peer_connection.hpp | 3 +- libtorrent/src/broadcast_socket.cpp | 9 +- libtorrent/src/bt_peer_connection.cpp | 6 + libtorrent/src/disk_io_thread.cpp | 17 +- libtorrent/src/ip_filter.cpp | 12 +- libtorrent/src/metadata_transfer.cpp | 3 +- libtorrent/src/pe_crypto.cpp | 8 +- libtorrent/src/peer_connection.cpp | 102 +++++++++--- libtorrent/src/policy.cpp | 6 + libtorrent/src/session_impl.cpp | 17 +- libtorrent/src/torrent.cpp | 33 +++- libtorrent/src/torrent_info.cpp | 21 ++- libtorrent/src/udp_tracker_connection.cpp | 1 - libtorrent/src/upnp.cpp | 37 ++++- 26 files changed, 492 insertions(+), 186 deletions(-) create mode 100644 libtorrent/include/libtorrent/bandwidth_limit.hpp create mode 100644 libtorrent/include/libtorrent/bandwidth_queue_entry.hpp diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp index 246e3b51b..dd3c6b737 100644 --- a/libtorrent/include/libtorrent/assert.hpp +++ b/libtorrent/include/libtorrent/assert.hpp @@ -41,6 +41,7 @@ TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file, ch #define TORRENT_ASSERT(x) if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__) #else +#include #define TORRENT_ASSERT(x) assert(x) #endif diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index cf627c70b..c9b6cb218 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -379,6 +379,7 @@ namespace libtorrent // this pool is used to allocate and recycle send // buffers from. boost::pool<> m_send_buffers; + boost::mutex m_send_buffer_mutex; // the file pool that all storages in this session's // torrents uses. It sets a limit on the number of diff --git a/libtorrent/include/libtorrent/bandwidth_limit.hpp b/libtorrent/include/libtorrent/bandwidth_limit.hpp new file mode 100644 index 000000000..e0675aa31 --- /dev/null +++ b/libtorrent/include/libtorrent/bandwidth_limit.hpp @@ -0,0 +1,120 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_LIMIT_HPP_INCLUDED +#define TORRENT_BANDWIDTH_LIMIT_HPP_INCLUDED + +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// member of peer_connection +struct bandwidth_limit +{ + static const int inf = boost::integer_traits::const_max; + + bandwidth_limit() + : m_quota_left(0) + , m_local_limit(inf) + , m_current_rate(0) + {} + + void throttle(int limit) + { + TORRENT_ASSERT(limit > 0); + m_local_limit = limit; + } + + int throttle() const + { + return m_local_limit; + } + + void assign(int amount) + { + TORRENT_ASSERT(amount >= 0); + m_current_rate += amount; + m_quota_left += amount; + } + + void use_quota(int amount) + { + TORRENT_ASSERT(amount <= m_quota_left); + m_quota_left -= amount; + } + + int quota_left() const + { + return (std::max)(m_quota_left, 0); + } + + void expire(int amount) + { + TORRENT_ASSERT(amount >= 0); + m_current_rate -= amount; + } + + int max_assignable() const + { + if (m_local_limit == inf) return inf; + if (m_local_limit <= m_current_rate) return 0; + return m_local_limit - m_current_rate; + } + +private: + + // this is the amount of bandwidth we have + // been assigned without using yet. i.e. + // the bandwidth that we use up every time + // we receive or send a message. Once this + // hits zero, we need to request more + // bandwidth from the torrent which + // in turn will request bandwidth from + // the bandwidth manager + int m_quota_left; + + // the local limit is the number of bytes + // per window size we are allowed to use. + int m_local_limit; + + // the current rate is the number of + // bytes we have been assigned within + // the window size. + int m_current_rate; +}; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index da251cf4b..68cb7c73a 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -44,6 +44,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" using boost::weak_ptr; using boost::shared_ptr; @@ -77,91 +79,6 @@ struct history_entry weak_ptr tor; }; -template -struct bw_queue_entry -{ - bw_queue_entry(boost::intrusive_ptr const& pe - , int blk, bool no_prio) - : peer(pe), max_block_size(blk), non_prioritized(no_prio) {} - boost::intrusive_ptr peer; - int max_block_size; - bool non_prioritized; -}; - -// member of peer_connection -struct bandwidth_limit -{ - static const int inf = boost::integer_traits::const_max; - - bandwidth_limit() throw() - : m_quota_left(0) - , m_local_limit(inf) - , m_current_rate(0) - {} - - void throttle(int limit) throw() - { - m_local_limit = limit; - } - - int throttle() const throw() - { - return m_local_limit; - } - - void assign(int amount) throw() - { - TORRENT_ASSERT(amount >= 0); - m_current_rate += amount; - m_quota_left += amount; - } - - void use_quota(int amount) throw() - { - TORRENT_ASSERT(amount <= m_quota_left); - m_quota_left -= amount; - } - - int quota_left() const throw() - { - return (std::max)(m_quota_left, 0); - } - - void expire(int amount) throw() - { - TORRENT_ASSERT(amount >= 0); - m_current_rate -= amount; - } - - int max_assignable() const throw() - { - if (m_local_limit == inf) return inf; - if (m_local_limit <= m_current_rate) return 0; - return m_local_limit - m_current_rate; - } - -private: - - // this is the amount of bandwidth we have - // been assigned without using yet. i.e. - // the bandwidth that we use up every time - // we receive or send a message. Once this - // hits zero, we need to request more - // bandwidth from the torrent which - // in turn will request bandwidth from - // the bandwidth manager - int m_quota_left; - - // the local limit is the number of bytes - // per window size we are allowed to use. - int m_local_limit; - - // the current rate is the number of - // bytes we have been assigned within - // the window size. - int m_current_rate; -}; - template T clamp(T val, T ceiling, T floor) throw() { @@ -203,6 +120,19 @@ struct bandwidth_manager m_history_timer.cancel(); } +#ifndef NDEBUG + bool is_in_history(PeerConnection const* peer) const + { + mutex_t::scoped_lock l(m_mutex); + for (typename history_t::const_iterator i + = m_history.begin(), end(m_history.end()); i != end; ++i) + { + if (i->peer.get() == peer) return true; + } + return false; + } +#endif + // non prioritized means that, if there's a line for bandwidth, // others will cut in front of the non-prioritized peers. // this is used by web seeds @@ -213,6 +143,7 @@ struct bandwidth_manager INVARIANT_CHECK; TORRENT_ASSERT(blk > 0); + mutex_t::scoped_lock l(m_mutex); TORRENT_ASSERT(!peer->ignore_bandwidth_limits()); // make sure this peer isn't already in line @@ -224,6 +155,7 @@ struct bandwidth_manager TORRENT_ASSERT(i->peer < peer || peer < i->peer); } #endif + TORRENT_ASSERT(peer->max_assignable_bandwidth(m_channel) > 0); boost::shared_ptr t = peer->associated_torrent().lock(); @@ -257,7 +189,7 @@ struct bandwidth_manager #ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT std::cerr << " req_bandwidht. m_queue.size() = " << m_queue.size() << std::endl; #endif - if (!m_queue.empty()) hand_out_bandwidth(); + if (!m_queue.empty()) hand_out_bandwidth(l); } #ifndef NDEBUG @@ -309,6 +241,7 @@ private: TORRENT_ASSERT(!m_history.empty()); + mutex_t::scoped_lock l(m_mutex); ptime now(time_now()); while (!m_history.empty() && m_history.back().expires_at <= now) { @@ -318,8 +251,10 @@ private: TORRENT_ASSERT(m_current_quota >= 0); intrusive_ptr c = e.peer; shared_ptr t = e.tor.lock(); + l.unlock(); if (!c->is_disconnecting()) c->expire_bandwidth(m_channel, e.amount); if (t) t->expire_bandwidth(m_channel, e.amount); + l.lock(); } // now, wait for the next chunk to expire @@ -332,7 +267,7 @@ private: // since some bandwidth just expired, it // means we can hand out more (in case there // are still consumers in line) - if (!m_queue.empty()) hand_out_bandwidth(); + if (!m_queue.empty()) hand_out_bandwidth(l); #ifndef NDEBUG } catch (std::exception&) @@ -342,7 +277,7 @@ private: #endif } - void hand_out_bandwidth() throw() + void hand_out_bandwidth(boost::mutex::scoped_lock& l) throw() { // if we're already handing out bandwidth, just return back // to the loop further down on the callstack @@ -355,9 +290,7 @@ private: ptime now(time_now()); - mutex_t::scoped_lock l(m_mutex); int limit = m_limit; - l.unlock(); // available bandwidth to hand out int amount = limit - m_current_quota; @@ -369,34 +302,41 @@ private: << " m_current_quota = " << m_current_quota << std::endl; #endif - while (!m_queue.empty() && amount > 0) + if (amount <= 0) + { + m_in_hand_out_bandwidth = false; + return; + } + + queue_t q; + queue_t tmp; + m_queue.swap(q); + while (!q.empty() && amount > 0) { TORRENT_ASSERT(amount == limit - m_current_quota); - bw_queue_entry qe = m_queue.front(); + bw_queue_entry qe = q.front(); TORRENT_ASSERT(qe.max_block_size > 0); - m_queue.pop_front(); + q.pop_front(); shared_ptr t = qe.peer->associated_torrent().lock(); if (!t) continue; if (qe.peer->is_disconnecting()) { + l.unlock(); t->expire_bandwidth(m_channel, qe.max_block_size); - TORRENT_ASSERT(amount == limit - m_current_quota); + l.lock(); + amount = limit - m_current_quota; continue; } // at this point, max_assignable may actually be zero. Since - // the bandwidth quota is subtracted once the data has been - // sent. If the peer was added to the queue while the data was - // still being sent, max_assignable may have been > 0 at that time. - int max_assignable = (std::min)( - qe.peer->max_assignable_bandwidth(m_channel) - , t->max_assignable_bandwidth(m_channel)); + // the rate limit of the peer might have changed while it + // was in the queue. + int max_assignable = qe.peer->max_assignable_bandwidth(m_channel); if (max_assignable == 0) { - t->expire_bandwidth(m_channel, qe.max_block_size); - qe.peer->assign_bandwidth(m_channel, 0); - TORRENT_ASSERT(amount == limit - m_current_quota); + TORRENT_ASSERT(is_in_history(qe.peer.get())); + tmp.push_back(qe); continue; } @@ -441,7 +381,7 @@ private: #endif if (amount < block_size / 2) { - m_queue.push_front(qe); + tmp.push_back(qe); break; } @@ -454,12 +394,16 @@ private: TORRENT_ASSERT(amount == limit - m_current_quota); amount -= hand_out_amount; TORRENT_ASSERT(hand_out_amount <= qe.max_block_size); + l.unlock(); t->assign_bandwidth(m_channel, hand_out_amount, qe.max_block_size); qe.peer->assign_bandwidth(m_channel, hand_out_amount); + l.lock(); add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); - TORRENT_ASSERT(amount == limit - m_current_quota); + amount = limit - m_current_quota; } + if (!q.empty()) m_queue.insert(m_queue.begin(), q.begin(), q.end()); + if (!tmp.empty()) m_queue.insert(m_queue.begin(), tmp.begin(), tmp.end()); #ifndef NDEBUG } catch (std::exception& e) diff --git a/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp b/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp new file mode 100644 index 000000000..76c119d96 --- /dev/null +++ b/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED +#define TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED + +#include + +namespace libtorrent { + +template +struct bw_queue_entry +{ + bw_queue_entry(boost::intrusive_ptr const& pe + , int blk, bool no_prio) + : peer(pe), max_block_size(blk), non_prioritized(no_prio) {} + boost::intrusive_ptr peer; + int max_block_size; + bool non_prioritized; +}; + +} + +#endif + diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index a77522356..1d9d88534 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -30,6 +30,9 @@ POSSIBILITY OF SUCH DAMAGE. */ +#ifndef TORRENT_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + #ifdef TORRENT_DISK_STATS #include #endif @@ -99,7 +102,9 @@ namespace libtorrent int disk_allocations() const { return m_allocations; } #endif - + + void join(); + // aborts read operations void stop(boost::intrusive_ptr s); void add_job(disk_io_job const& j @@ -152,3 +157,5 @@ namespace libtorrent } +#endif + diff --git a/libtorrent/include/libtorrent/ip_filter.hpp b/libtorrent/include/libtorrent/ip_filter.hpp index eee76cdc4..1adb14551 100644 --- a/libtorrent/include/libtorrent/ip_filter.hpp +++ b/libtorrent/include/libtorrent/ip_filter.hpp @@ -76,9 +76,9 @@ namespace detail template Addr zero() { - typename Addr::bytes_type zero; + Addr zero; std::fill(zero.begin(), zero.end(), 0); - return Addr(zero); + return zero; } template<> @@ -87,8 +87,8 @@ namespace detail template Addr plus_one(Addr const& a) { - typename Addr::bytes_type tmp(a.to_bytes()); - typedef typename Addr::bytes_type::reverse_iterator iter; + Addr tmp(a); + typedef typename Addr::reverse_iterator iter; for (iter i = tmp.rbegin() , end(tmp.rend()); i != end; ++i) { @@ -99,7 +99,7 @@ namespace detail } *i = 0; } - return Addr(tmp); + return tmp; } inline boost::uint16_t plus_one(boost::uint16_t val) { return val + 1; } @@ -107,8 +107,8 @@ namespace detail template Addr minus_one(Addr const& a) { - typename Addr::bytes_type tmp(a.to_bytes()); - typedef typename Addr::bytes_type::reverse_iterator iter; + Addr tmp(a); + typedef typename Addr::reverse_iterator iter; for (iter i = tmp.rbegin() , end(tmp.rend()); i != end; ++i) { @@ -119,7 +119,7 @@ namespace detail } *i = (std::numeric_limits::max)(); } - return Addr(tmp); + return tmp; } inline boost::uint16_t minus_one(boost::uint16_t val) { return val - 1; } @@ -127,9 +127,9 @@ namespace detail template Addr max_addr() { - typename Addr::bytes_type tmp; + Addr tmp; std::fill(tmp.begin(), tmp.end() - , (std::numeric_limits::max)()); + , (std::numeric_limits::max)()); return Addr(tmp); } @@ -220,23 +220,24 @@ namespace detail return i->access; } - std::vector > export_filter() const + template + std::vector > export_filter() const { - std::vector > ret; + std::vector > ret; ret.reserve(m_access_list.size()); for (typename range_t::const_iterator i = m_access_list.begin() , end(m_access_list.end()); i != end;) { - ip_range r; - r.first = i->start; + ip_range r; + r.first = ExternalAddressType(i->start); r.flags = i->access; ++i; if (i == end) - r.last = max_addr(); + r.last = ExternalAddressType(max_addr()); else - r.last = minus_one(i->start); + r.last = ExternalAddressType(minus_one(i->start)); ret.push_back(r); } @@ -288,8 +289,8 @@ public: private: - detail::filter_impl m_filter4; - detail::filter_impl m_filter6; + detail::filter_impl m_filter4; + detail::filter_impl m_filter6; }; class TORRENT_EXPORT port_filter diff --git a/libtorrent/include/libtorrent/pe_crypto.hpp b/libtorrent/include/libtorrent/pe_crypto.hpp index e2276dee6..5db77f6c7 100644 --- a/libtorrent/include/libtorrent/pe_crypto.hpp +++ b/libtorrent/include/libtorrent/pe_crypto.hpp @@ -62,7 +62,7 @@ namespace libtorrent private: int get_local_key_size () const { - assert (m_DH); + TORRENT_ASSERT(m_DH); return BN_num_bytes (m_DH->pub_key); } @@ -97,8 +97,8 @@ namespace libtorrent void encrypt (char* pos, int len) { - assert (len >= 0); - assert (pos); + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(pos); RC4 (&m_local_key, len, reinterpret_cast(pos), reinterpret_cast(pos)); @@ -106,8 +106,8 @@ namespace libtorrent void decrypt (char* pos, int len) { - assert (len >= 0); - assert (pos); + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(pos); RC4 (&m_remote_key, len, reinterpret_cast(pos), reinterpret_cast(pos)); diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 805b38d9d..60cb4bc17 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -69,7 +69,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" #include "libtorrent/session.hpp" -#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/bandwidth_limit.hpp" #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" #include "libtorrent/intrusive_ptr_base.hpp" diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index 7cc7d26da..2817d27d2 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -117,6 +117,7 @@ namespace libtorrent , use_dht_as_fallback(true) #endif , free_torrent_hashes(true) + , upnp_ignore_nonrouters(true) {} // this is the user agent that will be sent to the tracker @@ -292,6 +293,11 @@ namespace libtorrent // make the get_torrent_info() function to return an incomplete // torrent object that cannot be passed back to add_torrent() bool free_torrent_hashes; + + // when this is true, the upnp port mapper will ignore + // any upnp devices that don't have an address that matches + // our currently configured router. + bool upnp_ignore_nonrouters; }; #ifndef TORRENT_DISABLE_DHT diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 4731da6b2..f66620999 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -65,7 +65,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/piece_picker.hpp" #include "libtorrent/config.hpp" #include "libtorrent/escape_string.hpp" -#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" #include "libtorrent/storage.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/assert.hpp" diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index 0b799d1e9..be8ec15cb 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -68,13 +68,14 @@ class upnp : public intrusive_ptr_base public: upnp(io_service& ios, connection_queue& cc , address const& listen_interface, std::string const& user_agent - , portmap_callback_t const& cb); + , portmap_callback_t const& cb, bool ignore_nonrouters); ~upnp(); // maps the ports, if a port is set to 0 // it will not be mapped void set_mappings(int tcp, int udp); + void discover_device(); void close(); private: @@ -89,7 +90,6 @@ private: void resend_request(asio::error_code const& e); void on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); - void discover_device(); struct rootdevice; @@ -233,6 +233,8 @@ private: connection_queue& m_cc; + std::vector

m_filter; + #ifdef TORRENT_UPNP_LOGGING std::ofstream m_log; #endif diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index 8871ad8ec..742d823f0 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -120,7 +120,8 @@ namespace libtorrent void write_interested() {} void write_not_interested() {} void write_request(peer_request const& r); - void write_cancel(peer_request const& r) {} + void write_cancel(peer_request const& r) + { incoming_reject_request(r); } void write_have(int index) {} void write_piece(peer_request const& r, char* buffer) { TORRENT_ASSERT(false); } void write_keepalive() {} diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index e03ad2274..3437648d9 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -170,12 +170,17 @@ namespace libtorrent for (std::list::iterator i = m_sockets.begin() , end(m_sockets.end()); i != end; ++i) { + if (!i->socket) continue; asio::error_code e; i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); #ifndef NDEBUG // std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; #endif - if (e) ec = e; + if (e) + { + i->socket->close(e); + i->socket.reset(); + } } } @@ -184,6 +189,7 @@ namespace libtorrent { if (ec || bytes_transferred == 0 || !m_on_receive) return; m_on_receive(s->remote, s->buffer, bytes_transferred); + if (!s->socket) return; s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer)) , s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2)); } @@ -195,6 +201,7 @@ namespace libtorrent for (std::list::iterator i = m_sockets.begin() , end(m_sockets.end()); i != end; ++i) { + if (!i->socket) continue; i->socket->close(); } } diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 384bc2375..ca98888bd 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -295,6 +295,8 @@ namespace libtorrent { INVARIANT_CHECK; + if (!m_supports_fast) return; + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); @@ -312,6 +314,7 @@ namespace libtorrent TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_supports_fast); char msg[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; char* ptr = msg + 5; @@ -1297,6 +1300,9 @@ namespace libtorrent detail::write_int32(r.start, ptr); // begin detail::write_int32(r.length, ptr); // length send_buffer(msg, sizeof(msg)); + + if (!m_supports_fast) + incoming_reject_request(r); } void bt_peer_connection::write_request(peer_request const& r) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index e0fa982bc..886d5d891 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -62,12 +62,7 @@ namespace libtorrent disk_io_thread::~disk_io_thread() { - mutex_t::scoped_lock l(m_mutex); - m_abort = true; - m_signal.notify_all(); - l.unlock(); - - m_disk_io_thread.join(); + TORRENT_ASSERT(m_abort == true); } #ifndef NDEBUG @@ -95,6 +90,16 @@ namespace libtorrent #endif + void disk_io_thread::join() + { + mutex_t::scoped_lock l(m_mutex); + m_abort = true; + m_signal.notify_all(); + l.unlock(); + + m_disk_io_thread.join(); + } + // aborts read operations void disk_io_thread::stop(boost::intrusive_ptr s) { diff --git a/libtorrent/src/ip_filter.cpp b/libtorrent/src/ip_filter.cpp index 05334e578..eb91de0d0 100644 --- a/libtorrent/src/ip_filter.cpp +++ b/libtorrent/src/ip_filter.cpp @@ -44,12 +44,12 @@ namespace libtorrent if (first.is_v4()) { TORRENT_ASSERT(last.is_v4()); - m_filter4.add_rule(first.to_v4(), last.to_v4(), flags); + m_filter4.add_rule(first.to_v4().to_bytes(), last.to_v4().to_bytes(), flags); } else if (first.is_v6()) { TORRENT_ASSERT(last.is_v6()); - m_filter6.add_rule(first.to_v6(), last.to_v6(), flags); + m_filter6.add_rule(first.to_v6().to_bytes(), last.to_v6().to_bytes(), flags); } else TORRENT_ASSERT(false); @@ -58,15 +58,15 @@ namespace libtorrent int ip_filter::access(address const& addr) const { if (addr.is_v4()) - return m_filter4.access(addr.to_v4()); + return m_filter4.access(addr.to_v4().to_bytes()); TORRENT_ASSERT(addr.is_v6()); - return m_filter6.access(addr.to_v6()); + return m_filter6.access(addr.to_v6().to_bytes()); } ip_filter::filter_tuple_t ip_filter::export_filter() const { - return boost::make_tuple(m_filter4.export_filter() - , m_filter6.export_filter()); + return boost::make_tuple(m_filter4.export_filter() + , m_filter6.export_filter()); } void port_filter::add_rule(boost::uint16_t first, boost::uint16_t last, int flags) diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index 50dc57ec7..228566ed8 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -95,7 +95,6 @@ namespace libtorrent { namespace return ret; } - struct metadata_plugin : torrent_plugin { metadata_plugin(torrent& t) @@ -217,7 +216,7 @@ namespace libtorrent { namespace m_metadata_size = total_size; } - void piece_pass(int) + void on_piece_pass(int) { // if we became a seed, copy the metadata from // the torrent before it is deallocated diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index 955a7fea0..093bb1265 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -52,11 +52,11 @@ namespace libtorrent { m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); m_DH->length = 160l; - assert (sizeof(m_dh_prime) == DH_size(m_DH)); + TORRENT_ASSERT(sizeof(m_dh_prime) == DH_size(m_DH)); DH_generate_key (m_DH); // TODO Check != 0 - assert (m_DH->pub_key); + TORRENT_ASSERT(m_DH->pub_key); // DH can generate key sizes that are smaller than the size of // P with exponentially decreasing probability, in which case @@ -78,7 +78,7 @@ namespace libtorrent { DH_key_exchange::~DH_key_exchange () { - assert (m_DH); + TORRENT_ASSERT(m_DH); DH_free (m_DH); } @@ -91,7 +91,7 @@ namespace libtorrent { // compute shared secret given remote public key void DH_key_exchange::compute_secret (char const* remote_pubkey) { - assert (remote_pubkey); + TORRENT_ASSERT(remote_pubkey); BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); char dh_secret[96]; diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 9bd089234..ac61f042f 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -759,7 +759,7 @@ namespace libtorrent } // ----------------------------- - // -------- REJECT PIECE ------- + // ------- SUGGEST PIECE ------- // ----------------------------- void peer_connection::incoming_suggest(int index) @@ -1127,6 +1127,18 @@ namespace libtorrent "i: " << m_peer_interested << " | " "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; + + (*m_logger) << time_now_string() + << " ==> REJECT_PIECE [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " ]\n"; + + (*m_logger) << time_now_string() + << " ==> REJECT_PIECE [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " ]\n"; #endif write_reject_request(r); return; @@ -1155,8 +1167,13 @@ namespace libtorrent { write_reject_request(r); #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " *** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]\n"; + (*m_logger) << time_now_string() + << " *** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]\n"; + (*m_logger) << time_now_string() + << " ==> REJECT_PIECE [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " ]\n"; #endif } else @@ -1179,6 +1196,12 @@ namespace libtorrent "n: " << t->torrent_file().num_pieces() << " | " "h: " << t->have_piece(r.piece) << " | " "block_limit: " << t->block_size() << " ]\n"; + + (*m_logger) << time_now_string() + << " ==> REJECT_PIECE [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " ]\n"; #endif write_reject_request(r); @@ -1365,6 +1388,9 @@ namespace libtorrent m_outstanding_writing_bytes += p.length; TORRENT_ASSERT(!m_reading); picker.mark_as_writing(block_finished, peer_info_struct()); +#ifndef NDEBUG + t->check_invariant(); +#endif } void peer_connection::on_disk_write_complete(int ret, disk_io_job const& j @@ -1479,6 +1505,14 @@ namespace libtorrent if (i != m_requests.end()) { m_requests.erase(i); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> REJECT_PIECE [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " ]\n"; +#endif + write_reject_request(r); } else { @@ -1612,12 +1646,6 @@ namespace libtorrent } #endif - // if we already have the piece, we can - // ignore this message - if (t->valid_metadata() - && t->have_piece(index)) - return; - if (index < 0 || index >= int(m_have_piece.size())) { #ifdef TORRENT_VERBOSE_LOGGING @@ -1627,6 +1655,12 @@ namespace libtorrent return; } + // if we already have the piece, we can + // ignore this message + if (t->valid_metadata() + && t->have_piece(index)) + return; + m_allowed_fast.push_back(index); // if the peer has the piece and we want @@ -1735,11 +1769,6 @@ namespace libtorrent // sent yet, so we don't have to send a cancel. return; } - else - { - m_download_queue.erase(it); - t->picker().abort_download(block); - } int block_offset = block.block_index * t->block_size(); int block_size @@ -1753,13 +1782,12 @@ namespace libtorrent r.start = block_offset; r.length = block_size; - write_cancel(r); - #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " ==> CANCEL [ piece: " << block.piece_index << " | s: " << block_offset << " | l: " << block_size << " | " << block.block_index << " ]\n"; #endif + write_cancel(r); } void peer_connection::send_choke() @@ -1783,6 +1811,19 @@ namespace libtorrent // reject the requests we have in the queue std::for_each(m_requests.begin(), m_requests.end() , bind(&peer_connection::write_reject_request, this, _1)); + +#ifdef TORRENT_VERBOSE_LOGGING + for (std::deque::iterator i = m_requests.begin() + , end(m_requests.end()); i != end; ++i) + { + peer_request const& r = *i; + (*m_logger) << time_now_string() + << " ==> REJECT_PIECE [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " ]\n"; + } +#endif m_requests.clear(); } @@ -2206,18 +2247,24 @@ namespace libtorrent else { piece_picker& picker = t->picker(); - while (!m_download_queue.empty()) + + std::deque dl(m_download_queue); + for (std::deque::iterator i = dl.begin() + , end(dl.end()); i != end; ++i) { piece_block const& r = m_download_queue.back(); - picker.abort_download(r); +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() + << " ==> CANCEL [ piece: " << r.piece_index + << " | block: " << r.block_index + << " ]\n"; +#endif write_cancel(t->to_req(r)); - m_download_queue.pop_back(); } while (!m_request_queue.empty()) { piece_block const& r = m_request_queue.back(); picker.abort_download(r); - write_cancel(t->to_req(r)); m_request_queue.pop_back(); } @@ -2562,6 +2609,7 @@ namespace libtorrent // return value is destructed buffer::interval peer_connection::allocate_send_buffer(int size) { + TORRENT_ASSERT(size > 0); char* insert = m_send_buffer.allocate_appendix(size); if (insert == 0) { @@ -2618,6 +2666,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "**ERROR**: " << error.message() << "[in peer_connection::on_receive_data]\n"; #endif + set_failed(); on_receive(error, bytes_transferred); throw std::runtime_error(error.message()); } @@ -2778,6 +2827,7 @@ namespace libtorrent (*m_ses.m_logger) << "CONNECTION FAILED: " << m_remote.address().to_string() << ": " << e.message() << "\n"; #endif + set_failed(); m_ses.connection_failed(self(), m_remote, e.message().c_str()); return; } @@ -2838,6 +2888,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << "**ERROR**: " << error.message() << " [in peer_connection::on_send_data]\n"; #endif + set_failed(); throw std::runtime_error(error.message()); } if (m_disconnecting) return; @@ -2869,6 +2920,17 @@ namespace libtorrent #ifndef NDEBUG void peer_connection::check_invariant() const { + for (int i = 0; i < 2; ++i) + { + // this peer is in the bandwidth history iff max_assignable < limit + TORRENT_ASSERT((m_bandwidth_limit[i].max_assignable() < m_bandwidth_limit[i].throttle()) + == m_ses.m_bandwidth_manager[i]->is_in_history(this) + || m_bandwidth_limit[i].throttle() == bandwidth_limit::inf); + } + std::set unique; + std::copy(m_download_queue.begin(), m_download_queue.end(), std::inserter(unique, unique.begin())); + std::copy(m_request_queue.begin(), m_request_queue.end(), std::inserter(unique, unique.begin())); + TORRENT_ASSERT(unique.size() == m_download_queue.size() + m_request_queue.size()); if (m_peer_info) { TORRENT_ASSERT(m_peer_info->connection == this diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index f203eaa56..2fdc5358a 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -533,6 +533,12 @@ namespace libtorrent if (i->second.type == peer::not_connectable) continue; if (i->second.seed && finished) continue; if (i->second.failcount >= max_failcount) continue; + + // prefer peers with lower failcount + if (candidate != m_peers.end() + && candidate->second.failcount < i->second.failcount) + continue; + if (now - i->second.connected < seconds(i->second.failcount * min_reconnect_time)) continue; if (ses.m_port_filter.access(i->second.ip.port()) & port_filter::blocked) diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index c1969d523..055bf38db 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -2198,6 +2198,11 @@ namespace detail #endif m_checker_thread->join(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " waiting for disk io thread\n"; +#endif + m_disk_thread.join(); + TORRENT_ASSERT(m_torrents.empty()); TORRENT_ASSERT(m_connections.empty()); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -2345,8 +2350,10 @@ namespace detail , m_listen_interface.address() , m_settings.user_agent , bind(&session_impl::on_port_mapping - , this, _1, _2, _3)); + , this, _1, _2, _3) + , m_settings.upnp_ignore_nonrouters); + m_upnp->discover_device(); m_upnp->set_mappings(m_listen_interface.port(), #ifndef TORRENT_DISABLE_DHT m_dht ? m_dht_settings.service_port : @@ -2385,7 +2392,11 @@ namespace detail std::pair session_impl::allocate_buffer(int size) { + TORRENT_ASSERT(size > 0); int num_buffers = (size + send_buffer_size - 1) / send_buffer_size; + TORRENT_ASSERT(num_buffers > 0); + + boost::mutex::scoped_lock l(m_send_buffer_mutex); #ifdef TORRENT_STATS m_buffer_allocations += num_buffers; m_buffer_usage_logger << log_time() << " protocol_buffer: " @@ -2397,8 +2408,12 @@ namespace detail void session_impl::free_buffer(char* buf, int size) { + TORRENT_ASSERT(size > 0); TORRENT_ASSERT(size % send_buffer_size == 0); int num_buffers = size / send_buffer_size; + TORRENT_ASSERT(num_buffers > 0); + + boost::mutex::scoped_lock l(m_send_buffer_mutex); #ifdef TORRENT_STATS m_buffer_allocations -= num_buffers; TORRENT_ASSERT(m_buffer_allocations >= 0); diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index e8c1f2247..12db1e59b 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -1589,6 +1589,18 @@ namespace libtorrent p->set_peer_info(0); TORRENT_ASSERT(i != m_connections.end()); m_connections.erase(i); + + // remove from bandwidth request-queue + for (int c = 0; c < 2; ++c) + { + for (queue_t::iterator i = m_bandwidth_queue[c].begin() + , end(m_bandwidth_queue[c].end()); i != end; ++i) + { + if (i->peer != p) continue; + m_bandwidth_queue[c].erase(i); + break; + } + } } catch (std::exception& e) { @@ -2147,6 +2159,11 @@ namespace libtorrent throw protocol_error("session is closing"); } + if (int(m_connections.size()) >= m_max_connections) + { + throw protocol_error("reached connection limit"); + } + TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); peer_iterator ci = m_connections.insert(p).first; try @@ -2220,6 +2237,7 @@ namespace libtorrent , bool non_prioritized) { TORRENT_ASSERT(m_bandwidth_limit[channel].throttle() > 0); + TORRENT_ASSERT(p->max_assignable_bandwidth(channel) > 0); int block_size = m_bandwidth_limit[channel].throttle() / 10; if (block_size <= 0) block_size = 1; @@ -2244,16 +2262,23 @@ namespace libtorrent TORRENT_ASSERT(amount > 0); m_bandwidth_limit[channel].expire(amount); - + queue_t tmp; while (!m_bandwidth_queue[channel].empty()) { bw_queue_entry qe = m_bandwidth_queue[channel].front(); if (m_bandwidth_limit[channel].max_assignable() == 0) break; m_bandwidth_queue[channel].pop_front(); + if (qe.peer->max_assignable_bandwidth(channel) <= 0) + { + TORRENT_ASSERT(m_ses.m_bandwidth_manager[channel]->is_in_history(qe.peer.get())); + if (!qe.peer->is_disconnecting()) tmp.push_back(qe); + continue; + } perform_bandwidth_request(channel, qe.peer , qe.max_block_size, qe.non_prioritized); } + m_bandwidth_queue[channel].insert(m_bandwidth_queue[channel].begin(), tmp.begin(), tmp.end()); } void torrent::perform_bandwidth_request(int channel @@ -2592,6 +2617,9 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + TORRENT_ASSERT(m_bandwidth_queue[0].size() <= m_connections.size()); + TORRENT_ASSERT(m_bandwidth_queue[1].size() <= m_connections.size()); + int num_uploads = 0; std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -2615,7 +2643,8 @@ namespace libtorrent for (std::map::iterator i = num_requests.begin() , end(num_requests.end()); i != end; ++i) { - TORRENT_ASSERT(m_picker->num_peers(i->first) == i->second); + if (!m_picker->is_downloaded(i->first)) + TORRENT_ASSERT(m_picker->num_peers(i->first) == i->second); } } diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index b89510f9f..0aac5a7ba 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -350,10 +350,23 @@ namespace libtorrent { m_name = info["name"].string(); } fs::path tmp = m_name; - if (tmp.is_complete()) throw std::runtime_error("torrent contains " - "a file with an absolute path: '" + m_name + "'"); - if (tmp.has_branch_path()) throw std::runtime_error( - "torrent contains name with directories: '" + m_name + "'"); + if (tmp.is_complete()) + { + m_name = tmp.leaf(); + } + else if (tmp.has_branch_path()) + { + fs::path p; + for (fs::path::iterator i = tmp.begin() + , end(tmp.end()); i != end; ++i) + { + if (*i == "." || *i == "..") continue; + p /= *i; + } + m_name = p.string(); + } + if (m_name == ".." || m_name == ".") + throw std::runtime_error("invalid 'name' of torrent (possible exploit attempt)"); // extract file list entry const* i = info.find_key("files"); diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index eb138e67a..0ad38ef86 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -106,7 +106,6 @@ namespace libtorrent , udp::resolver::iterator i) try { if (error == asio::error::operation_aborted) return; - if (!m_socket.is_open()) return; // the operation was aborted if (error || i == udp::resolver::iterator()) { fail(-1, error.message().c_str()); diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 116eb1dfe..4f6ac7fbf 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/http_tracker_connection.hpp" #include "libtorrent/xml_parse.hpp" #include "libtorrent/connection_queue.hpp" +#include "libtorrent/enum_net.hpp" #include #include @@ -61,7 +62,7 @@ namespace libtorrent upnp::upnp(io_service& ios, connection_queue& cc , address const& listen_interface, std::string const& user_agent - , portmap_callback_t const& cb) + , portmap_callback_t const& cb, bool ignore_nonrouters) : m_udp_local_port(0) , m_tcp_local_port(0) , m_user_agent(user_agent) @@ -81,7 +82,21 @@ upnp::upnp(io_service& ios, connection_queue& cc m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif m_retry_count = 0; - discover_device(); + + if (ignore_nonrouters) + { + asio::error_code ec; + std::vector
const& net = enum_net_interfaces(m_io_service, ec); + m_filter.reserve(net.size()); + for (std::vector
::const_iterator i = net.begin() + , end(net.end()); i != end; ++i) + { + asio::error_code e; + address a = router_for_interface(*i, e); + if (e || is_loopback(a)) continue; + m_filter.push_back(a); + } + } } upnp::~upnp() @@ -266,6 +281,18 @@ try Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0 */ + if (!m_filter.empty() && std::find(m_filter.begin(), m_filter.end() + , from.address()) == m_filter.end()) + { + // this upnp device is filtered because it's not in the + // list of configured routers +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() << " <== (" << from << ") Rootdevice " + "ignored because it's not out router" << std::endl; +#endif + return; + } + http_parser p; try { @@ -663,7 +690,7 @@ void upnp::on_upnp_xml(asio::error_code const& e parse_state s; s.reset("urn:schemas-upnp-org:service:WANIPConnection:1"); xml_parse((char*)p.get_body().begin, (char*)p.get_body().end - , m_strand.wrap(bind(&find_control_url, _1, _2, boost::ref(s)))); + , bind(&find_control_url, _1, _2, boost::ref(s))); if (s.found_service) { d.service_namespace = s.service_type; @@ -674,7 +701,7 @@ void upnp::on_upnp_xml(asio::error_code const& e // a PPP connection s.reset("urn:schemas-upnp-org:service:WANPPPConnection:1"); xml_parse((char*)p.get_body().begin, (char*)p.get_body().end - , m_strand.wrap(bind(&find_control_url, _1, _2, boost::ref(s)))); + , bind(&find_control_url, _1, _2, boost::ref(s))); if (s.found_service) { d.service_namespace = s.service_type; @@ -821,7 +848,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e error_code_parse_state s; xml_parse((char*)p.get_body().begin, (char*)p.get_body().end - , m_strand.wrap(bind(&find_error_code, _1, _2, boost::ref(s)))); + , bind(&find_error_code, _1, _2, boost::ref(s))); #ifdef TORRENT_UPNP_LOGGING if (s.error_code != -1) From 2e895795b6e5ae85ed969b3e6686a78b9aefca44 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 9 Jan 2008 07:12:04 +0000 Subject: [PATCH 0322/1009] Force '-DNDEBUG'. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 51a3a913c..0b8374273 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,8 @@ _extra_compile_args = [ "-DHAVE_PTHREAD=1", "-DTORRENT_USE_OPENSSL=1", "-DHAVE_SSL=1", - "-O2" + "-O2", + "-DNDEBUG" ] removals = ["-Wstrict-prototypes"] From e65d720890147c514f11ee8b512f53465eb1eb14 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 9 Jan 2008 23:43:00 +0000 Subject: [PATCH 0323/1009] Fix autostarting daemon. --- deluge/ui/gtkui/connectionmanager.py | 1 + deluge/ui/gtkui/gtkui.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 3d7182f74..25cee17ac 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -36,6 +36,7 @@ import pkg_resources import gobject import socket import os +import time import deluge.component as component import deluge.xmlrpclib as xmlrpclib diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index b90e27880..becfd4737 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -94,6 +94,9 @@ DEFAULT_PREFS = { class GtkUI: def __init__(self, args): + # Initialize gdk threading + gtk.gdk.threads_init() + # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') locale.bindtextdomain("deluge", @@ -143,7 +146,6 @@ class GtkUI: self.connectionmanager.show() # Start the gtk main loop - gtk.gdk.threads_init() gtk.main() log.debug("gtkui shutting down..") From a5d3444fd8b999f25982fc15605cf98c75962237 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 11 Jan 2008 01:42:47 +0000 Subject: [PATCH 0324/1009] Initial commit of WebUI support for 0.6. The WebUI revision is 187. You can start the webui by doing: $ deluge --ui web --- deluge/main.py | 5 +- deluge/ui/ui.py | 18 +- deluge/ui/webui/__init__.py | 0 deluge/ui/webui/webui.py | 41 + deluge/ui/webui/webui_plugin/LICENSE | 350 ++++++ deluge/ui/webui/webui_plugin/TODO | 20 + deluge/ui/webui/webui_plugin/__init__.py | 287 +++++ .../ui/webui/webui_plugin/dbus_interface.py | 283 +++++ deluge/ui/webui/webui_plugin/debugerror.py | 373 ++++++ .../ui/webui/webui_plugin/deluge_webserver.py | 62 + deluge/ui/webui/webui_plugin/json_api.py | 211 ++++ deluge/ui/webui/webui_plugin/lib/__init__.py | 0 .../lib/gtk_cherrypy_wsgiserver.py | 1077 +++++++++++++++++ deluge/ui/webui/webui_plugin/lib/pythonize.py | 38 + deluge/ui/webui/webui_plugin/lib/readme.txt | 8 + .../webui/webui_plugin/lib/static_handler.py | 136 +++ .../Dependency-not-really part of webui.txt | 1 + .../webui_plugin/lib/webpy022/__init__.py | 62 + .../webui_plugin/lib/webpy022/changes.txt | 5 + .../webui_plugin/lib/webpy022/cheetah.py | 98 ++ .../ui/webui/webui_plugin/lib/webpy022/db.py | 703 +++++++++++ .../webui_plugin/lib/webpy022/debugerror.py | 316 +++++ .../webui/webui_plugin/lib/webpy022/form.py | 215 ++++ .../webui/webui_plugin/lib/webpy022/http.py | 270 +++++ .../webui_plugin/lib/webpy022/httpserver.py | 227 ++++ .../ui/webui/webui_plugin/lib/webpy022/net.py | 155 +++ .../webui_plugin/lib/webpy022/request.py | 153 +++ .../webui_plugin/lib/webpy022/template.py | 878 ++++++++++++++ .../webui/webui_plugin/lib/webpy022/utils.py | 796 ++++++++++++ .../webui/webui_plugin/lib/webpy022/webapi.py | 369 ++++++ .../webui/webui_plugin/lib/webpy022/wsgi.py | 54 + .../lib/webpy022/wsgiserver/__init__.py | 1019 ++++++++++++++++ .../ui/webui/webui_plugin/page_decorators.py | 71 ++ deluge/ui/webui/webui_plugin/pages.py | 366 ++++++ deluge/ui/webui/webui_plugin/render.py | 139 +++ deluge/ui/webui/webui_plugin/revno | 1 + deluge/ui/webui/webui_plugin/run_webserver | 5 + deluge/ui/webui/webui_plugin/run_webserver06 | 5 + .../scripts/add_torrent_to_deluge_webui | 10 + .../scripts/add_torrents_to_deluge.user.js | 207 ++++ .../scripts/build_webui_tarball.sh | 7 + .../webui/webui_plugin/scripts/curl-example | 1 + .../scripts/extract_template_strings.py | 28 + .../webui_plugin/scripts/template_strings.py | 69 ++ deluge/ui/webui/webui_plugin/ssl/deluge.key | 27 + deluge/ui/webui/webui_plugin/ssl/deluge.pem | 22 + .../static/images/deluge_icon.gif | Bin 0 -> 588 bytes .../static/images/downloading16.png | Bin 0 -> 662 bytes .../webui_plugin/static/images/inactive16.png | Bin 0 -> 588 bytes .../webui_plugin/static/images/seeding16.png | Bin 0 -> 612 bytes .../webui_plugin/static/images/simple_bg.jpg | Bin 0 -> 1150 bytes .../static/images/simple_line.jpg | Bin 0 -> 631 bytes .../static/images/simple_logo.jpg | Bin 0 -> 1785 bytes .../static/images/tango/details.png | Bin 0 -> 498 bytes .../webui_plugin/static/images/tango/down.png | Bin 0 -> 627 bytes .../static/images/tango/list-add.png | Bin 0 -> 323 bytes .../static/images/tango/list-remove.png | Bin 0 -> 247 bytes .../static/images/tango/pause.png | Bin 0 -> 464 bytes .../images/tango/preferences-system.png | Bin 0 -> 611 bytes .../static/images/tango/process-stop.png | Bin 0 -> 820 bytes .../static/images/tango/queue-down.png | Bin 0 -> 683 bytes .../static/images/tango/queue-up.png | Bin 0 -> 652 bytes .../static/images/tango/start.png | Bin 0 -> 660 bytes .../webui_plugin/static/images/tango/stop.png | Bin 0 -> 429 bytes .../static/images/tango/system-log-out.png | Bin 0 -> 799 bytes .../webui_plugin/static/images/tango/up.png | Bin 0 -> 592 bytes .../static/images/tango/user-trash.png | Bin 0 -> 655 bytes .../static/images/tango/view-refresh.png | Bin 0 -> 912 bytes .../webui_plugin/static/simple_site_style.css | 91 ++ .../templates/advanced/header.html | 28 + .../templates/advanced/index.html | 147 +++ .../templates/advanced/part_categories.html | 37 + .../templates/advanced/part_stats.html | 36 + .../templates/advanced/part_tb_button.html | 35 + .../templates/advanced/static/advanced.css | 263 ++++ .../templates/advanced/static/deluge.js | 148 +++ .../advanced/static/scrolling_table.css | 106 ++ .../advanced/torrent_info_inner.html | 15 + .../webui_plugin/templates/deluge/about.html | 49 + .../webui_plugin/templates/deluge/authors.txt | 5 + .../webui_plugin/templates/deluge/config.html | 10 + .../webui_plugin/templates/deluge/error.html | 6 + .../webui_plugin/templates/deluge/footer.html | 6 + .../webui_plugin/templates/deluge/header.html | 23 + .../webui_plugin/templates/deluge/index.html | 61 + .../webui_plugin/templates/deluge/login.html | 25 + .../templates/deluge/part_button.html | 26 + .../templates/deluge/part_stats.html | 35 + .../templates/deluge/refresh_form.html | 11 + .../templates/deluge/sort_column_head.html | 12 + .../templates/deluge/tab_meta.html | 85 ++ .../templates/deluge/torrent_add.html | 34 + .../templates/deluge/torrent_delete.html | 31 + .../templates/deluge/torrent_info.html | 50 + .../templates/hacking-templates.txt | 39 + .../ui/webui/webui_plugin/tests/test_all.py | 376 ++++++ deluge/ui/webui/webui_plugin/utils.py | 267 ++++ deluge/ui/webui/webui_plugin/version | 1 + .../ui/webui/webui_plugin/webserver_common.py | 209 ++++ setup.py | 13 +- 100 files changed, 11463 insertions(+), 5 deletions(-) create mode 100644 deluge/ui/webui/__init__.py create mode 100644 deluge/ui/webui/webui.py create mode 100644 deluge/ui/webui/webui_plugin/LICENSE create mode 100644 deluge/ui/webui/webui_plugin/TODO create mode 100644 deluge/ui/webui/webui_plugin/__init__.py create mode 100644 deluge/ui/webui/webui_plugin/dbus_interface.py create mode 100644 deluge/ui/webui/webui_plugin/debugerror.py create mode 100644 deluge/ui/webui/webui_plugin/deluge_webserver.py create mode 100644 deluge/ui/webui/webui_plugin/json_api.py create mode 100644 deluge/ui/webui/webui_plugin/lib/__init__.py create mode 100644 deluge/ui/webui/webui_plugin/lib/gtk_cherrypy_wsgiserver.py create mode 100644 deluge/ui/webui/webui_plugin/lib/pythonize.py create mode 100644 deluge/ui/webui/webui_plugin/lib/readme.txt create mode 100644 deluge/ui/webui/webui_plugin/lib/static_handler.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/Dependency-not-really part of webui.txt create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/__init__.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/changes.txt create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/cheetah.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/db.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/debugerror.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/form.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/http.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/httpserver.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/net.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/request.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/template.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/utils.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/webapi.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/wsgi.py create mode 100644 deluge/ui/webui/webui_plugin/lib/webpy022/wsgiserver/__init__.py create mode 100644 deluge/ui/webui/webui_plugin/page_decorators.py create mode 100644 deluge/ui/webui/webui_plugin/pages.py create mode 100644 deluge/ui/webui/webui_plugin/render.py create mode 100644 deluge/ui/webui/webui_plugin/revno create mode 100755 deluge/ui/webui/webui_plugin/run_webserver create mode 100755 deluge/ui/webui/webui_plugin/run_webserver06 create mode 100755 deluge/ui/webui/webui_plugin/scripts/add_torrent_to_deluge_webui create mode 100644 deluge/ui/webui/webui_plugin/scripts/add_torrents_to_deluge.user.js create mode 100755 deluge/ui/webui/webui_plugin/scripts/build_webui_tarball.sh create mode 100644 deluge/ui/webui/webui_plugin/scripts/curl-example create mode 100644 deluge/ui/webui/webui_plugin/scripts/extract_template_strings.py create mode 100644 deluge/ui/webui/webui_plugin/scripts/template_strings.py create mode 100644 deluge/ui/webui/webui_plugin/ssl/deluge.key create mode 100644 deluge/ui/webui/webui_plugin/ssl/deluge.pem create mode 100644 deluge/ui/webui/webui_plugin/static/images/deluge_icon.gif create mode 100644 deluge/ui/webui/webui_plugin/static/images/downloading16.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/inactive16.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/seeding16.png create mode 100755 deluge/ui/webui/webui_plugin/static/images/simple_bg.jpg create mode 100755 deluge/ui/webui/webui_plugin/static/images/simple_line.jpg create mode 100755 deluge/ui/webui/webui_plugin/static/images/simple_logo.jpg create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/details.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/down.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/list-add.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/list-remove.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/pause.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/preferences-system.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/process-stop.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/queue-down.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/queue-up.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/start.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/stop.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/system-log-out.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/up.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/user-trash.png create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/view-refresh.png create mode 100755 deluge/ui/webui/webui_plugin/static/simple_site_style.css create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/header.html create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/index.html create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/part_categories.html create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/part_tb_button.html create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/static/deluge.js create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/static/scrolling_table.css create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/about.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/authors.txt create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/config.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/error.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/footer.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/header.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/index.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/login.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/part_button.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/refresh_form.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/sort_column_head.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/torrent_delete.html create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html create mode 100644 deluge/ui/webui/webui_plugin/templates/hacking-templates.txt create mode 100644 deluge/ui/webui/webui_plugin/tests/test_all.py create mode 100644 deluge/ui/webui/webui_plugin/utils.py create mode 100644 deluge/ui/webui/webui_plugin/version create mode 100644 deluge/ui/webui/webui_plugin/webserver_common.py diff --git a/deluge/main.py b/deluge/main.py index 3709eb729..d74c61878 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -47,6 +47,9 @@ def start_ui(): parser = OptionParser(usage="%prog [options] [actions]", version=deluge.common.get_version()) + parser.add_option("-u", "--ui", dest="ui", + help="The UI that you wish to launch", action="store", type="str") + # Get the options and args from the OptionParser (options, args) = parser.parse_args() @@ -58,7 +61,7 @@ def start_ui(): from deluge.ui.ui import UI log.info("Starting ui..") - UI(args) + UI(options, args) def start_daemon(): """Entry point for daemon script""" diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 32b768ead..6612ae1ed 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -40,11 +40,23 @@ DEFAULT_PREFS = { } class UI: - def __init__(self, args): + def __init__(self, options, args): log.debug("UI init..") - self.config = ConfigManager("ui.conf", DEFAULT_PREFS) + config = ConfigManager("ui.conf", DEFAULT_PREFS) - if self.config["selected_ui"] == "gtk": + if options.ui != None: + config["selected_ui"] = options.ui + + selected_ui = config["selected_ui"] + config.save() + del config + + if selected_ui == "gtk": log.info("Starting GtkUI..") from deluge.ui.gtkui.gtkui import GtkUI ui = GtkUI(args) + elif selected_ui == "web": + log.info("Starting WebUI..") + from deluge.ui.webui.webui import WebUI + ui = WebUI(args) + diff --git a/deluge/ui/webui/__init__.py b/deluge/ui/webui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/webui/webui.py b/deluge/ui/webui/webui.py new file mode 100644 index 000000000..c8c13750f --- /dev/null +++ b/deluge/ui/webui/webui.py @@ -0,0 +1,41 @@ +# +# webui.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +class WebUI: + def __init__(self, args): + from webui_plugin.webserver_common import ws + ws.init_06(uri="http://localhost:58846") + + import webui_plugin.deluge_webserver + webui_plugin.deluge_webserver.run() + diff --git a/deluge/ui/webui/webui_plugin/LICENSE b/deluge/ui/webui/webui_plugin/LICENSE new file mode 100644 index 000000000..4856598ea --- /dev/null +++ b/deluge/ui/webui/webui_plugin/LICENSE @@ -0,0 +1,350 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the OpenSSL + library. + You must obey the GNU General Public License in all respects for all of + the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete + this exception statement from your version. If you delete this exception + statement from all source files in the program, then also delete it here. diff --git a/deluge/ui/webui/webui_plugin/TODO b/deluge/ui/webui/webui_plugin/TODO new file mode 100644 index 000000000..e0944ed43 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/TODO @@ -0,0 +1,20 @@ +0.5.7 +SSL +torrent/add http-post for private sites +rename reannounce->update-tracker. +queued displays as seeding/downloading + + +0.5.7 advanced layout +fonts +fix auto-refresh-layout +buttons +hide 0.0 kbps like in gtk-ui +update-tracker. + +0.6 +prepare for cat: + filters on status (prepare for cat) + filters on tracker +categories +greasemonkey : private sites. \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/__init__.py b/deluge/ui/webui/webui_plugin/__init__.py new file mode 100644 index 000000000..1807ac222 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/__init__.py @@ -0,0 +1,287 @@ +# -*- coding: utf-8 -*- +# +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception + +plugin_name = "Web User Interface" +plugin_author = "Martijn Voncken" +plugin_version = "rev." +plugin_description = """A Web based User Interface + +Firefox greasemonkey script: http://userscripts.org/scripts/show/12639 + +Remotely add a file: "curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add" + +Advanced template is only tested on firefox and garanteed not to work in IE6 + +ssl keys are located in WebUi/ssl/ + +Other contributors: +*somedude : template enhancements. +*markybob : stability : synced with his changes in deluge-svn. +""" + +import deluge.common +try: + import deluge.pref + from deluge.dialogs import show_popup_warning + from webserver_common import ws +except ImportError: + print 'WebUi:not imported as a plugin' + + + +try: + from dbus_interface import get_dbus_manager +except: + print 'error importing dbus-interface!' + pass #for unit-test. + +import time + +import gtk +import os +from subprocess import Popen +from md5 import md5 +from threading import Thread +import random +random.seed() + +try: + plugin_version += open(os.path.join(os.path.dirname(__file__),'revno')).read() +except: + plugin_version = "No Version" +try: + plugin_description += ( + open(os.path.join(os.path.dirname(__file__),'version')).read()) +except: + plugin_description = "No Version" + +def deluge_init(deluge_path): + global path + path = deluge_path + +def enable(core, interface): + global path + return plugin_WebUi(path, core, interface) + +class plugin_WebUi(object): + def __init__(self, path, deluge_core, deluge_interface): + self.path = path + self.core = deluge_core + self.interface = deluge_interface + self.proc = None + self.web_server = None + if not deluge.common.windows_check(): + import commands + status = commands.getstatusoutput( + 'ps x |grep -v grep |grep run_webserver') + if status[0] == 0: + os.kill(int(status[1].split()[0]), 9) + time.sleep(1) #safe time to wait for kill to finish. + self.config_file = deluge.common.CONFIG_DIR + "/webui.conf" + self.config = deluge.pref.Preferences(self.config_file, False) + try: + self.config.load() + except IOError: + # File does not exist + pass + + if not self.config.get('port'): #ugly way to detect new config file. + #set default values: + self.config.set("port", 8112) + self.config.set("button_style", 2) + self.config.set("auto_refresh", False) + self.config.set("auto_refresh_secs", 4) + self.config.set("template", "deluge") + self.config.save(self.config_file) + + if not self.config.get("pwd_salt"): + self.config.set("pwd_salt", "invalid") + self.config.set("pwd_md5", "invalid") + + if self.config.get("cache_templates") == None: + self.config.set("cache_templates", True) + + if deluge.common.windows_check(): + self.config.set("run_in_thread", True) + else: + self.config.set("run_in_thread", False) + + if self.config.get("use_https") == None: + self.config.set("use_https", False) + + self.dbus_manager = get_dbus_manager(deluge_core, deluge_interface, + self.config, self.config_file) + + self.start_server() + + def unload(self): + print 'WebUI:unload..' + self.kill_server() + + def update(self): + pass + + ## This will be only called if your plugin is configurable + def configure(self,parent_dialog): + d = ConfigDialog(self.config, self, parent_dialog) + if d.run() == gtk.RESPONSE_OK: + d.save_config() + d.destroy() + + def start_server(self): + self.kill_server() + + if self.config.get("run_in_thread"): + print 'Start Webui(inside gtk)..' + ws.init_gtk_05() #reload changed config. + from deluge_webserver import WebServer #only import in threaded mode + + + self.web_server = WebServer() + self.web_server.start_gtk() + + else: + print 'Start Webui(in process)..' + server_bin = os.path.dirname(__file__) + '/run_webserver' + self.proc = Popen((server_bin,'env=0.5')) + + def kill_server(self): + if self.web_server: + print "webserver: stop" + self.web_server.stop_gtk() + self.web_server = None + if self.proc: + print "webserver: kill %i" % self.proc.pid + os.system("kill -9 %i" % self.proc.pid) + time.sleep(1) #safe time to wait for kill to finish. + self.proc = None + + def __del__(self): + self.kill_server() + +class ConfigDialog(gtk.Dialog): + """ + sorry, can't get used to gui builders. + from what I read glade is better, but i dont want to invest time in them. + """ + def __init__(self, config, plugin, parent): + gtk.Dialog.__init__(self ,parent=parent) + self.config = config + self.plugin = plugin + self.vb = gtk.VBox() + self.set_title(_("WebUi Config")) + + template_path = os.path.join(os.path.dirname(__file__), 'templates') + self.templates = [dirname for dirname + in os.listdir(template_path) + if os.path.isdir(os.path.join(template_path, dirname)) + and not dirname.startswith('.')] + + self.port = self.add_widget(_('Port Number'), gtk.SpinButton()) + self.pwd1 = self.add_widget(_('New Password'), gtk.Entry()) + self.pwd2 = self.add_widget(_('New Password(confirm)'), gtk.Entry()) + self.template = self.add_widget(_('Template'), gtk.combo_box_new_text()) + self.button_style = self.add_widget(_('Button Style'), + gtk.combo_box_new_text()) + self.cache_templates = self.add_widget(_('Cache Templates'), + gtk.CheckButton()) + self.use_https = self.add_widget(_('https://'), + gtk.CheckButton()) + + #self.share_downloads = self.add_widget(_('Share Download Directory'), + # gtk.CheckButton()) + + self.port.set_range(80, 65536) + self.port.set_increments(1, 10) + self.pwd1.set_visibility(False) + self.pwd2.set_visibility(False) + + for item in self.templates: + self.template.append_text(item) + + if not self.config.get("template") in self.templates: + self.config.set("template","deluge") + + for item in [_('Text and image'), _('Image Only'), _('Text Only')]: + self.button_style.append_text(item) + if self.config.get("button_style") == None: + self.config.set("button_style", 2) + + self.port.set_value(int(self.config.get("port"))) + self.template.set_active( + self.templates.index(self.config.get("template"))) + self.button_style.set_active(self.config.get("button_style")) + #self.share_downloads.set_active( + # bool(self.config.get("share_downloads"))) + + self.cache_templates.set_active(self.config.get("cache_templates")) + self.use_https.set_active(self.config.get("use_https")) + + self.vbox.pack_start(self.vb, True, True, 0) + self.vb.show_all() + + self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL + ,gtk.STOCK_OK, gtk.RESPONSE_OK) + + def add_widget(self,label,w=None): + hb = gtk.HBox() + lbl = gtk.Label(label) + lbl.set_size_request(200,20) + hb.pack_start(lbl,False,False, 0) + hb.pack_start(w,True,True, 0) + + self.vb.pack_start(hb,False,False, 0) + return w + self.add_buttons(dgtk.STOCK_CLOSE, dgtk.RESPONSE_CLOSE) + + def save_config(self): + if self.pwd1.get_text() > '': + if self.pwd1.get_text() <> self.pwd2.get_text(): + show_popup_warning(self,_("Confirmed Password <> New Password\n" + + "Password was not changed")) + else: + sm = md5() + sm.update(str(random.getrandbits(5000))) + salt = sm.digest() + self.config.set("pwd_salt", salt) + # + m = md5() + m.update(salt) + m.update(unicode(self.pwd1.get_text())) + self.config.set("pwd_md5", m.digest()) + + self.config.set("port", int(self.port.get_value())) + self.config.set("template", self.template.get_active_text()) + self.config.set("button_style", self.button_style.get_active()) + self.config.set("cache_templates", self.cache_templates.get_active()) + self.config.set("use_https", self.use_https.get_active()) + #self.config.set("share_downloads", self.share_downloads.get_active()) + self.config.save(self.plugin.config_file) + self.plugin.start_server() #restarts server diff --git a/deluge/ui/webui/webui_plugin/dbus_interface.py b/deluge/ui/webui/webui_plugin/dbus_interface.py new file mode 100644 index 000000000..4743b77a0 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/dbus_interface.py @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- +# Dbus Ipc for experimental web interface +# +# dbus_interface.py +# +# Copyright (C) Martijn Voncken 2007 +# Contains copy and pasted code from other parts of deluge,see deluge AUTHORS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import os +import gtk +import dbus +import deluge.common as common +from lib.pythonize import pythonize +import base64 +import random +random.seed() + +dbus_interface="org.deluge_torrent.dbusplugin" +dbus_service="/org/deluge_torrent/DelugeDbusPlugin" + +dbus_manager = None +def get_dbus_manager(*args): + #another way to make a singleton. + global dbus_manager + if not dbus_manager: + dbus_manager = DbusManager(*args) + return dbus_manager + +class DbusManager(dbus.service.Object): + def __init__(self, core, interface,config,config_file): + self.core = core + self.interface = interface + self.config = config + self.config_file = config_file + self.bus = dbus.SessionBus() + bus_name = dbus.service.BusName(dbus_interface,bus=self.bus) + dbus.service.Object.__init__(self, bus_name,dbus_service) + + # + #todo : add: get_interface_version in=i,get_config_value in=s out=s + # + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="",out_signature="as") + def get_session_state(self): + """Returns a list of torrent_ids in the session. + same as 0.6, but returns type "as" instead of a pickle + """ + torrent_list = [str(key) for key in self.core.unique_IDs] + return torrent_list + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="sas",out_signature="a{sv}") + def get_torrent_status(self, torrent_id, keys): + """return torrent metadata of a single torrent as a dict + 0.6 returns a pickle, this returns a dbus-type. + +added some more values to the dict + """ + + torrent_id = int(torrent_id) + # Convert the array of strings to a python list of strings + nkeys = [str(key) for key in keys] + + state = self.core.get_torrent_state(torrent_id) + torrent = self.core.unique_IDs[torrent_id] + + status = { + "name": state["name"], + "total_size": state["total_size"], + "num_pieces": state["num_pieces"], + "state": state['state'], + "user_paused": self.core.is_user_paused(torrent_id), + "paused":state['is_paused'], + "progress": int(state["progress"] * 100), + "next_announce": state["next_announce"], + "total_payload_download":state["total_payload_download"], + "total_payload_upload": state["total_payload_upload"], + "download_payload_rate": state["download_rate"], + "upload_payload_rate": state["upload_rate"], + "num_peers": state["num_peers"], + "num_seeds": state["num_seeds"], + "total_wanted": state["total_wanted"], + "eta": common.estimate_eta(state), + "ratio": self.interface.manager.calc_ratio(torrent_id,state), + #non 0.6 values follow here: + "tracker_status": state.get("tracker_status","?"), + "uploaded_memory": torrent.uploaded_memory, + } + #more non 0.6 values + for key in ["total_seeds", "total_peers","is_seed", "total_done", + "total_download", "total_upload" + #, "download_rate","upload_rate" + , "num_files", "piece_length", "distributed_copies" + ,"next_announce","tracker","queue_pos"]: + status[key] = state[key] + + #print 'all_keys:',sorted(status.keys()) + + status_subset = {} + for key in keys: + if key in status: + status_subset[key] = status[key] + else: + print 'mbus error,no key named:', key + return status_subset + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="as",out_signature="") + def pause_torrent(self, torrents): + """same as 0.6 interface""" + for torrent_id in torrents: + torrent_id = int(torrent_id) + self.core.set_user_pause(torrent_id, True) + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="as", out_signature="") + def resume_torrent(self, torrents): + """same as 0.6 interface""" + for torrent_id in torrents: + torrent_id = int(torrent_id) + self.core.set_user_pause(torrent_id, False) + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="as", out_signature="") + def force_reannounce(self, torrents): + """same as 0.6 interface""" + for torrent_id in torrents: + torrent_id = int(torrent_id) + self.core.update_tracker(torrent_id) + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="asbb", out_signature="") + def remove_torrent(self, torrent_ids, data_also, torrent_also): + """remove a torrent,and optionally data and torrent + additions compared to 0.6 interface: (data_also, torrent_also) + """ + for torrent_id in torrent_ids: + torrent_id = int(torrent_id) + self.core.remove_torrent(torrent_id, bool(data_also) + ,bool( torrent_also)) + + #this should not be needed: + gtk.gdk.threads_enter() + try: + self.interface.torrent_model_remove(torrent_id) + except: + pass + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="s", out_signature="b") + def add_torrent_url(self, url): + filename = fetch_url(url) + self._add_torrent(filename) + return True + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="s", out_signature="b") + def queue_up(self, torrent_id): + self.core.queue_up(int(torrent_id)) + return True + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="s", out_signature="b") + def queue_down(self, torrent_id): + self.core.queue_down(int(torrent_id)) + return True + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="ss", out_signature="b") + def add_torrent_filecontent(self, name, filecontent_b64): + """not available in deluge 0.6 interface""" + #name = fillename without directory + name = name.replace('\\','/') + name = 'deluge_' + str(random.random()) + '_' + name.split('/')[-1] + filename = os.path.join(self.core.config.get("default_download_path"), name) + + filecontent = base64.b64decode(filecontent_b64) + f = open(filename,"wb") #no with statement, that's py 2.5+ + f.write(filecontent) + f.close() + print 'write:',filename + self._add_torrent(filename) + return True + + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="a{sv}") + def get_config(self): + return self.core.config.mapping + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="s", out_signature="v") + def get_config_value(self,key): + return self.core.config.mapping[pythonize(key)] #ugly! + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="a{sv}", out_signature="") + def set_config(self, config): + """Set the config with values from dictionary""" + config = deluge.common.pythonize(config) + # Load all the values into the configuration + for key in self.core.config.keys(): + self.core.config[key] = config[key] + self.core.apply_prefs() + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="v") + def get_download_rate(self): + return self.core.get_state()['download_rate'] + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="v") + def get_upload_rate(self): + return self.core.get_state()['upload_rate'] + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="v") + def get_num_connections(self): + core_state = self.core.get_state() + return core_state['num_connections'] + + #internal + def _add_torrent(self, filename): + filename = unicode(filename) + target = self.core.config.get("default_download_path") + + torrent_id = self.core.add_torrent(filename, target, + self.interface.config.get("use_compact_storage")) + + #update gtk-ui This should not be needed!! + gtk.gdk.threads_enter() + try: + self.interface.torrent_model_append(torrent_id) + except: + pass + #finally is 2.5 only! + gtk.gdk.threads_leave() + + return True + +def fetch_url(url): + import urllib + + try: + filename, headers = urllib.urlretrieve(url) + except IOError: + raise Exception( "Network error while trying to fetch torrent from %s" + % url) + else: + if (filename.endswith(".torrent") or + headers["content-type"]=="application/x-bittorrent"): + return filename + else: + raise Exception("URL doesn't appear to be a valid torrent file:%s" + % url) + + return None diff --git a/deluge/ui/webui/webui_plugin/debugerror.py b/deluge/ui/webui/webui_plugin/debugerror.py new file mode 100644 index 000000000..259dcac72 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/debugerror.py @@ -0,0 +1,373 @@ +""" +pretty debug errors +(part of web.py) + +adapted from Django +Copyright (c) 2005, the Lawrence Journal-World +Used under the modified BSD license: +http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +""" + +__all__ = ["debugerror", "djangoerror"] + +import sys, urlparse, pprint +from lib.webpy022.net import websafe +from lib.webpy022.template import Template +import lib.webpy022.webapi as web +import webserver_common as ws +from traceback import format_tb + +import os, os.path +whereami = os.path.join(os.getcwd(), __file__) +whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1]) +djangoerror_t = """\ +$def with (exception_type, exception_value, frames, exception_message, version_info, tback_txt) + + + + + + $exception_type at $ctx.path + + + + + +
+

$exception_type : $exception_value

+
+
+

+ + Oops, Deluge Broke :-( , You might have found a bug, or you did something really stupid ;-). +
If the error persists :
+ Read the Faq.
+ Try downloading the latest version at + deluge-torrent.org +
Visit the forum + or the buglist for more info. +

+
+ + +
+Paste the contents of this text-box when you are asked for a traceback:
+ + +
+Use a pastebin on IRC!
+ + +
+ + +
+ +
+$if ctx.output or ctx.headers: +

Response so far

+

HEADERS

+

+ $for kv in ctx.headers: + $kv[0]: $kv[1]
+ $else: + [no headers] +

+ +

BODY

+

+ $ctx.output +

+ +

Request information

+ +

INPUT

+$:dicttable(web.input()) + + +$:dicttable(web.cookies()) + +

META

+$ newctx = [] +$# ) and (k not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute']): +$for k, v in ctx.iteritems(): + $if not k.startswith('_') and (k in x): + $newctx.append(kv) +$:dicttable(dict(newctx)) + +

ENVIRONMENT

+$:dicttable(ctx.env) +
+ + + +""" + +dicttable_t = r"""$def with (d, kls='req', id=None) +$if d: + + + $ temp = d.items() + $temp.sort() + $for kv in temp: + + +
VariableValue
$kv[0]
$prettify(kv[1])
+$else: +

No data.

+""" + +dicttable_r = Template(dicttable_t, filter=websafe) +djangoerror_r = Template(djangoerror_t, filter=websafe) + +def djangoerror(): + def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = \ + [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = \ + [line.strip('\n') for line in source[lineno + 1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + + exception_type, exception_value, tback = sys.exc_info() + + exception_message = 'Error' + try: + exception_message = exception_value.message + except AttributeError: + exception_message = 'no message' + exception_type = exception_type.__name__ + + version_info = ( + "WebUi : rev." + ws.REVNO + + "Python : " + str(sys.version) + ) + try: + import dbus + version_info += '\ndbus:' + str(dbus.__version__) + except: + pass + + tback_txt = ''.join(format_tb(tback)) + + + frames = [] + while tback is not None: + filename = tback.tb_frame.f_code.co_filename + function = tback.tb_frame.f_code.co_name + lineno = tback.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = \ + _get_lines_from_file(filename, lineno, 7) + frames.append(web.storage({ + 'tback': tback, + 'filename': filename, + 'function': function, + 'lineno': lineno, + 'vars': tback.tb_frame.f_locals, + 'id': id(tback), + 'pre_context': pre_context, + 'context_line': context_line, + 'post_context': post_context, + 'pre_context_lineno': pre_context_lineno, + })) + tback = tback.tb_next + frames.reverse() + urljoin = urlparse.urljoin + def prettify(x): + try: + out = pprint.pformat(x) + except Exception, e: + out = '[could not display: <' + e.__class__.__name__ + \ + ': '+str(e)+'>]' + return out + dt = dicttable_r + dt.globals = {'prettify': prettify} + t = djangoerror_r + t.globals = {'ctx': web.ctx, 'web':web, 'dicttable':dt, 'dict':dict, 'str':str} + return t(exception_type, exception_value, frames, exception_message, version_info, tback_txt) + +def deluge_debugerror(): + """ + A replacement for `internalerror` that presents a nice page with lots + of debug information for the programmer. + + (Based on the beautiful 500 page from [Django](http://djangoproject.com/), + designed by [Wilson Miner](http://wilsonminer.com/).) + """ + web.ctx.headers = [ + ('Content-Type', 'text/html') + ] + web.ctx.output = djangoerror() + +if __name__ == "__main__": + urls = ( + '/', 'index' + ) + + class index: + def GET(self): + thisdoesnotexist + + web.internalerror = web.debugerror + web.run(urls) diff --git a/deluge/ui/webui/webui_plugin/deluge_webserver.py b/deluge/ui/webui/webui_plugin/deluge_webserver.py new file mode 100644 index 000000000..2f9d31c2a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/deluge_webserver.py @@ -0,0 +1,62 @@ +# +# webserver_framework.py +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + + +from webserver_common import ws + +from lib.webpy022.request import webpyfunc +from lib.webpy022 import webapi +from lib.gtk_cherrypy_wsgiserver import CherryPyWSGIServer +import os + +def create_webserver(urls, methods): + func = webapi.wsgifunc(webpyfunc(urls, methods, False)) + server_address=("0.0.0.0", int(ws.config.get('port'))) + + server = CherryPyWSGIServer(server_address, func, server_name="localhost") + if ws.config.get('use_https'): + server.ssl_certificate = os.path.join(ws.webui_path,'ssl/deluge.pem') + server.ssl_private_key = os.path.join(ws.webui_path,'ssl/deluge.key') + + print "http://%s:%d/" % server_address + return server + +def WebServer(): + import pages + return create_webserver(pages.urls, pages) + +def run(): + server = WebServer() + try: + server.start() + except KeyboardInterrupt: + server.stop() + diff --git a/deluge/ui/webui/webui_plugin/json_api.py b/deluge/ui/webui/webui_plugin/json_api.py new file mode 100644 index 000000000..92aece07d --- /dev/null +++ b/deluge/ui/webui/webui_plugin/json_api.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# webserver_framework.py +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +""" +json api. +only used for XUL and/or external scripts +it would be possible not to include the python-json dependency. +""" + +from new import instancemethod +from inspect import getargspec +from utils import ws,get_torrent_status,get_category_choosers, get_stats,filter_torrent_state,fsize,fspeed +from page_decorators import remote +from operator import attrgetter +import lib.webpy022 as web +proxy = ws.proxy + +def to_json(obj): + from lib.pythonize import pythonize + obj = pythonize(obj) + try: + import json + return json.write(obj) + except ImportError: + raise ImportError("""Install python-json using your package-manager + http://sourceforge.net/projects/json-py/""") + +class json_api: + """ + eperimental json api + generic proxy for all methods onm self. + """ + illegal_methods = ['shutdown', 'socket', 'xmlrpclib','pickle','os', + 'is_localhost','CoreProxy','connect_on_new_core', 'connect_on_no_core', + 'connected','deluge','GET','POST'] + def __init__(self): + self._add_proxy_methods() + + #extra exposed: + get_torrent_status = get_torrent_status + + @remote + def POST(self,name): + import json + if name.startswith('_'): + raise AttributeError('_ methods are illegal.') + if name in self.illegal_methods: + raise AttributeError('Illegal method.') + if not(hasattr(self,name)): + raise AttributeError('No such method') + + method = getattr(self,name) + vars = web.input(kwargs= None) + ws.log.debug('vars=%s' % vars) + if vars.kwargs: + kwargs = json.read(vars.kwargs) + else: + kwargs = {} + + result = method(**kwargs) + + return "(" + to_json(result) + ")" + + + def list_methods(self): + """ + list all json methods + returns a dict of {methodname:{args:[list of kwargs],doc:'string'},..} + """ + methods = [getattr(self,m) for m in dir(self) + if not m.startswith('_') + and (not m in self.illegal_methods) + and callable(getattr(self,m)) + ] + + return dict([(f.__name__, + {'args':getargspec(f)[0],'doc':(f.__doc__ or '').strip()}) + for f in methods]) + + def _add_proxy_methods(self): + methods = [getattr(proxy,m) for m in dir(proxy) + if not m.startswith('_') + and (not m in self.illegal_methods) + and callable(getattr(proxy,m)) + ] + for m in methods: + setattr(self,m.__name__,m) + + #extra's: + def list_torrents(self): + return [get_torrent_status(torrent_id) + for torrent_id in ws.proxy.get_session_state()] + + def simplify_torrent_status(self, torrent): + """smaller subset and preformatted data for the treelist""" + data = { + "id":torrent.id, + "message":torrent.message, + "name":torrent.name, + "total_size":fsize(torrent.total_size), + "progress":torrent.progress, + "category":torrent.category, + "seeds":"", + "peers":"", + "download_rate":"", + "upload_rate":"", + "eta":"", + "distributed_copies":"", + "ratio":"", + "calc_state_str":torrent.calc_state_str, + "queue_pos":torrent.queue_pos + } + if torrent.total_seeds > 0: + data['seeds'] = "%s (%s)" % (torrent.num_seeds, torrent.total_seeds) + if torrent.total_peers > 0: + data['peers'] = "%s (%s)" % (torrent.num_peers, torrent.total_peers) + if torrent.download_rate > 0: + data['download_rate'] = fspeed(torrent.download_rate) + if torrent.upload_rate > 0: + data['upload_rate'] = fspeed(torrent.upload_rate) + if torrent.eta > 0: + data['eta'] = ("%.3f" % torrent.eta) + if torrent.distributed_copies > 0: + data['distributed_copies'] = "%.3f" % torrent.distributed_copies + if torrent.ratio > 0: + data['ratio'] = "%.3f" % torrent.ratio + return data + + def update_ui(self, filter=None, category=None ,sort='name' ,order='down'): + """ + Combines the most important ui calls into 1 composite call. + xmlhttp requests are expensive,max 2 running at the same time. + and performance over the internet is mostly related to the number + of requests (low ping) + returns : + {torrent_list:[{},..],'categories':[],'filters':'','stats':{}} + """ + torrent_list = self.list_torrents(); + filter_tabs, category_tabs = get_category_choosers(torrent_list) + + + #filter-state + if filter: + torrent_list = filter_torrent_state(torrent_list, filter) + + #filter-cat + if category: + torrent_list = [t for t in torrent_list if t.category == category] + + #sorting + if sort: + torrent_list.sort(key=attrgetter(sort)) + if order == 'up': + torrent_list = reversed(torrent_list) + + torrent_list = [self.simplify_torrent_status(t) for t in torrent_list] + + return { + 'torrent_list':torrent_list, + 'categories':category_tabs, + 'filters':filter_tabs, + 'stats':get_stats() + } + + + +if __name__ == '__main__': + from pprint import pprint + #proxy.set_core_uri('http://localhost:58846') #How to configure this? + j = json_api() + if True: + print 'list-methods:' + methods = j.list_methods() + names = methods.keys() + names.sort() + for name in names: + m = methods[name] + print "%s(%s)\n %s\n" % (name , m['args'] , m['doc']) + + #j.GET('list_torrents') + j.POST('list_torrents') + diff --git a/deluge/ui/webui/webui_plugin/lib/__init__.py b/deluge/ui/webui/webui_plugin/lib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/webui/webui_plugin/lib/gtk_cherrypy_wsgiserver.py b/deluge/ui/webui/webui_plugin/lib/gtk_cherrypy_wsgiserver.py new file mode 100644 index 000000000..ce55b3fa7 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/gtk_cherrypy_wsgiserver.py @@ -0,0 +1,1077 @@ +""" +mvoncken: +Modified this to integrate into the gtk main-loop. +*split start() into start_common(),start,start_gtk(),start() +*add stop_gtk() +*add CherryPy license in comment +---- +Copyright (c) 2004-2007, CherryPy Team (team@cherrypy.org) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +A high-speed, production ready, thread pooled, generic WSGI server. + +Simplest example on how to use this module directly +(without using CherryPy's application machinery): + + from cherrypy import wsgiserver + + def my_crazy_app(environ, start_response): + status = '200 OK' + response_headers = [('Content-type','text/plain')] + start_response(status, response_headers) + return ['Hello world!\n'] + + # Here we set our application to the script_name '/' + wsgi_apps = [('/', my_crazy_app)] + + server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps, + server_name='localhost') + + # Want SSL support? Just set these attributes + # server.ssl_certificate = + # server.ssl_private_key = + + if __name__ == '__main__': + try: + server.start() + except KeyboardInterrupt: + server.stop() + +This won't call the CherryPy engine (application side) at all, only the +WSGI server, which is independant from the rest of CherryPy. Don't +let the name "CherryPyWSGIServer" throw you; the name merely reflects +its origin, not it's coupling. + +The CherryPy WSGI server can serve as many WSGI applications +as you want in one instance: + + wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] + +""" +import base64 +import Queue +import os +import re +quoted_slash = re.compile("(?i)%2F") +import rfc822 +import socket +try: + import cStringIO as StringIO +except ImportError: + import StringIO +import sys +import threading +import time +import traceback +from urllib import unquote +from urlparse import urlparse +try: + import gobject +except ImportError: + pass + +try: + from OpenSSL import SSL + from OpenSSL import crypto +except ImportError: + SSL = None + +import errno +socket_errors_to_ignore = [] +# Not all of these names will be defined for every platform. +for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET", + "EHOSTDOWN", "EHOSTUNREACH", + "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET", + "WSAENETRESET", "WSAETIMEDOUT"): + if _ in dir(errno): + socket_errors_to_ignore.append(getattr(errno, _)) +# de-dupe the list +socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys() +socket_errors_to_ignore.append("timed out") + +comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', + 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', + 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', + 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', + 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', + 'WWW-AUTHENTICATE'] + +class HTTPRequest(object): + """An HTTP Request (and response). + + A single HTTP connection may consist of multiple request/response pairs. + + connection: the HTTP Connection object which spawned this request. + rfile: the 'read' fileobject from the connection's socket + ready: when True, the request has been parsed and is ready to begin + generating the response. When False, signals the calling Connection + that the response should not be generated and the connection should + close. + close_connection: signals the calling Connection that the request + should close. This does not imply an error! The client and/or + server may each request that the connection be closed. + chunked_write: if True, output will be encoded with the "chunked" + transfer-coding. This value is set automatically inside + send_headers. + """ + + def __init__(self, connection): + self.connection = connection + self.rfile = self.connection.rfile + self.sendall = self.connection.sendall + self.environ = connection.environ.copy() + + self.ready = False + self.started_response = False + self.status = "" + self.outheaders = [] + self.sent_headers = False + self.close_connection = False + self.chunked_write = False + + def parse_request(self): + """Parse the next HTTP request start-line and message-headers.""" + # HTTP/1.1 connections are persistent by default. If a client + # requests a page, then idles (leaves the connection open), + # then rfile.readline() will raise socket.error("timed out"). + # Note that it does this based on the value given to settimeout(), + # and doesn't need the client to request or acknowledge the close + # (although your TCP stack might suffer for it: cf Apache's history + # with FIN_WAIT_2). + request_line = self.rfile.readline() + if not request_line: + # Force self.ready = False so the connection will close. + self.ready = False + return + + if request_line == "\r\n": + # RFC 2616 sec 4.1: "...if the server is reading the protocol + # stream at the beginning of a message and receives a CRLF + # first, it should ignore the CRLF." + # But only ignore one leading line! else we enable a DoS. + request_line = self.rfile.readline() + if not request_line: + self.ready = False + return + + server = self.connection.server + environ = self.environ + environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version + + method, path, req_protocol = request_line.strip().split(" ", 2) + environ["REQUEST_METHOD"] = method + + # path may be an abs_path (including "http://host.domain.tld"); + scheme, location, path, params, qs, frag = urlparse(path) + + if frag: + self.simple_response("400 Bad Request", + "Illegal #fragment in Request-URI.") + return + + if scheme: + environ["wsgi.url_scheme"] = scheme + if params: + path = path + ";" + params + + # Unquote the path+params (e.g. "/this%20path" -> "this path"). + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 + # + # But note that "...a URI must be separated into its components + # before the escaped characters within those components can be + # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 + atoms = [unquote(x) for x in quoted_slash.split(path)] + path = "%2F".join(atoms) + + if path == "*": + # This means, of course, that the last wsgi_app (shortest path) + # will always handle a URI of "*". + environ["SCRIPT_NAME"] = "" + environ["PATH_INFO"] = "*" + self.wsgi_app = server.mount_points[-1][1] + else: + for mount_point, wsgi_app in server.mount_points: + # The mount_points list should be sorted by length, descending. + if path.startswith(mount_point + "/") or path == mount_point: + environ["SCRIPT_NAME"] = mount_point + environ["PATH_INFO"] = path[len(mount_point):] + self.wsgi_app = wsgi_app + break + else: + self.simple_response("404 Not Found") + return + + # Note that, like wsgiref and most other WSGI servers, + # we unquote the path but not the query string. + environ["QUERY_STRING"] = qs + + # Compare request and server HTTP protocol versions, in case our + # server does not support the requested protocol. Limit our output + # to min(req, server). We want the following output: + # request server actual written supported response + # protocol protocol response protocol feature set + # a 1.0 1.0 1.0 1.0 + # b 1.0 1.1 1.1 1.0 + # c 1.1 1.0 1.0 1.0 + # d 1.1 1.1 1.1 1.1 + # Notice that, in (b), the response will be "HTTP/1.1" even though + # the client only understands 1.0. RFC 2616 10.5.6 says we should + # only return 505 if the _major_ version is different. + rp = int(req_protocol[5]), int(req_protocol[7]) + sp = int(server.protocol[5]), int(server.protocol[7]) + if sp[0] != rp[0]: + self.simple_response("505 HTTP Version Not Supported") + return + # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. + environ["SERVER_PROTOCOL"] = req_protocol + # set a non-standard environ entry so the WSGI app can know what + # the *real* server protocol is (and what features to support). + # See http://www.faqs.org/rfcs/rfc2145.html. + environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol + self.response_protocol = "HTTP/%s.%s" % min(rp, sp) + + # If the Request-URI was an absoluteURI, use its location atom. + if location: + environ["SERVER_NAME"] = location + + # then all the http headers + try: + self.read_headers() + except ValueError, ex: + self.simple_response("400 Bad Request", repr(ex.args)) + return + + creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1) + environ["AUTH_TYPE"] = creds[0] + if creds[0].lower() == 'basic': + user, pw = base64.decodestring(creds[1]).split(":", 1) + environ["REMOTE_USER"] = user + + # Persistent connection support + if self.response_protocol == "HTTP/1.1": + if environ.get("HTTP_CONNECTION", "") == "close": + self.close_connection = True + else: + # HTTP/1.0 + if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": + self.close_connection = True + + # Transfer-Encoding support + te = None + if self.response_protocol == "HTTP/1.1": + te = environ.get("HTTP_TRANSFER_ENCODING") + if te: + te = [x.strip().lower() for x in te.split(",") if x.strip()] + + read_chunked = False + + if te: + for enc in te: + if enc == "chunked": + read_chunked = True + else: + # Note that, even if we see "chunked", we must reject + # if there is an extension we don't recognize. + self.simple_response("501 Unimplemented") + self.close_connection = True + return + + if read_chunked: + if not self.decode_chunked(): + return + + # From PEP 333: + # "Servers and gateways that implement HTTP 1.1 must provide + # transparent support for HTTP 1.1's "expect/continue" mechanism. + # This may be done in any of several ways: + # 1. Respond to requests containing an Expect: 100-continue request + # with an immediate "100 Continue" response, and proceed normally. + # 2. Proceed with the request normally, but provide the application + # with a wsgi.input stream that will send the "100 Continue" + # response if/when the application first attempts to read from + # the input stream. The read request must then remain blocked + # until the client responds. + # 3. Wait until the client decides that the server does not support + # expect/continue, and sends the request body on its own. + # (This is suboptimal, and is not recommended.) + # + # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, + # but it seems like it would be a big slowdown for such a rare case. + if environ.get("HTTP_EXPECT", "") == "100-continue": + self.simple_response(100) + + self.ready = True + + def read_headers(self): + """Read header lines from the incoming stream.""" + environ = self.environ + + while True: + line = self.rfile.readline() + if not line: + # No more data--illegal end of headers + raise ValueError("Illegal end of headers.") + + if line == '\r\n': + # Normal end of headers + break + + if line[0] in ' \t': + # It's a continuation line. + v = line.strip() + else: + k, v = line.split(":", 1) + k, v = k.strip().upper(), v.strip() + envname = "HTTP_" + k.replace("-", "_") + + if k in comma_separated_headers: + existing = environ.get(envname) + if existing: + v = ", ".join((existing, v)) + environ[envname] = v + + ct = environ.pop("HTTP_CONTENT_TYPE", None) + if ct: + environ["CONTENT_TYPE"] = ct + cl = environ.pop("HTTP_CONTENT_LENGTH", None) + if cl: + environ["CONTENT_LENGTH"] = cl + + def decode_chunked(self): + """Decode the 'chunked' transfer coding.""" + cl = 0 + data = StringIO.StringIO() + while True: + line = self.rfile.readline().strip().split(";", 1) + chunk_size = int(line.pop(0), 16) + if chunk_size <= 0: + break +## if line: chunk_extension = line[0] + cl += chunk_size + data.write(self.rfile.read(chunk_size)) + crlf = self.rfile.read(2) + if crlf != "\r\n": + self.simple_response("400 Bad Request", + "Bad chunked transfer coding " + "(expected '\\r\\n', got %r)" % crlf) + return + + # Grab any trailer headers + self.read_headers() + + data.seek(0) + self.environ["wsgi.input"] = data + self.environ["CONTENT_LENGTH"] = str(cl) or "" + return True + + def respond(self): + """Call the appropriate WSGI app and write its iterable output.""" + response = self.wsgi_app(self.environ, self.start_response) + try: + for chunk in response: + # "The start_response callable must not actually transmit + # the response headers. Instead, it must store them for the + # server or gateway to transmit only after the first + # iteration of the application return value that yields + # a NON-EMPTY string, or upon the application's first + # invocation of the write() callable." (PEP 333) + if chunk: + self.write(chunk) + finally: + if hasattr(response, "close"): + response.close() + if (self.ready and not self.sent_headers + and not self.connection.server.interrupt): + self.sent_headers = True + self.send_headers() + if self.chunked_write: + self.sendall("0\r\n\r\n") + + def simple_response(self, status, msg=""): + """Write a simple response back to the client.""" + status = str(status) + buf = ["%s %s\r\n" % (self.connection.server.protocol, status), + "Content-Length: %s\r\n" % len(msg)] + + if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': + # Request Entity Too Large + self.close_connection = True + buf.append("Connection: close\r\n") + + buf.append("\r\n") + if msg: + buf.append(msg) + self.sendall("".join(buf)) + + def start_response(self, status, headers, exc_info = None): + """WSGI callable to begin the HTTP response.""" + if self.started_response: + if not exc_info: + raise AssertionError("WSGI start_response called a second " + "time with no exc_info.") + else: + try: + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None + self.started_response = True + self.status = status + self.outheaders.extend(headers) + return self.write + + def write(self, chunk): + """WSGI callable to write unbuffered data to the client. + + This method is also used internally by start_response (to write + data from the iterable returned by the WSGI application). + """ + if not self.started_response: + raise AssertionError("WSGI write called before start_response.") + + if not self.sent_headers: + self.sent_headers = True + self.send_headers() + + if self.chunked_write and chunk: + buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] + self.sendall("".join(buf)) + else: + self.sendall(chunk) + + def send_headers(self): + """Assert, process, and send the HTTP response message-headers.""" + hkeys = [key.lower() for key, value in self.outheaders] + status = int(self.status[:3]) + + if status == 413: + # Request Entity Too Large. Close conn to avoid garbage. + self.close_connection = True + elif "content-length" not in hkeys: + # "All 1xx (informational), 204 (no content), + # and 304 (not modified) responses MUST NOT + # include a message-body." So no point chunking. + if status < 200 or status in (204, 205, 304): + pass + else: + if self.response_protocol == 'HTTP/1.1': + # Use the chunked transfer-coding + self.chunked_write = True + self.outheaders.append(("Transfer-Encoding", "chunked")) + else: + # Closing the conn is the only way to determine len. + self.close_connection = True + + if "connection" not in hkeys: + if self.response_protocol == 'HTTP/1.1': + if self.close_connection: + self.outheaders.append(("Connection", "close")) + else: + if not self.close_connection: + self.outheaders.append(("Connection", "Keep-Alive")) + + if "date" not in hkeys: + self.outheaders.append(("Date", rfc822.formatdate())) + + server = self.connection.server + + if "server" not in hkeys: + self.outheaders.append(("Server", server.version)) + + buf = [server.protocol, " ", self.status, "\r\n"] + try: + buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] + except TypeError: + if not isinstance(k, str): + raise TypeError("WSGI response header key %r is not a string.") + if not isinstance(v, str): + raise TypeError("WSGI response header value %r is not a string.") + else: + raise + buf.append("\r\n") + self.sendall("".join(buf)) + + +class NoSSLError(Exception): + """Exception raised when a client speaks HTTP to an HTTPS socket.""" + pass + + +def _ssl_wrap_method(method, is_reader=False): + """Wrap the given method with SSL error-trapping. + + is_reader: if False (the default), EOF errors will be raised. + If True, EOF errors will return "" (to emulate normal sockets). + """ + def ssl_method_wrapper(self, *args, **kwargs): +## print (id(self), method, args, kwargs) + start = time.time() + while True: + try: + return method(self, *args, **kwargs) + except (SSL.WantReadError, SSL.WantWriteError): + # Sleep and try again. This is dangerous, because it means + # the rest of the stack has no way of differentiating + # between a "new handshake" error and "client dropped". + # Note this isn't an endless loop: there's a timeout below. + time.sleep(self.ssl_retry) + except SSL.SysCallError, e: + if is_reader and e.args == (-1, 'Unexpected EOF'): + return "" + + errno = e.args[0] + if is_reader and errno in socket_errors_to_ignore: + return "" + raise socket.error(errno) + except SSL.Error, e: + if is_reader and e.args == (-1, 'Unexpected EOF'): + return "" + + thirdarg = None + try: + thirdarg = e.args[0][0][2] + except IndexError: + pass + + if is_reader and thirdarg == 'ssl handshake failure': + return "" + if thirdarg == 'http request': + # The client is talking HTTP to an HTTPS server. + raise NoSSLError() + raise + if time.time() - start > self.ssl_timeout: + raise socket.timeout("timed out") + return ssl_method_wrapper + +class SSL_fileobject(socket._fileobject): + """Faux file object attached to a socket object.""" + + ssl_timeout = 3 + ssl_retry = .01 + + close = _ssl_wrap_method(socket._fileobject.close) + flush = _ssl_wrap_method(socket._fileobject.flush) + write = _ssl_wrap_method(socket._fileobject.write) + writelines = _ssl_wrap_method(socket._fileobject.writelines) + read = _ssl_wrap_method(socket._fileobject.read, is_reader=True) + readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True) + readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True) + + +class HTTPConnection(object): + """An HTTP connection (active socket). + + socket: the raw socket object (usually TCP) for this connection. + addr: the "bind address" for the remote end of the socket. + For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). + For UNIX domain sockets, this will be a string. + server: the HTTP Server for this Connection. Usually, the server + object possesses a passive (server) socket which spawns multiple, + active (client) sockets, one for each connection. + + environ: a WSGI environ template. This will be copied for each request. + rfile: a fileobject for reading from the socket. + sendall: a function for writing (+ flush) to the socket. + """ + + rbufsize = -1 + RequestHandlerClass = HTTPRequest + environ = {"wsgi.version": (1, 0), + "wsgi.url_scheme": "http", + "wsgi.multithread": True, + "wsgi.multiprocess": False, + "wsgi.run_once": False, + "wsgi.errors": sys.stderr, + } + + def __init__(self, sock, addr, server): + self.socket = sock + self.addr = addr + self.server = server + + # Copy the class environ into self. + self.environ = self.environ.copy() + + if SSL and isinstance(sock, SSL.ConnectionType): + timeout = sock.gettimeout() + self.rfile = SSL_fileobject(sock, "r", self.rbufsize) + self.rfile.ssl_timeout = timeout + self.sendall = _ssl_wrap_method(sock.sendall) + self.environ["wsgi.url_scheme"] = "https" + self.environ["HTTPS"] = "on" + sslenv = getattr(server, "ssl_environ", None) + if sslenv: + self.environ.update(sslenv) + else: + self.rfile = sock.makefile("rb", self.rbufsize) + self.sendall = sock.sendall + + self.environ.update({"wsgi.input": self.rfile, + "SERVER_NAME": self.server.server_name, + }) + + if isinstance(self.server.bind_addr, basestring): + # AF_UNIX. This isn't really allowed by WSGI, which doesn't + # address unix domain sockets. But it's better than nothing. + self.environ["SERVER_PORT"] = "" + else: + self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) + # optional values + # Until we do DNS lookups, omit REMOTE_HOST + self.environ["REMOTE_ADDR"] = self.addr[0] + self.environ["REMOTE_PORT"] = str(self.addr[1]) + + def communicate(self): + """Read each request and respond appropriately.""" + try: + while True: + # (re)set req to None so that if something goes wrong in + # the RequestHandlerClass constructor, the error doesn't + # get written to the previous request. + req = None + req = self.RequestHandlerClass(self) + # This order of operations should guarantee correct pipelining. + req.parse_request() + if not req.ready: + return + req.respond() + if req.close_connection: + return + except socket.error, e: + errno = e.args[0] + if errno not in socket_errors_to_ignore: + if req: + req.simple_response("500 Internal Server Error", + format_exc()) + return + except (KeyboardInterrupt, SystemExit): + raise + except NoSSLError: + # Unwrap our sendall + req.sendall = self.socket._sock.sendall + req.simple_response("400 Bad Request", + "The client sent a plain HTTP request, but " + "this server only speaks HTTPS on this port.") + except: + if req: + req.simple_response("500 Internal Server Error", format_exc()) + + def close(self): + """Close the socket underlying this connection.""" + self.rfile.close() + self.socket.close() + + +def format_exc(limit=None): + """Like print_exc() but return a string. Backport for Python 2.3.""" + try: + etype, value, tb = sys.exc_info() + return ''.join(traceback.format_exception(etype, value, tb, limit)) + finally: + etype = value = tb = None + + +_SHUTDOWNREQUEST = None + +class WorkerThread(threading.Thread): + """Thread which continuously polls a Queue for Connection objects. + + server: the HTTP Server which spawned this thread, and which owns the + Queue and is placing active connections into it. + ready: a simple flag for the calling server to know when this thread + has begun polling the Queue. + + Due to the timing issues of polling a Queue, a WorkerThread does not + check its own 'ready' flag after it has started. To stop the thread, + it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue + (one for each running WorkerThread). + """ + + def __init__(self, server): + self.ready = False + self.server = server + threading.Thread.__init__(self) + + def run(self): + try: + self.ready = True + while True: + conn = self.server.requests.get() + if conn is _SHUTDOWNREQUEST: + return + + try: + conn.communicate() + finally: + conn.close() + except (KeyboardInterrupt, SystemExit), exc: + self.server.interrupt = exc + + +class SSLConnection: + """A thread-safe wrapper for an SSL.Connection. + + *args: the arguments to create the wrapped SSL.Connection(*args). + """ + + def __init__(self, *args): + self._ssl_conn = SSL.Connection(*args) + self._lock = threading.RLock() + + for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', + 'renegotiate', 'bind', 'listen', 'connect', 'accept', + 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', + 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', + 'makefile', 'get_app_data', 'set_app_data', 'state_string', + 'sock_shutdown', 'get_peer_certificate', 'want_read', + 'want_write', 'set_connect_state', 'set_accept_state', + 'connect_ex', 'sendall', 'settimeout'): + exec """def %s(self, *args): + self._lock.acquire() + try: + return self._ssl_conn.%s(*args) + finally: + self._lock.release() +""" % (f, f) + + +class CherryPyWSGIServer(object): + """An HTTP server for WSGI. + + bind_addr: a (host, port) tuple if TCP sockets are desired; + for UNIX sockets, supply the filename as a string. + wsgi_app: the WSGI 'application callable'; multiple WSGI applications + may be passed as (script_name, callable) pairs. + numthreads: the number of worker threads to create (default 10). + server_name: the string to set for WSGI's SERVER_NAME environ entry. + Defaults to socket.gethostname(). + max: the maximum number of queued requests (defaults to -1 = no limit). + request_queue_size: the 'backlog' argument to socket.listen(); + specifies the maximum number of queued connections (default 5). + timeout: the timeout in seconds for accepted connections (default 10). + + protocol: the version string to write in the Status-Line of all + HTTP responses. For example, "HTTP/1.1" (the default). This + also limits the supported features used in the response. + + + SSL/HTTPS + --------- + The OpenSSL module must be importable for SSL functionality. + You can obtain it from http://pyopenssl.sourceforge.net/ + + ssl_certificate: the filename of the server SSL certificate. + ssl_privatekey: the filename of the server's private key file. + + If either of these is None (both are None by default), this server + will not use SSL. If both are given and are valid, they will be read + on server start and used in the SSL context for the listening socket. + """ + + protocol = "HTTP/1.1" + version = "CherryPy/3.0.2" + ready = False + _interrupt = None + ConnectionClass = HTTPConnection + + # Paths to certificate and private key files + ssl_certificate = None + ssl_private_key = None + + def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, + max=-1, request_queue_size=5, timeout=10): + self.requests = Queue.Queue(max) + + if callable(wsgi_app): + # We've been handed a single wsgi_app, in CP-2.1 style. + # Assume it's mounted at "". + self.mount_points = [("", wsgi_app)] + else: + # We've been handed a list of (mount_point, wsgi_app) tuples, + # so that the server can call different wsgi_apps, and also + # correctly set SCRIPT_NAME. + self.mount_points = wsgi_app + self.mount_points.sort() + self.mount_points.reverse() + + self.bind_addr = bind_addr + self.numthreads = numthreads or 1 + if not server_name: + server_name = socket.gethostname() + self.server_name = server_name + self.request_queue_size = request_queue_size + self._workerThreads = [] + self.gtk_idle_id = None + + self.timeout = timeout + + def start_common(self): + """Run the server forever.""" + # We don't have to trap KeyboardInterrupt or SystemExit here, + # because cherrpy.server already does so, calling self.stop() for us. + # If you're using this server with another framework, you should + # trap those exceptions in whatever code block calls start(). + self._interrupt = None + + # Select the appropriate socket + if isinstance(self.bind_addr, basestring): + # AF_UNIX socket + + # So we can reuse the socket... + try: os.unlink(self.bind_addr) + except: pass + + # So everyone can access the socket... + try: os.chmod(self.bind_addr, 0777) + except: pass + + info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] + else: + # AF_INET or AF_INET6 socket + # Get the correct address family for our host (allows IPv6 addresses) + host, port = self.bind_addr + flags = 0 + if host == '': + # Despite the socket module docs, using '' does not + # allow AI_PASSIVE to work. Passing None instead + # returns '0.0.0.0' like we want. In other words: + # host AI_PASSIVE result + # '' Y 192.168.x.y + # '' N 192.168.x.y + # None Y 0.0.0.0 + # None N 127.0.0.1 + host = None + flags = socket.AI_PASSIVE + try: + info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, flags) + except socket.gaierror: + # Probably a DNS issue. Assume IPv4. + info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] + + self.socket = None + msg = "No socket could be created" + for res in info: + af, socktype, proto, canonname, sa = res + try: + self.bind(af, socktype, proto) + except socket.error, msg: + if self.socket: + self.socket.close() + self.socket = None + continue + break + if not self.socket: + raise socket.error, msg + + # Timeout so KeyboardInterrupt can be caught on Win32 + self.socket.settimeout(1) + self.socket.listen(self.request_queue_size) + + # Create worker threads + for i in xrange(self.numthreads): + self._workerThreads.append(WorkerThread(self)) + for worker in self._workerThreads: + worker.setName("CP WSGIServer " + worker.getName()) + worker.start() + for worker in self._workerThreads: + while not worker.ready: + time.sleep(.1) + self.ready = True + + def start(self): + self.start_common() + while self.ready: + self.tick() + if self.interrupt: + while self.interrupt is True: + # Wait for self.stop() to complete. See _set_interrupt. + time.sleep(0.1) + raise self.interrupt + + def start_gtk(self): + self.start_common() + self.socket.settimeout(0.0001) + self.timeout = 0.3 + self.gtk_idle_id = gobject.idle_add(self.tick) + #self.gtk_idle_id = gobject.timeout_add(100, self.tick) #needs tweaking! + + + def bind(self, family, type, proto=0): + """Create (or recreate) the actual socket object.""" + self.socket = socket.socket(family, type, proto) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +## self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) + if self.ssl_certificate and self.ssl_private_key: + if SSL is None: + raise ImportError("You must install pyOpenSSL to use HTTPS.") + + # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(self.ssl_private_key) + ctx.use_certificate_file(self.ssl_certificate) + self.socket = SSLConnection(ctx, self.socket) + self.populate_ssl_environ() + self.socket.bind(self.bind_addr) + + def tick(self): + """Accept a new connection and put it on the Queue.""" + #print 'tick!' + try: + s, addr = self.socket.accept() + if not self.ready: + return True + if hasattr(s, 'settimeout'): + s.settimeout(self.timeout) + conn = self.ConnectionClass(s, addr, self) + self.requests.put(conn) + except socket.timeout: + # The only reason for the timeout in start() is so we can + # notice keyboard interrupts on Win32, which don't interrupt + # accept() by default + # mvoncken and i'ts usefull for gtk too. + return True + except socket.error, x: + msg = x.args[1] + if msg in ("Bad file descriptor", "Socket operation on non-socket"): + # Our socket was closed. + return True + if msg == "Resource temporarily unavailable": + # Just try again. See http://www.cherrypy.org/ticket/479. + return True + raise #mvoncken:should it raise here? + return True + + def _get_interrupt(self): + return self._interrupt + def _set_interrupt(self, interrupt): + self._interrupt = True + self.stop() + self._interrupt = interrupt + interrupt = property(_get_interrupt, _set_interrupt, + doc="Set this to an Exception instance to " + "interrupt the server.") + + def stop(self): + """Gracefully shutdown a server that is serving forever.""" + self.ready = False + + sock = getattr(self, "socket", None) + if sock: + if not isinstance(self.bind_addr, basestring): + # Touch our own socket to make accept() return immediately. + try: + host, port = sock.getsockname()[:2] + except socket.error, x: + if x.args[1] != "Bad file descriptor": + raise + else: + # Note that we're explicitly NOT using AI_PASSIVE, + # here, because we want an actual IP to touch. + # localhost won't work if we've bound to a public IP, + # but it would if we bound to INADDR_ANY via host = ''. + for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + s = None + try: + s = socket.socket(af, socktype, proto) + # See http://groups.google.com/group/cherrypy-users/ + # browse_frm/thread/bbfe5eb39c904fe0 + s.settimeout(1.0) + s.connect((host, port)) + s.close() + except socket.error: + if s: + s.close() + if hasattr(sock, "close"): + sock.close() + self.socket = None + + # Must shut down threads here so the code that calls + # this method can know when all threads are stopped. + for worker in self._workerThreads: + self.requests.put(_SHUTDOWNREQUEST) + + # Don't join currentThread (when stop is called inside a request). + current = threading.currentThread() + while self._workerThreads: + worker = self._workerThreads.pop() + if worker is not current and worker.isAlive: + try: + worker.join() + except AssertionError: + pass + + def stop_gtk(self): + self.stop() + if self.gtk_idle_id == None: + raise Exception('gtk_idle_id == None in stop_gtk') + gobject.source_remove(self.gtk_idle_id) + self.gtk_idle_id = None + + + def populate_ssl_environ(self): + """Create WSGI environ entries to be merged into each request.""" + cert = open(self.ssl_certificate).read() + cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + self.ssl_environ = { + # pyOpenSSL doesn't provide access to any of these AFAICT +## 'SSL_PROTOCOL': 'SSLv2', +## SSL_CIPHER string The cipher specification name +## SSL_VERSION_INTERFACE string The mod_ssl program version +## SSL_VERSION_LIBRARY string The OpenSSL program version + } + + # Server certificate attributes + self.ssl_environ.update({ + 'SSL_SERVER_M_VERSION': cert.get_version(), + 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), +## 'SSL_SERVER_V_START': Validity of server's certificate (start time), +## 'SSL_SERVER_V_END': Validity of server's certificate (end time), + }) + + for prefix, dn in [("I", cert.get_issuer()), + ("S", cert.get_subject())]: + # X509Name objects don't seem to have a way to get the + # complete DN string. Use str() and slice it instead, + # because str(dn) == "" + dnstr = str(dn)[18:-2] + + wsgikey = 'SSL_SERVER_%s_DN' % prefix + self.ssl_environ[wsgikey] = dnstr + + # The DN should be of the form: /k1=v1/k2=v2, but we must allow + # for any value to contain slashes itself (in a URL). + while dnstr: + pos = dnstr.rfind("=") + dnstr, value = dnstr[:pos], dnstr[pos + 1:] + pos = dnstr.rfind("/") + dnstr, key = dnstr[:pos], dnstr[pos + 1:] + if key and value: + wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) + self.ssl_environ[wsgikey] = value + + diff --git a/deluge/ui/webui/webui_plugin/lib/pythonize.py b/deluge/ui/webui/webui_plugin/lib/pythonize.py new file mode 100644 index 000000000..699c61dac --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/pythonize.py @@ -0,0 +1,38 @@ +""" +some dbus to python type conversions +-decorator for interface +-wrapper class for proxy +""" +def pythonize(var): + """translates dbus types back to basic python types.""" + if isinstance(var, list): + return [pythonize(value) for value in var] + if isinstance(var, tuple): + return tuple([pythonize(value) for value in var]) + if isinstance(var, dict): + return dict( + [(pythonize(key), pythonize(value)) for key, value in var.iteritems()] + ) + + for klass in [unicode, str, bool, int, float, long]: + if isinstance(var,klass): + return klass(var) + return var + +def pythonize_call(func): + def deco(*args,**kwargs): + return pythonize(func(*args, **kwargs)) + return deco + +def pythonize_interface(func): + def deco(*args, **kwargs): + args = pythonize(args) + kwargs = pythonize(kwargs) + return func(*args, **kwargs) + return deco + +class PythonizeProxy(object): + def __init__(self,proxy): + self.proxy = proxy + def __getattr__(self, key): + return pythonize_call(getattr(self.proxy, key)) diff --git a/deluge/ui/webui/webui_plugin/lib/readme.txt b/deluge/ui/webui/webui_plugin/lib/readme.txt new file mode 100644 index 000000000..16c5eee85 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/readme.txt @@ -0,0 +1,8 @@ +This folder may only contain general purpose utilities/files/tools. +They should be usable outside of deluge. + +Disclaimer: + +Some may have been adapted to work better with deluge. +But they will import other parts of deluge or Webui. + diff --git a/deluge/ui/webui/webui_plugin/lib/static_handler.py b/deluge/ui/webui/webui_plugin/lib/static_handler.py new file mode 100644 index 000000000..d5b706cca --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/static_handler.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +#(c) Martijn Voncken, mvoncken@gmail.com +#Same Licence as web.py 0.22 ->Public Domain +# +""" +static fileserving for web.py +without the need for wsgi wrapper magic. +""" +import webpy022 as web +from webpy022.http import seeother, url + +import posixpath +import urlparse +import urllib +import mimetypes +import os +import datetime +import cgi +from StringIO import StringIO +mimetypes.init() # try to read system mime.types + +class static_handler: + """ + mostly c&p from SimpleHttpServer + serves relative from start location + """ + base_dir = './' + extensions_map = mimetypes.types_map + + def get_base_dir(self): + #override this if you have a config that changes the base dir at runtime + #deluge on windows :( + return self.base_dir + + def GET(self, path): + path = self.translate_path(path) + if os.path.isdir(path): + if not path.endswith('/'): + path += "/" + return self.list_directory(path) + + ctype = self.guess_type(path) + + try: + f = open(path, 'rb') + except IOError: + raise Exception('file not found:%s' % path) + #web.header("404", "File not found") + #return + web.header("Content-type", ctype) + fs = os.fstat(f.fileno()) + web.header("Content-Length", str(fs[6])) + web.lastmodified(datetime.datetime.fromtimestamp(fs.st_mtime)) + print f.read() + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = urlparse.urlparse(path)[2] + path = posixpath.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + path = self.get_base_dir() + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in (os.curdir, os.pardir): continue + path = os.path.join(path, word) + return path + + def guess_type(self, path): + base, ext = posixpath.splitext(path) + if ext in self.extensions_map: + return self.extensions_map[ext] + ext = ext.lower() + if ext in self.extensions_map: + return self.extensions_map[ext] + else: + return 'application/octet-stream' + + + def list_directory(self, path): + """Helper to produce a directory listing (absent index.html). + + Return value is either a file object, or None (indicating an + error). In either case, the headers are sent, making the + interface the same as for send_head(). + #TODO ->use web.py +template! + """ + try: + list = os.listdir(path) + except os.error: + web.header('404', "No permission to list directory") + return None + list.sort(key=lambda a: a.lower()) + f = StringIO() + displaypath = cgi.escape(urllib.unquote(path)) + f.write("Directory listing for %s\n" % displaypath) + f.write("

Directory listing for %s

\n" % displaypath) + f.write("
\n
    \n") + for name in list: + fullname = os.path.join(path, name) + displayname = linkname = name + # Append / for directories or @ for symbolic links + if os.path.isdir(fullname): + displayname = name + "/" + linkname = name + "/" + if os.path.islink(fullname): + displayname = name + "@" + # Note: a link to a directory displays with @ and links with / + f.write('
  • %s\n' + % (urllib.quote(linkname), cgi.escape(displayname))) + f.write("
\n
\n") + length = f.tell() + f.seek(0) + + web.header("Content-type", "text/html") + web.header("Content-Length", str(length)) + print f.read() + + +if __name__ == '__main__': + #example: + class usr_static(static_handler): + base_dir = os.path.expanduser('~') + + urls = ('/relative/(.*)','static_handler', + '/(.*)','usr_static') + + web.run(urls,globals()) diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/Dependency-not-really part of webui.txt b/deluge/ui/webui/webui_plugin/lib/webpy022/Dependency-not-really part of webui.txt new file mode 100644 index 000000000..54e2330e9 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/Dependency-not-really part of webui.txt @@ -0,0 +1 @@ + http://webpy.org/ \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/__init__.py b/deluge/ui/webui/webui_plugin/lib/webpy022/__init__.py new file mode 100644 index 000000000..25e03d137 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/__init__.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +from __future__ import generators + +"""web.py: makes web apps (http://webpy.org)""" +__version__ = "0.22" +__revision__ = "$Rev: 183 $" +__author__ = "Aaron Swartz " +__license__ = "public domain" +__contributors__ = "see http://webpy.org/changes" + +# todo: +# - some sort of accounts system + +import utils, db, net, wsgi, http, webapi, request, httpserver, debugerror +import template, form + +from utils import * +from db import * +from net import * +from wsgi import * +from http import * +from webapi import * +from request import * +from httpserver import * +from debugerror import * + +try: + import cheetah + from cheetah import * +except ImportError: + pass + +def main(): + import doctest + + doctest.testmod(utils) + doctest.testmod(db) + doctest.testmod(net) + doctest.testmod(wsgi) + doctest.testmod(http) + doctest.testmod(webapi) + doctest.testmod(request) + + try: + doctest.testmod(cheetah) + except NameError: + pass + + template.test() + + import sys + urls = ('/web.py', 'source') + class source: + def GET(self): + header('Content-Type', 'text/python') + print open(sys.argv[0]).read() + + if listget(sys.argv, 1) != 'test': + run(urls, locals()) + +if __name__ == "__main__": main() + diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/changes.txt b/deluge/ui/webui/webui_plugin/lib/webpy022/changes.txt new file mode 100644 index 000000000..326e8a177 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/changes.txt @@ -0,0 +1,5 @@ +1:Commented out some code to enable a relative redirect. +This is not according to HTTP/1.1 Spec +But many deluge users will want to route the webui through firewalls/routers or use apache redirects. + +2:Disabled logging in the builtin http-server. diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/cheetah.py b/deluge/ui/webui/webui_plugin/lib/webpy022/cheetah.py new file mode 100644 index 000000000..db9fbf305 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/cheetah.py @@ -0,0 +1,98 @@ +""" +Cheetah API +(from web.py) +""" + +__all__ = ["render"] + +import re, urlparse, pprint, traceback, sys +from Cheetah.Compiler import Compiler +from Cheetah.Filters import Filter +from utils import re_compile, memoize, dictadd +from net import htmlquote, websafe +from webapi import ctx, header, output, input, cookies, loadhooks + +def upvars(level=2): + """Guido van Rossum sez: don't use this function.""" + return dictadd( + sys._getframe(level).f_globals, + sys._getframe(level).f_locals) + +r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M) +def __compiletemplate(template, base=None, isString=False): + if isString: + text = template + else: + text = open('templates/'+template).read() + # implement #include at compile-time + def do_include(match): + text = open('templates/'+match.groups()[0]).read() + return text + while r_include.findall(text): + text = r_include.sub(do_include, text) + + execspace = _compiletemplate.bases.copy() + tmpl_compiler = Compiler(source=text, mainClassName='GenTemplate') + tmpl_compiler.addImportedVarNames(execspace.keys()) + exec str(tmpl_compiler) in execspace + if base: + _compiletemplate.bases[base] = execspace['GenTemplate'] + + return execspace['GenTemplate'] + +_compiletemplate = memoize(__compiletemplate) +_compiletemplate.bases = {} + +def render(template, terms=None, asTemplate=False, base=None, + isString=False): + """ + Renders a template, caching where it can. + + `template` is the name of a file containing the a template in + the `templates/` folder, unless `isString`, in which case it's the + template itself. + + `terms` is a dictionary used to fill the template. If it's None, then + the caller's local variables are used instead, plus context, if it's not + already set, is set to `context`. + + If asTemplate is False, it `output`s the template directly. Otherwise, + it returns the template object. + + If the template is a potential base template (that is, something other templates) + can extend, then base should be a string with the name of the template. The + template will be cached and made available for future calls to `render`. + + Requires [Cheetah](http://cheetahtemplate.org/). + """ + # terms=['var1', 'var2'] means grab those variables + if isinstance(terms, list): + new = {} + old = upvars() + for k in terms: + new[k] = old[k] + terms = new + # default: grab all locals + elif terms is None: + terms = {'context': ctx, 'ctx':ctx} + terms.update(sys._getframe(1).f_locals) + # terms=d means use d as the searchList + if not isinstance(terms, tuple): + terms = (terms,) + + if 'headers' in ctx and not isString and template.endswith('.html'): + header('Content-Type','text/html; charset=utf-8', unique=True) + + if loadhooks.has_key('reloader'): + compiled_tmpl = __compiletemplate(template, base=base, isString=isString) + else: + compiled_tmpl = _compiletemplate(template, base=base, isString=isString) + compiled_tmpl = compiled_tmpl(searchList=terms, filter=WebSafe) + if asTemplate: + return compiled_tmpl + else: + return output(str(compiled_tmpl)) + +class WebSafe(Filter): + def filter(self, val, **keywords): + return websafe(val) diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/db.py b/deluge/ui/webui/webui_plugin/lib/webpy022/db.py new file mode 100644 index 000000000..2438a162f --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/db.py @@ -0,0 +1,703 @@ +""" +Database API +(part of web.py) +""" + +# todo: +# - test with sqlite +# - a store function? + +__all__ = [ + "UnknownParamstyle", "UnknownDB", + "sqllist", "sqlors", "aparam", "reparam", + "SQLQuery", "sqlquote", + "SQLLiteral", "sqlliteral", + "connect", + "TransactionError", "transaction", "transact", "commit", "rollback", + "query", + "select", "insert", "update", "delete" +] + +import time +try: import datetime +except ImportError: datetime = None + +from utils import storage, iters, iterbetter +import webapi as web + +try: + from DBUtils import PooledDB + web.config._hasPooling = True +except ImportError: + web.config._hasPooling = False + +class _ItplError(ValueError): + def __init__(self, text, pos): + ValueError.__init__(self) + self.text = text + self.pos = pos + def __str__(self): + return "unfinished expression in %s at char %d" % ( + repr(self.text), self.pos) + +def _interpolate(format): + """ + Takes a format string and returns a list of 2-tuples of the form + (boolean, string) where boolean says whether string should be evaled + or not. + + from (public domain, Ka-Ping Yee) + """ + from tokenize import tokenprog + + def matchorfail(text, pos): + match = tokenprog.match(text, pos) + if match is None: + raise _ItplError(text, pos) + return match, match.end() + + namechars = "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + chunks = [] + pos = 0 + + while 1: + dollar = format.find("$", pos) + if dollar < 0: + break + nextchar = format[dollar + 1] + + if nextchar == "{": + chunks.append((0, format[pos:dollar])) + pos, level = dollar + 2, 1 + while level: + match, pos = matchorfail(format, pos) + tstart, tend = match.regs[3] + token = format[tstart:tend] + if token == "{": + level = level + 1 + elif token == "}": + level = level - 1 + chunks.append((1, format[dollar + 2:pos - 1])) + + elif nextchar in namechars: + chunks.append((0, format[pos:dollar])) + match, pos = matchorfail(format, dollar + 1) + while pos < len(format): + if format[pos] == "." and \ + pos + 1 < len(format) and format[pos + 1] in namechars: + match, pos = matchorfail(format, pos + 1) + elif format[pos] in "([": + pos, level = pos + 1, 1 + while level: + match, pos = matchorfail(format, pos) + tstart, tend = match.regs[3] + token = format[tstart:tend] + if token[0] in "([": + level = level + 1 + elif token[0] in ")]": + level = level - 1 + else: + break + chunks.append((1, format[dollar + 1:pos])) + + else: + chunks.append((0, format[pos:dollar + 1])) + pos = dollar + 1 + (nextchar == "$") + + if pos < len(format): + chunks.append((0, format[pos:])) + return chunks + +class UnknownParamstyle(Exception): + """ + raised for unsupported db paramstyles + + (currently supported: qmark, numeric, format, pyformat) + """ + pass + +def aparam(): + """ + Returns the appropriate string to be used to interpolate + a value with the current `web.ctx.db_module` or simply %s + if there isn't one. + + >>> aparam() + '%s' + """ + if hasattr(web.ctx, 'db_module'): + style = web.ctx.db_module.paramstyle + else: + style = 'pyformat' + + if style == 'qmark': + return '?' + elif style == 'numeric': + return ':1' + elif style in ['format', 'pyformat']: + return '%s' + raise UnknownParamstyle, style + +def reparam(string_, dictionary): + """ + Takes a string and a dictionary and interpolates the string + using values from the dictionary. Returns an `SQLQuery` for the result. + + >>> reparam("s = $s", dict(s=True)) + + """ + vals = [] + result = [] + for live, chunk in _interpolate(string_): + if live: + result.append(aparam()) + vals.append(eval(chunk, dictionary)) + else: result.append(chunk) + return SQLQuery(''.join(result), vals) + +def sqlify(obj): + """ + converts `obj` to its proper SQL version + + >>> sqlify(None) + 'NULL' + >>> sqlify(True) + "'t'" + >>> sqlify(3) + '3' + """ + + # because `1 == True and hash(1) == hash(True)` + # we have to do this the hard way... + + if obj is None: + return 'NULL' + elif obj is True: + return "'t'" + elif obj is False: + return "'f'" + elif datetime and isinstance(obj, datetime.datetime): + return repr(obj.isoformat()) + else: + return repr(obj) + +class SQLQuery: + """ + You can pass this sort of thing as a clause in any db function. + Otherwise, you can pass a dictionary to the keyword argument `vars` + and the function will call reparam for you. + """ + # tested in sqlquote's docstring + def __init__(self, s='', v=()): + self.s, self.v = str(s), tuple(v) + + def __getitem__(self, key): # for backwards-compatibility + return [self.s, self.v][key] + + def __add__(self, other): + if isinstance(other, str): + self.s += other + elif isinstance(other, SQLQuery): + self.s += other.s + self.v += other.v + return self + + def __radd__(self, other): + if isinstance(other, str): + self.s = other + self.s + return self + else: + return NotImplemented + + def __str__(self): + try: + return self.s % tuple([sqlify(x) for x in self.v]) + except (ValueError, TypeError): + return self.s + + def __repr__(self): + return '' % repr(str(self)) + +class SQLLiteral: + """ + Protects a string from `sqlquote`. + + >>> insert('foo', time=SQLLiteral('NOW()'), _test=True) + + """ + def __init__(self, v): + self.v = v + + def __repr__(self): + return self.v + +sqlliteral = SQLLiteral + +def sqlquote(a): + """ + Ensures `a` is quoted properly for use in a SQL query. + + >>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3) + + """ + return SQLQuery(aparam(), (a,)) + +class UnknownDB(Exception): + """raised for unsupported dbms""" + pass + +def connect(dbn, **keywords): + """ + Connects to the specified database. + + `dbn` currently must be "postgres", "mysql", or "sqlite". + + If DBUtils is installed, connection pooling will be used. + """ + if dbn == "postgres": + try: + import psycopg2 as db + except ImportError: + try: + import psycopg as db + except ImportError: + import pgdb as db + if 'pw' in keywords: + keywords['password'] = keywords['pw'] + del keywords['pw'] + keywords['database'] = keywords['db'] + del keywords['db'] + + elif dbn == "mysql": + import MySQLdb as db + if 'pw' in keywords: + keywords['passwd'] = keywords['pw'] + del keywords['pw'] + db.paramstyle = 'pyformat' # it's both, like psycopg + + elif dbn == "sqlite": + try: + import sqlite3 as db + db.paramstyle = 'qmark' + except ImportError: + try: + from pysqlite2 import dbapi2 as db + db.paramstyle = 'qmark' + except ImportError: + import sqlite as db + web.config._hasPooling = False + keywords['database'] = keywords['db'] + del keywords['db'] + + elif dbn == "firebird": + import kinterbasdb as db + if 'pw' in keywords: + keywords['passwd'] = keywords['pw'] + del keywords['pw'] + keywords['database'] = keywords['db'] + del keywords['db'] + + else: + raise UnknownDB, dbn + + web.ctx.db_name = dbn + web.ctx.db_module = db + web.ctx.db_transaction = 0 + web.ctx.db = keywords + + def _PooledDB(db, keywords): + # In DBUtils 0.9.3, `dbapi` argument is renamed as `creator` + # see Bug#122112 + if PooledDB.__version__.split('.') < '0.9.3'.split('.'): + return PooledDB.PooledDB(dbapi=db, **keywords) + else: + return PooledDB.PooledDB(creator=db, **keywords) + + def db_cursor(): + if isinstance(web.ctx.db, dict): + keywords = web.ctx.db + if web.config._hasPooling: + if 'db' not in globals(): + globals()['db'] = _PooledDB(db, keywords) + web.ctx.db = globals()['db'].connection() + else: + web.ctx.db = db.connect(**keywords) + return web.ctx.db.cursor() + web.ctx.db_cursor = db_cursor + + web.ctx.dbq_count = 0 + + def db_execute(cur, sql_query, dorollback=True): + """executes an sql query""" + + web.ctx.dbq_count += 1 + + try: + a = time.time() + out = cur.execute(sql_query.s, sql_query.v) + b = time.time() + except: + if web.config.get('db_printing'): + print >> web.debug, 'ERR:', str(sql_query) + if dorollback: rollback(care=False) + raise + + if web.config.get('db_printing'): + print >> web.debug, '%s (%s): %s' % (round(b-a, 2), web.ctx.dbq_count, str(sql_query)) + + return out + web.ctx.db_execute = db_execute + return web.ctx.db + +class TransactionError(Exception): pass + +class transaction: + """ + A context that can be used in conjunction with "with" statements + to implement SQL transactions. Starts a transaction on enter, + rolls it back if there's an error; otherwise it commits it at the + end. + """ + def __enter__(self): + transact() + + def __exit__(self, exctype, excvalue, traceback): + if exctype is not None: + rollback() + else: + commit() + +def transact(): + """Start a transaction.""" + if not web.ctx.db_transaction: + # commit everything up to now, so we don't rollback it later + if hasattr(web.ctx.db, 'commit'): + web.ctx.db.commit() + else: + db_cursor = web.ctx.db_cursor() + web.ctx.db_execute(db_cursor, + SQLQuery("SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction)) + web.ctx.db_transaction += 1 + +def commit(): + """Commits a transaction.""" + web.ctx.db_transaction -= 1 + if web.ctx.db_transaction < 0: + raise TransactionError, "not in a transaction" + + if not web.ctx.db_transaction: + if hasattr(web.ctx.db, 'commit'): + web.ctx.db.commit() + else: + db_cursor = web.ctx.db_cursor() + web.ctx.db_execute(db_cursor, + SQLQuery("RELEASE SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction)) + +def rollback(care=True): + """Rolls back a transaction.""" + web.ctx.db_transaction -= 1 + if web.ctx.db_transaction < 0: + web.db_transaction = 0 + if care: + raise TransactionError, "not in a transaction" + else: + return + + if not web.ctx.db_transaction: + if hasattr(web.ctx.db, 'rollback'): + web.ctx.db.rollback() + else: + db_cursor = web.ctx.db_cursor() + web.ctx.db_execute(db_cursor, + SQLQuery("ROLLBACK TO SAVEPOINT webpy_sp_%s" % web.ctx.db_transaction), + dorollback=False) + +def query(sql_query, vars=None, processed=False, _test=False): + """ + Execute SQL query `sql_query` using dictionary `vars` to interpolate it. + If `processed=True`, `vars` is a `reparam`-style list to use + instead of interpolating. + + >>> query("SELECT * FROM foo", _test=True) + + >>> query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True) + + >>> query("SELECT * FROM foo WHERE x = " + sqlquote('f'), _test=True) + + """ + if vars is None: vars = {} + + if not processed and not isinstance(sql_query, SQLQuery): + sql_query = reparam(sql_query, vars) + + if _test: return sql_query + + db_cursor = web.ctx.db_cursor() + web.ctx.db_execute(db_cursor, sql_query) + + if db_cursor.description: + names = [x[0] for x in db_cursor.description] + def iterwrapper(): + row = db_cursor.fetchone() + while row: + yield storage(dict(zip(names, row))) + row = db_cursor.fetchone() + out = iterbetter(iterwrapper()) + if web.ctx.db_name != "sqlite": + out.__len__ = lambda: int(db_cursor.rowcount) + out.list = lambda: [storage(dict(zip(names, x))) \ + for x in db_cursor.fetchall()] + else: + out = db_cursor.rowcount + + if not web.ctx.db_transaction: web.ctx.db.commit() + return out + +def sqllist(lst): + """ + Converts the arguments for use in something like a WHERE clause. + + >>> sqllist(['a', 'b']) + 'a, b' + >>> sqllist('a') + 'a' + + """ + if isinstance(lst, str): + return lst + else: + return ', '.join(lst) + +def sqlors(left, lst): + """ + `left is a SQL clause like `tablename.arg = ` + and `lst` is a list of values. Returns a reparam-style + pair featuring the SQL that ORs together the clause + for each item in the lst. + + >>> sqlors('foo = ', []) + + >>> sqlors('foo = ', [1]) + + >>> sqlors('foo = ', 1) + + >>> sqlors('foo = ', [1,2,3]) + + """ + if isinstance(lst, iters): + lst = list(lst) + ln = len(lst) + if ln == 0: + return SQLQuery("2+2=5", []) + if ln == 1: + lst = lst[0] + + if isinstance(lst, iters): + return SQLQuery('(' + left + + (' OR ' + left).join([aparam() for param in lst]) + ")", lst) + else: + return SQLQuery(left + aparam(), [lst]) + +def sqlwhere(dictionary, grouping=' AND '): + """ + Converts a `dictionary` to an SQL WHERE clause `SQLQuery`. + + >>> sqlwhere({'cust_id': 2, 'order_id':3}) + + >>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ') + + """ + + return SQLQuery(grouping.join([ + '%s = %s' % (k, aparam()) for k in dictionary.keys() + ]), dictionary.values()) + +def select(tables, vars=None, what='*', where=None, order=None, group=None, + limit=None, offset=None, _test=False): + """ + Selects `what` from `tables` with clauses `where`, `order`, + `group`, `limit`, and `offset`. Uses vars to interpolate. + Otherwise, each clause can be a SQLQuery. + + >>> select('foo', _test=True) + + >>> select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True) + + """ + if vars is None: vars = {} + qout = "" + + def gen_clause(sql, val): + if isinstance(val, (int, long)): + if sql == 'WHERE': + nout = 'id = ' + sqlquote(val) + else: + nout = SQLQuery(val) + elif isinstance(val, (list, tuple)) and len(val) == 2: + nout = SQLQuery(val[0], val[1]) # backwards-compatibility + elif isinstance(val, SQLQuery): + nout = val + elif val: + nout = reparam(val, vars) + else: + return "" + + out = "" + if qout: out += " " + out += sql + " " + nout + return out + + if web.ctx.get('db_name') == "firebird": + for (sql, val) in ( + ('FIRST', limit), + ('SKIP', offset) + ): + qout += gen_clause(sql, val) + if qout: + SELECT = 'SELECT ' + qout + else: + SELECT = 'SELECT' + qout = "" + sql_clauses = ( + (SELECT, what), + ('FROM', sqllist(tables)), + ('WHERE', where), + ('GROUP BY', group), + ('ORDER BY', order) + ) + else: + sql_clauses = ( + ('SELECT', what), + ('FROM', sqllist(tables)), + ('WHERE', where), + ('GROUP BY', group), + ('ORDER BY', order), + ('LIMIT', limit), + ('OFFSET', offset) + ) + + for (sql, val) in sql_clauses: + qout += gen_clause(sql, val) + + if _test: return qout + return query(qout, processed=True) + +def insert(tablename, seqname=None, _test=False, **values): + """ + Inserts `values` into `tablename`. Returns current sequence ID. + Set `seqname` to the ID if it's not the default, or to `False` + if there isn't one. + + >>> insert('foo', joe='bob', a=2, _test=True) + + """ + + if values: + sql_query = SQLQuery("INSERT INTO %s (%s) VALUES (%s)" % ( + tablename, + ", ".join(values.keys()), + ', '.join([aparam() for x in values]) + ), values.values()) + else: + sql_query = SQLQuery("INSERT INTO %s DEFAULT VALUES" % tablename) + + if _test: return sql_query + + db_cursor = web.ctx.db_cursor() + if seqname is False: + pass + elif web.ctx.db_name == "postgres": + if seqname is None: + seqname = tablename + "_id_seq" + sql_query += "; SELECT currval('%s')" % seqname + elif web.ctx.db_name == "mysql": + web.ctx.db_execute(db_cursor, sql_query) + sql_query = SQLQuery("SELECT last_insert_id()") + elif web.ctx.db_name == "sqlite": + web.ctx.db_execute(db_cursor, sql_query) + # not really the same... + sql_query = SQLQuery("SELECT last_insert_rowid()") + + web.ctx.db_execute(db_cursor, sql_query) + try: + out = db_cursor.fetchone()[0] + except Exception: + out = None + + if not web.ctx.db_transaction: web.ctx.db.commit() + + return out + +def update(tables, where, vars=None, _test=False, **values): + """ + Update `tables` with clause `where` (interpolated using `vars`) + and setting `values`. + + >>> joe = 'Joseph' + >>> update('foo', where='name = $joe', name='bob', age=5, + ... vars=locals(), _test=True) + + """ + if vars is None: vars = {} + + if isinstance(where, (int, long)): + where = "id = " + sqlquote(where) + elif isinstance(where, (list, tuple)) and len(where) == 2: + where = SQLQuery(where[0], where[1]) + elif isinstance(where, SQLQuery): + pass + else: + where = reparam(where, vars) + + query = ( + "UPDATE " + sqllist(tables) + + " SET " + sqlwhere(values, ', ') + + " WHERE " + where) + + if _test: return query + + db_cursor = web.ctx.db_cursor() + web.ctx.db_execute(db_cursor, query) + + if not web.ctx.db_transaction: web.ctx.db.commit() + return db_cursor.rowcount + +def delete(table, where=None, using=None, vars=None, _test=False): + """ + Deletes from `table` with clauses `where` and `using`. + + >>> name = 'Joe' + >>> delete('foo', where='name = $name', vars=locals(), _test=True) + + """ + if vars is None: vars = {} + + if isinstance(where, (int, long)): + where = "id = " + sqlquote(where) + elif isinstance(where, (list, tuple)) and len(where) == 2: + where = SQLQuery(where[0], where[1]) + elif isinstance(where, SQLQuery): + pass + elif where is None: + pass + else: + where = reparam(where, vars) + + q = 'DELETE FROM ' + table + if where: + q += ' WHERE ' + where + if using and web.ctx.get('db_name') != "firebird": + q += ' USING ' + sqllist(using) + + if _test: return q + + db_cursor = web.ctx.db_cursor() + web.ctx.db_execute(db_cursor, q) + + if not web.ctx.db_transaction: web.ctx.db.commit() + return db_cursor.rowcount + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/debugerror.py b/deluge/ui/webui/webui_plugin/lib/webpy022/debugerror.py new file mode 100644 index 000000000..1de465a81 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/debugerror.py @@ -0,0 +1,316 @@ +""" +pretty debug errors +(part of web.py) + +adapted from Django +Copyright (c) 2005, the Lawrence Journal-World +Used under the modified BSD license: +http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +""" + +__all__ = ["debugerror", "djangoerror"] + +import sys, urlparse, pprint +from net import websafe +from template import Template +import webapi as web + +import os, os.path +whereami = os.path.join(os.getcwd(), __file__) +whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1]) +djangoerror_t = """\ +$def with (exception_type, exception_value, frames) + + + + + + $exception_type at $ctx.path + + + + + +
+

$exception_type at $ctx.path

+

$exception_value

+ + + + + + +
Python$frames[0].filename in $frames[0].function, line $frames[0].lineno
Web$ctx.method $ctx.home$ctx.path
+
+
+

Traceback (innermost first)

+
    +$for frame in frames: +
  • + $frame.filename in $frame.function + $if frame.context_line: +
    + $if frame.pre_context: +
      + $for line in frame.pre_context: +
    1. $line
    2. +
    +
    1. $frame.context_line ...
    + $if frame.post_context: +
      + $for line in frame.post_context: +
    1. $line
    2. +
    +
    + + $if frame.vars: +
    + Local vars + $# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame)) +
    + $:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id))) +
  • +
+
+ +
+$if ctx.output or ctx.headers: +

Response so far

+

HEADERS

+

+ $for kv in ctx.headers: + $kv[0]: $kv[1]
+ $else: + [no headers] +

+ +

BODY

+

+ $ctx.output +

+ +

Request information

+ +

INPUT

+$:dicttable(web.input()) + + +$:dicttable(web.cookies()) + +

META

+$ newctx = [] +$# ) and (k not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute']): +$for k, v in ctx.iteritems(): + $if not k.startswith('_') and (k in x): + $newctx.append(kv) +$:dicttable(dict(newctx)) + +

ENVIRONMENT

+$:dicttable(ctx.env) +
+ +
+

+ You're seeing this error because you have web.internalerror + set to web.debugerror. Change that if you want a different one. +

+
+ + + +""" + +dicttable_t = r"""$def with (d, kls='req', id=None) +$if d: + + + $ temp = d.items() + $temp.sort() + $for kv in temp: + + +
VariableValue
$kv[0]
$prettify(kv[1])
+$else: +

No data.

+""" + +dicttable_r = Template(dicttable_t, filter=websafe) +djangoerror_r = Template(djangoerror_t, filter=websafe) + +def djangoerror(): + def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = \ + [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = \ + [line.strip('\n') for line in source[lineno + 1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + + exception_type, exception_value, tback = sys.exc_info() + frames = [] + while tback is not None: + filename = tback.tb_frame.f_code.co_filename + function = tback.tb_frame.f_code.co_name + lineno = tback.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = \ + _get_lines_from_file(filename, lineno, 7) + frames.append(web.storage({ + 'tback': tback, + 'filename': filename, + 'function': function, + 'lineno': lineno, + 'vars': tback.tb_frame.f_locals, + 'id': id(tback), + 'pre_context': pre_context, + 'context_line': context_line, + 'post_context': post_context, + 'pre_context_lineno': pre_context_lineno, + })) + tback = tback.tb_next + frames.reverse() + urljoin = urlparse.urljoin + def prettify(x): + try: + out = pprint.pformat(x) + except Exception, e: + out = '[could not display: <' + e.__class__.__name__ + \ + ': '+str(e)+'>]' + return out + dt = dicttable_r + dt.globals = {'prettify': prettify} + t = djangoerror_r + t.globals = {'ctx': web.ctx, 'web':web, 'dicttable':dt, 'dict':dict, 'str':str} + return t(exception_type, exception_value, frames) + +def debugerror(): + """ + A replacement for `internalerror` that presents a nice page with lots + of debug information for the programmer. + + (Based on the beautiful 500 page from [Django](http://djangoproject.com/), + designed by [Wilson Miner](http://wilsonminer.com/).) + """ + + web.ctx.headers = [('Content-Type', 'text/html')] + web.ctx.output = djangoerror() + +if __name__ == "__main__": + urls = ( + '/', 'index' + ) + + class index: + def GET(self): + thisdoesnotexist + + web.internalerror = web.debugerror + web.run(urls) \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/form.py b/deluge/ui/webui/webui_plugin/lib/webpy022/form.py new file mode 100644 index 000000000..b1b808e49 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/form.py @@ -0,0 +1,215 @@ +""" +HTML forms +(part of web.py) +""" + +import copy, re +import webapi as web +import utils, net + +def attrget(obj, attr, value=None): + if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr] + if hasattr(obj, attr): return getattr(obj, attr) + return value + +class Form: + def __init__(self, *inputs, **kw): + self.inputs = inputs + self.valid = True + self.note = None + self.validators = kw.pop('validators', []) + + def __call__(self, x=None): + o = copy.deepcopy(self) + if x: o.validates(x) + return o + + def render(self): + out = '' + out += self.rendernote(self.note) + out += '\n' + for i in self.inputs: + out += ' ' % (i.id, i.description) + out += "" + out += '\n' % (i.id, self.rendernote(i.note)) + out += "
"+i.pre+i.render()+i.post+"%s
" + return out + + def rendernote(self, note): + if note: return '%s' % note + else: return "" + + def validates(self, source=None, _validate=True, **kw): + source = source or kw or web.input() + out = True + for i in self.inputs: + v = attrget(source, i.name) + if _validate: + out = i.validate(v) and out + else: + i.value = v + if _validate: + out = out and self._validate(source) + self.valid = out + return out + + def _validate(self, value): + self.value = value + for v in self.validators: + if not v.valid(value): + self.note = v.msg + return False + return True + + def fill(self, source=None, **kw): + return self.validates(source, _validate=False, **kw) + + def __getitem__(self, i): + for x in self.inputs: + if x.name == i: return x + raise KeyError, i + + def _get_d(self): #@@ should really be form.attr, no? + return utils.storage([(i.name, i.value) for i in self.inputs]) + d = property(_get_d) + +class Input(object): + def __init__(self, name, *validators, **attrs): + self.description = attrs.pop('description', name) + self.value = attrs.pop('value', None) + self.pre = attrs.pop('pre', "") + self.post = attrs.pop('post', "") + self.id = attrs.setdefault('id', name) + if 'class_' in attrs: + attrs['class'] = attrs['class_'] + del attrs['class_'] + self.name, self.validators, self.attrs, self.note = name, validators, attrs, None + + def validate(self, value): + self.value = value + for v in self.validators: + if not v.valid(value): + self.note = v.msg + return False + return True + + def render(self): raise NotImplementedError + + def addatts(self): + str = "" + for (n, v) in self.attrs.items(): + str += ' %s="%s"' % (n, net.websafe(v)) + return str + +#@@ quoting + +class Textbox(Input): + def render(self): + x = '' + +class Checkbox(Input): + def render(self): + x = 'moved permanently') + +def found(url): + """A `302 Found` redirect.""" + return redirect(url, '302 Found') + +def seeother(url): + """A `303 See Other` redirect.""" + return redirect(url, '303 See Other') + +def tempredirect(url): + """A `307 Temporary Redirect` redirect.""" + return redirect(url, '307 Temporary Redirect') + +def write(cgi_response): + """ + Converts a standard CGI-style string response into `header` and + `output` calls. + """ + cgi_response = str(cgi_response) + cgi_response.replace('\r\n', '\n') + head, body = cgi_response.split('\n\n', 1) + lines = head.split('\n') + + for line in lines: + if line.isspace(): + continue + hdr, value = line.split(":", 1) + value = value.strip() + if hdr.lower() == "status": + web.ctx.status = value + else: + web.header(hdr, value) + + web.output(body) + +def urlencode(query): + """ + Same as urllib.urlencode, but supports unicode strings. + + >>> urlencode({'text':'foo bar'}) + 'text=foo+bar' + """ + query = dict([(k, utils.utf8(v)) for k, v in query.items()]) + return urllib.urlencode(query) + +def changequery(query=None, **kw): + """ + Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return + `/foo?a=3&b=2` -- the same URL but with the arguments you requested + changed. + """ + if query is None: + query = web.input(_method='get') + for k, v in kw.iteritems(): + if v is None: + query.pop(k, None) + else: + query[k] = v + out = web.ctx.path + if query: + out += '?' + urlencode(query) + return out + +def url(path=None, **kw): + """ + Makes url by concatinating web.ctx.homepath and path and the + query string created using the arguments. + """ + if path is None: + path = web.ctx.path + if path.startswith("/"): + out = web.ctx.homepath + path + else: + out = path + + if kw: + out += '?' + urlencode(kw) + + return out + +def background(func): + """A function decorator to run a long-running function as a background thread.""" + def internal(*a, **kw): + web.data() # cache it + + tmpctx = web._context[threading.currentThread()] + web._context[threading.currentThread()] = utils.storage(web.ctx.copy()) + + def newfunc(): + web._context[threading.currentThread()] = tmpctx + func(*a, **kw) + myctx = web._context[threading.currentThread()] + for k in myctx.keys(): + if k not in ['status', 'headers', 'output']: + try: del myctx[k] + except KeyError: pass + + t = threading.Thread(target=newfunc) + background.threaddb[id(t)] = t + t.start() + web.ctx.headers = [] + return seeother(changequery(_t=id(t))) + return internal +background.threaddb = {} + +def backgrounder(func): + def internal(*a, **kw): + i = web.input(_method='get') + if '_t' in i: + try: + t = background.threaddb[int(i._t)] + except KeyError: + return web.notfound() + web._context[threading.currentThread()] = web._context[t] + return + else: + return func(*a, **kw) + return internal + +class Reloader: + """ + Before every request, checks to see if any loaded modules have changed on + disk and, if so, reloads them. + """ + def __init__(self, func): + self.func = func + self.mtimes = {} + # cheetah: + # b = _compiletemplate.bases + # _compiletemplate = globals()['__compiletemplate'] + # _compiletemplate.bases = b + + web.loadhooks['reloader'] = self.check + # todo: + # - replace relrcheck with a loadhook + #if reloader in middleware: + # relr = reloader(None) + # relrcheck = relr.check + # middleware.remove(reloader) + #else: + # relr = None + # relrcheck = lambda: None + # if relr: + # relr.func = wsgifunc + # return wsgifunc + # + + + def check(self): + for mod in sys.modules.values(): + try: + mtime = os.stat(mod.__file__).st_mtime + except (AttributeError, OSError, IOError): + continue + if mod.__file__.endswith('.pyc') and \ + os.path.exists(mod.__file__[:-1]): + mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime) + if mod not in self.mtimes: + self.mtimes[mod] = mtime + elif self.mtimes[mod] < mtime: + try: + reload(mod) + self.mtimes[mod] = mtime + except ImportError: + pass + return True + + def __call__(self, e, o): + self.check() + return self.func(e, o) + +reloader = Reloader + +def profiler(app): + """Outputs basic profiling information at the bottom of each response.""" + from utils import profile + def profile_internal(e, o): + out, result = profile(app)(e, o) + return out + ['
' + net.websafe(result) + '
'] + return profile_internal + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/httpserver.py b/deluge/ui/webui/webui_plugin/lib/webpy022/httpserver.py new file mode 100644 index 000000000..6df60b1d8 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/httpserver.py @@ -0,0 +1,227 @@ +__all__ = ["runsimple"] + +import sys, os +import webapi as web +import net + +def runbasic(func, server_address=("0.0.0.0", 8080)): + """ + Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` + is hosted statically. + + Based on [WsgiServer][ws] from [Colin Stewart][cs]. + + [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html + [cs]: http://www.owlfish.com/ + """ + # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + # Modified somewhat for simplicity + # Used under the modified BSD license: + # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + + import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse + import socket, errno + import traceback + + class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def run_wsgi_app(self): + protocol, host, path, parameters, query, fragment = \ + urlparse.urlparse('http://dummyhost%s' % self.path) + + # we only use path, query + env = {'wsgi.version': (1, 0) + ,'wsgi.url_scheme': 'http' + ,'wsgi.input': self.rfile + ,'wsgi.errors': sys.stderr + ,'wsgi.multithread': 1 + ,'wsgi.multiprocess': 0 + ,'wsgi.run_once': 0 + ,'REQUEST_METHOD': self.command + ,'REQUEST_URI': self.path + ,'PATH_INFO': path + ,'QUERY_STRING': query + ,'CONTENT_TYPE': self.headers.get('Content-Type', '') + ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') + ,'REMOTE_ADDR': self.client_address[0] + ,'SERVER_NAME': self.server.server_address[0] + ,'SERVER_PORT': str(self.server.server_address[1]) + ,'SERVER_PROTOCOL': self.request_version + } + + for http_header, http_value in self.headers.items(): + env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ + http_value + + # Setup the state + self.wsgi_sent_headers = 0 + self.wsgi_headers = [] + + try: + # We have there environment, now invoke the application + result = self.server.app(env, self.wsgi_start_response) + try: + try: + for data in result: + if data: + self.wsgi_write_data(data) + finally: + if hasattr(result, 'close'): + result.close() + except socket.error, socket_err: + # Catch common network errors and suppress them + if (socket_err.args[0] in \ + (errno.ECONNABORTED, errno.EPIPE)): + return + except socket.timeout, socket_timeout: + return + except: + print >> web.debug, traceback.format_exc(), + + if (not self.wsgi_sent_headers): + # We must write out something! + self.wsgi_write_data(" ") + return + + do_POST = run_wsgi_app + do_PUT = run_wsgi_app + do_DELETE = run_wsgi_app + + def do_GET(self): + if self.path.startswith('/static/'): + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + else: + self.run_wsgi_app() + + def wsgi_start_response(self, response_status, response_headers, + exc_info=None): + if (self.wsgi_sent_headers): + raise Exception \ + ("Headers already sent and start_response called again!") + # Should really take a copy to avoid changes in the application.... + self.wsgi_headers = (response_status, response_headers) + return self.wsgi_write_data + + def wsgi_write_data(self, data): + if (not self.wsgi_sent_headers): + status, headers = self.wsgi_headers + # Need to send header prior to data + status_code = status[:status.find(' ')] + status_msg = status[status.find(' ') + 1:] + self.send_response(int(status_code), status_msg) + for header, value in headers: + self.send_header(header, value) + self.end_headers() + self.wsgi_sent_headers = 1 + # Send the data + self.wfile.write(data) + + class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + def __init__(self, func, server_address): + BaseHTTPServer.HTTPServer.__init__(self, + server_address, + WSGIHandler) + self.app = func + self.serverShuttingDown = 0 + + print "http://%s:%d/" % server_address + WSGIServer(func, server_address).serve_forever() + +def runsimple(func, server_address=("0.0.0.0", 8080)): + """ + Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. + The directory `static/` is hosted statically. + + [cp]: http://www.cherrypy.org + """ + from wsgiserver import CherryPyWSGIServer + from SimpleHTTPServer import SimpleHTTPRequestHandler + from BaseHTTPServer import BaseHTTPRequestHandler + + class StaticApp(SimpleHTTPRequestHandler): + """WSGI application for serving static files.""" + def __init__(self, environ, start_response): + self.headers = [] + self.environ = environ + self.start_response = start_response + + def send_response(self, status, msg=""): + self.status = str(status) + " " + msg + + def send_header(self, name, value): + self.headers.append((name, value)) + + def end_headers(self): + pass + + def log_message(*a): pass + + def __iter__(self): + environ = self.environ + + self.path = environ.get('PATH_INFO', '') + self.client_address = environ.get('REMOTE_ADDR','-'), \ + environ.get('REMOTE_PORT','-') + self.command = environ.get('REQUEST_METHOD', '-') + + from cStringIO import StringIO + self.wfile = StringIO() # for capturing error + + f = self.send_head() + self.start_response(self.status, self.headers) + + if f: + block_size = 16 * 1024 + while True: + buf = f.read(block_size) + if not buf: + break + yield buf + f.close() + else: + value = self.wfile.getvalue() + yield value + + class WSGIWrapper(BaseHTTPRequestHandler): + """WSGI wrapper for logging the status and serving static files.""" + def __init__(self, app): + self.app = app + self.format = '%s - - [%s] "%s %s %s" - %s' + + def __call__(self, environ, start_response): + def xstart_response(status, response_headers, *args): + write = start_response(status, response_headers, *args) + self.log(status, environ) + return write + + path = environ.get('PATH_INFO', '') + if path.startswith('/static/'): + return StaticApp(environ, xstart_response) + else: + return self.app(environ, xstart_response) + + def log(self, status, environ): + #mvoncken,no logging.. + return + + outfile = environ.get('wsgi.errors', web.debug) + req = environ.get('PATH_INFO', '_') + protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') + method = environ.get('REQUEST_METHOD', '-') + host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), + environ.get('REMOTE_PORT','-')) + + #@@ It is really bad to extend from + #@@ BaseHTTPRequestHandler just for this method + time = self.log_date_time_string() + + print >> outfile, self.format % (host, time, protocol, + method, req, status) + + func = WSGIWrapper(func) + server = CherryPyWSGIServer(server_address, func, server_name="localhost") + + print "http://%s:%d/" % server_address + try: + server.start() + except KeyboardInterrupt: + server.stop() diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/net.py b/deluge/ui/webui/webui_plugin/lib/webpy022/net.py new file mode 100644 index 000000000..b97d4e155 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/net.py @@ -0,0 +1,155 @@ +""" +Network Utilities +(from web.py) +""" + +__all__ = [ + "validipaddr", "validipport", "validip", "validaddr", + "urlquote", + "httpdate", "parsehttpdate", + "htmlquote", "websafe", +] + +import urllib, time +try: import datetime +except ImportError: pass + +def validipaddr(address): + """returns True if `address` is a valid IPv4 address""" + try: + octets = address.split('.') + assert len(octets) == 4 + for x in octets: + assert 0 <= int(x) <= 255 + except (AssertionError, ValueError): + return False + return True + +def validipport(port): + """returns True if `port` is a valid IPv4 port""" + try: + assert 0 <= int(port) <= 65535 + except (AssertionError, ValueError): + return False + return True + +def validip(ip, defaultaddr="0.0.0.0", defaultport=8080): + """returns `(ip_address, port)` from string `ip_addr_port`""" + addr = defaultaddr + port = defaultport + + ip = ip.split(":", 1) + if len(ip) == 1: + if not ip[0]: + pass + elif validipaddr(ip[0]): + addr = ip[0] + elif validipport(ip[0]): + port = int(ip[0]) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + elif len(ip) == 2: + addr, port = ip + if not validipaddr(addr) and validipport(port): + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + port = int(port) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + return (addr, port) + +def validaddr(string_): + """ + returns either (ip_address, port) or "/path/to/socket" from string_ + + >>> validaddr('/path/to/socket') + '/path/to/socket' + >>> validaddr('8000') + ('0.0.0.0', 8000) + >>> validaddr('127.0.0.1') + ('127.0.0.1', 8080) + >>> validaddr('127.0.0.1:8000') + ('127.0.0.1', 8000) + >>> validaddr('fff') + Traceback (most recent call last): + ... + ValueError: fff is not a valid IP address/port + """ + if '/' in string_: + return string_ + else: + return validip(string_) + +def urlquote(val): + """ + Quotes a string for use in a URL. + + >>> urlquote('://?f=1&j=1') + '%3A//%3Ff%3D1%26j%3D1' + >>> urlquote(None) + '' + >>> urlquote(u'\u203d') + '%E2%80%BD' + """ + if val is None: return '' + if not isinstance(val, unicode): val = str(val) + else: val = val.encode('utf-8') + return urllib.quote(val) + +def httpdate(date_obj): + """ + Formats a datetime object for use in HTTP headers. + + >>> import datetime + >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1)) + 'Thu, 01 Jan 1970 01:01:01 GMT' + """ + return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT") + +def parsehttpdate(string_): + """ + Parses an HTTP date into a datetime object. + + >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT') + datetime.datetime(1970, 1, 1, 1, 1, 1) + """ + try: + t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z") + except ValueError: + return None + return datetime.datetime(*t[:6]) + +def htmlquote(text): + """ + Encodes `text` for raw use in HTML. + + >>> htmlquote("<'&\\">") + '<'&">' + """ + text = text.replace("&", "&") # Must be done first! + text = text.replace("<", "<") + text = text.replace(">", ">") + text = text.replace("'", "'") + text = text.replace('"', """) + return text + +def websafe(val): + """ + Converts `val` so that it's safe for use in UTF-8 HTML. + + >>> websafe("<'&\\">") + '<'&">' + >>> websafe(None) + '' + >>> websafe(u'\u203d') + '\\xe2\\x80\\xbd' + """ + if val is None: + return '' + if isinstance(val, unicode): + val = val.encode('utf-8') + val = str(val) + return htmlquote(val) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/request.py b/deluge/ui/webui/webui_plugin/lib/webpy022/request.py new file mode 100644 index 000000000..0826d822a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/request.py @@ -0,0 +1,153 @@ +""" +Request Delegation +(from web.py) +""" + +__all__ = ["handle", "nomethod", "autodelegate", "webpyfunc", "run"] + +import sys, re, types, os.path, urllib + +import http, wsgi, utils, webapi +import webapi as web + +def handle(mapping, fvars=None): + """ + Call the appropriate function based on the url to function mapping in `mapping`. + If no module for the function is specified, look up the function in `fvars`. If + `fvars` is empty, using the caller's context. + + `mapping` should be a tuple of paired regular expressions with function name + substitutions. `handle` will import modules as necessary. + """ + for url, ofno in utils.group(mapping, 2): + if isinstance(ofno, tuple): + ofn, fna = ofno[0], list(ofno[1:]) + else: + ofn, fna = ofno, [] + fn, result = utils.re_subm('^' + url + '$', ofn, web.ctx.path) + if result: # it's a match + if fn.split(' ', 1)[0] == "redirect": + url = fn.split(' ', 1)[1] + if web.ctx.method == "GET": + x = web.ctx.env.get('QUERY_STRING', '') + if x: + url += '?' + x + return http.redirect(url) + elif '.' in fn: + x = fn.split('.') + mod, cls = '.'.join(x[:-1]), x[-1] + mod = __import__(mod, globals(), locals(), [""]) + cls = getattr(mod, cls) + else: + cls = fn + mod = fvars + if isinstance(mod, types.ModuleType): + mod = vars(mod) + try: + cls = mod[cls] + except KeyError: + return web.notfound() + + meth = web.ctx.method + if meth == "HEAD": + if not hasattr(cls, meth): + meth = "GET" + if not hasattr(cls, meth): + return nomethod(cls) + tocall = getattr(cls(), meth) + args = list(result.groups()) + for d in re.findall(r'\\(\d+)', ofn): + args.pop(int(d) - 1) + return tocall(*([x and urllib.unquote(x) for x in args] + fna)) + + return web.notfound() + +def nomethod(cls): + """Returns a `405 Method Not Allowed` error for `cls`.""" + web.ctx.status = '405 Method Not Allowed' + web.header('Content-Type', 'text/html') + web.header('Allow', \ + ', '.join([method for method in \ + ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'] \ + if hasattr(cls, method)])) + + # commented out for the same reason redirect is + # return output('method not allowed') + +def autodelegate(prefix=''): + """ + Returns a method that takes one argument and calls the method named prefix+arg, + calling `notfound()` if there isn't one. Example: + + urls = ('/prefs/(.*)', 'prefs') + + class prefs: + GET = autodelegate('GET_') + def GET_password(self): pass + def GET_privacy(self): pass + + `GET_password` would get called for `/prefs/password` while `GET_privacy` for + `GET_privacy` gets called for `/prefs/privacy`. + + If a user visits `/prefs/password/change` then `GET_password(self, '/change')` + is called. + """ + def internal(self, arg): + if '/' in arg: + first, rest = arg.split('/', 1) + func = prefix + first + args = ['/' + rest] + else: + func = prefix + arg + args = [] + + if hasattr(self, func): + try: + return getattr(self, func)(*args) + except TypeError: + return web.notfound() + else: + return web.notfound() + return internal + +def webpyfunc(inp, fvars, autoreload=False): + """If `inp` is a url mapping, returns a function that calls handle.""" + if not hasattr(inp, '__call__'): + if autoreload: + def modname(): + """find name of the module name from fvars.""" + file, name = fvars['__file__'], fvars['__name__'] + if name == '__main__': + # Since the __main__ module can't be reloaded, the module has + # to be imported using its file name. + name = os.path.splitext(os.path.basename(file))[0] + return name + + mod = __import__(modname(), None, None, [""]) + #@@probably should replace this with some inspect magic + name = utils.dictfind(fvars, inp) + func = lambda: handle(getattr(mod, name), mod) + else: + func = lambda: handle(inp, fvars) + else: + func = inp + return func + +def run(inp, fvars, *middleware): + """ + Starts handling requests. If called in a CGI or FastCGI context, it will follow + that protocol. If called from the command line, it will start an HTTP + server on the port named in the first command line argument, or, if there + is no argument, on port 8080. + + `input` is a callable, then it's called with no arguments. + Otherwise, it's a `mapping` object to be passed to `handle(...)`. + + **Caveat:** So that `reloader` will work correctly, input has to be a variable, + it can't be a tuple passed in directly. + + `middleware` is a list of WSGI middleware which is applied to the resulting WSGI + function. + """ + autoreload = http.reloader in middleware + return wsgi.runwsgi(webapi.wsgifunc(webpyfunc(inp, fvars, autoreload), *middleware)) diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/template.py b/deluge/ui/webui/webui_plugin/lib/webpy022/template.py new file mode 100644 index 000000000..b21903401 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/template.py @@ -0,0 +1,878 @@ +""" +simple, elegant templating +(part of web.py) +""" + +import re, glob, os, os.path +from types import FunctionType as function +from utils import storage, group, utf8 +from net import websafe + +# differences from python: +# - for: has an optional else: that gets called if the loop never runs +# differences to add: +# - you can use the expression inside if, while blocks +# - special for loop attributes, like django? +# - you can check to see if a variable is defined (perhaps w/ get func?) +# all these are probably good ideas for python... + +# todo: +# inline tuple +# relax constraints on spacing +# continue, break, etc. +# tracebacks + +global_globals = {'None':None, 'False':False, 'True': True} +MAX_ITERS = 100000 + +WHAT = 0 +ARGS = 4 +KWARGS = 6 +NAME = 2 +BODY = 4 +CLAUSE = 2 +ELIF = 6 +ELSE = 8 +IN = 6 +NAME = 2 +EXPR = 4 +FILTER = 4 +THING = 2 +ATTR = 4 +ITEM = 4 +NEGATE = 4 +X = 2 +OP = 4 +Y = 6 +LINENO = -1 + +# http://docs.python.org/ref/identifiers.html +r_var = '[a-zA-Z_][a-zA-Z0-9_]*' + +class ParseError(Exception): pass +class Parser: + def __init__(self, text, name=""): + self.t = text + self.p = 0 + self._lock = [False] + self.name = name + + def lock(self): + self._lock[-1] = True + + def curline(self): + return self.t[:self.p].count('\n')+1 + + def csome(self): + return repr(self.t[self.p:self.p+5]+'...') + + def Error(self, x, y=None): + if y is None: y = self.csome() + raise ParseError, "%s: expected %s, got %s (line %s)" % (self.name, x, y, self.curline()) + + def q(self, f): + def internal(*a, **kw): + checkp = self.p + self._lock.append(False) + try: + q = f(*a, **kw) + except ParseError: + if self._lock[-1]: + raise + self.p = checkp + self._lock.pop() + return False + self._lock.pop() + return q or True + return internal + + def tokr(self, t): + text = self.c(len(t)) + if text != t: + self.Error(repr(t), repr(text)) + return t + + def ltokr(self, *l): + for x in l: + o = self.tokq(x) + if o: return o + self.Error('one of '+repr(l)) + + def rer(self, r): + x = re.match(r, self.t[self.p:]) #@@re_compile + if not x: + self.Error('r'+repr(r)) + return self.tokr(x.group()) + + def endr(self): + if self.p != len(self.t): + self.Error('EOF') + + def c(self, n=1): + out = self.t[self.p:self.p+n] + if out == '' and n != 0: + self.Error('character', 'EOF') + self.p += n + return out + + def lookbehind(self, t): + return self.t[self.p-len(t):self.p] == t + + def __getattr__(self, a): + if a.endswith('q'): + return self.q(getattr(self, a[:-1]+'r')) + raise AttributeError, a + +class TemplateParser(Parser): + def __init__(self, *a, **kw): + Parser.__init__(self, *a, **kw) + self.curws = '' + self.curind = '' + + def o(self, *a): + return a+('lineno', self.curline()) + + def go(self): + # maybe try to do some traceback parsing/hacking + return self.gor() + + def gor(self): + header = self.defwithq() + results = self.lines(start=True) + self.endr() + return header, results + + def ws(self): + n = 0 + while self.tokq(" "): n += 1 + return " " * n + + def defwithr(self): + self.tokr('$def with ') + self.lock() + self.tokr('(') + args = [] + kw = [] + x = self.req(r_var) + while x: + if self.tokq('='): + v = self.exprr() + kw.append((x, v)) + else: + args.append(x) + x = self.tokq(', ') and self.req(r_var) + self.tokr(')\n') + return self.o('defwith', 'null', None, 'args', args, 'kwargs', kw) + + def literalr(self): + o = ( + self.req('"[^"]*"') or #@@ no support for escapes + self.req("'[^']*'") + ) + if o is False: + o = self.req('\-?[0-9]+(\.[0-9]*)?') + if o is not False: + if '.' in o: o = float(o) + else: o = int(o) + + if o is False: self.Error('literal') + return self.o('literal', 'thing', o) + + def listr(self): + self.tokr('[') + self.lock() + x = [] + if not self.tokq(']'): + while True: + t = self.exprr() + x.append(t) + if not self.tokq(', '): break + self.tokr(']') + return self.o('list', 'thing', x) + + def dictr(self): + self.tokr('{') + self.lock() + x = {} + if not self.tokq('}'): + while True: + k = self.exprr() + self.tokr(': ') + v = self.exprr() + x[k] = v + if not self.tokq(', '): break + self.tokr('}') + return self.o('dict', 'thing', x) + + def parenr(self): + self.tokr('(') + self.lock() + o = self.exprr() # todo: allow list + self.tokr(')') + return self.o('paren', 'thing', o) + + def atomr(self): + """returns var, literal, paren, dict, or list""" + o = ( + self.varq() or + self.parenq() or + self.dictq() or + self.listq() or + self.literalq() + ) + if o is False: self.Error('atom') + return o + + def primaryr(self): + """returns getattr, call, or getitem""" + n = self.atomr() + while 1: + if self.tokq('.'): + v = self.req(r_var) + if not v: + self.p -= 1 # get rid of the '.' + break + else: + n = self.o('getattr', 'thing', n, 'attr', v) + elif self.tokq('('): + args = [] + kw = [] + + while 1: + # need to see if we're doing a keyword argument + checkp = self.p + k = self.req(r_var) + if k and self.tokq('='): # yup + v = self.exprr() + kw.append((k, v)) + else: + self.p = checkp + x = self.exprq() + if x: # at least it's something + args.append(x) + else: + break + + if not self.tokq(', '): break + self.tokr(')') + n = self.o('call', 'thing', n, 'args', args, 'kwargs', kw) + elif self.tokq('['): + v = self.exprr() + self.tokr(']') + n = self.o('getitem', 'thing', n, 'item', v) + else: + break + + return n + + def exprr(self): + negate = self.tokq('not ') + x = self.primaryr() + if self.tokq(' '): + operator = self.ltokr('not in', 'in', 'is not', 'is', '==', '!=', '>=', '<=', '<', '>', 'and', 'or', '*', '+', '-', '/', '%') + self.tokr(' ') + y = self.exprr() + x = self.o('test', 'x', x, 'op', operator, 'y', y) + + return self.o('expr', 'thing', x, 'negate', negate) + + def varr(self): + return self.o('var', 'name', self.rer(r_var)) + + def liner(self): + out = [] + o = self.curws + while 1: + c = self.c() + self.lock() + if c == '\n': + self.p -= 1 + break + if c == '$': + if self.lookbehind('\\$'): + o = o[:-1] + c + else: + filter = not bool(self.tokq(':')) + + if self.tokq('{'): + out.append(o) + out.append(self.o('itpl', 'name', self.exprr(), 'filter', filter)) + self.tokr('}') + o = '' + else: + g = self.primaryq() + if g: + out.append(o) + out.append(self.o('itpl', 'name', g, 'filter', filter)) + o = '' + else: + o += c + else: + o += c + self.tokr('\n') + if not self.lookbehind('\\\n'): + o += '\n' + else: + o = o[:-1] + out.append(o) + return self.o('line', 'thing', out) + + def varsetr(self): + self.tokr('$var ') + self.lock() + what = self.rer(r_var) + self.tokr(':') + body = self.lines() + return self.o('varset', 'name', what, 'body', body) + + def ifr(self): + self.tokr("$if ") + self.lock() + expr = self.exprr() + self.tokr(":") + ifc = self.lines() + + elifs = [] + while self.tokq(self.curws + self.curind + '$elif '): + v = self.exprr() + self.tokr(':') + c = self.lines() + elifs.append(self.o('elif', 'clause', v, 'body', c)) + + if self.tokq(self.curws + self.curind + "$else:"): + elsec = self.lines() + else: + elsec = None + + return self.o('if', 'clause', expr, 'then', ifc, 'elif', elifs, 'else', elsec) + + def forr(self): + self.tokr("$for ") + self.lock() + v = self.setabler() + self.tokr(" in ") + g = self.exprr() + self.tokr(":") + l = self.lines() + + if self.tokq(self.curws + self.curind + '$else:'): + elsec = self.lines() + else: + elsec = None + + return self.o('for', 'name', v, 'body', l, 'in', g, 'else', elsec) + + def whiler(self): + self.tokr('$while ') + self.lock() + v = self.exprr() + self.tokr(":") + l = self.lines() + + if self.tokq(self.curws + self.curind + '$else:'): + elsec = self.lines() + else: + elsec = None + + return self.o('while', 'clause', v, 'body', l, 'null', None, 'else', elsec) + + def assignr(self): + self.tokr('$ ') + assign = self.rer(r_var) # NOTE: setable + self.tokr(' = ') + expr = self.exprr() + self.tokr('\n') + + return self.o('assign', 'name', assign, 'expr', expr) + + def commentr(self): + self.tokr('$#') + self.lock() + while self.c() != '\n': pass + return self.o('comment') + + def setabler(self): + out = [self.varr()] #@@ not quite right + while self.tokq(', '): + out.append(self.varr()) + return out + + def lines(self, start=False): + """ + This function gets called from two places: + 1. at the start, where it's matching the document itself + 2. after any command, where it matches one line or an indented block + """ + o = [] + if not start: # try to match just one line + singleline = self.tokq(' ') and self.lineq() + if singleline: + return [singleline] + else: + self.rer(' *') #@@slurp space? + self.tokr('\n') + oldind = self.curind + self.curind += ' ' + while 1: + oldws = self.curws + t = self.tokq(oldws + self.curind) + if not t: break + + self.curws += self.ws() + x = t and ( + self.varsetq() or + self.ifq() or + self.forq() or + self.whileq() or + self.assignq() or + self.commentq() or + self.lineq()) + self.curws = oldws + if not x: + break + elif x[WHAT] == 'comment': + pass + else: + o.append(x) + + if not start: self.curind = oldind + return o + +class Stowage(storage): + def __str__(self): return self.get('_str') + #@@ edits in place + def __add__(self, other): + if isinstance(other, (unicode, str)): + self._str += other + return self + else: + raise TypeError, 'cannot add' + def __radd__(self, other): + if isinstance(other, (unicode, str)): + self._str = other + self._str + return self + else: + raise TypeError, 'cannot add' + +class WTF(AssertionError): pass +class SecurityError(Exception): + """The template seems to be trying to do something naughty.""" + pass + + + + +Required = object() +class Template: + globals = {} + content_types = { + '.html' : 'text/html; charset=utf-8', + '.txt' : 'text/plain', + } + + def __init__(self, text, filter=None, filename=""): + self.filter = filter + self.filename = filename + # universal newlines: + text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs() + if not text.endswith('\n'): text += '\n' + header, tree = TemplateParser(text, filename).go() + self.tree = tree + if header: + self.h_defwith(header) + else: + self.args, self.kwargs = (), {} + + def __call__(self, *a, **kw): + d = self.globals.copy() + d.update(self._parseargs(a, kw)) + f = Fill(self.tree, d=d) + if self.filter: f.filter = self.filter + + import webapi as web + if 'headers' in web.ctx and self.filename: + content_type = self.find_content_type() + if content_type: + web.header('Content-Type', content_type, unique=True) + + return f.go() + + def find_content_type(self): + for ext, content_type in self.content_types.iteritems(): + if self.filename.endswith(ext): + return content_type + + def _parseargs(self, inargs, inkwargs): + # difference from Python: + # no error on setting a keyword arg twice + d = {} + for arg in self.args: + d[arg] = Required + for kw, val in self.kwargs: + d[kw] = val + + for n, val in enumerate(inargs): + if n < len(self.args): + d[self.args[n]] = val + elif n < len(self.args)+len(self.kwargs): + kw = self.kwargs[n - len(self.args)][0] + d[kw] = val + + for kw, val in inkwargs.iteritems(): + d[kw] = val + + unset = [] + for k, v in d.iteritems(): + if v is Required: + unset.append(k) + if unset: + raise TypeError, 'values for %s are required' % unset + + return d + + def h_defwith(self, header): + assert header[WHAT] == 'defwith' + f = Fill(self.tree, d={}) + + self.args = header[ARGS] + self.kwargs = [] + for var, valexpr in header[KWARGS]: + self.kwargs.append((var, f.h(valexpr))) + + def __repr__(self): + return "" % self.filename + +class Handle: + def __init__(self, parsetree, **kw): + self._funccache = {} + self.parsetree = parsetree + for (k, v) in kw.iteritems(): setattr(self, k, v) + + def h(self, item): + return getattr(self, 'h_' + item[WHAT])(item) + +class Fill(Handle): + builtins = global_globals + def filter(self, text): + if text is None: return '' + else: return utf8(text) + # often replaced with stuff like net.websafe + + def h_literal(self, i): + item = i[THING] + if isinstance(item, (unicode, str)) and item[0] in ['"', "'"]: + item = item[1:-1] + elif isinstance(item, (float, int)): + pass + return item + + def h_list(self, i): + x = i[THING] + out = [] + for item in x: + out.append(self.h(item)) + return out + + def h_dict(self, i): + x = i[THING] + out = {} + for k, v in x.iteritems(): + out[self.h(k)] = self.h(v) + return out + + def h_paren(self, i): + item = i[THING] + if isinstance(item, list): + raise NotImplementedError, 'tuples' + return self.h(item) + + def h_getattr(self, i): + thing, attr = i[THING], i[ATTR] + thing = self.h(thing) + if attr.startswith('_') or attr.startswith('func_') or attr.startswith('im_'): + raise SecurityError, 'tried to get ' + attr + try: + if thing in self.builtins: + raise SecurityError, 'tried to getattr on ' + repr(thing) + except TypeError: + pass # raised when testing an unhashable object + try: + return getattr(thing, attr) + except AttributeError: + if isinstance(thing, list) and attr == 'join': + return lambda s: s.join(thing) + else: + raise + + def h_call(self, i): + call = self.h(i[THING]) + args = [self.h(x) for x in i[ARGS]] + kw = dict([(x, self.h(y)) for (x, y) in i[KWARGS]]) + return call(*args, **kw) + + def h_getitem(self, i): + thing, item = i[THING], i[ITEM] + thing = self.h(thing) + item = self.h(item) + return thing[item] + + def h_expr(self, i): + item = self.h(i[THING]) + if i[NEGATE]: + item = not item + return item + + def h_test(self, item): + ox, op, oy = item[X], item[OP], item[Y] + # for short-circuiting to work, we can't eval these here + e = self.h + if op == 'is': + return e(ox) is e(oy) + elif op == 'is not': + return e(ox) is not e(oy) + elif op == 'in': + return e(ox) in e(oy) + elif op == 'not in': + return e(ox) not in e(oy) + elif op == '==': + return e(ox) == e(oy) + elif op == '!=': + return e(ox) != e(oy) + elif op == '>': + return e(ox) > e(oy) + elif op == '<': + return e(ox) < e(oy) + elif op == '<=': + return e(ox) <= e(oy) + elif op == '>=': + return e(ox) >= e(oy) + elif op == 'and': + return e(ox) and e(oy) + elif op == 'or': + return e(ox) or e(oy) + elif op == '+': + return e(ox) + e(oy) + elif op == '-': + return e(ox) - e(oy) + elif op == '*': + return e(ox) * e(oy) + elif op == '/': + return e(ox) / e(oy) + elif op == '%': + return e(ox) % e(oy) + else: + raise WTF, 'op ' + op + + def h_var(self, i): + v = i[NAME] + if v in self.d: + return self.d[v] + elif v in self.builtins: + return self.builtins[v] + elif v == 'self': + return self.output + else: + raise NameError, 'could not find %s (line %s)' % (repr(i[NAME]), i[LINENO]) + + def h_line(self, i): + out = [] + for x in i[THING]: + #@@ what if x is unicode + if isinstance(x, str): + out.append(x) + elif x[WHAT] == 'itpl': + o = self.h(x[NAME]) + if x[FILTER]: + o = self.filter(o) + else: + o = (o is not None and utf8(o)) or "" + out.append(o) + else: + raise WTF, x + return ''.join(out) + + def h_varset(self, i): + self.output[i[NAME]] = ''.join(self.h_lines(i[BODY])) + return '' + + def h_if(self, i): + expr = self.h(i[CLAUSE]) + if expr: + do = i[BODY] + else: + for e in i[ELIF]: + expr = self.h(e[CLAUSE]) + if expr: + do = e[BODY] + break + else: + do = i[ELSE] + return ''.join(self.h_lines(do)) + + def h_for(self, i): + out = [] + assert i[IN][WHAT] == 'expr' + invar = self.h(i[IN]) + forvar = i[NAME] + if invar: + for nv in invar: + if len(forvar) == 1: + fv = forvar[0] + assert fv[WHAT] == 'var' + self.d[fv[NAME]] = nv # same (lack of) scoping as Python + else: + for x, y in zip(forvar, nv): + assert x[WHAT] == 'var' + self.d[x[NAME]] = y + + out.extend(self.h_lines(i[BODY])) + else: + if i[ELSE]: + out.extend(self.h_lines(i[ELSE])) + return ''.join(out) + + def h_while(self, i): + out = [] + expr = self.h(i[CLAUSE]) + if not expr: + return ''.join(self.h_lines(i[ELSE])) + c = 0 + while expr: + c += 1 + if c >= MAX_ITERS: + raise RuntimeError, 'too many while-loop iterations (line %s)' % i[LINENO] + out.extend(self.h_lines(i[BODY])) + expr = self.h(i[CLAUSE]) + return ''.join(out) + + def h_assign(self, i): + self.d[i[NAME]] = self.h(i[EXPR]) + return '' + + def h_comment(self, i): pass + + def h_lines(self, lines): + if lines is None: return [] + return map(self.h, lines) + + def go(self): + self.output = Stowage() + self.output._str = ''.join(map(self.h, self.parsetree)) + if self.output.keys() == ['_str']: + self.output = self.output['_str'] + return self.output + +class render: + def __init__(self, loc='templates/', cache=True): + self.loc = loc + if cache: + self.cache = {} + else: + self.cache = False + + def _do(self, name, filter=None): + if self.cache is False or name not in self.cache: + + tmplpath = os.path.join(self.loc, name) + p = [f for f in glob.glob(tmplpath + '.*') if not f.endswith('~')] # skip backup files + if not p and os.path.isdir(tmplpath): + return render(tmplpath, cache=self.cache) + elif not p: + raise AttributeError, 'no template named ' + name + + p = p[0] + c = Template(open(p).read(), filename=p) + if self.cache is not False: self.cache[name] = (p, c) + + if self.cache is not False: p, c = self.cache[name] + + if p.endswith('.html') or p.endswith('.xml'): + if not filter: c.filter = websafe + return c + + def __getattr__(self, p): + return self._do(p) + +def frender(fn, *a, **kw): + return Template(open(fn).read(), *a, **kw) + +def test(): + import sys + verbose = '-v' in sys.argv + def assertEqual(a, b): + if a == b: + if verbose: + sys.stderr.write('.') + sys.stderr.flush() + else: + assert a == b, "\nexpected: %s\ngot: %s" % (repr(b), repr(a)) + + from utils import storage, group + + class t: + def __init__(self, text): + self.text = text + + def __call__(self, *a, **kw): + return TestResult(self.text, Template(self.text)(*a, **kw)) + + class TestResult: + def __init__(self, source, value): + self.source = source + self.value = value + + def __eq__(self, other): + if self.value == other: + if verbose: + sys.stderr.write('.') + else: + print >> sys.stderr, 'FAIL:', repr(self.source), 'expected', repr(other), ', got', repr(self.value) + sys.stderr.flush() + + t('1')() == '1\n' + t('$def with ()\n1')() == '1\n' + t('$def with (a)\n$a')(1) == '1\n' + t('$def with (a=0)\n$a')(1) == '1\n' + t('$def with (a=0)\n$a')(a=1) == '1\n' + t('$if 1: 1')() == '1\n' + t('$if 1:\n 1')() == '1\n' + t('$if 0: 0\n$elif 1: 1')() == '1\n' + t('$if 0: 0\n$elif None: 0\n$else: 1')() == '1\n' + t('$if (0 < 1) and (1 < 2): 1')() == '1\n' + t('$for x in [1, 2, 3]: $x')() == '1\n2\n3\n' + t('$for x in []: 0\n$else: 1')() == '1\n' + t('$def with (a)\n$while a and a.pop(): 1')([1, 2, 3]) == '1\n1\n1\n' + t('$while 0: 0\n$else: 1')() == '1\n' + t('$ a = 1\n$a')() == '1\n' + t('$# 0')() == '' + t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1}) == '1\n' + t('$def with (a)\n$(a)')(1) == '1\n' + t('$def with (a)\n$a')(1) == '1\n' + t('$def with (a)\n$a.b')(storage(b=1)) == '1\n' + t('$def with (a)\n$a[0]')([1]) == '1\n' + t('${0 or 1}')() == '1\n' + t('$ a = [1]\n$a[0]')() == '1\n' + t('$ a = {1: 1}\n$a.keys()[0]')() == '1\n' + t('$ a = []\n$if not a: 1')() == '1\n' + t('$ a = {}\n$if not a: 1')() == '1\n' + t('$ a = -1\n$a')() == '-1\n' + t('$ a = "1"\n$a')() == '1\n' + t('$if 1 is 1: 1')() == '1\n' + t('$if not 0: 1')() == '1\n' + t('$if 1:\n $if 1: 1')() == '1\n' + t('$ a = 1\n$a')() == '1\n' + t('$ a = 1.\n$a')() == '1.0\n' + t('$({1: 1}.keys()[0])')() == '1\n' + t('$for x in [1, 2, 3]:\n\t$x')() == ' 1\n 2\n 3\n' + t('$def with (a)\n$:a')(1) == '1\n' + t('$def with (a)\n$a')(u'\u203d') == '\xe2\x80\xbd\n' + t(u'$def with (f)\n$:f("x")')(lambda x: x) == 'x\n' + + j = Template("$var foo: bar")() + assertEqual(str(j), '') + assertEqual(j.foo, 'bar\n') + if verbose: sys.stderr.write('\n') + + +if __name__ == "__main__": + test() diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/utils.py b/deluge/ui/webui/webui_plugin/lib/webpy022/utils.py new file mode 100644 index 000000000..5b6187583 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/utils.py @@ -0,0 +1,796 @@ +""" +General Utilities +(part of web.py) +""" + +__all__ = [ + "Storage", "storage", "storify", + "iters", + "rstrips", "lstrips", "strips", "utf8", + "TimeoutError", "timelimit", + "Memoize", "memoize", + "re_compile", "re_subm", + "group", + "IterBetter", "iterbetter", + "dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd", + "listget", "intget", "datestr", + "numify", "denumify", "dateify", + "CaptureStdout", "capturestdout", "Profile", "profile", + "tryall", + "ThreadedDict", + "autoassign", + "to36", + "safemarkdown" +] + +import re, sys, time, threading +try: import datetime +except ImportError: pass + +class Storage(dict): + """ + A Storage object is like a dictionary except `obj.foo` can be used + in addition to `obj['foo']`. + + >>> o = storage(a=1) + >>> o.a + 1 + >>> o['a'] + 1 + >>> o.a = 2 + >>> o['a'] + 2 + >>> del o.a + >>> o.a + Traceback (most recent call last): + ... + AttributeError: 'a' + + """ + def __getattr__(self, key): + try: + return self[key] + except KeyError, k: + raise AttributeError, k + + def __setattr__(self, key, value): + self[key] = value + + def __delattr__(self, key): + try: + del self[key] + except KeyError, k: + raise AttributeError, k + + def __repr__(self): + return '' + +storage = Storage + +def storify(mapping, *requireds, **defaults): + """ + Creates a `storage` object from dictionary `mapping`, raising `KeyError` if + d doesn't have all of the keys in `requireds` and using the default + values for keys found in `defaults`. + + For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of + `storage({'a':1, 'b':2, 'c':3})`. + + If a `storify` value is a list (e.g. multiple values in a form submission), + `storify` returns the last element of the list, unless the key appears in + `defaults` as a list. Thus: + + >>> storify({'a':[1, 2]}).a + 2 + >>> storify({'a':[1, 2]}, a=[]).a + [1, 2] + >>> storify({'a':1}, a=[]).a + [1] + >>> storify({}, a=[]).a + [] + + Similarly, if the value has a `value` attribute, `storify will return _its_ + value, unless the key appears in `defaults` as a dictionary. + + >>> storify({'a':storage(value=1)}).a + 1 + >>> storify({'a':storage(value=1)}, a={}).a + + >>> storify({}, a={}).a + {} + + """ + def getvalue(x): + if hasattr(x, 'value'): + return x.value + else: + return x + + stor = Storage() + for key in requireds + tuple(mapping.keys()): + value = mapping[key] + if isinstance(value, list): + if isinstance(defaults.get(key), list): + value = [getvalue(x) for x in value] + else: + value = value[-1] + if not isinstance(defaults.get(key), dict): + value = getvalue(value) + if isinstance(defaults.get(key), list) and not isinstance(value, list): + value = [value] + setattr(stor, key, value) + + for (key, value) in defaults.iteritems(): + result = value + if hasattr(stor, key): + result = stor[key] + if value == () and not isinstance(result, tuple): + result = (result,) + setattr(stor, key, result) + + return stor + +iters = [list, tuple] +import __builtin__ +if hasattr(__builtin__, 'set'): + iters.append(set) +try: + from sets import Set + iters.append(Set) +except ImportError: + pass + +class _hack(tuple): pass +iters = _hack(iters) +iters.__doc__ = """ +A list of iterable items (like lists, but not strings). Includes whichever +of lists, tuples, sets, and Sets are available in this version of Python. +""" + +def _strips(direction, text, remove): + if direction == 'l': + if text.startswith(remove): + return text[len(remove):] + elif direction == 'r': + if text.endswith(remove): + return text[:-len(remove)] + else: + raise ValueError, "Direction needs to be r or l." + return text + +def rstrips(text, remove): + """ + removes the string `remove` from the right of `text` + + >>> rstrips("foobar", "bar") + 'foo' + + """ + return _strips('r', text, remove) + +def lstrips(text, remove): + """ + removes the string `remove` from the left of `text` + + >>> lstrips("foobar", "foo") + 'bar' + + """ + return _strips('l', text, remove) + +def strips(text, remove): + """removes the string `remove` from the both sides of `text` + + >>> strips("foobarfoo", "foo") + 'bar' + + """ + return rstrips(lstrips(text, remove), remove) + +def utf8(text): + """Encodes text in utf-8. + + >> utf8(u'\u1234') # doctest doesn't seem to like utf-8 + '\xe1\x88\xb4' + + >>> utf8('hello') + 'hello' + >>> utf8(42) + '42' + """ + if isinstance(text, unicode): + return text.encode('utf-8') + elif isinstance(text, str): + return text + else: + return str(text) + +class TimeoutError(Exception): pass +def timelimit(timeout): + """ + A decorator to limit a function to `timeout` seconds, raising `TimeoutError` + if it takes longer. + + >>> import time + >>> def meaningoflife(): + ... time.sleep(.2) + ... return 42 + >>> + >>> timelimit(.1)(meaningoflife)() + Traceback (most recent call last): + ... + TimeoutError: took too long + >>> timelimit(1)(meaningoflife)() + 42 + + _Caveat:_ The function isn't stopped after `timeout` seconds but continues + executing in a separate thread. (There seems to be no way to kill a thread.) + + inspired by + """ + def _1(function): + def _2(*args, **kw): + class Dispatch(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.result = None + self.error = None + + self.setDaemon(True) + self.start() + + def run(self): + try: + self.result = function(*args, **kw) + except: + self.error = sys.exc_info() + + c = Dispatch() + c.join(timeout) + if c.isAlive(): + raise TimeoutError, 'took too long' + if c.error: + raise c.error[0], c.error[1] + return c.result + return _2 + return _1 + +class Memoize: + """ + 'Memoizes' a function, caching its return values for each input. + + >>> import time + >>> def meaningoflife(): + ... time.sleep(.2) + ... return 42 + >>> fastlife = memoize(meaningoflife) + >>> meaningoflife() + 42 + >>> timelimit(.1)(meaningoflife)() + Traceback (most recent call last): + ... + TimeoutError: took too long + >>> fastlife() + 42 + >>> timelimit(.1)(fastlife)() + 42 + + """ + def __init__(self, func): + self.func = func + self.cache = {} + def __call__(self, *args, **keywords): + key = (args, tuple(keywords.items())) + if key not in self.cache: + self.cache[key] = self.func(*args, **keywords) + return self.cache[key] + +memoize = Memoize + +re_compile = memoize(re.compile) #@@ threadsafe? +re_compile.__doc__ = """ +A memoized version of re.compile. +""" + +class _re_subm_proxy: + def __init__(self): + self.match = None + def __call__(self, match): + self.match = match + return '' + +def re_subm(pat, repl, string): + """ + Like re.sub, but returns the replacement _and_ the match object. + + >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball') + >>> t + 'foooooolish' + >>> m.groups() + ('oooooo',) + """ + compiled_pat = re_compile(pat) + proxy = _re_subm_proxy() + compiled_pat.sub(proxy.__call__, string) + return compiled_pat.sub(repl, string), proxy.match + +def group(seq, size): + """ + Returns an iterator over a series of lists of length size from iterable. + + >>> list(group([1,2,3,4], 2)) + [[1, 2], [3, 4]] + """ + if not hasattr(seq, 'next'): + seq = iter(seq) + while True: + yield [seq.next() for i in xrange(size)] + +class IterBetter: + """ + Returns an object that can be used as an iterator + but can also be used via __getitem__ (although it + cannot go backwards -- that is, you cannot request + `iterbetter[0]` after requesting `iterbetter[1]`). + + >>> import itertools + >>> c = iterbetter(itertools.count()) + >>> c[1] + 1 + >>> c[5] + 5 + >>> c[3] + Traceback (most recent call last): + ... + IndexError: already passed 3 + """ + def __init__(self, iterator): + self.i, self.c = iterator, 0 + def __iter__(self): + while 1: + yield self.i.next() + self.c += 1 + def __getitem__(self, i): + #todo: slices + if i < self.c: + raise IndexError, "already passed "+str(i) + try: + while i > self.c: + self.i.next() + self.c += 1 + # now self.c == i + self.c += 1 + return self.i.next() + except StopIteration: + raise IndexError, str(i) +iterbetter = IterBetter + +def dictreverse(mapping): + """ + >>> dictreverse({1: 2, 3: 4}) + {2: 1, 4: 3} + """ + return dict([(value, key) for (key, value) in mapping.iteritems()]) + +def dictfind(dictionary, element): + """ + Returns a key whose value in `dictionary` is `element` + or, if none exists, None. + + >>> d = {1:2, 3:4} + >>> dictfind(d, 4) + 3 + >>> dictfind(d, 5) + """ + for (key, value) in dictionary.iteritems(): + if element is value: + return key + +def dictfindall(dictionary, element): + """ + Returns the keys whose values in `dictionary` are `element` + or, if none exists, []. + + >>> d = {1:4, 3:4} + >>> dictfindall(d, 4) + [1, 3] + >>> dictfindall(d, 5) + [] + """ + res = [] + for (key, value) in dictionary.iteritems(): + if element is value: + res.append(key) + return res + +def dictincr(dictionary, element): + """ + Increments `element` in `dictionary`, + setting it to one if it doesn't exist. + + >>> d = {1:2, 3:4} + >>> dictincr(d, 1) + 3 + >>> d[1] + 3 + >>> dictincr(d, 5) + 1 + >>> d[5] + 1 + """ + dictionary.setdefault(element, 0) + dictionary[element] += 1 + return dictionary[element] + +def dictadd(*dicts): + """ + Returns a dictionary consisting of the keys in the argument dictionaries. + If they share a key, the value from the last argument is used. + + >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1}) + {1: 0, 2: 1, 3: 1} + """ + result = {} + for dct in dicts: + result.update(dct) + return result + +def listget(lst, ind, default=None): + """ + Returns `lst[ind]` if it exists, `default` otherwise. + + >>> listget(['a'], 0) + 'a' + >>> listget(['a'], 1) + >>> listget(['a'], 1, 'b') + 'b' + """ + if len(lst)-1 < ind: + return default + return lst[ind] + +def intget(integer, default=None): + """ + Returns `integer` as an int or `default` if it can't. + + >>> intget('3') + 3 + >>> intget('3a') + >>> intget('3a', 0) + 0 + """ + try: + return int(integer) + except (TypeError, ValueError): + return default + +def datestr(then, now=None): + """ + Converts a (UTC) datetime object to a nice string representation. + + >>> from datetime import datetime, timedelta + >>> d = datetime(1970, 5, 1) + >>> datestr(d, now=d) + '0 microseconds ago' + >>> for t, v in { + ... timedelta(microseconds=1): '1 microsecond ago', + ... timedelta(microseconds=2): '2 microseconds ago', + ... -timedelta(microseconds=1): '1 microsecond from now', + ... -timedelta(microseconds=2): '2 microseconds from now', + ... timedelta(microseconds=2000): '2 milliseconds ago', + ... timedelta(seconds=2): '2 seconds ago', + ... timedelta(seconds=2*60): '2 minutes ago', + ... timedelta(seconds=2*60*60): '2 hours ago', + ... timedelta(days=2): '2 days ago', + ... }.iteritems(): + ... assert datestr(d, now=d+t) == v + >>> datestr(datetime(1970, 1, 1), now=d) + 'January 1' + >>> datestr(datetime(1969, 1, 1), now=d) + 'January 1, 1969' + >>> datestr(datetime(1970, 6, 1), now=d) + 'June 1, 1970' + """ + def agohence(n, what, divisor=None): + if divisor: n = n // divisor + + out = str(abs(n)) + ' ' + what # '2 day' + if abs(n) != 1: out += 's' # '2 days' + out += ' ' # '2 days ' + if n < 0: + out += 'from now' + else: + out += 'ago' + return out # '2 days ago' + + oneday = 24 * 60 * 60 + + if not now: now = datetime.datetime.utcnow() + if type(now).__name__ == "DateTime": + now = datetime.datetime.fromtimestamp(now) + if type(then).__name__ == "DateTime": + then = datetime.datetime.fromtimestamp(then) + delta = now - then + deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06) + deltadays = abs(deltaseconds) // oneday + if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor + + if deltadays: + if abs(deltadays) < 4: + return agohence(deltadays, 'day') + + out = then.strftime('%B %e') # e.g. 'June 13' + if then.year != now.year or deltadays < 0: + out += ', %s' % then.year + return out + + if int(deltaseconds): + if abs(deltaseconds) > (60 * 60): + return agohence(deltaseconds, 'hour', 60 * 60) + elif abs(deltaseconds) > 60: + return agohence(deltaseconds, 'minute', 60) + else: + return agohence(deltaseconds, 'second') + + deltamicroseconds = delta.microseconds + if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity + if abs(deltamicroseconds) > 1000: + return agohence(deltamicroseconds, 'millisecond', 1000) + + return agohence(deltamicroseconds, 'microsecond') + +def numify(string): + """ + Removes all non-digit characters from `string`. + + >>> numify('800-555-1212') + '8005551212' + >>> numify('800.555.1212') + '8005551212' + + """ + return ''.join([c for c in str(string) if c.isdigit()]) + +def denumify(string, pattern): + """ + Formats `string` according to `pattern`, where the letter X gets replaced + by characters from `string`. + + >>> denumify("8005551212", "(XXX) XXX-XXXX") + '(800) 555-1212' + + """ + out = [] + for c in pattern: + if c == "X": + out.append(string[0]) + string = string[1:] + else: + out.append(c) + return ''.join(out) + +def dateify(datestring): + """ + Formats a numified `datestring` properly. + """ + return denumify(datestring, "XXXX-XX-XX XX:XX:XX") + +class CaptureStdout: + """ + Captures everything `func` prints to stdout and returns it instead. + + >>> def idiot(): + ... print "foo" + >>> capturestdout(idiot)() + 'foo\\n' + + **WARNING:** Not threadsafe! + """ + def __init__(self, func): + self.func = func + def __call__(self, *args, **keywords): + from cStringIO import StringIO + # Not threadsafe! + out = StringIO() + oldstdout = sys.stdout + sys.stdout = out + try: + self.func(*args, **keywords) + finally: + sys.stdout = oldstdout + return out.getvalue() + +capturestdout = CaptureStdout + +class Profile: + """ + Profiles `func` and returns a tuple containing its output + and a string with human-readable profiling information. + + >>> import time + >>> out, inf = profile(time.sleep)(.001) + >>> out + >>> inf[:10].strip() + 'took 0.0' + """ + def __init__(self, func): + self.func = func + def __call__(self, *args): ##, **kw): kw unused + import hotshot, hotshot.stats, tempfile ##, time already imported + temp = tempfile.NamedTemporaryFile() + prof = hotshot.Profile(temp.name) + + stime = time.time() + result = prof.runcall(self.func, *args) + stime = time.time() - stime + + prof.close() + stats = hotshot.stats.load(temp.name) + stats.strip_dirs() + stats.sort_stats('time', 'calls') + x = '\n\ntook '+ str(stime) + ' seconds\n' + x += capturestdout(stats.print_stats)(40) + x += capturestdout(stats.print_callers)() + return result, x + +profile = Profile + + +import traceback +# hack for compatibility with Python 2.3: +if not hasattr(traceback, 'format_exc'): + from cStringIO import StringIO + def format_exc(limit=None): + strbuf = StringIO() + traceback.print_exc(limit, strbuf) + return strbuf.getvalue() + traceback.format_exc = format_exc + +def tryall(context, prefix=None): + """ + Tries a series of functions and prints their results. + `context` is a dictionary mapping names to values; + the value will only be tried if it's callable. + + >>> tryall(dict(j=lambda: True)) + j: True + ---------------------------------------- + results: + True: 1 + + For example, you might have a file `test/stuff.py` + with a series of functions testing various things in it. + At the bottom, have a line: + + if __name__ == "__main__": tryall(globals()) + + Then you can run `python test/stuff.py` and get the results of + all the tests. + """ + context = context.copy() # vars() would update + results = {} + for (key, value) in context.iteritems(): + if not hasattr(value, '__call__'): + continue + if prefix and not key.startswith(prefix): + continue + print key + ':', + try: + r = value() + dictincr(results, r) + print r + except: + print 'ERROR' + dictincr(results, 'ERROR') + print ' ' + '\n '.join(traceback.format_exc().split('\n')) + + print '-'*40 + print 'results:' + for (key, value) in results.iteritems(): + print ' '*2, str(key)+':', value + +class ThreadedDict: + """ + Takes a dictionary that maps threads to objects. + When a thread tries to get or set an attribute or item + of the threadeddict, it passes it on to the object + for that thread in dictionary. + """ + def __init__(self, dictionary): + self.__dict__['_ThreadedDict__d'] = dictionary + + def __getattr__(self, attr): + return getattr(self.__d[threading.currentThread()], attr) + + def __getitem__(self, item): + return self.__d[threading.currentThread()][item] + + def __setattr__(self, attr, value): + if attr == '__doc__': + self.__dict__[attr] = value + else: + return setattr(self.__d[threading.currentThread()], attr, value) + + def __delattr__(self, item): + try: + del self.__d[threading.currentThread()][item] + except KeyError, k: + raise AttributeError, k + + def __delitem__(self, item): + del self.__d[threading.currentThread()][item] + + def __setitem__(self, item, value): + self.__d[threading.currentThread()][item] = value + + def __hash__(self): + return hash(self.__d[threading.currentThread()]) + +threadeddict = ThreadedDict + +def autoassign(self, locals): + """ + Automatically assigns local variables to `self`. + + >>> self = storage() + >>> autoassign(self, dict(a=1, b=2)) + >>> self + + + Generally used in `__init__` methods, as in: + + def __init__(self, foo, bar, baz=1): autoassign(self, locals()) + """ + for (key, value) in locals.iteritems(): + if key == 'self': + continue + setattr(self, key, value) + +def to36(q): + """ + Converts an integer to base 36 (a useful scheme for human-sayable IDs). + + >>> to36(35) + 'z' + >>> to36(119292) + '2k1o' + >>> int(to36(939387374), 36) + 939387374 + >>> to36(0) + '0' + >>> to36(-393) + Traceback (most recent call last): + ... + ValueError: must supply a positive integer + + """ + if q < 0: raise ValueError, "must supply a positive integer" + letters = "0123456789abcdefghijklmnopqrstuvwxyz" + converted = [] + while q != 0: + q, r = divmod(q, 36) + converted.insert(0, letters[r]) + return "".join(converted) or '0' + + +r_url = re_compile('(?', text) + text = markdown(text) + return text + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/webapi.py b/deluge/ui/webui/webui_plugin/lib/webpy022/webapi.py new file mode 100644 index 000000000..39d3be873 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/webapi.py @@ -0,0 +1,369 @@ +""" +Web API (wrapper around WSGI) +(from web.py) +""" + +__all__ = [ + "config", + "badrequest", "notfound", "gone", "internalerror", + "header", "output", "flush", "debug", + "input", "data", + "setcookie", "cookies", + "ctx", + "loadhooks", "load", "unloadhooks", "unload", "_loadhooks", + "wsgifunc" +] + +import sys, os, cgi, threading, Cookie, pprint, traceback +try: import itertools +except ImportError: pass +from utils import storage, storify, threadeddict, dictadd, intget, lstrips, utf8 + +config = storage() +config.__doc__ = """ +A configuration object for various aspects of web.py. + +`db_parameters` + : A dictionary containing the parameters to be passed to `connect` + when `load()` is called. +`db_printing` + : Set to `True` if you would like SQL queries and timings to be + printed to the debug output. + +""" + +def badrequest(): + """Return a `400 Bad Request` error.""" + ctx.status = '400 Bad Request' + header('Content-Type', 'text/html') + return output('bad request') + +def notfound(): + """Returns a `404 Not Found` error.""" + ctx.status = '404 Not Found' + header('Content-Type', 'text/html') + return output('not found') + +def gone(): + """Returns a `410 Gone` error.""" + ctx.status = '410 Gone' + header('Content-Type', 'text/html') + return output("gone") + +def internalerror(): + """Returns a `500 Internal Server` error.""" + ctx.status = "500 Internal Server Error" + ctx.headers = [('Content-Type', 'text/html')] + ctx.output = "internal server error" + +def header(hdr, value, unique=False): + """ + Adds the header `hdr: value` with the response. + + If `unique` is True and a header with that name already exists, + it doesn't add a new one. + """ + hdr, value = utf8(hdr), utf8(value) + # protection against HTTP response splitting attack + if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value: + raise ValueError, 'invalid characters in header' + + if unique is True: + for h, v in ctx.headers: + if h.lower() == hdr.lower(): return + + ctx.headers.append((hdr, value)) + +def output(string_): + """Appends `string_` to the response.""" + if isinstance(string_, unicode): string_ = string_.encode('utf8') + if ctx.get('flush'): + ctx._write(string_) + else: + ctx.output += str(string_) + +def flush(): + ctx.flush = True + return flush + +def input(*requireds, **defaults): + """ + Returns a `storage` object with the GET and POST arguments. + See `storify` for how `requireds` and `defaults` work. + """ + from cStringIO import StringIO + def dictify(fs): return dict([(k, fs[k]) for k in fs.keys()]) + + _method = defaults.pop('_method', 'both') + + e = ctx.env.copy() + a = b = {} + + if _method.lower() in ['both', 'post']: + if e['REQUEST_METHOD'] == 'POST': + a = cgi.FieldStorage(fp = StringIO(data()), environ=e, + keep_blank_values=1) + a = dictify(a) + + if _method.lower() in ['both', 'get']: + e['REQUEST_METHOD'] = 'GET' + b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1)) + + out = dictadd(b, a) + try: + return storify(out, *requireds, **defaults) + except KeyError: + badrequest() + raise StopIteration + +def data(): + """Returns the data sent with the request.""" + if 'data' not in ctx: + cl = intget(ctx.env.get('CONTENT_LENGTH'), 0) + ctx.data = ctx.env['wsgi.input'].read(cl) + return ctx.data + +def setcookie(name, value, expires="", domain=None): + """Sets a cookie.""" + if expires < 0: + expires = -1000000000 + kargs = {'expires': expires, 'path':'/'} + if domain: + kargs['domain'] = domain + # @@ should we limit cookies to a different path? + cookie = Cookie.SimpleCookie() + cookie[name] = value + for key, val in kargs.iteritems(): + cookie[name][key] = val + header('Set-Cookie', cookie.items()[0][1].OutputString()) + +def cookies(*requireds, **defaults): + """ + Returns a `storage` object with all the cookies in it. + See `storify` for how `requireds` and `defaults` work. + """ + cookie = Cookie.SimpleCookie() + cookie.load(ctx.env.get('HTTP_COOKIE', '')) + try: + return storify(cookie, *requireds, **defaults) + except KeyError: + badrequest() + raise StopIteration + +def debug(*args): + """ + Prints a prettyprinted version of `args` to stderr. + """ + try: + out = ctx.environ['wsgi.errors'] + except: + out = sys.stderr + for arg in args: + print >> out, pprint.pformat(arg) + return '' + +def _debugwrite(x): + try: + out = ctx.environ['wsgi.errors'] + except: + out = sys.stderr + out.write(x) +debug.write = _debugwrite + +class _outputter: + """Wraps `sys.stdout` so that print statements go into the response.""" + def __init__(self, file): self.file = file + def write(self, string_): + if hasattr(ctx, 'output'): + return output(string_) + else: + self.file.write(string_) + def __getattr__(self, attr): return getattr(self.file, attr) + def __getitem__(self, item): return self.file[item] + +def _capturedstdout(): + sysstd = sys.stdout + while hasattr(sysstd, 'file'): + if isinstance(sys.stdout, _outputter): return True + sysstd = sysstd.file + if isinstance(sys.stdout, _outputter): return True + return False + +if not _capturedstdout(): + sys.stdout = _outputter(sys.stdout) + +_context = {threading.currentThread(): storage()} +ctx = context = threadeddict(_context) + +ctx.__doc__ = """ +A `storage` object containing various information about the request: + +`environ` (aka `env`) + : A dictionary containing the standard WSGI environment variables. + +`host` + : The domain (`Host` header) requested by the user. + +`home` + : The base path for the application. + +`ip` + : The IP address of the requester. + +`method` + : The HTTP method used. + +`path` + : The path request. + +`query` + : If there are no query arguments, the empty string. Otherwise, a `?` followed + by the query string. + +`fullpath` + : The full path requested, including query arguments (`== path + query`). + +### Response Data + +`status` (default: "200 OK") + : The status code to be used in the response. + +`headers` + : A list of 2-tuples to be used in the response. + +`output` + : A string to be used as the response. +""" + +loadhooks = {} +_loadhooks = {} + +def load(): + """ + Loads a new context for the thread. + + You can ask for a function to be run at loadtime by + adding it to the dictionary `loadhooks`. + """ + _context[threading.currentThread()] = storage() + ctx.status = '200 OK' + ctx.headers = [] + if config.get('db_parameters'): + import db + db.connect(**config.db_parameters) + + for x in loadhooks.values(): x() + +def _load(env): + load() + ctx.output = '' + ctx.environ = ctx.env = env + ctx.host = env.get('HTTP_HOST') + ctx.homedomain = 'http://' + env.get('HTTP_HOST', '[unknown]') + ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')) + ctx.home = ctx.homedomain + ctx.homepath + ctx.ip = env.get('REMOTE_ADDR') + ctx.method = env.get('REQUEST_METHOD') + ctx.path = env.get('PATH_INFO') + # http://trac.lighttpd.net/trac/ticket/406 requires: + if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'): + ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], + os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))) + + if env.get('QUERY_STRING'): + ctx.query = '?' + env.get('QUERY_STRING', '') + else: + ctx.query = '' + + ctx.fullpath = ctx.path + ctx.query + for x in _loadhooks.values(): x() + +unloadhooks = {} + +def unload(): + """ + Unloads the context for the thread. + + You can ask for a function to be run at loadtime by + adding it ot the dictionary `unloadhooks`. + """ + for x in unloadhooks.values(): x() + # ensures db cursors and such are GCed promptly + del _context[threading.currentThread()] + +def _unload(): + unload() + +def wsgifunc(func, *middleware): + """Returns a WSGI-compatible function from a webpy-function.""" + middleware = list(middleware) + + def wsgifunc(env, start_resp): + _load(env) + try: + result = func() + except StopIteration: + result = None + except: + print >> debug, traceback.format_exc() + result = internalerror() + + is_generator = result and hasattr(result, 'next') + if is_generator: + # wsgi requires the headers first + # so we need to do an iteration + # and save the result for later + try: + firstchunk = result.next() + except StopIteration: + firstchunk = '' + + status, headers, output = ctx.status, ctx.headers, ctx.output + ctx._write = start_resp(status, headers) + + # and now, the fun: + + def cleanup(): + # we insert this little generator + # at the end of our itertools.chain + # so that it unloads the request + # when everything else is done + + yield '' # force it to be a generator + _unload() + + # result is the output of calling the webpy function + # it could be a generator... + + if is_generator: + if firstchunk is flush: + # oh, it's just our special flush mode + # ctx._write is set up, so just continue execution + try: + result.next() + except StopIteration: + pass + + _unload() + return [] + else: + return itertools.chain([firstchunk], result, cleanup()) + + # ... but it's usually just None + # + # output is the stuff in ctx.output + # it's usually a string... + if isinstance(output, str): #@@ other stringlikes? + _unload() + return [output] + # it could be a generator... + elif hasattr(output, 'next'): + return itertools.chain(output, cleanup()) + else: + _unload() + raise Exception, "Invalid ctx.output" + + for mw_func in middleware: + wsgifunc = mw_func(wsgifunc) + + return wsgifunc diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/wsgi.py b/deluge/ui/webui/webui_plugin/lib/webpy022/wsgi.py new file mode 100644 index 000000000..26abf9291 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/wsgi.py @@ -0,0 +1,54 @@ +""" +WSGI Utilities +(from web.py) +""" + +import os, sys + +import http +import webapi as web +from utils import listget +from net import validaddr, validip +import httpserver + +def runfcgi(func, addr=('localhost', 8000)): + """Runs a WSGI function as a FastCGI server.""" + import flup.server.fcgi as flups + return flups.WSGIServer(func, multiplexed=True, bindAddress=addr).run() + +def runscgi(func, addr=('localhost', 4000)): + """Runs a WSGI function as an SCGI server.""" + import flup.server.scgi as flups + return flups.WSGIServer(func, bindAddress=addr).run() + +def runwsgi(func): + """ + Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server, + as appropriate based on context and `sys.argv`. + """ + + if os.environ.has_key('SERVER_SOFTWARE'): # cgi + os.environ['FCGI_FORCE_CGI'] = 'Y' + + if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi + or os.environ.has_key('SERVER_SOFTWARE')): + return runfcgi(func, None) + + if 'fcgi' in sys.argv or 'fastcgi' in sys.argv: + args = sys.argv[1:] + if 'fastcgi' in args: args.remove('fastcgi') + elif 'fcgi' in args: args.remove('fcgi') + if args: + return runfcgi(func, validaddr(args[0])) + else: + return runfcgi(func, None) + + if 'scgi' in sys.argv: + args = sys.argv[1:] + args.remove('scgi') + if args: + return runscgi(func, validaddr(args[0])) + else: + return runscgi(func) + + return httpserver.runsimple(func, validip(listget(sys.argv, 1, ''))) diff --git a/deluge/ui/webui/webui_plugin/lib/webpy022/wsgiserver/__init__.py b/deluge/ui/webui/webui_plugin/lib/webpy022/wsgiserver/__init__.py new file mode 100644 index 000000000..1fe1c71ec --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/webpy022/wsgiserver/__init__.py @@ -0,0 +1,1019 @@ +"""A high-speed, production ready, thread pooled, generic WSGI server. + +Simplest example on how to use this module directly +(without using CherryPy's application machinery): + + from cherrypy import wsgiserver + + def my_crazy_app(environ, start_response): + status = '200 OK' + response_headers = [('Content-type','text/plain')] + start_response(status, response_headers) + return ['Hello world!\n'] + + # Here we set our application to the script_name '/' + wsgi_apps = [('/', my_crazy_app)] + + server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps, + server_name='localhost') + + # Want SSL support? Just set these attributes + # server.ssl_certificate = + # server.ssl_private_key = + + if __name__ == '__main__': + try: + server.start() + except KeyboardInterrupt: + server.stop() + +This won't call the CherryPy engine (application side) at all, only the +WSGI server, which is independant from the rest of CherryPy. Don't +let the name "CherryPyWSGIServer" throw you; the name merely reflects +its origin, not it's coupling. + +The CherryPy WSGI server can serve as many WSGI applications +as you want in one instance: + + wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] + +""" + +import base64 +import Queue +import os +import re +quoted_slash = re.compile("(?i)%2F") +import rfc822 +import socket +try: + import cStringIO as StringIO +except ImportError: + import StringIO +import sys +import threading +import time +import traceback +from urllib import unquote +from urlparse import urlparse + +try: + from OpenSSL import SSL + from OpenSSL import crypto +except ImportError: + SSL = None + +import errno +socket_errors_to_ignore = [] +# Not all of these names will be defined for every platform. +for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET", + "EHOSTDOWN", "EHOSTUNREACH", + "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET", + "WSAENETRESET", "WSAETIMEDOUT"): + if _ in dir(errno): + socket_errors_to_ignore.append(getattr(errno, _)) +# de-dupe the list +socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys() +socket_errors_to_ignore.append("timed out") + + +comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', + 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', + 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', + 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', + 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', + 'WWW-AUTHENTICATE'] + +class HTTPRequest(object): + """An HTTP Request (and response). + + A single HTTP connection may consist of multiple request/response pairs. + + connection: the HTTP Connection object which spawned this request. + rfile: the 'read' fileobject from the connection's socket + ready: when True, the request has been parsed and is ready to begin + generating the response. When False, signals the calling Connection + that the response should not be generated and the connection should + close. + close_connection: signals the calling Connection that the request + should close. This does not imply an error! The client and/or + server may each request that the connection be closed. + chunked_write: if True, output will be encoded with the "chunked" + transfer-coding. This value is set automatically inside + send_headers. + """ + + def __init__(self, connection): + self.connection = connection + self.rfile = self.connection.rfile + self.sendall = self.connection.sendall + self.environ = connection.environ.copy() + + self.ready = False + self.started_response = False + self.status = "" + self.outheaders = [] + self.sent_headers = False + self.close_connection = False + self.chunked_write = False + + def parse_request(self): + """Parse the next HTTP request start-line and message-headers.""" + # HTTP/1.1 connections are persistent by default. If a client + # requests a page, then idles (leaves the connection open), + # then rfile.readline() will raise socket.error("timed out"). + # Note that it does this based on the value given to settimeout(), + # and doesn't need the client to request or acknowledge the close + # (although your TCP stack might suffer for it: cf Apache's history + # with FIN_WAIT_2). + request_line = self.rfile.readline() + if not request_line: + # Force self.ready = False so the connection will close. + self.ready = False + return + + if request_line == "\r\n": + # RFC 2616 sec 4.1: "...if the server is reading the protocol + # stream at the beginning of a message and receives a CRLF + # first, it should ignore the CRLF." + # But only ignore one leading line! else we enable a DoS. + request_line = self.rfile.readline() + if not request_line: + self.ready = False + return + + server = self.connection.server + environ = self.environ + environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version + + method, path, req_protocol = request_line.strip().split(" ", 2) + environ["REQUEST_METHOD"] = method + + # path may be an abs_path (including "http://host.domain.tld"); + scheme, location, path, params, qs, frag = urlparse(path) + + if frag: + self.simple_response("400 Bad Request", + "Illegal #fragment in Request-URI.") + return + + if scheme: + environ["wsgi.url_scheme"] = scheme + if params: + path = path + ";" + params + + # Unquote the path+params (e.g. "/this%20path" -> "this path"). + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 + # + # But note that "...a URI must be separated into its components + # before the escaped characters within those components can be + # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 + atoms = [unquote(x) for x in quoted_slash.split(path)] + path = "%2F".join(atoms) + + if path == "*": + # This means, of course, that the last wsgi_app (shortest path) + # will always handle a URI of "*". + environ["SCRIPT_NAME"] = "" + environ["PATH_INFO"] = "*" + self.wsgi_app = server.mount_points[-1][1] + else: + for mount_point, wsgi_app in server.mount_points: + # The mount_points list should be sorted by length, descending. + if path.startswith(mount_point + "/") or path == mount_point: + environ["SCRIPT_NAME"] = mount_point + environ["PATH_INFO"] = path[len(mount_point):] + self.wsgi_app = wsgi_app + break + else: + self.simple_response("404 Not Found") + return + + # Note that, like wsgiref and most other WSGI servers, + # we unquote the path but not the query string. + environ["QUERY_STRING"] = qs + + # Compare request and server HTTP protocol versions, in case our + # server does not support the requested protocol. Limit our output + # to min(req, server). We want the following output: + # request server actual written supported response + # protocol protocol response protocol feature set + # a 1.0 1.0 1.0 1.0 + # b 1.0 1.1 1.1 1.0 + # c 1.1 1.0 1.0 1.0 + # d 1.1 1.1 1.1 1.1 + # Notice that, in (b), the response will be "HTTP/1.1" even though + # the client only understands 1.0. RFC 2616 10.5.6 says we should + # only return 505 if the _major_ version is different. + rp = int(req_protocol[5]), int(req_protocol[7]) + sp = int(server.protocol[5]), int(server.protocol[7]) + if sp[0] != rp[0]: + self.simple_response("505 HTTP Version Not Supported") + return + # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. + environ["SERVER_PROTOCOL"] = req_protocol + # set a non-standard environ entry so the WSGI app can know what + # the *real* server protocol is (and what features to support). + # See http://www.faqs.org/rfcs/rfc2145.html. + environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol + self.response_protocol = "HTTP/%s.%s" % min(rp, sp) + + # If the Request-URI was an absoluteURI, use its location atom. + if location: + environ["SERVER_NAME"] = location + + # then all the http headers + try: + self.read_headers() + except ValueError, ex: + self.simple_response("400 Bad Request", repr(ex.args)) + return + + creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1) + environ["AUTH_TYPE"] = creds[0] + if creds[0].lower() == 'basic': + user, pw = base64.decodestring(creds[1]).split(":", 1) + environ["REMOTE_USER"] = user + + # Persistent connection support + if self.response_protocol == "HTTP/1.1": + if environ.get("HTTP_CONNECTION", "") == "close": + self.close_connection = True + else: + # HTTP/1.0 + if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": + self.close_connection = True + + # Transfer-Encoding support + te = None + if self.response_protocol == "HTTP/1.1": + te = environ.get("HTTP_TRANSFER_ENCODING") + if te: + te = [x.strip().lower() for x in te.split(",") if x.strip()] + + read_chunked = False + + if te: + for enc in te: + if enc == "chunked": + read_chunked = True + else: + # Note that, even if we see "chunked", we must reject + # if there is an extension we don't recognize. + self.simple_response("501 Unimplemented") + self.close_connection = True + return + + if read_chunked: + if not self.decode_chunked(): + return + + # From PEP 333: + # "Servers and gateways that implement HTTP 1.1 must provide + # transparent support for HTTP 1.1's "expect/continue" mechanism. + # This may be done in any of several ways: + # 1. Respond to requests containing an Expect: 100-continue request + # with an immediate "100 Continue" response, and proceed normally. + # 2. Proceed with the request normally, but provide the application + # with a wsgi.input stream that will send the "100 Continue" + # response if/when the application first attempts to read from + # the input stream. The read request must then remain blocked + # until the client responds. + # 3. Wait until the client decides that the server does not support + # expect/continue, and sends the request body on its own. + # (This is suboptimal, and is not recommended.) + # + # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, + # but it seems like it would be a big slowdown for such a rare case. + if environ.get("HTTP_EXPECT", "") == "100-continue": + self.simple_response(100) + + self.ready = True + + def read_headers(self): + """Read header lines from the incoming stream.""" + environ = self.environ + + while True: + line = self.rfile.readline() + if not line: + # No more data--illegal end of headers + raise ValueError("Illegal end of headers.") + + if line == '\r\n': + # Normal end of headers + break + + if line[0] in ' \t': + # It's a continuation line. + v = line.strip() + else: + k, v = line.split(":", 1) + k, v = k.strip().upper(), v.strip() + envname = "HTTP_" + k.replace("-", "_") + + if k in comma_separated_headers: + existing = environ.get(envname) + if existing: + v = ", ".join((existing, v)) + environ[envname] = v + + ct = environ.pop("HTTP_CONTENT_TYPE", None) + if ct: + environ["CONTENT_TYPE"] = ct + cl = environ.pop("HTTP_CONTENT_LENGTH", None) + if cl: + environ["CONTENT_LENGTH"] = cl + + def decode_chunked(self): + """Decode the 'chunked' transfer coding.""" + cl = 0 + data = StringIO.StringIO() + while True: + line = self.rfile.readline().strip().split(";", 1) + chunk_size = int(line.pop(0), 16) + if chunk_size <= 0: + break +## if line: chunk_extension = line[0] + cl += chunk_size + data.write(self.rfile.read(chunk_size)) + crlf = self.rfile.read(2) + if crlf != "\r\n": + self.simple_response("400 Bad Request", + "Bad chunked transfer coding " + "(expected '\\r\\n', got %r)" % crlf) + return + + # Grab any trailer headers + self.read_headers() + + data.seek(0) + self.environ["wsgi.input"] = data + self.environ["CONTENT_LENGTH"] = str(cl) or "" + return True + + def respond(self): + """Call the appropriate WSGI app and write its iterable output.""" + response = self.wsgi_app(self.environ, self.start_response) + try: + for chunk in response: + # "The start_response callable must not actually transmit + # the response headers. Instead, it must store them for the + # server or gateway to transmit only after the first + # iteration of the application return value that yields + # a NON-EMPTY string, or upon the application's first + # invocation of the write() callable." (PEP 333) + if chunk: + self.write(chunk) + finally: + if hasattr(response, "close"): + response.close() + if (self.ready and not self.sent_headers + and not self.connection.server.interrupt): + self.sent_headers = True + self.send_headers() + if self.chunked_write: + self.sendall("0\r\n\r\n") + + def simple_response(self, status, msg=""): + """Write a simple response back to the client.""" + status = str(status) + buf = ["%s %s\r\n" % (self.connection.server.protocol, status), + "Content-Length: %s\r\n" % len(msg)] + + if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': + # Request Entity Too Large + self.close_connection = True + buf.append("Connection: close\r\n") + + buf.append("\r\n") + if msg: + buf.append(msg) + self.sendall("".join(buf)) + + def start_response(self, status, headers, exc_info = None): + """WSGI callable to begin the HTTP response.""" + if self.started_response: + if not exc_info: + raise AssertionError("WSGI start_response called a second " + "time with no exc_info.") + else: + try: + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None + self.started_response = True + self.status = status + self.outheaders.extend(headers) + return self.write + + def write(self, chunk): + """WSGI callable to write unbuffered data to the client. + + This method is also used internally by start_response (to write + data from the iterable returned by the WSGI application). + """ + if not self.started_response: + raise AssertionError("WSGI write called before start_response.") + + if not self.sent_headers: + self.sent_headers = True + self.send_headers() + + if self.chunked_write and chunk: + buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] + self.sendall("".join(buf)) + else: + self.sendall(chunk) + + def send_headers(self): + """Assert, process, and send the HTTP response message-headers.""" + hkeys = [key.lower() for key, value in self.outheaders] + status = int(self.status[:3]) + + if status == 413: + # Request Entity Too Large. Close conn to avoid garbage. + self.close_connection = True + elif "content-length" not in hkeys: + # "All 1xx (informational), 204 (no content), + # and 304 (not modified) responses MUST NOT + # include a message-body." So no point chunking. + if status < 200 or status in (204, 205, 304): + pass + else: + if self.response_protocol == 'HTTP/1.1': + # Use the chunked transfer-coding + self.chunked_write = True + self.outheaders.append(("Transfer-Encoding", "chunked")) + else: + # Closing the conn is the only way to determine len. + self.close_connection = True + + if "connection" not in hkeys: + if self.response_protocol == 'HTTP/1.1': + if self.close_connection: + self.outheaders.append(("Connection", "close")) + else: + if not self.close_connection: + self.outheaders.append(("Connection", "Keep-Alive")) + + if "date" not in hkeys: + self.outheaders.append(("Date", rfc822.formatdate())) + + server = self.connection.server + + if "server" not in hkeys: + self.outheaders.append(("Server", server.version)) + + buf = [server.protocol, " ", self.status, "\r\n"] + try: + buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] + except TypeError: + if not isinstance(k, str): + raise TypeError("WSGI response header key %r is not a string.") + if not isinstance(v, str): + raise TypeError("WSGI response header value %r is not a string.") + else: + raise + buf.append("\r\n") + self.sendall("".join(buf)) + + +class NoSSLError(Exception): + """Exception raised when a client speaks HTTP to an HTTPS socket.""" + pass + + +def _ssl_wrap_method(method, is_reader=False): + """Wrap the given method with SSL error-trapping. + + is_reader: if False (the default), EOF errors will be raised. + If True, EOF errors will return "" (to emulate normal sockets). + """ + def ssl_method_wrapper(self, *args, **kwargs): +## print (id(self), method, args, kwargs) + start = time.time() + while True: + try: + return method(self, *args, **kwargs) + except (SSL.WantReadError, SSL.WantWriteError): + # Sleep and try again. This is dangerous, because it means + # the rest of the stack has no way of differentiating + # between a "new handshake" error and "client dropped". + # Note this isn't an endless loop: there's a timeout below. + time.sleep(self.ssl_retry) + except SSL.SysCallError, e: + if is_reader and e.args == (-1, 'Unexpected EOF'): + return "" + + errno = e.args[0] + if is_reader and errno in socket_errors_to_ignore: + return "" + raise socket.error(errno) + except SSL.Error, e: + if is_reader and e.args == (-1, 'Unexpected EOF'): + return "" + + thirdarg = None + try: + thirdarg = e.args[0][0][2] + except IndexError: + pass + + if is_reader and thirdarg == 'ssl handshake failure': + return "" + if thirdarg == 'http request': + # The client is talking HTTP to an HTTPS server. + raise NoSSLError() + raise + if time.time() - start > self.ssl_timeout: + raise socket.timeout("timed out") + return ssl_method_wrapper + +class SSL_fileobject(socket._fileobject): + """Faux file object attached to a socket object.""" + + ssl_timeout = 3 + ssl_retry = .01 + + close = _ssl_wrap_method(socket._fileobject.close) + flush = _ssl_wrap_method(socket._fileobject.flush) + write = _ssl_wrap_method(socket._fileobject.write) + writelines = _ssl_wrap_method(socket._fileobject.writelines) + read = _ssl_wrap_method(socket._fileobject.read, is_reader=True) + readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True) + readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True) + + +class HTTPConnection(object): + """An HTTP connection (active socket). + + socket: the raw socket object (usually TCP) for this connection. + addr: the "bind address" for the remote end of the socket. + For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). + For UNIX domain sockets, this will be a string. + server: the HTTP Server for this Connection. Usually, the server + object possesses a passive (server) socket which spawns multiple, + active (client) sockets, one for each connection. + + environ: a WSGI environ template. This will be copied for each request. + rfile: a fileobject for reading from the socket. + sendall: a function for writing (+ flush) to the socket. + """ + + rbufsize = -1 + RequestHandlerClass = HTTPRequest + environ = {"wsgi.version": (1, 0), + "wsgi.url_scheme": "http", + "wsgi.multithread": True, + "wsgi.multiprocess": False, + "wsgi.run_once": False, + "wsgi.errors": sys.stderr, + } + + def __init__(self, sock, addr, server): + self.socket = sock + self.addr = addr + self.server = server + + # Copy the class environ into self. + self.environ = self.environ.copy() + + if SSL and isinstance(sock, SSL.ConnectionType): + timeout = sock.gettimeout() + self.rfile = SSL_fileobject(sock, "r", self.rbufsize) + self.rfile.ssl_timeout = timeout + self.sendall = _ssl_wrap_method(sock.sendall) + self.environ["wsgi.url_scheme"] = "https" + self.environ["HTTPS"] = "on" + sslenv = getattr(server, "ssl_environ", None) + if sslenv: + self.environ.update(sslenv) + else: + self.rfile = sock.makefile("r", self.rbufsize) + self.sendall = sock.sendall + + self.environ.update({"wsgi.input": self.rfile, + "SERVER_NAME": self.server.server_name, + }) + + if isinstance(self.server.bind_addr, basestring): + # AF_UNIX. This isn't really allowed by WSGI, which doesn't + # address unix domain sockets. But it's better than nothing. + self.environ["SERVER_PORT"] = "" + else: + self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) + # optional values + # Until we do DNS lookups, omit REMOTE_HOST + self.environ["REMOTE_ADDR"] = self.addr[0] + self.environ["REMOTE_PORT"] = str(self.addr[1]) + + def communicate(self): + """Read each request and respond appropriately.""" + try: + while True: + # (re)set req to None so that if something goes wrong in + # the RequestHandlerClass constructor, the error doesn't + # get written to the previous request. + req = None + req = self.RequestHandlerClass(self) + # This order of operations should guarantee correct pipelining. + req.parse_request() + if not req.ready: + return + req.respond() + if req.close_connection: + return + except socket.error, e: + errno = e.args[0] + if errno not in socket_errors_to_ignore: + if req: + req.simple_response("500 Internal Server Error", + format_exc()) + return + except (KeyboardInterrupt, SystemExit): + raise + except NoSSLError: + # Unwrap our sendall + req.sendall = self.socket._sock.sendall + req.simple_response("400 Bad Request", + "The client sent a plain HTTP request, but " + "this server only speaks HTTPS on this port.") + except: + if req: + req.simple_response("500 Internal Server Error", format_exc()) + + def close(self): + """Close the socket underlying this connection.""" + self.rfile.close() + self.socket.close() + + +def format_exc(limit=None): + """Like print_exc() but return a string. Backport for Python 2.3.""" + try: + etype, value, tb = sys.exc_info() + return ''.join(traceback.format_exception(etype, value, tb, limit)) + finally: + etype = value = tb = None + + +_SHUTDOWNREQUEST = None + +class WorkerThread(threading.Thread): + """Thread which continuously polls a Queue for Connection objects. + + server: the HTTP Server which spawned this thread, and which owns the + Queue and is placing active connections into it. + ready: a simple flag for the calling server to know when this thread + has begun polling the Queue. + + Due to the timing issues of polling a Queue, a WorkerThread does not + check its own 'ready' flag after it has started. To stop the thread, + it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue + (one for each running WorkerThread). + """ + + def __init__(self, server): + self.ready = False + self.server = server + threading.Thread.__init__(self) + + def run(self): + try: + self.ready = True + while True: + conn = self.server.requests.get() + if conn is _SHUTDOWNREQUEST: + return + + try: + conn.communicate() + finally: + conn.close() + except (KeyboardInterrupt, SystemExit), exc: + self.server.interrupt = exc + + +class SSLConnection: + """A thread-safe wrapper for an SSL.Connection. + + *args: the arguments to create the wrapped SSL.Connection(*args). + """ + + def __init__(self, *args): + self._ssl_conn = SSL.Connection(*args) + self._lock = threading.RLock() + + for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', + 'renegotiate', 'bind', 'listen', 'connect', 'accept', + 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', + 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', + 'makefile', 'get_app_data', 'set_app_data', 'state_string', + 'sock_shutdown', 'get_peer_certificate', 'want_read', + 'want_write', 'set_connect_state', 'set_accept_state', + 'connect_ex', 'sendall', 'settimeout'): + exec """def %s(self, *args): + self._lock.acquire() + try: + return self._ssl_conn.%s(*args) + finally: + self._lock.release() +""" % (f, f) + + +class CherryPyWSGIServer(object): + """An HTTP server for WSGI. + + bind_addr: a (host, port) tuple if TCP sockets are desired; + for UNIX sockets, supply the filename as a string. + wsgi_app: the WSGI 'application callable'; multiple WSGI applications + may be passed as (script_name, callable) pairs. + numthreads: the number of worker threads to create (default 10). + server_name: the string to set for WSGI's SERVER_NAME environ entry. + Defaults to socket.gethostname(). + max: the maximum number of queued requests (defaults to -1 = no limit). + request_queue_size: the 'backlog' argument to socket.listen(); + specifies the maximum number of queued connections (default 5). + timeout: the timeout in seconds for accepted connections (default 10). + + protocol: the version string to write in the Status-Line of all + HTTP responses. For example, "HTTP/1.1" (the default). This + also limits the supported features used in the response. + + + SSL/HTTPS + --------- + The OpenSSL module must be importable for SSL functionality. + You can obtain it from http://pyopenssl.sourceforge.net/ + + ssl_certificate: the filename of the server SSL certificate. + ssl_privatekey: the filename of the server's private key file. + + If either of these is None (both are None by default), this server + will not use SSL. If both are given and are valid, they will be read + on server start and used in the SSL context for the listening socket. + """ + + protocol = "HTTP/1.1" + version = "CherryPy/3.0.1" + ready = False + _interrupt = None + ConnectionClass = HTTPConnection + + # Paths to certificate and private key files + ssl_certificate = None + ssl_private_key = None + + def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, + max=-1, request_queue_size=5, timeout=10): + self.requests = Queue.Queue(max) + + if callable(wsgi_app): + # We've been handed a single wsgi_app, in CP-2.1 style. + # Assume it's mounted at "". + self.mount_points = [("", wsgi_app)] + else: + # We've been handed a list of (mount_point, wsgi_app) tuples, + # so that the server can call different wsgi_apps, and also + # correctly set SCRIPT_NAME. + self.mount_points = wsgi_app + self.mount_points.sort() + self.mount_points.reverse() + + self.bind_addr = bind_addr + self.numthreads = numthreads or 1 + if not server_name: + server_name = socket.gethostname() + self.server_name = server_name + self.request_queue_size = request_queue_size + self._workerThreads = [] + + self.timeout = timeout + + def start(self): + """Run the server forever.""" + # We don't have to trap KeyboardInterrupt or SystemExit here, + # because cherrpy.server already does so, calling self.stop() for us. + # If you're using this server with another framework, you should + # trap those exceptions in whatever code block calls start(). + self._interrupt = None + + # Select the appropriate socket + if isinstance(self.bind_addr, basestring): + # AF_UNIX socket + + # So we can reuse the socket... + try: os.unlink(self.bind_addr) + except: pass + + # So everyone can access the socket... + try: os.chmod(self.bind_addr, 0777) + except: pass + + info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] + else: + # AF_INET or AF_INET6 socket + # Get the correct address family for our host (allows IPv6 addresses) + host, port = self.bind_addr + flags = 0 + if host == '': + # Despite the socket module docs, using '' does not + # allow AI_PASSIVE to work. Passing None instead + # returns '0.0.0.0' like we want. In other words: + # host AI_PASSIVE result + # '' Y 192.168.x.y + # '' N 192.168.x.y + # None Y 0.0.0.0 + # None N 127.0.0.1 + host = None + flags = socket.AI_PASSIVE + try: + info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, flags) + except socket.gaierror: + # Probably a DNS issue. Assume IPv4. + info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] + + self.socket = None + msg = "No socket could be created" + for res in info: + af, socktype, proto, canonname, sa = res + try: + self.bind(af, socktype, proto) + except socket.error, msg: + if self.socket: + self.socket.close() + self.socket = None + continue + break + if not self.socket: + raise socket.error, msg + + # Timeout so KeyboardInterrupt can be caught on Win32 + self.socket.settimeout(1) + self.socket.listen(self.request_queue_size) + + # Create worker threads + for i in xrange(self.numthreads): + self._workerThreads.append(WorkerThread(self)) + for worker in self._workerThreads: + worker.setName("CP WSGIServer " + worker.getName()) + worker.start() + for worker in self._workerThreads: + while not worker.ready: + time.sleep(.1) + + self.ready = True + while self.ready: + self.tick() + if self.interrupt: + while self.interrupt is True: + # Wait for self.stop() to complete. See _set_interrupt. + time.sleep(0.1) + raise self.interrupt + + def bind(self, family, type, proto=0): + """Create (or recreate) the actual socket object.""" + self.socket = socket.socket(family, type, proto) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +## self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) + if self.ssl_certificate and self.ssl_private_key: + if SSL is None: + raise ImportError("You must install pyOpenSSL to use HTTPS.") + + # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(self.ssl_private_key) + ctx.use_certificate_file(self.ssl_certificate) + self.socket = SSLConnection(ctx, self.socket) + self.populate_ssl_environ() + self.socket.bind(self.bind_addr) + + def tick(self): + """Accept a new connection and put it on the Queue.""" + try: + s, addr = self.socket.accept() + if not self.ready: + return + if hasattr(s, 'settimeout'): + s.settimeout(self.timeout) + conn = self.ConnectionClass(s, addr, self) + self.requests.put(conn) + except socket.timeout: + # The only reason for the timeout in start() is so we can + # notice keyboard interrupts on Win32, which don't interrupt + # accept() by default + return + except socket.error, x: + msg = x.args[1] + if msg in ("Bad file descriptor", "Socket operation on non-socket"): + # Our socket was closed. + return + if msg == "Resource temporarily unavailable": + # Just try again. See http://www.cherrypy.org/ticket/479. + return + raise + + def _get_interrupt(self): + return self._interrupt + def _set_interrupt(self, interrupt): + self._interrupt = True + self.stop() + self._interrupt = interrupt + interrupt = property(_get_interrupt, _set_interrupt, + doc="Set this to an Exception instance to " + "interrupt the server.") + + def stop(self): + """Gracefully shutdown a server that is serving forever.""" + self.ready = False + + sock = getattr(self, "socket", None) + if sock: + if not isinstance(self.bind_addr, basestring): + # Touch our own socket to make accept() return immediately. + try: + host, port = sock.getsockname()[:2] + except socket.error, x: + if x.args[1] != "Bad file descriptor": + raise + else: + # Note that we're explicitly NOT using AI_PASSIVE, + # here, because we want an actual IP to touch. + # localhost won't work if we've bound to a public IP, + # but it would if we bound to INADDR_ANY via host = ''. + for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + s = None + try: + s = socket.socket(af, socktype, proto) + # See http://groups.google.com/group/cherrypy-users/ + # browse_frm/thread/bbfe5eb39c904fe0 + s.settimeout(1.0) + s.connect((host, port)) + s.close() + except socket.error: + if s: + s.close() + if hasattr(sock, "close"): + sock.close() + self.socket = None + + # Must shut down threads here so the code that calls + # this method can know when all threads are stopped. + for worker in self._workerThreads: + self.requests.put(_SHUTDOWNREQUEST) + + # Don't join currentThread (when stop is called inside a request). + current = threading.currentThread() + while self._workerThreads: + worker = self._workerThreads.pop() + if worker is not current and worker.isAlive: + try: + worker.join() + except AssertionError: + pass + + def populate_ssl_environ(self): + """Create WSGI environ entries to be merged into each request.""" + cert = open(self.ssl_certificate).read() + cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + self.ssl_environ = { + # pyOpenSSL doesn't provide access to any of these AFAICT +## 'SSL_PROTOCOL': 'SSLv2', +## SSL_CIPHER string The cipher specification name +## SSL_VERSION_INTERFACE string The mod_ssl program version +## SSL_VERSION_LIBRARY string The OpenSSL program version + } + + # Server certificate attributes + self.ssl_environ.update({ + 'SSL_SERVER_M_VERSION': cert.get_version(), + 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), +## 'SSL_SERVER_V_START': Validity of server's certificate (start time), +## 'SSL_SERVER_V_END': Validity of server's certificate (end time), + }) + + for prefix, dn in [("I", cert.get_issuer()), + ("S", cert.get_subject())]: + # X509Name objects don't seem to have a way to get the + # complete DN string. Use str() and slice it instead, + # because str(dn) == "" + dnstr = str(dn)[18:-2] + + wsgikey = 'SSL_SERVER_%s_DN' % prefix + self.ssl_environ[wsgikey] = dnstr + + # The DN should be of the form: /k1=v1/k2=v2, but we must allow + # for any value to contain slashes itself (in a URL). + while dnstr: + pos = dnstr.rfind("=") + dnstr, value = dnstr[:pos], dnstr[pos + 1:] + pos = dnstr.rfind("/") + dnstr, key = dnstr[:pos], dnstr[pos + 1:] + if key and value: + wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) + self.ssl_environ[wsgikey] = value + diff --git a/deluge/ui/webui/webui_plugin/page_decorators.py b/deluge/ui/webui/webui_plugin/page_decorators.py new file mode 100644 index 000000000..ccb252606 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/page_decorators.py @@ -0,0 +1,71 @@ +""" +decorators for html-pages. +""" +#relative imports +from render import render +from webserver_common import ws +from utils import * +#/relative + +from lib.webpy022.webapi import cookies, setcookie as w_setcookie +from lib.webpy022.http import seeother, url +from lib.webpy022 import changequery as self_url + +#deco's: +def deluge_page_noauth(func): + """ + add http headers + print result of func + """ + def deco(self, name = None): + web.header("Content-Type", "text/html; charset=utf-8") + web.header("Cache-Control", "no-cache, must-revalidate") + res = func(self, name) + print res + deco.__name__ = func.__name__ + return deco + +def check_session(func): + """ + a decorator + return func if session is valid, else redirect to login page. + """ + def deco(self, name = None): + ws.log.debug('%s.%s(name=%s)' % (self.__class__.__name__, func.__name__, + name)) + vars = web.input(redir_after_login = None) + ck = cookies() + if ck.has_key("session_id") and ck["session_id"] in ws.SESSIONS: + return func(self, name) #ok, continue.. + elif vars.redir_after_login: + seeother(url("/login",redir=self_url())) + else: + seeother("/login") #do not continue, and redirect to login page + return deco + +def deluge_page(func): + return check_session(deluge_page_noauth(func)) + +#combi-deco's: +def auto_refreshed(func): + "decorator:adds a refresh header" + def deco(self, name = None): + if getcookie('auto_refresh') == '1': + web.header("Refresh", "%i ; url=%s" % + (int(getcookie('auto_refresh_secs',10)),self_url())) + return func(self, name) + deco.__name__ = func.__name__ + return deco + +def remote(func): + "decorator for remote api's" + def deco(self, name = None): + try: + ws.log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name ) + print func(self, name) + except Exception, e: + print 'error:%s' % e.message + print '-'*20 + print traceback.format_exc() + deco.__name__ = func.__name__ + return deco diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py new file mode 100644 index 000000000..5e35a40fc --- /dev/null +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# deluge_webserver.py +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from webserver_common import ws +from utils import * +from render import render, error_page +import page_decorators as deco + +import lib.webpy022 as web +from lib.webpy022.http import seeother, url +from lib.static_handler import static_handler + +import base64 +from operator import attrgetter +import os + +from json_api import json_api + +#routing: +urls = ( + "/login", "login", + "/index", "index", + "/torrent/info/(.*)", "torrent_info", + "/torrent/info_inner/(.*)", "torrent_info_inner", + "/torrent/stop/(.*)", "torrent_stop", + "/torrent/start/(.*)", "torrent_start", + "/torrent/reannounce/(.*)", "torrent_reannounce", + "/torrent/add(.*)", "torrent_add", + "/torrent/delete/(.*)", "torrent_delete", + "/torrent/queue/up/(.*)", "torrent_queue_up", + "/torrent/queue/down/(.*)", "torrent_queue_down", + "/pause_all", "pause_all", + "/resume_all", "resume_all", + "/refresh/set", "refresh_set", + "/refresh/(.*)", "refresh", + "/config", "config_", + "/home", "home", + "/about", "about", + "/logout", "logout", + #remote-api: + "/remote/torrent/add(.*)", "remote_torrent_add", + "/json/(.*)","json_api", + #static: + "/static/(.*)", "static", + "/template/static/(.*)", "template_static", + #"/downloads/(.*)","downloads" disabled until it can handle large downloads + #default-pages + "/", "home", + "", "home" +) +#/routing + +#pages: +class login: + @deco.deluge_page_noauth + def GET(self, name): + vars = web.input(error = None) + return render.login(vars.error) + + def POST(self): + vars = web.input(pwd = None, redir = None) + + if check_pwd(vars.pwd): + #start new session + start_session() + do_redirect() + elif vars.redir: + seeother(url('/login', error=1, redir=vars.redir)) + else: + seeother('/login?error=1') + +class index: + "page containing the torrent list." + @deco.deluge_page + @deco.auto_refreshed + def GET(self, name): + vars = web.input(sort=None, order=None ,filter=None , category=None) + torrent_list = [get_torrent_status(torrent_id) + for torrent_id in ws.proxy.get_session_state()] + all_torrents = torrent_list[:] + + #filter-state + if vars.filter: + torrent_list = filter_torrent_state(torrent_list, vars.filter) + setcookie("filter", vars.filter) + else: + setcookie("filter", "") + + #filter-cat + if vars.category: + torrent_list = [t for t in torrent_list if t.category == vars.category] + setcookie("category", vars.category) + else: + setcookie("category", "") + + #sorting: + if vars.sort: + torrent_list.sort(key=attrgetter(vars.sort)) + if vars.order == 'up': + torrent_list = reversed(torrent_list) + + setcookie("order", vars.order) + setcookie("sort", vars.sort) + + return render.index(torrent_list, all_torrents) + +class torrent_info: + @deco.deluge_page + @deco.auto_refreshed + def GET(self, name): + torrent_id = name.split(',')[0] + return render.torrent_info(get_torrent_status(torrent_id)) + +class torrent_info_inner: + @deco.deluge_page + def GET(self, torrent_ids): + torrent_ids = torrent_ids.split(',') + info = get_torrent_status(torrent_ids[0]) + return render.torrent_info_inner(info) + +class torrent_start: + @deco.check_session + def POST(self, name): + torrent_ids = name.split(',') + ws.proxy.resume_torrent(torrent_ids) + do_redirect() + +class torrent_stop: + @deco.check_session + def POST(self, name): + torrent_ids = name.split(',') + ws.proxy.pause_torrent(torrent_ids) + do_redirect() + +class torrent_reannounce: + @deco.check_session + def POST(self, torrent_id): + ws.proxy.force_reannounce([torrent_id]) + do_redirect() + +class torrent_add: + @deco.deluge_page + def GET(self, name): + return render.torrent_add() + + @deco.check_session + def POST(self, name): + """ + allows: + *posting of url + *posting file-upload + *posting of data as string(for greasemonkey-private) + """ + + vars = web.input(url = None, torrent = {}) + + torrent_name = None + torrent_data = None + if vars.torrent.filename: + torrent_name = vars.torrent.filename + torrent_data = vars.torrent.file.read() + + if vars.url and torrent_name: + error_page(_("Choose an url or a torrent, not both.")) + if vars.url: + ws.proxy.add_torrent_url(vars.url) + do_redirect() + elif torrent_name: + data_b64 = base64.b64encode(torrent_data) + #b64 because of strange bug-reports related to binary data + ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64) + do_redirect() + else: + error_page(_("no data.")) + +class remote_torrent_add: + """ + For use in remote scripts etc. + curl ->POST pwd and torrent as file + greasemonkey: POST pwd torrent_name and data_b64 + """ + @deco.remote + def POST(self, name): + vars = web.input(pwd = None, torrent = {}, + data_b64 = None , torrent_name= None) + + if not check_pwd(vars.pwd): + return 'error:wrong password' + + if vars.data_b64: #b64 post (greasemonkey) + data_b64 = unicode(vars.data_b64) + torrent_name = vars.torrent_name + else: #file-post (curl) + data_b64 = base64.b64encode(vars.torrent.file.read()) + torrent_name = vars.torrent.filename + + ws.proxy.add_torrent_filecontent(torrent_name, data_b64) + return 'ok' + +class torrent_delete: + @deco.deluge_page + def GET(self, name): + torrent_ids = name.split(',') + torrent_list = [get_torrent_status(id) for id in torrent_ids] + return render.torrent_delete(name, torrent_list) + + @deco.check_session + def POST(self, name): + torrent_ids = name.split(',') + vars = web.input(data_also = None, torrent_also = None) + data_also = bool(vars.data_also) + torrent_also = bool(vars.torrent_also) + ws.proxy.remove_torrent(torrent_ids, data_also, torrent_also) + do_redirect() + +class torrent_queue_up: + @deco.check_session + def POST(self, name): + #a bit too verbose.. + torrent_ids = name.split(',') + torrents = [get_torrent_status(id) for id in torrent_ids] + torrents.sort(lambda x, y : x.queue_pos - y.queue_pos) + torrent_ids = [t.id for t in torrents] + for torrent_id in torrent_ids: + ws.proxy.queue_up(torrent_id) + do_redirect() + +class torrent_queue_down: + @deco.check_session + def POST(self, name): + #a bit too verbose.. + torrent_ids = name.split(',') + torrents = [get_torrent_status(id) for id in torrent_ids] + torrents.sort(lambda x, y : x.queue_pos - y.queue_pos) + torrent_ids = [t.id for t in torrents] + for torrent_id in reversed(torrent_ids): + ws.proxy.queue_down(torrent_id) + do_redirect() + +class pause_all: + @deco.check_session + def POST(self, name): + ws.proxy.pause_torrent(ws.proxy.get_session_state()) + do_redirect() + +class resume_all: + @deco.check_session + def POST(self, name): + ws.proxy.resume_torrent(ws.proxy.get_session_state()) + do_redirect() + +class refresh: + @deco.check_session + def POST(self, name): + auto_refresh = {'off': '0', 'on': '1'}[name] + setcookie('auto_refresh', auto_refresh) + if not getcookie('auto_refresh_secs'): + setcookie('auto_refresh_secs', 10) + do_redirect() + +class refresh_set: + @deco.deluge_page + def GET(self, name): + return render.refresh_form() + + @deco.check_session + def POST(self, name): + vars = web.input(refresh = 0) + refresh = int(vars.refresh) + if refresh > 0: + setcookie('auto_refresh', '1') + setcookie('auto_refresh_secs', str(refresh)) + do_redirect() + else: + error_page(_('refresh must be > 0')) + +class config_: #namespace clash? + """core config + TODO:good validation. + """ + """ + SOMEHOW ONLY BREAKS 0.6 ?? + cfg_form = web.form.Form( + web.form.Dropdown('max_download',SPEED_VALUES, + description=_('Download Speed Limit'), + post='%s Kib/sec' % ws.proxy.get_config_value('max_download_speed') + ) + ,web.form.Dropdown('max_upload', ws.SPEED_VALUES, + description=_('Upload Speed Limit'), + post='%s Kib/sec' % ws.proxy.get_config_value('max_upload_speed') + ) + ) + + @deco.deluge_page + def GET(self, name): + return render.config(self.cfg_form()) + + def POST(self, name): + vars = web.input(max_download=None, max_upload=None) + + #self.config.set("max_download_speed", float(str_bwdown)) + raise NotImplementedError('todo') + """ + +class home: + @deco.check_session + def GET(self, name): + do_redirect() + +class about: + @deco.deluge_page_noauth + def GET(self, name): + return render.about() + +class logout: + @deco.check_session + def POST(self, name): + end_session() + seeother('/login') + +class static(static_handler): + base_dir = os.path.join(os.path.dirname(__file__), 'static') + +class template_static(static_handler): + def get_base_dir(self): + return os.path.join(os.path.dirname(__file__), + 'templates/%s/static' % ws.config.get('template')) + +class downloads(static_handler): + def GET(self, name): + self.base_dir = ws.proxy.get_config_value('default_download_path') + if not ws.config.get('share_downloads'): + raise Exception('Access to downloads is forbidden.') + return static_handler.GET(self, name) +#/pages + diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py new file mode 100644 index 000000000..0a0f97bd1 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/render.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# +# webserver_framework.py +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + + +#relative: +from webserver_common import ws,REVNO,VERSION +from utils import * +#/relative +from deluge import common +from lib.webpy022 import changequery as self_url, template +import os + +class subclassed_render(object): + """ + try to use the html template in configured dir. + not available : use template in /deluge/ + """ + def __init__(self, template_dirname, cache=False): + self.base_template = template.render( + os.path.join(ws.webui_path, 'templates/deluge/'), + cache=cache) + + self.sub_template = template.render( + os.path.join(ws.webui_path, 'templates/%s/' % template_dirname), + cache=cache) + + def __getattr__(self, attr): + if hasattr(self.sub_template, attr): + return getattr(self.sub_template, attr) + else: + return getattr(self.base_template, attr) + +render = subclassed_render( + ws.config.get('template'), + ws.config.get('cache_templates')) + +def error_page(error): + web.header("Content-Type", "text/html; charset=utf-8") + web.header("Cache-Control", "no-cache, must-revalidate") + print render.error(error) + +#template-defs: + + +def category_tabs(torrent_list): + filter_tabs, category_tabs = get_category_choosers(torrent_list) + return render.part_categories(filter_tabs, category_tabs) + + +def template_crop(text, end): + if len(text) > end: + return text[0:end - 3] + '...' + return text + +def template_sort_head(id,name): + #got tired of doing these complex things inside templetor.. + vars = web.input(sort = None, order = None) + active_up = False + active_down = False + order = 'down' + + if vars.sort == id: + if vars.order == 'down': + order = 'up' + active_down = True + else: + active_up = True + + return render.sort_column_head(id, name, order, active_up, active_down) + +def template_part_stats(): + return render.part_stats(get_stats()) + +def get_config(var): + return ws.config.get(var) + +irow = 0 +def altrow(reset = False): + global irow + if reset: + irow = 1 + return + irow +=1 + irow = irow % 2 + return "altrow%s" % irow + +template.Template.globals.update({ + 'sort_head': template_sort_head, + 'part_stats':template_part_stats, + 'category_tabs':category_tabs, + 'crop': template_crop, + '_': _ , #gettext/translations + 'str': str, #because % in templetor is broken. + 'int':int, + 'sorted': sorted, + 'altrow':altrow, + 'get_config': get_config, + 'self_url': self_url, + 'fspeed': common.fspeed, + 'fsize': common.fsize, + 'render': render, #for easy resuse of templates + 'rev': 'rev.%s' % (REVNO, ), + 'version': VERSION, + 'getcookie':getcookie, + 'get': lambda (var): getattr(web.input(**{var:None}), var) # unreadable :-( +}) +#/template-defs + + +__all__ = ['render'] diff --git a/deluge/ui/webui/webui_plugin/revno b/deluge/ui/webui/webui_plugin/revno new file mode 100644 index 000000000..725a5ba2a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/revno @@ -0,0 +1 @@ +185 diff --git a/deluge/ui/webui/webui_plugin/run_webserver b/deluge/ui/webui/webui_plugin/run_webserver new file mode 100755 index 000000000..7122e41dd --- /dev/null +++ b/deluge/ui/webui/webui_plugin/run_webserver @@ -0,0 +1,5 @@ +#!/usr/bin/env python +import deluge_webserver +deluge_webserver.ws.init_05() +deluge_webserver.run() + diff --git a/deluge/ui/webui/webui_plugin/run_webserver06 b/deluge/ui/webui/webui_plugin/run_webserver06 new file mode 100755 index 000000000..9c59c3749 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/run_webserver06 @@ -0,0 +1,5 @@ +#!/usr/bin/env python +import deluge_webserver + +deluge_webserver.ws.init_06(uri = 'http://localhost:58846') +deluge_webserver.run() diff --git a/deluge/ui/webui/webui_plugin/scripts/add_torrent_to_deluge_webui b/deluge/ui/webui/webui_plugin/scripts/add_torrent_to_deluge_webui new file mode 100755 index 000000000..740857ccb --- /dev/null +++ b/deluge/ui/webui/webui_plugin/scripts/add_torrent_to_deluge_webui @@ -0,0 +1,10 @@ +#!/bin/bash + +pwd=deluge +url=http://localhost:8112 + +for arg in "$@" +do + curl -F torrent=@"$arg" -F pwd=$pwd $url/remote/torrent/add +done + diff --git a/deluge/ui/webui/webui_plugin/scripts/add_torrents_to_deluge.user.js b/deluge/ui/webui/webui_plugin/scripts/add_torrents_to_deluge.user.js new file mode 100644 index 000000000..ca6c59efc --- /dev/null +++ b/deluge/ui/webui/webui_plugin/scripts/add_torrents_to_deluge.user.js @@ -0,0 +1,207 @@ +// ==UserScript== +// @name Add Torrents To Deluge +// @namespace http://blog.monstuff.com/archives/cat_greasemonkey.html +// @description Let's you add torrents to the deluge WebUi +// @include http://isohunt.com/torrent_details/* +// @include http://thepiratebay.org/details.php?* +// @include http://torrentreactor.net/view.php?* +// @include http://www.mininova.org/* +// @include http://www.torrentspy.com/* +// @include http://ts.searching.com/* +// @include * +// ==/UserScript== + +//url-based submit and parsing based on : "Add Torrents To utorrent" by Julien Couvreur +//binary magic,contains from http://mgran.blogspot.com/2006/08/downloading-binary-streams-with.html + +//these parameters need to be edited before using the script + +// Server address +var host = "localhost"; +// Server port +var port = "8112"; +//open_page: "_blank" for a new window or "deluge_webui" for window re-use +//(not for private=1) +var open_page = "_blank" +//Private-trackers 0/1 +//different behavior, gets torrent-data from (private) site and pops up a message. +var private_submit = 1; +//deluge_password, only needed if private_submit = 1. +var deluge_password = 'deluge'; +//======================== + + +if (host == "") { alert('You need to configure the "Add Torrents To Deluge" user script with your WebUI parameters before using it.'); } + + + +function scanLinks() { + var links = getLinks(); + + for (var i=0; i < links.length; i++){ + var link = links[i]; + if (match(link.href)) { + if (private_submit) { + makeUTorrentLink_private(link,i); + } + else { + makeUTorrentLink(link); + } + } + } +} + +function makeUTorrentLink(link) { + var uTorrentLink = document.createElement('a'); + uTorrentLink.setAttribute("href", makeUTorrentUrl(link.href)); + uTorrentLink.setAttribute("target", open_page); + uTorrentLink.style.paddingLeft = "5px"; + uTorrentLink.innerHTML = ""; + link.parentNode.insertBefore(uTorrentLink, link.nextSibling); + return uTorrentLink +} + +function makeUTorrentUrl(url) { + var uTorrentUrl = "http://"+host+":"+port+"/torrent/add?redir_after_login=1"; + return uTorrentUrl + "&url=" + escape(url); +} + +function makeUTorrentLink_private(link,i) { + var id = 'deluge_link' + i; + var uTorrentLink = document.createElement('a'); + uTorrentLink.setAttribute("href", '#'); + uTorrentLink.setAttribute("id", id); + uTorrentLink.style.paddingLeft = "5px"; + uTorrentLink.innerHTML = ""; + link.parentNode.insertBefore(uTorrentLink, link.nextSibling); + + ulink = document.getElementById(id) + ulink.addEventListener("click", evt_private_submit_factory(link.href),false); + + return uTorrentLink +} + +function evt_private_submit_factory(url) { + //can this be done without magic? + function evt_private_submit(evt) { + GM_xmlhttpRequest({ method: 'GET', url: url, + overrideMimeType: 'text/plain; charset=x-user-defined', + onload: function(xhr) { + var stream = translateToBinaryString(xhr.responseText); + var data_b64 = window.btoa(stream); + post_to_webui(url, data_b64); + }, + onerror:function(xhr) { + alert('error fetching torrent file'); + } + }); + return false; + } + return evt_private_submit; +} + + +function post_to_webui(url,data_b64){ + //alert('here1'); + //data contains the content of the .torrent-file. + var POST_data = ('pwd=' + encodeURIComponent(deluge_password) + + '&torrent_name=' + encodeURIComponent(url) + '.torrent' + //+.torrent is a clutch! + '&data_b64=' + encodeURIComponent(data_b64) ); + //alert(POST_data); + + GM_xmlhttpRequest({ method: 'POST', + url: "http://"+host+":"+port+"/remote/torrent/add", + headers:{'Content-type':'application/x-www-form-urlencoded'}, + data: POST_data, + onload: function(xhr) { + if (xhr.responseText == 'ok\n') { + alert('Added torrent to webui : \n' + url); + } + else { + alert('Error adding torrent to webui:\n"' + xhr.responseText + '"'); + } + + }, + onerror:function(xhr) { + alert('error submitting torrent file'); + } + + }); +} + + + + + +function match(url) { + + // isohunt format + if (url.match(/http:\/\/.*isohunt\.com\/download\//i)) { + return true; + } + + if (url.match(/\.torrent$/)) { + return true; + } + + if (url.match(/http:\/\/.*bt-chat\.com\/download\.php/)) { + return true; + } + + // TorrentReactor + if (url.match(/http:\/\/dl\.torrentreactor\.net\/download.php\?/i)) { + return true; + } + + // Mininova + if (url.match(/http:\/\/www\.mininova\.org\/get\//i)) { + return true; + } + + // Mininova + if (url.match(/http:\/\/www\.mininova\.org\/get\//i)) { + return true; + } + + // TorrentSpy + if (url.match(/http:\/\/ts\.searching\.com\/download\.asp\?/i)) { + return true; + } + if (url.match(/http:\/\/www\.torrentspy\.com\/download.asp\?/i)) { + return true; + } + + // Seedler + if (url.match(/http:\/\/.*seedler\.org\/download\.x\?/i)) { + return true; + } + return false; +} + + +function getLinks() { + var doc_links = document.links; + var links = new Array(); + for (var i=0; i < doc_links.length; i++){ + links.push(doc_links[i]); + } + return links; +} + +var image = "data:image/gif;base64,R0lGODlhEAAQAMZyAB1CdihAYx5CdiBEeCJGeSZJfChKfChLfSpPgTBRgThRdDRUgzRVhDVWhDZWhThYhjtbiD1ciD5diT5eiz9eikBeiUFeiT5fjT1gjkBfjERijkdjiUhljkVnlEdolUxokExqkk5qkU9rklBrklFtk1BullFulk5vmlZymFx3nE97rVZ5pUx8sl54nlt5oVl6pE5/tWJ6nVp9qFqArWOEq1uIuW6EpGCItl2Ku26Gp2KKuGuIrF+MvWaLtl+Nv3KJqG+KrGaOu2aQv2SRwnGOs2uQvGqSwICOpoCQqm6Ww3OVvHKWv3iWuoKWsn+XtnacxXaeynifyXigzICewn2gxnqizoqfunujzpWesX6l0IyivYijw4+jvpOiuoOp0puktY2x2I6y2Y+z2pG02pW43Ze42pa43Z/A4qjG56jH56nI6KzJ6a/M67nR67zW8sLa9cff+M/k+P///////////////////////////////////////////////////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgB/ACwAAAAAEAAQAAAHkIB/goOEhYaCX1iHhkdIXU2LgzFARExbkYInCBcvRVSRHgQNEiYoPUmHGAkjO1FSSilBNYYQFTllY2BeSzJChg4iWmhpZ2JXOjgqhBMFH1xvbmtmWUMwM4QZBws/cXBsZFU+LCuFDwIhVm1qYVA8Nx2FEQQDHDZOU09GNIcWDAAGFEC0cBEpwAYNJUgowMQwEAA7"; + +scanLinks(); + +/* +binary magic,contains code taken from +http://mgran.blogspot.com/2006/08/downloading-binary-streams-with.html +*/ +function translateToBinaryString(text){ + var out; + out=''; + for(i=0;i revno +bzr version-info > version +rm ~/prj/WebUi/WebUi.tgz +cd ~/prj +tar -zcvf ~/prj/WebUi/WebUi.tgz WebUi/ --exclude '.*' --exclude '*.pyc' --exclude '*.tgz' --exclude 'attic' --exclude 'xul' --exclude '*.sh' --exclude '*.*~' \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/scripts/curl-example b/deluge/ui/webui/webui_plugin/scripts/curl-example new file mode 100644 index 000000000..f6fb0821a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/scripts/curl-example @@ -0,0 +1 @@ +curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add diff --git a/deluge/ui/webui/webui_plugin/scripts/extract_template_strings.py b/deluge/ui/webui/webui_plugin/scripts/extract_template_strings.py new file mode 100644 index 000000000..71c1cd371 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/scripts/extract_template_strings.py @@ -0,0 +1,28 @@ +import os +import re +template_dirs = ['~/prj/WebUi/templates/deluge', + '~/prj/WebUi/templates/advanced'] + +template_dirs = [os.path.expanduser(template_dir ) for template_dir in template_dirs] + + +files = [] +for template_dir in template_dirs: + files += [os.path.join(template_dir,fname) + for fname in os.listdir(template_dir) + if fname.endswith('.html')] + + +all_strings = [] +for filename in files: + f = open(filename,'r') + content = f.read() + all_strings += re.findall("_\(\"(.*?)\"\)",content) + all_strings += re.findall("_\(\'(.*?)\'\)",content) + +all_strings = sorted(set(all_strings)) + +f = open ('./template_strings.py','w') +for value in all_strings: + f.write("_('%s')\n" % value ) + diff --git a/deluge/ui/webui/webui_plugin/scripts/template_strings.py b/deluge/ui/webui/webui_plugin/scripts/template_strings.py new file mode 100644 index 000000000..2a52302ad --- /dev/null +++ b/deluge/ui/webui/webui_plugin/scripts/template_strings.py @@ -0,0 +1,69 @@ +_('# Of Files') +_('About') +_('Add') +_('Add Another (Stay in add page)') +_('Add Torrent') +_('Add torrent') +_('Apply') +_('Auto refresh:') +_('Ava') +_('Availability') +_('Config') +_('Connections') +_('Debug:Data Dump') +_('Delete .torrent file') +_('Delete downloaded files.') +_('Details') +_('Disable') +_('Down') +_('Down Speed') +_('Download') +_('Downloaded') +_('ETA') +_('Enable') +_('Error') +_('Eta') +_('Filter on Tracker') +_('Filter on state') +_('Login') +_('Logout') +_('Name') +_('Next Announce') +_('Off') +_('Password') +_('Password is invalid,try again') +_('Pause') +_('Pause all') +_('Peers') +_('Pieces') +_('Progress') +_('Queue Down') +_('Queue Position') +_('Queue Up') +_('Ratio') +_('Reannounce') +_('Refresh') +_('Refresh page every:') +_('Remove') +_('Remove torrent') +_('Resume') +_('Resume all') +_('Seeders') +_('Set') +_('Set Timeout') +_('Share Ratio') +_('Size') +_('Speed') +_('Start') +_('Submit') +_('Torrent list') +_('Total Size') +_('Tracker') +_('Tracker Status') +_('Up') +_('Up Speed') +_('Upload') +_('Upload torrent') +_('Uploaded') +_('Url') +_('seconds') diff --git a/deluge/ui/webui/webui_plugin/ssl/deluge.key b/deluge/ui/webui/webui_plugin/ssl/deluge.key new file mode 100644 index 000000000..a9d5db5ce --- /dev/null +++ b/deluge/ui/webui/webui_plugin/ssl/deluge.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA1sPXr1O6l2J9NAEvEYQ/JFDSVcJHh9YxP7kPdjsu7k9Ih845 +BHMX52A3Ypbe5MHe2bCj/8dRYCixRdF1KUTAKXdzc7mw9prgf3sS3RvmfcRsln6u +x7XRg7YprZJ46hFmcHiUPRgtTFLuFO2YWBnqxu/caTtAxx3PdoK6LDVnuVjHYofC +8uD4A9k6yL/jj3Yrkf8WYQqJ6pJcMAz/2c8ZXlBuiUCb9j5xKTzYoJaiUkKN2YrA +hoxRxfI7Zc7MH2yWw8/fTZJbGXo8nrfek7coSE7yQS1M6ciwkYk5VO2mBVJBJgAT +QUR/jGfLzEqNKXghQ564v9wmuFmUMd99a0tkVwIDAQABAoIBACID6sluLYOEqefu +uBHCLG4IDwheOQ4esrYxDW3gedJs5EP+ObGmuQaAisUmuC7rNeysuYzteMoOJ+Wz +AyeCKB1pOfP+WTT12tDWIWq73InW7ov3jJ89AO4nj/pZ1KTeFKeDsZbrmWEZUXQn +HZX2pOTVYMeaBuyCoDVZBzuxSbhlON4wS6ClMhem+eBOxg351CDTZa2cbq7Ffcos +VP7LY2ORQYNDTQSLguV/dJrFSotB8Eoz2xIpg5XR7msp6lzPzyAd+Aoz/T1lYxCY +IFZCJYKnIpgoYQvmtUlhQrdD8P0J4Kth7I8NgkWvXCKazQjhpUm+wojLKD0G7Kcz +9znIV+ECgYEA+qfp1C8jWbaAn1yAeORUA9aB6aGIURfOpZjnCvtMWM0Nu0nAJYDv +X7L5GRa1ulfKhfUG1Jv/ynMKXYuBUDhyccYLpP7BHpd29Arr7YAgb52KaD1PoKNa +Z45c61dj4sFoCmJEbDoL21UGb0LX3mc4XzPzwWs8AKfLW4aZh1NwCisCgYEA21gJ +Hy3egBgMT9+nVjqsgtIXgJOnzQRhvRwT7IFf392ZyFi8iM+pDUsx1yj0zSG4XNPw +NY8VtZuTBUlG73RKcrrz31jhCMfLCnoRkQeweZv0QWzbLU3V8DleUYdjFc/t0me5 +4NBR9lBlwYHgyU3GQ814vum+m0IAH0Ng1UxAVIUCgYAFOHwZTEYLN07kgtO2MOND +FTOtfwzMy5clQdMGGofTjanMjdOvtEjIEH05tYxhbjSsp5bV1M32FIFRw3cVCafw +kLRrYlb5YSQ8HwIc9z81s+1PEH/ZE63tXDy5Nh/BeE/Hb5aHPopCrjmtFZJTcojt +CrL4A1jDlrsYk+wcsnMx8wKBgEhJJQhvd2pDgps4G8+hGoUqc7Bd+OjpzsQh4rcI +k+4U+7847zkvJolJBK3hw3tu53FAL2OXOhJVqQgO9B+p9XcGAaTTh6X7IgDb5bok +DJanPMHq+/hcNGssnNbFhXQEyF2U7X8XaEuCh2ZURR5SUUq7BlX0dmp4P84NyHXC +4Vh5AoGAZYWkXxQUGzVm+H3fPpmETWGRNFDTimzi+6N+/uHkqkiDa3LGSnabmKh+ +voKm//DUjEVGlAZ3CGOjO/5SlZc/zjkgh1vg7KOU4x7DqVOuZjom5Tx3ZI4xVVVt +tVtvK0qjzUTVcwAQALN/PNak+gs9534e954rmA9kmc3xBe4ho9M= +-----END RSA PRIVATE KEY----- diff --git a/deluge/ui/webui/webui_plugin/ssl/deluge.pem b/deluge/ui/webui/webui_plugin/ssl/deluge.pem new file mode 100644 index 000000000..effef476e --- /dev/null +++ b/deluge/ui/webui/webui_plugin/ssl/deluge.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIJAPnW/GEzRy8xMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNV +BAYTAkFVMRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBX +ZWJ1aTAeFw0wNzExMjQxMDAzNDRaFw0wODExMjMxMDAzNDRaMDsxCzAJBgNVBAYT +AkFVMRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBXZWJ1 +aTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbD169TupdifTQBLxGE +PyRQ0lXCR4fWMT+5D3Y7Lu5PSIfOOQRzF+dgN2KW3uTB3tmwo//HUWAosUXRdSlE +wCl3c3O5sPaa4H97Et0b5n3EbJZ+rse10YO2Ka2SeOoRZnB4lD0YLUxS7hTtmFgZ +6sbv3Gk7QMcdz3aCuiw1Z7lYx2KHwvLg+APZOsi/4492K5H/FmEKieqSXDAM/9nP +GV5QbolAm/Y+cSk82KCWolJCjdmKwIaMUcXyO2XOzB9slsPP302SWxl6PJ633pO3 +KEhO8kEtTOnIsJGJOVTtpgVSQSYAE0FEf4xny8xKjSl4IUOeuL/cJrhZlDHffWtL +ZFcCAwEAAaOBnTCBmjAdBgNVHQ4EFgQU1BbX1/4WtAKRKmWI1gqryIoj7BQwawYD +VR0jBGQwYoAU1BbX1/4WtAKRKmWI1gqryIoj7BShP6Q9MDsxCzAJBgNVBAYTAkFV +MRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBXZWJ1aYIJ +APnW/GEzRy8xMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEoiSz5x +hRCplxUG34g3F5yJe0QboqzJ/XmECfO80a980C/WVeivM2Kb1uafsKNp+WK7wD8g +mei+todYXG+fD8WmG41LG87Xi2Xe4SlAcemEpGcC5F1bpCdvqnVAWFnqoF88FOHx +NDlrq5H5lhMH9wVrX9qJvxL+StaDJ0sFk4kMGWEN+bdSYfFdBQzF903nPtm+PlvO +1Uo6gCuRTMYM5J1DC/GpNpo/Fzrkgm8mMf1MYy3rljiNgMt2rnxhtwi6jugwyMui +id6Of6gYAtvhi7kmaUpdI5PHO35dqRK7pHXH+YXaulosCPw/+bSRptFTykeEMrBj +CzotqJ+74MwXZyM= +-----END CERTIFICATE----- diff --git a/deluge/ui/webui/webui_plugin/static/images/deluge_icon.gif b/deluge/ui/webui/webui_plugin/static/images/deluge_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..f001493122d05bf3f5b08ecdec5937b225fe8534 GIT binary patch literal 588 zcmZ?wbhEHb6krfwI99|U>r|%UkSym^rr=VcI|6jw1X zx^iJ;)e^t@tw~jLqiR<~HLOi;SsmT6Gp}VyLdUk)uHAWU%agixWOuBI@7bHyy)C|X ze^KX({H`@=eY?{p>`$3^sIYHy_JloIlMXcWEo+#tDsS51;;DOzrtPnowySB{ruymI z%H|v`n|G>W{>h33XBy@ms$FobYSFo_`MatYpPM>wW8KmVJ&X2sEI!=7c;DnjyP8*C zn!RLe@5UQ_n{M`RzBO^nt*JZiPTz5B+K#*P4?J3N?D>l0&sUy!vF7B<^=Dr1y!d+0 zwNHm`eLa5v$N48e{u2!p|A{&mr6!i7rYMwWmSiY|WTYy%d-?`2DE?$&1;vT((t^g;~bTFWBGBq+N`Mflb6=OM#%~0XAM$4~+vy3cWRiIv7qOv<;CDHT zd(ZiC&-plvh;UT-Qch9C=xZmiZ<*Ob4-?{(4>6uxi_7|wsoz`&rZC)~zhchSriivRpDC8b_Efo%lj z0RRYu1Gg@xm6`YXQZ6wDJlR-Xl?C-$$7#W8^heRNdvJpONli+>IRwP`^0uB-ra!jb zJ!xmXiMix?XfU9`!1iVpkxYn~lsNNc@7?Yt<9bnu^7xV$I+2hh*89N0zP@2j@wu?vxV|#m)1(RPobUh+1o~iR3pN1!0sn&H^`hXPu>b%707*qoM6N<$f~$@n9RL6T literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/inactive16.png b/deluge/ui/webui/webui_plugin/static/images/inactive16.png new file mode 100644 index 0000000000000000000000000000000000000000..10342be186f9e3f4a2b700a54e6d5346e7b3143a GIT binary patch literal 588 zcmV-S0<-;zP)Pg-{@ty9Tdk*Ko zy_PWshY&(AO6kWu&!0q5l%WJtCKzLHJ&A}=60vkIlYkJy6*e^e*aet09lsNAPBw)A+DS= z#BuzT5YpN0cIV=YqClSKb>H_topF^`t929vK`+ZPr`#6+wAOGO=jM1ko`qp}YEw$@ zYpuP}XoNIPkt7MEl<4>SNYfO<;Sf?vFveaBAqa6@_u+cIM!jA~v)ROCGJ#SGjYb2< z$HyRqz!(Fq^>xQ_9@|Ax+$taMUyxG5b=?;>fc2$n0LB;?V{BsE_AdaK&*w;z1dGK2 zVHjdMoq|$|*=&Ydt%gda0%OciYcLpmUaeN$%WC<)52e&QTT1zcbFTjFKfmkU_1a=zY#Rt#5{x9e7>o(Bb#QQC zGDxFF!(^kziLkgB{sZ@_QKMke&B4VO{{f8z2T2455lUMuExlf^z4q?sV5nG4^t(MT zpFGd=%_F3g99AZWKcus0In3s>SoBHlPsD;eNDMH&XCZ-^{+lkB)41f*g(n|n;=vn>BH;b@3wR{Li&fRs?GqYp=UmB|tL z4Y>Z8n*oifY7?A#0XFiqiYv6cU^}GZA(jpR-{;|l_)vs5JNsy@{cV!oI_J|<=w6Lt zp+MI^NG! yJY;kz!n~?*!SzT0M%UwA+2pyDyv}E70{j42#Jhng#r9qR0000fIowxiJ26YI#rXF0W>*;W*NmA>3o;efaST&JabV>R>f*newAVB&i%*L zJk)sN%X| zSDvrG`S#)^U;E{b&MQ~1`+ItC+zj06yK}dH;MdT--v;jw4M#?vj*k5vjZwfPamX_X z^fw;z`6-%Z8CIq6P_%>G49{|T=9J8$3f1bWto*4v)rR8z$6GF>j-R&piD28(tuYo@ z?o1d`kP<}y4yg8j5j_KX#)JDn%TVOEVR%3gZ;yzB4=_B3VYIMYL~R`d=tm4^n&Z1L z1pHoCjdOM|R&5%)hoPlWwng=S^;^OrQ-1j@`KWhTk6}%vXkOVV9>!2UN0!7~- z?HD>&N%q9xgJdv@reXMu928fo+mh8Oq4G|VR2O0x&L)$N{jhY{*__xN_3)RcSHoI^;q;F3I~bGwd_3%vCSBWsaQ`qluyQu+{TcH;b~Ky zQnM!;j|807ebM_Uyib|8*LT=E=#gh)xN#!Hj1jz5@vX#T*<|F93Ndt`SZ{H`&%~WL z6jZpJR?%OcXsN8yUz03}mY%kH^aw+LtBlr!Gqz$ULYGc*>t^> zxn63%ex$a4NbZU^{WRBwVbjRzym+iF;o{=v z;^GnD0RsUZK7IjyJ|1CV5fNcw8EI*08F@HhWM^mR<>8eO5Ri}(6%>_%OAyQWe~3Yl zgMp2qmYGqIfk}{&S&;Gn5r!%TMn<69kpUYch7hv=hM0v%zAXnpPGAIzasX{-WM!JDE8ry1qR0Uz;1&oZOB^%o0Tcgk0sxl@cq9M- literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/simple_logo.jpg b/deluge/ui/webui/webui_plugin/static/images/simple_logo.jpg new file mode 100755 index 0000000000000000000000000000000000000000..7ddc334559d2fbe820b138aea3b6b5bac0542f1c GIT binary patch literal 1785 zcma)5YgAKL7Ctw*2{9&c^FSnoLIM$kBjuG^EJ~B`NT7njU_}%cHgOVO8Y(DbeS;us z5Q2b!w$SiWK}iLHHBg>KnX#a#gy0~8SdmQBDJm6f=7O!Z|GLj(o%8MQ?DOrj*D>qO zqW}pG6bAwb1Aq)|z-$B*|Ku;?QUL^D0DvzFnZOdhZ2ML@khP%*gay_x7EdGtfcXgk zY!LuB0s#1z=3$Tq@GuPHU_1_oUw}u)LINI7SV$rg7ZQmigfwpm$ae#SCkTHRGM*uP)3TfIeioW+j{P0xz|GF?6bgKYPK?+sX)(Qd zp}U(`WODp@O|H+Pp$mOA{KH=`6M z7bwRgXCLgV+KwObElRSHNdn{IERoo#qcQ4e<>^L|G$LH8FYi;|7UhmV*3V18%t;u+ z)M<&`nfkRl#iNe`?D4JZ;x`qiuTLm)`TINxJ4Z77n44(Xu|{3Lwtv~^BxdG9l--uM zAuxD9%>K8;_e%&uQB{}!x$~@<=Ui3~Q`MJz{&jyj?|yI7FZ4lFcWK7XB_`jujuQHaO+FJf}a!rQYMlJQeJnJY^6F*(B{87{~#yw@C zb25#7ao=sz7N@~yR8yqnt1!-Wi(Nu?R&dDmCBl8L)R7kodPqOoN9}P=?jIdLD<3#H zyGixJ4CZx0Sjdl6lHSGNd0(wL6fWhQp14muHbSj%+{#` zwhmVGZA}v5Qsj0UG{LPGi>YjB_>*eFk&7|A5~8nfy5&~JW32Nu1I5M1PE%c+!6s`? z`_5ohQ$;2%T*6d-dWN^t+cx1yZQrkM%Gpvc#a^PK*2iJeEx`-h3s*QLYrb8!+2cie zaz>u6@d@A9>?|FZ$%Wsy-y5wwc#2fDYvz;J4sRlB?z%qeyXd(OZeyWg+`oH+n4Bd{ zxfNY~d+!NFIiW;bvvzvs(YB{vyXnfVDdXytJTEtE@{kJ7TQ8QvV~GB>Y~N1v^H25tiP47RpqbDyOP4Ow`h-uXgc|DqIJlx zC7av99?lYT#^@4{N0$Z6Mp2megjKnBRczWPMT7%d8>hydSDVx-cPVF4`U1rjmbmxO z=ZhS?S185gef1}Na@a|3#lIkGwXyJqG{5;D)tjdDvu)e9#V1Bg)O}O%otufc-v{vu z)eUUES1Nz`+<249FPZ#jxoMVMBN$8cX^zM(^V&oG`3?EW*L9jUNkwS4=Noj3_V;B) zx~sbgy>KbZMvZnAf%)ZgA(?Ub-pZo?{7HtwbI5 k47#l~gC=gf>-dVw)qlrn`;5>TpP1GeeRk|u8}rcr0lnOv<^TWy literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/details.png b/deluge/ui/webui/webui_plugin/static/images/tango/details.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd48c494924874a088590a749193994d075c22f GIT binary patch literal 498 zcmVl*+}S4tk-SEw(HMMH$UOFgT8OyWIuox(>z|!eNT?Q)#FJN+p=4fyzsz3(&M5 z1Oj8QEH>2c<<$iq9`D@|1fH*+ou9fEhcV03wkV2NSzd)@nNYt}lHgoRaN9}vHG1pg5Cj3DD1s1zdc6*S;N$b%7N-)ik literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/down.png b/deluge/ui/webui/webui_plugin/static/images/tango/down.png new file mode 100644 index 0000000000000000000000000000000000000000..732d46f37de7f9620096ce1591e34890810b7473 GIT binary patch literal 627 zcmV-(0*w8MP)aa zpn$LwE2Y2~V^Y!wd%1WPKnp!Vq*S&qJ$QIO@TISbq$Z-i`}~D-l*wey0Ehs90RW6K zbt-$h(D6Fkc5z7)5$c~e@ujs5_lE-_iSDlsUM{{+N(Jwm&E`4)wJYddhci7neYsLu zRiu+)oA7@bg!mE zjbf=p0R?Y--{08#3dY{#WH$S4V*JFNwe>YpK>*9NptVM$v5kCwLDoLjuLvQ$fdT-C zMq^vYC&us2%{^Ab$rQpcM7jJ*dfjdz9*b}7=l>E+O-*(Z@zm2=z2p=y#aQ<@{(Q{#FRFys=p= zBSc|k<+bj|nuDiYw^hLIxsL1Fw(Sg}j6sEKGc#mHjCwLL{OO7hmi)!cT|6FkAON{jSt(_u!Lq7!B=^4B;IBq%dL`6}O%gpGjO3}J1 z4Y?KfzQ;%l3Rp(0FF*F|EI&io>j^)N99Xu9ZE@HjcqrgTrepSr)S!;v7$d z7#j|r*sm7k|1`qSzwL6X$#3JGkN$s>{Cx9Ec6v|es;&OpMC)hUh7@^ocW)~11Nx1@ M)78&qol`;+0M{XYA^-pY literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/list-remove.png b/deluge/ui/webui/webui_plugin/static/images/tango/list-remove.png new file mode 100644 index 0000000000000000000000000000000000000000..00b654e8ca567c380fa477d4b32f808c3b5500d3 GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkEZLFV~EA+w-YyVHU|i_?Pt%tbHGab z1&5^a+FUmdNd@kXwZ%tROFFJ>Q*!zI&R_A==FP^f@-i~)|I20A*?&KNrgrv}l!w`~ zS-W;DV_2Rr!zN$cO_)z&Zy0B$7WFVdQ&MBb@09MjczW@LL literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/pause.png b/deluge/ui/webui/webui_plugin/static/images/tango/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..c8b4fe2251f885e2362b7ef36b85d6c3a178f7c8 GIT binary patch literal 464 zcmV;>0WbcEP)fkXfR0bNN% zK~y-)&5}!Q6hRDzzbd<#?kMWj*ortnatw&zNJwy`D9RZCg*!wL5UgjGNHA&oVHW+! z028qUFG{7aT)%!l;9p1DyPy6&J1w6g;v*0`zpdA6&YwMbzc2ds_4Qc{Lq320^k$!5 zzj=4I*=`dMd4^_&s>bET6_*!RsOsQmtPgN=Ura3y+#M02?|VLd`GAOE?sQ$Z@Y~Nf zBRBg~z|0U4c6Z+?v_MoabGo)gM7V2qh$y0hKP1q!JDR3JR1gu|*FYL}%{SWi2j!|- zoXv6H10NkUZ9^gMh7Gi5vrqV;>BD-9GXJBeDwkV$vM~cQN1dU zzy8jT>#CjwmhIYXH>X4*rDN*a%Jzqz;k7x5ETI&B|H7dZO>0000f=aN=#)Y=sMXW9~6k=xULU18eq^Pwm5>f&c14BZf zYe95nK#JnRiH*==SAxmkLmxdrjL^?BWW`;2x>%z19x#ygF@3}wxCj{ps zol&BI3dr$TBKH?fG@Vgwj@VLE`>*x`sHT~#`1-X0&1|$C>5O7D8g(O`Q4IH5!>UCA z#B@f9T4bxM*W2c190hpr@ILWaLI!dGBA^c595S9hn=kAOg|QenCF1t1xZCGP(7 zj844Dzcfmf65S_zD3%MD6HEfSt3fc5ItMc{ zK~y-)ZIioCQ(+i~pL6L2ZH>mr;3bB}Kr47@YPbohHi(*-khrlBSo{xMIye|<92`s> zj0 zfhzpM@ALIWhKAhp&ub29c4n916Y9^A&F#&y1u(Xj-$dR3#d>J@)tT!?^ zxB;)Of@N(J5UR>rI!z!F(O2i@jx3c*X9mOJ=UQhb^X7PCW6$}40e3bY$EvSKkHsLF zL}s%av!6J@-n)0HBoY{3zj8br){Ru^=#NAqsP(AolnVvazCP5J7L<+- z`2HPr|9>&Rx53dc9cF)8t|?gl%I3HXy4zQg`&b zxfypTgs$s291eB|gVcpW$SN$#WyIXvUjZpZ4OUxP@ZP$G)9YnD9%prQ6sO07@7_H; z?d_Pk9LBeA$dwfUbSdSAnqeR;i@LTpTvd2+co^F>(SQT}{nVX0#rlH>plR6EqS^&1 zyDP=w;XqrPR!Sr&y?#yQ=T8=H-lUw#P+nXl_v#gEv$G^`-$v0ie11Rar6v2v@^W5@ zfm4IGCmR~NTZ2JYDjsL8Sgb8;>r)&K8k(9&7YgEII=x^+r#1#60#9C-Mn2yDwjT2}2(B$L*s yY<9+mt|%;SyT+{|0(y3`SA}UC&P8E)GxrC_O;YyPhW8Wz0000i}4~9FCHr5K@{qtRq-ka(iJ?ZP=uTm zA)-=KTXM11RS2~$6nm&uv{cQSuKf$?CbP4jht?XK7~kVJ-#qia^UW|KLbc-EY953z z>WRV7dqi_}NvUZfgl}C)!*&F0M_|b+V97E80CykVr~%gk05Haon|Y41T|%KagO77# zXg_$ht|?xxSRKlv`0H+L2mld?91sx{?rsQB5|>q-9K_b`yI?say^?H5vawt?QN0%L zQr8VKjyDQ9NJT;|(TgXq`xKW7BF8I9f|vxjL{S!?dO5hlaQ@UaF9;B#f^;@jnqQnt zF(N{uTTQn`k0*~rlb-kaSCCvlqKp-L5!3TI5NItHN89$(7@Zg?Pfm^Zz3vh1d@XXv z%dw2{#h9WC`nbQFn~GCVFR$PcMeY21IKx0j@A8ZjM9Y6Ue=LTlryr z>(@2W+wdKbgN~t*f$yfVK)ah_*yWGG{JKoJQ9bX-)!YpMx+aPwk<4VDSzECWL8lc@ z`=3~j{FA#{Y~yeIt$3GuF4Da7HUP}#KVRBt{l5SJIDAFD4*5 zlTT<9K@`TnH@h?0jfHw>O#jh>1nt2m6SAzrkW<%*%=Q}tRboP9pC%r$9&(LA+0sq)ybQD9srhR zz3Fxu)^6aqJl)?FN%nOeOgb)4?+M_zJQ@)DvRBSb2FFH|!GH*69hXP{3*flC1BAti zbJNzAm&ca3iTKDR3xq|-Y2`eKx?tij|4I5$vH1yqf%5H^Fb8J54jK)5$VM-C5%i8b<|k&Kxh=#FG>Ox&>r z4?sb}hlkg>1-vahgJcyDBP0eg&{{(&pkA-x@zeQAv9vj35<}`!ZpEItce&w-qk8xH z6RRw9@QrP7!N5#{!3lE@ZdYYZTfgiFi6Lb!&3aDLCbZTHrTP~zgJ5t59%w*hOH0h#)QuLQNI1SZtsXO8U^GGfgrxxh}+}VkxnAb?)YabMHAocX%9NxLrB| zzyY8MfO-Y6yO;d<*y~_0mS+_>8utCg%c)OS>fRtAV|%-`d2TEk2V8nL~zb!m)|pEA$SF2->jn-2hWj=^AH zPL5B%P2Stz^{Pr7#{fi4VAUxi)#JB!S3FMcd3k;2Es0696z@F%)>@ou3v24+*vND076Ixy33;9q2pAv`@ZPsg6f3A(Qi1k* zFkavM@*Aa-5I$9)h)6g*I{0=DB#I*EIzT(hNs`o~9IE3`*}+soD!;iA7_JPj`4{UQ XDQtDP+C_)X00000NkvXXu0mjf5}&Iv literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/system-log-out.png b/deluge/ui/webui/webui_plugin/static/images/tango/system-log-out.png new file mode 100644 index 0000000000000000000000000000000000000000..0010931e2c2c35eda774f972dad5f305ff7b6766 GIT binary patch literal 799 zcmV+)1K|9LP)#yLz&Ndx##4AnMLxFXBZ(g%HuJ zXi&qdfM(p}?71_;z-a>z1OY)1kR%Cdn$l{uaL%E% z20$r|G3H}0Xl>|O*#nSQ0Qr2LFbv7%a)e>nxm#-iK?Fn;rO}H#*uEVR!P*eOn08KU zEzY@4ZV^E#g$TGTkl0Q5yTko z96r1hp-s`2)|%YZ6svCDq_A@*KN&-G|2`-NL;2>{ay<)mpM1>)Xl?vJ194<}C>UEMz1(#)? z2-^S9HcD2kVCu*bayPCMH=C^7umK&0EG{h2Qi}O%6`Yd}!S|+-h9S4NY{31bkNg24I=4Iksz%a>Tzznj;k653ieJ|1K8_%QpT zsQ-%yp65||Q^JNmza1DLpU)yz5Kkbm-p{$%*@Oz8Th_`>gu&CBehx;5n=4n*f;C^bvfq< dg5VDz{U1L){aup)rDXsB002ovPDHLkV1hluXGs76 literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/up.png b/deluge/ui/webui/webui_plugin/static/images/tango/up.png new file mode 100644 index 0000000000000000000000000000000000000000..c4fae73dee01db01c2b2d73bac7b2b9e7d03829d GIT binary patch literal 592 zcmV-W0SFHd50o_SN zK~y-)#ZgOW6HydB_f6ttQfY{FXt0Y=anW`l?WQh-(gtfp{FLAiH$@9}Cy0BgE?g+M z5F)q|+y*x;t^1jwlL4V{QHYK+3cp2_bY>1yTn2H?QAapPfB#Yxzj00000Cml&2>S!-Vj|(%DY|xv2rmiUs+--iLl+4{ z?G|A~=Ohg>vv9>N%M}DMW!>HPeV%zbm|I~4O9R8eF!SL%Uk3g&6hQCgwr&w;ys}lf zIX1F%*Xv2Q_jYug8SU>IsAzqBV(1P`&UnCRf8PMZ!;e^5{(|GUxUTch+I8+M7rU?V zW_t34vrC+PH%AZz1VKm`?q)<3MMP26&l(YiA-R=YS&0X*u<(|l`}Zn3VP^W}j)^_# z_6NY#uC7z$Rx^mTh**raXl*fCh+`p13_7uhfHnqW4A$CV1<;djx7Tl^S>Kr5@mDOH z8zq#g#&c^~-&kgAv%q#=W$W-maC!CHfe5XaxietSj|8I1&`<#3&|j$e*lNa`-i+0@!$Xe002ovPDHLkV1g_FA-@0s literal 0 HcmV?d00001 diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/view-refresh.png b/deluge/ui/webui/webui_plugin/static/images/tango/view-refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..3fd71d6e5929ba0c40db1960e36e9acba9d7e525 GIT binary patch literal 912 zcmV;B18@9^P)oWowDaEkzU!j%l38$)o7}}cCnx7z zVrKYAWwOsS=-Lqw-t?qu)r6QQ!notg696vQmg&~r9t3cLe1UW(yG7H)Ku^~yeO+g> z(bg0Jm{BNJFfw+(d}sQhCl&9uE%R)8S2n|p?*PPznUTt5*BiPR+1l3~ipPS`iO?Jk zARN#U4H*a;0yD)$9M29{3dM>Y4K?&WE>{g^G!Zl7)jelV^{i|AG#D_%trsJC8h7YDw+>Nu`!(E)&&KfE(t5U!_KF)uRXGsl%@ z#;0eK6ZhtiUhin`+P8I6C=m!d1ufk}o{_gOutKfI-_b^R{JP z`QzJ2^LHMWS&9G(o-t)@yh1Uq9cxfG6X$C)H~ghbOC7?WrZ-yyL0>0QOs`0x)U>uAAQg z>;&LGL0Gpf^PVr@oj>}%1`wDT7wv!4sq=s3q*Oh&WftpMhqGg=O68@F-$%x80Ep=T zKm-sG?*3PXAs8nI|0Dok)RR-0Y?z4d_HJBzCO6*s&6^5?o^0No>h2ra)My_p{u2^&LcltM%9%NL@3OcL;J5G-fbF(raxE|ez + + Deluge:$title + + + + + + + + + + + +
+ + + + +
+ +
+
diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html new file mode 100644 index 000000000..bb84f9d52 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -0,0 +1,147 @@ +$def with (torrent_list, all_torrents) +$:render.header(_('Torrent list')) + +
+ + + + + + + + + + + + + + + + + + $:category_tabs(all_torrents) + +
+ + +
+ + + + $:(sort_head('calc_state_str', 'S')) + $:(sort_head('queue_pos', '#')) + $:(sort_head('name', _('Name'))) + $:(sort_head('total_size', _('Size'))) + $:(sort_head('progress', _('Progress'))) + $if (not get('category')): + $:(sort_head('category', _('Tracker'))) + $:(sort_head('num_seeds', _('Seeders'))) + $:(sort_head('num_peers', _('Peers'))) + $:(sort_head('download_rate', _('Download'))) + $:(sort_head('upload_rate', _('Upload'))) + $:(sort_head('eta', _('Eta'))) + $:(sort_head('distributed_copies', _('Ava'))) + $:(sort_head('ratio', _('Ratio'))) + + + +$altrow(True) +$#4-space indentation is mandatory for for-loops in templetor! +$for torrent in torrent_list: + + + + + + + $if (not get('category')): + + + + + + + + + +
+
+ +
+
$torrent.queue_pos + $(crop(torrent.name, 40))$fsize(torrent.total_size) +
+
+ $torrent.message $int(torrent.progress) % +
+
+
$torrent.category$torrent.num_seeds ($torrent.total_seeds)$torrent.num_peers ($torrent.total_peers) + $if (torrent.download_rate): + $fspeed(torrent.download_rate) + $else: +   + + $if (torrent.upload_rate): + $fspeed(torrent.upload_rate) + $else: +   + $torrent.eta$("%.3f" % torrent.distributed_copies)$("%.3f" % torrent.ratio) +
+
+ + +$:part_stats() + +
+ +
+ +
+ + + + + +
+ +
+ + +$:render.footer() + diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_categories.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_categories.html new file mode 100644 index 000000000..3e8bbf806 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_categories.html @@ -0,0 +1,37 @@ +$def with (filter_tabs, category_tabs) +
+ + + + + + + +
+ + + + diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html new file mode 100644 index 000000000..6ce594919 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html @@ -0,0 +1,36 @@ +$def with (stats) + + +
+ +$_('Auto refresh:') +$if getcookie('auto_refresh') == '1': + ($getcookie('auto_refresh_secs')) $_('seconds')   + $:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png') + $:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png') +$else: + $_('Off')   + $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') +$#end +
+ +
+ + + $_('Connections') : $stats.num_connections ($stats.max_num_connections) + + $_('Down Speed') : $stats.download_rate ($stats.max_download) + + $_('Up Speed') : $stats.upload_rate ($stats.max_upload) + + + + +
+ + + + + diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_tb_button.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_tb_button.html new file mode 100644 index 000000000..bc5ec9a21 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_tb_button.html @@ -0,0 +1,35 @@ +$def with (method, func, title, image='') +
+
+ +$if (get_config('button_style') == 0): + + +$if (get_config('button_style') == 1): + $if image: + + $else: + + +$if (get_config('button_style') == 2): + + +
+
+ \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css new file mode 100644 index 000000000..b4fa9db10 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -0,0 +1,263 @@ +/* ----------------------------------------------------------- Theme Name: Simple Theme URI: http://deluge-torrent.org Description: Deluge Theme Version: 1.0 ----------------------------------------------------------- */ BODY { background: #304663 url(../../static/images/simple_bg.jpg) repeat-x; font-family: Bitstream Vera,Verdana; font-size: 10pt; margin: 0; + padding:0; + border:0; } /* GENERIC STYLES */ a img {border: 0px} hr {color: #627082; margin: 15px 0 15px 0;} +td {font-family: Bitstream Vera,Verdana;} +tr {font-family: Bitstream Vera,Verdana;} +table {font-family: Bitstream Vera,Verdana;} div {font-family: Bitstream Vera,Verdana;} /* STRUCTURE */ #page { min-width: 800px; margin-left: auto; margin-right: auto; + margin: 0; + padding:0; + font-family: Bitstream Vera,Verdana; } #main_content { background:url(../../static/images/simple_line.jpg) repeat-x; + margin: 0; + padding:0; } #simple_logo { background:url(../../static/images/simple_logo.jpg) no-repeat; } #main { + margin: 0; + padding:0; padding-top: 6px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr th { background: #1f3044; font-size: 16px; border: 0px; + white-space: nowrap; } #main form table tr td{ border: 0px; color: #fff; font-size: 12px; white-space: nowrap; + font-family: Bitstream Vera,Verdana; } #main form table tr th a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr th a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr th a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap; + font-family: Bitstream Vera,Verdana; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} input{ + background-color: #37506f; + border:1px solid #68a; + + background: #99acc3; + color: #000; + /*vertical-align:middle;*/ + -moz-border-radius:5px; + /*margin-top:5px;*/ + } + +input:hover { + background-color:#68a; +} TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: Bitstream Vera,Verdana; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ } + +div.progress_bar_outer { /*used in table-view*/ + width:150px; +} + td.progress_bar { white-space: nowrap; } td.info_label { font-weight: bold; } td { font-size: 10pt; color: #d1dae5; white-space: nowrap; } tr { font-size: 10pt; color: #d1dae5; } + +div.panel { + padding:10px; + width:750px; + background-color: #37506f; + -moz-border-radius:10px; /*ff-only!*/ + margin-top:10px; + margin-bottom:10px; +} + + +/*New styles:*/ + +div.deluge_button { + display:inline; +} +form.deluge_button { + display:inline; +} +button.deluge_button { + background-color: #37506f; + border:1px solid #68a; + + background: #99acc3; + color: #000; + vertical-align:middle; + -moz-border-radius:7px; +} +button.deluge_button:hover { + background-color:#68a; +} +div.error { + background-color:#FFFFFF; + color:#AA0000; + font-weight:bold; + -moz-border-radius:10px; + width:200px; + margin-bottom:20px; + padding:10px; + +} + +tr.torrent_table:hover { + background-color:#68a; +} + +tr.altrow0:hover { + background-color:#68a; +} +tr.altrow1:hover { + background-color:#68a; +} + +tr.altrow1{ + background-color: #37506f; +} + + + +tr.torrent_table_selected { + background-color:#900; +} + +th.torrent_table:hover { + background-color:#68a; +} +th.torrent_table { + background-color: #37506f; +} + +img.button { + margin-bottom:0px; + padding:0px; + position:relative; + top:2px; +} + +body.inner { + background:none; +} + +#stats_panel { + -moz-border-radius:0px; + width:100%; + position:fixed; + bottom:0px; + left:0px; + background-color:#304663; + margin: 0; + padding:0; + text-align:left; + height:20px; + background-color:#ddd; + color:#000; + border-style:solid; + border:0; + border-top:1px; + border-color:#000; +} + +#about { + position:fixed; + bottom:0px; + right:10px; +} + +#info_panel_div2 { + position:fixed; + bottom:10px; + right:0px; + width:100%; + background-color:#304663; +} + +#refresh_panel { + -moz-border-radius:0px; + width:350px; + position:fixed; + bottom:0px; + right:0px; + background-color:#304663; + margin: 0; + padding:0; + text-align:right; + height:20px; + background-color:#ddd; + color:#000; + z-index:999; +} + +#refresh_panel button { + background-color:#304663; + color:#FFFFFF; + border:0; + position:relative; + top:0px; + height:20px; + background-color:#ddd; + color:#000; +} +#refresh_panel button:hover { + text-decoration: underline; +} + +#category_panel { + margin-bottom:0; + padding-bottom:0; + -moz-border-radius-bottomleft:0px; + -moz-border-radius-bottomright:0px; + padding-right:32px; +} + +#toolbar { + text-align:left; + margin-top:0; + padding-top:0; + margin-bottom: 30px; + -moz-border-radius-topleft:0px; + -moz-border-radius-topright:0px; + padding-top:5px; + padding-bottom:5px; + margin-bottom: 15px; + padding-left:32px; + height:20px; +} + +#toolbar select{ + /*border:1px solid #68a;*/ + border:0; + background-color: #37506f; + color: #FFF; +} +#toolbar select:hover{ + background-color:#68a; +} + +a.toolbar_btn { + width:20px; + height:20px; + padding-left:3px; + padding-top:7px; + padding-right:3px; + text-decoration: none; + margin-bottom:3px; +} +a.toolbar_btn:hover { + background-color:#68a; + -moz-border-radius:5px; + text-decoration: none; +} + + +#toolbar_refresh { + margin:0; + border:0; + background-color:none; + padding-left:2px; + padding-top:2px; + padding-right:2px; + text-decoration: none; + background-color: #37506f; + position:relative; + top:5px; +} +#toolbar_refresh:hover { + background-color:#68a; + -moz-border-radius:5px; + text-decoration: none; +} +#category_form{ + display:inline; + position:relative; + top:-3px; + padding-left:20px; +} + + +form { /*all forms!*/ + margin:0; + padding:0; + border:0; +} + +#torrent_list { + -moz-border-radius:7px; +} + /* Hides from IE-mac \*/ * html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */ + + diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/deluge.js b/deluge/ui/webui/webui_plugin/templates/advanced/static/deluge.js new file mode 100644 index 000000000..da6be06e1 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/deluge.js @@ -0,0 +1,148 @@ +/* +all javascript is optional, everything should work web 1.0 +but javascript may/will enhance the experience. +i'm not a full time web-dev so don't expect beautifull patterns. +There's so much crap out there,i can't find good examples. +so i'd rather start from scratch, +Probably broken in an unexpected way , but worksforme. +*/ +state = { + 'row_js_continue':true + ,'selected_rows': new Array() +}; + +function $(el_id){ + return document.getElementById(el_id) +} +function get_row(id){ + return $('torrent_' + id); +} + +function on_click_row(e,id) { + /*filter out web 1.0 events for detail-link and pause*/ + if (state.row_js_continue) { + on_click_action(e,id); + } + state.row_js_continue = true; +} + +function on_click_row_js(e, id) { + /*real onClick event*/ + if (!e.ctrlKey) { + deselect_all_rows(); + select_row(id); + open_inner_details(id); + } + else if (state.selected_rows.indexOf(id) != -1) { + deselect_row(id); + } + else{ + select_row(id); + open_inner_details(id); + } +} + +function select_row(id){ + var row = get_row(id); + if (row) { + if (!(row.default_class_name)) { + row.default_class_name = row.className; + } + row.className = 'torrent_table_selected'; + state.selected_rows[state.selected_rows.length] = id; + setCookie('selected_rows',state.selected_rows); + } +} + +function deselect_row(id){ + var row = get_row(id); + if (row) { + row.className = row.default_class_name + /*remove from state.selected_rows*/ + var idx = state.selected_rows.indexOf(id); + state.selected_rows.splice(idx,1); + setCookie('selected_rows',state.selected_rows); + } +} + +function deselect_all_rows(){ + /*unbind state.selected_rows from for..in: + there must be a better way to do this*/ + var a = new Array() + for (i in state.selected_rows) { + a[a.length] = state.selected_rows[i]; + } + for (i in a){ + deselect_row(a[i]); + } +} + +function reselect_rows(){ + var selected_rows = getCookie('selected_rows').split(','); + for (i in getCookie('selected_rows')) { + select_row(selected_rows[i]); + } +} + +function open_details(e, id){ + alert(id); + window.location.href = '/torrent/info/' + id; +} + +function open_inner_details(id){ + /*probably broken for IE, use FF!*/ + $('torrent_info').src = '/torrent/info_inner/' + id; +} + +function on_click_do_nothing(e, id){ +} + +on_click_action = on_click_do_nothing; + +/*toobar buttons, */ +function toolbar_post(url, selected) { + if ((!selected) || (state.selected_rows.length > 0)) { + var ids = state.selected_rows.join(','); + var form = $('toolbar_form'); + form.action = url +ids; + form.submit(); + } + return false; +} + +function toolbar_get(url , selected) { + if (!selected) { + window.location.href = url + } + else if (state.selected_rows.length > 0) { + var ids = state.selected_rows.join(','); + window.location.href = url +ids; + } + return false; +} + +/*stuff copied from various places:*/ +/*http://www.w3schools.com/js/js_cookies.asp*/ +function setCookie(c_name,value,expiredays) +{ + var exdate=new Date() + exdate.setDate(exdate.getDate()+expiredays) + document.cookie=c_name+ "=" +escape(value)+ + ((expiredays==null) ? "" : ";expires="+exdate.toGMTString()) +} + +function getCookie(c_name) +{ +if (document.cookie.length>0) + { + c_start=document.cookie.indexOf(c_name + "=") + if (c_start!=-1) + { + c_start=c_start + c_name.length+1 + c_end=document.cookie.indexOf(";",c_start) + if (c_end==-1) c_end=document.cookie.length + return unescape(document.cookie.substring(c_start,c_end)) + } + } +return "" +} \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/scrolling_table.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/scrolling_table.css new file mode 100644 index 000000000..b046bd9c4 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/scrolling_table.css @@ -0,0 +1,106 @@ +/*Taken from: +http://www.imaputz.com/cssStuff/bigFourVersion.html +*/ + +/* define height and width of scrollable area. Add 16px to width for scrollbar */ +div.tableContainer { + clear: both; + /*border: 1px solid #963;*/ + height: 285px; + overflow: auto; + width: 756px; +} + +/* Reset overflow value to hidden for all non-IE browsers. */ +html>body div.tableContainer { + overflow: hidden; + width: 756px +} + +/* define width of table. IE browsers only */ +div.tableContainer table { + float: left; + width: 740px; +} + +/* define width of table. Add 16px to width for scrollbar. */ +/* All other non-IE browsers. */ +html>body div.tableContainer table { + width: 756px +} + +/* set table header to a fixed position. WinIE 6.x only */ +/* In WinIE 6.x, any element with a position property set to relative and is a child of */ +/* an element that has an overflow property set, the relative value translates into fixed. */ +/* Ex: parent element DIV with a class of tableContainer has an overflow property set to auto */ +thead.fixedHeader tr { + position: relative +} + +/* set THEAD element to have block level attributes. All other non-IE browsers */ +/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */ +html>body thead.fixedHeader tr { + display: block +} + +/* define the table content to be scrollable */ +/* set TBODY element to have block level attributes. All other non-IE browsers */ +/* this enables overflow to work on TBODY element. All other non-IE, non-Mozilla browsers */ +/* induced side effect is that child TDs no longer accept width: auto */ +html>body tbody.scrollContent { + display: block; + height: 262px; + overflow: auto; + width: 100% +} + +/* make TD elements pretty. Provide alternating classes for striping the table */ +/* http://www.alistapart.com/articles/zebratables/ */ +tbody.scrollContent td, tbody.scrollContent tr.normalRow td { + /*background: #FFF;*/ + + border-bottom: none; + border-left: none; + /*border-right: 1px solid #CCC; + border-top: 1px solid #DDD;*/ + padding: 2px 3px 3px 4px +} + +tbody.scrollContent tr.alternateRow td { + /*background: #EEE;*/ + border-bottom: none; + border-left: none; + /*border-right: 1px solid #CCC; + border-top: 1px solid #DDD;*/ + padding: 2px 3px 3px 4px +} + +/* define width of TH elements: 1st, 2nd, and 3rd respectively. */ +/* Add 16px to last TH for scrollbar padding. All other non-IE browsers. */ +/* http://www.w3.org/TR/REC-CSS2/selector.html#adjacent-selectors */ +html>body thead.fixedHeader th { + width: 200px +} + +html>body thead.fixedHeader th + th { + width: 240px +} + +html>body thead.fixedHeader th + th + th { + width: 316px +} + +/* define width of TD elements: 1st, 2nd, and 3rd respectively. */ +/* All other non-IE browsers. */ +/* http://www.w3.org/TR/REC-CSS2/selector.html#adjacent-selectors */ +html>body tbody.scrollContent td { + width: 200px +} + +html>body tbody.scrollContent td + td { + width: 240px +} + +html>body tbody.scrollContent td + td + td { + width: 300px +} \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html b/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html new file mode 100644 index 000000000..907bfcf55 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html @@ -0,0 +1,15 @@ +$def with (torrent) + + + + + Deluge:$torrent.name + + + + + + +$:render.tab_meta(torrent) + +$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/about.html b/deluge/ui/webui/webui_plugin/templates/deluge/about.html new file mode 100644 index 000000000..b34b7877a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/about.html @@ -0,0 +1,49 @@ +$:render.header(_('About')) +
+

Version

+
$version 
+

Links

+ + +

Authors

+
    +

    WebUi

    +
      +
    • Martijn Voncken
    • +
    + +

    Template

    +
      +
    • Martijn Voncken
    • +
    • somedude
    • +
    + +

    Deluge

    +
      +
    • Zach Tibbitts
    • +
    • Alon Zakai
    • + +
    • Alon Zakai
    • +
    • Marcos Pinto
    • +
    • Andrew Resch
    • +
    • Alex Dedul
    • +
    + +

    Windows Port

    +
      +
    • Slurdge
    • +
    + +
+*and all other authors/helpers/contributors I forgot to mention. +
+ +$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/authors.txt b/deluge/ui/webui/webui_plugin/templates/deluge/authors.txt new file mode 100644 index 000000000..878772037 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/authors.txt @@ -0,0 +1,5 @@ +-first layout taken from deluge website +improved by: +-mvoncken +-somedude + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/config.html b/deluge/ui/webui/webui_plugin/templates/deluge/config.html new file mode 100644 index 000000000..e2670d0ae --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/config.html @@ -0,0 +1,10 @@ +$def with (form) +$:render.header(_('Config')) + +
Not Implemented!
+
+$:form.render() + +
+ +$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/error.html b/deluge/ui/webui/webui_plugin/templates/deluge/error.html new file mode 100644 index 000000000..002cf3fc2 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/error.html @@ -0,0 +1,6 @@ +$def with (error_msg) +$:render.header(_('Error')) +
+    $error_msg
+
+$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/footer.html b/deluge/ui/webui/webui_plugin/templates/deluge/footer.html new file mode 100644 index 000000000..ca03a0154 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/footer.html @@ -0,0 +1,6 @@ +
+
+
+
+ + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/header.html b/deluge/ui/webui/webui_plugin/templates/deluge/header.html new file mode 100644 index 000000000..42cc62eed --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/header.html @@ -0,0 +1,23 @@ +$def with (title) + + + Deluge:$title + + + + + + + +
+ + + + +
+ +
+
diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/index.html b/deluge/ui/webui/webui_plugin/templates/deluge/index.html new file mode 100644 index 000000000..4c29278fc --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/index.html @@ -0,0 +1,61 @@ +$def with (torrent_list, all_torrents) +$:render.header(_('Torrent list')) + + + + $:(sort_head('calc_state_str', 'S')) + $:(sort_head('queue_pos', '#')) + $:(sort_head('name', _('Name'))) + $:(sort_head('total_size', _('Size'))) + $:(sort_head('progress', _('Progress'))) + $:(sort_head('num_seeds', _('Seeders'))) + $:(sort_head('num_peers', _('Peers'))) + $:(sort_head('download_rate', _('Download'))) + $:(sort_head('upload_rate', _('Upload'))) + $:(sort_head('eta', _('Eta'))) + $:(sort_head('distributed_copies', _('Ava'))) + $:(sort_head('ratio', _('Ratio'))) + +$#4-space indentation is mandatory for for-loops in templetor! +$for torrent in torrent_list: + + + + + + + + + + + + + +
+
+
$torrent.queue_pos + + $(crop(torrent.name, 40))$fsize(torrent.total_size) +
+
+ $torrent.message $int(torrent.progress) % +
+
+
$torrent.num_seeds ($torrent.total_seeds)$torrent.num_peers ($torrent.total_peers)$fspeed(torrent.download_rate)$fspeed(torrent.upload_rate)$torrent.eta$("%.3f" % torrent.distributed_copies)$("%.3f" % torrent.ratio) +
+ + + +
+$:render.part_button('GET', '/torrent/add', _('Add torrent'), 'tango/list-add.png') +$:render.part_button('POST', '/pause_all', _('Pause all'), 'tango/pause.png') +$:render.part_button('POST', '/resume_all', _('Resume all'), 'tango/start.png') + +
+ +$:part_stats() + +$:render.footer() + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/login.html b/deluge/ui/webui/webui_plugin/templates/deluge/login.html new file mode 100644 index 000000000..0436e9076 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/login.html @@ -0,0 +1,25 @@ +$def with (error) +$:render.header(_('Login')) +
+$if error > 0: +
$_("Password is invalid,try again")
+ +
+ +
+
+ $_('Password') + +
+
+ + +
+ +
+ $_('About') +
+
+
+$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html b/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html new file mode 100644 index 000000000..8c420560f --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html @@ -0,0 +1,26 @@ +$def with (method, url, title, image='') +
+
+ +$if (get_config('button_style') == 0): + + +$if (get_config('button_style') == 1): + $if image: + + $else: + + +$if (get_config('button_style') == 2): + + +
+
diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html b/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html new file mode 100644 index 000000000..342b8049f --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html @@ -0,0 +1,35 @@ +$def with (stats) + + +
+ +$_('Auto refresh:') +$if getcookie('auto_refresh') == '1': + ($getcookie('auto_refresh_secs')) $_('seconds')   + $:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png') + $:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png') +$else: + $_('Off')   + $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') +$#end +
+ +
+ + + $_('Connections') : $stats.num_connections ($stats.max_num_connections) + + $_('Down Speed') : $stats.download_rate ($stats.max_download) + + $_('Up Speed') : $stats.upload_rate ($stats.max_upload) + + + + + + ($_('About')) + + +
+ + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/refresh_form.html b/deluge/ui/webui/webui_plugin/templates/deluge/refresh_form.html new file mode 100644 index 000000000..0ac2cffda --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/refresh_form.html @@ -0,0 +1,11 @@ +$:render.header(_('Set Timeout')) +
+
+ $_('Refresh page every:') + + $_('seconds') + +
+
+$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/sort_column_head.html b/deluge/ui/webui/webui_plugin/templates/deluge/sort_column_head.html new file mode 100644 index 000000000..d354a6c4f --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/sort_column_head.html @@ -0,0 +1,12 @@ +$def with (column_id, column_name, order, active_up, active_down) + + +$column_name\ +$if active_up: + +$if active_down: + + + + + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html new file mode 100644 index 000000000..e7f7f4bf2 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html @@ -0,0 +1,85 @@ +$def with (torrent) + + +
+ +
+ + $torrent.progress %
+
+ + + + + + + + + + + + + + + + + + + + + + + +
$_('Downloaded'):$torrent.calc_total_downloaded
$_('Uploaded'):$torrent.calc_total_uploaded
$_('Seeders'):$torrent.num_seeds ($torrent.total_seeds )
$_('Share Ratio'):$("%.3f" % torrent.ratio)
$_('Pieces'):$torrent.num_pieces x $fsize(torrent.piece_length)
  
+
+ + + + + + + + + + + + + + + + + + + + + +
$_('Speed'): +$fspeed(torrent.download_rate)
$_('Speed'):$fspeed(torrent.upload_rate)
$_('Peers'):$torrent.num_peers ($torrent.total_peers )
$_('ETA'):$torrent.eta
$_('Availability'):$("%.3f" % torrent.distributed_copies)
  
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
$_('Total Size'):$fspeed(torrent.total_size)
$_('# Of Files'):$torrent.num_files
$_('Tracker'):$(crop(torrent.tracker, 30))
$_('Tracker Status'):$(crop(torrent.tracker_status, 30))
$_('Next Announce'):$torrent.next_announce
$_('Queue Position'):$torrent.queue_pos
+ +
diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html new file mode 100644 index 000000000..490f300bf --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html @@ -0,0 +1,34 @@ +$:render.header(_("Add Torrent")) +
+
+ +
+
+ $_('Url') + +
+
+ $_('Upload torrent') + +
+ +
+ + + +
+
+
+
+$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_delete.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_delete.html new file mode 100644 index 000000000..a9aceb551 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_delete.html @@ -0,0 +1,31 @@ +$def with (torrent_ids, torrent_list) +$:render.header(_("Remove torrent")) +
+
+
+ +

$_("Remove torrent")

+
    +$for torrent in torrent_list: +
  • $torrent.name
  • +
+ +
+ + $_('Delete .torrent file') +
+
+ + $_('Delete downloaded files.') +
+
+ + +
+
+
+
+$:render.footer() \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html new file mode 100644 index 000000000..660273e1a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html @@ -0,0 +1,50 @@ +$def with (torrent) + +$:(render.header(torrent.message + '/' + torrent.name)) +
+

$_('Details')

+ +$:render.tab_meta(torrent) + +$if (torrent.action == 'start'): + $:render.part_button('POST', '/torrent/start/' + str(torrent.id), _('Resume'), 'tango/start.png') +$else: + $:render.part_button('POST', '/torrent/stop/' + str(torrent.id), _('Pause'), 'tango/pause.png') + + +$:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/list-remove.png') +$:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reannounce'), 'tango/view-refresh.png') + +$:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png') +$:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png') + +
+ + +
+ + + + +$:part_stats() + +$:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/hacking-templates.txt b/deluge/ui/webui/webui_plugin/templates/hacking-templates.txt new file mode 100644 index 000000000..600ba907e --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/hacking-templates.txt @@ -0,0 +1,39 @@ +Quickstart: +Just copy and rename an existing template. +-The settings panel will see all directory's in this folder ,and let you choose your new template. +-Clicking Ok in the settings panel will restart the webserver and reload your template. + +Limited "Subclassing": +All templates are "subclassed" from the /deluge/ template. +If a html file is not found in the template dir, the file from /deluge/ will be used. + + +Notes: +Please configure your editor to use 4-space indents instead of tabs. +Or use scite and my config: http://mvoncken.sohosted.com/deluge/SciTEUser.properties.txt + +template language: http://webpy.org/templetor + +Exposed methods and variables (c&p from webserver_framework.py): + template.Template.globals.update({ + 'sort_head': template_sort_head, + 'part_stats':template_part_stats, + 'crop': template_crop, + '_': _ , #gettext/translations + 'str': str, #because % in templetor is broken. + 'sorted': sorted, + 'get_config': get_config, + 'self_url': self_url, + 'fspeed': common.fspeed, + 'fsize': common.fsize, + 'render': ws.render, #for easy resuse of templates + 'rev': 'rev.%s' % (REVNO, ), + 'version': VERSION, + 'getcookie':getcookie, + 'get': lambda (var): getattr(web.input(**{var:None}), var) # unreadable :-( +}) + +I will update this file if there is interest in making templates. + + + diff --git a/deluge/ui/webui/webui_plugin/tests/test_all.py b/deluge/ui/webui/webui_plugin/tests/test_all.py new file mode 100644 index 000000000..a5ca186ee --- /dev/null +++ b/deluge/ui/webui/webui_plugin/tests/test_all.py @@ -0,0 +1,376 @@ +""" +Testing the REST api, not the units. +unittest the right way feels so unpythonic :( +!! BIG FAT WARNING !!: this test deletes active torrents . +!! BIG FAT WARNING 2!!: this test hammers the tracker that is tested against. +""" +import unittest +import cookielib, urllib2 , urllib +from WebUi.webserver_common import ws,TORRENT_KEYS +import operator + +ws.init_06() +print 'test-env=',ws.env + + + +#CONFIG: +BASE_URL = 'http://localhost:8112' +PWD = 'deluge' + +def get_status(id): + return ws.proxy.get_torrent_status(id,TORRENT_KEYS) + +#BASE: +#303 = see other +#404 = not found +#500 = server error +#200 = OK, page exists. +class TestWebUiBase(unittest.TestCase): + def setUp(self): + #cookie aware-opener that DOES NOT use redirects. + opener = urllib2.OpenerDirector() + self.cj = cookielib.CookieJar() + for handler in [urllib2.HTTPHandler(),urllib2.HTTPDefaultErrorHandler(), + urllib2.FileHandler(),urllib2.HTTPErrorProcessor(), + urllib2.HTTPCookieProcessor(self.cj)]: + opener.add_handler(handler) + #/opener + self.opener = opener + + def open_url(self, page, post=None): + url = BASE_URL + page + + if post == 1: + post = {'Force_a_post' : 'spam'} + if post: + post = urllib.urlencode(post) + r = self.opener.open(url , data = post) + + + #BUG: error-page does not return status 500, but status 200 + #workaround... + data = r.read() + if '' in data: + error = IOError() + error.code = 500 + #print data + raise error + if r.code <> 200: + fail('no code 200, error-code=%s' % r.code) + return r + + def get_cookies(self): + return dict((c.name,c.value) for c in self.cj) + cookies = property(get_cookies) + + def assert_status(self,status, page, post): + try : + r = self.open_url(page, post) + except IOError,e: + self.assertEqual(e.code, status) + else: + self.fail('page was found "%s" (%s)' % (page, r.code )) + + def assert_404(self, page, post = None): + self.assert_status(404, page, post) + + def assert_500(self, page, post = None): + self.assert_status(500, page, post) + + def assert_303(self, page, redirect_to, post=None): + try : + r = self.open_url(page, post) + except IOError,e: + self.assertEqual(e.code, 303) + self.assertEqual(e.headers['Location'], redirect_to) + else: + #print r + self.fail('No 303!') + + def assert_exists(self, page, post = None): + try : + r = self.open_url(page, post) + except IOError,e: + self.fail('page was not found "%s" (%s)' % (page, e.code)) + else: + pass + + first_torrent_id = property(lambda self: ws.proxy.get_session_state()[0]) + first_torrent = property(lambda self: get_status(self.first_torrent_id)) + + +class TestNoAuth(TestWebUiBase): + def test303(self): + self.assert_303('/','/login') + self.assert_303('','/login') + self.assert_303('/index','/login') + #self.assert_303('/torrent/pause/','/login') + #self.assert_303('/config','/login') + self.assert_303('/torrent/info/','/login') + + def test404(self): + self.assert_404('/torrent/info') + self.assert_404('/garbage') + #self.assert_404('/static/garbage') + #self.assert_404('/template/static/garbage') + self.assert_404('/torrent/pause/', post=1) + + def testOpen(self): + self.assert_exists('/login') + self.assert_exists('/about') + + def testStatic(self): + self.assert_exists('/static/images/simple_line.jpg') + self.assert_exists('/static/images/tango/up.png') + #test 404 + + #test template-static + + + +class TestSession(TestWebUiBase): + def testLogin(self): + self.assert_303('/home','/login') + #invalid pwd: + self.assert_303('/login','/login?error=1',{'pwd':'invalid'}) + #login + self.assert_303('/login','/index',{'pwd':PWD}) + #now i'm logged-in! + #there are no sort-coockies yet so the default page is /index. + self.assert_303('/home','/index') + self.assert_exists('/index') + #self.assert_exists('/config') + self.assert_exists('/torrent/add') + self.assert_303('/','/index') + self.assert_303('','/index') + + #logout + self.assert_303('/logout','/login', post=1) + #really logged out? + self.assert_303('/','/login') + self.assert_303('','/login') + self.assert_303('/index','/login') + self.assert_303('/torrent/add','/login') + self.assert_exists('/about') + + + def testRefresh(self): + #starting pos + self.assert_303('/login','/index',{'pwd':PWD}) + r = self.open_url('/index') + assert not 'auto_refresh' in self.cookies + assert not 'auto_refresh_secs' in self.cookies + assert not r.headers.has_key('Refresh') + + #on: + self.assert_303('/refresh/on','/index', post=1) + + assert 'auto_refresh' in self.cookies + assert 'auto_refresh_secs' in self.cookies + self.assertEqual(self.cookies['auto_refresh'],'1') + self.assertEqual(self.cookies['auto_refresh_secs'],'10') + + r = self.open_url('/index') + assert r.headers['Refresh'] == '10 ; url=/index' + + #set: + self.assert_303('/refresh/set','/index',{'refresh':'5'}) + self.assertEqual(self.cookies['auto_refresh_secs'],'5') + + r = self.open_url('/index') + assert r.headers['Refresh'] == '5 ; url=/index' + self.assert_500('/refresh/set',{'refresh':'a string'}) + + #off: + self.assert_303('/refresh/off','/index', post=1) + self.assertEqual(self.cookies['auto_refresh'],'0') + self.assertEqual(self.cookies['auto_refresh_secs'],'5') + + r = self.open_url('/index') + assert not 'Refresh' in r.headers + +class TestIntegration(TestWebUiBase): + initialized = False + def setUp(self): + TestWebUiBase.setUp(self) + + self.assert_303('/login','/index',{'pwd':PWD}) + self.urls = sorted([ + 'http://torrents.aelitis.com:88/torrents/azplatform2_1.13.zip.torrent', + 'http://torrents.aelitis.com:88/torrents/azplugins_2.1.4.jar.torrent', + 'http://torrents.aelitis.com:88/torrents/azautoseeder_0.1.1.jar.torrent' + ]) + + torrent_ids = ws.proxy.get_session_state() + + #avoid hammering, investigate current torrent-list and do not re-add. + #correct means : 3 torrent's in list (for now) + if len(torrent_ids) <> 3: + #delete all, nice use case for refactoring delete.. + torrent_ids = ws.proxy.get_session_state() + for torrent in torrent_ids: + ws.proxy.remove_torrent([torrent], False, False) + + torrent_ids = ws.proxy.get_session_state() + self.assertEqual(torrent_ids, []) + + #add 3 using url. + for url in self.urls: + self.assert_303('/torrent/add','/index',{'url':url,'torrent':None}) + + #added? + self.torrent_ids = ws.proxy.get_session_state() + self.assertEqual(len(self.torrent_ids), 3) + + else: + #test correctness of existing-list + for url in self.urls: + self.assert_500('/torrent/add',{'url':url,'torrent':None}) + + def testPauseResume(self): + #pause all + self.assert_303('/pause_all','/index', post=1) + #pause worked? + pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()] + for paused in pause_status: + self.assertEqual(paused, True) + + #resume all + self.assert_303('/resume_all','/index', post=1) + #resume worked? + pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()] + for paused in pause_status: + self.assertEqual(paused,False) + #pause again. + self.assert_303('/pause_all','/index', post=1) + + torrent_id = self.first_torrent_id + #single resume. + self.assert_303('/torrent/start/%s' % torrent_id ,'/index', post=1) + self.assertEqual(get_status(torrent_id)["user_paused"] ,False) + #single pause + self.assert_303('/torrent/stop/%s' % torrent_id,'/index', post=1) + self.assertEqual(get_status(torrent_id)["user_paused"] , True) + + def testQueue(self): + #find last: + torrent_id = [id for id in ws.proxy.get_session_state() + if (get_status(id)['queue_pos'] ==3 )][0] + + #queue + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 3) + #up: + self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1) + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 2) + self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1) + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 1) + self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1) + #upper limit + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 1) + #down: + self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1) + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 2) + self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1) + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 3) + self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1) + #down limit + torrent = get_status(torrent_id) + self.assertEqual(torrent['queue_pos'], 3) + + def testMeta(self): + #info available? + for torrent_id in ws.proxy.get_session_state(): + self.assert_exists('/torrent/info/%s' % torrent_id) + self.assert_exists('/torrent/delete/%s' % torrent_id) + + #no info: + self.assert_500('/torrent/info/99999999') + self.assert_500('/torrent/delete/99999999') + + def testAddRemove(self): + #add a duplicate: + self.assert_500('/torrent/add', post={'url':self.urls[0],'torrent':None}) + + #add a 4th using url + + #delete + + #add torrrent-file + #./test01.torrent + + + def test_do_redirect(self): + self.assert_303('/home','/index') + #1 + self.assert_exists('/index?sort=download_rate&order=down') + self.assert_303('/home','/index?sort=download_rate&order=down') + assert self.cookies['sort'] == 'download_rate' + assert self.cookies['order'] == 'down' + #2 + self.assert_exists('/index?sort=progress&order=up') + self.assert_303('/home','/index?sort=progress&order=up') + assert self.cookies['sort'] == 'progress' + assert self.cookies['order'] == 'up' + #redir after pause-POST? in /index. + self.assert_exists('/index?sort=name&order=down') + torrent_id = self.first_torrent_id + self.assert_303('/torrent/stop/%s' % torrent_id, + '/index?sort=name&order=down', post=1) + #redir in details 1 + self.assert_303('/torrent/stop/%s?redir=/torrent/info/%s' %(torrent_id,torrent_id) + ,'/torrent/info/' + torrent_id, post = 1) + #redir in details 2 + self.assert_303('/torrent/stop/%s' % torrent_id + ,'/torrent/info/' + torrent_id , + post={'redir': '/torrent/info/' + torrent_id}) + + def testRemote(self): + pass + + def test_redir_after_login(self): + pass + + def testReannounce(self): + torrent_id = self.first_torrent_id + self.assert_303( + '/torrent/reannounce/%(id)s?redir=/torrent/info/%(id)s' + % {'id':torrent_id} + ,'/torrent/info/' + torrent_id, post = 1) + + def testRecheck(self): + #add test before writing code.. + #RELEASE-->disable + """ + torrent_id = self.first_torrent_id + self.assert_303( + '/torrent/recheck/%(id)s?redir=/torrent/info/%(id)s' + % {'id':torrent_id} + ,'/torrent/info/' + torrent_id, post = 1) + """ + + + +# + +if False: + suiteFew = unittest.TestSuite() + + suiteFew.addTest(TestSession("testRefresh")) + + unittest.TextTestRunner(verbosity=2).run(suiteFew) + +elif False: + suiteFew = unittest.TestSuite() + suiteFew.addTest(TestIntegration("testDoRedirect")) + unittest.TextTestRunner(verbosity=2).run(suiteFew) + +else: + unittest.main() + diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py new file mode 100644 index 000000000..6cafac4f1 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +# +# webserver_framework.py +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import lib.webpy022 as web +import os +from lib.webpy022.webapi import cookies, setcookie as w_setcookie +from lib.webpy022 import changequery as self_url, template +from lib.webpy022.utils import Storage +from lib.webpy022.http import seeother, url + +from deluge.common import fsize,fspeed + +import traceback +import random +from operator import attrgetter +import datetime +import pickle +from md5 import md5 +from urlparse import urlparse + +from webserver_common import REVNO, VERSION, TORRENT_KEYS, STATE_MESSAGES +from webserver_common import ws +from debugerror import deluge_debugerror + +#init: +web.webapi.internalerror = deluge_debugerror +debug_unicode = False + +#methods: +def setcookie(key, val): + """add 30 days expires header for persistent cookies""" + return w_setcookie(key, val , expires=2592000) + +#really simple sessions, to bad i had to implement them myself. +def start_session(): + ws.log.debug('start session') + session_id = str(random.random()) + ws.SESSIONS.append(session_id) + #if len(ws.SESSIONS) > 20: #save max 20 sessions? + # ws.SESSIONS = ws.SESSIONS[-20:] + #not thread safe! , but a verry rare bug. + #f = open(ws.session_file,'wb') + #pickle.dump(ws.SESSIONS, f) + #f.close() + setcookie("session_id", session_id) + +def end_session(): + session_id = getcookie("session_id") + #if session_id in ws.SESSIONS: + # ws.SESSIONS.remove(session_id) + #not thread safe! , but a verry rare bug. + #f = open(ws.session_file,'wb') + #pickle.dump(ws.SESSIONS, f) + #f.close() + setcookie("session_id","") + +def do_redirect(): + """for redirects after a POST""" + vars = web.input(redir = None) + ck = cookies() + url_vars = {} + + if vars.redir: + seeother(vars.redir) + return + #todo:cleanup + if ("order" in ck and "sort" in ck): + url_vars.update({'sort':ck['sort'] ,'order':ck['order'] }) + if ("filter" in ck) and ck['filter']: + url_vars['filter'] = ck['filter'] + if ("category" in ck) and ck['category']: + url_vars['category'] = ck['category'] + + seeother(url("/index", **url_vars)) + +def getcookie(key, default = None): + key = str(key).strip() + ck = cookies() + return ck.get(key, default) + +#utils: +def check_pwd(pwd): + m = md5() + m.update(ws.config.get('pwd_salt')) + m.update(pwd) + return (m.digest() == ws.config.get('pwd_md5')) + +def get_stats(): + stats = Storage({ + 'download_rate':fspeed(ws.proxy.get_download_rate()), + 'upload_rate':fspeed(ws.proxy.get_upload_rate()), + 'max_download':ws.proxy.get_config_value('max_download_speed_bps'), + 'max_upload':ws.proxy.get_config_value('max_upload_speed_bps'), + 'num_connections':ws.proxy.get_num_connections(), + 'max_num_connections':ws.proxy.get_config_value('max_connections_global') + }) + if stats.max_upload < 0: + stats.max_upload = _("Unlimited") + else: + stats.max_upload = fspeed(stats.max_upload) + + if stats.max_download < 0: + stats.max_download = _("Unlimited") + else: + stats.max_download = fspeed(stats.max_download) + + return stats + + +def get_torrent_status(torrent_id): + """ + helper method. + enhance ws.proxy.get_torrent_status with some extra data + """ + status = Storage(ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS)) + + #add missing values for deluge 0.6: + for key in TORRENT_KEYS: + if not key in status: + status[key] = 0 + ws.log.warning('torrent_status:empty key in status:%s' % key) + elif status[key] == None: + status[key] = 0 + ws.log.warning('torrent_status:None key in status:%s' % key) + + + status["id"] = torrent_id + + url = urlparse(status.tracker) + if hasattr(url,'hostname'): + status.category = url.hostname or 'unknown' + else: + status.category = 'No-tracker' + + #0.5-->0.6 + status.download_rate = status.download_payload_rate + status.upload_rate = status.upload_payload_rate + + #for naming the status-images + status.calc_state_str = "downloading" + if status.paused: + status.calc_state_str= "inactive" + elif status.is_seed: + status.calc_state_str = "seeding" + + #action for torrent_pause + if status.user_paused: + status.action = "start" + else: + status.action = "stop" + + if status.user_paused: + status.message = _("Paused") + elif status.paused: + status.message = _("Queued") + else: + status.message = (STATE_MESSAGES[status.state]) + + #add some pre-calculated values + status.update({ + "calc_total_downloaded" : (fsize(status.total_done) + + " (" + fsize(status.total_download) + ")"), + "calc_total_uploaded": (fsize(status.uploaded_memory + + status.total_payload_upload) + " (" + + fsize(status.total_upload) + ")"), + }) + + #no non-unicode string may enter the templates. + #FIXED,l was a translation bug.. + if debug_unicode: + for k, v in status.iteritems(): + if (not isinstance(v, unicode)) and isinstance(v, str): + try: + status[k] = unicode(v) + except: + raise Exception('Non Unicode for key:%s' % (k, )) + return status + +def get_categories(torrent_list): + trackers = [(torrent['category'] or 'unknown') for torrent in torrent_list] + categories = {} + for tracker in trackers: + categories[tracker] = categories.get(tracker,0) + 1 + return categories + +def filter_torrent_state(torrent_list,filter_name): + filters = { + 'downloading': lambda t: (not t.paused and not t.is_seed) + ,'queued':lambda t: (t.paused and not t.user_paused) + ,'paused':lambda t: (t.user_paused) + ,'seeding':lambda t:(t.is_seed and not t.paused ) + ,'traffic':lambda t: (t.download_rate > 0 or t.upload_rate > 0) + } + filter_func = filters[filter_name] + return [t for t in torrent_list if filter_func(t)] + +def get_category_choosers(torrent_list): + """ + todo: split into 2 parts... + """ + categories = get_categories(torrent_list) + + filter_tabs = [Storage(title='All (%s)' % len(torrent_list), + filter='', category=None)] + + #static filters + for title, filter_name in [ + (_('Downloading'),'downloading') , + (_('Queued'),'queued') , + (_('Paused'),'paused') , + (_('Seeding'),'seeding'), + (_('Traffic'),'traffic') + ]: + title += ' (%s)' % ( + len(filter_torrent_state(torrent_list, filter_name)), ) + filter_tabs.append(Storage(title=title, filter=filter_name)) + + categories = [x for x in get_categories(torrent_list).iteritems()] + categories.sort() + + #trackers: + category_tabs = [] + category_tabs.append( + Storage(title=_('Trackers'),category='')) + for title,count in categories: + category = title + title += ' (%s)' % (count, ) + category_tabs.append(Storage(title=title, category=category)) + + return filter_tabs, category_tabs + +#/utils + +__all__ = [ + 'do_redirect', 'start_session','getcookie' + ,'setcookie','end_session', + 'get_torrent_status', 'check_pwd','get_categories' + ,'filter_torrent_state','web','get_category_choosers','get_stats'] diff --git a/deluge/ui/webui/webui_plugin/version b/deluge/ui/webui/webui_plugin/version new file mode 100644 index 000000000..725a5ba2a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/version @@ -0,0 +1 @@ +185 diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py new file mode 100644 index 000000000..7b418ce29 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) Martijn Voncken 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +""" +initializes config,render and proxy. +All hacks go here, so this is a really ugly source-file.. +Support running in process0.5 ,run inside-gtk0.5 and run in process0.6 +""" + +import os +import deluge +import random +import pickle +import sys +import base64 + +random.seed() + +try: + _('translate something') +except: + import gettext + gettext.install('~/') + #log.error('no translations :(') + + +#constants +try: + REVNO = open(os.path.join(os.path.dirname(__file__),'revno')).read() +except: + REVNO = '' +try: + VERSION = open(os.path.join(os.path.dirname(__file__),'version')).read() +except: + VERSION = '' + +TORRENT_KEYS = ['distributed_copies', 'download_payload_rate', + 'eta', 'is_seed', 'name', 'next_announce', + 'num_files', 'num_peers', 'num_pieces', 'num_seeds', 'paused', + 'piece_length','progress', 'ratio', 'total_done', 'total_download', + 'total_payload_download', 'total_payload_upload', 'total_peers', + 'total_seeds', 'total_size', 'total_upload', 'total_wanted', + 'tracker_status', 'upload_payload_rate', + 'uploaded_memory','tracker','state','queue_pos','user_paused'] + +STATE_MESSAGES = (_("Queued"), + _("Checking"), + _("Connecting"), + _("Downloading Metadata"), + _("Downloading"), + _("Finished"), + _("Seeding"), + _("Allocating")) + +SPEED_VALUES = [ + (-1, 'Unlimited'), + (5, '5.0 Kib/sec'), + (10, '10.0 Kib/sec'), + (15, '15.0 Kib/sec'), + (25, '25.0 Kib/sec'), + (30, '30.0 Kib/sec'), + (50, '50.0 Kib/sec'), + (80, '80.0 Kib/sec'), + (300, '300.0 Kib/sec'), + (500, '500.0 Kib/sec') + ] +CONFIG_DEFAULTS = { + "port":8112, + "button_style":2, + "auto_refresh":False, + "auto_refresh_secs": 10, + "template":"advanced", + "pwd_salt":"2540626806573060601127357001536142078273646936492343724296134859793541603059837926595027859394922651189016967573954758097008242073480355104215558310954", + "pwd_md5":"\xea\x8d\x90\x98^\x9f\xa9\xe2\x19l\x7f\x1a\xca\x82u%", + "cache_templates":False, + "use_https":False +} + +#/constants + + +class Ws: + """ + singleton + important attributes here are environment dependent. + + Most important public attrs: + ws.proxy + ws.log + ws.config + + Other: + ws.env + ws.config_dir + ws.session_file + ws.SESSIONS + """ + def __init__(self): + self.webui_path = os.path.dirname(__file__) + self.env = 'UNKNOWN' + + try: + self.config_dir = deluge.common.CONFIG_DIR + except: + self.config_dir = os.path.expanduser("~/.config/deluge") + + self.config_file = os.path.join(self.config_dir,'webui.conf') + self.session_file = os.path.join(self.config_dir,'webui.sessions') + self.SESSIONS = [] + + def init_process(self): + self.config = pickle.load(open(self.config_file)) + + def init_06(self, uri = 'http://localhost:58846'): + import deluge.ui.client as proxy + from deluge.log import LOG as log + self.log = log + proxy.set_core_uri(uri) + self.proxy = proxy + + #MONKEY PATCH, TODO->REMOVE!!! + def add_torrent_filecontent(name , data_b64): + self.log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' % + (name , len(data_b64))) + + name = name.replace('\\','/') + name = 'deluge06_' + str(random.random()) + '_' + name.split('/')[-1] + filename = os.path.join('/tmp', name) + + self.log.debug('write: %s' % filename) + f = open(filename,"wb") + f.write(base64.b64decode(data_b64)) + f.close() + + self.proxy.add_torrent_file([filename]) + + self.proxy.add_torrent_filecontent = add_torrent_filecontent + self.log.debug('cfg-file %s' % self.config_file) + + if not os.path.exists(self.config_file): + self.log.debug('create cfg file %s' % config_file) + #load&save defaults. + f = file(self.config_file,'wb') + pickle.dump(CONFIG_DEFAULTS,f) + f.close() + + self.init_process() + self.proxy = proxy + self.env = '0.6' + + def init_05(self): + import dbus + self.init_process() + bus = dbus.SessionBus() + self.proxy = bus.get_object("org.deluge_torrent.dbusplugin" + , "/org/deluge_torrent/DelugeDbusPlugin") + + self.env = '0.5_process' + self.init_logger() + + def init_gtk_05(self): + #appy possibly changed config-vars, only called in when runing inside gtk. + #new bug ws.render will not update!!!! + #other bug: must warn if blocklist plugin is active! + from dbus_interface import get_dbus_manager + self.proxy = get_dbus_manager() + self.config = deluge.pref.Preferences(config_file, False) + self.env = '0.5_gtk' + self.init_logger() + + def init_logger(self): + #only for 0.5.. + import logging + logging.basicConfig(level=logging.DEBUG, + format="[%(levelname)s] %(message)s") + self.log = logging + +ws =Ws() + + + diff --git a/setup.py b/setup.py index 0b8374273..f8c4abc8f 100644 --- a/setup.py +++ b/setup.py @@ -173,7 +173,18 @@ setup( "data/pixmaps/logo.svg", "plugins/*.egg", "i18n/*.pot", - "i18n/*/LC_MESSAGES/*.mo"]}, + "i18n/*/LC_MESSAGES/*.mo", + "ui/webui/webui_plugin/LICENSE", + "ui/webui/webui_plugin/scripts/*", + "ui/webui/webui_plugin/ssl/*", + "ui/webui/webui_plugin/static/*.css", + "ui/webui/webui_plugin/static/images/*.png", + "ui/webui/webui_plugin/static/images/*.jpg", + "ui/webui/webui_plugin/static/images/tango/*.png", + "ui/webui/webui_plugin/templates/deluge/*", + "ui/webui/webui_plugin/templates/advanced/*.html", + "ui/webui/webui_plugin/templates/advanced/static/*" + ]}, data_files = [('/usr/share/icons/scalable/apps', [ 'deluge/data/icons/scalable/apps/deluge.svg']), ('/usr/share/icons/hicolor/128x128/apps', [ From 806e3885b4e3dcefced3b7dd81ee9ce1a524fab2 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sun, 13 Jan 2008 11:16:56 +0000 Subject: [PATCH 0325/1009] test-commit/sync to 189 --- deluge/ui/webui/webui_plugin/__init__.py | 15 ++++-------- deluge/ui/webui/webui_plugin/pages.py | 24 +++++-------------- deluge/ui/webui/webui_plugin/render.py | 12 ++++++---- .../ui/webui/webui_plugin/webserver_common.py | 4 ++-- 4 files changed, 20 insertions(+), 35 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/__init__.py b/deluge/ui/webui/webui_plugin/__init__.py index 1807ac222..5ccb24fe6 100644 --- a/deluge/ui/webui/webui_plugin/__init__.py +++ b/deluge/ui/webui/webui_plugin/__init__.py @@ -46,11 +46,13 @@ Other contributors: *markybob : stability : synced with his changes in deluge-svn. """ + import deluge.common +from webserver_common import ws,REVNO,VERSION + try: import deluge.pref from deluge.dialogs import show_popup_warning - from webserver_common import ws except ImportError: print 'WebUi:not imported as a plugin' @@ -72,15 +74,8 @@ from threading import Thread import random random.seed() -try: - plugin_version += open(os.path.join(os.path.dirname(__file__),'revno')).read() -except: - plugin_version = "No Version" -try: - plugin_description += ( - open(os.path.join(os.path.dirname(__file__),'version')).read()) -except: - plugin_description = "No Version" +plugin_version += REVNO +plugin_description += VERSION def deluge_init(deluge_path): global path diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 5e35a40fc..e62d1055f 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -35,6 +35,7 @@ from webserver_common import ws from utils import * from render import render, error_page import page_decorators as deco +import forms import lib.webpy022 as web from lib.webpy022.http import seeother, url @@ -63,7 +64,7 @@ urls = ( "/resume_all", "resume_all", "/refresh/set", "refresh_set", "/refresh/(.*)", "refresh", - "/config", "config_", + "/config", "config", "/home", "home", "/about", "about", "/logout", "logout", @@ -304,33 +305,20 @@ class refresh_set: else: error_page(_('refresh must be > 0')) -class config_: #namespace clash? +class config: #namespace clash? """core config TODO:good validation. """ - """ - SOMEHOW ONLY BREAKS 0.6 ?? - cfg_form = web.form.Form( - web.form.Dropdown('max_download',SPEED_VALUES, - description=_('Download Speed Limit'), - post='%s Kib/sec' % ws.proxy.get_config_value('max_download_speed') - ) - ,web.form.Dropdown('max_upload', ws.SPEED_VALUES, - description=_('Upload Speed Limit'), - post='%s Kib/sec' % ws.proxy.get_config_value('max_upload_speed') - ) - ) - @deco.deluge_page def GET(self, name): - return render.config(self.cfg_form()) + return render.config(forms.bandwith()) - def POST(self, name): + def POST(self): vars = web.input(max_download=None, max_upload=None) #self.config.set("max_download_speed", float(str_bwdown)) raise NotImplementedError('todo') - """ + class home: @deco.check_session diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index 0a0f97bd1..b42ff3571 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -44,13 +44,17 @@ class subclassed_render(object): try to use the html template in configured dir. not available : use template in /deluge/ """ - def __init__(self, template_dirname, cache=False): + def __init__(self): + self.apply_cfg() + + def apply_cfg(self): + cache = ws.config.get('cache_templates') self.base_template = template.render( os.path.join(ws.webui_path, 'templates/deluge/'), cache=cache) self.sub_template = template.render( - os.path.join(ws.webui_path, 'templates/%s/' % template_dirname), + os.path.join(ws.webui_path, 'templates/%s/' % ws.config.get('template')), cache=cache) def __getattr__(self, attr): @@ -59,9 +63,7 @@ class subclassed_render(object): else: return getattr(self.base_template, attr) -render = subclassed_render( - ws.config.get('template'), - ws.config.get('cache_templates')) +render = subclassed_render() def error_page(error): web.header("Content-Type", "text/html; charset=utf-8") diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 7b418ce29..c1aa93294 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -166,7 +166,7 @@ class Ws: self.log.debug('cfg-file %s' % self.config_file) if not os.path.exists(self.config_file): - self.log.debug('create cfg file %s' % config_file) + self.log.debug('create cfg file %s' % self.config_file) #load&save defaults. f = file(self.config_file,'wb') pickle.dump(CONFIG_DEFAULTS,f) @@ -192,7 +192,7 @@ class Ws: #other bug: must warn if blocklist plugin is active! from dbus_interface import get_dbus_manager self.proxy = get_dbus_manager() - self.config = deluge.pref.Preferences(config_file, False) + self.config = deluge.pref.Preferences(self.config_file, False) self.env = '0.5_gtk' self.init_logger() From 57870b8094d29e3e8bef9ac19a59b7db974f96e5 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sun, 13 Jan 2008 11:22:51 +0000 Subject: [PATCH 0326/1009] don't import forms --- deluge/ui/webui/webui_plugin/pages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index e62d1055f..9c0c96562 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -35,7 +35,7 @@ from webserver_common import ws from utils import * from render import render, error_page import page_decorators as deco -import forms +#import forms import lib.webpy022 as web from lib.webpy022.http import seeother, url From 5a90f48ccbc0f0a16b65b6eb8953ac5833fe2ec7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 14 Jan 2008 05:40:40 +0000 Subject: [PATCH 0327/1009] Include deluge_icon.gif for WebUI. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f8c4abc8f..cd2135ec3 100644 --- a/setup.py +++ b/setup.py @@ -180,6 +180,7 @@ setup( "ui/webui/webui_plugin/static/*.css", "ui/webui/webui_plugin/static/images/*.png", "ui/webui/webui_plugin/static/images/*.jpg", + "ui/webui/webui_plugin/static/images/*.gif", "ui/webui/webui_plugin/static/images/tango/*.png", "ui/webui/webui_plugin/templates/deluge/*", "ui/webui/webui_plugin/templates/advanced/*.html", From eaddaaa844c437b237f240730c36f94aff99e05d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 14 Jan 2008 08:46:44 +0000 Subject: [PATCH 0328/1009] Fix adding torrents by URL. --- deluge/core/core.py | 5 +++-- deluge/ui/client.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index fa0164361..051548784 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -283,7 +283,7 @@ class Core( # Return False because the torrent was not added successfully return False - def export_add_torrent_url(self, url, save_path): + def export_add_torrent_url(self, url, save_path, options): log.info("Attempting to add url %s", url) # Get the actual filename of the torrent from the url provided. @@ -302,7 +302,8 @@ class Core( return False # Add the torrent to session - return self.export_add_torrent_file(filename, save_path, filedump) + return self.export_add_torrent_file( + filename, save_path, filedump, options) def export_remove_torrent(self, torrent_id, remove_torrent, remove_data): log.debug("Removing torrent %s from the core.", torrent_id) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index a7377a2db..a6c2bc3a7 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -263,12 +263,12 @@ def add_torrent_file(torrent_files, torrent_options=None): # The torrent was not added successfully. log.warning("Torrent %s was not added successfully.", filename) -def add_torrent_url(torrent_url): +def add_torrent_url(torrent_url, options=None): """Adds torrents to the core via url""" from deluge.common import is_url if is_url(torrent_url): try: - result = get_core().add_torrent_url(torrent_url, str()) + result = get_core().add_torrent_url(torrent_url, str(), options) except (AttributeError, socket.error): set_core_uri(None) result = False From f9a664b9c7381e49697c4b943e210e3aa7765c89 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 14 Jan 2008 09:00:13 +0000 Subject: [PATCH 0329/1009] Attempt to fix issues where torrents would not update correctly in the torrent view. Also an attempt at improving performance. --- deluge/ui/gtkui/torrentview.py | 62 +++++++++++----------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 590ba5e9c..cd00b9bdf 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -284,69 +284,47 @@ class TorrentView(listview.ListView, component.Component): """Callback function for get_torrents_status(). 'status' should be a dictionary of {torrent_id: {key, value}}.""" status = pickle.loads(status) - # Extract differences in this batch against the previous one. - # This is to prevent updating stuff we don't need to and should save - # GTK from redrawing needlessly. - new_status = {} - - for torrent_id in status.keys(): - if torrent_id in self.previous_batched_status.keys(): - old = self.previous_batched_status[torrent_id] - new = status[torrent_id] - - diff = {} - for key in new.keys(): - if not key in old.keys(): - diff[key] = new[key] - continue - - # There is a difference, so lets add it to our new dict - if new[key] != old[key]: - diff[key] = new[key] - if len(diff.keys()) > 0: - new_status[torrent_id] = diff - else: - # The torrent_id is not in the previous status - new_status[torrent_id] = status[torrent_id] - - self.previous_batched_status = status - row = self.liststore.get_iter_first() while row != None: torrent_id = self.liststore.get_value( row, self.columns["torrent_id"].column_indices[0]) - if torrent_id in new_status.keys(): + if torrent_id in status.keys(): # Set values for each column in the row for column in self.columns_to_update: column_index = self.get_column_index(column) if type(column_index) is not list: # We only have a single list store column we need to # update - if self.columns[column].status_field[0] in \ - new_status[torrent_id]: - try: + try: + # Only update if different + if self.liststore.get_value(row, column_index) != \ + status[torrent_id][ + self.columns[column].status_field[0]]: self.liststore.set_value(row, column_index, - new_status[torrent_id][ + status[torrent_id][ self.columns[column].status_field[0]]) - except (TypeError, KeyError), e: - log.warning("Unable to update column %s: %s", - column, e) + except (TypeError, KeyError), e: + log.warning("Unable to update column %s: %s", + column, e) else: # We have more than 1 liststore column to update for index in column_index: # Only update the column if the status field exists - if self.columns[column].status_field[ - column_index.index(index)] in \ - new_status[torrent_id]: - try: + try: + # Only update if different + if self.liststore.get_value(row, index) != \ + status[torrent_id][ + self.columns[column].status_field[ + column_index.index(index)]]: + self.liststore.set_value(row, index, - new_status[torrent_id][ + status[torrent_id][ self.columns[column].status_field[ column_index.index(index)]]) - except: - pass + except: + pass row = self.liststore.iter_next(row) self.status_signal_received = True From f5cc3db2d55f5a7b6c6a23e7d5bed3c6c4519be8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 14 Jan 2008 11:21:35 +0000 Subject: [PATCH 0330/1009] Make port spinbuttons editable. --- .../ui/gtkui/glade/preferences_dialog.glade | 226 +++++++++--------- 1 file changed, 112 insertions(+), 114 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index e2c8fb9e8..e9b32bb41 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -568,7 +568,6 @@ True True - False 5 1 0 0 65535 1 10 10 @@ -598,7 +597,6 @@ True True - False 5 1 0 0 65535 1 10 10 @@ -1080,71 +1078,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 3 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 1 - 2 + 2 + 3 GTK_FILL @@ -1169,43 +1136,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 2 - 3 + 1 + 2 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 3 4 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + @@ -1249,29 +1247,24 @@ Either 2 15 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - True @@ -1289,19 +1282,24 @@ Either - + True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL @@ -1552,15 +1550,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1590,38 +1606,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - From 73c14b8c471890421cc3b3e6f0698d5572c655a9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 14 Jan 2008 11:48:51 +0000 Subject: [PATCH 0331/1009] Prevent a stall in torrentview updates if a signal isn't received properly. --- deluge/ui/gtkui/torrentview.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index cd00b9bdf..7bd7bb0cb 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -39,6 +39,7 @@ import gtk, gtk.glade import gettext import gobject import cPickle as pickle +import time import deluge.common import deluge.component as component @@ -110,6 +111,7 @@ class TorrentView(listview.ListView, component.Component): self.load_state("torrentview.state") self.status_signal_received = True + self.status_signal_sent_time = 0 self.previous_batched_status = {} # Register the columns menu with the listview so it gets updated @@ -229,7 +231,8 @@ class TorrentView(listview.ListView, component.Component): # We will only send another status request if we have received the # previous. This is to prevent things from going out of sync. if not self.status_signal_received: - return + if time.time() - self.status_signal_sent_time < 2: + return # Store the 'status_fields' we need to send to core status_keys = [] @@ -274,11 +277,12 @@ class TorrentView(listview.ListView, component.Component): if torrent_ids == []: return - + # Request the statuses for all these torrent_ids, this is async so we # will deal with the return in a signal callback. self.status_signal_received = False client.get_torrents_status(torrent_ids, status_keys) + self.status_signal_sent_time = time.time() def on_torrent_status_signal(self, status): """Callback function for get_torrents_status(). 'status' should be a @@ -337,6 +341,7 @@ class TorrentView(listview.ListView, component.Component): row, self.columns["torrent_id"].column_indices[0], torrent_id) + self.update() def remove_row(self, torrent_id): """Removes a row with torrent_id""" From 8ae26b049cba83f3bf4c775283b86fb404fee606 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 14 Jan 2008 12:02:53 +0000 Subject: [PATCH 0332/1009] Fix SystemTray on systems with old PyGTK. --- deluge/ui/gtkui/systemtray.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index a5c915579..0fa339303 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -64,13 +64,18 @@ class SystemTray(component.Component): def enable(self): """Enables the system tray icon.""" log.debug("Enabling the system tray icon..") - self.tray = gtk.status_icon_new_from_icon_name("deluge") - self.tray.connect("activate", self.on_tray_clicked) - self.tray.connect("popup-menu", self.on_tray_popup) - self.tray_glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/tray_menu.glade")) + try: + self.tray = gtk.status_icon_new_from_icon_name("deluge") + except: + log.warning("Update PyGTK to 2.10 or greater for SystemTray..") + return + + self.tray.connect("activate", self.on_tray_clicked) + self.tray.connect("popup-menu", self.on_tray_popup) + self.tray_glade.signal_autoconnect({ "on_menuitem_show_deluge_activate": \ From c71d4f325852e21fea5ec5e5836408cb8da53fb2 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 14 Jan 2008 22:13:32 +0000 Subject: [PATCH 0333/1009] config within webui, see /config/ for a preview --- deluge/ui/webui/webui_plugin/config.py | 144 +++++ .../webui/webui_plugin/config_tabs_deluge.py | 46 ++ .../webui/webui_plugin/config_tabs_webui.py | 95 ++++ .../webui/webui_plugin/lib/newforms/LICENSE | 27 + .../webui_plugin/lib/newforms/__init__.py | 16 + .../webui/webui_plugin/lib/newforms/fields.py | 492 ++++++++++++++++++ .../webui/webui_plugin/lib/newforms/forms.py | 309 +++++++++++ .../webui/webui_plugin/lib/newforms/util.py | 78 +++ .../lib/newforms/utils/__init__.py | 0 .../lib/newforms/utils/__init__.pyc | Bin 0 -> 127 bytes .../lib/newforms/utils/datastructures.py | 262 ++++++++++ .../lib/newforms/utils/datastructures.pyc | Bin 0 -> 12400 bytes .../webui_plugin/lib/newforms/utils/html.py | 7 + .../webui_plugin/lib/newforms/utils/html.pyc | Bin 0 -> 619 bytes .../webui_plugin/lib/newforms/widgets.py | 353 +++++++++++++ deluge/ui/webui/webui_plugin/lib/readme.txt | 6 + deluge/ui/webui/webui_plugin/pages.py | 24 +- .../webui_plugin/static/simple_site_style.css | 36 +- .../templates/advanced/static/advanced.css | 52 +- .../webui_plugin/templates/deluge/config.html | 39 +- .../ui/webui/webui_plugin/tests/test_all.py | 7 +- deluge/ui/webui/webui_plugin/utils.py | 12 - .../ui/webui/webui_plugin/webserver_common.py | 34 ++ 23 files changed, 1991 insertions(+), 48 deletions(-) create mode 100644 deluge/ui/webui/webui_plugin/config.py create mode 100644 deluge/ui/webui/webui_plugin/config_tabs_deluge.py create mode 100644 deluge/ui/webui/webui_plugin/config_tabs_webui.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/LICENSE create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/__init__.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/fields.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/forms.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/util.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/__init__.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/__init__.pyc create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/datastructures.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/datastructures.pyc create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/html.py create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/html.pyc create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/widgets.py diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py new file mode 100644 index 000000000..5cd403ba9 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/config.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# deluge_webserver.py +# +# Copyright (C) Martijn Voncken 2008 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import lib.newforms as forms +import page_decorators as deco +import lib.webpy022 as web +from webserver_common import ws +from render import render +from lib.webpy022.http import seeother + +groups = [] +blocks = forms.utils.datastructures.SortedDict() + +class Form(forms.Form): + info = "" + title = "No Title" + def __init__(self,data = None): + if data == None: + data = self.initial_data() + forms.Form.__init__(self,data) + + def initial_data(self): + "override in subclass" + raise NotImplementedError() + + def start_save(self): + "called by config_page" + self.save(web.Storage(self.clean_data)) + self.post_save() + + def save(self, vars): + "override in subclass" + raise NotImplementedError() + + def post_save(self): + "override in subclass" + pass + + +class WebCfgForm(Form): + "config base for webui" + def initial_data(self): + return ws.config + + def save(self, data): + ws.config.update(data) + ws.save_config() + self.post_save() + + def post_save(self): + pass + + +class CfgForm(Form): + "config base for deluge-cfg" + def initial_data(self): + return ws.proxy.get_config() + def save(data): + ws.proxy.set_config(data) + + +class config_page: + """ + web.py config page + """ + def get_form_class(self,name): + try: + return blocks[name] + except KeyError: + raise Exception('no config page named:"%s"') + + @deco.deluge_page + def GET(self, name): + if name == '': + return seeother('/config/template') + + form_class = self.get_form_class(name) + f = form_class() + f.full_clean() + return self.render(f , name) + + @deco.deluge_page + def POST(self,name): + + form_class = self.get_form_class(name) + fields = form_class.base_fields.keys() + form_data = web.Storage() + vars = web.input() + for field in fields: + form_data[field] = vars.get(field) + + form = form_class(form_data) + if form.is_valid(): + ws.log.debug('save config %s' % form_data) + try: + form.start_save() + return self.render(form , name, _('These changes were saved')) + except forms.ValidationError, e: + ws.log.debug(e.message) + return self.render(form , name, error = e.message) + else: + return self.render(form , name, _('Please correct errors and try again')) + + def render(self, f , name , message = '' , error=''): + return render.config(groups, blocks, f, name , message , error) + +def register_block(group, name, form): + if not group in groups: + groups.append(group) + form.group = group + blocks[name] = form + + + diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py new file mode 100644 index 000000000..bf961a2de --- /dev/null +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# deluge_webserver.py +# +# Copyright (C) Martijn Voncken 2008 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import lib.newforms as forms +import config +import utils + + +class BandWidth(config.CfgForm): + title = _("Bandwidth") + up = forms.IntegerField(label = "TODO") + +config.register_block('deluge','bandwidth',BandWidth) + + + diff --git a/deluge/ui/webui/webui_plugin/config_tabs_webui.py b/deluge/ui/webui/webui_plugin/config_tabs_webui.py new file mode 100644 index 000000000..dbaaba10a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/config_tabs_webui.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# deluge_webserver.py +# +# Copyright (C) Martijn Voncken 2008 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import lib.newforms as forms +import config +import utils +from render import render +from webserver_common import ws + + +class Template(config.WebCfgForm): + title = _("Template") + + template = forms.ChoiceField( label=_("Template"), + choices = [(t,t) for t in ws.get_templates()]) + + button_style = forms.ChoiceField( label=_("Button style"), + choices=[ + (0,_('Text and image')), + (1, _('Image Only')), + (2, _('Text Only'))]) + + cache_templates = forms.BooleanField(label = _("Cache templates"), + required=False) + + def post_save(self): + render.apply_cfg() + + +class Server(config.WebCfgForm): + info = _("Restart webui after changing these values.") + title = _("Server") + + port = forms.IntegerField(label = _("Port"),min_value=80) + use_https = forms.BooleanField(label = _("Use https") , required=False) + +class Password(config.Form): + title = _("Password") + old_pwd = forms.CharField(widget = forms.PasswordInput + ,label = _("Current Password"), required=False) + + new1 = forms.CharField(widget = forms.PasswordInput + ,label = _("New Password"), required=False) + + new2 = forms.CharField(widget = forms.PasswordInput + ,label = _("New Password (Confirm)"), required=False) + + def initial_data(self): + return {} + + def save(self,data): + if not ws.check_pwd(data.old_pwd): + raise forms.ValidationError(_("Old password is invalid")) + if data.new1 <> data.new2: + raise forms.ValidationError(_("New Password is not equal to New Password(confirm)")) + + ws.update_pwd(data.new1) + ws.save_config() + + def post_save(self): + utils.end_session() + +config.register_block('webui','template', Template) +config.register_block('webui','server',Server) +config.register_block('webui','password',Password) diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/LICENSE b/deluge/ui/webui/webui_plugin/lib/newforms/LICENSE new file mode 100644 index 000000000..ba3e68a06 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/newforms/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2005, the Lawrence Journal-World +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/__init__.py b/deluge/ui/webui/webui_plugin/lib/newforms/__init__.py new file mode 100644 index 000000000..62125e218 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/newforms/__init__.py @@ -0,0 +1,16 @@ +""" +Django validation and HTML form handling. + +TODO: + Default value for field + Field labels + Nestable Forms + FatalValidationError -- short-circuits all other validators on a form + ValidationWarning + "This form field requires foo.js" and form.js_includes() +""" + +from util import ValidationError +from widgets import * +from fields import * +from forms import * diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/fields.py b/deluge/ui/webui/webui_plugin/lib/newforms/fields.py new file mode 100644 index 000000000..5076c5824 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/newforms/fields.py @@ -0,0 +1,492 @@ +""" +Field classes +""" + +from gettext import gettext +from util import ErrorList, ValidationError, smart_unicode +from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple +import datetime +import re +import time + +__all__ = ( + 'Field', 'CharField', 'IntegerField', + 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', + 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', + 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', + 'RegexField', 'EmailField', 'URLField', 'BooleanField', + 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', + 'ComboField', 'MultiValueField', + 'SplitDateTimeField', +) + +# These values, if given to to_python(), will trigger the self.required check. +EMPTY_VALUES = (None, '') + +try: + set # Only available in Python 2.4+ +except NameError: + from sets import Set as set # Python 2.3 fallback + +class Field(object): + widget = TextInput # Default widget to use when rendering this type of Field. + hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". + + # Tracks each time a Field instance is created. Used to retain order. + creation_counter = 0 + + def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None): + # required -- Boolean that specifies whether the field is required. + # True by default. + # widget -- A Widget class, or instance of a Widget class, that should be + # used for this Field when displaying it. Each Field has a default + # Widget that it'll use if you don't specify this. In most cases, + # the default widget is TextInput. + # label -- A verbose name for this field, for use in displaying this field in + # a form. By default, Django will use a "pretty" version of the form + # field name, if the Field is part of a Form. + # initial -- A value to use in this Field's initial display. This value is + # *not* used as a fallback if data isn't given. + # help_text -- An optional string to use as "help text" for this Field. + if label is not None: + label = smart_unicode(label) + self.required, self.label, self.initial = required, label, initial + self.help_text = smart_unicode(help_text or '') + widget = widget or self.widget + if isinstance(widget, type): + widget = widget() + + # Hook into self.widget_attrs() for any Field-specific HTML attributes. + extra_attrs = self.widget_attrs(widget) + if extra_attrs: + widget.attrs.update(extra_attrs) + + self.widget = widget + + # Increase the creation counter, and save our local copy. + self.creation_counter = Field.creation_counter + Field.creation_counter += 1 + + def clean(self, value): + """ + Validates the given value and returns its "cleaned" value as an + appropriate Python object. + + Raises ValidationError for any errors. + """ + if self.required and value in EMPTY_VALUES: + raise ValidationError(gettext(u'This field is required.')) + return value + + def widget_attrs(self, widget): + """ + Given a Widget instance (*not* a Widget class), returns a dictionary of + any HTML attributes that should be added to the Widget, based on this + Field. + """ + return {} + +class CharField(Field): + def __init__(self, max_length=None, min_length=None, *args, **kwargs): + self.max_length, self.min_length = max_length, min_length + super(CharField, self).__init__(*args, **kwargs) + + def clean(self, value): + "Validates max_length and min_length. Returns a Unicode object." + super(CharField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = smart_unicode(value) + if self.max_length is not None and len(value) > self.max_length: + raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length) + if self.min_length is not None and len(value) < self.min_length: + raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length) + return value + + def widget_attrs(self, widget): + if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): + return {'maxlength': str(self.max_length)} + +class IntegerField(Field): + def __init__(self, max_value=None, min_value=None, *args, **kwargs): + self.max_value, self.min_value = max_value, min_value + super(IntegerField, self).__init__(*args, **kwargs) + + def clean(self, value): + """ + Validates that int() can be called on the input. Returns the result + of int(). Returns None for empty values. + """ + super(IntegerField, self).clean(value) + if value in EMPTY_VALUES: + return None + try: + value = int(value) + except (ValueError, TypeError): + raise ValidationError(gettext(u'Enter a whole number.')) + if self.max_value is not None and value > self.max_value: + raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value) + if self.min_value is not None and value < self.min_value: + raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) + return value + +DEFAULT_DATE_INPUT_FORMATS = ( + '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' + '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' + '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' + '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' + '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' +) + +class DateField(Field): + def __init__(self, input_formats=None, *args, **kwargs): + super(DateField, self).__init__(*args, **kwargs) + self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS + + def clean(self, value): + """ + Validates that the input can be converted to a date. Returns a Python + datetime.date object. + """ + super(DateField, self).clean(value) + if value in EMPTY_VALUES: + return None + if isinstance(value, datetime.datetime): + return value.date() + if isinstance(value, datetime.date): + return value + for format in self.input_formats: + try: + return datetime.date(*time.strptime(value, format)[:3]) + except ValueError: + continue + raise ValidationError(gettext(u'Enter a valid date.')) + +DEFAULT_TIME_INPUT_FORMATS = ( + '%H:%M:%S', # '14:30:59' + '%H:%M', # '14:30' +) + +class TimeField(Field): + def __init__(self, input_formats=None, *args, **kwargs): + super(TimeField, self).__init__(*args, **kwargs) + self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS + + def clean(self, value): + """ + Validates that the input can be converted to a time. Returns a Python + datetime.time object. + """ + super(TimeField, self).clean(value) + if value in EMPTY_VALUES: + return None + if isinstance(value, datetime.time): + return value + for format in self.input_formats: + try: + return datetime.time(*time.strptime(value, format)[3:6]) + except ValueError: + continue + raise ValidationError(gettext(u'Enter a valid time.')) + +DEFAULT_DATETIME_INPUT_FORMATS = ( + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' + '%Y-%m-%d', # '2006-10-25' + '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' + '%m/%d/%Y %H:%M', # '10/25/2006 14:30' + '%m/%d/%Y', # '10/25/2006' + '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' + '%m/%d/%y %H:%M', # '10/25/06 14:30' + '%m/%d/%y', # '10/25/06' +) + +class DateTimeField(Field): + def __init__(self, input_formats=None, *args, **kwargs): + super(DateTimeField, self).__init__(*args, **kwargs) + self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS + + def clean(self, value): + """ + Validates that the input can be converted to a datetime. Returns a + Python datetime.datetime object. + """ + super(DateTimeField, self).clean(value) + if value in EMPTY_VALUES: + return None + if isinstance(value, datetime.datetime): + return value + if isinstance(value, datetime.date): + return datetime.datetime(value.year, value.month, value.day) + for format in self.input_formats: + try: + return datetime.datetime(*time.strptime(value, format)[:6]) + except ValueError: + continue + raise ValidationError(gettext(u'Enter a valid date/time.')) + +class RegexField(Field): + def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): + """ + regex can be either a string or a compiled regular expression object. + error_message is an optional error message to use, if + 'Enter a valid value' is too generic for you. + """ + super(RegexField, self).__init__(*args, **kwargs) + if isinstance(regex, basestring): + regex = re.compile(regex) + self.regex = regex + self.max_length, self.min_length = max_length, min_length + self.error_message = error_message or gettext(u'Enter a valid value.') + + def clean(self, value): + """ + Validates that the input matches the regular expression. Returns a + Unicode object. + """ + super(RegexField, self).clean(value) + if value in EMPTY_VALUES: + value = u'' + value = smart_unicode(value) + if value == u'': + return value + if self.max_length is not None and len(value) > self.max_length: + raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length) + if self.min_length is not None and len(value) < self.min_length: + raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length) + if not self.regex.search(value): + raise ValidationError(self.error_message) + return value + +email_re = re.compile( + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string + r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain + +class EmailField(RegexField): + def __init__(self, max_length=None, min_length=None, *args, **kwargs): + RegexField.__init__(self, email_re, max_length, min_length, + gettext(u'Enter a valid e-mail address.'), *args, **kwargs) + +url_re = re.compile( + r'^https?://' # http:// or https:// + r'(?:[A-Z0-9-]+\.)+[A-Z]{2,6}' # domain + r'(?::\d+)?' # optional port + r'(?:/?|/\S+)$', re.IGNORECASE) + +try: + from django.conf import settings + URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT +except ImportError: + # It's OK if Django settings aren't configured. + URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' + +class URLField(RegexField): + def __init__(self, max_length=None, min_length=None, verify_exists=False, + validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): + super(URLField, self).__init__(url_re, max_length, min_length, gettext(u'Enter a valid URL.'), *args, **kwargs) + self.verify_exists = verify_exists + self.user_agent = validator_user_agent + + def clean(self, value): + value = super(URLField, self).clean(value) + if value == u'': + return value + if self.verify_exists: + import urllib2 + from django.conf import settings + headers = { + "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", + "Accept-Language": "en-us,en;q=0.5", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Connection": "close", + "User-Agent": self.user_agent, + } + try: + req = urllib2.Request(value, None, headers) + u = urllib2.urlopen(req) + except ValueError: + raise ValidationError(gettext(u'Enter a valid URL.')) + except: # urllib2.URLError, httplib.InvalidURL, etc. + raise ValidationError(gettext(u'This URL appears to be a broken link.')) + return value + +class BooleanField(Field): + widget = CheckboxInput + + def clean(self, value): + "Returns a Python boolean object." + super(BooleanField, self).clean(value) + return bool(value) + +class NullBooleanField(BooleanField): + """ + A field whose valid values are None, True and False. Invalid values are + cleaned to None. + """ + widget = NullBooleanSelect + + def clean(self, value): + return {True: True, False: False}.get(value, None) + +class ChoiceField(Field): + def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None, help_text=None): + super(ChoiceField, self).__init__(required, widget, label, initial, help_text) + self.choices = choices + + def _get_choices(self): + return self._choices + + def _set_choices(self, value): + # Setting choices also sets the choices on the widget. + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + def clean(self, value): + """ + Validates that the input is in self.choices. + """ + value = super(ChoiceField, self).clean(value) + if value in EMPTY_VALUES: + value = u'' + value = smart_unicode(value) + if value == u'': + return value + valid_values = set([str(k) for k, v in self.choices]) + if value not in valid_values: + raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) + return value + +class MultipleChoiceField(ChoiceField): + hidden_widget = MultipleHiddenInput + + def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None, help_text=None): + super(MultipleChoiceField, self).__init__(choices, required, widget, label, initial, help_text) + + def clean(self, value): + """ + Validates that the input is a list or tuple. + """ + if self.required and not value: + raise ValidationError(gettext(u'This field is required.')) + elif not self.required and not value: + return [] + if not isinstance(value, (list, tuple)): + raise ValidationError(gettext(u'Enter a list of values.')) + new_value = [] + for val in value: + val = smart_unicode(val) + new_value.append(val) + # Validate that each value in the value list is in self.choices. + valid_values = set([smart_unicode(k) for k, v in self.choices]) + for val in new_value: + if val not in valid_values: + raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val) + return new_value + +class ComboField(Field): + """ + A Field whose clean() method calls multiple Field clean() methods. + """ + def __init__(self, fields=(), *args, **kwargs): + super(ComboField, self).__init__(*args, **kwargs) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by ComboField, not by those + # individual fields. + for f in fields: + f.required = False + self.fields = fields + + def clean(self, value): + """ + Validates the given value against all of self.fields, which is a + list of Field instances. + """ + super(ComboField, self).clean(value) + for field in self.fields: + value = field.clean(value) + return value + +class MultiValueField(Field): + """ + A Field that is composed of multiple Fields. + + Its clean() method takes a "decompressed" list of values. Each value in + this list is cleaned by the corresponding field -- the first value is + cleaned by the first field, the second value is cleaned by the second + field, etc. Once all fields are cleaned, the list of clean values is + "compressed" into a single value. + + Subclasses should implement compress(), which specifies how a list of + valid values should be converted to a single value. Subclasses should not + have to implement clean(). + + You'll probably want to use this with MultiWidget. + """ + def __init__(self, fields=(), *args, **kwargs): + super(MultiValueField, self).__init__(*args, **kwargs) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by MultiValueField, not by those + # individual fields. + for f in fields: + f.required = False + self.fields = fields + + def clean(self, value): + """ + Validates every value in the given list. A value is validated against + the corresponding Field in self.fields. + + For example, if this MultiValueField was instantiated with + fields=(DateField(), TimeField()), clean() would call + DateField.clean(value[0]) and TimeField.clean(value[1]). + """ + clean_data = [] + errors = ErrorList() + if self.required and not value: + raise ValidationError(gettext(u'This field is required.')) + elif not self.required and not value: + return self.compress([]) + if not isinstance(value, (list, tuple)): + raise ValidationError(gettext(u'Enter a list of values.')) + for i, field in enumerate(self.fields): + try: + field_value = value[i] + except KeyError: + field_value = None + if self.required and field_value in EMPTY_VALUES: + raise ValidationError(gettext(u'This field is required.')) + try: + clean_data.append(field.clean(field_value)) + except ValidationError, e: + # Collect all validation errors in a single list, which we'll + # raise at the end of clean(), rather than raising a single + # exception for the first error we encounter. + errors.extend(e.messages) + if errors: + raise ValidationError(errors) + return self.compress(clean_data) + + def compress(self, data_list): + """ + Returns a single value for the given list of values. The values can be + assumed to be valid. + + For example, if this MultiValueField was instantiated with + fields=(DateField(), TimeField()), this might return a datetime + object created by combining the date and time in data_list. + """ + raise NotImplementedError('Subclasses must implement this method.') + +class SplitDateTimeField(MultiValueField): + def __init__(self, *args, **kwargs): + fields = (DateField(), TimeField()) + super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) + + def compress(self, data_list): + if data_list: + return datetime.datetime.combine(*data_list) + return None diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/forms.py b/deluge/ui/webui/webui_plugin/lib/newforms/forms.py new file mode 100644 index 000000000..97f0efcb6 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/newforms/forms.py @@ -0,0 +1,309 @@ +""" +Form classes +""" + +from utils.datastructures import SortedDict, MultiValueDict +from utils.html import escape +from fields import Field +from widgets import TextInput, Textarea, HiddenInput, MultipleHiddenInput +from util import flatatt, StrAndUnicode, ErrorDict, ErrorList, ValidationError +import copy + +__all__ = ('BaseForm', 'Form') + +NON_FIELD_ERRORS = '__all__' + +def pretty_name(name): + "Converts 'first_name' to 'First name'" + name = name[0].upper() + name[1:] + return name.replace('_', ' ') + +class SortedDictFromList(SortedDict): + "A dictionary that keeps its keys in the order in which they're inserted." + # This is different than django.utils.datastructures.SortedDict, because + # this takes a list/tuple as the argument to __init__(). + def __init__(self, data=None): + if data is None: data = [] + self.keyOrder = [d[0] for d in data] + dict.__init__(self, dict(data)) + + def copy(self): + return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()]) + +class DeclarativeFieldsMetaclass(type): + """ + Metaclass that converts Field attributes to a dictionary called + 'base_fields', taking into account parent class 'base_fields' as well. + """ + def __new__(cls, name, bases, attrs): + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + + attrs['base_fields'] = SortedDictFromList(fields) + return type.__new__(cls, name, bases, attrs) + +class BaseForm(StrAndUnicode): + # This is the main implementation of all the Form logic. Note that this + # class is different than Form. See the comments by the Form class for more + # information. Any improvements to the form API should be made to *this* + # class, not to the Form class. + def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None): + self.is_bound = data is not None + self.data = data or {} + self.auto_id = auto_id + self.prefix = prefix + self.initial = initial or {} + self.__errors = None # Stores the errors after clean() has been called. + + # The base_fields class attribute is the *class-wide* definition of + # fields. Because a particular *instance* of the class might want to + # alter self.fields, we create self.fields here by copying base_fields. + # Instances should always modify self.fields; they should not modify + # self.base_fields. + self.fields = self.base_fields.copy() + + def __unicode__(self): + return self.as_table() + + def __iter__(self): + for name, field in self.fields.items(): + yield BoundField(self, field, name) + + def __getitem__(self, name): + "Returns a BoundField with the given name." + try: + field = self.fields[name] + except KeyError: + raise KeyError('Key %r not found in Form' % name) + return BoundField(self, field, name) + + def _errors(self): + "Returns an ErrorDict for self.data" + if self.__errors is None: + self.full_clean() + return self.__errors + errors = property(_errors) + + def is_valid(self): + """ + Returns True if the form has no errors. Otherwise, False. If errors are + being ignored, returns False. + """ + return self.is_bound and not bool(self.errors) + + def add_prefix(self, field_name): + """ + Returns the field name with a prefix appended, if this Form has a + prefix set. + + Subclasses may wish to override. + """ + return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name + + def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): + "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." + top_errors = self.non_field_errors() # Errors that should be displayed above all fields. + output, hidden_fields = [], [] + for name, field in self.fields.items(): + bf = BoundField(self, field, name) + bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable. + if bf.is_hidden: + if bf_errors: + top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors]) + hidden_fields.append(unicode(bf)) + else: + if errors_on_separate_row and bf_errors: + output.append(error_row % bf_errors) + label = bf.label and bf.label_tag(escape(bf.label + ':')) or '' + if field.help_text: + help_text = help_text_html % field.help_text + else: + help_text = u'' + output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text}) + if top_errors: + output.insert(0, error_row % top_errors) + if hidden_fields: # Insert any hidden fields in the last row. + str_hidden = u''.join(hidden_fields) + if output: + last_row = output[-1] + # Chop off the trailing row_ender (e.g. '') and insert the hidden fields. + output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender + else: # If there aren't any rows in the output, just append the hidden fields. + output.append(str_hidden) + return u'\n'.join(output) + + def as_table(self): + "Returns this form rendered as HTML s -- excluding the
." + return self._html_output(u'%(label)s%(errors)s%(field)s%(help_text)s', u'%s', '', u'
%s', False) + + def as_ul(self): + "Returns this form rendered as HTML
  • s -- excluding the
      ." + return self._html_output(u'
    • %(errors)s%(label)s %(field)s%(help_text)s
    • ', u'
    • %s
    • ', '', u' %s', False) + + def as_p(self): + "Returns this form rendered as HTML

      s." + return self._html_output(u'

      %(label)s %(field)s%(help_text)s

      ', u'

      %s

      ', '

      ', u' %s', True) + + def non_field_errors(self): + """ + Returns an ErrorList of errors that aren't associated with a particular + field -- i.e., from Form.clean(). Returns an empty ErrorList if there + are none. + """ + return self.errors.get(NON_FIELD_ERRORS, ErrorList()) + + def full_clean(self): + """ + Cleans all of self.data and populates self.__errors and self.clean_data. + """ + errors = ErrorDict() + if not self.is_bound: # Stop further processing. + self.__errors = errors + return + self.clean_data = {} + for name, field in self.fields.items(): + # value_from_datadict() gets the data from the dictionary. + # Each widget type knows how to retrieve its own data, because some + # widgets split data over several HTML fields. + value = field.widget.value_from_datadict(self.data, self.add_prefix(name)) + try: + value = field.clean(value) + self.clean_data[name] = value + if hasattr(self, 'clean_%s' % name): + value = getattr(self, 'clean_%s' % name)() + self.clean_data[name] = value + except ValidationError, e: + errors[name] = e.messages + try: + self.clean_data = self.clean() + except ValidationError, e: + errors[NON_FIELD_ERRORS] = e.messages + if errors: + delattr(self, 'clean_data') + self.__errors = errors + + def clean(self): + """ + Hook for doing any extra form-wide cleaning after Field.clean() been + called on every field. Any ValidationError raised by this method will + not be associated with a particular field; it will have a special-case + association with the field named '__all__'. + """ + return self.clean_data + +class Form(BaseForm): + "A collection of Fields, plus their associated data." + # This is a separate class from BaseForm in order to abstract the way + # self.fields is specified. This class (Form) is the one that does the + # fancy metaclass stuff purely for the semantic sugar -- it allows one + # to define a form using declarative syntax. + # BaseForm itself has no way of designating self.fields. + __metaclass__ = DeclarativeFieldsMetaclass + +class BoundField(StrAndUnicode): + "A Field plus data" + def __init__(self, form, field, name): + self.form = form + self.field = field + self.name = name + self.html_name = form.add_prefix(name) + if self.field.label is None: + self.label = pretty_name(name) + else: + self.label = self.field.label + self.help_text = field.help_text or '' + + def __unicode__(self): + "Renders this field as an HTML widget." + # Use the 'widget' attribute on the field to determine which type + # of HTML widget to use. + value = self.as_widget(self.field.widget) + if not isinstance(value, basestring): + # Some Widget render() methods -- notably RadioSelect -- return a + # "special" object rather than a string. Call the __str__() on that + # object to get its rendered value. + value = value.__str__() + return value + + def _errors(self): + """ + Returns an ErrorList for this field. Returns an empty ErrorList + if there are none. + """ + return self.form.errors.get(self.name, ErrorList()) + errors = property(_errors) + + def as_widget(self, widget, attrs=None): + attrs = attrs or {} + auto_id = self.auto_id + if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'): + attrs['id'] = auto_id + if not self.form.is_bound: + data = self.form.initial.get(self.name, self.field.initial) + else: + data = self.data + return widget.render(self.html_name, data, attrs=attrs) + + def as_text(self, attrs=None): + """ + Returns a string of HTML for representing this as an . + """ + return self.as_widget(TextInput(), attrs) + + def as_textarea(self, attrs=None): + "Returns a string of HTML for representing this as a ' % (flatatt(final_attrs), escape(value)) + +class CheckboxInput(Widget): + def __init__(self, attrs=None, check_test=bool): + # check_test is a callable that takes a value and returns True + # if the checkbox should be checked for that value. + self.attrs = attrs or {} + self.check_test = check_test + + def render(self, name, value, attrs=None): + final_attrs = self.build_attrs(attrs, type='checkbox', name=name) + try: + result = self.check_test(value) + except: # Silently catch exceptions + result = False + if result: + final_attrs['checked'] = 'checked' + if value not in ('', True, False, None): + final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty. + return u'' % flatatt(final_attrs) + +class Select(Widget): + def __init__(self, attrs=None, choices=()): + self.attrs = attrs or {} + # choices can be any iterable, but we may need to render this widget + # multiple times. Thus, collapse it into a list so it can be consumed + # more than once. + self.choices = list(choices) + + def render(self, name, value, attrs=None, choices=()): + if value is None: value = '' + final_attrs = self.build_attrs(attrs, name=name) + output = [u'' % flatatt(final_attrs)] + str_value = smart_unicode(value) # Normalize to string. + for option_value, option_label in chain(self.choices, choices): + option_value = smart_unicode(option_value) + selected_html = (option_value == str_value) and u' selected="selected"' or '' + output.append(u'' % (escape(option_value), selected_html, escape(smart_unicode(option_label)))) + output.append(u'') + return u'\n'.join(output) + +class NullBooleanSelect(Select): + """ + A Select Widget intended to be used with NullBooleanField. + """ + def __init__(self, attrs=None): + choices = ((u'1', gettext('Unknown')), (u'2', gettext('Yes')), (u'3', gettext('No'))) + super(NullBooleanSelect, self).__init__(attrs, choices) + + def render(self, name, value, attrs=None, choices=()): + try: + value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value] + except KeyError: + value = u'1' + return super(NullBooleanSelect, self).render(name, value, attrs, choices) + + def value_from_datadict(self, data, name): + value = data.get(name, None) + return {u'2': True, u'3': False, True: True, False: False}.get(value, None) + +class SelectMultiple(Widget): + def __init__(self, attrs=None, choices=()): + # choices can be any iterable + self.attrs = attrs or {} + self.choices = choices + + def render(self, name, value, attrs=None, choices=()): + if value is None: value = [] + final_attrs = self.build_attrs(attrs, name=name) + output = [u'') + return u'\n'.join(output) + + def value_from_datadict(self, data, name): + if isinstance(data, MultiValueDict): + return data.getlist(name) + return data.get(name, None) + +class RadioInput(StrAndUnicode): + "An object used by RadioFieldRenderer that represents a single ." + def __init__(self, name, value, attrs, choice, index): + self.name, self.value = name, value + self.attrs = attrs + self.choice_value = smart_unicode(choice[0]) + self.choice_label = smart_unicode(choice[1]) + self.index = index + + def __unicode__(self): + return u'' % (self.tag(), self.choice_label) + + def is_checked(self): + return self.value == self.choice_value + + def tag(self): + if self.attrs.has_key('id'): + self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) + final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) + if self.is_checked(): + final_attrs['checked'] = 'checked' + return u'' % flatatt(final_attrs) + +class RadioFieldRenderer(StrAndUnicode): + "An object used by RadioSelect to enable customization of radio widgets." + def __init__(self, name, value, attrs, choices): + self.name, self.value, self.attrs = name, value, attrs + self.choices = choices + + def __iter__(self): + for i, choice in enumerate(self.choices): + yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) + + def __getitem__(self, idx): + choice = self.choices[idx] # Let the IndexError propogate + return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx) + + def __unicode__(self): + "Outputs a
        for this set of radio fields." + return u'
          \n%s\n
        ' % u'\n'.join([u'
      • %s
      • ' % w for w in self]) + +class RadioSelect(Select): + def render(self, name, value, attrs=None, choices=()): + "Returns a RadioFieldRenderer instance rather than a Unicode string." + if value is None: value = '' + str_value = smart_unicode(value) # Normalize to string. + attrs = attrs or {} + return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices))) + + def id_for_label(self, id_): + # RadioSelect is represented by multiple fields, + # each of which has a distinct ID. The IDs are made distinct by a "_X" + # suffix, where X is the zero-based index of the radio field. Thus, + # the label for a RadioSelect should reference the first one ('_0'). + if id_: + id_ += '_0' + return id_ + id_for_label = classmethod(id_for_label) + +class CheckboxSelectMultiple(SelectMultiple): + def render(self, name, value, attrs=None, choices=()): + if value is None: value = [] + has_id = attrs and attrs.has_key('id') + final_attrs = self.build_attrs(attrs, name=name) + output = [u'
          '] + str_values = set([smart_unicode(v) for v in value]) # Normalize to strings. + for i, (option_value, option_label) in enumerate(chain(self.choices, choices)): + # If an ID attribute was given, add a numeric index as a suffix, + # so that the checkboxes don't all have the same ID attribute. + if has_id: + final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i)) + cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values) + option_value = smart_unicode(option_value) + rendered_cb = cb.render(name, option_value) + output.append(u'
        • ' % (rendered_cb, escape(smart_unicode(option_label)))) + output.append(u'
        ') + return u'\n'.join(output) + + def id_for_label(self, id_): + # See the comment for RadioSelect.id_for_label() + if id_: + id_ += '_0' + return id_ + id_for_label = classmethod(id_for_label) + +class MultiWidget(Widget): + """ + A widget that is composed of multiple widgets. + + Its render() method takes a "decompressed" list of values, not a single + value. Each value in this list is rendered in the corresponding widget -- + the first value is rendered in the first widget, the second value is + rendered in the second widget, etc. + + Subclasses should implement decompress(), which specifies how a single + value should be converted to a list of values. Subclasses should not + have to implement clean(). + + Subclasses may implement format_output(), which takes the list of rendered + widgets and returns HTML that formats them any way you'd like. + + You'll probably want to use this with MultiValueField. + """ + def __init__(self, widgets, attrs=None): + self.widgets = [isinstance(w, type) and w() or w for w in widgets] + super(MultiWidget, self).__init__(attrs) + + def render(self, name, value, attrs=None): + # value is a list of values, each corresponding to a widget + # in self.widgets. + if not isinstance(value, list): + value = self.decompress(value) + output = [] + for i, widget in enumerate(self.widgets): + try: + widget_value = value[i] + except KeyError: + widget_value = None + output.append(widget.render(name + '_%s' % i, widget_value, attrs)) + return self.format_output(output) + + def value_from_datadict(self, data, name): + return [data.get(name + '_%s' % i) for i in range(len(self.widgets))] + + def format_output(self, rendered_widgets): + return u''.join(rendered_widgets) + + def decompress(self, value): + """ + Returns a list of decompressed values for the given compressed value. + The given value can be assumed to be valid, but not necessarily + non-empty. + """ + raise NotImplementedError('Subclasses must implement this method.') + +class SplitDateTimeWidget(MultiWidget): + """ + A Widget that splits datetime input into two boxes. + """ + def __init__(self, attrs=None): + widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs)) + super(SplitDateTimeWidget, self).__init__(widgets, attrs) + + def decompress(self, value): + if value: + return [value.date(), value.time()] + return [None, None] diff --git a/deluge/ui/webui/webui_plugin/lib/readme.txt b/deluge/ui/webui/webui_plugin/lib/readme.txt index 16c5eee85..50a6a66c2 100644 --- a/deluge/ui/webui/webui_plugin/lib/readme.txt +++ b/deluge/ui/webui/webui_plugin/lib/readme.txt @@ -6,3 +6,9 @@ Disclaimer: Some may have been adapted to work better with deluge. But they will import other parts of deluge or Webui. +LICENCE: +All components are GPL compatible. +All these components are Licensed under their original license. +See docstring or LICENSE files. + + diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 9c0c96562..86761b05e 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -35,6 +35,9 @@ from webserver_common import ws from utils import * from render import render, error_page import page_decorators as deco +import config_tabs_webui #auto registers +import config_tabs_deluge #auto registers +from config import config_page #import forms import lib.webpy022 as web @@ -64,7 +67,7 @@ urls = ( "/resume_all", "resume_all", "/refresh/set", "refresh_set", "/refresh/(.*)", "refresh", - "/config", "config", + "/config/(.*)", "config_page", "/home", "home", "/about", "about", "/logout", "logout", @@ -91,7 +94,7 @@ class login: def POST(self): vars = web.input(pwd = None, redir = None) - if check_pwd(vars.pwd): + if ws.check_pwd(vars.pwd): #start new session start_session() do_redirect() @@ -215,7 +218,7 @@ class remote_torrent_add: vars = web.input(pwd = None, torrent = {}, data_b64 = None , torrent_name= None) - if not check_pwd(vars.pwd): + if not ws.check_pwd(vars.pwd): return 'error:wrong password' if vars.data_b64: #b64 post (greasemonkey) @@ -305,21 +308,6 @@ class refresh_set: else: error_page(_('refresh must be > 0')) -class config: #namespace clash? - """core config - TODO:good validation. - """ - @deco.deluge_page - def GET(self, name): - return render.config(forms.bandwith()) - - def POST(self): - vars = web.input(max_download=None, max_upload=None) - - #self.config.set("max_download_speed", float(str_bwdown)) - raise NotImplementedError('todo') - - class home: @deco.check_session def GET(self, name): diff --git a/deluge/ui/webui/webui_plugin/static/simple_site_style.css b/deluge/ui/webui/webui_plugin/static/simple_site_style.css index 3776994e8..092b79eb9 100755 --- a/deluge/ui/webui/webui_plugin/static/simple_site_style.css +++ b/deluge/ui/webui/webui_plugin/static/simple_site_style.css @@ -1,5 +1,5 @@ -/* ----------------------------------------------------------- Theme Name: Simple Theme URI: http://deluge-torrent.org Description: Deluge Theme Version: 1.0 ----------------------------------------------------------- */ BODY { background: #304663 url(images/simple_bg.jpg) repeat-x; font-family: trebuchet ms; font-size: 10pt; margin: 0; } /* GENERIC STYLES */ a img {border: 0px} hr {color: #627082; margin: 15px 0 15px 0;} /* STRUCTURE */ #page { min-width: 800px; margin-left: auto; margin-right: auto; } #main_content { background:url(images/simple_line.jpg) repeat-x; } #simple_logo { background:url(images/simple_logo.jpg) no-repeat; } #main { padding-top: 20px; padding-left: 20px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr th { background: #1f3044; font-size: 16px; border: 0px; - white-space: nowrap; } #main form table tr td{ border: 0px; color: #fff; font-size: 12px; white-space: nowrap; } #main form table tr th a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr th a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr th a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} #button { border:1px solid #23344b; background: #99acc3; color: #000; font-family:verdana, arial, helvetica, sans-serif; font-size:10px; margin-top:5px; } INPUT{ border:1px solid #23344b; background: #99acc3; color: #000; } TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: trebuchet MS; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ } +/* ----------------------------------------------------------- Theme Name: Simple Theme URI: http://deluge-torrent.org Description: Deluge Theme Version: 1.0 ----------------------------------------------------------- */ BODY { background: #304663 url(images/simple_bg.jpg) repeat-x; font-family: trebuchet ms; font-size: 10pt; margin: 0; } /* GENERIC STYLES */ a img {border: 0px} hr {color: #627082; margin: 15px 0 15px 0;} /* STRUCTURE */ #page { min-width: 800px; margin-left: auto; margin-right: auto; } #main_content { background:url(images/simple_line.jpg) repeat-x; } #simple_logo { background:url(images/simple_logo.jpg) no-repeat; } #main { padding-top: 20px; padding-left: 20px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr { background: #1f3044; font-size: 16px; border: 0px; + white-space: nowrap; } #main form table tr td{ border: 0px; color: #fff; font-size: 12px; white-space: nowrap; } #main form table tr a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} #button { border:1px solid #23344b; background: #99acc3; color: #000; font-family:verdana, arial, helvetica, sans-serif; font-size:10px; margin-top:5px; } INPUT{ border:1px solid #23344b; background: #99acc3; color: #000; } TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: trebuchet MS; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ } div.progress_bar_outer { /*used in table-view*/ width:150px; @@ -88,4 +88,36 @@ th { border: #2a425c 1px solid; } +#config_chooser { + float: left; + width:150px; + text-align:left; + height:60%; +} + +#config_chooser ul { + list-style-type: none; +} + +#config_chooser li:hover { + background-color:#68a; +} + +#config_chooser li.selected { + background-color:#900; +} + +#config_panel { + height:60%; +} +#config_panel th { + font-size: 12px; + text-align:right; + color:#FFFFFF; +} + +#config_panel table { + background-color:none; +} + /* Hides from IE-mac \*/ * html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */ diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index b4fa9db10..2b801a1b8 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -10,23 +10,20 @@ table {font-family: Bitstream Vera,Verdana;} div {font-family: Bitstream Vera,Ve margin: 0; padding:0; } #simple_logo { background:url(../../static/images/simple_logo.jpg) no-repeat; } #main { margin: 0; - padding:0; padding-top: 6px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr th { background: #1f3044; font-size: 16px; border: 0px; + padding:0; padding-top: 6px; color: #fff; } #main form table { border: #2a425c 1px solid; } #main form table tr { border: 0px; } #main form table tr { font-size: 16px; border: 0px; white-space: nowrap; } #main form table tr td{ border: 0px; color: #fff; font-size: 12px; white-space: nowrap; - font-family: Bitstream Vera,Verdana; } #main form table tr th a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr th a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr th a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap; - font-family: Bitstream Vera,Verdana; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} input{ + font-family: Bitstream Vera,Verdana; } #main form table tr a { color: #8fa6c3; font-size: 16px; white-space: nowrap; } #main form table tr th a, a:active, a:visited { color: #8fa6c3; text-decoration: none; } #main form table tr th a:hover {color: #fff; text-decoration: underline;} #main form table tr td a { color: #fff; font-size: 12px; white-space: nowrap; + font-family: Bitstream Vera,Verdana; } #main form table tr td a, a:active, a:visited { color: #fff; text-decoration: none;} #main form table tr td a:hover {color: #fff; text-decoration: underline;} #main a { color: #fff; font-size: 12px; } #main a, a:active, a:visited { color: #fff; text-decoration: none;} #main a:hover {color: #fff; text-decoration: underline;} .info { text-align: right; padding: 0 50px 0 0; color: #8fa6c3; font-size: 16px; letter-spacing: 4px; font-weight: bold; } .title { color: #dce4ee; font-size: 32px; padding: 10px 50px 0 0; text-align: right; } .title a, a:active, a:visited { color: #dce4ee; text-decoration: none;} .title a:hover {color: #fff; text-decoration: underline;} /*DISABLED! +input{ background-color: #37506f; border:1px solid #68a; - background: #99acc3; color: #000; - /*vertical-align:middle;*/ - -moz-border-radius:5px; - /*margin-top:5px;*/ - } + -moz-border-radius:5px; } input:hover { background-color:#68a; -} TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: Bitstream Vera,Verdana; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ } +} TEXTAREA{ border:1px solid #23344b; background: #99acc3; width:480px; } */ .footertext a { color: #c0c0c0; text-decoration:none;} .footertext a:visited { color: #c0c0c0; text-decoration:none;} .footertext a:active { color: #c0c0c0; text-decoration:none;} .footertext a:hover {color: #fff; text-decoration: underline;} .footertext { text-align: center; padding: 60px 0 0 0; font-size: 8pt; left: -100px; font-family: Bitstream Vera,Verdana; color: #fff; position: relative; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } div.progress_bar{ background-color:#4573a5; /*color:blue;*/ -moz-border-radius:5px; /*ff only setting*/ } div.progress_bar_outer { /*used in table-view*/ width:150px; @@ -255,6 +252,43 @@ form { /*all forms!*/ border:0; } +#config_chooser { + float: left; + width:150px; + text-align:left; + height:60%; +} + +#config_chooser ul { + list-style-type: none; +} + +#config_chooser li:hover { + background-color:#68a; +} + +#config_chooser li.selected { + background-color:#900; +} + +#config_panel { + height:60%; +} +#config_panel th { + font-size: 12px; + text-align:right; + color:#FFFFFF; +} + +#config_panel table { + background-color:none; +} + +ul.errorlist { + display:hidden; +} + + #torrent_list { -moz-border-radius:7px; } diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/config.html b/deluge/ui/webui/webui_plugin/templates/deluge/config.html index e2670d0ae..4ee2d75cc 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/config.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/config.html @@ -1,10 +1,39 @@ -$def with (form) -$:render.header(_('Config')) +$def with (groups, pages, form, selected, message, error) -
        Not Implemented!
        +$:render.header(_("Config")) + + +
        +$for group in groups: +

        $group

        + +
        + + +
        +

        $form.group / $form.title

        +
        $form.info
        -$:form.render() - + +$:form.as_table() +
        +$if message: +
        $message
        +$if error: +
        $error
        + + + + +
        +
        $:render.footer() diff --git a/deluge/ui/webui/webui_plugin/tests/test_all.py b/deluge/ui/webui/webui_plugin/tests/test_all.py index a5ca186ee..4c163c344 100644 --- a/deluge/ui/webui/webui_plugin/tests/test_all.py +++ b/deluge/ui/webui/webui_plugin/tests/test_all.py @@ -12,8 +12,6 @@ import operator ws.init_06() print 'test-env=',ws.env - - #CONFIG: BASE_URL = 'http://localhost:8112' PWD = 'deluge' @@ -358,6 +356,11 @@ class TestIntegration(TestWebUiBase): # +if True: + cfg = ws.proxy.get_config() + for key in sorted(cfg.keys()): + print key,cfg[key] + if False: suiteFew = unittest.TestSuite() diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 6cafac4f1..78bcfa75b 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -44,7 +44,6 @@ import random from operator import attrgetter import datetime import pickle -from md5 import md5 from urlparse import urlparse from webserver_common import REVNO, VERSION, TORRENT_KEYS, STATE_MESSAGES @@ -107,12 +106,6 @@ def getcookie(key, default = None): ck = cookies() return ck.get(key, default) -#utils: -def check_pwd(pwd): - m = md5() - m.update(ws.config.get('pwd_salt')) - m.update(pwd) - return (m.digest() == ws.config.get('pwd_md5')) def get_stats(): stats = Storage({ @@ -260,8 +253,3 @@ def get_category_choosers(torrent_list): #/utils -__all__ = [ - 'do_redirect', 'start_session','getcookie' - ,'setcookie','end_session', - 'get_torrent_status', 'check_pwd','get_categories' - ,'filter_torrent_state','web','get_category_choosers','get_stats'] diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index c1aa93294..743ad20e2 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -41,6 +41,7 @@ import random import pickle import sys import base64 +from md5 import md5 random.seed() @@ -203,6 +204,39 @@ class Ws: format="[%(levelname)s] %(message)s") self.log = logging + + #utils for config: + def get_templates(self): + template_path = os.path.join(os.path.dirname(__file__), 'templates') + return [dirname for dirname + in os.listdir(template_path) + if os.path.isdir(os.path.join(template_path, dirname)) + and not dirname.startswith('.')] + + def save_config(self): + self.log.debug('Save Webui Config') + data = pickle.dumps(self.config) + f = open(self.config_file,'wb') + f.write(data) + f.close() + + def update_pwd(self,pwd): + sm = md5() + sm.update(str(random.getrandbits(5000))) + salt = sm.digest() + self.config["pwd_salt"] = salt + # + m = md5() + m.update(salt) + m.update(pwd) + self.config["pwd_md5"] = m.digest() + + def check_pwd(self,pwd): + m = md5() + m.update(self.config.get('pwd_salt')) + m.update(pwd) + return (m.digest() == self.config.get('pwd_md5')) + ws =Ws() From 20cce5aa6871b20cea9ad8797690c4ffded46a31 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 14 Jan 2008 22:14:14 +0000 Subject: [PATCH 0334/1009] don't commit *.pyc files --- .../lib/newforms/utils/__init__.pyc | Bin 127 -> 0 bytes .../lib/newforms/utils/datastructures.pyc | Bin 12400 -> 0 bytes .../webui_plugin/lib/newforms/utils/html.pyc | Bin 619 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/__init__.pyc delete mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/datastructures.pyc delete mode 100644 deluge/ui/webui/webui_plugin/lib/newforms/utils/html.pyc diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/utils/__init__.pyc b/deluge/ui/webui/webui_plugin/lib/newforms/utils/__init__.pyc deleted file mode 100644 index 245c96906c1cad5d7e53a457bfb40e507e6cd16c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127 zcmdn|iI>a3K*>Fs0SXv_v;z3F#QjjENaABn24bU@QU@K~%L}7O}1BYE8GKwr9FW zeS2&TSX6?ktWv2^#Uh)mvr83~s%)~!E{iO($S=qu+bpt9RdUXE`!%w7c@)EK-RZ}@ z-|K$gxu^RN|2{nX+;gYi?5OPD1pdB;rV|`|r2?fkEahA36Q%Ae_0#M4QBy%p&Q26( zCzM}TL0!&H7H21w-^lNsD$X{PKb@bQDb7wQe^y;-9m2Hf3=Z!G$!4$|cG5z$8qU6s zrq^+RToeVzS|;6f$WY^O*Xzm`$*yDmx}3q3v&x5HmbyMop?KSj@~y%8sNCkebXj5h5kxND!f6DH}v>z=ZG2pz_e zmxO_~(=9Kxy$-Jo?QR_3mca&n`;ONg;CdL@y*LSYUu4HoP(D38Yxglz;Pakz3lqnY zPLn|=jgz*F(y<^Mt-|&unm&xfJxkqF$`$WjY4Mg7Ii{27Qa0V<{b?Ns9dtJw-qJ!d z2i-<*`TWg(a_eS)m~O?p*WRL{9jhm+Ni0g(?n7VEQea#7! z)!`oo6wh5o)34#M^8)1yG09p>?bg(APTi|f>XTPiw`Wy7wbZRy6;CM(|8T;3ABR<( z{aE~+tHiNIbea8~VE9Io#7T?OIieqXw}YYRNwvpQk#Xs|n?V|;LCfn6tBq#^MoC7aHET5aAV%OyRCAPrDFPd?JE7^wW64&S3=CMZBb57-`o% zir=|UI6Ye-;R1dbhwx~rU#db#u_IN*V2pa#HNnE?Mb`1eG-1<;`$N}V#E`j&^c))v zAx3-}9fpYnH^L-3fiO`LCWjvg${PoAo3+?amne@i&7qB8H(29YseLF8comQpsa>0=@Vu8DztkJmv_z-dOa?B zReTsvD=S4PGHXfFnkbZGAc}GhBSn(Z)npKeen0lQIwTmyd_GYt{?N}QyDzdxoyIvd3_ za{_CCSAb>=tO1%aum)(xz#3pC0b5@a(*Of~0^D<0`G=%4Pn1Ke=(ljVRDm7_w%iVa zJ`hT(Z4@YhDuf|%;s*&a$=$86vqfYwYy!Zb&;(SkE!mI3KxbU$=;v|3I`@&Wr&Wx# zu_YNILJB}xM?Nr#{XP;nv&01R15UZnJdekf7K?%N2oGHF;%NyOiYD=a&U+NCtfZ#& z1Fe)Eei-Lcj~CH2Csx)8tfCIju`#$Vqv=<0fDf7CL|G)d`xBxMR~EARl!_PeTYf-A zD9RK_8&s6!aH3yo@P^LuhVvN5rvP5RkG!}iTSyuaL^+BrD4q`thG7{gmzm@e_r|$3=M>MQ znZv^*6k?6{&~g=M6ox6AsK5fa^Rz%F*>x|keOukE5pC=ubC?)F)al6ZfH!c;1Y@m8lZ+z?#_yt&X(+HgTUU}RT{&h-i*vpVF}6(t zrRyX`Rq`qQ2Ut0lW&-BGdt^SFI0CS{(xSS~w|HOlQWYub^Hgwi2v&Rl~USwu|*B9ktiZ)d?({L zI=CYLm{DIxlg(!UWM>i3@shD@p>=6i={-!&dIqCza4yD0!%Dd;92Kt+i+6}ve?-TK zh5SUG?pTg#znrQwm=@snxKMS#z@X~T2U~~!erz4D0^H&%!0n>oTlD9ZKPN!V%(mAU9BhrCL3(~;|N2Rk!BhqxjljYpsVBPS3*aMMF5`J%JcLq9*d*KHl>C)H+ zh3A7H?9ppv3QhGDs*r14WBWoR-JX>}FJ8Q8`}V8$s1;iuHo47dp0{r_FZoI6Ma{Ez z^Geu@qvjfV{aA;bU2a$o?gZVqA0*AS-81E&H_Q-gawdDWyxPv_f|j| zC9qvGq(9Eu=#pZM2b)`9V{v^oSm!-GZR^2$FHB`54XPJjr461j3BB)yiO#rKMm-zT z%4J5+A`W|DGS)#})Oc^Xm8qn{IFz}T?wZe0C*ks1DTF>_bLY7E?;m3_NJKR9+Z-P#V?4Z|A@jd|leWe&qqFCGS zWNDtaM=@h{5cwv^IH_h~z&-Zqq`b`S_~|74FaBjaWkGwwga~?K`W6o2ZPxoeRJox= z2`?nz#)~x0`7<=lTB;@|tR+x|kL1iK9XzjKsDKVmFKn|>9CYCg#}Hd!`7Dq!hLf^EDr z1j%MHjUNX(YNobSNfT}qud?T(q_Pi_Op0G+i50VJmk25{ps7_H`~b)$ zg5Td!P6oGv9Ts7S2Y0^QYwXEw-zc%sf@3*MxbX~u{T_qm`Hk5sZ{n*baDvGf0ScaS zUINra^#uu26X1EW!_5_D#H_=E*>Qi4k#jPkYElX-g0|5q1YO37dNwF`LWN#n5~vSbkx7Ikf!Lx776gf%qSP3L z3)0w>$P*Gu#fo_eqg@heSn!2#41mo7m?LnEk=GTtVdFJ!+zCP4`1ai}-68;9F&X0( z3}JFI)3LLUot*KrE%U#ju!%MPgkOfCtz`@?tx}|rXYm8ZD5<>r{A%DtR^(s@#wf5G z5Z$ruqa~N8e~t$WTva3zV*u>nS^~-h!=J%N769>Q@irsCTMUEivHzgq1k;Hn!>AZd zE^rUA*O0t`aZ99TGBN>9CET9E&jZLnteSsDGb?(slOq-;xXV1xR9)nr0;uRGar%qw zL5F1K$e0>Qp$CX% z^N(Rl#1hgF0tra-R$awQlAo|~DlU9Hp`=z=D*hY=1b0pdCIZ?6%mDyFPK(bgYv2{dU7pvgLYk9$La&PjPmw!NxWxBsE6;d%LZ2oF!~c^J3ud3ajgYnX>W zQ>%PB|E_4k><7Up)fIMu9QS?7&CilB>FhTc`ClO3CjnWwO!QS83?tq;14PD;7c__o zgdjJ905RAccfAfi`4YIs&&H4~Bn)-izSPw*J`?K%ee<^UF5a@*Ub3m%sAL$O{G%Bi zkN5HR9qAhsvXB+$c;lc4E(U3A6FbJ!K4Jv&)6ADy!J(Rj|^TAuOo!9h?NTuX8FVBIXR9zT9M0c z?yKIe5uy3~gAbSUg7F=FIG2BScUR;PUi?O;ZPSq0xj>^&=2o>!ZhAihX7Y?ao6iby zMzkyi-B+3$uuLw}=zw1-%&uX#pRnJI)6SN6-gbn>09*&|97O#jV1#-gwDV1_#4)d) z8bqY1Pn|Ce^kC$5+Vt7;(5KU;172Vcf6rCk41AcKkzqPqk4#KVNtd}c`K|NLr>yhH z4EI@`?~lB8-Uao85-At}NC4arKk~z8Ni}6JuRWr6QHH@Hq24#tmeg=OvHh}I9Uc?@ z2#?FnCoinxrF9*UfFJrAf)@AJ0SjvR9enaIN^|jco5^%VUR}2hu|%pvGNhnN-N#oY z=3U*7aG|Uw4mS}F6S~vHLAJ!5L6V@7NugQC_f5i%pi}16he@fF@IPq6&?>1A4eN+? z2(QbQP&l-jLpYHO=IgwRf?X)Z(hru-Eln)l`yX0mP;CGJ diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/utils/html.pyc b/deluge/ui/webui/webui_plugin/lib/newforms/utils/html.pyc deleted file mode 100644 index e7a8e37047e9a10a996ed8bd950c13185a8bcc28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 619 zcmb7B!A{#i5S=v;XsSw)`U5LK;6ufwCmMvPK!QWd0XS8b(#`IWEOOSyGj4gWFT+{5xQa-K-gXMq6lOVGfJ5Cb8D>wHa<`vMm? zKY!SkV;MOjS!yG@af$Y}YV4$}#;&w2;epqV8frY1px*)v@F-~DIfHrthBMXA2A%+t z0V5)=>13HHCCcSpU-1nHy%pbtu(jeJAoP`A=O1;_cY!P9wn=1pigptJV6xrf^T<={ zW;HhCQlKyPzO7h3LxlCNK_-jIR{?`839v^Sieeac`iRz5m%Nb23prWJZR%-OTQ2hZ z3;WL3!(pyQ?h8bUiP$BNog6zt;wGjEcmJ3h-ju2a>(Y75bqiMSlxJlwCv-r&!p^=G zb#qtL4?L|>k#H_|Frz{*hYGDfkd&^3w0$r82XrNeMF0Q* From 03c9806b40f070396676342429a33de60d43ee29 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 15 Jan 2008 21:04:49 +0000 Subject: [PATCH 0335/1009] config pages --- deluge/ui/webui/webui_plugin/config.py | 47 +++++++++- .../webui/webui_plugin/config_tabs_deluge.py | 86 ++++++++++++++++++- .../webui/webui_plugin/config_tabs_webui.py | 32 +++---- deluge/ui/webui/webui_plugin/render.py | 3 +- .../templates/advanced/part_stats.html | 11 ++- .../templates/advanced/static/advanced.css | 20 ++++- .../templates/deluge/part_button.html | 6 +- .../templates/deluge/part_stats.html | 7 +- .../ui/webui/webui_plugin/tests/test_all.py | 1 + 9 files changed, 178 insertions(+), 35 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index 5cd403ba9..cb02efb62 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -37,6 +37,7 @@ import lib.webpy022 as web from webserver_common import ws from render import render from lib.webpy022.http import seeother +import sys groups = [] blocks = forms.utils.datastructures.SortedDict() @@ -85,8 +86,48 @@ class CfgForm(Form): "config base for deluge-cfg" def initial_data(self): return ws.proxy.get_config() - def save(data): - ws.proxy.set_config(data) + def save(self, data): + ws.proxy.set_config(dict(data)) + + +#convenience Fields. + +class _IntInput(forms.TextInput): + """ + because deluge-floats are edited as ints. + """ + def render(self, name, value, attrs=None): + try: + value = int(float(value)) + except: + pass + return forms.TextInput.render(self, name, value, attrs) + +class CheckBox(forms.BooleanField): + "Non Required ChoiceField" + def __init__(self,label, **kwargs): + forms.BooleanField.__init__(self,label=label,required=False,**kwargs) + +class IntCombo(forms.ChoiceField): + """ + choices are the display-values + returns int for the chosen display-value. + """ + def __init__(self, label, choices, **kwargs): + forms.ChoiceField.__init__(self, label=label, choices=enumerate(choices) + , **kwargs) + + def clean(self, value): + return int(forms.ChoiceField.clean(self, value)) + +class DelugeInt(forms.IntegerField): + def __init__(self, label , **kwargs): + forms.IntegerField.__init__(self, label=label, min_value=-1, + max_value=sys.maxint, widget=_IntInput, **kwargs) + +class DelugeFloat(DelugeInt): + def clean(self, value): + return int(DelugeInt.clean(self, value)) class config_page: @@ -129,7 +170,7 @@ class config_page: ws.log.debug(e.message) return self.render(form , name, error = e.message) else: - return self.render(form , name, _('Please correct errors and try again')) + return self.render(form , name, error= _('Please correct the errors above and try again')) def render(self, f , name , message = '' , error=''): return render.config(groups, blocks, f, name , message , error) diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index bf961a2de..532d90f63 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -36,11 +36,89 @@ import config import utils -class BandWidth(config.CfgForm): - title = _("Bandwidth") - up = forms.IntegerField(label = "TODO") -config.register_block('deluge','bandwidth',BandWidth) +class ServerFolderField(forms.CharField): + pass +class NetworkPorts(config.CfgForm ): + title = _("Ports") + info = _("Restart daemon after changing these values.") + _port_from = forms.IntegerField(_("From")) + _port_to = forms.IntegerField(_("To")) + random_port = config.CheckBox(_("Random")) + + def initial_data(self): + data = config.CfgForm.initial_data(self) + data['_port_from'] , data['_port_to'] = data['listen_ports'] + return data + + def save(self,data): + data['listen_ports'] = [data['_port_from'] , data['_port_to'] ] + if (data['_port_to'] < data['_port_from']): + raise ValidationError('"Port from" must be greater than "Port to"') + config.CfgForm.save() + +config.register_block('network','ports', NetworkPorts) + +class NetworkExtra(config.CfgForm ): + title = _("Extra's") + dht = config.CheckBox(_("Mainline DHT")) + upnp = config.CheckBox(_("UpNP")) + natpmp = config.CheckBox(_("NAT-PMP")) + utpex = config.CheckBox(_("Peer-Exchange")) + lsd = config.CheckBox(_("LSD")) + +config.register_block('network','extra', NetworkExtra) + +class NetworkEnc(config.CfgForm ): + title = _("Encryption") + + _enc_choices = [_("Forced"),_("Enabled"),_("Disabled")] + _level_choices = [_("Handshake"), _("Full") , _("Either")] + + enc_in_policy = config.IntCombo(_("Inbound"), _enc_choices) + enc_out_policy = config.IntCombo(_("Outbound"), _enc_choices) + enc_level = config.IntCombo(_("Level"), _level_choices) + enc_prefer_rc4 = config.CheckBox("Prefer to encrypt entire stream") + +config.register_block('network','encryption', NetworkEnc) + + +class BandwithGlobal(config.CfgForm): + title = _("Global") + info = _("-1 = Unlimited") + max_connections_global = config.DelugeInt(_("Maximum Connections")) + max_download_speed = config.DelugeFloat(_("Maximum Download Speed (Kib/s)")) + max_upload_speed = config.DelugeFloat(_("Maximum Upload Speed (Kib/s)")) + max_upload_slots_global = config.DelugeInt(_("Maximum Upload Slots")) + +config.register_block('bandwidth','global', BandwithGlobal) + +class BandwithTorrent(config.CfgForm): + title = _("Per Torrent") + info = _("-1 = Unlimited") + max_connections_per_torrent = config.DelugeInt(_("Maximum Connections")) + max_upload_slots_per_torrent = config.DelugeInt(_("Maximum Upload Slots")) + +config.register_block('bandwidth','torrent', BandwithTorrent) + + +class Download(config.CfgForm): + title = _("Download") + download_location = ServerFolderField(_("Store all downoads in")) + torrentfiles_location = ServerFolderField(_("Save .torrent files to")) + autoadd_location = ServerFolderField(_("Auto Add folder") , required=False) + compact_allocation = config.CheckBox(_('Use Compact Allocation')) + prioritize_first_last_pieces = config.CheckBox(_('Prioritize first and last pieces')) + +config.register_block('deluge','download', Download) + +class Daemon(config.CfgForm): + title = _("Daemon") + daemon_port = forms.IntegerField(_("Port")) + allow_remote = config.CheckBox(_("Allow Remote Connections")) + +config.register_block('deluge','daemon', Daemon) + diff --git a/deluge/ui/webui/webui_plugin/config_tabs_webui.py b/deluge/ui/webui/webui_plugin/config_tabs_webui.py index dbaaba10a..eb0d7b20e 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_webui.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_webui.py @@ -41,42 +41,40 @@ from webserver_common import ws class Template(config.WebCfgForm): title = _("Template") - template = forms.ChoiceField( label=_("Template"), - choices = [(t,t) for t in ws.get_templates()]) + _templates = [(t,t) for t in ws.get_templates()] + _button_choices = [_('Text and image'), _('Image Only'), _('Text Only')] - button_style = forms.ChoiceField( label=_("Button style"), - choices=[ - (0,_('Text and image')), - (1, _('Image Only')), - (2, _('Text Only'))]) - - cache_templates = forms.BooleanField(label = _("Cache templates"), - required=False) + template = forms.ChoiceField( label=_("Template"), choices = _templates) + button_style = config.IntCombo(_("Button style"),_button_choices) + cache_templates = config.CheckBox(_("Cache templates")) def post_save(self): render.apply_cfg() class Server(config.WebCfgForm): - info = _("Restart webui after changing these values.") title = _("Server") port = forms.IntegerField(label = _("Port"),min_value=80) - use_https = forms.BooleanField(label = _("Use https") , required=False) + use_https = config.CheckBox(_("Use https")) + + def post_save(self): + pass + #raise forms.ValidationError(_("Manually restart server to apply these changes.")) class Password(config.Form): title = _("Password") old_pwd = forms.CharField(widget = forms.PasswordInput - ,label = _("Current Password"), required=False) + ,label = _("Current Password")) new1 = forms.CharField(widget = forms.PasswordInput - ,label = _("New Password"), required=False) + ,label = _("New Password")) new2 = forms.CharField(widget = forms.PasswordInput - ,label = _("New Password (Confirm)"), required=False) + ,label = _("New Password (Confirm)")) def initial_data(self): - return {} + return None def save(self,data): if not ws.check_pwd(data.old_pwd): @@ -87,8 +85,10 @@ class Password(config.Form): ws.update_pwd(data.new1) ws.save_config() + def post_save(self): utils.end_session() + #raise forms.ValidationError(_("Password changed,please login again")) config.register_block('webui','template', Template) config.register_block('webui','server',Server) diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index b42ff3571..4e4ae502f 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -133,7 +133,8 @@ template.Template.globals.update({ 'rev': 'rev.%s' % (REVNO, ), 'version': VERSION, 'getcookie':getcookie, - 'get': lambda (var): getattr(web.input(**{var:None}), var) # unreadable :-( + 'get': lambda (var): getattr(web.input(**{var:None}), var), # unreadable :-( + 'env':ws.env }) #/template-defs diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html index 6ce594919..2bccc8bfe 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html @@ -12,16 +12,21 @@ $else: $_('Off')   $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') $#end +
      +$if env == '0.6': + $:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png') - $_('Connections') : $stats.num_connections ($stats.max_num_connections) +$_('Connections') : $stats.num_connections ($stats.max_num_connections) + +$_('Down Speed') : $stats.download_rate ($stats.max_download) + +$_('Up Speed') : $stats.upload_rate ($stats.max_upload) - $_('Down Speed') : $stats.download_rate ($stats.max_download) - $_('Up Speed') : $stats.upload_rate ($stats.max_upload) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index 2b801a1b8..5fb590ba6 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -165,14 +165,30 @@ body.inner { border:0; position:relative; top:0px; - height:20px; + height:15px; background-color:#ddd; - color:#000; + color:#00F; } + #refresh_panel button:hover { text-decoration: underline; } +#stats_panel button { + background-color:#304663; + color:#FFFFFF; + border:0; + position:relative; + top:0px; + height:20px; + background-color:#ddd; + color:#00F; +} +#stats_panel button:hover { + text-decoration: underline; +} + + #category_panel { margin-bottom:0; padding-bottom:0; diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html b/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html index 8c420560f..2dc23e65a 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/part_button.html @@ -2,14 +2,14 @@ $def with (method, url, title, image='')
      -$if (get_config('button_style') == 0): +$if (int(get_config('button_style')) == 0): -$if (get_config('button_style') == 1): +$if (int(get_config('button_style')) == 1): $if image: $else: @@ -17,7 +17,7 @@ $if (get_config('button_style') == 1): $title -$if (get_config('button_style') == 2): +$if (int(get_config('button_style')) == 2): diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html b/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html index 342b8049f..305a9ea25 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html @@ -26,9 +26,10 @@ $#end - - ($_('About')) - +$if env == '0.6': + $:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png') + +
      diff --git a/deluge/ui/webui/webui_plugin/tests/test_all.py b/deluge/ui/webui/webui_plugin/tests/test_all.py index 4c163c344..d8298c6ba 100644 --- a/deluge/ui/webui/webui_plugin/tests/test_all.py +++ b/deluge/ui/webui/webui_plugin/tests/test_all.py @@ -362,6 +362,7 @@ if True: print key,cfg[key] + if False: suiteFew = unittest.TestSuite() From 350cad9f3f999ac0679e38b77cd10731844de6bd Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 15 Jan 2008 23:44:30 +0000 Subject: [PATCH 0336/1009] config plugins --- deluge/ui/webui/webui_plugin/config.py | 39 +++++++++++++++++-- .../webui/webui_plugin/config_tabs_deluge.py | 14 +++++-- .../templates/advanced/static/advanced.css | 3 ++ .../ui/webui/webui_plugin/tests/test_all.py | 28 +++++++++---- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index cb02efb62..4bc80e171 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -81,6 +81,20 @@ class WebCfgForm(Form): def post_save(self): pass +class CookieCfgForm(Form): + "config base for webui" + def initial_data(self): + return ws.config + + def save(self, data): + ws.config.update(data) + ws.save_config() + self.post_save() + + def post_save(self): + pass + + class CfgForm(Form): "config base for deluge-cfg" @@ -90,8 +104,7 @@ class CfgForm(Form): ws.proxy.set_config(dict(data)) -#convenience Fields. - +#convenience Input Fields. class _IntInput(forms.TextInput): """ because deluge-floats are edited as ints. @@ -99,6 +112,8 @@ class _IntInput(forms.TextInput): def render(self, name, value, attrs=None): try: value = int(float(value)) + if value == -1: + value = _("Unlimited") except: pass return forms.TextInput.render(self, name, value, attrs) @@ -125,10 +140,23 @@ class DelugeInt(forms.IntegerField): forms.IntegerField.__init__(self, label=label, min_value=-1, max_value=sys.maxint, widget=_IntInput, **kwargs) + def clean(self, value): + if str(value).lower() == _('Unlimited').lower(): + value = -1 + return int(forms.IntegerField.clean(self, value)) + class DelugeFloat(DelugeInt): def clean(self, value): return int(DelugeInt.clean(self, value)) +class MultipleChoice(forms.MultipleChoiceField): + #temp/test + def __init__(self, label, choices, **kwargs): + forms.MultipleChoiceField.__init__(self, label=label, choices=choices, + widget=forms.CheckboxSelectMultiple, required=False) + + +#/fields class config_page: """ @@ -159,6 +187,11 @@ class config_page: vars = web.input() for field in fields: form_data[field] = vars.get(field) + #DIRTY HACK: (for multiple-select) + if isinstance(form_class.base_fields[field], + forms.MultipleChoiceField): + form_data[field] = web.input(**{field:[]})[field] + #/DIRTY HACK form = form_class(form_data) if form.is_valid(): @@ -170,7 +203,7 @@ class config_page: ws.log.debug(e.message) return self.render(form , name, error = e.message) else: - return self.render(form , name, error= _('Please correct the errors above and try again')) + return self.render(form , name, error= _('Correct the errors above and try again')) def render(self, f , name , message = '' , error=''): return render.config(groups, blocks, f, name , message , error) diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index 532d90f63..159a72fbb 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -34,7 +34,7 @@ import lib.newforms as forms import config import utils - +from webserver_common import ws class ServerFolderField(forms.CharField): @@ -54,10 +54,12 @@ class NetworkPorts(config.CfgForm ): return data def save(self,data): - data['listen_ports'] = [data['_port_from'] , data['_port_to'] ] if (data['_port_to'] < data['_port_from']): raise ValidationError('"Port from" must be greater than "Port to"') - config.CfgForm.save() + data['listen_ports'] = [data['_port_from'] , data['_port_to'] ] + del(data['_port_from']) + del(data['_port_to']) + config.CfgForm.save(self, data) config.register_block('network','ports', NetworkPorts) @@ -121,4 +123,10 @@ class Daemon(config.CfgForm): config.register_block('deluge','daemon', Daemon) +class Plugins(config.CfgForm): + title = _("Enabled Plugins") + _choices = [(p,p) for p in ws.proxy.get_available_plugins()] + enabled_plugins = config.MultipleChoice(_(""), _choices) + +config.register_block('deluge','plugins', Plugins) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index 5fb590ba6..015a9e57c 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -289,6 +289,9 @@ form { /*all forms!*/ #config_panel { height:60%; + float:left; + width:500px; + margin-left:20px; } #config_panel th { font-size: 12px; diff --git a/deluge/ui/webui/webui_plugin/tests/test_all.py b/deluge/ui/webui/webui_plugin/tests/test_all.py index d8298c6ba..68b073e79 100644 --- a/deluge/ui/webui/webui_plugin/tests/test_all.py +++ b/deluge/ui/webui/webui_plugin/tests/test_all.py @@ -222,9 +222,10 @@ class TestIntegration(TestWebUiBase): self.assertEqual(len(self.torrent_ids), 3) else: - #test correctness of existing-list - for url in self.urls: - self.assert_500('/torrent/add',{'url':url,'torrent':None}) + if ws.env <> '0.6': + #test correctness of existing-list + for url in self.urls: + self.assert_500('/torrent/add',{'url':url,'torrent':None}) def testPauseResume(self): #pause all @@ -353,13 +354,24 @@ class TestIntegration(TestWebUiBase): ,'/torrent/info/' + torrent_id, post = 1) """ + def testConfig(self): + #0.6 only + if ws.env <> '0.6': + return + # + import WebUi.config_tabs_webui #auto registers + import WebUi.config_tabs_deluge #auto registers + import WebUi.config as config + + for name in config.blocks: + self.assert_exists("/config/%s" % name) + + #todo->post page with current values. + + + -# -if True: - cfg = ws.proxy.get_config() - for key in sorted(cfg.keys()): - print key,cfg[key] From 966b2fa4590f473ee5cdf23121d13e80a9ac205d Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Wed, 16 Jan 2008 00:00:21 +0000 Subject: [PATCH 0337/1009] config plugins:raise todo --- deluge/ui/webui/webui_plugin/config_tabs_deluge.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index 159a72fbb..469dfe663 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -123,10 +123,17 @@ class Daemon(config.CfgForm): config.register_block('deluge','daemon', Daemon) -class Plugins(config.CfgForm): +class Plugins(config.Form): title = _("Enabled Plugins") - _choices = [(p,p) for p in ws.proxy.get_available_plugins()] enabled_plugins = config.MultipleChoice(_(""), _choices) + def initial_data(self): + return {'enabled_plugins':ws.proxy.get_enabled_plugins()} + + def save(self, value): + raise NotImplementedError("TODO") + + + config.register_block('deluge','plugins', Plugins) From 1e5af9bd8715404284c239cd456dd7727ee646da Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 16 Jan 2008 06:40:58 +0000 Subject: [PATCH 0338/1009] Use threading in ConnectionManager to test online status of hosts. --- deluge/ui/gtkui/connectionmanager.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 25cee17ac..e20b7d4ad 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -37,6 +37,7 @@ import gobject import socket import os import time +import threading import deluge.component as component import deluge.xmlrpclib as xmlrpclib @@ -80,6 +81,9 @@ class ConnectionManager(component.Component): self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, int) + # Holds the online status of hosts + self.online_status = {} + # Fill in hosts from config file for host in self.config["hosts"]: row = self.liststore.append() @@ -174,7 +178,11 @@ class ConnectionManager(component.Component): def update_row(model=None, path=None, row=None, columns=None): uri = model.get_value(row, HOSTLIST_COL_URI) uri = "http://" + uri - online = self.test_online_status(uri) + threading.Thread(target=self.test_online_status, args=(uri,)).start() + try: + online = self.online_status[uri] + except: + online = False if online: image = gtk.STOCK_YES @@ -301,7 +309,7 @@ class ConnectionManager(component.Component): online = False del host - + self.online_status[uri] = online return online ## Callbacks From 89af88e370a77083ce3a5a40658a51864a2df4cd Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 16 Jan 2008 06:42:43 +0000 Subject: [PATCH 0339/1009] Refactor TorrentView to have async returns populate a local status dictionary. We now update every second against the local status dictionary instead of updating on every async return. --- deluge/ui/gtkui/torrentview.py | 79 ++++++++++++++++------------------ 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 7bd7bb0cb..1a9845fba 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -110,9 +110,8 @@ class TorrentView(listview.ListView, component.Component): # Try to load the state file if available self.load_state("torrentview.state") - self.status_signal_received = True - self.status_signal_sent_time = 0 - self.previous_batched_status = {} + # This is where status updates are put + self.status = {} # Register the columns menu with the listview so it gets updated # accordingly. @@ -204,36 +203,8 @@ class TorrentView(listview.ListView, component.Component): """Sets filters for the torrentview..""" self.filter = (field, condition) self.update() - - def update(self, columns=None): - """Update the view. If columns is not None, it will attempt to only - update those columns selected. - """ - def foreachrow(model, path, row, data): - filter_column = self.columns["filter"].column_indices[0] - # Create a function to create a new liststore with only the - # desired rows based on the filter. - field, condition = data - if field == None and condition == None: - model.set_value(row, filter_column, True) - return - - torrent_id = model.get_value(row, 0) - value = client.get_torrent_status(torrent_id, [field])[field] - # Condition is True, so lets show this row, if not we hide it - if value == condition: - model.set_value(row, filter_column, True) - else: - model.set_value(row, filter_column, False) - - self.liststore.foreach(foreachrow, self.filter) - - # We will only send another status request if we have received the - # previous. This is to prevent things from going out of sync. - if not self.status_signal_received: - if time.time() - self.status_signal_sent_time < 2: - return - + + def send_status_request(self, columns=None): # Store the 'status_fields' we need to send to core status_keys = [] # Store the actual columns we will be updating @@ -280,14 +251,33 @@ class TorrentView(listview.ListView, component.Component): # Request the statuses for all these torrent_ids, this is async so we # will deal with the return in a signal callback. - self.status_signal_received = False client.get_torrents_status(torrent_ids, status_keys) - self.status_signal_sent_time = time.time() - - def on_torrent_status_signal(self, status): - """Callback function for get_torrents_status(). 'status' should be a - dictionary of {torrent_id: {key, value}}.""" - status = pickle.loads(status) + + def update(self, columns=None): + """Update the view. If columns is not None, it will attempt to only + update those columns selected. + """ + def foreachrow(model, path, row, data): + filter_column = self.columns["filter"].column_indices[0] + # Create a function to create a new liststore with only the + # desired rows based on the filter. + field, condition = data + if field == None and condition == None: + model.set_value(row, filter_column, True) + return + + torrent_id = model.get_value(row, 0) + value = client.get_torrent_status(torrent_id, [field])[field] + # Condition is True, so lets show this row, if not we hide it + if value == condition: + model.set_value(row, filter_column, True) + else: + model.set_value(row, filter_column, False) + + self.liststore.foreach(foreachrow, self.filter) + + # Update the torrent view model with data we've received + status = self.status row = self.liststore.get_iter_first() while row != None: torrent_id = self.liststore.get_value( @@ -330,7 +320,14 @@ class TorrentView(listview.ListView, component.Component): except: pass row = self.liststore.iter_next(row) - self.status_signal_received = True + + # Send a request for a status update + self.send_status_request(columns) + + def on_torrent_status_signal(self, status): + """Callback function for get_torrents_status(). 'status' should be a + dictionary of {torrent_id: {key, value}}.""" + self.status = pickle.loads(status) def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" From b73dd60e6bceb30f6e36bd38ef6a3a094fb38b3f Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Thu, 17 Jan 2008 20:09:05 +0000 Subject: [PATCH 0340/1009] minor css fix+pwd-cfg --- deluge/ui/webui/webui_plugin/config.py | 49 +++++++++++++------ .../webui/webui_plugin/config_tabs_deluge.py | 23 ++++----- .../webui/webui_plugin/config_tabs_webui.py | 29 +++++------ .../templates/advanced/static/advanced.css | 8 +-- 4 files changed, 61 insertions(+), 48 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index 4bc80e171..d8b6f5ae1 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -38,6 +38,7 @@ from webserver_common import ws from render import render from lib.webpy022.http import seeother import sys +import os groups = [] blocks = forms.utils.datastructures.SortedDict() @@ -52,11 +53,13 @@ class Form(forms.Form): def initial_data(self): "override in subclass" - raise NotImplementedError() + return None def start_save(self): "called by config_page" - self.save(web.Storage(self.clean_data)) + data = web.Storage(self.clean_data) + self.validate(data) + self.save(data) self.post_save() def save(self, vars): @@ -64,7 +67,9 @@ class Form(forms.Form): raise NotImplementedError() def post_save(self): - "override in subclass" + pass + + def validate(self, data): pass @@ -76,10 +81,6 @@ class WebCfgForm(Form): def save(self, data): ws.config.update(data) ws.save_config() - self.post_save() - - def post_save(self): - pass class CookieCfgForm(Form): "config base for webui" @@ -89,11 +90,6 @@ class CookieCfgForm(Form): def save(self, data): ws.config.update(data) ws.save_config() - self.post_save() - - def post_save(self): - pass - class CfgForm(Form): @@ -129,8 +125,8 @@ class IntCombo(forms.ChoiceField): returns int for the chosen display-value. """ def __init__(self, label, choices, **kwargs): - forms.ChoiceField.__init__(self, label=label, choices=enumerate(choices) - , **kwargs) + forms.ChoiceField.__init__(self, label=label, + choices=enumerate(choices), **kwargs) def clean(self, value): return int(forms.ChoiceField.clean(self, value)) @@ -155,6 +151,23 @@ class MultipleChoice(forms.MultipleChoiceField): forms.MultipleChoiceField.__init__(self, label=label, choices=choices, widget=forms.CheckboxSelectMultiple, required=False) +class ServerFolder(forms.CharField): + def __init__(self, label, **kwargs): + forms.CharField.__init__(self, label=label,**kwargs) + + def clean(self, value): + value = value.rstrip('/').rstrip('\\') + self.validate(value) + return forms.CharField.clean(self, value) + + def validate(self, value): + if (value and not os.path.isdir(value)): + raise forms.ValidationError(_("This folder does not exist.")) + +class Password(forms.CharField): + def __init__(self, label, **kwargs): + forms.CharField.__init__(self, label=label, widget=forms.PasswordInput, + **kwargs) #/fields @@ -203,7 +216,8 @@ class config_page: ws.log.debug(e.message) return self.render(form , name, error = e.message) else: - return self.render(form , name, error= _('Correct the errors above and try again')) + return self.render(form , name, + error= _('Correct the errors above and try again')) def render(self, f , name , message = '' , error=''): return render.config(groups, blocks, f, name , message , error) @@ -214,5 +228,10 @@ def register_block(group, name, form): form.group = group blocks[name] = form +def unregister_block(name): + del blocks[name] + + + diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index 469dfe663..281fda2ea 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -37,10 +37,6 @@ import utils from webserver_common import ws -class ServerFolderField(forms.CharField): - pass - - class NetworkPorts(config.CfgForm ): title = _("Ports") info = _("Restart daemon after changing these values.") @@ -54,13 +50,15 @@ class NetworkPorts(config.CfgForm ): return data def save(self,data): - if (data['_port_to'] < data['_port_from']): - raise ValidationError('"Port from" must be greater than "Port to"') data['listen_ports'] = [data['_port_from'] , data['_port_to'] ] del(data['_port_from']) del(data['_port_to']) config.CfgForm.save(self, data) + def validate(self, data): + if (data['_port_to'] < data['_port_from']): + raise ValidationError('"Port from" must be greater than "Port to"') + config.register_block('network','ports', NetworkPorts) class NetworkExtra(config.CfgForm ): @@ -108,11 +106,12 @@ config.register_block('bandwidth','torrent', BandwithTorrent) class Download(config.CfgForm): title = _("Download") - download_location = ServerFolderField(_("Store all downoads in")) - torrentfiles_location = ServerFolderField(_("Save .torrent files to")) - autoadd_location = ServerFolderField(_("Auto Add folder") , required=False) + download_location = config.ServerFolder(_("Store all downoads in")) + torrentfiles_location = config.ServerFolder(_("Save .torrent files to")) + autoadd_location = config.ServerFolder(_("Auto Add folder"), required=False) compact_allocation = config.CheckBox(_('Use Compact Allocation')) - prioritize_first_last_pieces = config.CheckBox(_('Prioritize first and last pieces')) + prioritize_first_last_pieces = config.CheckBox( + _('Prioritize first and last pieces')) config.register_block('deluge','download', Download) @@ -132,8 +131,6 @@ class Plugins(config.Form): return {'enabled_plugins':ws.proxy.get_enabled_plugins()} def save(self, value): - raise NotImplementedError("TODO") - - + raise forms.ValidationError("SAVE:TODO") config.register_block('deluge','plugins', Plugins) diff --git a/deluge/ui/webui/webui_plugin/config_tabs_webui.py b/deluge/ui/webui/webui_plugin/config_tabs_webui.py index eb0d7b20e..74845a947 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_webui.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_webui.py @@ -34,7 +34,6 @@ import lib.newforms as forms import config import utils -from render import render from webserver_common import ws @@ -49,6 +48,7 @@ class Template(config.WebCfgForm): cache_templates = config.CheckBox(_("Cache templates")) def post_save(self): + from render import render render.apply_cfg() @@ -60,31 +60,26 @@ class Server(config.WebCfgForm): def post_save(self): pass - #raise forms.ValidationError(_("Manually restart server to apply these changes.")) + #raise forms.ValidationError( + # _("Manually restart server to apply these changes.")) class Password(config.Form): title = _("Password") - old_pwd = forms.CharField(widget = forms.PasswordInput - ,label = _("Current Password")) - new1 = forms.CharField(widget = forms.PasswordInput - ,label = _("New Password")) - - new2 = forms.CharField(widget = forms.PasswordInput - ,label = _("New Password (Confirm)")) - - def initial_data(self): - return None + old_pwd = config.Password(_("Current Password")) + new1 = config.Password(_("New Password")) + new2 = config.Password(_("New Password (Confirm)")) def save(self,data): - if not ws.check_pwd(data.old_pwd): - raise forms.ValidationError(_("Old password is invalid")) - if data.new1 <> data.new2: - raise forms.ValidationError(_("New Password is not equal to New Password(confirm)")) - ws.update_pwd(data.new1) ws.save_config() + def validate(self, data): + if not ws.check_pwd(data.old_pwd): + raise forms.ValidationError(_("Old password is invalid")) + if data.new1 <> data.new2: + raise forms.ValidationError( + _("New Password is not equal to New Password(confirm)")) def post_save(self): utils.end_session() diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index 015a9e57c..a4381fd13 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -164,7 +164,7 @@ body.inner { color:#FFFFFF; border:0; position:relative; - top:0px; + top:-2px; height:15px; background-color:#ddd; color:#00F; @@ -179,8 +179,8 @@ body.inner { color:#FFFFFF; border:0; position:relative; - top:0px; - height:20px; + top:-2px; + height:15px; background-color:#ddd; color:#00F; } @@ -269,6 +269,7 @@ form { /*all forms!*/ } #config_chooser { + margin-left:20px; float: left; width:150px; text-align:left; @@ -279,6 +280,7 @@ form { /*all forms!*/ list-style-type: none; } + #config_chooser li:hover { background-color:#68a; } From 7e03f82d730ed1e7d7d7fc42260bd901ae2754bd Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 19 Jan 2008 16:47:46 +0000 Subject: [PATCH 0341/1009] Import proper xmlrpclib. --- deluge/core/signalmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index d47e01fad..c82d8ab5f 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import xmlrpclib +import deluge.xmlrpclib as xmlrpclib import socket import gobject From 29c77e1a048847b761defb437acf06a22f747793 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 19 Jan 2008 16:48:45 +0000 Subject: [PATCH 0342/1009] Hack xmlrpclib to stop from dieing with long numbers. --- deluge/xmlrpclib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/xmlrpclib.py b/deluge/xmlrpclib.py index 9305e1018..5bd6171a4 100644 --- a/deluge/xmlrpclib.py +++ b/deluge/xmlrpclib.py @@ -657,8 +657,8 @@ class Marshaller: dispatch[bool] = dump_bool def dump_long(self, value, write): - if value > MAXINT or value < MININT: - raise OverflowError, "long int exceeds XML-RPC limits" + #if value > MAXINT or value < MININT: + # raise OverflowError, "long int exceeds XML-RPC limits" write("") write(str(int(value))) write("\n") From 1fa301cb697a4099a610c5c93dbe82239b3c21b2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 19 Jan 2008 22:33:30 +0000 Subject: [PATCH 0343/1009] Change client.py to use multicalls. This forces all client methods to be async. --- deluge/core/core.py | 23 +-- deluge/ui/client.py | 270 ++++++++++------------------ deluge/ui/gtkui/addtorrentdialog.py | 12 +- deluge/ui/gtkui/gtkui.py | 12 -- deluge/ui/gtkui/listview.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 14 +- deluge/ui/gtkui/preferences.py | 28 ++- deluge/ui/gtkui/signals.py | 14 +- deluge/ui/gtkui/statusbar.py | 59 +++++- deluge/ui/gtkui/systemtray.py | 51 +++++- deluge/ui/gtkui/toolbar.py | 93 +++++----- deluge/ui/gtkui/torrentdetails.py | 94 +++++----- deluge/ui/gtkui/torrentview.py | 73 +++++--- deluge/ui/signalreceiver.py | 8 +- 14 files changed, 388 insertions(+), 365 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 051548784..05dda81b7 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -35,7 +35,6 @@ import gettext import locale import pkg_resources import sys -import cPickle as pickle import shutil import os @@ -119,7 +118,9 @@ class Core( except: log.info("Daemon already running or port not available..") sys.exit(0) - + + self.register_multicall_functions() + # Register all export_* functions for func in dir(self): if func.startswith("export_"): @@ -350,14 +351,9 @@ class Core( leftover_fields = list(set(keys) - set(status.keys())) if len(leftover_fields) > 0: status.update(self.plugins.get_status(torrent_id, leftover_fields)) - return pickle.dumps(status) + return status def export_get_torrents_status(self, torrent_ids, keys): - """Returns dictionary of statuses for torrent_ids""" - # This is an async command, so we want to return right away - gobject.idle_add(self._get_torrents_status, torrent_ids, keys) - - def _get_torrents_status(self, torrent_ids, keys): status_dict = {}.fromkeys(torrent_ids) # Get the torrent status for each torrent_id @@ -374,9 +370,8 @@ class Core( status_dict[torrent_id] = status # Emit the torrent_status signal to the clients - self.torrent_status(pickle.dumps(status_dict)) - return False - + return status_dict + def export_get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager @@ -477,11 +472,7 @@ class Core( """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") self.signals.emit("torrent_all_resumed", torrent_id) - - def torrent_status(self, status): - """Emitted when the torrent statuses are ready to be sent""" - self.signals.emit("torrent_status", status) - + # Config set functions def _on_set_torrentfiles_location(self, key, value): try: diff --git a/deluge/ui/client.py b/deluge/ui/client.py index a6c2bc3a7..536e6dcd7 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -132,7 +132,42 @@ class CoreProxy(gobject.GObject): gobject.GObject.__init__(self) self._uri = None self._core = None + self._multi = None + self._callbacks = [] + gobject.timeout_add(10, self.do_multicall) + + def call(self, func, callback, *args): + if self._core is None or self._multi is None: + if self.get_core() is None: + return + _func = getattr(self._multi, func) + + if _func is not None: + if len(args) == 0: + _func() + else: + _func(*args) + self._callbacks.append(callback) + + + def do_multicall(self, block=False): + if len(self._callbacks) == 0: + return True + if self._multi is not None: + for i, ret in enumerate(self._multi()): + try: + if block == False: + gobject.idle_add(self._callbacks[i], ret) + else: + self._callbacks[i](ret) + except: + pass + + self._callbacks = [] + self._multi = xmlrpclib.MultiCall(self._core) + return True + def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) @@ -158,26 +193,18 @@ class CoreProxy(gobject.GObject): if self._core is None and self._uri is not None: log.debug("Creating ServerProxy..") self._core = xmlrpclib.ServerProxy(self._uri, allow_none=True) + self._multi = xmlrpclib.MultiCall(self._core) # Call any callbacks registered self.emit("new_core") return self._core - + _core = CoreProxy() def get_core(): """Get the core object and return it""" - return _core.get_core() + return _core -def get_core_plugin(plugin): - """Get the core plugin object and return it""" - log.debug("Getting core plugin %s from DBUS..", plugin) - bus = dbus.SessionBus() - proxy = bus.get_object("org.deluge_torrent.Deluge", - "/org/deluge_torrent/Plugin/" + plugin) - core = dbus.Interface(proxy, "org.deluge_torrent.Deluge." + plugin) - return core - def connect_on_new_core(callback): """Connect a callback whenever a new core is connected to.""" return _core.connect("new_core", callback) @@ -211,15 +238,27 @@ def connected(): if get_core_uri() != None: return True return False - + +def register_client(port): + get_core().call("register_client", None, port) + +def deregister_client(): + get_core().call("deregister_client", None) + def shutdown(): """Shutdown the core daemon""" try: - get_core().shutdown() + get_core().call("shutdown", None) + force_call(block=False) except: # Ignore everything set_core_uri(None) - + +def force_call(block=True): + """Forces the multicall batch to go now and not wait for the timer. This + call also blocks until all callbacks have been dealt with.""" + get_core().do_multicall(block=block) + def add_torrent_file(torrent_files, torrent_options=None): """Adds torrent files to the core Expects a list of torrent files @@ -252,216 +291,101 @@ def add_torrent_file(torrent_files, torrent_options=None): else: options = None - try: - result = get_core().add_torrent_file( - filename, str(), fdump, options) - except (AttributeError, socket.error): - set_core_uri(None) - result = False - - if result is False: - # The torrent was not added successfully. - log.warning("Torrent %s was not added successfully.", filename) + get_core().call("add_torrent_file", None, + filename, str(), fdump, options) def add_torrent_url(torrent_url, options=None): """Adds torrents to the core via url""" from deluge.common import is_url if is_url(torrent_url): - try: - result = get_core().add_torrent_url(torrent_url, str(), options) - except (AttributeError, socket.error): - set_core_uri(None) - result = False - - if result is False: - # The torrent url was not added successfully. - log.warning("Torrent %s was not added successfully.", torrent_url) + get_core().call("add_torrent_url", None, + torrent_url, str(), options) else: log.warning("Invalid URL %s", torrent_url) def remove_torrent(torrent_ids, remove_torrent=False, remove_data=False): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) - try: - for torrent_id in torrent_ids: - get_core().remove_torrent(torrent_id, remove_torrent, remove_data) - except (AttributeError, socket.error): - set_core_uri(None) + for torrent_id in torrent_ids: + get_core().call("remove_torrent", None, torrent_id, remove_torrent, remove_data) def pause_torrent(torrent_ids): """Pauses torrent_ids""" - try: - for torrent_id in torrent_ids: - get_core().pause_torrent(torrent_id) - except (AttributeError, socket.error): - set_core_uri(None) + for torrent_id in torrent_ids: + get_core().call("pause_torrent", None, torrent_id) def pause_all_torrents(): """Pauses all torrents""" - try: - get_core().pause_all_torrents() - except (AttributeError, socket.error): - set_core_uri(None) + get_core().call("pause_all_torrents", None) def resume_all_torrents(): """Resumes all torrents""" - try: - get_core().resume_all_torrents() - except (AttributeError, socket.error): - set_core_uri(None) + get_core().call("resume_all_torrents", None) def resume_torrent(torrent_ids): """Resume torrent_ids""" - try: - for torrent_id in torrent_ids: - get_core().resume_torrent(torrent_id) - except (AttributeError, socket.error): - set_core_uri(None) + for torrent_id in torrent_ids: + get_core().call("resume_torrent", None, torrent_id) def force_reannounce(torrent_ids): """Reannounce to trackers""" - try: - for torrent_id in torrent_ids: - get_core().force_reannounce(torrent_id) - except (AttributeError, socket.error): - set_core_uri(None) + for torrent_id in torrent_ids: + get_core().call("force_reannounce", None, torrent_id) -@cache_dict -def get_torrent_status(torrent_id, keys): +def get_torrent_status(callback, torrent_id, keys): """Builds the status dictionary and returns it""" - try: - status = get_core().get_torrent_status(torrent_id, keys) - except (AttributeError, socket.error): - set_core_uri(None) - return {} - - if status == None: - return {} - - return pickle.loads(status) + get_core().call("get_torrent_status", callback, torrent_id, keys) -def get_torrents_status(torrent_ids, keys): +def get_torrents_status(callback, torrent_ids, keys): """Builds a dictionary of torrent_ids status. Expects 2 lists. This is asynchronous so the return value will be sent as the signal 'torrent_status'""" - try: - get_core().get_torrents_status(torrent_ids, keys) - except (AttributeError, socket.error): - set_core_uri(None) + get_core().call("get_torrents_status", callback, torrent_ids, keys) - return None - -@cache -def get_session_state(): - try: - state = get_core().get_session_state() - except (AttributeError, socket.error): - set_core_uri(None) - state = [] - return state +def get_session_state(callback): + get_core().call("get_session_state", callback) -@cache -def get_config(): - try: - config = get_core().get_config() - except (AttributeError, socket.error): - set_core_uri(None) - config = {} - return config +def get_config(callback): + get_core().call("get_config", callback) -@cache -def get_config_value(key): - try: - config_value = get_core().get_config_value(key) - except (AttributeError, socket.error): - set_core_uri(None) - config_value = None - return config_value +def get_config_value(callback, key): + get_core().call("get_config_value", callback, key) def set_config(config): if config == {}: return - try: - get_core().set_config(config) - except (AttributeError, socket.error): - set_core_uri(None) -@cache -def get_listen_port(): - try: - port = get_core().get_listen_port() - except (AttributeError, socket.error): - set_core_uri(None) - port = 0 - return int(port) + get_core().call("set_config", None, config) -@cache -def get_available_plugins(): - try: - available = get_core().get_available_plugins() - except (AttributeError, socket.error): - set_core_uri(None) - available = [] - return available +def get_listen_port(callback): + get_core().call("get_listen_port", callback) -@cache -def get_enabled_plugins(): - try: - enabled = get_core().get_enabled_plugins() - except (AttributeError, socket.error): - set_core_uri(None) - enabled = [] - return enabled +def get_available_plugins(callback): + get_core().call("get_available_plugins", callback) -@cache -def get_download_rate(): - try: - rate = get_core().get_download_rate() - except (AttributeError, socket.error): - set_core_uri(None) - rate = -1 - return rate +def get_enabled_plugins(callback): + get_core().call("get_enabled_plugins", callback) -@cache -def get_upload_rate(): - try: - rate = get_core().get_upload_rate() - except (AttributeError, socket.error): - set_core_uri(None) - rate = -1 - return rate +def get_download_rate(callback): + get_core().call("get_download_rate", callback) -@cache -def get_num_connections(): - try: - num_connections = get_core().get_num_connections() - except (AttributeError, socket.error): - set_core_uri(None) - num_connections = 0 - return num_connections +def get_upload_rate(callback): + get_core().call("get_upload_rate", callback) + +def get_num_connections(callback): + get_core().call("get_num_connections", callback) def enable_plugin(plugin): - try: - get_core().enable_plugin(plugin) - except (AttributeError, socket.error): - set_core_uri(None) + get_core().call("enable_plugin", None, plugin) def disable_plugin(plugin): - try: - get_core().disable_plugin(plugin) - except (AttributeError, socket.error): - set_core_uri(None) + get_core().call("disable_plugin", None, plugin) def force_recheck(torrent_ids): """Forces a data recheck on torrent_ids""" - try: - for torrent_id in torrent_ids: - get_core().force_recheck(torrent_id) - except (AttributeError, socket.error): - set_core_uri(None) + for torrent_id in torrent_ids: + get_core().call("force_recheck", None, torrent_id) def set_torrent_trackers(torrent_id, trackers): """Sets the torrents trackers""" - try: - get_core().set_torrent_trackers(torrent_id, trackers) - except (AttributeError, socket.error): - set_core_uri(None) + get_core().call("set_torrent_trackers", None, torrent_id, trackers) + diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 49fdc1e66..a6b555b6f 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -119,10 +119,14 @@ class AddTorrentDialog: "add_paused", "default_private" ] + self.core_config = {} + # Send requests to the core for these config values for key in self.core_keys: - self.core_config[key] = client.get_config_value(key) + client.get_config_value(self._on_config_value, key) + # Force a call to the core because we need this data now + client.force_call() self.set_default_options() def show(self): @@ -133,6 +137,12 @@ class AddTorrentDialog: self.dialog.destroy() return None + def _on_config_value(self, value): + for key in self.core_keys: + if not self.core_config.has_key(key): + self.core_config[key] = value + break + def add_to_torrent_list(self, filenames): import deluge.libtorrent as lt import os.path diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index becfd4737..a5158e845 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -152,21 +152,9 @@ class GtkUI: # Make sure the config is saved. config.save() - del config - # Clean-up # Shutdown all components component.shutdown() - del self.mainwindow - del self.systemtray - del self.menubar - del self.toolbar - del self.torrentview - del self.torrentdetails - del self.preferences - del self.signal_receiver - del self.plugins - del deluge.configmanager def _on_new_core(self, data): component.start() diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index b93689136..75bc1dcc2 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -72,7 +72,7 @@ def cell_data_time(column, cell, model, row, data): """Display value as time, eg 1m10s""" time = model.get_value(row, data) if time <= 0: - time_str = _("Infinity") + time_str = "" else: time_str = deluge.common.ftime(time) cell.set_property('text', time_str) diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index b3e2416a9..4a6c7fd23 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -48,17 +48,19 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def start(self): """Start the plugin manager""" # Update the enabled_plugins from the core - enabled_plugins = client.get_enabled_plugins() + client.get_enabled_plugins(self._on_get_enabled_plugins) + + def stop(self): + # Disable the plugins + self.disable_plugins() + + def _on_get_enabled_plugins(self, enabled_plugins): log.debug("Core has these plugins enabled: %s", enabled_plugins) self.config["enabled_plugins"] = enabled_plugins # Enable the plugins that are enabled in the config and core self.enable_plugins() - - def stop(self): - # Disable the plugins - self.disable_plugins() - + ## Plugin functions.. will likely move to own class.. def add_torrentview_text_column(self, *args, **kwargs): diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 192fc75de..6f3546519 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -147,10 +147,28 @@ class Preferences(component.Component): self.notebook.remove_page(self.page_num_to_remove) if self.iter_to_remove != None: self.liststore.remove(self.iter_to_remove) - + + def _on_get_config(self, config): + self.core_config = config + + def _on_get_available_plugins(self, plugins): + self.all_plugins = plugins + + def _on_get_enabled_plugins(self, plugins): + self.enabled_plugins = plugins + + def _on_get_listen_port(self, port): + self.active_port = port + def show(self): # Update the preferences dialog to reflect current config settings - self.core_config = client.get_config() + self.core_config = {} + client.get_config(self._on_get_config) + client.get_available_plugins(self._on_get_available_plugins) + client.get_enabled_plugins(self._on_get_enabled_plugins) + client.get_listen_port(self._on_get_listen_port) + # Force these calls and block until we've done them all + client.force_call() if self.core_config != {} and self.core_config != None: core_widgets = { @@ -171,7 +189,7 @@ class Preferences(component.Component): self.core_config["prioritize_first_last_pieces"]), "spin_port_min": ("value", self.core_config["listen_ports"][0]), "spin_port_max": ("value", self.core_config["listen_ports"][1]), - "active_port_label": ("text", str(client.get_listen_port())), + "active_port_label": ("text", str(self.active_port)), "chk_random_port": ("active", self.core_config["random_port"]), "chk_dht": ("active", self.core_config["dht"]), "chk_upnp": ("active", self.core_config["upnp"]), @@ -297,8 +315,8 @@ class Preferences(component.Component): self.gtkui_config["send_info"]) ## Plugins tab ## - all_plugins = client.get_available_plugins() - enabled_plugins = client.get_enabled_plugins() + all_plugins = self.all_plugins + enabled_plugins = self.enabled_plugins # Clear the existing list so we don't duplicate entries. self.plugin_liststore.clear() # Iterate through the lists and add them to the liststore diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 33022d3c4..5cb91296a 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -57,8 +57,6 @@ class Signals(component.Component): self.torrent_all_paused) self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) - self.receiver.connect_to_signal("torrent_status", - self.torrent_status) def stop(self): self.receiver.shutdown() @@ -80,22 +78,20 @@ class Signals(component.Component): def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") component.get("TorrentView").update() - component.get("ToolBar").update_buttons() + component.get("ToolBar").update_buttons("paused", torrent_id) def torrent_resumed(self, torrent_id): log.debug("torrent_resumed signal received..") component.get("TorrentView").update() - component.get("ToolBar").update_buttons() + component.get("ToolBar").update_buttons("resumed", torrent_id) def torrent_all_paused(self): log.debug("torrent_all_paused signal received..") component.get("TorrentView").update() - component.get("ToolBar").update_buttons() + component.get("ToolBar").update_buttons("paused") def torrent_all_resumed(self): log.debug("torrent_all_resumed signal received..") component.get("TorrentView").update() - component.get("ToolBar").update_buttons() - - def torrent_status(self, status): - component.get("TorrentView").on_torrent_status_signal(status) + component.get("ToolBar").update_buttons("resumed") + diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 945d0c00f..1b2cf4c22 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -82,7 +82,8 @@ class StatusBarItem: self._image.set_from_stock(stock, gtk.ICON_SIZE_MENU) def set_text(self, text): - self._label.set_text(text) + if self._label.get_text() != text: + self._label.set_text(text) def get_widgets(self): return self._widgets() @@ -96,6 +97,14 @@ class StatusBar(component.Component): self.window = component.get("MainWindow") self.statusbar = self.window.main_glade.get_widget("statusbar") + # Status variables that are updated via callback + self.max_connections = -1 + self.num_connections = 0 + self.max_download_speed = -1.0 + self.download_rate = 0.0 + self.max_upload_speed = -1.0 + self.upload_rate = 0.0 + # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() self.hbox.set_spacing(5) @@ -121,6 +130,8 @@ class StatusBar(component.Component): image=deluge.common.get_pixmap("seeding16.png")) self.hbox.pack_start( self.upload_item.get_eventbox(), expand=False, fill=False) + + self.send_status_request() def stop(self): # When stopped, we just show the not connected thingy @@ -153,35 +164,65 @@ class StatusBar(component.Component): def remove(child): self.hbox.remove(child) self.hbox.foreach(remove) + + def send_status_request(self): + # Sends an async request for data from the core + client.get_config_value( + self._on_max_connections_global, "max_connections_global") + client.get_num_connections(self._on_get_num_connections) + client.get_config_value( + self._on_max_download_speed, "max_download_speed") + client.get_download_rate(self._on_get_download_rate) + client.get_config_value( + self._on_max_upload_speed, "max_upload_speed") + client.get_upload_rate(self._on_get_upload_rate) + + def _on_max_connections_global(self, max_connections): + self.max_connections = max_connections + def _on_get_num_connections(self, num_connections): + self.num_connections = num_connections + + def _on_max_download_speed(self, max_download_speed): + self.max_download_speed = max_download_speed + + def _on_get_download_rate(self, download_rate): + self.download_rate = deluge.common.fsize(download_rate) + + def _on_max_upload_speed(self, max_upload_speed): + self.max_upload_speed = max_upload_speed + + def _on_get_upload_rate(self, upload_rate): + self.upload_rate = deluge.common.fsize(upload_rate) + def update(self): # Set the max connections label - max_connections = client.get_config_value("max_connections_global") + max_connections = self.max_connections if max_connections < 0: max_connections = _("Unlimited") self.connections_item.set_text("%s (%s)" % ( - client.get_num_connections(), max_connections)) + self.num_connections, max_connections)) # Set the download speed label - max_download_speed = client.get_config_value("max_download_speed") + max_download_speed = self.max_download_speed if max_download_speed < 0: max_download_speed = _("Unlimited") else: max_download_speed = "%s %s" % (max_download_speed, _("KiB/s")) self.download_item.set_text("%s/s (%s)" % ( - deluge.common.fsize(client.get_download_rate()), - max_download_speed)) + self.download_rate, max_download_speed)) # Set the upload speed label - max_upload_speed = client.get_config_value("max_upload_speed") + max_upload_speed = self.max_upload_speed if max_upload_speed < 0: max_upload_speed = _("Unlimited") else: max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s")) self.upload_item.set_text("%s/s (%s)" % ( - deluge.common.fsize(client.get_upload_rate()), + self.upload_rate, max_upload_speed)) - + + self.send_status_request() diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 0fa339303..622182bdf 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -60,6 +60,11 @@ class SystemTray(component.Component): ] self.config.register_set_function("enable_system_tray", self.on_enable_system_tray_set) + + self.max_download_speed = -1.0 + self.download_rate = 0.0 + self.max_upload_speed = -1.0 + self.upload_rate = 0.0 def enable(self): """Enables the system tray icon.""" @@ -111,6 +116,8 @@ class SystemTray(component.Component): # Build the bandwidth speed limit menus self.build_tray_bwsetsubmenu() + + self.send_status_request() def stop(self): try: @@ -120,36 +127,62 @@ class SystemTray(component.Component): except Exception, e: log.debug("Unable to hide system tray menu widgets: %s", e) + def send_status_request(self): + client.get_config_value( + self._on_max_download_speed, "max_download_speed") + client.get_download_rate(self._on_get_download_rate) + client.get_config_value( + self._on_max_upload_speed, "max_upload_speed") + client.get_upload_rate(self._on_get_upload_rate) + + def _on_max_download_speed(self, max_download_speed): + if self.max_download_speed != max_download_speed: + self.max_download_speed = max_download_speed + self.build_tray_bwsetsubmenu() + + def _on_get_download_rate(self, download_rate): + self.download_rate = deluge.common.fsize(download_rate) + + def _on_max_upload_speed(self, max_upload_speed): + if self.max_upload_speed != max_upload_speed: + self.max_upload_speed = max_upload_speed + self.build_tray_bwsetsubmenu() + + def _on_get_upload_rate(self, upload_rate): + self.upload_rate = deluge.common.fsize(upload_rate) + def update(self): # Set the tool tip text - max_download_speed = client.get_config_value("max_download_speed") - max_upload_speed = client.get_config_value("max_upload_speed") + max_download_speed = self.max_download_speed + max_upload_speed = self.max_upload_speed if max_download_speed == -1: max_download_speed = _("Unlimited") if max_upload_speed == -1: max_upload_speed = _("Unlimited") - msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (\ - _("Deluge Bittorrent Client"), _("Down Speed"), \ - deluge.common.fspeed(client.get_download_rate()), - max_download_speed, _("Up Speed"), \ - deluge.common.fspeed(client.get_upload_rate()), max_upload_speed) + msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % ( + _("Deluge Bittorrent Client"), _("Down Speed"), + self.download_rate, + max_download_speed, _("Up Speed"), + self.upload_rate, max_upload_speed) # Set the tooltip self.tray.set_tooltip(msg) + self.send_status_request() + def build_tray_bwsetsubmenu(self): # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( self.config["tray_download_speed_list"], self.tray_setbwdown, - client.get_config_value("max_download_speed"), + self.max_download_speed, _("KiB/s"), show_notset=True, show_other=True) # Create the Upload speed list sub-menu submenu_bwupset = self.build_menu_radio_list( self.config["tray_upload_speed_list"], self.tray_setbwup, - client.get_config_value("max_upload_speed"), + self.max_upload_speed, _("KiB/s"), show_notset=True, show_other=True) # Add the sub-menus to the tray menu diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index e776de875..472f1b9ee 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -164,56 +164,55 @@ class ToolBar(component.Component): # Use the menubar's callbacks component.get("MenuBar").on_menuitem_connectionmanager_activate(data) - def update_buttons(self): - log.debug("update_buttons") - # If all the selected torrents are paused, then disable the 'Pause' - # button. - # The same goes for the 'Resume' button. - pause = False - resume = False + def update_buttons(self, action=None, torrent_id=None): + if action == None: + # If all the selected torrents are paused, then disable the 'Pause' + # button. + # The same goes for the 'Resume' button. + pause = False + resume = False - # Disable the 'Clear Seeders' button if there's no finished torrent - finished = False + selected = component.get('TorrentView').get_selected_torrents() + if not selected: + selected = [] - selected = component.get('TorrentView').get_selected_torrents() - if not selected: - selected = [] - - for torrent in selected: - try: - status = client.get_torrent_status(torrent, ['state'])['state'] - except KeyError, e: - log.debug("Error getting torrent state: %s", e) - continue - if status == self.STATE_PAUSED: - resume = True - elif status in [self.STATE_FINISHED, self.STATE_SEEDING]: - finished = True - pause = True - else: - pause = True - if pause and resume and finished: - break - - # Enable the 'Remove Torrent' button only if there's some selected - # torrent. - remove = (len(selected) > 0) - - if not finished: - torrents = client.get_session_state() - for torrent in torrents: - if torrent in selected: + for torrent in selected: + try: + status = component.get("TorrentView").get_torrent_status(torrent)['state'] + except KeyError, e: + log.debug("Error getting torrent state: %s", e) continue - status = client.get_torrent_status(torrent, ['state'])['state'] - if status in [self.STATE_FINISHED, self.STATE_SEEDING]: - finished = True + if status == self.STATE_PAUSED: + resume = True + else: + pause = True + if pause and resume: break - for name, sensitive in (("toolbutton_pause", pause), - ("toolbutton_resume", resume), - ("toolbutton_remove", remove), - ("toolbutton_clear", finished)): - self.window.main_glade.get_widget(name).set_sensitive(sensitive) - + # Enable the 'Remove Torrent' button only if there's some selected + # torrent. + remove = (len(selected) > 0) + + for name, sensitive in (("toolbutton_pause", pause), + ("toolbutton_resume", resume), + ("toolbutton_remove", remove)): + self.window.main_glade.get_widget(name).set_sensitive(sensitive) + + return False + + pause = False + resume = False + if action == "paused": + pause = False + resume = True + elif action == "resumed": + pause = True + resume = False + selected = component.get('TorrentView').get_selected_torrents() + if torrent_id == None or (torrent_id in selected and len(selected) == 1): + self.window.main_glade.get_widget("toolbutton_pause").set_sensitive(pause) + self.window.main_glade.get_widget("toolbutton_resume").set_sensitive(resume) + else: + self.update_buttons() + return False - diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 580cf360f..649361b24 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -92,7 +92,7 @@ class TorrentDetails(component.Component): def stop(self): self.clear() - + def update(self): # Show tabs if more than 1 page if self.notebook.get_n_pages() > 1: @@ -107,7 +107,7 @@ class TorrentDetails(component.Component): selected = component.get("TorrentView").get_selected_torrents() # Only use the first torrent in the list or return if None selected - if selected is not None: + if len(selected) != 0: selected = selected[0] else: # No torrent is selected in the torrentview @@ -121,50 +121,54 @@ class TorrentDetails(component.Component): "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", "tracker_status", "save_path"] - status = client.get_torrent_status(selected, status_keys) + client.get_torrent_status( + self._on_get_torrent_status, selected, status_keys) + + def _on_get_torrent_status(self, status): + # Check to see if we got valid data from the core + if status is None: + return - # Check to see if we got valid data from the core - if status is None: - return - - # We need to adjust the value core gives us for progress - try: - progress = status["progress"]/100 - - self.progress_bar.set_fraction(progress) - self.progress_bar.set_text(deluge.common.fpcnt(progress)) - - self.name.set_text(status["name"]) - self.total_size.set_text( - deluge.common.fsize(status["total_size"])) - self.num_files.set_text(str(status["num_files"])) - self.pieces.set_text("%s (%s)" % (status["num_pieces"], - deluge.common.fsize(status["piece_length"]))) - self.availability.set_text( - "%.3f" % status["distributed_copies"]) - self.total_downloaded.set_text("%s (%s)" % \ - (deluge.common.fsize(status["total_done"]), - deluge.common.fsize(status["total_payload_download"]))) - self.total_uploaded.set_text("%s (%s)" % \ - (deluge.common.fsize(status["total_uploaded"]), - deluge.common.fsize(status["total_payload_upload"]))) - self.download_speed.set_text( - deluge.common.fspeed(status["download_payload_rate"])) - self.upload_speed.set_text( - deluge.common.fspeed(status["upload_payload_rate"])) - self.seeders.set_text(deluge.common.fpeer(status["num_seeds"], - status["total_seeds"])) - self.peers.set_text(deluge.common.fpeer(status["num_peers"], - status["total_peers"])) - self.eta.set_text(deluge.common.ftime(status["eta"])) - self.share_ratio.set_text("%.3f" % status["ratio"]) - self.tracker.set_text(status["tracker"]) - self.tracker_status.set_text(status["tracker_status"]) - self.next_announce.set_text( - deluge.common.ftime(status["next_announce"])) - self.torrent_path.set_text(status["save_path"]) - except KeyError, e: - log.debug(e) + # We need to adjust the value core gives us for progress + try: + progress = status["progress"]/100 + + self.progress_bar.set_fraction(progress) + self.progress_bar.set_text(deluge.common.fpcnt(progress)) + + self.name.set_text(status["name"]) + self.total_size.set_text( + deluge.common.fsize(status["total_size"])) + self.num_files.set_text(str(status["num_files"])) + self.pieces.set_text("%s (%s)" % (status["num_pieces"], + deluge.common.fsize(status["piece_length"]))) + self.availability.set_text( + "%.3f" % status["distributed_copies"]) + self.total_downloaded.set_text("%s (%s)" % \ + (deluge.common.fsize(status["total_done"]), + deluge.common.fsize(status["total_payload_download"]))) + self.total_uploaded.set_text("%s (%s)" % \ + (deluge.common.fsize(status["total_uploaded"]), + deluge.common.fsize(status["total_payload_upload"]))) + self.download_speed.set_text( + deluge.common.fspeed(status["download_payload_rate"])) + self.upload_speed.set_text( + deluge.common.fspeed(status["upload_payload_rate"])) + self.seeders.set_text(deluge.common.fpeer(status["num_seeds"], + status["total_seeds"])) + self.peers.set_text(deluge.common.fpeer(status["num_peers"], + status["total_peers"])) + self.eta.set_text(deluge.common.ftime(status["eta"])) + self.share_ratio.set_text("%.3f" % status["ratio"]) + self.tracker.set_text(status["tracker"]) + self.tracker_status.set_text(status["tracker_status"]) + self.next_announce.set_text( + deluge.common.ftime(status["next_announce"])) + self.torrent_path.set_text(status["save_path"]) + except KeyError, e: + log.debug(e) + + def clear(self): # Only update if this page is showing diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 1a9845fba..3195755d8 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,6 +40,7 @@ import gettext import gobject import cPickle as pickle import time +import traceback import deluge.common import deluge.component as component @@ -186,10 +187,14 @@ class TorrentView(listview.ListView, component.Component): """Start the torrentview""" # We need to get the core session state to know which torrents are in # the session so we can add them to our list. - session_state = client.get_session_state() - for torrent_id in session_state: - self.add_row(torrent_id) + client.get_session_state(self._on_session_state) + def _on_session_state(self, state): + for torrent_id in state: + self.add_row(torrent_id) + + self.update() + def stop(self): """Stops the torrentview""" # We need to clear the liststore @@ -245,18 +250,17 @@ class TorrentView(listview.ListView, component.Component): torrent_ids.append(self.liststore.get_value( row, self.columns["torrent_id"].column_indices[0])) row = self.liststore.iter_next(row) - + if torrent_ids == []: return # Request the statuses for all these torrent_ids, this is async so we # will deal with the return in a signal callback. - client.get_torrents_status(torrent_ids, status_keys) - - def update(self, columns=None): - """Update the view. If columns is not None, it will attempt to only - update those columns selected. - """ + client.get_torrents_status( + self._on_get_torrents_status, torrent_ids, status_keys) + + def update(self): + # Update the filter view def foreachrow(model, path, row, data): filter_column = self.columns["filter"].column_indices[0] # Create a function to create a new liststore with only the @@ -275,7 +279,13 @@ class TorrentView(listview.ListView, component.Component): model.set_value(row, filter_column, False) self.liststore.foreach(foreachrow, self.filter) + # Send a status request + self.send_status_request() + def update_view(self, columns=None): + """Update the view. If columns is not None, it will attempt to only + update those columns selected. + """ # Update the torrent view model with data we've received status = self.status row = self.liststore.get_iter_first() @@ -320,15 +330,18 @@ class TorrentView(listview.ListView, component.Component): except: pass row = self.liststore.iter_next(row) - - # Send a request for a status update - self.send_status_request(columns) - - def on_torrent_status_signal(self, status): + + def _on_get_torrents_status(self, status): """Callback function for get_torrents_status(). 'status' should be a dictionary of {torrent_id: {key, value}}.""" - self.status = pickle.loads(status) - + if status != None: + self.status = status + else: + self.status = {} + + if self.status != {}: + self.update_view() + def add_row(self, torrent_id): """Adds a new torrent row to the treeview""" # Insert a new row to the liststore @@ -338,7 +351,6 @@ class TorrentView(listview.ListView, component.Component): row, self.columns["torrent_id"].column_indices[0], torrent_id) - self.update() def remove_row(self, torrent_id): """Removes a row with torrent_id""" @@ -366,22 +378,31 @@ class TorrentView(listview.ListView, component.Component): try: paths = self.treeview.get_selection().get_selected_rows()[1] except AttributeError: - # paths is likely None .. so lets return None - return None + # paths is likely None .. so lets return [] + return [] try: for path in paths: torrent_ids.append( - self.liststore.get_value( - self.liststore.get_iter(path), 0)) + self.model_filter.get_value( + self.model_filter.get_iter(path), 0)) if len(torrent_ids) is 0: - # Only return a list if there is something in it. - return None + return [] return torrent_ids except ValueError: - return None - + return [] + + def get_torrent_status(self, torrent_id): + """Returns data stored in self.status, it may not be complete""" + try: + return self.status[torrent_id] + except: + return {} + + def get_visible_torrents(self): + return self.status.keys() + ### Callbacks ### def on_button_press_event(self, widget, event): """This is a callback for showing the right-click context menu.""" diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 60689d474..606d8a6cd 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -79,17 +79,13 @@ class SignalReceiver( self.register_function(self.emit_signal) # Register the signal receiver with the core - core = client.get_core() - core.register_client(str(port)) + client.register_client(str(port)) def shutdown(self): """Shutdowns receiver thread""" self._shutdown = True # De-register with the daemon so it doesn't try to send us more signals - try: - client.get_core().deregister_client() - except (socket.error, AttributeError): - pass + client.deregister_client() # Hacky.. sends a request to our local receiver to ensure that it # shutdowns.. This is because handle_request() is a blocking call. From 0f7a91797f5c576ee12a1876d11106f75e73022b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 19 Jan 2008 23:05:55 +0000 Subject: [PATCH 0344/1009] Remove 'Clear Seeders' tool button. Remove 'Add URL' menu item. Modify the Preferences dialog a bit. --- deluge/ui/gtkui/glade/main_window.glade | 38 -- .../ui/gtkui/glade/preferences_dialog.glade | 326 +++++++++++------- deluge/ui/gtkui/menubar.py | 17 +- deluge/ui/gtkui/preferences.py | 2 - deluge/ui/gtkui/toolbar.py | 7 - 5 files changed, 194 insertions(+), 196 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index e6fa3cf18..9704c58a4 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -37,30 +37,6 @@ - - - True - False - Add _URL - True - - - - - - True - False - _Clear Completed - True - - - True - gtk-clear - 1 - - - - @@ -301,20 +277,6 @@ False - - - True - False - Remove the seeding torrents - Clear Seeds - True - gtk-clear - - - - False - - True diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index e9b32bb41..3ff4cf106 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -430,20 +430,6 @@ True - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Enable selecting files for torrents before loading - Enable selecting files for torrents before loading - 0 - True - - - False - - True @@ -456,6 +442,18 @@ False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Set Private by default + 0 + True + + 1 @@ -1078,69 +1076,35 @@ Either 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): - 1 - 2 3 4 GTK_FILL - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The maximum download speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. 0 - Maximum Download Speed (KiB/s): + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: 1 @@ -1168,39 +1132,73 @@ Either - + True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum download speed for all torrents. Set -1 for unlimited. 0 - Maximum Upload Slots: + Maximum Download Speed (KiB/s): + 2 + 3 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The maximum download speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True + + + 1 + 2 3 4 GTK_FILL - + True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 2 - 3 + 1 + 2 + 1 + 2 GTK_FILL @@ -1243,28 +1241,95 @@ Either True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 + 4 2 15 - + True True - The maximum upload slots per torrent. Set -1 for unlimited. + The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 - True + 1 True 1 2 + 3 + 4 + GTK_FILL + + + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + 1 2 GTK_FILL + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + True @@ -1282,24 +1347,19 @@ Either - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL @@ -1550,33 +1610,15 @@ Either 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1606,20 +1648,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 0df52ba71..3413b8a7c 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -63,9 +63,6 @@ class MenuBar(component.Component): ## File Menu "on_menuitem_addtorrent_activate": \ self.on_menuitem_addtorrent_activate, - "on_menuitem_addurl_activate": self.on_menuitem_addurl_activate, - "on_menuitem_clear_activate": \ - self.on_menuitem_clear_activate, "on_menuitem_quitdaemon_activate": \ self.on_menuitem_quitdaemon_activate, "on_menuitem_quit_activate": self.on_menuitem_quit_activate, @@ -103,9 +100,7 @@ class MenuBar(component.Component): }) self.change_sensitivity = [ - "menuitem_addtorrent", - "menuitem_addurl", - "menuitem_clear" + "menuitem_addtorrent" ] def start(self): @@ -157,16 +152,6 @@ class MenuBar(component.Component): #client.add_torrent_file(AddTorrentDialog().run()) AddTorrentDialog().show() - def on_menuitem_addurl_activate(self, data=None): - log.debug("on_menuitem_addurl_activate") - from addtorrenturl import AddTorrentUrl - result = AddTorrentUrl().run() - if result is not None: - client.add_torrent_url(result) - - def on_menuitem_clear_activate(self, data=None): - log.debug("on_menuitem_clear_activate") - def on_menuitem_quitdaemon_activate(self, data=None): log.debug("on_menuitem_quitdaemon_activate") # Tell the core to shutdown diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 6f3546519..c556fa435 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -286,8 +286,6 @@ class Preferences(component.Component): self.gtkui_config["autoadd_enable"]) self.glade.get_widget("autoadd_folder_button").set_filename( self.gtkui_config["autoadd_location"]) - self.glade.get_widget("chk_enable_files_dialog").set_active( - self.gtkui_config["enable_files_dialog"]) ## Interface tab ## self.glade.get_widget("chk_use_tray").set_active( diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 472f1b9ee..dffea8ee2 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -54,7 +54,6 @@ class ToolBar(component.Component): self.window.main_glade.signal_autoconnect({ "on_toolbutton_add_clicked": self.on_toolbutton_add_clicked, "on_toolbutton_remove_clicked": self.on_toolbutton_remove_clicked, - "on_toolbutton_clear_clicked": self.on_toolbutton_clear_clicked, "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked, "on_toolbutton_resume_clicked": self.on_toolbutton_resume_clicked, "on_toolbutton_preferences_clicked": \ @@ -65,7 +64,6 @@ class ToolBar(component.Component): self.change_sensitivity = [ "toolbutton_add", "toolbutton_remove", - "toolbutton_clear", "toolbutton_pause", "toolbutton_resume" ] @@ -139,11 +137,6 @@ class ToolBar(component.Component): # Use the menubar's callbacks component.get("MenuBar").on_menuitem_remove_activate(data) - def on_toolbutton_clear_clicked(self, data): - log.debug("on_toolbutton_clear_clicked") - # Use the menubar's callbacks - component.get("MenuBar").on_menuitem_clear_activate(data) - def on_toolbutton_pause_clicked(self, data): log.debug("on_toolbutton_pause_clicked") # Use the menubar's callbacks From 42b5f6872c116c9401e959604a495fc9d5881bff Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 19 Jan 2008 23:11:12 +0000 Subject: [PATCH 0345/1009] Fix labels. --- deluge/ui/gtkui/torrentview.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 3195755d8..c1a40bb8e 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -271,7 +271,10 @@ class TorrentView(listview.ListView, component.Component): return torrent_id = model.get_value(row, 0) - value = client.get_torrent_status(torrent_id, [field])[field] + try: + value = self.status[torrent_id][field] + except: + return # Condition is True, so lets show this row, if not we hide it if value == condition: model.set_value(row, filter_column, True) From 0e5c81c2e02e49e2425085fcaefcaf90f1fc68fd Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 19 Jan 2008 23:33:09 +0000 Subject: [PATCH 0346/1009] Handle losing contact with the daemon gracefully. --- deluge/ui/client.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 536e6dcd7..4e0842e89 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -155,16 +155,21 @@ class CoreProxy(gobject.GObject): return True if self._multi is not None: - for i, ret in enumerate(self._multi()): - try: - if block == False: - gobject.idle_add(self._callbacks[i], ret) - else: - self._callbacks[i](ret) - except: - pass - - self._callbacks = [] + try: + for i, ret in enumerate(self._multi()): + try: + if block == False: + gobject.idle_add(self._callbacks[i], ret) + else: + self._callbacks[i](ret) + except: + pass + except socket.error, e: + log.warning("Could not contact daemon: %s", e) + self.set_core_uri(None) + finally: + self._callbacks = [] + self._multi = xmlrpclib.MultiCall(self._core) return True From 3a102189cf436a98b49a9f3ccf90469b13ecd68d Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sat, 19 Jan 2008 23:58:03 +0000 Subject: [PATCH 0347/1009] add missing pixmap --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cd2135ec3..b1e246653 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,8 @@ _libraries = [ 'boost_python', 'z', 'pthread', - 'ssl' + 'ssl', + ] _sources = glob.glob("./libtorrent/src/*.cpp") + \ @@ -213,7 +214,8 @@ setup( ('/usr/share/icons/hicolor/96x96/apps', [ 'deluge/data/icons/hicolor/96x96/apps/deluge.png']), ('/usr/share/applications', [ - 'deluge/data/share/applications/deluge.desktop'])], + 'deluge/data/share/applications/deluge.desktop']), + ('/usr/share/pixmaps' , ['deluge.png'])], ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(exclude=["plugins"]), From 071a0cdbaad2ac51d89b13efcf6a544d1881f2ec Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 00:11:50 +0000 Subject: [PATCH 0348/1009] fix path oops in last --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b1e246653..2e65268ca 100644 --- a/setup.py +++ b/setup.py @@ -215,7 +215,7 @@ setup( 'deluge/data/icons/hicolor/96x96/apps/deluge.png']), ('/usr/share/applications', [ 'deluge/data/share/applications/deluge.desktop']), - ('/usr/share/pixmaps' , ['deluge.png'])], + ('/usr/share/pixmaps' , ['deluge/data/pixmaps/deluge.png'])], ext_package = "deluge", ext_modules = [libtorrent], packages = find_packages(exclude=["plugins"]), From e5f9a313c2249d03486e78c9d7516684faa5322e Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 00:30:43 +0000 Subject: [PATCH 0349/1009] fix missing icon --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2e65268ca..85930fdba 100644 --- a/setup.py +++ b/setup.py @@ -171,7 +171,7 @@ setup( include_package_data = True, package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png", - "data/pixmaps/logo.svg", + "data/pixmaps/deluge.svg", "plugins/*.egg", "i18n/*.pot", "i18n/*/LC_MESSAGES/*.mo", From bf26a9ef501fc198c660379bdc02585c3a528539 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 00:38:36 +0000 Subject: [PATCH 0350/1009] import signal and fix shutdown call --- deluge/core/core.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 05dda81b7..90596f5f1 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -37,7 +37,7 @@ import pkg_resources import sys import shutil import os - +import signal import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import deluge.xmlrpclib as xmlrpclib @@ -141,6 +141,27 @@ class Core( gettext.install("deluge", pkg_resources.resource_filename( "deluge", "i18n")) + try: + import gnome.ui + self.client = gnome.ui.Client() + self.client.connect("save_yourself", self._shutdown) + except: + pass + + signal.signal(signal.SIGINT, self._shutdown) + signal.signal(signal.SIGTERM, self._shutdown) + if not deluge.common.windows_check(): + signal.signal(signal.SIGHUP, self._shutdown) + else: + from win32api import SetConsoleCtrlHandler + from win32con import CTRL_CLOSE_EVENT + result = 0 + def win_handler(self, ctrl_type): + if ctrl_type == CTRL_CLOSE_EVENT: + self._shutdown() + result = 1 + return result + SetConsoleCtrlHandler(win_handler) def get_request(self): """Get the request and client address from the socket. From 77a63c791cfb0bee952d44526dff538ba974b861 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 00:40:12 +0000 Subject: [PATCH 0351/1009] fix finally call for 2.4 compatibility --- deluge/ui/client.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 4e0842e89..64b108655 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -156,17 +156,18 @@ class CoreProxy(gobject.GObject): if self._multi is not None: try: - for i, ret in enumerate(self._multi()): - try: - if block == False: - gobject.idle_add(self._callbacks[i], ret) - else: - self._callbacks[i](ret) - except: - pass - except socket.error, e: - log.warning("Could not contact daemon: %s", e) - self.set_core_uri(None) + try: + for i, ret in enumerate(self._multi()): + try: + if block == False: + gobject.idle_add(self._callbacks[i], ret) + else: + self._callbacks[i](ret) + except: + pass + except socket.error, e: + log.warning("Could not contact daemon: %s", e) + self.set_core_uri(None) finally: self._callbacks = [] From f042bf0dda686117297e8be1118e1398a3ffb173 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 00:46:52 +0000 Subject: [PATCH 0352/1009] use die in signal --- deluge/core/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 90596f5f1..2337be13a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -144,7 +144,7 @@ class Core( try: import gnome.ui self.client = gnome.ui.Client() - self.client.connect("save_yourself", self._shutdown) + self.client.connect("die", self._shutdown) except: pass From 76a0eb12e8e55fdae70d0d06ffc9c01550abc62e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 00:56:42 +0000 Subject: [PATCH 0353/1009] Do not make Core a thread anymore as it is not needed. --- deluge/core/core.py | 10 +++++----- deluge/core/daemon.py | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 2337be13a..1a7ecd9aa 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -90,12 +90,11 @@ DEFAULT_PREFS = { } class Core( - threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self, port): log.debug("Core init..") - threading.Thread.__init__(self) + #threading.Thread.__init__(self) self.client_address = None @@ -141,6 +140,7 @@ class Core( gettext.install("deluge", pkg_resources.resource_filename( "deluge", "i18n")) + # Setup signals try: import gnome.ui self.client = gnome.ui.Client() @@ -162,7 +162,7 @@ class Core( result = 1 return result SetConsoleCtrlHandler(win_handler) - + def get_request(self): """Get the request and client address from the socket. We override this so that we can get the ip address of the client. @@ -173,7 +173,7 @@ class Core( def run(self): """Starts the core""" - + # Create the client fingerprint version = [] for value in deluge.common.get_version().split("."): @@ -250,7 +250,7 @@ class Core( self.loop = gobject.MainLoop() self.loop.run() - def _shutdown(self): + def _shutdown(self, data=None): """This is called by a thread from shutdown()""" log.info("Shutting down core..") component.shutdown() diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index 6e23b6c95..b24e16ab0 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -37,7 +37,5 @@ from deluge.log import LOG as log class Daemon: def __init__(self, port): # Start the core as a thread and join it until it's done - self.core = Core(port) - self.core.start() - self.core.join() + self.core = Core(port).run() From df29d7d69a10861a6c9ddc1302700e0ee6b1806b Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 00:59:49 +0000 Subject: [PATCH 0354/1009] lt sync 1958 --- .../include/libtorrent/bandwidth_manager.hpp | 124 +++++++----------- .../libtorrent/bandwidth_queue_entry.hpp | 9 +- .../include/libtorrent/http_connection.hpp | 11 +- .../include/libtorrent/invariant_check.hpp | 2 +- .../include/libtorrent/peer_connection.hpp | 16 +-- libtorrent/include/libtorrent/peer_info.hpp | 4 +- .../include/libtorrent/session_status.hpp | 3 + libtorrent/include/libtorrent/torrent.hpp | 7 +- .../include/libtorrent/torrent_handle.hpp | 5 + libtorrent/src/disk_io_thread.cpp | 6 +- libtorrent/src/http_connection.cpp | 66 +++++----- libtorrent/src/pe_crypto.cpp | 50 ++++--- libtorrent/src/peer_connection.cpp | 34 +++-- libtorrent/src/session_impl.cpp | 7 +- libtorrent/src/storage.cpp | 16 ++- libtorrent/src/torrent.cpp | 49 ++++--- libtorrent/src/web_peer_connection.cpp | 2 +- 17 files changed, 221 insertions(+), 190 deletions(-) diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 68cb7c73a..89efcb9e8 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -124,6 +124,12 @@ struct bandwidth_manager bool is_in_history(PeerConnection const* peer) const { mutex_t::scoped_lock l(m_mutex); + return is_in_history(peer, l); + } + + bool is_in_history(PeerConnection const* peer, boost::mutex::scoped_lock& l) const + { + TORRENT_ASSERT(l.locked()); for (typename history_t::const_iterator i = m_history.begin(), end(m_history.end()); i != end; ++i) { @@ -133,17 +139,21 @@ struct bandwidth_manager } #endif + int queue_size() const + { + mutex_t::scoped_lock l(m_mutex); + return m_queue.size(); + } + // non prioritized means that, if there's a line for bandwidth, // others will cut in front of the non-prioritized peers. // this is used by web seeds void request_bandwidth(intrusive_ptr peer - , int blk - , bool non_prioritized) throw() + , int blk, int priority) { + mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; TORRENT_ASSERT(blk > 0); - - mutex_t::scoped_lock l(m_mutex); TORRENT_ASSERT(!peer->ignore_bandwidth_limits()); // make sure this peer isn't already in line @@ -157,38 +167,13 @@ struct bandwidth_manager #endif TORRENT_ASSERT(peer->max_assignable_bandwidth(m_channel) > 0); - boost::shared_ptr t = peer->associated_torrent().lock(); - - if (peer->max_assignable_bandwidth(m_channel) == 0) - { - t->expire_bandwidth(m_channel, blk); - peer->assign_bandwidth(m_channel, 0); - return; - } - m_queue.push_back(bw_queue_entry(peer, blk, non_prioritized)); - if (!non_prioritized) - { - typename queue_t::reverse_iterator i = m_queue.rbegin(); - typename queue_t::reverse_iterator j(i); - for (++j; j != m_queue.rend(); ++j) - { - // if the peer's torrent is not the same one - // continue looking for a peer from the same torrent - if (j->peer->associated_torrent().lock() != t) - continue; - // if we found a peer from the same torrent that - // is prioritized, there is no point looking - // any further. - if (!j->non_prioritized) break; - - using std::swap; - swap(*i, *j); - i = j; - } - } -#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " req_bandwidht. m_queue.size() = " << m_queue.size() << std::endl; -#endif + typename queue_t::reverse_iterator i(m_queue.rbegin()); + while (i != m_queue.rend() && priority > i->priority) + { + ++i->priority; + ++i; + } + m_queue.insert(i.base(), bw_queue_entry(peer, blk, priority)); if (!m_queue.empty()) hand_out_bandwidth(l); } @@ -201,18 +186,24 @@ struct bandwidth_manager { current_quota += i->amount; } - TORRENT_ASSERT(current_quota == m_current_quota); + + typename queue_t::const_iterator j = m_queue.begin(); + if (j != m_queue.end()) + { + ++j; + for (typename queue_t::const_iterator i = m_queue.begin() + , end(m_queue.end()); i != end && j != end; ++i, ++j) + TORRENT_ASSERT(i->priority >= j->priority); + } } #endif private: - void add_history_entry(history_entry const& e) throw() + void add_history_entry(history_entry const& e) { -#ifndef NDEBUG try { -#endif INVARIANT_CHECK; m_history.push_front(e); m_current_quota += e.amount; @@ -224,24 +215,19 @@ private: m_history_timer.expires_at(e.expires_at); m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); -#ifndef NDEBUG } - catch (std::exception&) { TORRENT_ASSERT(false); } -#endif + catch (std::exception&) {} } - void on_history_expire(asio::error_code const& e) throw() + void on_history_expire(asio::error_code const& e) { -#ifndef NDEBUG try { -#endif - INVARIANT_CHECK; - if (e) return; + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; TORRENT_ASSERT(!m_history.empty()); - mutex_t::scoped_lock l(m_mutex); ptime now(time_now()); while (!m_history.empty() && m_history.back().expires_at <= now) { @@ -268,24 +254,19 @@ private: // means we can hand out more (in case there // are still consumers in line) if (!m_queue.empty()) hand_out_bandwidth(l); -#ifndef NDEBUG } - catch (std::exception&) - { - TORRENT_ASSERT(false); - } -#endif + catch (std::exception&) {} } void hand_out_bandwidth(boost::mutex::scoped_lock& l) throw() { + TORRENT_ASSERT(l.locked()); // if we're already handing out bandwidth, just return back // to the loop further down on the callstack if (m_in_hand_out_bandwidth) return; m_in_hand_out_bandwidth = true; -#ifndef NDEBUG + try { -#endif INVARIANT_CHECK; ptime now(time_now()); @@ -308,24 +289,20 @@ private: return; } - queue_t q; queue_t tmp; - m_queue.swap(q); - while (!q.empty() && amount > 0) + while (!m_queue.empty() && amount > 0) { - TORRENT_ASSERT(amount == limit - m_current_quota); - bw_queue_entry qe = q.front(); + bw_queue_entry qe = m_queue.front(); TORRENT_ASSERT(qe.max_block_size > 0); - q.pop_front(); + m_queue.pop_front(); - shared_ptr t = qe.peer->associated_torrent().lock(); + shared_ptr t = qe.torrent.lock(); if (!t) continue; if (qe.peer->is_disconnecting()) { l.unlock(); t->expire_bandwidth(m_channel, qe.max_block_size); l.lock(); - amount = limit - m_current_quota; continue; } @@ -335,7 +312,7 @@ private: int max_assignable = qe.peer->max_assignable_bandwidth(m_channel); if (max_assignable == 0) { - TORRENT_ASSERT(is_in_history(qe.peer.get())); + TORRENT_ASSERT(is_in_history(qe.peer.get(), l)); tmp.push_back(qe); continue; } @@ -379,11 +356,6 @@ private: #ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; #endif - if (amount < block_size / 2) - { - tmp.push_back(qe); - break; - } // so, hand out max_assignable, but no more than // the available bandwidth (amount) and no more @@ -391,7 +363,6 @@ private: int hand_out_amount = (std::min)((std::min)(block_size, max_assignable) , amount); TORRENT_ASSERT(hand_out_amount > 0); - TORRENT_ASSERT(amount == limit - m_current_quota); amount -= hand_out_amount; TORRENT_ASSERT(hand_out_amount <= qe.max_block_size); l.unlock(); @@ -400,15 +371,14 @@ private: l.lock(); add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); - amount = limit - m_current_quota; } - if (!q.empty()) m_queue.insert(m_queue.begin(), q.begin(), q.end()); if (!tmp.empty()) m_queue.insert(m_queue.begin(), tmp.begin(), tmp.end()); -#ifndef NDEBUG } catch (std::exception& e) - { TORRENT_ASSERT(false); }; -#endif + { + m_in_hand_out_bandwidth = false; + throw; + } m_in_hand_out_bandwidth = false; } diff --git a/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp b/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp index 76c119d96..f8b44846c 100644 --- a/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp +++ b/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp @@ -40,12 +40,15 @@ namespace libtorrent { template struct bw_queue_entry { + typedef typename PeerConnection::torrent_type torrent_type; bw_queue_entry(boost::intrusive_ptr const& pe - , int blk, bool no_prio) - : peer(pe), max_block_size(blk), non_prioritized(no_prio) {} + , int blk, int prio) + : peer(pe), torrent(peer->associated_torrent()) + , max_block_size(blk), priority(prio) {} boost::intrusive_ptr peer; + boost::weak_ptr torrent; int max_block_size; - bool non_prioritized; + int priority; // 0 is low prio }; } diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index 9d32af2e9..b65b303ae 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -78,7 +78,7 @@ struct http_connection : boost::enable_shared_from_this, boost: , m_download_quota(0) , m_limiter_timer_active(false) , m_limiter_timer(ios) - , m_redirect(true) + , m_redirects(5) , m_connection_ticket(-1) , m_cc(cc) { @@ -93,10 +93,10 @@ struct http_connection : boost::enable_shared_from_this, boost: std::string sendbuffer; void get(std::string const& url, time_duration timeout = seconds(30) - , bool handle_redirect = true); + , int handle_redirects = 5); void start(std::string const& hostname, std::string const& port - , time_duration timeout, bool handle_redirect = true); + , time_duration timeout, int handle_redirect = 5); void close(); tcp::socket const& socket() const { return m_sock; } @@ -153,9 +153,8 @@ private: // as all the quota was used. deadline_timer m_limiter_timer; - // if set to true, the connection should handle - // HTTP redirects. - bool m_redirect; + // the number of redirects to follow (in sequence) + int m_redirects; int m_connection_ticket; connection_queue& m_cc; diff --git a/libtorrent/include/libtorrent/invariant_check.hpp b/libtorrent/include/libtorrent/invariant_check.hpp index 3075b8975..c687b6a63 100755 --- a/libtorrent/include/libtorrent/invariant_check.hpp +++ b/libtorrent/include/libtorrent/invariant_check.hpp @@ -66,7 +66,7 @@ namespace libtorrent } } -#ifndef NDEBUG +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS #define INVARIANT_CHECK \ invariant_checker const& _invariant_check = make_invariant_checker(*this); \ (void)_invariant_check; \ diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 60cb4bc17..13fab90ca 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -98,6 +98,8 @@ namespace libtorrent friend class invariant_access; public: + typedef torrent torrent_type; + enum channels { upload_channel, @@ -173,8 +175,8 @@ namespace libtorrent void request_large_blocks(bool b) { m_request_large_blocks = b; } - void set_non_prioritized(bool b) - { m_non_prioritized = b; } + void set_priority(int p) + { m_priority = p; } void fast_reconnect(bool r); bool fast_reconnect() const { return m_fast_reconnect; } @@ -224,7 +226,7 @@ namespace libtorrent void add_stat(size_type downloaded, size_type uploaded); // is called once every second by the main loop - void second_tick(float tick_interval) throw(); + void second_tick(float tick_interval); boost::shared_ptr get_socket() const { return m_socket; } tcp::endpoint const& remote() const { return m_remote; } @@ -683,11 +685,9 @@ namespace libtorrent // at a time. bool m_request_large_blocks; - // if this is true, other (prioritized) peers will - // skip ahead of it in the queue for bandwidth. The - // effect is that non prioritized peers will only use - // the left-over bandwidth (suitable for web seeds). - bool m_non_prioritized; + // this is the priority with which this peer gets + // download bandwidth quota assigned to it. + int m_priority; int m_upload_limit; int m_download_limit; diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index b07acffd4..e65f33a18 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -96,8 +96,10 @@ namespace libtorrent // time since last download or upload time_duration last_active; - // the size of the send buffer for this peer + // the size of the send buffer for this peer, in bytes int send_buffer_size; + // the number bytes that's actually used of the send buffer + int used_send_buffer; // the number of failed hashes for this peer int num_hashfails; diff --git a/libtorrent/include/libtorrent/session_status.hpp b/libtorrent/include/libtorrent/session_status.hpp index adbb1b57d..e0a9b88a7 100644 --- a/libtorrent/include/libtorrent/session_status.hpp +++ b/libtorrent/include/libtorrent/session_status.hpp @@ -55,6 +55,9 @@ namespace libtorrent int num_peers; + int up_bandwidth_queue; + int down_bandwidth_queue; + #ifndef TORRENT_DISABLE_DHT int dht_nodes; int dht_node_cache; diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index f66620999..773188cce 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -229,13 +229,12 @@ namespace libtorrent void request_bandwidth(int channel , boost::intrusive_ptr const& p - , bool non_prioritized); + , int priority); void perform_bandwidth_request(int channel , boost::intrusive_ptr const& p - , int block_size - , bool non_prioritized); - + , int block_size, int priority); + void expire_bandwidth(int channel, int amount); void assign_bandwidth(int channel, int amount, int blk); diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 48a17e2ec..14217d9a4 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -112,6 +112,8 @@ namespace libtorrent , uploads_limit(0) , connections_limit(0) , storage_mode(storage_mode_sparse) + , up_bandwidth_queue(0) + , down_bandwidth_queue(0) {} enum state_t @@ -231,6 +233,9 @@ namespace libtorrent // true if the torrent is saved in compact mode // false if it is saved in full allocation mode storage_mode_t storage_mode; + + int up_bandwidth_queue; + int down_bandwidth_queue; }; struct TORRENT_EXPORT block_info diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index 886d5d891..e1cfbfe5f 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -333,7 +333,11 @@ namespace libtorrent catch (std::exception& e) { // std::cerr << "DISK THREAD: exception: " << e.what() << std::endl; - j.str = e.what(); + try + { + j.str = e.what(); + } + catch (std::exception&) {} ret = -1; } diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index a73d0a9af..359bd52f6 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -45,9 +45,8 @@ namespace libtorrent enum { max_bottled_buffer = 1024 * 1024 }; void http_connection::get(std::string const& url, time_duration timeout - , bool handle_redirect) + , int handle_redirects) { - m_redirect = handle_redirect; std::string protocol; std::string auth; std::string hostname; @@ -62,21 +61,23 @@ void http_connection::get(std::string const& url, time_duration timeout headers << "Authorization: Basic " << base64encode(auth) << "\r\n"; headers << "\r\n"; sendbuffer = headers.str(); - start(hostname, boost::lexical_cast(port), timeout); + start(hostname, boost::lexical_cast(port), timeout, handle_redirects); } void http_connection::start(std::string const& hostname, std::string const& port - , time_duration timeout, bool handle_redirect) + , time_duration timeout, int handle_redirects) { - m_redirect = handle_redirect; + m_redirects = handle_redirects; m_timeout = timeout; m_timer.expires_from_now(m_timeout); m_timer.async_wait(bind(&http_connection::on_timeout , boost::weak_ptr(shared_from_this()), _1)); m_called = false; + m_parser.reset(); + m_recvbuffer.clear(); + m_read_pos = 0; if (m_sock.is_open() && m_hostname == hostname && m_port == port) { - m_parser.reset(); asio::async_write(m_sock, asio::buffer(sendbuffer) , bind(&http_connection::on_write, shared_from_this(), _1)); } @@ -233,6 +234,7 @@ void http_connection::on_read(asio::error_code const& e if (e == asio::error::eof) { + TORRENT_ASSERT(bytes_transferred == 0); char const* data = 0; std::size_t size = 0; if (m_bottled && m_parser.header_finished()) @@ -247,6 +249,7 @@ void http_connection::on_read(asio::error_code const& e if (e) { + TORRENT_ASSERT(bytes_transferred == 0); callback(e); close(); return; @@ -255,31 +258,6 @@ void http_connection::on_read(asio::error_code const& e m_read_pos += bytes_transferred; TORRENT_ASSERT(m_read_pos <= int(m_recvbuffer.size())); - // having a nonempty path means we should handle redirects - if (m_redirect && m_parser.header_finished()) - { - int code = m_parser.status_code(); - if (code >= 300 && code < 400) - { - // attempt a redirect - std::string const& url = m_parser.header("location"); - if (url.empty()) - { - // missing location header - callback(e); - return; - } - - m_limiter_timer_active = false; - close(); - - get(url, m_timeout); - return; - } - - m_redirect = false; - } - if (m_bottled || !m_parser.header_finished()) { libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0] @@ -295,6 +273,32 @@ void http_connection::on_read(asio::error_code const& e m_handler.clear(); return; } + + // having a nonempty path means we should handle redirects + if (m_redirects && m_parser.header_finished()) + { + int code = m_parser.status_code(); + + if (code >= 300 && code < 400) + { + // attempt a redirect + std::string const& url = m_parser.header("location"); + if (url.empty()) + { + // missing location header + callback(e); + return; + } + + asio::error_code ec; + m_sock.close(ec); + get(url, m_timeout, m_redirects - 1); + return; + } + + m_redirects = 0; + } + if (!m_bottled && m_parser.header_finished()) { if (m_read_pos > m_parser.body_start()) diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index 093bb1265..a865b4c73 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -1,6 +1,6 @@ /* -Copyright (c) 2007, Un Shyam +Copyright (c) 2007, Un Shyam & Arvid Norberg All rights reserved. Redistribution and use in source and binary forms, with or without @@ -40,23 +40,33 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pe_crypto.hpp" #include "libtorrent/assert.hpp" -namespace libtorrent { +namespace libtorrent +{ - // Set the prime P and the generator, generate local public key - DH_key_exchange::DH_key_exchange () + DH_key_exchange::DH_key_exchange() { - m_DH = DH_new (); + m_DH = DH_new(); + if (m_DH == 0) throw std::bad_alloc(); + + m_DH->p = BN_bin2bn(m_dh_prime, sizeof(m_dh_prime), NULL); + m_DH->g = BN_bin2bn(m_dh_generator, sizeof(m_dh_generator), NULL); + if (m_DH->p == 0 || m_DH->g == 0) + { + DH_free(m_DH); + throw std::bad_alloc(); + } - m_DH->p = BN_bin2bn (m_dh_prime, sizeof(m_dh_prime), NULL); - m_DH->g = BN_bin2bn (m_dh_generator, sizeof(m_dh_generator), NULL); m_DH->length = 160l; TORRENT_ASSERT(sizeof(m_dh_prime) == DH_size(m_DH)); - DH_generate_key (m_DH); // TODO Check != 0 - - TORRENT_ASSERT(m_DH->pub_key); + DH_generate_key(m_DH); + if (m_DH->pub_key == 0) + { + DH_free(m_DH); + throw std::bad_alloc(); + } // DH can generate key sizes that are smaller than the size of // P with exponentially decreasing probability, in which case @@ -76,27 +86,29 @@ namespace libtorrent { BN_bn2bin(m_DH->pub_key, (unsigned char*)m_dh_local_key); // TODO Check return value } - DH_key_exchange::~DH_key_exchange () + DH_key_exchange::~DH_key_exchange() { TORRENT_ASSERT(m_DH); - DH_free (m_DH); + DH_free(m_DH); } - char const* DH_key_exchange::get_local_key () const + char const* DH_key_exchange::get_local_key() const { return m_dh_local_key; } // compute shared secret given remote public key - void DH_key_exchange::compute_secret (char const* remote_pubkey) + void DH_key_exchange::compute_secret(char const* remote_pubkey) { TORRENT_ASSERT(remote_pubkey); BIGNUM* bn_remote_pubkey = BN_bin2bn ((unsigned char*)remote_pubkey, 96, NULL); + if (bn_remote_pubkey == 0) throw std::bad_alloc(); char dh_secret[96]; - int secret_size = DH_compute_key ( (unsigned char*)dh_secret, - bn_remote_pubkey, m_DH); // TODO Check for errors + int secret_size = DH_compute_key((unsigned char*)dh_secret + , bn_remote_pubkey, m_DH); + if (secret_size < 0 || secret_size > 96) throw std::bad_alloc(); if (secret_size != 96) { @@ -104,11 +116,10 @@ namespace libtorrent { std::fill(m_dh_secret, m_dh_secret + 96 - secret_size, 0); } std::copy(dh_secret, dh_secret + secret_size, m_dh_secret + 96 - secret_size); - - BN_free (bn_remote_pubkey); + BN_free(bn_remote_pubkey); } - char const* DH_key_exchange::get_secret () const + char const* DH_key_exchange::get_secret() const { return m_dh_secret; } @@ -129,3 +140,4 @@ namespace libtorrent { } // namespace libtorrent #endif // #ifndef TORRENT_DISABLE_ENCRYPTION + diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index ac61f042f..0e120e3c8 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -109,7 +109,7 @@ namespace libtorrent , m_reading(false) , m_prefer_whole_pieces(false) , m_request_large_blocks(false) - , m_non_prioritized(false) + , m_priority(1) , m_upload_limit(bandwidth_limit::inf) , m_download_limit(bandwidth_limit::inf) , m_peer_info(peerinfo) @@ -186,7 +186,7 @@ namespace libtorrent , m_reading(false) , m_prefer_whole_pieces(false) , m_request_large_blocks(false) - , m_non_prioritized(false) + , m_priority(1) , m_upload_limit(bandwidth_limit::inf) , m_download_limit(bandwidth_limit::inf) , m_peer_info(peerinfo) @@ -376,6 +376,7 @@ namespace libtorrent peer_connection::~peer_connection() { // INVARIANT_CHECK; + TORRENT_ASSERT(!m_in_constructor); TORRENT_ASSERT(m_disconnecting); #ifdef TORRENT_VERBOSE_LOGGING @@ -593,6 +594,7 @@ namespace libtorrent // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. t->attach_peer(this); + if (m_disconnecting) return; m_torrent = wpt; TORRENT_ASSERT(!m_torrent.expired()); @@ -1740,7 +1742,8 @@ namespace libtorrent INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); - TORRENT_ASSERT(t); + // this peer might be disconnecting + if (!t) return; TORRENT_ASSERT(t->valid_metadata()); @@ -1991,16 +1994,17 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + TORRENT_ASSERT(!m_in_constructor); boost::intrusive_ptr me(this); INVARIANT_CHECK; if (m_disconnecting) return; - m_disconnecting = true; - if (m_connecting) + if (m_connecting && m_connection_ticket >= 0) + { m_ses.m_half_open.done(m_connection_ticket); - - m_ses.m_io_service.post(boost::bind(&close_socket_ignore_error, m_socket)); + m_connection_ticket = -1; + } boost::shared_ptr t = m_torrent.lock(); @@ -2026,7 +2030,9 @@ namespace libtorrent m_torrent.reset(); } + m_disconnecting = true; m_ses.close_connection(me); + m_ses.m_io_service.post(boost::bind(&close_socket_ignore_error, m_socket)); } void peer_connection::set_upload_limit(int limit) @@ -2071,7 +2077,8 @@ namespace libtorrent bool peer_connection::on_local_network() const { - if (libtorrent::is_local(m_remote.address())) return true; + if (libtorrent::is_local(m_remote.address()) + || is_loopback(m_remote.address())) return true; return false; } @@ -2154,6 +2161,7 @@ namespace libtorrent } p.send_buffer_size = m_send_buffer.capacity(); + p.used_send_buffer = m_send_buffer.size(); } void peer_connection::cut_receive_buffer(int size, int packet_size) @@ -2178,7 +2186,7 @@ namespace libtorrent if (m_packet_size >= m_recv_pos) m_recv_buffer.resize(m_packet_size); } - void peer_connection::second_tick(float tick_interval) throw() + void peer_connection::second_tick(float tick_interval) { INVARIANT_CHECK; @@ -2355,8 +2363,7 @@ namespace libtorrent else if (buffer_size_watermark > 80 * 1024) buffer_size_watermark = 80 * 1024; while (!m_requests.empty() - && (send_buffer_size() + m_reading_bytes < buffer_size_watermark) - && !m_choked) + && (send_buffer_size() + m_reading_bytes < buffer_size_watermark)) { TORRENT_ASSERT(t->valid_metadata()); peer_request& r = m_requests.front(); @@ -2482,7 +2489,7 @@ namespace libtorrent // peers that we are not interested in are non-prioritized m_writing = true; t->request_bandwidth(upload_channel, self() - , !(is_interesting() && !has_peer_choked())); + , is_interesting() * 2); } return; } @@ -2535,7 +2542,7 @@ namespace libtorrent (*m_logger) << "req bandwidth [ " << download_channel << " ]\n"; #endif m_reading = true; - t->request_bandwidth(download_channel, self(), m_non_prioritized); + t->request_bandwidth(download_channel, self(), m_priority); } return; } @@ -3161,7 +3168,6 @@ namespace libtorrent bool peer_connection::is_seed() const { - INVARIANT_CHECK; // if m_num_pieces == 0, we probably don't have the // metadata yet. return m_num_pieces == (int)m_have_piece.size() && m_num_pieces > 0; diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 055bf38db..209e833c3 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -1068,7 +1068,7 @@ namespace detail } // don't allow more connections than the max setting - if (num_connections() > max_connections()) + if (num_connections() >= max_connections()) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << "number of connections limit exceeded (conns: " @@ -2015,6 +2015,10 @@ namespace detail // INVARIANT_CHECK; session_status s; + + s.up_bandwidth_queue = m_upload_channel.queue_size(); + s.down_bandwidth_queue = m_download_channel.queue_size(); + s.has_incoming_connections = m_incoming_connection; s.num_peers = (int)m_connections.size(); @@ -2437,6 +2441,7 @@ namespace detail boost::shared_ptr t = (*i)->associated_torrent().lock(); peer_connection* p = i->get(); + TORRENT_ASSERT(!p->is_disconnecting()); if (!p->is_choked()) ++unchokes; if (p->peer_info_struct() && p->peer_info_struct()->optimistically_unchoked) diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 07878e7ee..2f3c7e7f9 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -452,15 +452,19 @@ namespace libtorrent // the directory exists. if (file_iter->size == 0) { - file(m_save_path / file_iter->path, file::out); + try { + file(m_save_path / file_iter->path, file::out); + } catch (std::exception&) {} continue; } - if (allocate_files) - { - m_files.open_file(this, m_save_path / file_iter->path, file::in | file::out) - ->set_size(file_iter->size); - } + try { + if (allocate_files) + { + m_files.open_file(this, m_save_path / file_iter->path, file::in | file::out) + ->set_size(file_iter->size); + } + } catch (std::exception&) {} } // close files that were opened in write mode m_files.release(this); diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 12db1e59b..e04e0be71 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -2164,13 +2164,8 @@ namespace libtorrent throw protocol_error("reached connection limit"); } - TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); - peer_iterator ci = m_connections.insert(p).first; try { - // if new_connection throws, we have to remove the - // it from the list. - #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2179,15 +2174,14 @@ namespace libtorrent if (pp) p->add_extension(pp); } #endif - TORRENT_ASSERT(m_connections.find(p) == ci); - TORRENT_ASSERT(*ci == p); - m_policy.new_connection(**ci); + m_policy.new_connection(*p); } catch (std::exception& e) { - m_connections.erase(ci); throw; } + TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); + peer_iterator ci = m_connections.insert(p).first; TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint()); #ifndef NDEBUG @@ -2222,7 +2216,11 @@ namespace libtorrent #ifndef NDEBUG std::size_t size = m_connections.size(); #endif - p->disconnect(); + + if (p->is_disconnecting()) + m_connections.erase(m_connections.begin()); + else + p->disconnect(); TORRENT_ASSERT(m_connections.size() <= size); } } @@ -2234,7 +2232,7 @@ namespace libtorrent void torrent::request_bandwidth(int channel , boost::intrusive_ptr const& p - , bool non_prioritized) + , int priority) { TORRENT_ASSERT(m_bandwidth_limit[channel].throttle() > 0); TORRENT_ASSERT(p->max_assignable_bandwidth(channel) > 0); @@ -2243,16 +2241,20 @@ namespace libtorrent if (m_bandwidth_limit[channel].max_assignable() > 0) { - perform_bandwidth_request(channel, p, block_size, non_prioritized); + perform_bandwidth_request(channel, p, block_size, priority); } else { // skip forward in the queue until we find a prioritized peer // or hit the front of it. queue_t::reverse_iterator i = m_bandwidth_queue[channel].rbegin(); - while (i != m_bandwidth_queue[channel].rend() && i->non_prioritized) ++i; + while (i != m_bandwidth_queue[channel].rend() && priority > i->priority) + { + ++i->priority; + ++i; + } m_bandwidth_queue[channel].insert(i.base(), bw_queue_entry( - p, block_size, non_prioritized)); + p, block_size, priority)); } } @@ -2276,7 +2278,7 @@ namespace libtorrent continue; } perform_bandwidth_request(channel, qe.peer - , qe.max_block_size, qe.non_prioritized); + , qe.max_block_size, qe.priority); } m_bandwidth_queue[channel].insert(m_bandwidth_queue[channel].begin(), tmp.begin(), tmp.end()); } @@ -2284,10 +2286,10 @@ namespace libtorrent void torrent::perform_bandwidth_request(int channel , boost::intrusive_ptr const& p , int block_size - , bool non_prioritized) + , int priority) { m_ses.m_bandwidth_manager[channel]->request_bandwidth(p - , block_size, non_prioritized); + , block_size, priority); m_bandwidth_limit[channel].assign(block_size); } @@ -2620,6 +2622,16 @@ namespace libtorrent TORRENT_ASSERT(m_bandwidth_queue[0].size() <= m_connections.size()); TORRENT_ASSERT(m_bandwidth_queue[1].size() <= m_connections.size()); + for (int c = 0; c < 2; ++c) + { + queue_t::const_iterator j = m_bandwidth_queue[c].begin(); + if (j == m_bandwidth_queue[c].end()) continue; + ++j; + for (queue_t::const_iterator i = m_bandwidth_queue[c].begin() + , end(m_bandwidth_queue[c].end()); i != end && j != end; ++i, ++j) + TORRENT_ASSERT(i->priority >= j->priority); + } + int num_uploads = 0; std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) @@ -3063,6 +3075,9 @@ namespace libtorrent torrent_status st; + st.up_bandwidth_queue = (int)m_bandwidth_queue[peer_connection::upload_channel].size(); + st.down_bandwidth_queue = (int)m_bandwidth_queue[peer_connection::download_channel].size(); + st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end() , !boost::bind(&peer_connection::is_connecting, _1)); diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index 71ce2d430..54eefb8fc 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -73,7 +73,7 @@ namespace libtorrent // we can request more bytes at once request_large_blocks(true); // we only want left-over bandwidth - set_non_prioritized(true); + set_priority(0); shared_ptr tor = t.lock(); TORRENT_ASSERT(tor); int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); From 28a4883a72e7a8190375d31c530f072fc0501dc8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 01:00:39 +0000 Subject: [PATCH 0355/1009] Fix signals. --- deluge/core/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 1a7ecd9aa..ded1ea0a8 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -94,7 +94,6 @@ class Core( SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self, port): log.debug("Core init..") - #threading.Thread.__init__(self) self.client_address = None @@ -248,7 +247,10 @@ class Core( gobject.threads_init() self.loop = gobject.MainLoop() - self.loop.run() + try: + self.loop.run() + except KeyboardInterrupt: + self._shutdown() def _shutdown(self, data=None): """This is called by a thread from shutdown()""" From f327290e33970dc9fe4d4c51d9d2b0cfa44d26b3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 01:52:27 +0000 Subject: [PATCH 0356/1009] Fix preferences for removed preference. --- deluge/ui/gtkui/preferences.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index c556fa435..0baf6e0c4 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -357,8 +357,6 @@ class Preferences(component.Component): new_core_config["prioritize_first_last_pieces"] = \ self.glade.get_widget( "chk_prioritize_first_last_pieces").get_active() - new_gtkui_config["enable_files_dialog"] = \ - self.glade.get_widget("chk_enable_files_dialog").get_active() ## Network tab ## listen_ports = [] From 23840ce390945af2acf5cc1d63196a0c0145b8b1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 01:55:17 +0000 Subject: [PATCH 0357/1009] Destroy AddTorrentDialog window after clicking 'Add'. --- deluge/ui/gtkui/addtorrentdialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index a6b555b6f..3c10f1584 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -407,6 +407,7 @@ class AddTorrentDialog: row = self.torrent_liststore.iter_next(row) client.add_torrent_file(torrent_filenames, torrent_options) + self.dialog.destroy() def _on_button_apply_clicked(self, widget): log.debug("_on_button_apply_clicked") From 6485986294b663ec3ddb587df057729344c22226 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 02:00:42 +0000 Subject: [PATCH 0358/1009] Silence xmlrpclib about int exceeding XML-RPC limits. --- deluge/xmlrpclib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/xmlrpclib.py b/deluge/xmlrpclib.py index 5bd6171a4..96439eac2 100644 --- a/deluge/xmlrpclib.py +++ b/deluge/xmlrpclib.py @@ -642,8 +642,8 @@ class Marshaller: def dump_int(self, value, write): # in case ints are > 32 bits - if value > MAXINT or value < MININT: - raise OverflowError, "int exceeds XML-RPC limits" + #if value > MAXINT or value < MININT: + # raise OverflowError, "int exceeds XML-RPC limits" write("") write(str(value)) write("\n") From 5d1dbc98e54c32c7358096a3f689c72de5d79519 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 02:37:26 +0000 Subject: [PATCH 0359/1009] Give the Queue plugin some love. --- deluge/core/torrent.py | 2 +- deluge/plugins/queue/queue/gtkui.py | 2 +- deluge/ui/gtkui/torrentview.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 7b5f7f9f9..751da9d77 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -225,7 +225,7 @@ class Torrent: self.torrent_info = None # Create the desired status dictionary and return it - status_dict = {}.fromkeys(keys) + status_dict = {} if len(keys) == 0: status_dict = full_status diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index 60e93293e..fb6a7bf1a 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -116,4 +116,4 @@ class GtkUI(ui.UI): self.plugin.remove_preferences_page("Queue") def update(self): - self.plugin.update_torrent_view(["#"]) + self.plugin.update_torrent_view() diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index c1a40bb8e..da4ce2cd8 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -239,7 +239,7 @@ class TorrentView(listview.ListView, component.Component): # Remove duplicates from status_key list status_keys = list(set(status_keys)) - + # Create list of torrent_ids in need of status updates torrent_ids = [] row = self.liststore.get_iter_first() From d965adff82fc48acd58563a94a4c6f52c1c5450f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 02:49:25 +0000 Subject: [PATCH 0360/1009] Disconnect from Gnome session during shutdown. --- deluge/core/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index ded1ea0a8..05d2fef43 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -142,8 +142,8 @@ class Core( # Setup signals try: import gnome.ui - self.client = gnome.ui.Client() - self.client.connect("die", self._shutdown) + self.gnome_client = gnome.ui.Client() + self.gnome_client.connect("die", self._shutdown) except: pass @@ -262,6 +262,10 @@ class Core( del deluge.configmanager del self.session self.loop.quit() + try: + self.gnome_client.disconnect() + except: + pass # Exported Methods def export_ping(self): From bb86fefe66e565584e249dd5cb27b32f23f6b606 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 02:58:41 +0000 Subject: [PATCH 0361/1009] try/else pass for pygtk.require --- deluge/ui/gtkui/gtkui.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index a5158e845..41024e979 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -32,7 +32,10 @@ # statement from all source files in the program, then also delete it here. import pygtk -pygtk.require('2.0') +try: + pygtk.require('2.0') +else: + pass import gtk, gtk.glade import gettext import locale From 0b2dcd5dc21d592df7e1407c9fc9dbe3634fa446 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 03:00:11 +0000 Subject: [PATCH 0362/1009] i need to stop smoking weed --- deluge/ui/gtkui/gtkui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 41024e979..65d974a80 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -34,7 +34,7 @@ import pygtk try: pygtk.require('2.0') -else: +except: pass import gtk, gtk.glade import gettext From 3e2631213cd22bdc1532de5084e6875777dd18b2 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 20 Jan 2008 03:13:09 +0000 Subject: [PATCH 0363/1009] log pygtk.require warning --- deluge/ui/gtkui/gtkui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 65d974a80..3899e03e9 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -31,11 +31,12 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +from deluge.log import LOG as log import pygtk try: pygtk.require('2.0') except: - pass + log.warning("It is suggested that you upgrade your PyGTK to 2.10 or greater.") import gtk, gtk.glade import gettext import locale @@ -59,7 +60,6 @@ from dbusinterface import DbusInterface from queuedtorrents import QueuedTorrents from deluge.configmanager import ConfigManager import deluge.common -from deluge.log import LOG as log import deluge.configmanager DEFAULT_PREFS = { From f0b6833d174150d11e08f55c108647c6e160b2b8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 19:11:36 +0000 Subject: [PATCH 0364/1009] Reduce the amount of calls in the MultiCall batch by removing duplicates. Increase the MultiCall timer to 200ms. --- deluge/ui/client.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 64b108655..b2b61db79 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -134,7 +134,7 @@ class CoreProxy(gobject.GObject): self._core = None self._multi = None self._callbacks = [] - gobject.timeout_add(10, self.do_multicall) + gobject.timeout_add(200, self.do_multicall) def call(self, func, callback, *args): if self._core is None or self._multi is None: @@ -143,26 +143,31 @@ class CoreProxy(gobject.GObject): _func = getattr(self._multi, func) if _func is not None: - if len(args) == 0: - _func() + if (func, args) in self._multi.get_call_list(): + index = self._multi.get_call_list().index((func, args)) + self._callbacks[index].append(callback) else: - _func(*args) - self._callbacks.append(callback) - + if len(args) == 0: + _func() + else: + _func(*args) + + self._callbacks.append([callback]) def do_multicall(self, block=False): if len(self._callbacks) == 0: return True - + if self._multi is not None: try: try: for i, ret in enumerate(self._multi()): try: - if block == False: - gobject.idle_add(self._callbacks[i], ret) - else: - self._callbacks[i](ret) + for callback in self._callbacks[i]: + if block == False: + gobject.idle_add(callback, ret) + else: + callback(ret) except: pass except socket.error, e: From 80514ad8295ea82759bf846c6ea8c272e475a8ee Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 21:37:11 +0000 Subject: [PATCH 0365/1009] Fix last commit. Use 'config_value_changed' signal from core to get config value updates instead of polling every update for StatusBar and SystemTray. --- deluge/config.py | 13 +++++++++++-- deluge/core/core.py | 9 +++++++++ deluge/core/signalmanager.py | 8 ++++---- deluge/ui/gtkui/signals.py | 6 ++++++ deluge/ui/gtkui/statusbar.py | 26 ++++++++++++++++++++------ deluge/ui/gtkui/systemtray.py | 27 ++++++++++++++++++++++----- deluge/ui/signalreceiver.py | 4 ++-- deluge/xmlrpclib.py | 3 +++ 8 files changed, 77 insertions(+), 19 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index faec54a87..884151938 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -48,6 +48,7 @@ class Config: self.config = {} self.previous_config = {} self.set_functions = {} + self._change_callback = None # If defaults is not None then we need to use "defaults". if defaults != None: @@ -121,9 +122,13 @@ class Config: self.config[key] = value # Run the set_function for this key if any try: - self.set_functions[key](key, value) + gobject.idle_add(self.set_functions[key], key, value) except KeyError: pass + try: + gobject.idle_add(self._change_callback, key, value) + except: + pass def get(self, key): """Get the value of 'key'. If it is an invalid key then get() will @@ -145,7 +150,11 @@ class Config: def get_previous_config(self): """Returns the config prior to the last set()""" return self.previous_config - + + def register_change_callback(self, callback): + """Registers a callback that will be called when a value is changed""" + self._change_callback = callback + def register_set_function(self, key, function, apply_now=True): """Register a function to be run when a config value changes.""" log.debug("Registering function for %s key..", key) diff --git a/deluge/core/core.py b/deluge/core/core.py index 05d2fef43..eee01ca8b 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -223,6 +223,7 @@ class Core( self.config.register_set_function("max_upload_slots_global", self._on_set_max_upload_slots_global) + self.config.register_change_callback(self._on_config_value_change) # Start the AlertManager self.alerts = AlertManager(self.session) @@ -500,7 +501,15 @@ class Core( log.debug("torrent_all_resumed signal emitted") self.signals.emit("torrent_all_resumed", torrent_id) + def config_value_changed(self, key, value): + """Emitted when a config value has changed""" + log.debug("config_value_changed signal emitted") + self.signals.emit("config_value_changed", key, value) + # Config set functions + def _on_config_value_change(self, key, value): + self.config_value_changed(key, value) + def _on_set_torrentfiles_location(self, key, value): try: old = self.config.get_previous_config()["torrentfiles_location"] diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index c82d8ab5f..e292efed5 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -61,13 +61,13 @@ class SignalManager(component.Component): log.debug("Registering %s as a signal reciever..", uri) self.clients[uri] = xmlrpclib.ServerProxy(uri) - def emit(self, signal, data): + def emit(self, signal, *data): for client in self.clients.values(): - gobject.idle_add(self._emit, client, signal, data) + gobject.idle_add(self._emit, client, signal, *data) - def _emit(self, client, signal, data): + def _emit(self, client, signal, *data): try: - client.emit_signal(signal, data) + client.emit_signal(signal, *data) except (socket.error, Exception), e: log.warning("Unable to emit signal to client %s: %s", client, e) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 5cb91296a..286c93d27 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -57,6 +57,8 @@ class Signals(component.Component): self.torrent_all_paused) self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) + self.receiver.connect_to_signal("config_value_changed", + self.config_value_changed) def stop(self): self.receiver.shutdown() @@ -95,3 +97,7 @@ class Signals(component.Component): component.get("TorrentView").update() component.get("ToolBar").update_buttons("resumed") + def config_value_changed(self, key, value): + log.debug("config_value_changed signal received..") + component.get("StatusBar").config_value_changed(key, value) + component.get("SystemTray").config_value_changed(key, value) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 1b2cf4c22..3428ad27d 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -105,6 +105,11 @@ class StatusBar(component.Component): self.max_upload_speed = -1.0 self.upload_rate = 0.0 + self.config_value_changed_dict = { + "max_connections_global": self._on_max_connections_global, + "max_download_speed": self._on_max_download_speed, + "max_upload_speed": self._on_max_upload_speed + } # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() self.hbox.set_spacing(5) @@ -130,6 +135,14 @@ class StatusBar(component.Component): image=deluge.common.get_pixmap("seeding16.png")) self.hbox.pack_start( self.upload_item.get_eventbox(), expand=False, fill=False) + + # Get some config values + client.get_config_value( + self._on_max_connections_global, "max_connections_global") + client.get_config_value( + self._on_max_download_speed, "max_download_speed") + client.get_config_value( + self._on_max_upload_speed, "max_upload_speed") self.send_status_request() @@ -167,16 +180,17 @@ class StatusBar(component.Component): def send_status_request(self): # Sends an async request for data from the core - client.get_config_value( - self._on_max_connections_global, "max_connections_global") client.get_num_connections(self._on_get_num_connections) - client.get_config_value( - self._on_max_download_speed, "max_download_speed") client.get_download_rate(self._on_get_download_rate) - client.get_config_value( - self._on_max_upload_speed, "max_upload_speed") client.get_upload_rate(self._on_get_upload_rate) + def config_value_changed(self, key, value): + """This is called when we received a config_value_changed signal from + the core.""" + + if key in self.config_value_changed_dict.keys(): + self.config_value_changed_dict[key](value) + def _on_max_connections_global(self, max_connections): self.max_connections = max_connections diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 622182bdf..ac2fa6d79 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -65,7 +65,12 @@ class SystemTray(component.Component): self.download_rate = 0.0 self.max_upload_speed = -1.0 self.upload_rate = 0.0 - + + self.config_value_changed_dict = { + "max_download_speed": self._on_max_download_speed, + "max_upload_speed": self._on_max_upload_speed + } + def enable(self): """Enables the system tray icon.""" log.debug("Enabling the system tray icon..") @@ -117,6 +122,11 @@ class SystemTray(component.Component): # Build the bandwidth speed limit menus self.build_tray_bwsetsubmenu() + # Get some config values + client.get_config_value( + self._on_max_download_speed, "max_download_speed") + client.get_config_value( + self._on_max_upload_speed, "max_upload_speed") self.send_status_request() def stop(self): @@ -128,13 +138,16 @@ class SystemTray(component.Component): log.debug("Unable to hide system tray menu widgets: %s", e) def send_status_request(self): - client.get_config_value( - self._on_max_download_speed, "max_download_speed") client.get_download_rate(self._on_get_download_rate) - client.get_config_value( - self._on_max_upload_speed, "max_upload_speed") client.get_upload_rate(self._on_get_upload_rate) + def config_value_changed(self, key, value): + """This is called when we received a config_value_changed signal from + the core.""" + + if key in self.config_value_changed_dict.keys(): + self.config_value_changed_dict[key](value) + def _on_max_download_speed(self, max_download_speed): if self.max_download_speed != max_download_speed: self.max_download_speed = max_download_speed @@ -158,8 +171,12 @@ class SystemTray(component.Component): if max_download_speed == -1: max_download_speed = _("Unlimited") + else: + max_download_speed = "%s KiB/s" % (max_download_speed) if max_upload_speed == -1: max_upload_speed = _("Unlimited") + else: + max_upload_speed = "%s KiB/s" % (max_upload_speed) msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % ( _("Deluge Bittorrent Client"), _("Down Speed"), diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 606d8a6cd..c3c600059 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -108,13 +108,13 @@ class SignalReceiver( self._shutdown = False self.server_close() - def emit_signal(self, signal, data): + def emit_signal(self, signal, *data): """Exported method used by the core to emit a signal to the client""" try: if data != None: for callback in self.signals[signal]: try: - gobject.idle_add(callback, data) + gobject.idle_add(callback, *data) except: log.warning("Unable to call callback for signal %s", signal) diff --git a/deluge/xmlrpclib.py b/deluge/xmlrpclib.py index 96439eac2..3871c7f73 100644 --- a/deluge/xmlrpclib.py +++ b/deluge/xmlrpclib.py @@ -989,6 +989,9 @@ class MultiCall: return MultiCallIterator(self.__server.system.multicall(marshalled_list)) + def get_call_list(self): + return self.__call_list + # -------------------------------------------------------------------- # convenience functions From bcd2bcd2d45f1e695dfb1451552a52c806f52434 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 20 Jan 2008 23:47:57 +0000 Subject: [PATCH 0366/1009] SignalReceiver now chooses a random port to listen on. --- deluge/ui/gtkui/signals.py | 2 +- deluge/ui/signalreceiver.py | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 286c93d27..ac7037f79 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -44,7 +44,7 @@ class Signals(component.Component): remote = False if not client.is_localhost(): remote = True - self.receiver = SignalReceiver(6667, remote) + self.receiver = SignalReceiver(remote) self.receiver.start() self.receiver.connect_to_signal("torrent_added", self.torrent_added_signal) diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index c3c600059..6486189c4 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -33,6 +33,7 @@ import sys import socket +import random import gobject @@ -49,14 +50,13 @@ class SignalReceiver( ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): - def __init__(self, port, remote=False): + def __init__(self, remote=False): log.debug("SignalReceiver init..") gobject.threads_init() threading.Thread.__init__(self) # Set to true so that the receiver thread will exit self._shutdown = False - self.port = port # Daemonize the thread so it exits when the main program does self.setDaemon(True) @@ -66,12 +66,20 @@ class SignalReceiver( host = "" # Setup the xmlrpc server - try: - SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, (host, port), logRequests=False, allow_none=True) - except: - log.info("SignalReceiver already running or port not available..") - sys.exit(0) + server_ready = False + while not server_ready: + port = random.randint(40000, 65535) + try: + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( + self, (host, port), logRequests=False, allow_none=True) + except socket.error, e: + log.debug("Trying again with another port: %s", e) + except: + log.error("Could not start SignalReceiver XMLRPC server: %s", e) + sys.exit(0) + else: + self.port = port + server_ready = True self.signals = {} @@ -79,7 +87,7 @@ class SignalReceiver( self.register_function(self.emit_signal) # Register the signal receiver with the core - client.register_client(str(port)) + client.register_client(str(self.port)) def shutdown(self): """Shutdowns receiver thread""" From 799037a46bff58ca3d33cd574cc03d6887c9aab9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 01:13:56 +0000 Subject: [PATCH 0367/1009] Improve performance by only updating labels that have changed. --- deluge/ui/gtkui/torrentdetails.py | 142 +++++++++++++----------------- 1 file changed, 62 insertions(+), 80 deletions(-) diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 649361b24..f47c4b1e4 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -43,6 +43,18 @@ import deluge.ui.client as client import deluge.common from deluge.log import LOG as log +def fpeer_sized(first, second): + return "%s (%s)" % (deluge.common.fsize(first), deluge.common.fsize(second)) + +def fpeer_size_second(first, second): + return "%s (%s)" % (first, deluge.common.fsize(second)) + +def fratio(value): + return "%.3f" % value + +def fpcnt(value): + return "%.2f%%" % value + class TorrentDetails(component.Component): def __init__(self): component.Component.__init__(self, "TorrentDetails") @@ -61,25 +73,27 @@ class TorrentDetails(component.Component): self.is_visible = True # Get the labels we need to update. - self.progress_bar = glade.get_widget("progressbar") - self.name = glade.get_widget("summary_name") - self.total_size = glade.get_widget("summary_total_size") - self.num_files = glade.get_widget("summary_num_files") - self.pieces = glade.get_widget("summary_pieces") - self.availability = glade.get_widget("summary_availability") - self.total_downloaded = glade.get_widget("summary_total_downloaded") - self.total_uploaded = glade.get_widget("summary_total_uploaded") - self.download_speed = glade.get_widget("summary_download_speed") - self.upload_speed = glade.get_widget("summary_upload_speed") - self.seeders = glade.get_widget("summary_seeders") - self.peers = glade.get_widget("summary_peers") - self.percentage_done = glade.get_widget("summary_percentage_done") - self.share_ratio = glade.get_widget("summary_share_ratio") - self.tracker = glade.get_widget("summary_tracker") - self.tracker_status = glade.get_widget("summary_tracker_status") - self.next_announce = glade.get_widget("summary_next_announce") - self.eta = glade.get_widget("summary_eta") - self.torrent_path = glade.get_widget("summary_torrent_path") + # widgetname, modifier function, status keys + self.label_widgets = [ + (glade.get_widget("summary_name"), None, ("name",)), + (glade.get_widget("summary_total_size"), deluge.common.fsize, ("total_size",)), + (glade.get_widget("summary_num_files"), str, ("num_files",)), + (glade.get_widget("summary_pieces"), fpeer_size_second, ("num_pieces", "piece_length")), + (glade.get_widget("summary_availability"), fratio, ("distributed_copies",)), + (glade.get_widget("summary_total_downloaded"), fpeer_sized, ("total_done", "total_payload_download")), + (glade.get_widget("summary_total_uploaded"), fpeer_sized, ("total_uploaded", "total_payload_upload")), + (glade.get_widget("summary_download_speed"), deluge.common.fspeed, ("download_payload_rate",)), + (glade.get_widget("summary_upload_speed"), deluge.common.fspeed, ("upload_payload_rate",)), + (glade.get_widget("summary_seeders"), deluge.common.fpeer, ("num_seeds", "total_seeds")), + (glade.get_widget("summary_peers"), deluge.common.fpeer, ("num_peers", "total_peers")), + (glade.get_widget("summary_eta"), deluge.common.ftime, ("eta",)), + (glade.get_widget("summary_share_ratio"), fratio, ("ratio",)), + (glade.get_widget("summary_tracker"), None, ("tracker",)), + (glade.get_widget("summary_tracker_status"), None, ("tracker_status",)), + (glade.get_widget("summary_next_announce"), deluge.common.ftime, ("next_announce",)), + (glade.get_widget("summary_torrent_path"), None, ("save_path",)), + (glade.get_widget("progressbar"), fpcnt, ("progress",)) + ] def visible(self, visible): if visible: @@ -103,7 +117,6 @@ class TorrentDetails(component.Component): self.notebook.get_current_page() and \ self.notebook.get_property("visible"): # Get the first selected torrent - #selected = self.window.torrentview.get_selected_torrents() selected = component.get("TorrentView").get_selected_torrents() # Only use the first torrent in the list or return if None selected @@ -128,68 +141,37 @@ class TorrentDetails(component.Component): # Check to see if we got valid data from the core if status is None: return - - # We need to adjust the value core gives us for progress - try: - progress = status["progress"]/100 - - self.progress_bar.set_fraction(progress) - self.progress_bar.set_text(deluge.common.fpcnt(progress)) - - self.name.set_text(status["name"]) - self.total_size.set_text( - deluge.common.fsize(status["total_size"])) - self.num_files.set_text(str(status["num_files"])) - self.pieces.set_text("%s (%s)" % (status["num_pieces"], - deluge.common.fsize(status["piece_length"]))) - self.availability.set_text( - "%.3f" % status["distributed_copies"]) - self.total_downloaded.set_text("%s (%s)" % \ - (deluge.common.fsize(status["total_done"]), - deluge.common.fsize(status["total_payload_download"]))) - self.total_uploaded.set_text("%s (%s)" % \ - (deluge.common.fsize(status["total_uploaded"]), - deluge.common.fsize(status["total_payload_upload"]))) - self.download_speed.set_text( - deluge.common.fspeed(status["download_payload_rate"])) - self.upload_speed.set_text( - deluge.common.fspeed(status["upload_payload_rate"])) - self.seeders.set_text(deluge.common.fpeer(status["num_seeds"], - status["total_seeds"])) - self.peers.set_text(deluge.common.fpeer(status["num_peers"], - status["total_peers"])) - self.eta.set_text(deluge.common.ftime(status["eta"])) - self.share_ratio.set_text("%.3f" % status["ratio"]) - self.tracker.set_text(status["tracker"]) - self.tracker_status.set_text(status["tracker_status"]) - self.next_announce.set_text( - deluge.common.ftime(status["next_announce"])) - self.torrent_path.set_text(status["save_path"]) - except KeyError, e: - log.debug(e) + + # Update all the label widgets + for widget in self.label_widgets: + if widget[1] != None: + args = [] + try: + for key in widget[2]: + args.append(status[key]) + except Exception, e: + log.debug("Unable to get status value: %s", e) + continue + + txt = widget[1](*args) + else: + txt = status[widget[2][0]] + + if widget[0].get_text() != txt: + widget[0].set_text(txt) - - + # Do the progress bar because it's a special case (not a label) + w = self.window.main_glade.get_widget("progressbar") + fraction = status["progress"] / 100 + if w.get_fraction() != fraction: + w.set_fraction(fraction) + def clear(self): # Only update if this page is showing if self.notebook.page_num(self.details_tab) is \ self.notebook.get_current_page(): - self.name.set_text("") - self.total_size.set_text("") - self.num_files.set_text("") - self.pieces.set_text("") - self.availability.set_text("") - self.total_downloaded.set_text("") - self.total_uploaded.set_text("") - self.download_speed.set_text("") - self.upload_speed.set_text("") - self.seeders.set_text("") - self.peers.set_text("") - self.progress_bar.set_fraction(0.0) - self.progress_bar.set_text("") - self.share_ratio.set_text("") - self.tracker.set_text("") - self.tracker_status.set_text("") - self.next_announce.set_text("") - self.eta.set_text("") - self.torrent_path.set_text("") + + for widget in self.label_widgets: + widget[0].set_text("") + + self.window.main_glade.get_widget("progressbar").set_fraction(0.0) From f9d25287ea5e3b824c3c1f6cda3716bb5b9201a7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 01:57:25 +0000 Subject: [PATCH 0368/1009] Change Component to use individual timers for the components. This allows for different update intervals for the components. --- deluge/component.py | 43 +++++++++++++++++++------------ deluge/ui/gtkui/queuedtorrents.py | 2 +- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/deluge/component.py b/deluge/component.py index 85e4f3cb8..a06b20e22 100644 --- a/deluge/component.py +++ b/deluge/component.py @@ -40,9 +40,11 @@ COMPONENT_STATE = [ ] class Component: - def __init__(self, name, depend=None): + def __init__(self, name, interval=1000, depend=None): # Register with the ComponentRegistry register(name, self, depend) + self._interval = interval + self._timer = None self._state = COMPONENT_STATE.index("Stopped") def get_state(self): @@ -53,25 +55,36 @@ class Component: def _start(self): self._state = COMPONENT_STATE.index("Started") + if self._update(): + self._timer = gobject.timeout_add(self._interval, self._update) def stop(self): pass def _stop(self): + try: + gobject.source_remove(self._timer) + except: + pass self._state = COMPONENT_STATE.index("Stopped") def shutdown(self): pass - - def update(self): - pass + + def _update(self): + try: + self.update() + except AttributeError: + # This will stop the timer since the component doesn't have an + # update method. + return False + return True class ComponentRegistry: def __init__(self): self.components = {} self.depend = {} - self.update_timer = None def register(self, name, obj, depend): """Registers a component.. depend must be list or None""" @@ -84,15 +97,10 @@ class ComponentRegistry: """Returns a reference to the component 'name'""" return self.components[name] - def start(self, update_interval=1000): + def start(self): """Starts all components""" for component in self.components.keys(): self.start_component(component) - - # Start the update timer - self.update_timer = gobject.timeout_add(update_interval, self.update) - # Do an update right away - self.update() def start_component(self, name): """Starts a component""" @@ -107,15 +115,14 @@ class ComponentRegistry: self.components[name].start() self.components[name]._start() - def stop(self): """Stops all components""" for component in self.components.keys(): - log.debug("Stopping component %s..", component) - self.components[component].stop() - self.components[component]._stop() - # Stop the update timer - gobject.source_remove(self.update_timer) + if self.components[component].get_state != \ + COMPONENT_STATE.index("Stopped"): + log.debug("Stopping component %s..", component) + self.components[component].stop() + self.components[component]._stop() def update(self): """Updates all components""" @@ -130,6 +137,8 @@ class ComponentRegistry: def shutdown(self): """Shuts down all components. This should be called when the program exits so that components can do any necessary clean-up.""" + # Stop all components first + self.stop() for component in self.components.keys(): log.debug("Shutting down component %s..", component) try: diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index 349755886..847bd4e2f 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -45,7 +45,7 @@ from deluge.log import LOG as log class QueuedTorrents(component.Component): def __init__(self): - component.Component.__init__(self, "QueuedTorrents", ["StatusBar"]) + component.Component.__init__(self, "QueuedTorrents", depend=["StatusBar"]) self.queue = [] self.status_item = None From 1c9c765cb16565ffa42df46cc0495dd57808d21c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 01:58:19 +0000 Subject: [PATCH 0369/1009] Change update intervals of several components. Have StatusBar update the respected label when it receives a 'config_value_changed' signal. --- deluge/ui/gtkui/statusbar.py | 24 ++++++++++++++++++------ deluge/ui/gtkui/systemtray.py | 2 +- deluge/ui/gtkui/torrentdetails.py | 2 +- deluge/ui/gtkui/torrentview.py | 4 ++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 3428ad27d..53f58e7c2 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -93,7 +93,7 @@ class StatusBarItem: class StatusBar(component.Component): def __init__(self): - component.Component.__init__(self, "StatusBar") + component.Component.__init__(self, "StatusBar", interval=3000) self.window = component.get("MainWindow") self.statusbar = self.window.main_glade.get_widget("statusbar") @@ -193,23 +193,26 @@ class StatusBar(component.Component): def _on_max_connections_global(self, max_connections): self.max_connections = max_connections + self.update_connections_label() def _on_get_num_connections(self, num_connections): self.num_connections = num_connections def _on_max_download_speed(self, max_download_speed): self.max_download_speed = max_download_speed + self.update_download_label() def _on_get_download_rate(self, download_rate): self.download_rate = deluge.common.fsize(download_rate) def _on_max_upload_speed(self, max_upload_speed): self.max_upload_speed = max_upload_speed - + self.update_upload_label() + def _on_get_upload_rate(self, upload_rate): self.upload_rate = deluge.common.fsize(upload_rate) - - def update(self): + + def update_connections_label(self): # Set the max connections label max_connections = self.max_connections if max_connections < 0: @@ -217,7 +220,8 @@ class StatusBar(component.Component): self.connections_item.set_text("%s (%s)" % ( self.num_connections, max_connections)) - + + def update_download_label(self): # Set the download speed label max_download_speed = self.max_download_speed if max_download_speed < 0: @@ -227,7 +231,8 @@ class StatusBar(component.Component): self.download_item.set_text("%s/s (%s)" % ( self.download_rate, max_download_speed)) - + + def update_upload_label(self): # Set the upload speed label max_upload_speed = self.max_upload_speed if max_upload_speed < 0: @@ -238,5 +243,12 @@ class StatusBar(component.Component): self.upload_item.set_text("%s/s (%s)" % ( self.upload_rate, max_upload_speed)) + + def update(self): + # Update the labels + self.update_connections_label() + self.update_download_label() + self.update_upload_label() + # Send status request self.send_status_request() diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index ac2fa6d79..828d1771e 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -42,7 +42,7 @@ from deluge.log import LOG as log class SystemTray(component.Component): def __init__(self): - component.Component.__init__(self, "SystemTray") + component.Component.__init__(self, "SystemTray", interval=4000) self.window = component.get("MainWindow") self.config = ConfigManager("gtkui.conf") # List of widgets that need to be hidden when not connected to a host diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index f47c4b1e4..f9d63d894 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -57,7 +57,7 @@ def fpcnt(value): class TorrentDetails(component.Component): def __init__(self): - component.Component.__init__(self, "TorrentDetails") + component.Component.__init__(self, "TorrentDetails", interval=2000) self.window = component.get("MainWindow") glade = self.window.main_glade diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index da4ce2cd8..f3a641267 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -102,7 +102,7 @@ def cell_data_progress(column, cell, model, row, data): class TorrentView(listview.ListView, component.Component): """TorrentView handles the listing of torrents.""" def __init__(self): - component.Component.__init__(self, "TorrentView") + component.Component.__init__(self, "TorrentView", interval=2000) self.window = component.get("MainWindow") # Call the ListView constructor listview.ListView.__init__(self, @@ -113,7 +113,7 @@ class TorrentView(listview.ListView, component.Component): # This is where status updates are put self.status = {} - + # Register the columns menu with the listview so it gets updated # accordingly. self.register_checklist_menu( From fdb07b49294d1a5cd945d73f626313e5d8f1221b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 03:14:24 +0000 Subject: [PATCH 0370/1009] Try to include svn revision number in version if available. --- setup.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 85930fdba..ecafa1684 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,17 @@ import os python_version = platform.python_version()[0:3] +# Try to get SVN revision number to append to version +revision_string = "" +try: + stdout = os.popen("svn info") + for line in stdout: + if line.split(" ")[0] == "Revision:": + revision_string = "r%s" % line.split(" ")[1].strip() + break +except: + pass + # The libtorrent extension _extra_compile_args = [ "-Wno-missing-braces", @@ -162,7 +173,7 @@ for path in glob.glob('deluge/plugins/*'): setup( name = "deluge", fullname = "Deluge Bittorent Client", - version = "0.6.0.0", + version = "0.6.0.0" + revision_string, author = "Andrew Resch, Marcos Pinto", author_email = "andrewresch@gmail.com, markybob@dipconsultants.com", description = "GTK+ bittorrent client", From f16092400d12a67d00a3decfd3f1704c77c9ed03 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 05:56:45 +0000 Subject: [PATCH 0371/1009] Change get_version() to only return version number, not revision. Added get_revision() to get svn revision number of build. --- deluge/common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/common.py b/deluge/common.py index 21bd7c94d..5fb4f25cf 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -53,7 +53,10 @@ TORRENT_STATE = [ def get_version(): """Returns the program version from the egg metadata""" - return pkg_resources.require("Deluge")[0].version + return pkg_resources.require("Deluge")[0].version.split("r")[0] + +def get_revision(): + return pkg_resources.require("Deluge")[0].version.split("r")[1] def get_config_dir(filename=None): """ Returns the config path if no filename is specified From 2a637c0b12397e1b3633a38242c4653602d3e570 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 06:03:38 +0000 Subject: [PATCH 0372/1009] Fix preferences when clicking Apply or Ok. Add revision number to about dialog. --- deluge/ui/gtkui/aboutdialog.py | 2 +- deluge/ui/gtkui/preferences.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 845664f59..58abb3566 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -50,7 +50,7 @@ class AboutDialog: "aboutdialog") self.about.set_position(gtk.WIN_POS_CENTER) self.about.set_name("Deluge") - self.about.set_version(deluge.common.get_version()) + self.about.set_version(deluge.common.get_version() + "r" + deluge.common.get_revision()) self.about.set_authors(["Andrew Resch", "Marcos Pinto"]) self.about.set_artists(["Andrew Wedderburn", "Andrew Resch"]) self.about.set_translator_credits(_("translator-credits")) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 0baf6e0c4..46b3f760d 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -459,9 +459,6 @@ class Preferences(component.Component): # Re-show the dialog to make sure everything has been updated self.show() - # Update the UI - self.window.update() - def hide(self): self.pref_dialog.hide() From f08fcc68d33da8237f20c20e93470162c44d7077 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 06:37:30 +0000 Subject: [PATCH 0373/1009] Only call a callback once for a specific method. --- deluge/ui/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index b2b61db79..87a67986f 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -145,7 +145,8 @@ class CoreProxy(gobject.GObject): if _func is not None: if (func, args) in self._multi.get_call_list(): index = self._multi.get_call_list().index((func, args)) - self._callbacks[index].append(callback) + if callback not in self._callbacks[index]: + self._callbacks[index].append(callback) else: if len(args) == 0: _func() From 72ea2d0632bf8bd7cded5ed29dd2b5bd4ffda24f Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 21 Jan 2008 07:58:16 +0000 Subject: [PATCH 0374/1009] catch index error --- deluge/ui/gtkui/aboutdialog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 58abb3566..83f160e4c 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -50,7 +50,10 @@ class AboutDialog: "aboutdialog") self.about.set_position(gtk.WIN_POS_CENTER) self.about.set_name("Deluge") - self.about.set_version(deluge.common.get_version() + "r" + deluge.common.get_revision()) + try: + self.about.set_version(deluge.common.get_version() + "r" + deluge.common.get_revision()) + except IndexError: + self.about.set_version(deluge.common.get_version()) self.about.set_authors(["Andrew Resch", "Marcos Pinto"]) self.about.set_artists(["Andrew Wedderburn", "Andrew Resch"]) self.about.set_translator_credits(_("translator-credits")) From 0b807e894550e0cbd0462cda24c6d807aeb0ce2c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 09:13:36 +0000 Subject: [PATCH 0375/1009] Fix errors on quit. --- deluge/ui/gtkui/signals.py | 5 ++++- deluge/ui/gtkui/statusbar.py | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index ac7037f79..d7d701c23 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -61,7 +61,10 @@ class Signals(component.Component): self.config_value_changed) def stop(self): - self.receiver.shutdown() + try: + self.receiver.shutdown() + except: + pass def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 53f58e7c2..c3be51694 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -148,9 +148,13 @@ class StatusBar(component.Component): def stop(self): # When stopped, we just show the not connected thingy - self.remove_item(self.connections_item) - self.remove_item(self.download_item) - self.remove_item(self.upload_item) + try: + self.remove_item(self.connections_item) + self.remove_item(self.download_item) + self.remove_item(self.upload_item) + self.remove_item(self.not_connected_item) + except: + pass self.show_not_connected() def show_not_connected(self): From b950d2878104b19d49898651a72fdaaaeb1c39db Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 09:25:21 +0000 Subject: [PATCH 0376/1009] Handle setting the revision in a different way. --- deluge/common.py | 10 +++++++++- deluge/data/revision | 0 deluge/ui/gtkui/aboutdialog.py | 12 ++++++++---- setup.py | 9 +++++++-- 4 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 deluge/data/revision diff --git a/deluge/common.py b/deluge/common.py index 5fb4f25cf..9e8513546 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -56,7 +56,15 @@ def get_version(): return pkg_resources.require("Deluge")[0].version.split("r")[0] def get_revision(): - return pkg_resources.require("Deluge")[0].version.split("r")[1] + revision = "" + try: + f = open(pkg_resources.resource_filename("deluge", os.path.join("data", "revision"))) + revision = f.read() + f.close() + except IOError, e: + log.debug("Could not open revision file: %s", e) + + return revision def get_config_dir(filename=None): """ Returns the config path if no filename is specified diff --git a/deluge/data/revision b/deluge/data/revision new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 83f160e4c..a47f4f37e 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -50,10 +50,14 @@ class AboutDialog: "aboutdialog") self.about.set_position(gtk.WIN_POS_CENTER) self.about.set_name("Deluge") - try: - self.about.set_version(deluge.common.get_version() + "r" + deluge.common.get_revision()) - except IndexError: - self.about.set_version(deluge.common.get_version()) + + # Get the version and revision numbers + rev = deluge.common.get_revision() + version = deluge.common.get_version() + if rev != "": + version = version + "r" + rev + + self.about.set_version(version) self.about.set_authors(["Andrew Resch", "Marcos Pinto"]) self.about.set_artists(["Andrew Wedderburn", "Andrew Resch"]) self.about.set_translator_credits(_("translator-credits")) diff --git a/setup.py b/setup.py index ecafa1684..7e2dc9dbc 100644 --- a/setup.py +++ b/setup.py @@ -50,11 +50,15 @@ try: stdout = os.popen("svn info") for line in stdout: if line.split(" ")[0] == "Revision:": - revision_string = "r%s" % line.split(" ")[1].strip() + revision_string = line.split(" ")[1].strip() break + f = open("deluge/data/revision", "w") + f.write(revision_string) + f.close() except: pass + # The libtorrent extension _extra_compile_args = [ "-Wno-missing-braces", @@ -173,7 +177,7 @@ for path in glob.glob('deluge/plugins/*'): setup( name = "deluge", fullname = "Deluge Bittorent Client", - version = "0.6.0.0" + revision_string, + version = "0.6.0.0", author = "Andrew Resch, Marcos Pinto", author_email = "andrewresch@gmail.com, markybob@dipconsultants.com", description = "GTK+ bittorrent client", @@ -183,6 +187,7 @@ setup( package_data = {"deluge": ["ui/gtkui/glade/*.glade", "data/pixmaps/*.png", "data/pixmaps/deluge.svg", + "data/revision", "plugins/*.egg", "i18n/*.pot", "i18n/*/LC_MESSAGES/*.mo", From a461074da26c46a38fcf5ce4343b267bb3038f58 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 09:54:03 +0000 Subject: [PATCH 0377/1009] Catch signals and KeyboardInterrupt and shutdown properly in GtkUI. --- deluge/ui/gtkui/gtkui.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 3899e03e9..7f7e473f3 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -41,6 +41,7 @@ import gtk, gtk.glade import gettext import locale import pkg_resources +import signal import deluge.component as component import deluge.ui.client as client @@ -114,8 +115,18 @@ class GtkUI: pkg_resources.resource_filename( "deluge", "i18n")) + # Setup signals + try: + import gnome.ui + self.gnome_client = gnome.ui.Client() + self.gnome_client.connect("die", self.shutdown) + except: + pass + signal.signal(signal.SIGINT, self.shutdown) + signal.signal(signal.SIGTERM, self.shutdown) + # Make sure gtkui.conf has at least the defaults set - config = ConfigManager("gtkui.conf", DEFAULT_PREFS) + self.config = ConfigManager("gtkui.conf", DEFAULT_PREFS) # Start the Dbus Interface before anything else.. Just in case we are # already running. @@ -145,16 +156,22 @@ class GtkUI: # Show the connection manager self.connectionmanager = ConnectionManager() - if config["show_connection_manager_on_start"]: + if self.config["show_connection_manager_on_start"]: self.connectionmanager.show() # Start the gtk main loop - gtk.main() - + try: + gtk.main() + except KeyboardInterrupt: + self.shutdown() + else: + self.shutdown() + + def shutdown(self, data=None): log.debug("gtkui shutting down..") # Make sure the config is saved. - config.save() + self.config.save() # Shutdown all components component.shutdown() From 762f8a52affa09eddd44182173065e4155c3f153 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 10:02:30 +0000 Subject: [PATCH 0378/1009] Fix preferences when not connected to daemon. --- deluge/ui/gtkui/preferences.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 46b3f760d..e6102197d 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -95,6 +95,10 @@ class Preferences(component.Component): "on_toggle": self.on_toggle, "on_test_port_clicked": self.on_test_port_clicked }) + + # These get updated by requests done to the core + self.all_plugins = [] + self.enabled_plugins = [] def __del__(self): del self.gtkui_config @@ -212,6 +216,10 @@ class Preferences(component.Component): ("value", self.core_config["max_connections_per_torrent"]), "spin_max_upload_slots_per_torrent": \ ("value", self.core_config["max_upload_slots_per_torrent"]), + "spin_max_download_per_torrent": \ + ("value", self.core_config["max_download_speed_per_torrent"]), + "spin_max_upload_per_torrent": \ + ("value", self.core_config["max_upload_speed_per_torrent"]), "spin_daemon_port": \ ("value", self.core_config["daemon_port"]), "chk_allow_remote_connections": \ @@ -266,6 +274,8 @@ class Preferences(component.Component): "spin_max_upload_slots_global", "spin_max_connections_per_torrent", "spin_max_upload_slots_per_torrent", + "spin_max_download_per_torrent", + "spin_max_upload_per_torrent", "spin_daemon_port", "chk_allow_remote_connections" ] From 217d7eaa9005f95959cebad5fd7da389d0f1a974 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 21 Jan 2008 17:57:38 +0000 Subject: [PATCH 0379/1009] sync-wrapper for new async client --- .../webui/webui_plugin/config_tabs_deluge.py | 20 +++++++++ .../webui/webui_plugin/config_tabs_webui.py | 7 +++ deluge/ui/webui/webui_plugin/debugerror.py | 43 +++++++++++++++++-- deluge/ui/webui/webui_plugin/pages.py | 4 ++ .../templates/advanced/index.html | 12 +++++- deluge/ui/webui/webui_plugin/utils.py | 14 ++++-- .../ui/webui/webui_plugin/webserver_common.py | 41 ++++++++++++++++-- 7 files changed, 129 insertions(+), 12 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index 281fda2ea..e8489e79b 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -134,3 +134,23 @@ class Plugins(config.Form): raise forms.ValidationError("SAVE:TODO") config.register_block('deluge','plugins', Plugins) + + +class Queue(config.Form): + title = _("Queue") + info = _("queue-cfg not finished") + + queue_top = config.CheckBox(_("Queue new torrents to top")) + total_active = config.DelugeInt(_("Total active torrents")) + total_seeding = config.DelugeInt(_("Total active seeding")) + total_downloading = config.DelugeInt(_("Total active downloading")) + + queue_bottom = config.CheckBox(_("Queue completed torrents to bottom")) + stop_on_ratio = config.CheckBox(_("Stop seeding when ratio reaches")) + stop_ratio = config.DelugeInt(_("TODO:float-edit-box")) + remove_after_stop = config.CheckBox(_("Remve torrent when ratio reached")) + + def save(self, value): + raise forms.ValidationError("SAVE:TODO") + +config.register_block('plugins','queue', Queue) \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/config_tabs_webui.py b/deluge/ui/webui/webui_plugin/config_tabs_webui.py index 74845a947..5937ca699 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_webui.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_webui.py @@ -55,9 +55,16 @@ class Template(config.WebCfgForm): class Server(config.WebCfgForm): title = _("Server") + try: + import OpenSSL + except ImportError: + info = _("pyopenssl not installed, install this for https.") + port = forms.IntegerField(label = _("Port"),min_value=80) + use_https = config.CheckBox(_("Use https")) + def post_save(self): pass #raise forms.ValidationError( diff --git a/deluge/ui/webui/webui_plugin/debugerror.py b/deluge/ui/webui/webui_plugin/debugerror.py index 259dcac72..eed0b2cab 100644 --- a/deluge/ui/webui/webui_plugin/debugerror.py +++ b/deluge/ui/webui/webui_plugin/debugerror.py @@ -1,15 +1,42 @@ """ -pretty debug errors -(part of web.py) +adapted for deluge-webui: +-edit-box with traceback for cut+paste. +-pretty errors for well known exceptions. + +web.py : adapted from Django Copyright (c) 2005, the Lawrence Journal-World Used under the modified BSD license: http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 """ - __all__ = ["debugerror", "djangoerror"] +import utils +print utils +print dir(utils) + + +pretty_errors_str = { +"org.freedesktop.DBus.Error.ServiceUnknown": + """ Webui Lost the connection to deluge
      + Unable to reconnect, please restart deluge. + """, +"InvalidUniqueIDError:": + """ + this torrent was removed, + click here to go to the torrent-list + """ +} + + +pretty_errors_cls = { + type(utils.UnknownTorrentError):""" + this torrent was removed, + click here to go to the torrent-list + """ +} + import sys, urlparse, pprint from lib.webpy022.net import websafe from lib.webpy022.template import Template @@ -299,6 +326,16 @@ def djangoerror(): exception_message = 'no message' exception_type = exception_type.__name__ + """ + for err_str in pretty_errors: + if err_str in exception_message: + #from render import render + return render.error(pretty_errors[err_str]) + """ + if exception_type in pretty_errors_cls: + from render import render + return render.error(pretty_errors_cls[exception_type]) + version_info = ( "WebUi : rev." + ws.REVNO + "Python : " + str(sys.version) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 86761b05e..79474248b 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -39,6 +39,10 @@ import config_tabs_webui #auto registers import config_tabs_deluge #auto registers from config import config_page #import forms +# +from debugerror import deluge_debugerror +web.webapi.internalerror = deluge_debugerror +# import lib.webpy022 as web from lib.webpy022.http import seeother, url diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index bb84f9d52..5313ac739 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -1,6 +1,15 @@ $def with (torrent_list, all_torrents) $:render.header(_('Torrent list')) + + +
      -
      @@ -142,6 +150,6 @@ $:part_stats() - $:render.footer() + diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 78bcfa75b..036f9b5e9 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -48,10 +48,7 @@ from urlparse import urlparse from webserver_common import REVNO, VERSION, TORRENT_KEYS, STATE_MESSAGES from webserver_common import ws -from debugerror import deluge_debugerror -#init: -web.webapi.internalerror = deluge_debugerror debug_unicode = False #methods: @@ -146,6 +143,10 @@ def get_torrent_status(torrent_id): ws.log.warning('torrent_status:None key in status:%s' % key) + if status.tracker == 0: + #0.6 does not raise a decent error on non-existing torrent. + raise UnknownTorrentError(torrent_id) + status["id"] = torrent_id url = urlparse(status.tracker) @@ -253,3 +254,10 @@ def get_category_choosers(torrent_list): #/utils +class WebUiError(Exception): + """the message of these exceptions will be rendered in + render.error(e.message) in debugerror.py""" + pass + +class UnknownTorrentError(WebUiError): + pass diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 743ad20e2..ed257461c 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -108,6 +108,36 @@ CONFIG_DEFAULTS = { #/constants +class SyncProxyFunction(): + """ + helper class for SyncProxy + """ + def __init__(self,client, func_name): + self.func_name = func_name + self.client = client + + def __call__(self,*args,**kwargs): + sync_result = [] + + def callback( result): + sync_result.append(result) + func = getattr(self.client,self.func_name) + + func(callback,*args) + + self.client.force_call(block=True) + + return sync_result[0] + +class SyncProxy(object): + """acts like the old synchonous proxy""" + def __init__(self, client): + self.client = client + + def __getattr__(self, attr,*args,**kwargs): + return SyncProxyFunction(self.client, attr) + + class Ws: """ singleton @@ -141,11 +171,15 @@ class Ws: self.config = pickle.load(open(self.config_file)) def init_06(self, uri = 'http://localhost:58846'): - import deluge.ui.client as proxy + import deluge.ui.client as async_proxy from deluge.log import LOG as log self.log = log - proxy.set_core_uri(uri) - self.proxy = proxy + async_proxy.set_core_uri(uri) + self.async_proxy = async_proxy + + self.proxy = SyncProxy(self.async_proxy) + + #MONKEY PATCH, TODO->REMOVE!!! def add_torrent_filecontent(name , data_b64): @@ -174,7 +208,6 @@ class Ws: f.close() self.init_process() - self.proxy = proxy self.env = '0.6' def init_05(self): From c9a5caa3ec4bbed357cbf33120ad16c453080be0 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 21 Jan 2008 18:28:09 +0000 Subject: [PATCH 0380/1009] test perf improvement of multicall --- .../webui_plugin/tests/multicall_notepad.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 deluge/ui/webui/webui_plugin/tests/multicall_notepad.py diff --git a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py new file mode 100644 index 000000000..8085a574d --- /dev/null +++ b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py @@ -0,0 +1,84 @@ +""" +test multicall. +""" +import time + +from WebUi.webserver_common import ws +ws.init_06() +async_proxy = ws.async_proxy + + + +# +#A: translate this into 1 multicall: + +start = time.time() +stats = { + 'download_rate':ws.proxy.get_download_rate(), + 'upload_rate':ws.proxy.get_upload_rate(), + 'max_download':ws.proxy.get_config_value('max_download_speed'), + 'max_upload':ws.proxy.get_config_value('max_upload_speed'), + 'num_connections':ws.proxy.get_num_connections(), + 'max_num_connections':ws.proxy.get_config_value('max_connections_global') +} + +print "sync-stats:",time.time() - start + +print stats + +# +#pattern: +#map callback to a a dict-setter +def dict_cb(key,d): + def callback(result): + d[key] = result + return callback + +start = time.time() +d = {} +async_proxy.get_download_rate(dict_cb('download_rate',d)) +async_proxy.get_upload_rate(dict_cb('upload_rate',d)) +async_proxy.get_config_value(dict_cb('max_download',d),"max_download_speed") +async_proxy.get_config_value(dict_cb('max_download',d),"max_upload_speed") +async_proxy.get_num_connections(dict_cb("num_connections",d)) +async_proxy.get_config_value(dict_cb('max_num_connections',d),"max_connections_global") + + +async_proxy.force_call(block=True) + +print "Async-stats:",time.time() - start +print d + +# +#B: translate this to multicall: +# + +#old-sync: +start = time.time() +torrent_list = [ws.proxy.get_torrent_status(id,[]) + for id in ws.proxy.get_session_state() + ] +print "sync-list:",time.time() - start +print torrent_list + +#new async: + +start = time.time() +torrent_ids = ws.proxy.get_session_state() #Syc-api. +torrent_list = [] +for id in torrent_ids: + async_proxy.get_torrent_status(torrent_list.append, id, []) +async_proxy.force_call(block=True) +print "Async-list:",time.time() - start + +print torrent_list + + + + + + + + + + From 3807b372ffbcbf3b100db07d004e5a5c516bb11d Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 21 Jan 2008 19:31:36 +0000 Subject: [PATCH 0381/1009] async status-bar + pause/resume --- .../webui_plugin/tests/multicall_notepad.py | 9 ++-- deluge/ui/webui/webui_plugin/utils.py | 42 ++++++++++++++----- .../ui/webui/webui_plugin/webserver_common.py | 20 +++++---- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py index 8085a574d..f6682eb5c 100644 --- a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py +++ b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py @@ -27,7 +27,6 @@ print "sync-stats:",time.time() - start print stats # -#pattern: #map callback to a a dict-setter def dict_cb(key,d): def callback(result): @@ -39,11 +38,10 @@ d = {} async_proxy.get_download_rate(dict_cb('download_rate',d)) async_proxy.get_upload_rate(dict_cb('upload_rate',d)) async_proxy.get_config_value(dict_cb('max_download',d),"max_download_speed") -async_proxy.get_config_value(dict_cb('max_download',d),"max_upload_speed") +async_proxy.get_config_value(dict_cb('max_upload',d),"max_upload_speed") async_proxy.get_num_connections(dict_cb("num_connections",d)) async_proxy.get_config_value(dict_cb('max_num_connections',d),"max_connections_global") - async_proxy.force_call(block=True) print "Async-stats:",time.time() - start @@ -55,22 +53,25 @@ print d #old-sync: start = time.time() + torrent_list = [ws.proxy.get_torrent_status(id,[]) for id in ws.proxy.get_session_state() ] + print "sync-list:",time.time() - start print torrent_list #new async: start = time.time() + torrent_ids = ws.proxy.get_session_state() #Syc-api. torrent_list = [] for id in torrent_ids: async_proxy.get_torrent_status(torrent_list.append, id, []) async_proxy.force_call(block=True) -print "Async-list:",time.time() - start +print "Async-list:",time.time() - start print torrent_list diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 036f9b5e9..21a937c0a 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -51,6 +51,12 @@ from webserver_common import ws debug_unicode = False +#async-proxy: map callback to a a dict-setter +def dict_cb(key,d): + def callback(result): + d[key] = result + return callback + #methods: def setcookie(key, val): """add 30 days expires header for persistent cookies""" @@ -105,23 +111,37 @@ def getcookie(key, default = None): def get_stats(): - stats = Storage({ - 'download_rate':fspeed(ws.proxy.get_download_rate()), - 'upload_rate':fspeed(ws.proxy.get_upload_rate()), - 'max_download':ws.proxy.get_config_value('max_download_speed_bps'), - 'max_upload':ws.proxy.get_config_value('max_upload_speed_bps'), - 'num_connections':ws.proxy.get_num_connections(), - 'max_num_connections':ws.proxy.get_config_value('max_connections_global') - }) + stats = Storage() + + ws.async_proxy.get_download_rate(dict_cb('download_rate',stats)) + ws.async_proxy.get_upload_rate(dict_cb('upload_rate',stats)) + ws.async_proxy.get_config_value(dict_cb('max_download',stats) + ,"max_download_speed") + ws.async_proxy.get_config_value(dict_cb('max_upload',stats) + ,"max_upload_speed") + ws.async_proxy.get_num_connections(dict_cb("num_connections",stats)) + ws.async_proxy.get_config_value(dict_cb('max_num_connections',stats) + ,"max_connections_global") + + ws.async_proxy.force_call(block=True) + + ws.log.debug(str(stats)) + + stats.download_rate = fspeed(stats.download_rate) + stats.upload_rate = fspeed(stats.upload_rate) + #stats.max_upload = stats.max_upload + #stats.max_download = stats.max_download + + if stats.max_upload < 0: stats.max_upload = _("Unlimited") else: - stats.max_upload = fspeed(stats.max_upload) + stats.max_upload = "%s KiB/s" % stats.max_upload if stats.max_download < 0: stats.max_download = _("Unlimited") else: - stats.max_download = fspeed(stats.max_download) + stats.max_download = "%s KiB/s" % stats.max_download return stats @@ -167,7 +187,7 @@ def get_torrent_status(torrent_id): status.calc_state_str = "seeding" #action for torrent_pause - if status.user_paused: + if status.paused: #no user-paused in 0.6 !!! status.action = "start" else: status.action = "stop" diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index ed257461c..6dacf6190 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -42,6 +42,7 @@ import pickle import sys import base64 from md5 import md5 +import inspect random.seed() @@ -118,16 +119,21 @@ class SyncProxyFunction(): def __call__(self,*args,**kwargs): sync_result = [] - - def callback( result): - sync_result.append(result) func = getattr(self.client,self.func_name) - func(callback,*args) + if self.has_callback(func): + func(sync_result.append,*args, **kwargs) + self.client.force_call(block=True) + return sync_result[0] + else: + ws.log.debug('no-cb: %s' % self.func_name) + func(*args, **kwargs) + self.client.force_call(block=True) + return - self.client.force_call(block=True) - - return sync_result[0] + @staticmethod + def has_callback(func): + return "callback" in inspect.getargspec(func)[0] class SyncProxy(object): """acts like the old synchonous proxy""" From 83cc479898c84086c8f489f509c80a28eb1564f9 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 21 Jan 2008 19:51:55 +0000 Subject: [PATCH 0382/1009] split torrent_status for future async --- deluge/ui/webui/webui_plugin/utils.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 21a937c0a..a2db7c06c 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -145,14 +145,11 @@ def get_stats(): return stats - -def get_torrent_status(torrent_id): +def enhance_torrent_status(torrent_id,status): """ - helper method. - enhance ws.proxy.get_torrent_status with some extra data + in: raw torrent_status + out: enhanced torrent_staus """ - status = Storage(ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS)) - #add missing values for deluge 0.6: for key in TORRENT_KEYS: if not key in status: @@ -209,7 +206,7 @@ def get_torrent_status(torrent_id): }) #no non-unicode string may enter the templates. - #FIXED,l was a translation bug.. + #FIXED, was a translation bug.. if debug_unicode: for k, v in status.iteritems(): if (not isinstance(v, unicode)) and isinstance(v, str): @@ -219,6 +216,16 @@ def get_torrent_status(torrent_id): raise Exception('Non Unicode for key:%s' % (k, )) return status +def get_torrent_status(torrent_id): + """ + helper method. + enhance ws.proxy.get_torrent_status with some extra data + """ + status = Storage(ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS)) + + return enhance_torrent_status(torrent_id, status) + + def get_categories(torrent_list): trackers = [(torrent['category'] or 'unknown') for torrent in torrent_list] categories = {} From db97daeeeb0ee03d9fb681754844e279f73d9cb9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 22:13:39 +0000 Subject: [PATCH 0383/1009] Remove update() call as it is no longer necessary. --- deluge/ui/gtkui/mainwindow.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 912f240d1..13708d44c 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -132,8 +132,6 @@ class MainWindow(component.Component): else: log.debug("MainWindow is not minimized..") self.is_minimized = False - # Force UI update as we don't update it while minimized - self.update() return False def on_window_delete_event(self, widget, event): From 1a0718b4ac1519eb9ca82ac016cb49ce1f8e5284 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 22:24:31 +0000 Subject: [PATCH 0384/1009] Update 'save_path' on storage_moved_alert. --- deluge/core/core.py | 2 +- deluge/core/torrent.py | 3 +++ deluge/core/torrentmanager.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index eee01ca8b..24ff7ddd2 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -633,4 +633,4 @@ class Core( # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Emit torrent_paused signal - self.torrent_paused(torrent_id) + self.torrent_paused(torrent_id) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 751da9d77..126f5c61e 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -108,6 +108,9 @@ class Torrent: def set_prioritize_first_last(self, prioritize): pass + def set_save_path(self, save_path): + self.save_path = save_path + def get_state(self): """Returns the state of this torrent for saving to the session state""" status = self.handle.status() diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index edbed0eb4..67710aa2f 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -100,6 +100,8 @@ class TorrentManager(component.Component): self.alerts.register_handler("tracker_alert", self.on_alert_tracker) self.alerts.register_handler("tracker_warning_alert", self.on_alert_tracker_warning) + self.alerts.register_handler("storage_moved_alert", + self.on_alert_storage_moved) def start(self): # Try to load the state from file @@ -611,3 +613,13 @@ class TorrentManager(component.Component): self.torrents[torrent_id].set_tracker_status(tracker_status) except KeyError: log.debug("torrent_id doesn't exist.") + + def on_alert_storage_moved(self, alert): + log.debug("on_alert_storage_moved") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + try: + self.torrents[torrent_id].set_save_path(alert.handle.save_path()) + except KeyError: + log.debug("torrent_id doesn't exist.") + From 6cb3b2b9e01bf159e79de5ae8b8daea164d5cd1f Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 21 Jan 2008 22:38:58 +0000 Subject: [PATCH 0385/1009] add move torrent --- deluge/core/core.py | 5 +++++ deluge/core/torrentmanager.py | 9 +++++++++ deluge/ui/client.py | 5 +++++ deluge/ui/gtkui/glade/torrent_menu.glade | 17 +++++++++++++++++ deluge/ui/gtkui/gtkui.py | 5 +++-- deluge/ui/gtkui/menubar.py | 22 +++++++++++++++++++++- 6 files changed, 60 insertions(+), 3 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 24ff7ddd2..77295b733 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -351,6 +351,11 @@ class Core( if not self.torrents.pause(torrent_id): log.warning("Error pausing torrent %s", torrent_id) + def export_move_torrent(self, torrent_id, folder): + log.debug("Moving torrent %s to %s", torrent_id, folder) + if not self.torrents.move(torrent_id, folder): + log.warning("Error moving torrent %s to %s", torrent_id, folder) + def export_pause_all_torrents(self): """Pause all torrents in the session""" if not self.torrents.pause_all(): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 67710aa2f..268e57f84 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -335,6 +335,15 @@ class TorrentManager(component.Component): return True + def move(self, torrent_id, folder): + """Move a torrent""" + try: + self.torrents[torrent_id].handle.move_storage(folder) + except: + return False + + return True + def pause_all(self): """Pauses all torrents.. Returns a list of torrents paused.""" torrent_was_paused = False diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 87a67986f..c400dfd66 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -326,6 +326,11 @@ def pause_torrent(torrent_ids): for torrent_id in torrent_ids: get_core().call("pause_torrent", None, torrent_id) +def move_torrent(torrent_ids, folder): + """Pauses torrent_ids""" + for torrent_id in torrent_ids: + get_core().call("move_torrent", None, torrent_id, folder) + def pause_all_torrents(): """Pauses all torrents""" get_core().call("pause_all_torrents", None) diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 0e58f551c..1e046c273 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -141,5 +141,22 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Move _Torrent + True + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-save-as + 1 + + + + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 7f7e473f3..fb7638211 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -42,6 +42,7 @@ import gettext import locale import pkg_resources import signal +import os.path import deluge.component as component import deluge.ui.client as client @@ -93,7 +94,8 @@ DEFAULT_PREFS = { "autostart_localhost": False, "autoadd_queued": False, "autoadd_enable": False, - "autoadd_location": "" + "autoadd_location": "", + "choose_directory_dialog_path": os.path.expanduser("~") } class GtkUI: @@ -181,4 +183,3 @@ class GtkUI: def _on_no_core(self, data): component.stop() - diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 3413b8a7c..fa56070d4 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -96,7 +96,8 @@ class MenuBar(component.Component): self.on_menuitem_edittrackers_activate, "on_menuitem_remove_activate": self.on_menuitem_remove_activate, "on_menuitem_recheck_activate": self.on_menuitem_recheck_activate, - "on_menuitem_open_folder": self.on_menuitem_open_folder_activate + "on_menuitem_open_folder": self.on_menuitem_open_folder_activate, + "on_menuitem_move_activate": self.on_menuitem_move_activate }) self.change_sensitivity = [ @@ -211,6 +212,25 @@ class MenuBar(component.Component): def on_menuitem_open_folder_activate(self, data=None): log.debug("on_menuitem_open_folder") + def on_menuitem_move_activate(self, data=None): + log.debug("on_menuitem_move_activate") + from deluge.configmanager import ConfigManager + config = ConfigManager("gtkui.conf") + chooser = gtk.FileChooserDialog(_("Choose a directory to move files to"\ + ) , component.get("MainWindow").window, \ + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL, \ + gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) + if not common.windows_check(): + chooser.set_icon(common.get_logo(18)) + chooser.set_property("skip-taskbar-hint", True) + chooser.set_current_folder(config["choose_directory_dialog_path"]) + if chooser.run() == gtk.RESPONSE_OK: + result = chooser.get_filename() + config["choose_directory_dialog_path"] = result + client.move_torrent( + component.get("TorrentView").get_selected_torrents(), result) + chooser.destroy() + ## View Menu ## def on_menuitem_toolbar_toggled(self, value): log.debug("on_menuitem_toolbar_toggled") From 10d7f86f6f1e7e7dcf1fa7bfe5a2f1b725bbbfdf Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 21 Jan 2008 22:39:10 +0000 Subject: [PATCH 0386/1009] Reset revision file to "" after build and install. --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 7e2dc9dbc..5004c4b61 100644 --- a/setup.py +++ b/setup.py @@ -241,3 +241,10 @@ setup( deluge = deluge.main:start_ui deluged = deluge.main:start_daemon """) + +try: + f = open("deluge/data/revision", "w") + f.write("") + f.close() +except: + pass From 3aca2fa33ce64ade1e4fa8442bc7ff3585874520 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 21 Jan 2008 22:53:24 +0000 Subject: [PATCH 0387/1009] use get_default_download_dir() for move torrent pref --- deluge/ui/gtkui/gtkui.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index fb7638211..3baf7897d 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -42,7 +42,6 @@ import gettext import locale import pkg_resources import signal -import os.path import deluge.component as component import deluge.ui.client as client @@ -95,7 +94,7 @@ DEFAULT_PREFS = { "autoadd_queued": False, "autoadd_enable": False, "autoadd_location": "", - "choose_directory_dialog_path": os.path.expanduser("~") + "choose_directory_dialog_path": deluge.common.get_default_download_dir() } class GtkUI: From 6438cb01213969d42ba97ede543c7484f4e8dc53 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 00:41:05 +0000 Subject: [PATCH 0388/1009] fix edit trackers to be async --- deluge/ui/gtkui/edittrackersdialog.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index 0784e21f9..e7babaef2 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -86,12 +86,19 @@ class EditTrackersDialog: return # Get the trackers for this torrent - trackers = client.get_torrent_status(self.torrent_id, ["trackers"]) - for tracker in trackers["trackers"]: + + client.get_torrent_status(self._on_get_torrent_status, self.torrent_id,\ + ["trackers"]) + + def _on_get_torrent_status(self, status): + """Display trackers dialog""" + for tracker in status["trackers"]: self.add_tracker(tracker["tier"], tracker["url"]) self.dialog.show() - + + + def add_tracker(self, tier, url): """Adds a tracker to the list""" self.liststore.append([tier, url]) From 7ed08211437d5b7bfd2811570da09bd8a00b77fb Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 01:26:31 +0000 Subject: [PATCH 0389/1009] Tweak EditTrackersDialog async call. --- deluge/ui/gtkui/edittrackersdialog.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index e7babaef2..a6f3689cb 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -87,8 +87,9 @@ class EditTrackersDialog: # Get the trackers for this torrent - client.get_torrent_status(self._on_get_torrent_status, self.torrent_id,\ - ["trackers"]) + client.get_torrent_status( + self._on_get_torrent_status, self.torrent_id, ["trackers"]) + client.force_call() def _on_get_torrent_status(self, status): """Display trackers dialog""" @@ -97,8 +98,6 @@ class EditTrackersDialog: self.dialog.show() - - def add_tracker(self, tier, url): """Adds a tracker to the list""" self.liststore.append([tier, url]) From 9a36ef3c267a14421dcea74c0a341adf173be7f1 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 03:20:08 +0000 Subject: [PATCH 0390/1009] fix alignment ugliness in preferences --- .../ui/gtkui/glade/preferences_dialog.glade | 563 +++++++++--------- 1 file changed, 267 insertions(+), 296 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 3ff4cf106..3162844b4 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -116,34 +116,26 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Store all downloads in: Store all downloads in: 0 True True radio_ask_save - - - False - True - False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER Select A Folder - False 1 @@ -189,17 +181,13 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Save .torrent files to: + 0 + Save .torrent files in: - - False - False - @@ -209,8 +197,6 @@ Select A Folder - False - False 1 @@ -249,79 +235,60 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrent files that are placed in this folder. - Client Folder: - 0 - True - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - - - False - False - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Daemon Folder: - 0 - True - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - False - 1 - - - 1 + 1 + 2 + 1 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder + + + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon folder: + 0 + True + + + 1 + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Client folder: + 0 + True + + @@ -876,102 +843,106 @@ True - 2 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 4 + 4 + + + + + + True - 1 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 Inbound: + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Level: + - False + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Outbound: + + + 2 + 3 True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Forced Enabled Disabled - False - 5 - 1 - - - - - True - 1 - Outbound: - - - False - 2 - - - - - True - Forced -Enabled -Disabled - - - False - 5 - 3 - - - - - - - True - - - True - 0 - Level: - - - False + 1 + 2 True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Handshake Full Stream Either - False - 6 - 1 + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Forced +Enabled +Disabled + + + 3 + 4 - - 1 - True True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Prefer to encrypt the entire stream - True 0 True - False - 2 + 1 @@ -1076,71 +1047,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 + 3 + 4 GTK_FILL @@ -1165,43 +1105,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 3 - 4 + 2 + 3 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 1 2 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + @@ -1245,18 +1216,85 @@ Either 2 15 - + True True - The maximum number of connections per torrent. Set -1 for unlimited. + The maximum upload slots per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 + 1 + True True 1 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + True + True + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + 3 4 GTK_FILL @@ -1281,87 +1319,20 @@ Either - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - True + 1 True 1 2 - GTK_FILL - - - - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True - - - 1 - 2 - 1 - 2 + 3 + 4 GTK_FILL @@ -1610,15 +1581,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1648,38 +1637,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - From c747e4e125c52b321ae94b96167be9f0bbce68d9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 03:31:50 +0000 Subject: [PATCH 0391/1009] Revert last. --- .../ui/gtkui/glade/preferences_dialog.glade | 539 +++++++++--------- 1 file changed, 284 insertions(+), 255 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 3162844b4..3ff4cf106 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -116,26 +116,34 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Store all downloads in: Store all downloads in: 0 True True radio_ask_save + + + False + True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER Select A Folder + False 1 @@ -181,13 +189,17 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Save .torrent files in: + Save .torrent files to: + + False + False + @@ -197,6 +209,8 @@ Select A Folder + False + False 1 @@ -235,60 +249,79 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 2 - + True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrent files that are placed in this folder. + Client Folder: + 0 + True + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + False + False + 1 + + - - 1 - 2 - 1 - 2 - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - Select A Folder + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon Folder: + 0 + True + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + False + 1 + + - 1 - 2 + 1 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Daemon folder: - 0 - True - - - 1 - 2 - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Client folder: - 0 - True - - @@ -843,106 +876,102 @@ True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 4 - 4 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 + 1 Inbound: - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Level: - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Outbound: - - - 2 - 3 + False True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Forced Enabled Disabled - 1 - 2 + False + 5 + 1 - + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Handshake -Full Stream -Either + 1 + Outbound: - 1 - 2 - 1 - 2 + False + 2 True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Forced Enabled Disabled - 3 - 4 + False + 5 + 3 + + + True + + + True + 0 + Level: + + + False + + + + + True + Handshake +Full Stream +Either + + + False + 6 + 1 + + + + + 1 + + True True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Prefer to encrypt the entire stream + True 0 True - 1 + False + 2 @@ -1047,40 +1076,71 @@ Disabled 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 3 - 4 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 GTK_FILL @@ -1105,74 +1165,43 @@ Disabled - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 2 - 3 + 1 + 2 + 3 + 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 1 2 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - @@ -1216,85 +1245,18 @@ Disabled 2 15 - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - True + 1 True 1 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Speed (KiB/s): - - 3 4 GTK_FILL @@ -1319,20 +1281,87 @@ Disabled - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 + True True 1 2 - 3 - 4 + GTK_FILL + + + + + True + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + + + 1 + 2 + 1 + 2 GTK_FILL @@ -1581,33 +1610,15 @@ Disabled 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1637,20 +1648,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + From e7ecead4731812bebda892ee5fe2dee8fa7482f0 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 03:40:44 +0000 Subject: [PATCH 0392/1009] Tweak Downloads tab. --- .../ui/gtkui/glade/preferences_dialog.glade | 438 +++++++++--------- 1 file changed, 210 insertions(+), 228 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 3ff4cf106..74dd7149e 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -143,7 +143,6 @@ Select A Folder - False 1 @@ -209,8 +208,6 @@ Select A Folder - False - False 1 @@ -249,77 +246,62 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrent files that are placed in this folder. - Client Folder: - 0 - True - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - - - False - False - 1 - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Daemon Folder: - 0 - True - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - False - 1 - - + Automatically add torrent files that are placed in this folder. + Client Folder: + 0 + True - 1 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon Folder: + 0 + True + + + 1 + 2 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + 1 + 2 @@ -1076,71 +1058,40 @@ Either 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 + 3 + 4 GTK_FILL @@ -1165,43 +1116,74 @@ Either - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 3 - 4 + 2 + 3 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 1 2 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + @@ -1245,18 +1227,85 @@ Either 2 15 - + True True - The maximum number of connections per torrent. Set -1 for unlimited. + The maximum upload slots per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 + 1 + True True 1 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + True + True + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + 3 4 GTK_FILL @@ -1281,87 +1330,20 @@ Either - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - True + 1 True 1 2 - GTK_FILL - - - - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True - - - 1 - 2 - 1 - 2 + 3 + 4 GTK_FILL @@ -1610,15 +1592,33 @@ Either 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1648,38 +1648,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - From a3fc292dd6feca4a00fe35868622c1f552d5ee5f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 03:54:29 +0000 Subject: [PATCH 0393/1009] Tweak Network tab. --- .../ui/gtkui/glade/preferences_dialog.glade | 132 ++++++++++-------- 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 74dd7149e..4a9eccf8a 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -856,64 +856,21 @@ 2 12 - + True - 2 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 - + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True 1 Inbound: - - False - - - - True - Forced -Enabled -Disabled - - - False - 5 - 1 - - - - - True - 1 - Outbound: - - - False - 2 - - - - - True - Forced -Enabled -Disabled - - - False - 5 - 3 - - - - - - - True True @@ -921,9 +878,27 @@ Disabled Level: - False + 1 + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + Forced +Enabled +Disabled + + True @@ -932,27 +907,66 @@ Full Stream Either - False - 6 1 + False + False 1 - + True - True - Prefer to encrypt the entire stream - True - 0 - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + 1 + Outbound: + + + False + False + + + + + True + Forced +Enabled +Disabled + + + False + 1 + + + + + + + True + True + Encrypt entire stream + True + 0 + True + + + False + 3 + 1 + + - False 2 From 3a47731483acc67a9f251d3ba6d64e4362e04d25 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 03:57:46 +0000 Subject: [PATCH 0394/1009] Default to Random Ports. --- deluge/core/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 77295b733..1d124e2ff 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -64,7 +64,7 @@ DEFAULT_PREFS = { "torrentfiles_location": deluge.common.get_default_torrent_dir(), "plugins_location": deluge.common.get_default_plugin_dir(), "prioritize_first_last_pieces": False, - "random_port": False, + "random_port": True, "dht": False, "upnp": False, "natpmp": False, From e092619d64c7a29da74550070345c1ad0ca20f69 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 04:11:29 +0000 Subject: [PATCH 0395/1009] Remove hack to set revision to "". --- setup.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/setup.py b/setup.py index 5004c4b61..7e2dc9dbc 100644 --- a/setup.py +++ b/setup.py @@ -241,10 +241,3 @@ setup( deluge = deluge.main:start_ui deluged = deluge.main:start_daemon """) - -try: - f = open("deluge/data/revision", "w") - f.write("") - f.close() -except: - pass From b64393d096b3b79c4efb98fba0dd8fe0c0fc4304 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 04:27:33 +0000 Subject: [PATCH 0396/1009] Get some error output when trying to get svn revision. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7e2dc9dbc..405f25f8e 100644 --- a/setup.py +++ b/setup.py @@ -55,8 +55,8 @@ try: f = open("deluge/data/revision", "w") f.write(revision_string) f.close() -except: - pass +except Exception, e: + print "Unable to get or write revision: ", e # The libtorrent extension From 93e631d893919f5418c1bad4945e1bb8e517ebcf Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 05:20:20 +0000 Subject: [PATCH 0397/1009] Add hack to determine svn revision on Gentoo with svn ebuild. --- setup.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 405f25f8e..62fc69220 100644 --- a/setup.py +++ b/setup.py @@ -52,11 +52,19 @@ try: if line.split(" ")[0] == "Revision:": revision_string = line.split(" ")[1].strip() break + # Try to get the SVN revision on Gentoo systems + if revision_string == "": + stdout = os.popen("svn info /usr/portage/distfiles/svn-src/deluge/deluge-0.6") + for line in stdout: + if line.split(" ")[0] == "Revision:": + revision_string = line.split(" ")[1].strip() + break + f = open("deluge/data/revision", "w") f.write(revision_string) f.close() -except Exception, e: - print "Unable to get or write revision: ", e +except: + pass # The libtorrent extension @@ -241,3 +249,10 @@ setup( deluge = deluge.main:start_ui deluged = deluge.main:start_daemon """) + +try: + f = open("deluge/data/revision", "w") + f.write("") + f.close() +except: + pass From a4e7e9c41a667aaf2f134f6d12a94694e76f90d1 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 06:42:50 +0000 Subject: [PATCH 0398/1009] fix webui fuck up --- deluge/ui/webui/webui_plugin/webserver_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 6dacf6190..aa12ca6fb 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -109,7 +109,7 @@ CONFIG_DEFAULTS = { #/constants -class SyncProxyFunction(): +class SyncProxyFunction: """ helper class for SyncProxy """ From 4036ff4310d69a69aea009aadd539f2eaf76bf55 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 07:30:35 +0000 Subject: [PATCH 0399/1009] remove torrent file by default --- deluge/ui/gtkui/glade/remove_torrent_dialog.glade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade index 5b1fb7f2d..244d2f62b 100644 --- a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade @@ -1,6 +1,6 @@ - + 350 @@ -94,6 +94,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Delete .torrent file(s) 0 + True True From 4ba32dc4bf46759107ebbdc13d88b9cbaee3646f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 11:43:58 +0000 Subject: [PATCH 0400/1009] Select a torrent in the list if none are selected on torrent add. --- deluge/ui/gtkui/addtorrentdialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 3c10f1584..cdef46521 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -170,11 +170,16 @@ class AddTorrentDialog: }) name = "%s (%s)" % (info.name(), os.path.split(filename)[-1]) - self.torrent_liststore.append( + new_row = self.torrent_liststore.append( [str(info.info_hash()), name, filename]) self.files[str(info.info_hash())] = files self.infos[str(info.info_hash())] = info + (model, row) = self.listview_torrents.get_selection().get_selected() + if row == None: + self.listview_torrents.get_selection().select_iter(new_row) + + def _on_torrent_changed(self, treeselection): (model, row) = treeselection.get_selected() self.files_liststore.clear() From bbe31367d433b6cd2f6cca1071e6ef8cc3d1a4c3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 22 Jan 2008 11:45:56 +0000 Subject: [PATCH 0401/1009] Use os.popen2() instead of os.fork() to daemonize. --- deluge/main.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/deluge/main.py b/deluge/main.py index d74c61878..ffd382339 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -70,7 +70,8 @@ def start_daemon(): version=deluge.common.get_version()) parser.add_option("-p", "--port", dest="port", help="Port daemon will listen on", action="store", type="int") - + parser.add_option("-d", "--do-not-daemonize", dest="donot", + help="Do not daemonize", action="store_true", default=False) # Get the options and args from the OptionParser (options, args) = parser.parse_args() @@ -81,8 +82,13 @@ def start_daemon(): log.debug("args: %s", args) from deluge.core.daemon import Daemon - - log.info("Starting daemon..") - pid = os.fork() - if not pid: + + if options.donot: + log.info("Starting daemon..") Daemon(options.port) + else: + cmd = "deluged -d " + "".join(a for a in args) + if options.port != None: + cmd = cmd + " -p %s" % options.port + + os.popen2(cmd) From 9236e9bff2bf51c23babb5bb3cde5c053b647206 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 22 Jan 2008 18:03:48 +0000 Subject: [PATCH 0402/1009] fix eta --- deluge/ui/webui/webui_plugin/render.py | 1 + deluge/ui/webui/webui_plugin/templates/advanced/index.html | 2 +- deluge/ui/webui/webui_plugin/templates/deluge/index.html | 2 +- deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html | 4 ++-- deluge/ui/webui/webui_plugin/utils.py | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index 4e4ae502f..f6fca2f94 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -129,6 +129,7 @@ template.Template.globals.update({ 'self_url': self_url, 'fspeed': common.fspeed, 'fsize': common.fsize, + 'ftime':common.ftime, 'render': render, #for easy resuse of templates 'rev': 'rev.%s' % (REVNO, ), 'version': VERSION, diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index 5313ac739..4ef2f996d 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -114,7 +114,7 @@ $for torrent in torrent_list: $else:   - + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/index.html b/deluge/ui/webui/webui_plugin/templates/deluge/index.html index 4c29278fc..1bcda5173 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/index.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/index.html @@ -40,7 +40,7 @@ $for torrent in torrent_list: - + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html index e7f7f4bf2..bdc5859b8 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html @@ -45,7 +45,7 @@ $fspeed(torrent.download_rate) - + @@ -62,7 +62,7 @@ $fspeed(torrent.download_rate)
      $torrent.eta$ftime(torrent.eta) $("%.3f" % torrent.distributed_copies) $("%.3f" % torrent.ratio)
      $torrent.num_peers ($torrent.total_peers) $fspeed(torrent.download_rate) $fspeed(torrent.upload_rate)$torrent.eta$ftime(torrent.eta) $("%.3f" % torrent.distributed_copies) $("%.3f" % torrent.ratio)
      $torrent.num_peers ($torrent.total_peers )
      $_('ETA'):$torrent.eta
      $ftime(torrent.eta)
      $_('Availability'): $("%.3f" % torrent.distributed_copies)
      - + diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index a2db7c06c..b20cfad7a 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -37,7 +37,7 @@ from lib.webpy022 import changequery as self_url, template from lib.webpy022.utils import Storage from lib.webpy022.http import seeother, url -from deluge.common import fsize,fspeed +from deluge.common import fsize,fspeed,ftime import traceback import random From 22c07f8e10986d9394cd4ba1146793b26dc4de76 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 22 Jan 2008 18:19:30 +0000 Subject: [PATCH 0403/1009] use async api for torrent_list(index-page) --- deluge/ui/webui/webui_plugin/pages.py | 4 ++-- .../webui_plugin/tests/multicall_notepad.py | 9 +++++--- deluge/ui/webui/webui_plugin/utils.py | 21 ++++++++++++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 79474248b..92b2f9ba2 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -113,8 +113,8 @@ class index: @deco.auto_refreshed def GET(self, name): vars = web.input(sort=None, order=None ,filter=None , category=None) - torrent_list = [get_torrent_status(torrent_id) - for torrent_id in ws.proxy.get_session_state()] + + torrent_list = get_torrent_list() all_torrents = torrent_list[:] #filter-state diff --git a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py index f6682eb5c..501f48c45 100644 --- a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py +++ b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py @@ -66,13 +66,16 @@ print torrent_list start = time.time() torrent_ids = ws.proxy.get_session_state() #Syc-api. -torrent_list = [] +torrent_dict = {} for id in torrent_ids: - async_proxy.get_torrent_status(torrent_list.append, id, []) + async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, []) async_proxy.force_call(block=True) print "Async-list:",time.time() - start -print torrent_list +print torrent_dict + + +print sorted(torrent_list[0].keys()) diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index b20cfad7a..e84e0d2ab 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -150,6 +150,7 @@ def enhance_torrent_status(torrent_id,status): in: raw torrent_status out: enhanced torrent_staus """ + status = Storage(status) #add missing values for deluge 0.6: for key in TORRENT_KEYS: if not key in status: @@ -221,11 +222,29 @@ def get_torrent_status(torrent_id): helper method. enhance ws.proxy.get_torrent_status with some extra data """ - status = Storage(ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS)) + status = ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS) return enhance_torrent_status(torrent_id, status) + +def get_torrent_list(): + """ + uses async. + """ + torrent_ids = ws.proxy.get_session_state() #Syc-api. + torrent_dict = {} + for id in torrent_ids: + ws.async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, []) + ws.async_proxy.force_call(block=True) + + return [enhance_torrent_status(id, status) + for id, status in torrent_dict.iteritems()] + + + + + def get_categories(torrent_list): trackers = [(torrent['category'] or 'unknown') for torrent in torrent_list] categories = {} From a1ead133df47162d6db42e65e02bce2b7fe44874 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 20:59:30 +0000 Subject: [PATCH 0404/1009] fix searching for bad url in edit trackers --- deluge/ui/gtkui/edittrackersdialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index a6f3689cb..82e7fb6f7 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -176,8 +176,9 @@ class EditTrackersDialog: def on_button_add_ok_clicked(self, widget): log.debug("on_button_add_ok_clicked") + import re tracker = self.glade.get_widget("entry_tracker").get_text() - if tracker[:7] != "http://" or tracker[:8] != "https://": + if not re.search("https?://", tracker): # Bad url.. lets prepend http:// tracker = "http://" + tracker From e439b692527fd57ea51af2dc4f201636b68cdb25 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 21:01:10 +0000 Subject: [PATCH 0405/1009] tweak last --- deluge/ui/gtkui/edittrackersdialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index 82e7fb6f7..d09603918 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -176,9 +176,9 @@ class EditTrackersDialog: def on_button_add_ok_clicked(self, widget): log.debug("on_button_add_ok_clicked") - import re + from re import as re_search tracker = self.glade.get_widget("entry_tracker").get_text() - if not re.search("https?://", tracker): + if not re_search("https?://", tracker): # Bad url.. lets prepend http:// tracker = "http://" + tracker From e231621e12dde9c054c957fb2fd0af1425216fec Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 22 Jan 2008 21:01:35 +0000 Subject: [PATCH 0406/1009] oops --- deluge/ui/gtkui/edittrackersdialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index d09603918..1ad9033a6 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -176,7 +176,7 @@ class EditTrackersDialog: def on_button_add_ok_clicked(self, widget): log.debug("on_button_add_ok_clicked") - from re import as re_search + from re import search as re_search tracker = self.glade.get_widget("entry_tracker").get_text() if not re_search("https?://", tracker): # Bad url.. lets prepend http:// From 50e6b343c32f0287119b96854015280726d3e946 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 23 Jan 2008 01:53:20 +0000 Subject: [PATCH 0407/1009] Fix removing torrents options in WebUI. They were reversed. --- deluge/ui/webui/webui_plugin/pages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 92b2f9ba2..8ed068048 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -248,7 +248,7 @@ class torrent_delete: vars = web.input(data_also = None, torrent_also = None) data_also = bool(vars.data_also) torrent_also = bool(vars.torrent_also) - ws.proxy.remove_torrent(torrent_ids, data_also, torrent_also) + ws.proxy.remove_torrent(torrent_ids, torrent_also, data_also) do_redirect() class torrent_queue_up: From 468e51c72bbaa4aeaf20382337320568e93b9316 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 24 Jan 2008 01:31:48 +0000 Subject: [PATCH 0408/1009] Fix deregistering signal receivers in the core. --- deluge/core/signalmanager.py | 4 ++-- deluge/ui/signalreceiver.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index e292efed5..164d3d7b7 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -50,8 +50,8 @@ class SignalManager(component.Component): def deregister_client(self, address): """Deregisters a client""" log.debug("Deregistering %s as a signal reciever..", address) - for client in self.clients: - if client[:len(address)] == address: + for client in self.clients.keys(): + if client.split("//")[1].split(":")[0] == address: del self.clients[client] break diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 6486189c4..61b11054e 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -94,7 +94,8 @@ class SignalReceiver( self._shutdown = True # De-register with the daemon so it doesn't try to send us more signals client.deregister_client() - + client.force_call() + # Hacky.. sends a request to our local receiver to ensure that it # shutdowns.. This is because handle_request() is a blocking call. receiver = xmlrpclib.ServerProxy("http://localhost:" + str(self.port), From 9d5e60c42f735728b8fed8bddf92d3ff980ae2c4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 24 Jan 2008 01:33:37 +0000 Subject: [PATCH 0409/1009] Modify the Downloads tab of Preferences to include changes associated with the new Add Torrent dialog. --- .../ui/gtkui/glade/preferences_dialog.glade | 510 ++++++++++-------- deluge/ui/gtkui/gtkui.py | 4 +- deluge/ui/gtkui/preferences.py | 25 +- 3 files changed, 296 insertions(+), 243 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 4a9eccf8a..cde426e7d 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -8,7 +8,7 @@ Preferences GTK_WIN_POS_CENTER_ON_PARENT 510 - 520 + 525 True GDK_WINDOW_TYPE_HINT_DIALOG False @@ -88,6 +88,76 @@ 1 + + + True + 0 + GTK_SHADOW_NONE + + + True + 2 + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Show the Add Torrents dialog when adding files from an external source + Always show + 0 + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Bring the Add Torrents dialog to focus + Bring the dialog to focus + 0 + True + + + + + 1 + + + + + + + + + True + <b>Add Torrents Dialog</b> + True + + + label_item + + + + + False + False + 5 + 2 + + True @@ -100,52 +170,28 @@ 2 12 - + True - 2 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 - - True - Ask where to save each download - True - 0 - True - - - - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Store all downloads in: - Store all downloads in: - 0 - True - True - radio_ask_save - - - - False - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - Select A Folder - - - 1 - - + Default download location: + + + False + False + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + Select A Folder 1 @@ -170,7 +216,7 @@ False False 5 - 2 + 3 @@ -231,7 +277,7 @@ False False 5 - 3 + 4 @@ -252,17 +298,27 @@ 2 2 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + 1 + 2 + + + + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrent files that are placed in this folder. - Client Folder: - 0 - True - GTK_FILL + 1 + 2 + 1 + 2 @@ -281,27 +337,17 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrent files that are placed in this folder. + Client Folder: + 0 + True - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - - - 1 - 2 + GTK_FILL @@ -324,7 +370,7 @@ False False 5 - 4 + 5 @@ -395,7 +441,7 @@ False False 5 - 5 + 6 @@ -431,7 +477,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Set Private by default + Set private flag by default 0 True @@ -446,7 +492,7 @@ True - <b>Torrents</b> + <b>Options</b> True @@ -458,7 +504,7 @@ False False 5 - 6 + 7 @@ -1072,40 +1118,71 @@ Disabled 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 3 - 4 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 GTK_FILL @@ -1130,74 +1207,43 @@ Disabled - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 2 - 3 + 1 + 2 + 3 + 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 1 2 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - @@ -1241,85 +1287,18 @@ Disabled 2 15 - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - True + 1 True 1 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Speed (KiB/s): - - 3 4 GTK_FILL @@ -1344,20 +1323,87 @@ Disabled - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 + True True 1 2 - 3 - 4 + GTK_FILL + + + + + True + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + + + 1 + 2 + 1 + 2 GTK_FILL @@ -1606,33 +1652,15 @@ Disabled 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1662,20 +1690,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 3baf7897d..633839773 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -65,8 +65,8 @@ import deluge.configmanager DEFAULT_PREFS = { "config_location": deluge.common.get_config_dir(), - "interactive_add": False, - "enable_files_dialog": False, + "interactive_add": True, + "focus_add_dialog": True, "enable_system_tray": True, "close_to_tray": True, "start_in_tray": False, diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index e6102197d..6499ac5c1 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -191,6 +191,8 @@ class Preferences(component.Component): "chk_prioritize_first_last_pieces": \ ("active", self.core_config["prioritize_first_last_pieces"]), + "chk_private": \ + ("active", self.core_config["default_private"]), "spin_port_min": ("value", self.core_config["listen_ports"][0]), "spin_port_max": ("value", self.core_config["listen_ports"][1]), "active_port_label": ("text", str(self.active_port)), @@ -255,6 +257,7 @@ class Preferences(component.Component): "radio_compact_allocation", "radio_full_allocation", "chk_prioritize_first_last_pieces", + "chk_private", "spin_port_min", "spin_port_max", "active_port_label", @@ -288,10 +291,10 @@ class Preferences(component.Component): widget.set_sensitive(False) ## Downloads tab ## - self.glade.get_widget("radio_ask_save").set_active( + self.glade.get_widget("chk_show_dialog").set_active( self.gtkui_config["interactive_add"]) - self.glade.get_widget("radio_save_all_to").set_active( - not self.gtkui_config["interactive_add"]) + self.glade.get_widget("chk_focus_dialog").set_active( + self.gtkui_config["focus_add_dialog"]) self.glade.get_widget("chk_autoadd_folder").set_active( self.gtkui_config["autoadd_enable"]) self.glade.get_widget("autoadd_folder_button").set_filename( @@ -349,7 +352,9 @@ class Preferences(component.Component): ## Downloads tab ## new_gtkui_config["interactive_add"] = \ - self.glade.get_widget("radio_ask_save").get_active() + self.glade.get_widget("chk_show_dialog").get_active() + new_gtkui_config["focus_add_dialog"] = \ + self.glade.get_widget("chk_focus_dialog").get_active() new_core_config["download_location"] = \ self.glade.get_widget("download_path_button").get_filename() new_core_config["torrentfiles_location"] = \ @@ -367,6 +372,8 @@ class Preferences(component.Component): new_core_config["prioritize_first_last_pieces"] = \ self.glade.get_widget( "chk_prioritize_first_last_pieces").get_active() + new_core_config["default_private"] = \ + self.glade.get_widget("chk_private").get_active() ## Network tab ## listen_ports = [] @@ -479,11 +486,11 @@ class Preferences(component.Component): def on_toggle(self, widget): """Handles widget sensitivity based on radio/check button values.""" value = widget.get_active() - # Disable the download path button if user wants to pick where each - # new torrent is saved. - if widget == self.glade.get_widget("radio_save_all_to"): - self.glade.get_widget("download_path_button").set_sensitive(value) - + + # Disable the focus dialog checkbox if the show dialog isn't active. + if widget == self.glade.get_widget("chk_show_dialog"): + self.glade.get_widget("chk_focus_dialog").set_sensitive(value) + # Disable the port spinners if random ports is selected. if widget == self.glade.get_widget("chk_random_port"): log.debug("chk_random_port set to: %s", value) From 10cced15a34e1c02e32f3bb0c7d13117bcbe9b65 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 24 Jan 2008 01:56:25 +0000 Subject: [PATCH 0410/1009] Fix GtkWarning in StatusBar when disconnecting from daemon. --- deluge/ui/gtkui/statusbar.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index c3be51694..cacaedc07 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -117,6 +117,9 @@ class StatusBar(component.Component): frame.remove(frame.get_children()[0]) frame.add(self.hbox) self.statusbar.show_all() + # Create the not connected item + self.not_connected_item = StatusBarItem( + stock=gtk.STOCK_STOP, text=_("Not Connected")) # Show the not connected status bar self.show_not_connected() @@ -153,13 +156,11 @@ class StatusBar(component.Component): self.remove_item(self.download_item) self.remove_item(self.upload_item) self.remove_item(self.not_connected_item) - except: - pass - self.show_not_connected() + except Exception, e: + log.debug("Unable to remove StatusBar item: %s", e) + self.show_not_connected() def show_not_connected(self): - self.not_connected_item = StatusBarItem( - stock=gtk.STOCK_STOP, text=_("Not Connected")) self.hbox.pack_start( self.not_connected_item.get_eventbox(), expand=False, fill=False) @@ -172,10 +173,11 @@ class StatusBar(component.Component): def remove_item(self, item): """Removes an item from the statusbar""" - try: - self.hbox.remove(item.get_eventbox()) - except Exception, e: - log.debug("Unable to remove widget: %s", e) + if item.get_eventbox() in self.hbox.get_children(): + try: + self.hbox.remove(item.get_eventbox()) + except Exception, e: + log.debug("Unable to remove widget: %s", e) def clear_statusbar(self): def remove(child): From 5ab95814e771e5495f4d2f6a9f74add3caf139e6 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 24 Jan 2008 01:59:36 +0000 Subject: [PATCH 0411/1009] Adjust Preferences dialog height a bit. --- deluge/ui/gtkui/glade/preferences_dialog.glade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index cde426e7d..78ecbeb5e 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -8,7 +8,7 @@ Preferences GTK_WIN_POS_CENTER_ON_PARENT 510 - 525 + 530 True GDK_WINDOW_TYPE_HINT_DIALOG False From 3ccfb4e03e84f0fd75342d8d3216adc34455a2e5 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 24 Jan 2008 03:51:15 +0000 Subject: [PATCH 0412/1009] Fix get_selected_torrents() when removing multiple torrents. --- deluge/ui/gtkui/torrentview.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index f3a641267..3b920da28 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -366,7 +366,7 @@ class TorrentView(listview.ListView, component.Component): self.update() break row = self.liststore.iter_next(row) - + def get_selected_torrent(self): """Returns a torrent_id or None. If multiple torrents are selected, it will return the torrent_id of the first one.""" @@ -385,15 +385,25 @@ class TorrentView(listview.ListView, component.Component): return [] try: for path in paths: - torrent_ids.append( - self.model_filter.get_value( - self.model_filter.get_iter(path), 0)) - - if len(torrent_ids) is 0: + try: + row = self.model_filter.get_iter(path) + except Exception, e: + log.debug("Unable to get iter from path: %s", e) + + child_row = self.model_filter.convert_iter_to_child_iter(None, row) + child_row = self.model_filter.get_model().convert_iter_to_child_iter(child_row) + if self.liststore.iter_is_valid(child_row): + try: + value = self.liststore.get_value(child_row, 0) + except Exception, e: + log.debug("Unable to get value from row: %s", e) + else: + torrent_ids.append(value) + if len(torrent_ids) == 0: return [] return torrent_ids - except ValueError: + except ValueError, TypeError: return [] def get_torrent_status(self, torrent_id): From d5ab09e11f9472077ac4846e51decf54adf6ead8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 25 Jan 2008 00:28:07 +0000 Subject: [PATCH 0413/1009] Remove main window update() attempts in SystemTray. Force the add_torrent call to happen right away. --- deluge/ui/gtkui/addtorrentdialog.py | 1 + deluge/ui/gtkui/sidebar.py | 2 +- deluge/ui/gtkui/systemtray.py | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index cdef46521..6cfca8a6a 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -412,6 +412,7 @@ class AddTorrentDialog: row = self.torrent_liststore.iter_next(row) client.add_torrent_file(torrent_filenames, torrent_options) + client.force_call() self.dialog.destroy() def _on_button_apply_clicked(self, widget): diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 3e67c1d53..901c2a22d 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -111,4 +111,4 @@ class SideBar(component.Component): if value == "Paused": component.get("TorrentView").set_filter("state", deluge.common.TORRENT_STATE.index("Paused")) - + diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 828d1771e..63f89e281 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -237,15 +237,11 @@ class SystemTray(component.Component): self.window.hide() else: self.window.present() - # Force UI update as we don't update it while minimized - self.window.update() else: if self.config["lock_tray"] == True: self.unlock_tray("mainwinshow") else: self.window.show() - # Force UI update as we don't update it while in tray - self.window.update() def on_tray_popup(self, status_icon, button, activate_time): """Called when the tray icon is right clicked.""" From 4e42c2744dc2ae6262e26fbd2ce56e46b9b09844 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 25 Jan 2008 00:31:19 +0000 Subject: [PATCH 0414/1009] Remove another window.update(). --- deluge/ui/gtkui/systemtray.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 63f89e281..f2fa2c593 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -409,8 +409,6 @@ class SystemTray(component.Component): self.config[ui_key].pop() # Re-build the menu self.build_tray_bwsetsubmenu() - # Update the UI - self.window.update() def unlock_tray(self, comingnext, is_showing_dlg=[False]): import sha From 073b62408fde10d5604769acdf3b34497a18ae6f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 25 Jan 2008 02:28:34 +0000 Subject: [PATCH 0415/1009] Show revision number in version if available. --- deluge/main.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/deluge/main.py b/deluge/main.py index ffd382339..954556d02 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -55,7 +55,11 @@ def start_ui(): from deluge.log import LOG as log - log.info("Deluge ui %s", deluge.common.get_version()) + version = deluge.common.get_version() + if deluge.common.get_revision() != "": + version = version + "r" + deluge.common.get_revision() + + log.info("Deluge ui %s", version) log.debug("options: %s", options) log.debug("args: %s", args) @@ -76,8 +80,12 @@ def start_daemon(): (options, args) = parser.parse_args() from deluge.log import LOG as log - - log.info("Deluge daemon %s", deluge.common.get_version()) + + version = deluge.common.get_version() + if deluge.common.get_revision() != "": + version = version + "r" + deluge.common.get_revision() + + log.info("Deluge daemon %s", version) log.debug("options: %s", options) log.debug("args: %s", args) From c1710ca0f4d5bc5b33b61169cbdf06791c9c0624 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 25 Jan 2008 02:55:48 +0000 Subject: [PATCH 0416/1009] Add the ability to set file priorities for torrents in the core. The Add Torrent dialog now sets proper file priorities based on user input. --- deluge/core/torrent.py | 6 ++++- deluge/core/torrentmanager.py | 9 +++++-- deluge/ui/gtkui/addtorrentdialog.py | 39 ++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 126f5c61e..0b0aba982 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -110,7 +110,11 @@ class Torrent: def set_save_path(self, save_path): self.save_path = save_path - + + def set_file_priorities(self, file_priorities): + self.file_priorities = file_priorities + self.handle.prioritize_files(file_priorities) + def get_state(self): """Returns the state of this torrent for saving to the session state""" status = self.handle.status() diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 268e57f84..c254a6a08 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -134,7 +134,7 @@ class TorrentManager(component.Component): trackers=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) - + log.debug("options: %s", options) # Make sure 'filename' is a python string filename = str(filename) @@ -214,7 +214,7 @@ class TorrentManager(component.Component): if not handle or not handle.is_valid(): # The torrent was not added to the session - return None + return None # Create a Torrent object torrent = Torrent(filename, handle, compact, @@ -237,6 +237,11 @@ class TorrentManager(component.Component): options["prioritize_first_last_pieces"]) torrent.set_private_flag(options["default_private"]) + if options.has_key("file_priorities"): + if options["file_priorities"] != None: + log.debug("set file priorities: %s", options["file_priorities"]) + torrent.set_file_priorities(options["file_priorities"]) + # Resume the torrent if needed if paused == False: handle.resume() diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 6cfca8a6a..8f23becc7 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -262,6 +262,17 @@ class AddTorrentDialog: self.options[torrent_id] = options + # Save the file priorities + row = self.files_liststore.get_iter_first() + files_priorities = [] + while row != None: + download = self.files_liststore.get_value(row, 0) + files_priorities.append(download) + row = self.files_liststore.iter_next(row) + + for i, file_dict in enumerate(self.files[torrent_id]): + file_dict["download"] = files_priorities[i] + def set_default_options(self): # FIXME: does not account for remote core self.glade.get_widget("button_location").set_current_folder( @@ -282,10 +293,26 @@ class AddTorrentDialog: self.core_config["prioritize_first_last_pieces"]) self.glade.get_widget("chk_private").set_active( self.core_config["default_private"]) - + + def get_file_priorities(self, torrent_id): + # A list of priorities + files_list = [] + + for file_dict in self.files[torrent_id]: + if file_dict["download"] == False: + files_list.append(0) + else: + files_list.append(1) + + return files_list + def _on_file_toggled(self, render, path): (model, paths) = self.listview_files.get_selection().get_selected_rows() - for path in paths: + if len(paths) > 1: + for path in paths: + row = model.get_iter(path) + model.set_value(row, 0, not model.get_value(row, 0)) + else: row = model.get_iter(path) model.set_value(row, 0, not model.get_value(row, 0)) @@ -399,13 +426,17 @@ class AddTorrentDialog: row = self.torrent_liststore.get_iter_first() while row != None: + torrent_id = self.torrent_liststore.get_value(row, 0) filename = self.torrent_liststore.get_value(row, 2) try: - options = self.options[ - self.torrent_liststore.get_value(row, 0)] + options = self.options[torrent_id] except: options = None + file_priorities = self.get_file_priorities(torrent_id) + if options != None: + options["file_priorities"] = file_priorities + torrent_filenames.append(filename) torrent_options.append(options) From a24256609922c1743de8f7c13f7b679565c5cd52 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 25 Jan 2008 03:11:05 +0000 Subject: [PATCH 0417/1009] Update TODO. --- TODO | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 574ba08b4..ee89e436e 100644 --- a/TODO +++ b/TODO @@ -11,7 +11,6 @@ * Add autoload folder * Add wizard * Add a health indication to the statusbar -* Option for adding torrents in paused/active state * Fix up preferences for when using a remote host.. the download folders, etc.. * Add decay items to statusbar.. items that will disappear after X seconds * Do not update UI when minimized or hidden @@ -23,3 +22,12 @@ * Don't save fastresume files on exit for finished or paused torrents * Clean-up TorrentManager and state saving.. Maybe use an 'options' dictionary similar to one used when adding new torrents. +* Add Files and Peers tabs, but make them optional through View menu +* Add per-torrent speed settings to the torrent menu +* Add per-torrent settings to the details pane.. max_download_speed, etc.. So + the user know what limits are set on the torrent. +* Come up with our own torrent states to replace libtorrents as they are not + really compatible with our system. +* Add number of torrents to labels +* Add a 'move storage' on completion option + From 38f0d9f71c1a57dbae60dab2fe6e9739e5ee84e5 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 26 Jan 2008 06:45:23 +0000 Subject: [PATCH 0418/1009] Fix exception due to common not importing log. --- deluge/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/common.py b/deluge/common.py index 9e8513546..308dfdfba 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -62,7 +62,7 @@ def get_revision(): revision = f.read() f.close() except IOError, e: - log.debug("Could not open revision file: %s", e) + pass return revision From a55dcc8099e10c27f48ee26beb6f83d025af67ce Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 26 Jan 2008 07:10:39 +0000 Subject: [PATCH 0419/1009] Catch RuntimeErrors in load_torrent(). These are likely due to bad torrent files. --- deluge/core/torrentmanager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c254a6a08..60e7d1bee 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -152,6 +152,9 @@ class TorrentManager(component.Component): else: # Get the data from the file filedump = self.load_torrent(filename) + if not filedump: + log.warning("Unable to load torrent file..") + return None # Attempt to load fastresume data try: @@ -288,7 +291,7 @@ class TorrentManager(component.Component): "rb") filedump = lt.bdecode(_file.read()) _file.close() - except IOError, e: + except (IOError, RuntimeError), e: log.warning("Unable to open %s: e", filename, e) return False From d1677a855fb56e07803cff8fac908d9459ec4518 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 26 Jan 2008 13:24:42 +0000 Subject: [PATCH 0420/1009] Fix debug message. --- deluge/core/torrentmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 60e7d1bee..f0c154a4d 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -292,7 +292,7 @@ class TorrentManager(component.Component): filedump = lt.bdecode(_file.read()) _file.close() except (IOError, RuntimeError), e: - log.warning("Unable to open %s: e", filename, e) + log.warning("Unable to open %s: %s", filename, e) return False return filedump From 94f36c72a11cc938d1b41b257a55a8f1f21a9f14 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 28 Jan 2008 03:19:21 +0000 Subject: [PATCH 0421/1009] lt sync - cpu spike fix --- .../include/libtorrent/bandwidth_manager.hpp | 6 +++--- .../libtorrent/bandwidth_queue_entry.hpp | 5 ++--- libtorrent/include/libtorrent/bencode.hpp | 17 ++++++++++++----- .../include/libtorrent/peer_connection.hpp | 2 -- libtorrent/include/libtorrent/torrent.hpp | 2 +- libtorrent/src/instantiate_connection.cpp | 2 +- libtorrent/src/torrent.cpp | 4 ++-- libtorrent/src/torrent_handle.cpp | 9 ++++++--- 8 files changed, 27 insertions(+), 20 deletions(-) diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 89efcb9e8..c3b4f5942 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -173,7 +173,7 @@ struct bandwidth_manager ++i->priority; ++i; } - m_queue.insert(i.base(), bw_queue_entry(peer, blk, priority)); + m_queue.insert(i.base(), bw_queue_entry(peer, blk, priority)); if (!m_queue.empty()) hand_out_bandwidth(l); } @@ -292,7 +292,7 @@ private: queue_t tmp; while (!m_queue.empty() && amount > 0) { - bw_queue_entry qe = m_queue.front(); + bw_queue_entry qe = m_queue.front(); TORRENT_ASSERT(qe.max_block_size > 0); m_queue.pop_front(); @@ -401,7 +401,7 @@ private: int m_current_quota; // these are the consumers that want bandwidth - typedef std::deque > queue_t; + typedef std::deque > queue_t; queue_t m_queue; // these are the consumers that have received bandwidth diff --git a/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp b/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp index f8b44846c..54f669062 100644 --- a/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp +++ b/libtorrent/include/libtorrent/bandwidth_queue_entry.hpp @@ -37,16 +37,15 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -template +template struct bw_queue_entry { - typedef typename PeerConnection::torrent_type torrent_type; bw_queue_entry(boost::intrusive_ptr const& pe , int blk, int prio) : peer(pe), torrent(peer->associated_torrent()) , max_block_size(blk), priority(prio) {} boost::intrusive_ptr peer; - boost::weak_ptr torrent; + boost::weak_ptr torrent; int max_block_size; int priority; // 0 is low prio }; diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index 1d3f1ea2b..9bea1d846 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -214,8 +214,14 @@ namespace libtorrent } template - void bdecode_recursive(InIt& in, InIt end, entry& ret, bool& err) + void bdecode_recursive(InIt& in, InIt end, entry& ret, bool& err, int depth) { + if (depth >= 100) + { + err = true; + return; + } + if (in == end) { err = true; @@ -247,7 +253,7 @@ namespace libtorrent { ret.list().push_back(entry()); entry& e = ret.list().back(); - bdecode_recursive(in, end, e, err); + bdecode_recursive(in, end, e, err, depth + 1); if (err) return; if (in == end) { @@ -268,10 +274,10 @@ namespace libtorrent while (*in != 'e') { entry key; - bdecode_recursive(in, end, key, err); + bdecode_recursive(in, end, key, err, depth + 1); if (err) return; entry& e = ret[key.string()]; - bdecode_recursive(in, end, e, err); + bdecode_recursive(in, end, e, err, depth + 1); if (err) return; if (in == end) { @@ -317,7 +323,7 @@ namespace libtorrent { entry e; bool err = false; - detail::bdecode_recursive(start, end, e, err); + detail::bdecode_recursive(start, end, e, err, 0); if (err) { #ifdef BOOST_NO_EXCEPTIONS @@ -332,3 +338,4 @@ namespace libtorrent } #endif // TORRENT_BENCODE_HPP_INCLUDED + diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 13fab90ca..97d76a3d1 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -98,8 +98,6 @@ namespace libtorrent friend class invariant_access; public: - typedef torrent torrent_type; - enum channels { upload_channel, diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 773188cce..92ea37a40 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -688,7 +688,7 @@ namespace libtorrent boost::scoped_ptr m_picker; // the queue of peer_connections that want more bandwidth - typedef std::deque > queue_t; + typedef std::deque > queue_t; queue_t m_bandwidth_queue[2]; std::vector m_trackers; diff --git a/libtorrent/src/instantiate_connection.cpp b/libtorrent/src/instantiate_connection.cpp index f9c997fb1..23a202da4 100644 --- a/libtorrent/src/instantiate_connection.cpp +++ b/libtorrent/src/instantiate_connection.cpp @@ -54,7 +54,7 @@ namespace libtorrent { s.instantiate(ios); s.get().set_proxy(ps.hostname, ps.port); - if (ps.type == proxy_settings::socks5_pw) + if (ps.type == proxy_settings::http_pw) s.get().set_username(ps.username, ps.password); } else if (ps.type == proxy_settings::socks5 diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index e04e0be71..dc2902096 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -2253,7 +2253,7 @@ namespace libtorrent ++i->priority; ++i; } - m_bandwidth_queue[channel].insert(i.base(), bw_queue_entry( + m_bandwidth_queue[channel].insert(i.base(), bw_queue_entry( p, block_size, priority)); } } @@ -2267,7 +2267,7 @@ namespace libtorrent queue_t tmp; while (!m_bandwidth_queue[channel].empty()) { - bw_queue_entry qe = m_bandwidth_queue[channel].front(); + bw_queue_entry qe = m_bandwidth_queue[channel].front(); if (m_bandwidth_limit[channel].max_assignable() == 0) break; m_bandwidth_queue[channel].pop_front(); diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 092d77c78..635390537 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -503,7 +503,7 @@ namespace libtorrent session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); mutex::scoped_lock l2(m_chk->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + torrent* t = find_torrent(m_ses, m_chk, m_info_hash); if (!t || !t->valid_metadata()) #ifdef BOOST_NO_EXCEPTIONS return entry(); @@ -599,11 +599,13 @@ namespace libtorrent for (policy::iterator i = pol.begin_peer() , end(pol.end_peer()); i != end; ++i) { + asio::error_code ec; if (i->second.banned) { tcp::endpoint ip = i->second.ip; entry peer(entry::dictionary_t); - peer["ip"] = ip.address().to_string(); + peer["ip"] = ip.address().to_string(ec); + if (ec) continue; peer["port"] = ip.port(); banned_peer_list.push_back(peer); continue; @@ -619,7 +621,8 @@ namespace libtorrent tcp::endpoint ip = i->second.ip; entry peer(entry::dictionary_t); - peer["ip"] = ip.address().to_string(); + peer["ip"] = ip.address().to_string(ec); + if (ec) continue; peer["port"] = ip.port(); peer_list.push_back(peer); } From b29583024da3fda6587e59b81de000116e9c5e48 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 29 Jan 2008 02:44:25 +0000 Subject: [PATCH 0422/1009] Set the multicall to None if disconnected from the daemon. --- deluge/ui/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index c400dfd66..f20887438 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -187,10 +187,12 @@ class CoreProxy(gobject.GObject): self.emit("no_core") self._uri = None self._core = None + self._multi = None return if uri != self._uri and self._uri != None: self._core = None + self._multi = None self.emit("no_core") self._uri = uri From f7575931465a078f76714361043da3c49b3029c9 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 29 Jan 2008 06:58:23 +0000 Subject: [PATCH 0423/1009] add dht nodes to status bar --- deluge/core/core.py | 7 +++++++ deluge/data/pixmaps/dht16.png | Bin 0 -> 607 bytes deluge/data/revision | 1 + deluge/ui/client.py | 3 +++ deluge/ui/gtkui/statusbar.py | 15 +++++++++++++++ 5 files changed, 26 insertions(+) create mode 100644 deluge/data/pixmaps/dht16.png diff --git a/deluge/core/core.py b/deluge/core/core.py index 1d124e2ff..b830b0a39 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -443,6 +443,10 @@ class Core( """Returns the current number of connections""" return self.session.num_connections() + def export_get_dht_nodes(self): + """Returns the number of dht nodes""" + return self.session.status().dht_nodes + def export_get_download_rate(self): """Returns the payload download rate""" return self.session.status().payload_download_rate @@ -568,6 +572,9 @@ class Core( log.debug("dht value set to %s", value) if value: self.session.start_dht(None) + self.session.add_dht_router("router.bittorrent.com", 6881) + self.session.add_dht_router("router.utorrent.com", 6881) + self.session.add_dht_router("router.bitcomet.com", 6881) else: self.session.stop_dht() diff --git a/deluge/data/pixmaps/dht16.png b/deluge/data/pixmaps/dht16.png new file mode 100644 index 0000000000000000000000000000000000000000..be59e5e61d3887445f2bc094dc230348a6539582 GIT binary patch literal 607 zcmV-l0-*hgP)qz*w7TpSVv)2#$V^iCnD&_Pt_)}A;NBPd7<;!vVkTJt(wPOkC% zrtf?I|M@<6d5t8(D)^aRAP*b?YQW8Gs{DlilnLR2pV<%013Mz`0?uSp<(F}=73r_E zfB*)#78N|t>kRuuMBf>=T@AH2p$_0R(BpaD3t%vM`B@zN{p*ic#+YNZTJ1W})078* zlYil{L({V#=azE7TBv>dyfV|VZ+lMC+hj6XjqLmh%p-|(QRcwF*dWlBUl`q;Ul{H0 zy^uT$v;(IkAqm%Y`&`$Za$Pqi36n1B1TYPB0X|??96lIh`XtR7V>*F7lIH9Z7SL94 zs6*iPWD^8P0oe_PNne2a#k Date: Tue, 29 Jan 2008 07:16:20 +0000 Subject: [PATCH 0424/1009] add add_dht_router to libtorrent bindings --- libtorrent/bindings/python/src/docstrings.cpp | 2 ++ libtorrent/bindings/python/src/session.cpp | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index 0fa001f39..d6e473836 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -96,6 +96,8 @@ char const* session_stop_dht_doc = ""; char const* session_dht_state_doc = ""; +char const* session_add_dht_router_doc = + "add dht router"; char const* session_add_torrent_doc = "Adds a new torrent to the session. Return a `torrent_handle`.\n" diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 481348730..91befd094 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -35,6 +35,7 @@ extern char const* session_status_m_doc; extern char const* session_start_dht_doc; extern char const* session_stop_dht_doc; extern char const* session_dht_state_doc; +extern char const* session_add_dht_router_doc; extern char const* session_add_torrent_doc; extern char const* session_remove_torrent_doc; extern char const* session_set_download_rate_limit_doc; @@ -66,6 +67,12 @@ namespace return s.listen_on(std::make_pair(min_, max_), interface); } + void add_dht_router(session& s, std::string router_, int port_) + { + allow_threading_guard guard; + return s.add_dht_router(std::make_pair(router_, port_)); + } + struct invoke_extension_factory { invoke_extension_factory(object const& callback) @@ -176,6 +183,11 @@ void bind_session() , (arg("min"), "max", arg("interface") = (char const*)0) , session_listen_on_doc ) + .def( + "add_dht_router", &add_dht_router + , (arg("router"), "port") + , session_add_dht_router_doc + ) .def("is_listening", allow_threads(&session::is_listening), session_is_listening_doc) .def("listen_port", allow_threads(&session::listen_port), session_listen_port_doc) .def("status", allow_threads(&session::status), session_status_m_doc) From 50fa1ff1a9f1d5daf322b6c97f91a3c15c3f27a5 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 29 Jan 2008 07:17:19 +0000 Subject: [PATCH 0425/1009] add dht to config_value_changed --- deluge/ui/gtkui/statusbar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 6fe7ff926..4d4d8aa15 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -109,7 +109,8 @@ class StatusBar(component.Component): self.config_value_changed_dict = { "max_connections_global": self._on_max_connections_global, "max_download_speed": self._on_max_download_speed, - "max_upload_speed": self._on_max_upload_speed + "max_upload_speed": self._on_max_upload_speed, + "dht": self._on_get_dht_nodes } # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() From 88067040438937659739ab246b632c7052f0b938 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 29 Jan 2008 07:29:14 +0000 Subject: [PATCH 0426/1009] fix dht value change --- deluge/ui/gtkui/statusbar.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 4d4d8aa15..996a7192c 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -110,7 +110,7 @@ class StatusBar(component.Component): "max_connections_global": self._on_max_connections_global, "max_download_speed": self._on_max_download_speed, "max_upload_speed": self._on_max_upload_speed, - "dht": self._on_get_dht_nodes + "dht": self._on_dht } # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() @@ -215,6 +215,14 @@ class StatusBar(component.Component): def _on_get_dht_nodes(self, dht_nodes): self.dht_nodes = dht_nodes + def _on_dht(self, dht_nodes): + if dht_nodes == False: + self.remove_item(self.dht_item) + else: + self.hbox.pack_start( + self.dht_item.get_eventbox(), expand=False, fill=False) + + def _on_max_download_speed(self, max_download_speed): self.max_download_speed = max_download_speed self.update_download_label() From c3061576f3bfedbdc7b6222a7fb5f0fc58af046c Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 29 Jan 2008 08:12:44 +0000 Subject: [PATCH 0427/1009] check dht value on start() and dont get_dht_nodes() when dht is off --- deluge/ui/gtkui/statusbar.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 996a7192c..efa2d1fdc 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -141,9 +141,10 @@ class StatusBar(component.Component): self.hbox.pack_start( self.upload_item.get_eventbox(), expand=False, fill=False) self.dht_item = StatusBarItem( - image=deluge.common.get_pixmap("dht16.png")) - self.hbox.pack_start( - self.dht_item.get_eventbox(), expand=False, fill=False) + image=deluge.common.get_pixmap("dht16.png")) + if client.get_config("dht"): + self.hbox.pack_start( + self.dht_item.get_eventbox(), expand=False, fill=False) # Get some config values client.get_config_value( @@ -152,6 +153,8 @@ class StatusBar(component.Component): self._on_max_download_speed, "max_download_speed") client.get_config_value( self._on_max_upload_speed, "max_upload_speed") + client.get_config_value( + self._on_dht, "dht") self.send_status_request() @@ -276,7 +279,8 @@ class StatusBar(component.Component): def update(self): # Update the labels self.update_connections_label() - self.update_dht_label() + if self.dht_nodes != False: + self.update_dht_label() self.update_download_label() self.update_upload_label() From 5e66a28e889ffb90e6b889069cd20f7af709d8b8 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Tue, 29 Jan 2008 08:51:46 +0000 Subject: [PATCH 0428/1009] check dht status in start() and dont get dht nodes when dht is off --- deluge/ui/gtkui/statusbar.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index efa2d1fdc..e0853bca6 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -142,9 +142,6 @@ class StatusBar(component.Component): self.upload_item.get_eventbox(), expand=False, fill=False) self.dht_item = StatusBarItem( image=deluge.common.get_pixmap("dht16.png")) - if client.get_config("dht"): - self.hbox.pack_start( - self.dht_item.get_eventbox(), expand=False, fill=False) # Get some config values client.get_config_value( @@ -197,6 +194,7 @@ class StatusBar(component.Component): def send_status_request(self): # Sends an async request for data from the core client.get_num_connections(self._on_get_num_connections) + client.get_config_value(self._on_dht, "dht") client.get_dht_nodes(self._on_get_dht_nodes) client.get_download_rate(self._on_get_download_rate) client.get_upload_rate(self._on_get_upload_rate) @@ -225,7 +223,6 @@ class StatusBar(component.Component): self.hbox.pack_start( self.dht_item.get_eventbox(), expand=False, fill=False) - def _on_max_download_speed(self, max_download_speed): self.max_download_speed = max_download_speed self.update_download_label() From f8adeb9c69164abca736e771836376e4cb3bd827 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 29 Jan 2008 09:01:02 +0000 Subject: [PATCH 0429/1009] Statusbar DHT touch-up --- deluge/ui/gtkui/statusbar.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index e0853bca6..21b6d7e65 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -105,6 +105,7 @@ class StatusBar(component.Component): self.max_upload_speed = -1.0 self.upload_rate = 0.0 self.dht_nodes = 0 + self.dht_status = False self.config_value_changed_dict = { "max_connections_global": self._on_max_connections_global, @@ -194,8 +195,8 @@ class StatusBar(component.Component): def send_status_request(self): # Sends an async request for data from the core client.get_num_connections(self._on_get_num_connections) - client.get_config_value(self._on_dht, "dht") - client.get_dht_nodes(self._on_get_dht_nodes) + if self.dht_status: + client.get_dht_nodes(self._on_get_dht_nodes) client.get_download_rate(self._on_get_download_rate) client.get_upload_rate(self._on_get_upload_rate) @@ -216,12 +217,13 @@ class StatusBar(component.Component): def _on_get_dht_nodes(self, dht_nodes): self.dht_nodes = dht_nodes - def _on_dht(self, dht_nodes): - if dht_nodes == False: - self.remove_item(self.dht_item) - else: + def _on_dht(self, value): + self.dht_status = value + if value: self.hbox.pack_start( self.dht_item.get_eventbox(), expand=False, fill=False) + else: + self.remove_item(self.dht_item) def _on_max_download_speed(self, max_download_speed): self.max_download_speed = max_download_speed @@ -276,7 +278,7 @@ class StatusBar(component.Component): def update(self): # Update the labels self.update_connections_label() - if self.dht_nodes != False: + if self.dht_status: self.update_dht_label() self.update_download_label() self.update_upload_label() From 2fba592e6c138fb60715cd2d39548fae90a8584e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 29 Jan 2008 09:08:27 +0000 Subject: [PATCH 0430/1009] Move update label calls to where they should be. --- deluge/ui/gtkui/statusbar.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 21b6d7e65..cc681cf0d 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -213,15 +213,18 @@ class StatusBar(component.Component): def _on_get_num_connections(self, num_connections): self.num_connections = num_connections + self.update_connections_label() def _on_get_dht_nodes(self, dht_nodes): self.dht_nodes = dht_nodes + self.update_dht_label() def _on_dht(self, value): self.dht_status = value if value: self.hbox.pack_start( self.dht_item.get_eventbox(), expand=False, fill=False) + client.get_dht_nodes(self._on_get_dht_nodes) else: self.remove_item(self.dht_item) @@ -231,13 +234,15 @@ class StatusBar(component.Component): def _on_get_download_rate(self, download_rate): self.download_rate = deluge.common.fsize(download_rate) - + self.update_download_label() + def _on_max_upload_speed(self, max_upload_speed): self.max_upload_speed = max_upload_speed self.update_upload_label() def _on_get_upload_rate(self, upload_rate): self.upload_rate = deluge.common.fsize(upload_rate) + self.update_upload_label() def update_connections_label(self): # Set the max connections label @@ -276,12 +281,5 @@ class StatusBar(component.Component): max_upload_speed)) def update(self): - # Update the labels - self.update_connections_label() - if self.dht_status: - self.update_dht_label() - self.update_download_label() - self.update_upload_label() - # Send status request self.send_status_request() From 1653a72c7a1135f004740de0edac76f2777bce15 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 30 Jan 2008 12:56:50 +0000 Subject: [PATCH 0431/1009] Do not update UI when minimized or hidden. --- TODO | 1 - deluge/component.py | 31 ++++++++++++++++++++----------- deluge/data/revision | 1 - deluge/ui/gtkui/mainwindow.py | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/TODO b/TODO index ee89e436e..491bf63e0 100644 --- a/TODO +++ b/TODO @@ -13,7 +13,6 @@ * Add a health indication to the statusbar * Fix up preferences for when using a remote host.. the download folders, etc.. * Add decay items to statusbar.. items that will disappear after X seconds -* Do not update UI when minimized or hidden * Add command line option to change config dir.. --config * Add method for plugins to add labels * Add context menus for labels.. ie. setting options for all torrents in label diff --git a/deluge/component.py b/deluge/component.py index a06b20e22..3f87b9cfe 100644 --- a/deluge/component.py +++ b/deluge/component.py @@ -118,12 +118,15 @@ class ComponentRegistry: def stop(self): """Stops all components""" for component in self.components.keys(): - if self.components[component].get_state != \ - COMPONENT_STATE.index("Stopped"): - log.debug("Stopping component %s..", component) - self.components[component].stop() - self.components[component]._stop() - + self.stop_component(component) + + def stop_component(self, component): + if self.components[component].get_state != \ + COMPONENT_STATE.index("Stopped"): + log.debug("Stopping component %s..", component) + self.components[component].stop() + self.components[component]._stop() + def update(self): """Updates all components""" for component in self.components.keys(): @@ -153,13 +156,19 @@ def register(name, obj, depend=None): """Registers a component with the registry""" _ComponentRegistry.register(name, obj, depend) -def start(): +def start(component=None): """Starts all components""" - _ComponentRegistry.start() + if component == None: + _ComponentRegistry.start() + else: + _ComponentRegistry.start_component(component) -def stop(): - """Stops all components""" - _ComponentRegistry.stop() +def stop(component=None): + """Stops all or specified components""" + if component == None: + _ComponentRegistry.stop() + else: + _ComponentRegistry.stop_component(component) def update(): """Updates all components""" diff --git a/deluge/data/revision b/deluge/data/revision index 31eb8b45f..e69de29bb 100644 --- a/deluge/data/revision +++ b/deluge/data/revision @@ -1 +0,0 @@ -2679 \ No newline at end of file diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 13708d44c..b4e6e2be6 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -77,11 +77,19 @@ class MainWindow(component.Component): self.show() def show(self): + try: + component.get("TorrentView").start() + component.get("StatusBar").start() + except: + pass + # Load the state prior to showing self.load_window_state() self.window.show() def hide(self): + component.stop("TorrentView") + component.stop("StatusBar") self.window.hide() def present(self): @@ -128,9 +136,16 @@ class MainWindow(component.Component): if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: log.debug("MainWindow is minimized..") + component.stop("TorrentView") + component.stop("StatusBar") self.is_minimized = True else: log.debug("MainWindow is not minimized..") + try: + component.start("TorrentView") + component.start("StatusBar") + except: + pass self.is_minimized = False return False From a23ff6cbcaf7f83463ec258fe3489b422ae29b27 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 31 Jan 2008 06:51:35 +0000 Subject: [PATCH 0432/1009] randomize encryption pad size --- libtorrent/src/bt_peer_connection.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index ca98888bd..1fbc3b8fa 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -411,11 +411,11 @@ namespace libtorrent sha1_hash const& info_hash = t->torrent_file().info_hash(); char const* const secret = m_DH_key_exchange->get_secret(); - int pad_size = 0; // rand() % 512; // Keep 0 for now + int pad_size = rand() % 512; // synchash,skeyhash,vc,crypto_provide,len(pad),pad,len(ia) buffer::interval send_buf = - allocate_send_buffer (20 + 20 + 8 + 4 + 2 + pad_size + 2); + allocate_send_buffer(20 + 20 + 8 + 4 + 2 + pad_size + 2); // sync hash (hash('req1',S)) h.reset(); @@ -423,7 +423,7 @@ namespace libtorrent h.update(secret, dh_key_len); sha1_hash sync_hash = h.final(); - std::copy (sync_hash.begin(), sync_hash.end(), send_buf.begin); + std::copy(sync_hash.begin(), sync_hash.end(), send_buf.begin); send_buf.begin += 20; // stream key obfuscated hash [ hash('req2',SKEY) xor hash('req3',S) ] @@ -438,7 +438,7 @@ namespace libtorrent sha1_hash obfsc_hash = h.final(); obfsc_hash ^= streamkey_hash; - std::copy (obfsc_hash.begin(), obfsc_hash.end(), send_buf.begin); + std::copy(obfsc_hash.begin(), obfsc_hash.end(), send_buf.begin); send_buf.begin += 20; // Discard DH key exchange data, setup RC4 keys @@ -486,9 +486,9 @@ namespace libtorrent TORRENT_ASSERT(crypto_select == 0x02 || crypto_select == 0x01); TORRENT_ASSERT(!m_sent_handshake); - int pad_size = 0; // rand() % 512; // Keep 0 for now + int pad_size =rand() % 512; - const int buf_size = 8+4+2+pad_size; + const int buf_size = 8 + 4 + 2 + pad_size; buffer::interval send_buf = allocate_send_buffer(buf_size); write_pe_vc_cryptofield(send_buf, crypto_select, pad_size); @@ -516,7 +516,6 @@ namespace libtorrent INVARIANT_CHECK; TORRENT_ASSERT(crypto_field <= 0x03 && crypto_field > 0); - TORRENT_ASSERT(pad_size == 0); // pad not used yet // vc,crypto_field,len(pad),pad, (len(ia)) TORRENT_ASSERT( (write_buf.left() == 8+4+2+pad_size+2 && is_local()) || (write_buf.left() == 8+4+2+pad_size && !is_local()) ); @@ -533,14 +532,14 @@ namespace libtorrent detail::write_uint16(pad_size, write_buf.begin); // len (pad) // fill pad with zeroes - // std::fill(write_buf.begin, write_buf.begin+pad_size, 0); - // write_buf.begin += pad_size; + std::generate(write_buf.begin, write_buf.begin + pad_size, &std::rand); + write_buf.begin += pad_size; // append len(ia) if we are initiating if (is_local()) detail::write_uint16(handshake_len, write_buf.begin); // len(IA) - assert (write_buf.begin == write_buf.end); + TORRENT_ASSERT(write_buf.begin == write_buf.end); } void bt_peer_connection::init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key) From e2eaa9abb4566d5ace1d1ca361ef39c1c411885b Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Thu, 31 Jan 2008 19:11:39 +0000 Subject: [PATCH 0433/1009] logout;more robust async --- .../ui/webui/webui_plugin/templates/advanced/part_stats.html | 2 ++ deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html | 3 +++ deluge/ui/webui/webui_plugin/webserver_common.py | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html index 2bccc8bfe..0a11c0bf4 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html @@ -13,6 +13,8 @@ $else: $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') $#end +$:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png') +
      diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html b/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html index 305a9ea25..314e7c2d4 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/part_stats.html @@ -12,6 +12,9 @@ $else: $_('Off')   $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') $#end + +$:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png') +
      diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index aa12ca6fb..ab332a119 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -118,12 +118,14 @@ class SyncProxyFunction: self.client = client def __call__(self,*args,**kwargs): - sync_result = [] func = getattr(self.client,self.func_name) if self.has_callback(func): + sync_result = [] func(sync_result.append,*args, **kwargs) self.client.force_call(block=True) + if not sync_result: + return None return sync_result[0] else: ws.log.debug('no-cb: %s' % self.func_name) From 9469610acaeae4d666e0e4bbb26ce250eae75015 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 2 Feb 2008 01:30:18 +0000 Subject: [PATCH 0434/1009] Use new torrent states instead of using libtorrent's. --- deluge/common.py | 30 ++++-- deluge/core/core.py | 16 +-- deluge/core/torrent.py | 189 ++++++++++++++++++++++++++++++--- deluge/core/torrentmanager.py | 164 +++++----------------------- deluge/ui/gtkui/sidebar.py | 10 +- deluge/ui/gtkui/toolbar.py | 5 +- deluge/ui/gtkui/torrentview.py | 34 +++--- 7 files changed, 251 insertions(+), 197 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 308dfdfba..22f1526cb 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -39,18 +39,26 @@ import pkg_resources import xdg, xdg.BaseDirectory -TORRENT_STATE = [ - "Queued", - "Checking", - "Connecting", - "Downloading Metadata", - "Downloading", - "Finished", - "Seeding", - "Allocating", - "Paused" -] +LT_TORRENT_STATE = { + "Queued": 0, + "Checking": 1, + "Connecting": 2, + "Downloading Metadata": 3, + "Downloading": 4, + "Finished": 5, + "Seeding": 6, + "Allocating": 7, + "Paused": 8 +} +TORRENT_STATE = { + "Allocating": 0, + "Checking": 1, + "Downloading": 2, + "Seeding": 3, + "Paused": 4, + "Error": 5 +} def get_version(): """Returns the program version from the egg metadata""" return pkg_resources.require("Deluge")[0].version.split("r")[0] diff --git a/deluge/core/core.py b/deluge/core/core.py index b830b0a39..f6117d346 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -344,17 +344,17 @@ class Core( def export_force_reannounce(self, torrent_id): log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id) - self.torrents.force_reannounce(torrent_id) + self.torrents[torrent_id].force_reannounce() def export_pause_torrent(self, torrent_id): log.debug("Pausing torrent %s", torrent_id) - if not self.torrents.pause(torrent_id): + if not self.torrents[torrent_id].pause(): log.warning("Error pausing torrent %s", torrent_id) - def export_move_torrent(self, torrent_id, folder): - log.debug("Moving torrent %s to %s", torrent_id, folder) - if not self.torrents.move(torrent_id, folder): - log.warning("Error moving torrent %s to %s", torrent_id, folder) + def export_move_torrent(self, torrent_id, dest): + log.debug("Moving torrent %s to %s", torrent_id, dest) + if not self.torrents[torrent_id].move(dest): + log.warning("Error moving torrent %s to %s", torrent_id, dest) def export_pause_all_torrents(self): """Pause all torrents in the session""" @@ -369,7 +369,7 @@ class Core( def export_resume_torrent(self, torrent_id): log.debug("Resuming torrent %s", torrent_id) - if self.torrents.resume(torrent_id): + if self.torrents[torrent_id].resume(): self.torrent_resumed(torrent_id) def export_get_torrent_status(self, torrent_id, keys): @@ -477,7 +477,7 @@ class Core( def export_set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" - return self.torrents.set_trackers(torrent_id, trackers) + return self.torrents[torrent_id].set_trackers(trackers) # Signals def torrent_added(self, torrent_id): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 0b0aba982..0d74758a0 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -33,13 +33,23 @@ """Internal Torrent class""" +import os + +import deluge.libtorrent as lt import deluge.common +from deluge.configmanager import ConfigManager +from deluge.log import LOG as log + +TORRENT_STATE = deluge.common.TORRENT_STATE class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ def __init__(self, filename, handle, compact, save_path, total_uploaded=0, trackers=None): + # Get the core config + self.config = ConfigManager("core.conf") + # Set the filename self.filename = filename # Set the libtorrent handle @@ -52,7 +62,22 @@ class Torrent: self.compact = compact # Where the torrent is being saved to self.save_path = save_path + # The state of the torrent + self.state = None + # Holds status info so that we don't need to keep getting it from lt + self.status = self.handle.status() + self.torrent_info = self.handle.torrent_info() + + # Set the initial state + if self.status.state == deluge.common.LT_TORRENT_STATE["Allocating"]: + self.set_state("Allocating") + elif self.status.state == deluge.common.LT_TORRENT_STATE["Checking"]: + self.set_state("Checking") + else: + self.set_state("Paused") + + # Various torrent options self.max_connections = -1 self.max_upload_slots = -1 self.max_upload_speed = -1 @@ -73,10 +98,6 @@ class Torrent: self.trackers.append(tracker) else: self.trackers = trackers - - # Holds status info so that we don't need to keep getting it from lt - self.status = None - self.torrent_info = None # Files dictionary self.files = self.get_files() @@ -114,8 +135,21 @@ class Torrent: def set_file_priorities(self, file_priorities): self.file_priorities = file_priorities self.handle.prioritize_files(file_priorities) - - def get_state(self): + + def set_state(self, state): + """Accepts state strings, ie, "Paused", "Seeding", etc.""" + + # Only set 'Downloading' or 'Seeding' state if not paused + if state == "Downloading" or state == "Seeding": + if self.handle.is_paused(): + state = "Paused" + + try: + self.state = TORRENT_STATE[state] + except: + pass + + def get_save_info(self): """Returns the state of this torrent for saving to the session state""" status = self.handle.status() return (self.torrent_id, self.filename, self.compact, status.paused, @@ -188,11 +222,6 @@ class Torrent: # Adjust progress to be 0-100 value progress = self.status.progress * 100 - # Set the state to 'Paused' if the torrent is paused. - state = self.status.state - if self.status.paused: - state = deluge.common.TORRENT_STATE.index("Paused") - # Adjust status.distributed_copies to return a non-negative value distributed_copies = self.status.distributed_copies if distributed_copies < 0: @@ -207,7 +236,7 @@ class Torrent: "distributed_copies": distributed_copies, "total_done": self.status.total_done, "total_uploaded": self.total_uploaded + self.status.total_payload_upload, - "state": int(state), + "state": self.state, "paused": self.status.paused, "progress": progress, "next_announce": self.status.next_announce.seconds, @@ -242,3 +271,139 @@ class Torrent: status_dict[key] = full_status[key] return status_dict + + def pause(self): + """Pause this torrent""" + try: + self.handle.pause() + except Exception, e: + log.debug("Unable to pause torrent: %s", e) + return False + + return True + + def resume(self): + """Resumes this torrent""" + if self.state != TORRENT_STATE["Paused"]: + return False + + try: + self.handle.resume() + except: + return False + + # Set the state + if self.handle.is_seed(): + self.set_state("Seeding") + else: + self.set_state("Downloading") + + status = self.get_status(["total_done", "total_wanted"]) + + # Only delete the .fastresume file if we're still downloading stuff + if status["total_done"] < status["total_wanted"]: + self.delete_fastresume() + return True + + def move_storage(self, dest): + """Move a torrent's storage location""" + try: + self.handle.move_storage(dest) + except: + return False + + return True + + def write_fastresume(self): + """Writes the .fastresume file for the torrent""" + resume_data = lt.bencode(self.handle.write_resume_data()) + path = "%s/%s.fastresume" % ( + self.config["torrentfiles_location"], + self.filename) + log.debug("Saving fastresume file: %s", path) + try: + fastresume = open(path, "wb") + fastresume.write(resume_data) + fastresume.close() + except IOError: + log.warning("Error trying to save fastresume file") + + def delete_fastresume(self): + """Deletes the .fastresume file""" + path = "%s/%s.fastresume" % ( + self.config["torrentfiles_location"], + self.filename) + log.debug("Deleting fastresume file: %s", path) + try: + os.remove(path) + except Exception, e: + log.warning("Unable to delete the fastresume file: %s", e) + + def force_reannounce(self): + """Force a tracker reannounce""" + try: + self.handle.force_reannounce() + except Exception, e: + log.debug("Unable to force reannounce: %s", e) + return False + + return True + + def scrape_tracker(self): + """Scrape the tracker""" + try: + self.handle.scrape_tracker() + except Exception, e: + log.debug("Unable to scrape tracker: %s", e) + return False + + return True + + def set_trackers(self, trackers): + """Sets trackers""" + if trackers == None: + trackers = [] + + log.debug("Setting trackers for %s: %s", self.torrent_id, trackers) + tracker_list = [] + + for tracker in trackers: + new_entry = lt.announce_entry(tracker["url"]) + new_entry.tier = tracker["tier"] + tracker_list.append(new_entry) + + self.handle.replace_trackers(tracker_list) + + # Print out the trackers + for t in self.handle.trackers(): + log.debug("tier: %s tracker: %s", t.tier, t.url) + # Set the tracker list in the torrent object + self.trackers = trackers + if len(trackers) > 0: + # Force a reannounce if there is at least 1 tracker + self.force_reannounce() + + def save_torrent_file(self, filedump=None): + """Saves a torrent file""" + log.debug("Attempting to save torrent file: %s", self.filename) + # Test if the torrentfiles_location is accessible + if os.access( + os.path.join(self.config["torrentfiles_location"]), os.F_OK) \ + is False: + # The directory probably doesn't exist, so lets create it + try: + os.makedirs(os.path.join(self.config["torrentfiles_location"])) + except IOError, e: + log.warning("Unable to create torrent files directory: %s", e) + + # Write the .torrent file to the torrent directory + try: + save_file = open(os.path.join(self.config["torrentfiles_location"], + self.filename), + "wb") + if filedump == None: + filedump = self.handle.torrent_info().create_torrent() + save_file.write(lt.bencode(filedump)) + save_file.close() + except IOError, e: + log.warning("Unable to save torrent file: %s", e) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index f0c154a4d..e893b56ee 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -93,6 +93,8 @@ class TorrentManager(component.Component): self.on_alert_torrent_finished) self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused) + self.alerts.register_handler("torrent_checked_alert", + self.on_alert_torrent_checked) self.alerts.register_handler("tracker_reply_alert", self.on_alert_tracker_reply) self.alerts.register_handler("tracker_announce_alert", @@ -116,7 +118,7 @@ class TorrentManager(component.Component): # Pause all torrents and save the .fastresume files self.pause_all() for key in self.torrents.keys(): - self.write_fastresume(key) + self.torrents[key].write_fastresume() def shutdown(self): self.stop() @@ -228,7 +230,7 @@ class TorrentManager(component.Component): # Set the trackers if trackers != None: - self.set_trackers(str(handle.info_hash()), trackers) + torrent.set_trackers(trackers) # Set per-torrent options torrent.set_max_connections(options["max_connections_per_torrent"]) @@ -250,34 +252,11 @@ class TorrentManager(component.Component): handle.resume() # Save the torrent file - self.save_torrent(filename, filedump) + torrent.save_torrent_file(filedump) # Save the session state self.save_state() return torrent.torrent_id - - def save_torrent(self, filename, filedump): - """Saves a torrent file""" - log.debug("Attempting to save torrent file: %s", filename) - # Test if the torrentfiles_location is accessible - if os.access( - os.path.join(self.config["torrentfiles_location"]), os.F_OK) \ - is False: - # The directory probably doesn't exist, so lets create it - try: - os.makedirs(os.path.join(self.config["torrentfiles_location"])) - except IOError, e: - log.warning("Unable to create torrent files directory: %s", e) - - # Write the .torrent file to the torrent directory - try: - save_file = open(os.path.join(self.config["torrentfiles_location"], - filename), - "wb") - save_file.write(lt.bencode(filedump)) - save_file.close() - except IOError, e: - log.warning("Unable to save torrent file: %s", e) def load_torrent(self, filename): """Load a torrent file and return it's torrent info""" @@ -322,7 +301,7 @@ class TorrentManager(component.Component): log.warning("Unable to remove .torrent file: %s", e) # Remove the .fastresume if it exists - self.delete_fastresume(torrent_id) + self.torrents[torrent_id].delete_fastresume() # Remove the torrent from deluge's session try: @@ -333,24 +312,6 @@ class TorrentManager(component.Component): # Save the session state self.save_state() return True - - def pause(self, torrent_id): - """Pause a torrent""" - try: - self.torrents[torrent_id].handle.pause() - except: - return False - - return True - - def move(self, torrent_id, folder): - """Move a torrent""" - try: - self.torrents[torrent_id].handle.move_storage(folder) - except: - return False - - return True def pause_all(self): """Pauses all torrents.. Returns a list of torrents paused.""" @@ -363,24 +324,9 @@ class TorrentManager(component.Component): log.warning("Unable to pause torrent %s", key) return torrent_was_paused - - def resume(self, torrent_id): - """Resume a torrent""" - try: - self.torrents[torrent_id].handle.resume() - except: - return False - - status = self.torrents[torrent_id].get_status( - ["total_done", "total_wanted"]) - - # Only delete the .fastresume file if we're still downloading stuff - if status["total_done"] < status["total_wanted"]: - self.delete_fastresume(torrent_id) - return True def resume_all(self): - """Resumes all torrents.. Returns a list of torrents resumed""" + """Resumes all torrents.. Returns True if at least 1 torrent is resumed""" torrent_was_resumed = False for key in self.torrents.keys(): if self.resume(key): @@ -389,50 +335,7 @@ class TorrentManager(component.Component): log.warning("Unable to resume torrent %s", key) return torrent_was_resumed - - def set_trackers(self, torrent_id, trackers): - """Sets trackers""" - if trackers == None: - trackers = [] - - log.debug("Setting trackers for %s: %s", torrent_id, trackers) - tracker_list = [] - - for tracker in trackers: - new_entry = lt.announce_entry(tracker["url"]) - new_entry.tier = tracker["tier"] - tracker_list.append(new_entry) - - self.torrents[torrent_id].handle.replace_trackers(tracker_list) - # Print out the trackers - for t in self.torrents[torrent_id].handle.trackers(): - log.debug("tier: %s tracker: %s", t.tier, t.url) - # Set the tracker list in the torrent object - self.torrents[torrent_id].trackers = trackers - if len(trackers) > 0: - # Force a reannounce if there is at least 1 tracker - self.force_reannounce(torrent_id) - - def force_reannounce(self, torrent_id): - """Force a tracker reannounce""" - try: - self.torrents[torrent_id].handle.force_reannounce() - except Exception, e: - log.debug("Unable to force reannounce: %s", e) - return False - - return True - - def scrape_tracker(self, torrent_id): - """Scrape the tracker""" - try: - self.torrents[torrent_id].handle.scrape_tracker() - except Exception, e: - log.debug("Unable to scrape tracker: %s", e) - return False - - return True - + def force_recheck(self, torrent_id): """Forces a re-check of the torrent's data""" log.debug("Doing a forced recheck on %s", torrent_id) @@ -443,7 +346,7 @@ class TorrentManager(component.Component): if os.access(os.path.join(self.config["torrentfiles_location"] +\ "/" + torrent.filename), os.F_OK) is False: torrent_info = torrent.handle.get_torrent_info().create_torrent() - self.save_torrent(torrent.filename, torrent_info) + torrent.save_torrent_file() # We start by removing it from the lt session try: @@ -453,7 +356,7 @@ class TorrentManager(component.Component): return False # Remove the fastresume file if there - self.delete_fastresume(torrent_id) + torrent.delete_fastresume() # Load the torrent info from file if needed if torrent_info == None: @@ -481,6 +384,9 @@ class TorrentManager(component.Component): # The torrent was not added to the session return False + # Set the state to Checking + torrent.set_state("Checking") + return True def load_state(self): @@ -508,7 +414,7 @@ class TorrentManager(component.Component): state = TorrentManagerState() # Create the state for each Torrent and append to the list for torrent in self.torrents.values(): - torrent_state = TorrentState(*torrent.get_state()) + torrent_state = TorrentState(*torrent.get_save_info()) state.torrents.append(torrent_state) # Pickle the TorrentManagerState object @@ -524,33 +430,6 @@ class TorrentManager(component.Component): # We return True so that the timer thread will continue return True - def delete_fastresume(self, torrent_id): - """Deletes the .fastresume file""" - torrent = self.torrents[torrent_id] - path = "%s/%s.fastresume" % ( - self.config["torrentfiles_location"], - torrent.filename) - log.debug("Deleting fastresume file: %s", path) - try: - os.remove(path) - except Exception, e: - log.warning("Unable to delete the fastresume file: %s", e) - - def write_fastresume(self, torrent_id): - """Writes the .fastresume file for the torrent""" - torrent = self.torrents[torrent_id] - resume_data = lt.bencode(torrent.handle.write_resume_data()) - path = "%s/%s.fastresume" % ( - self.config["torrentfiles_location"], - torrent.filename) - log.debug("Saving fastresume file: %s", path) - try: - fastresume = open(path,"wb") - fastresume.write(resume_data) - fastresume.close() - except IOError: - log.warning("Error trying to save fastresume file") - def on_set_max_connections_per_torrent(self, key, value): """Sets the per-torrent connection limit""" log.debug("max_connections_per_torrent set to %s..", value) @@ -571,16 +450,27 @@ class TorrentManager(component.Component): # Get the torrent_id torrent_id = str(alert.handle.info_hash()) log.debug("%s is finished..", torrent_id) + # Set the torrent state + self.torrents[torrent_id].set_state("Seeding") # Write the fastresume file - self.write_fastresume(torrent_id) + self.torrents[torrent_id].write_fastresume() def on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) + # Set the torrent state + self.torrents[torrent_id].set_state("Paused") # Write the fastresume file - self.write_fastresume(torrent_id) + self.torrents[torrent_id].write_fastresume() + def on_alert_torrent_checked(self, alert): + log.debug("on_alert_torrent_checked") + # Get the torrent_id + torrent_id = str(alert.handle.info_hash()) + # Set the torrent state + self.torrents[torrent_id].set_state("Downloading") + def on_alert_tracker_reply(self, alert): log.debug("on_alert_tracker_reply") # Get the torrent_id diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 901c2a22d..3e8ad2379 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -38,6 +38,8 @@ import deluge.component as component import deluge.common from deluge.log import LOG as log +TORRENT_STATE = deluge.common.TORRENT_STATE + class SideBar(component.Component): def __init__(self): component.Component.__init__(self, "SideBar") @@ -102,13 +104,11 @@ class SideBar(component.Component): component.get("TorrentView").set_filter(None, None) if value == "Downloading": component.get("TorrentView").set_filter("state", - deluge.common.TORRENT_STATE.index("Downloading")) - + TORRENT_STATE["Downloading"]) if value == "Seeding": component.get("TorrentView").set_filter("state", - deluge.common.TORRENT_STATE.index("Seeding")) - + TORRENT_STATE["Seeding"]) if value == "Paused": component.get("TorrentView").set_filter("state", - deluge.common.TORRENT_STATE.index("Paused")) + TORRENT_STATE["Paused"]) diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index dffea8ee2..09132c22f 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -42,9 +42,6 @@ from deluge.common import TORRENT_STATE import deluge.ui.client as client class ToolBar(component.Component): - STATE_FINISHED = TORRENT_STATE.index("Finished") - STATE_SEEDING = TORRENT_STATE.index("Seeding") - STATE_PAUSED = TORRENT_STATE.index("Paused") def __init__(self): component.Component.__init__(self, "ToolBar") log.debug("ToolBar Init..") @@ -175,7 +172,7 @@ class ToolBar(component.Component): except KeyError, e: log.debug("Error getting torrent state: %s", e) continue - if status == self.STATE_PAUSED: + if status == TORRENT_STATE["Paused"]: resume = True else: pause = True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 3b920da28..1c881758f 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -48,6 +48,8 @@ import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview +TORRENT_STATE = deluge.common.TORRENT_STATE + # Status icons.. Create them from file only once to avoid constantly # re-creating them. icon_downloading = gtk.gdk.pixbuf_new_from_file( @@ -56,18 +58,17 @@ icon_seeding = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("seeding16.png")) icon_inactive = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("inactive16.png")) +icon_alert = gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("alert16.png")) # Holds the info for which status icon to display based on state ICON_STATE = [ icon_inactive, - icon_downloading, - icon_downloading, - icon_downloading, + icon_inactive, icon_downloading, icon_seeding, - icon_seeding, - icon_downloading, - icon_inactive + icon_inactive, + icon_alert ] def cell_data_statusicon(column, cell, model, row, data): @@ -78,23 +79,16 @@ def cell_data_statusicon(column, cell, model, row, data): def cell_data_progress(column, cell, model, row, data): """Display progress bar with text""" - # Translated state strings - TORRENT_STATE = [ - _("Queued"), - _("Checking"), - _("Connecting"), - _("Downloading Metadata"), - _("Downloading"), - _("Finished"), - _("Seeding"), - _("Allocating"), - _("Paused") - ] (value, text) = model.get(row, *data) if cell.get_property("value") != value: cell.set_property("value", value) - textstr = "%s" % TORRENT_STATE[text] - if TORRENT_STATE[text] != "Seeding" and TORRENT_STATE[text] != "Finished": + state_str = "" + for key in TORRENT_STATE.keys(): + if TORRENT_STATE[key] == text: + state_str = key + break + textstr = "%s" % state_str + if state_str != "Seeding" and state_str != "Finished" and value < 100: textstr = textstr + " %.2f%%" % value if cell.get_property("text") != textstr: cell.set_property("text", textstr) From 8ab923fb946e807b75097b0557fc2dcc8a81754b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 2 Feb 2008 01:31:08 +0000 Subject: [PATCH 0435/1009] Update TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 491bf63e0..c9c94eafd 100644 --- a/TODO +++ b/TODO @@ -25,8 +25,6 @@ * Add per-torrent speed settings to the torrent menu * Add per-torrent settings to the details pane.. max_download_speed, etc.. So the user know what limits are set on the torrent. -* Come up with our own torrent states to replace libtorrents as they are not - really compatible with our system. * Add number of torrents to labels * Add a 'move storage' on completion option From 34fd115051b0b1aa92b6ec3cef17dd23575b0537 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 3 Feb 2008 01:04:26 +0000 Subject: [PATCH 0436/1009] add windows support to setup.py --- setup.py | 83 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index 62fc69220..bb5bed111 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,13 @@ import os python_version = platform.python_version()[0:3] +def windows_check(): + import platform + if platform.system() in ('Windows', 'Microsoft'): + return True + else: + return False + # Try to get SVN revision number to append to version revision_string = "" try: @@ -78,28 +85,47 @@ _extra_compile_args = [ "-DHAVE_SSL=1", "-O2", "-DNDEBUG" -] + ] +if windows_check(): + _extra_compile_args.remove("-Wno-missing-braces") + _extra_compile_args = _extra_compile_args + [ + "-DBOOST_WINDOWS", + "-DWIN32_LEAN_AND_MEAN", + "-D_WIN32_WINNT=0x0500", + "-D__USE_W32_SOCKETS", + "-D_WIN32", + "-DWIN32", + "-DUNICODE", + "-DBOOST_ALL_NO_LIB", + "-D_FILE_OFFSET_BITS=64", + "-DBOOST_THREAD_USE_LIB", + "-DTORRENT_BUILDING_SHARED", + "-DTORRENT_LINKING_SHARED", + ] removals = ["-Wstrict-prototypes"] -if python_version == '2.5': - cv_opt = sysconfig.get_config_vars()["CFLAGS"] - for removal in removals: - cv_opt = cv_opt.replace(removal, " ") - sysconfig.get_config_vars()["CFLAGS"] = " ".join(cv_opt.split()) -else: - cv_opt = sysconfig.get_config_vars()["OPT"] - for removal in removals: - cv_opt = cv_opt.replace(removal, " ") - sysconfig.get_config_vars()["OPT"] = " ".join(cv_opt.split()) +if not windows_check(): + if python_version == '2.5': + cv_opt = sysconfig.get_config_vars()["CFLAGS"] + for removal in removals: + cv_opt = cv_opt.replace(removal, " ") + sysconfig.get_config_vars()["CFLAGS"] = " ".join(cv_opt.split()) + else: + cv_opt = sysconfig.get_config_vars()["OPT"] + for removal in removals: + cv_opt = cv_opt.replace(removal, " ") + sysconfig.get_config_vars()["OPT"] = " ".join(cv_opt.split()) +_extra_link_args = [ +] _include_dirs = [ './libtorrent', './libtorrent/include', './libtorrent/include/libtorrent', '/usr/include/python' + python_version ] - + _libraries = [ 'boost_filesystem', 'boost_date_time', @@ -108,24 +134,43 @@ _libraries = [ 'z', 'pthread', 'ssl', - ] - + +if windows_check(): + _extra_link_args = _extra_link_args + [ + '-L./win32/lib' + ] + _include_dirs.remove('/usr/include/python' + python_version) + _include_dirs = _include_dirs + [ + './win32/include' + ] + _libraries.remove('ssl') + _libraries = _libraries + [ + 'ssleay32MT', + 'libeay32MT', + 'advapi32', + 'wsock32', + 'gdi32', + 'ws2_32' + ] + _sources = glob.glob("./libtorrent/src/*.cpp") + \ glob.glob("./libtorrent/src/kademlia/*.cpp") + \ glob.glob("./libtorrent/bindings/python/src/*.cpp") -# Remove file_win.cpp as it is only for Windows builds -for source in _sources: - if "file_win.cpp" in source: - _sources.remove(source) - break +# Remove file_win.cpp if not on windows +if not windows_check(): + for source in _sources: + if "file_win.cpp" in source: + _sources.remove(source) + break libtorrent = Extension( 'libtorrent', include_dirs = _include_dirs, libraries = _libraries, extra_compile_args = _extra_compile_args, + extra_link_args = _extra_link_args, sources = _sources ) From 684c3098d90036600edf66424f3dcd35222d2baa Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 3 Feb 2008 01:12:46 +0000 Subject: [PATCH 0437/1009] Touch-up setup.py. --- setup.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index bb5bed111..a278bda6b 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,6 @@ except: # The libtorrent extension _extra_compile_args = [ - "-Wno-missing-braces", "-DHAVE_INCLUDE_LIBTORRENT_ASIO____ASIO_HPP=1", "-DHAVE_INCLUDE_LIBTORRENT_ASIO_SSL_STREAM_HPP=1", "-DHAVE_INCLUDE_LIBTORRENT_ASIO_IP_TCP_HPP=1", @@ -86,9 +85,9 @@ _extra_compile_args = [ "-O2", "-DNDEBUG" ] + if windows_check(): - _extra_compile_args.remove("-Wno-missing-braces") - _extra_compile_args = _extra_compile_args + [ + _extra_compile_args += [ "-DBOOST_WINDOWS", "-DWIN32_LEAN_AND_MEAN", "-D_WIN32_WINNT=0x0500", @@ -102,7 +101,9 @@ if windows_check(): "-DTORRENT_BUILDING_SHARED", "-DTORRENT_LINKING_SHARED", ] - +else: + _extra_compile_args += ["-Wno-missing-braces"] + removals = ["-Wstrict-prototypes"] if not windows_check(): @@ -119,11 +120,11 @@ if not windows_check(): _extra_link_args = [ ] + _include_dirs = [ './libtorrent', './libtorrent/include', './libtorrent/include/libtorrent', - '/usr/include/python' + python_version ] _libraries = [ @@ -133,19 +134,12 @@ _libraries = [ 'boost_python', 'z', 'pthread', - 'ssl', ] if windows_check(): - _extra_link_args = _extra_link_args + [ - '-L./win32/lib' - ] - _include_dirs.remove('/usr/include/python' + python_version) - _include_dirs = _include_dirs + [ - './win32/include' - ] - _libraries.remove('ssl') - _libraries = _libraries + [ + _extra_link_args += ['-L./win32/lib'] + _include_dirs += ['./win32/include'] + _libraries += [ 'ssleay32MT', 'libeay32MT', 'advapi32', @@ -153,7 +147,10 @@ if windows_check(): 'gdi32', 'ws2_32' ] - +else: + _include_dirs += ['/usr/include/python' + python_version] + _libraries += ['ssl'] + _sources = glob.glob("./libtorrent/src/*.cpp") + \ glob.glob("./libtorrent/src/kademlia/*.cpp") + \ glob.glob("./libtorrent/bindings/python/src/*.cpp") From 586543917a903caac30248b56fe3d27b166c478a Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 3 Feb 2008 03:24:36 +0000 Subject: [PATCH 0438/1009] add proper boost path for windows --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a278bda6b..ee8bf6962 100644 --- a/setup.py +++ b/setup.py @@ -121,6 +121,9 @@ if not windows_check(): _extra_link_args = [ ] +_library_dirs = [ +] + _include_dirs = [ './libtorrent', './libtorrent/include', @@ -138,7 +141,8 @@ _libraries = [ if windows_check(): _extra_link_args += ['-L./win32/lib'] - _include_dirs += ['./win32/include'] + _include_dirs += ['./win32/include', 'C:/Program Files/boost/boost_1_34_1'] + _library_dirs += ['C:/Program Files/boost/boost_1_34_1/lib'] _libraries += [ 'ssleay32MT', 'libeay32MT', @@ -165,6 +169,7 @@ if not windows_check(): libtorrent = Extension( 'libtorrent', include_dirs = _include_dirs, + library_dirs = _library_dirs, libraries = _libraries, extra_compile_args = _extra_compile_args, extra_link_args = _extra_link_args, From d40387ada9e3a6ab46baf8fdd31738a6f475b4c1 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 3 Feb 2008 05:12:47 +0000 Subject: [PATCH 0439/1009] forgot z has to be zlib in windows --- setup.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index ee8bf6962..6c38f6e82 100644 --- a/setup.py +++ b/setup.py @@ -135,15 +135,15 @@ _libraries = [ 'boost_date_time', 'boost_thread', 'boost_python', - 'z', 'pthread', ] if windows_check(): _extra_link_args += ['-L./win32/lib'] - _include_dirs += ['./win32/include', 'C:/Program Files/boost/boost_1_34_1'] + _include_dirs += ['./win32/include/zlib', 'C:/Program Files/boost/boost_1_34_1'] _library_dirs += ['C:/Program Files/boost/boost_1_34_1/lib'] _libraries += [ + 'zlib', 'ssleay32MT', 'libeay32MT', 'advapi32', @@ -153,7 +153,10 @@ if windows_check(): ] else: _include_dirs += ['/usr/include/python' + python_version] - _libraries += ['ssl'] + _libraries += [ + 'ssl', + 'z' + ] _sources = glob.glob("./libtorrent/src/*.cpp") + \ glob.glob("./libtorrent/src/kademlia/*.cpp") + \ From f005a5d869adc1c400974b8e863369448ae4a07a Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 3 Feb 2008 05:37:10 +0000 Subject: [PATCH 0440/1009] remove file.cpp in win --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c38f6e82..a07100832 100644 --- a/setup.py +++ b/setup.py @@ -163,7 +163,12 @@ _sources = glob.glob("./libtorrent/src/*.cpp") + \ glob.glob("./libtorrent/bindings/python/src/*.cpp") # Remove file_win.cpp if not on windows -if not windows_check(): +if windows_check(): + for source in _sources: + if "file.cpp" in source: + _sources.remove(source) + break +else: for source in _sources: if "file_win.cpp" in source: _sources.remove(source) From 5e46ac1b42aa5e222448350ed7dcc5e43314c019 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 3 Feb 2008 09:05:55 +0000 Subject: [PATCH 0441/1009] fix library names for vc71 --- setup.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index a07100832..33b8208e0 100644 --- a/setup.py +++ b/setup.py @@ -130,19 +130,14 @@ _include_dirs = [ './libtorrent/include/libtorrent', ] -_libraries = [ - 'boost_filesystem', - 'boost_date_time', - 'boost_thread', - 'boost_python', - 'pthread', -] - if windows_check(): _extra_link_args += ['-L./win32/lib'] _include_dirs += ['./win32/include/zlib', 'C:/Program Files/boost/boost_1_34_1'] _library_dirs += ['C:/Program Files/boost/boost_1_34_1/lib'] - _libraries += [ + _libraries = [ + 'boost_filesystem-vc71-mt-1_34_1', + 'boost_date_time-vc71-mt-1_34_1', + 'boost_thread-vc71-mt-1_34_1 'zlib', 'ssleay32MT', 'libeay32MT', @@ -154,6 +149,11 @@ if windows_check(): else: _include_dirs += ['/usr/include/python' + python_version] _libraries += [ + 'boost_filesystem', + 'boost_date_time', + 'boost_thread', + 'boost_python', + 'pthread', 'ssl', 'z' ] From ea49afb9c379b53a2cab33b9c8a4c7a3bca419ce Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 3 Feb 2008 16:28:06 +0000 Subject: [PATCH 0442/1009] Fix tracker scraping in tracker_reply_alert. --- deluge/core/torrentmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index e893b56ee..2f2f24a74 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -486,7 +486,7 @@ class TorrentManager(component.Component): if alert.handle.status().num_complete == -1 or \ alert.handle.status().num_incomplete == -1: # We didn't get peer information, so lets send a scrape request - self.scrape_tracker(torrent_id) + self.torrents[torrent_id].scrape_tracker() def on_alert_tracker_announce(self, alert): log.debug("on_alert_tracker_announce") From cc38bbdfbff0312c69550cf842aa91ba3bb908c3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 3 Feb 2008 22:45:23 +0000 Subject: [PATCH 0443/1009] Fix typo. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33b8208e0..28fde34e2 100644 --- a/setup.py +++ b/setup.py @@ -137,7 +137,7 @@ if windows_check(): _libraries = [ 'boost_filesystem-vc71-mt-1_34_1', 'boost_date_time-vc71-mt-1_34_1', - 'boost_thread-vc71-mt-1_34_1 + 'boost_thread-vc71-mt-1_34_1', 'zlib', 'ssleay32MT', 'libeay32MT', From 22f6477d695137e2d62ee3d7a7e18cfc695cf693 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 3 Feb 2008 22:50:06 +0000 Subject: [PATCH 0444/1009] Fix another typo. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 28fde34e2..fe139e88e 100644 --- a/setup.py +++ b/setup.py @@ -148,7 +148,7 @@ if windows_check(): ] else: _include_dirs += ['/usr/include/python' + python_version] - _libraries += [ + _libraries = [ 'boost_filesystem', 'boost_date_time', 'boost_thread', From 3af0ea2e9f280ac30f107ea476ba1dcc69756382 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 4 Feb 2008 07:28:17 +0000 Subject: [PATCH 0445/1009] Fix exception in get_selected_torrents(). --- deluge/ui/gtkui/torrentview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 1c881758f..488105789 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -383,7 +383,8 @@ class TorrentView(listview.ListView, component.Component): row = self.model_filter.get_iter(path) except Exception, e: log.debug("Unable to get iter from path: %s", e) - + continue + child_row = self.model_filter.convert_iter_to_child_iter(None, row) child_row = self.model_filter.get_model().convert_iter_to_child_iter(child_row) if self.liststore.iter_is_valid(child_row): From f7c76443624b226f05f23dcc1c32dd01dd14f9fb Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 18:17:14 +0000 Subject: [PATCH 0446/1009] fix error in lib/readme.txt --- deluge/ui/webui/webui_plugin/lib/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/lib/readme.txt b/deluge/ui/webui/webui_plugin/lib/readme.txt index 50a6a66c2..35d23a8ec 100644 --- a/deluge/ui/webui/webui_plugin/lib/readme.txt +++ b/deluge/ui/webui/webui_plugin/lib/readme.txt @@ -4,7 +4,7 @@ They should be usable outside of deluge. Disclaimer: Some may have been adapted to work better with deluge. -But they will import other parts of deluge or Webui. +But they will not import other parts of deluge or Webui. LICENCE: All components are GPL compatible. From ab37facd5475aa0c155105362a72fe76423ea09d Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 18:57:36 +0000 Subject: [PATCH 0447/1009] max 2 digits after . for %-completed --- deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html index bdc5859b8..4f53e23c4 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html @@ -5,7 +5,7 @@ $def with (torrent)
      - $torrent.progress %
      + $("%.2f" % torrent.progress) %
      ', u'', '', u'
      %s', False, filter) + + def as_ul(self, filter = None): + "Returns this form rendered as HTML
    • s -- excluding the
        ." + return self._html_output_filtered(u'
      • %(errors)s%(label)s %(field)s%(help_text)s
      • ', u'
      • %s
      • ', '', u' %s', False , filter) + + def as_p(self , filter = None): + "Returns this form rendered as HTML

        s." + return self._html_output_filtered(u'

        %(label)s %(field)s%(help_text)s

        ', u'

        %s

        ', '

        ', u' %s', True, filter) + +class Form(newforms.Form): + info = "" + title = "No Title" + def __init__(self,data = None): + if data == None: + data = self.initial_data() + newforms.Form.__init__(self,data) + + def initial_data(self): + "override in subclass" + return None + + def start_save(self): + "called by config_page" + data = web.Storage(self.clean_data) + self.validate(data) + self.save(data) + self.post_save() + + def save(self, vars): + "override in subclass" + raise NotImplementedError() + + def post_save(self): + pass + + def validate(self, data): + pass + + +#convenience Input Fields. +class CheckBox(newforms.BooleanField): + "Non Required BooleanField,why the f is it required by default?" + def __init__(self,label, **kwargs): + newforms.BooleanField.__init__(self,label=label,required=False,**kwargs) + +class IntChoiceField(newforms.ChoiceField): + """same as ChoiceField, but returns an int + hint : Use IntChoiceField(choices=enumerate("list","of","strings"])) + for index-based values on a list of strings. + """ + def __init__(self, label, choices, **kwargs): + newforms.ChoiceField.__init__(self, label=label, choices=choices,**kwargs) + + def clean(self, value): + return int(newforms.ChoiceField.clean(self, value)) + +class MultipleChoice(newforms.MultipleChoiceField): + #temp/test/debug!! + "Non Required MultipleChoiceField,why the f is it required by default?" + def __init__(self, label, choices, **kwargs): + newforms.MultipleChoiceField.__init__(self, label=label, choices=choices, + widget=newforms.CheckboxSelectMultiple, required=False) + +class ServerFolder(newforms.CharField): + def __init__(self, label, **kwargs): + newforms.CharField.__init__(self, label=label,**kwargs) + + def clean(self, value): + value = value.rstrip('/').rstrip('\\') + self.validate(value) + return newforms.CharField.clean(self, value) + + def validate(self, value): + if (value and not os.path.isdir(value)): + raise newforms.ValidationError(_("This folder does not exist.")) + +class Password(newforms.CharField): + def __init__(self, label, **kwargs): + newforms.CharField.__init__(self, label=label, widget=newforms.PasswordInput, + **kwargs) + +#Deluge specific: +class _DelugeIntInput(newforms.TextInput): + """ + because deluge-floats are edited as ints. + """ + def render(self, name, value, attrs=None): + try: + value = int(float(value)) + if value == -1: + value = _("Unlimited") + except: + pass + return newforms.TextInput.render(self, name, value, attrs) + +class DelugeInt(newforms.IntegerField): + def __init__(self, label , **kwargs): + newforms.IntegerField.__init__(self, label=label, min_value=-1, + max_value=sys.maxint, widget=_DelugeIntInput, **kwargs) + + def clean(self, value): + if str(value).lower() == _('Unlimited').lower(): + value = -1 + return int(newforms.IntegerField.clean(self, value)) + +class DelugeFloat(DelugeInt): + def clean(self, value): + return int(DelugeInt.clean(self, value)) + +#/fields + + + + + diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index a77d31073..e5684560e 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -55,7 +55,7 @@ $for t in torrent_list:
        -
        $_('Total Size'):$fspeed(torrent.total_size)
        $fsize(torrent.total_size)
        $_('# Of Files'): $torrent.num_files
        From 6dbadf877da47bb2c7a01dc12414e48559ff0616 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 20:26:11 +0000 Subject: [PATCH 0448/1009] mimic gtk status-bar->images --- deluge/ui/webui/webui_plugin/render.py | 6 ++++++ .../static/images/tango/connections.png | Bin 0 -> 456 bytes .../templates/advanced/part_stats.html | 17 +++++++++-------- deluge/ui/webui/webui_plugin/utils.py | 2 ++ 4 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 deluge/ui/webui/webui_plugin/static/images/tango/connections.png diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index f6fca2f94..c04ee42fc 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -115,6 +115,11 @@ def altrow(reset = False): irow = irow % 2 return "altrow%s" % irow +def deluge_int(val): + if val == -1 : + return _("Unlimited") + return val + template.Template.globals.update({ 'sort_head': template_sort_head, 'part_stats':template_part_stats, @@ -123,6 +128,7 @@ template.Template.globals.update({ '_': _ , #gettext/translations 'str': str, #because % in templetor is broken. 'int':int, + 'deluge_int':deluge_int, 'sorted': sorted, 'altrow':altrow, 'get_config': get_config, diff --git a/deluge/ui/webui/webui_plugin/static/images/tango/connections.png b/deluge/ui/webui/webui_plugin/static/images/tango/connections.png new file mode 100644 index 0000000000000000000000000000000000000000..b57c65c86a666d62dc2120e6410ddd6cfc142857 GIT binary patch literal 456 zcmV;(0XP1MP))` zICpaqaV&^KTSINZ*0wQ7;?Ncwnndx&yZ7Gjp1V)r6Pdkj9Y_!yXG%aZF*7|&I2!XT zR!h(5c`X@aKsXv>A$3hC@00Iq!5QIVsvB@#O0R#XK_eE^Gt5JejPqX_> zje=FH@FFH?cPyG8rc0yrSyeUSIfY>u=y{FGTdS9m0dO4W2SHUM%q<;y8r)rO0$?}Z zT{{?@03o_;@%XrlwNQA(?!jtD^Fvn{TB0mVyjB~uJD)ULZr0rKf!Ba(m%&r90#5gJ zSs8J$t}LfqosuMRlFMe4K~b$__@1<0uY2Z|{-$rwH+-)Epj}<~8l35QE&0ozPp7wb ycKr;JYpd&+rs>()X9au>D3{CpH -$if env == '0.6': - $:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png') - -$_('Connections') : $stats.num_connections ($stats.max_num_connections) - -$_('Down Speed') : $stats.download_rate ($stats.max_download) - -$_('Up Speed') : $stats.upload_rate ($stats.max_upload) + $stats.num_connections ($deluge_int(stats.max_num_connections)) + + $stats.download_rate ($deluge_int(stats.max_download)) + + $stats.upload_rate ($deluge_int(stats.max_upload)) + +DHT: $stats.dht_nodes + +$:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png') diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index e84e0d2ab..07d3f9d8b 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -122,6 +122,8 @@ def get_stats(): ws.async_proxy.get_num_connections(dict_cb("num_connections",stats)) ws.async_proxy.get_config_value(dict_cb('max_num_connections',stats) ,"max_connections_global") + ws.async_proxy.get_dht_nodes(dict_cb('dht_nodes',stats)) + ws.async_proxy.force_call(block=True) From 6402b95099b253dedfb80cde4b82685df4f242bf Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 20:32:26 +0000 Subject: [PATCH 0449/1009] dht image --- .../ui/webui/webui_plugin/static/images/dht16.png | Bin 0 -> 607 bytes .../templates/advanced/part_stats.html | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 deluge/ui/webui/webui_plugin/static/images/dht16.png diff --git a/deluge/ui/webui/webui_plugin/static/images/dht16.png b/deluge/ui/webui/webui_plugin/static/images/dht16.png new file mode 100644 index 0000000000000000000000000000000000000000..be59e5e61d3887445f2bc094dc230348a6539582 GIT binary patch literal 607 zcmV-l0-*hgP)qz*w7TpSVv)2#$V^iCnD&_Pt_)}A;NBPd7<;!vVkTJt(wPOkC% zrtf?I|M@<6d5t8(D)^aRAP*b?YQW8Gs{DlilnLR2pV<%013Mz`0?uSp<(F}=73r_E zfB*)#78N|t>kRuuMBf>=T@AH2p$_0R(BpaD3t%vM`B@zN{p*ic#+YNZTJ1W})078* zlYil{L({V#=azE7TBv>dyfV|VZ+lMC+hj6XjqLmh%p-|(QRcwF*dWlBUl`q;Ul{H0 zy^uT$v;(IkAqm%Y`&`$Za$Pqi36n1B1TYPB0X|??96lIh`XtR7V>*F7lIH9Z7SL94 zs6*iPWD^8P0oe_PNne2a#k $stats.upload_rate ($deluge_int(stats.max_upload)) -DHT: $stats.dht_nodes + $stats.dht_nodes $:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png') From 7c0a997bbf3feba493cf348de83d5b06b746bc7c Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 20:36:35 +0000 Subject: [PATCH 0450/1009] move settings-link --- .../ui/webui/webui_plugin/templates/advanced/part_stats.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html index b296e3bd0..f5f297574 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html @@ -15,6 +15,7 @@ $#end $:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png') +$:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png')
        @@ -29,7 +30,7 @@ $:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png') $stats.dht_nodes -$:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png') + From 75a626b28db920d39ff22f4d074132ddee42ea72 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 21:06:02 +0000 Subject: [PATCH 0451/1009] more minor stuff on status bar --- .../webui_plugin/templates/advanced/part_stats.html | 12 ++++++------ .../templates/advanced/static/advanced.css | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html index f5f297574..00a2a4548 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html @@ -2,7 +2,7 @@ $def with (stats)
        - +[ $_('Auto refresh:') $if getcookie('auto_refresh') == '1': ($getcookie('auto_refresh_secs')) $_('seconds')   @@ -11,7 +11,7 @@ $if getcookie('auto_refresh') == '1': $else: $_('Off')   $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') -$#end +] $:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png') @@ -22,13 +22,13 @@ $:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system - $stats.num_connections ($deluge_int(stats.max_num_connections)) +$stats.num_connections ($deluge_int(stats.max_num_connections)) - $stats.download_rate ($deluge_int(stats.max_download)) +$stats.download_rate ($deluge_int(stats.max_download)) - $stats.upload_rate ($deluge_int(stats.max_upload)) +$stats.upload_rate ($deluge_int(stats.max_upload)) - $stats.dht_nodes +$stats.dht_nodes diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index a4381fd13..50f2cd2b0 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -145,7 +145,7 @@ body.inner { #refresh_panel { -moz-border-radius:0px; - width:350px; + width:500px; position:fixed; bottom:0px; right:0px; @@ -187,6 +187,10 @@ body.inner { #stats_panel button:hover { text-decoration: underline; } +#stats_panel img{ + position:relative; + top:3px; +} #category_panel { From 737b95250d69a964844f904a15d8cad6c5e2740f Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 4 Feb 2008 21:21:02 +0000 Subject: [PATCH 0452/1009] fix static-fileserver license --- deluge/ui/webui/webui_plugin/lib/static_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/lib/static_handler.py b/deluge/ui/webui/webui_plugin/lib/static_handler.py index d5b706cca..829d97792 100644 --- a/deluge/ui/webui/webui_plugin/lib/static_handler.py +++ b/deluge/ui/webui/webui_plugin/lib/static_handler.py @@ -1,6 +1,6 @@ #!/usr/bin/env python #(c) Martijn Voncken, mvoncken@gmail.com -#Same Licence as web.py 0.22 ->Public Domain +#Same Licence as python 2.5 # """ static fileserving for web.py From 9a1d5e9bfae250fb0b7968d626ed31cec5c681ea Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 7 Feb 2008 07:20:17 +0000 Subject: [PATCH 0453/1009] Allow incomplete options dictionaries when adding a torrent. The missing keys will be replaced by defaults. --- deluge/core/torrentmanager.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 2f2f24a74..88a25f6b0 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -174,22 +174,27 @@ class TorrentManager(component.Component): handle = None # Check if options is None and load defaults + options_keys = [ + "compact_allocation", + "max_connections_per_torrent", + "max_upload_slots_per_torrent", + "max_upload_speed_per_torrent", + "max_download_speed_per_torrent", + "prioritize_first_last_pieces", + "download_location", + "add_paused", + "default_private" + ] + if options == None: - options_keys = [ - "compact_allocation", - "max_connections_per_torrent", - "max_upload_slots_per_torrent", - "max_upload_speed_per_torrent", - "max_download_speed_per_torrent", - "prioritize_first_last_pieces", - "download_location", - "add_paused", - "default_private" - ] options = {} for key in options_keys: options[key] = self.config[key] - + else: + for key in options_keys: + if not options.has_key(key): + options[key] = self.config[key] + if paused is None: paused = options["add_paused"] From 1f3452277a3a3fe03e824684faca30e864faba8a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 7 Feb 2008 22:53:51 +0000 Subject: [PATCH 0454/1009] Add 'file_priorities' status key to torrent. --- deluge/core/torrent.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 0d74758a0..5999dafc0 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -255,7 +255,8 @@ class Torrent: "trackers": self.trackers, "tracker_status": self.tracker_status, "save_path": self.save_path, - "files": self.files + "files": self.files, + "file_priorities":, self.file_priorities } self.status = None self.torrent_info = None From 055e42776aa16c0bc93214cd1daf8e6374062b2a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 7 Feb 2008 22:55:04 +0000 Subject: [PATCH 0455/1009] Fix last. --- deluge/core/torrent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 5999dafc0..156e5f71a 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -256,7 +256,7 @@ class Torrent: "tracker_status": self.tracker_status, "save_path": self.save_path, "files": self.files, - "file_priorities":, self.file_priorities + "file_priorities": self.file_priorities } self.status = None self.torrent_info = None From 6342fe2e0869b47f1d56dce5f47036af2237e9c9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 7 Feb 2008 23:19:17 +0000 Subject: [PATCH 0456/1009] Set file_priorities default value to [] if not priorities have been set. --- deluge/core/torrent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 156e5f71a..a7d14e7b0 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -101,6 +101,7 @@ class Torrent: # Files dictionary self.files = self.get_files() + self.file_priorities = [] def set_tracker_status(self, status): """Sets the tracker status""" From f1d488a09ce2d6698acc8f2817f856fc0a905ae2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 8 Feb 2008 00:46:10 +0000 Subject: [PATCH 0457/1009] Fix pause/resume all torrents in the SystemTray. --- deluge/ui/gtkui/systemtray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index f2fa2c593..1c78190fc 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -270,11 +270,11 @@ class SystemTray(component.Component): def on_menuitem_pause_all_activate(self, menuitem): log.debug("on_menuitem_pause_all_activate") - self.core.pause_all_torrents() + client.pause_all_torrents() def on_menuitem_resume_all_activate(self, menuitem): log.debug("on_menuitem_resume_all_activate") - self.core.resume_all_torrents() + client.resume_all_torrents() def on_menuitem_quit_activate(self, menuitem): log.debug("on_menuitem_quit_activate") From ae792ccb21361d5ce6218efbee910913ce0487a1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 8 Feb 2008 05:43:14 +0000 Subject: [PATCH 0458/1009] Split up function calls in get_torrent_status() and add 'file_progress' key. Patch from Sadrul. --- deluge/core/torrent.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index a7d14e7b0..23cfdec60 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -229,11 +229,6 @@ class Torrent: distributed_copies = 0.0 full_status = { - "name": self.torrent_info.name(), - "total_size": self.torrent_info.total_size(), - "num_files": self.torrent_info.num_files(), - "num_pieces": self.torrent_info.num_pieces(), - "piece_length": self.torrent_info.piece_length(), "distributed_copies": distributed_copies, "total_done": self.status.total_done, "total_uploaded": self.total_uploaded + self.status.total_payload_upload, @@ -250,8 +245,6 @@ class Torrent: "total_peers": self.status.num_incomplete, "total_seeds": self.status.num_complete, "total_wanted": self.status.total_wanted, - "eta": self.get_eta(), - "ratio": self.get_ratio(), "tracker": self.status.current_tracker, "trackers": self.trackers, "tracker_status": self.tracker_status, @@ -259,6 +252,18 @@ class Torrent: "files": self.files, "file_priorities": self.file_priorities } + + fns = { + "name" : self.torrent_info.name, + "total_size" : self.torrent_info.total_size, + "num_files" : self.torrent_info.num_files, + "num_pieces" : self.torrent_info.num_pieces, + "piece_length" : self.torrent_info.piece_length, + "eta" : self.get_eta, + "ratio" : self.get_ratio, + "file_progress" : self.handle.file_progress + } + self.status = None self.torrent_info = None @@ -271,6 +276,8 @@ class Torrent: for key in keys: if key in full_status: status_dict[key] = full_status[key] + elif key in fns: + status_dict[key] = fns[key]() return status_dict From eeac163a9dd828c1f8321a393988f7399a7788ba Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 8 Feb 2008 18:33:35 +0000 Subject: [PATCH 0459/1009] use utf-8 infinity symbol instead of Unlimited/Infinity string --- deluge/ui/webui/webui_plugin/render.py | 9 +++++++-- .../templates/advanced/static/advanced.css | 1 + deluge/ui/webui/webui_plugin/utils.py | 4 ++-- deluge/ui/webui/webui_plugin/webserver_common.py | 12 ------------ 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index c04ee42fc..dbcf5ca1b 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -117,7 +117,12 @@ def altrow(reset = False): def deluge_int(val): if val == -1 : - return _("Unlimited") + return "∞" + return val + +def ftime(val): + if val <= 0: + return _("∞") return val template.Template.globals.update({ @@ -135,7 +140,7 @@ template.Template.globals.update({ 'self_url': self_url, 'fspeed': common.fspeed, 'fsize': common.fsize, - 'ftime':common.ftime, + 'ftime':ftime, 'render': render, #for easy resuse of templates 'rev': 'rev.%s' % (REVNO, ), 'version': VERSION, diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index 50f2cd2b0..b8135c2d8 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -190,6 +190,7 @@ body.inner { #stats_panel img{ position:relative; top:3px; + margin-right:3px; } diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 07d3f9d8b..2ebdb708f 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -136,12 +136,12 @@ def get_stats(): if stats.max_upload < 0: - stats.max_upload = _("Unlimited") + stats.max_upload = _("∞") else: stats.max_upload = "%s KiB/s" % stats.max_upload if stats.max_download < 0: - stats.max_download = _("Unlimited") + stats.max_download = _("∞") else: stats.max_download = "%s KiB/s" % stats.max_download diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index ab332a119..f9b901601 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -82,18 +82,6 @@ STATE_MESSAGES = (_("Queued"), _("Seeding"), _("Allocating")) -SPEED_VALUES = [ - (-1, 'Unlimited'), - (5, '5.0 Kib/sec'), - (10, '10.0 Kib/sec'), - (15, '15.0 Kib/sec'), - (25, '25.0 Kib/sec'), - (30, '30.0 Kib/sec'), - (50, '50.0 Kib/sec'), - (80, '80.0 Kib/sec'), - (300, '300.0 Kib/sec'), - (500, '500.0 Kib/sec') - ] CONFIG_DEFAULTS = { "port":8112, "button_style":2, From 1c33177a63423b73ea7f74b2243b0e4a6ac238db Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 8 Feb 2008 18:47:21 +0000 Subject: [PATCH 0460/1009] torrent-index use '-' for unknown/empty values --- .../webui_plugin/templates/advanced/index.html | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index 4ef2f996d..49d549185 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -100,19 +100,28 @@ $for torrent in torrent_list: $if (not get('category')):
        - - + + + $else: + - From eee68666efcd77146ad1be9887dc78037e281ab8 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 8 Feb 2008 18:56:35 +0000 Subject: [PATCH 0461/1009] torrent-index no - , mimic gtk ui and use 0 or '' --- .../ui/webui/webui_plugin/templates/advanced/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index 49d549185..a77d31073 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -104,24 +104,24 @@ $for torrent in torrent_list: $if torrent.total_seeds != -1: $torrent.num_seeds ($torrent.total_seeds) $else: - - + 0 $else: - - + 0 From c7c1ea26f9ccc7ec5a7b23de033cf0b2635b5a5b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 8 Feb 2008 22:51:41 +0000 Subject: [PATCH 0462/1009] Fix resuming all torrents. --- deluge/core/torrentmanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 88a25f6b0..6417390a9 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -323,7 +323,7 @@ class TorrentManager(component.Component): torrent_was_paused = False for key in self.torrents.keys(): try: - self.torrents[key].handle.pause() + self.torrents[key].pause() torrent_was_paused = True except: log.warning("Unable to pause torrent %s", key) @@ -334,7 +334,7 @@ class TorrentManager(component.Component): """Resumes all torrents.. Returns True if at least 1 torrent is resumed""" torrent_was_resumed = False for key in self.torrents.keys(): - if self.resume(key): + if self.torrents[key].resume(): torrent_was_resumed = True else: log.warning("Unable to resume torrent %s", key) From cb59fd31aaabb09ed55b7a01d12202ba75631ad4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 9 Feb 2008 03:37:42 +0000 Subject: [PATCH 0463/1009] More resume/pause all fixes. --- deluge/core/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index f6117d346..45c67d6b8 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -503,12 +503,12 @@ class Core( def torrent_all_paused(self): """Emitted when all torrents have been paused""" log.debug("torrent_all_paused signal emitted") - self.signals.emit("torrent_all_paused", torrent_id) + self.signals.emit("torrent_all_paused") def torrent_all_resumed(self): """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") - self.signals.emit("torrent_all_resumed", torrent_id) + self.signals.emit("torrent_all_resumed") def config_value_changed(self, key, value): """Emitted when a config value has changed""" From 40ac3d1ad176c3a5361aec898a495389f5315a9e Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 10 Feb 2008 04:36:19 +0000 Subject: [PATCH 0464/1009] sync libtorrent 1991 --- .../include/libtorrent/kademlia/node.hpp | 9 +++++--- .../include/libtorrent/kademlia/observer.hpp | 14 +++++++++++-- libtorrent/src/kademlia/closest_nodes.cpp | 3 +++ libtorrent/src/kademlia/find_data.cpp | 3 +++ libtorrent/src/kademlia/node.cpp | 10 +++++++-- libtorrent/src/kademlia/refresh.cpp | 6 ++++++ libtorrent/src/kademlia/rpc_manager.cpp | 3 +++ libtorrent/src/pe_crypto.cpp | 3 +-- libtorrent/src/peer_connection.cpp | 3 +++ libtorrent/src/session_impl.cpp | 21 +++++++++++++++---- 10 files changed, 62 insertions(+), 13 deletions(-) diff --git a/libtorrent/include/libtorrent/kademlia/node.hpp b/libtorrent/include/libtorrent/kademlia/node.hpp index ee75e7f0a..d93872db9 100644 --- a/libtorrent/include/libtorrent/kademlia/node.hpp +++ b/libtorrent/include/libtorrent/kademlia/node.hpp @@ -139,9 +139,12 @@ public: void timeout() {} void reply(msg const& r) { - m_rpc.invoke(messages::announce_peer, r.addr - , observer_ptr(new (m_rpc.allocator().malloc()) announce_observer( - m_rpc.allocator(), m_info_hash, m_listen_port, r.write_token))); + observer_ptr o(new (m_rpc.allocator().malloc()) announce_observer( + m_rpc.allocator(), m_info_hash, m_listen_port, r.write_token)); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif + m_rpc.invoke(messages::announce_peer, r.addr, o); m_fun(r.peers, m_info_hash); } void abort() {} diff --git a/libtorrent/include/libtorrent/kademlia/observer.hpp b/libtorrent/include/libtorrent/kademlia/observer.hpp index 141460dc0..073f453bc 100644 --- a/libtorrent/include/libtorrent/kademlia/observer.hpp +++ b/libtorrent/include/libtorrent/kademlia/observer.hpp @@ -56,9 +56,16 @@ struct observer : boost::noncopyable : sent(time_now()) , pool_allocator(p) , m_refs(0) - {} + { +#ifndef NDEBUG + m_in_constructor = true; +#endif + } - virtual ~observer() {} + virtual ~observer() + { + TORRENT_ASSERT(!m_in_constructor); + } // these two callbacks lets the observer add // information to the message before it's sent @@ -79,6 +86,9 @@ struct observer : boost::noncopyable udp::endpoint target_addr; ptime sent; +#ifndef NDEBUG + bool m_in_constructor; +#endif private: boost::pool<>& pool_allocator; // reference counter for intrusive_ptr diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp index 3fbbef07d..7551e806f 100644 --- a/libtorrent/src/kademlia/closest_nodes.cpp +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -101,6 +101,9 @@ closest_nodes::closest_nodes( void closest_nodes::invoke(node_id const& id, udp::endpoint addr) { observer_ptr o(new (m_rpc.allocator().malloc()) closest_nodes_observer(this, id, m_target)); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif m_rpc.invoke(messages::find_node, addr, o); } diff --git a/libtorrent/src/kademlia/find_data.cpp b/libtorrent/src/kademlia/find_data.cpp index 5db80fe80..9dc161888 100644 --- a/libtorrent/src/kademlia/find_data.cpp +++ b/libtorrent/src/kademlia/find_data.cpp @@ -110,6 +110,9 @@ void find_data::invoke(node_id const& id, asio::ip::udp::endpoint addr) } observer_ptr o(new (m_rpc.allocator().malloc()) find_data_observer(this, id, m_target)); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif m_rpc.invoke(messages::get_peers, addr, o); } diff --git a/libtorrent/src/kademlia/node.cpp b/libtorrent/src/kademlia/node.cpp index be42c8635..5b919393c 100644 --- a/libtorrent/src/kademlia/node.cpp +++ b/libtorrent/src/kademlia/node.cpp @@ -274,8 +274,11 @@ namespace for (std::vector::const_iterator i = v.begin() , end(v.end()); i != end; ++i) { - rpc.invoke(messages::get_peers, i->addr, observer_ptr( - new (rpc.allocator().malloc()) get_peers_observer(ih, listen_port, rpc, f))); + observer_ptr o(new (rpc.allocator().malloc()) get_peers_observer(ih, listen_port, rpc, f)); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif + rpc.invoke(messages::get_peers, i->addr, o); nodes = true; } } @@ -291,6 +294,9 @@ void node_impl::add_node(udp::endpoint node) // ping the node, and if we get a reply, it // will be added to the routing table observer_ptr o(new (m_rpc.allocator().malloc()) null_observer(m_rpc.allocator())); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif m_rpc.invoke(messages::ping, node, o); } diff --git a/libtorrent/src/kademlia/refresh.cpp b/libtorrent/src/kademlia/refresh.cpp index ce94ca93b..916b6d06a 100644 --- a/libtorrent/src/kademlia/refresh.cpp +++ b/libtorrent/src/kademlia/refresh.cpp @@ -105,6 +105,9 @@ void refresh::invoke(node_id const& nid, udp::endpoint addr) { observer_ptr o(new (m_rpc.allocator().malloc()) refresh_observer( this, nid, m_target)); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif m_rpc.invoke(messages::find_node, addr, o); } @@ -156,6 +159,9 @@ void refresh::invoke_pings_or_finish(bool prevent_request) { observer_ptr o(new (m_rpc.allocator().malloc()) ping_observer( this, node.id)); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif m_rpc.invoke(messages::ping, node.addr, o); ++m_active_pings; ++m_leftover_nodes_iterator; diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp index e4019bab8..7295cf0bf 100644 --- a/libtorrent/src/kademlia/rpc_manager.cpp +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -430,6 +430,9 @@ void rpc_manager::reply_with_ping(msg& m) io::write_uint16(m_next_transaction_id, out); observer_ptr o(new (allocator().malloc()) null_observer(allocator())); +#ifndef NDEBUG + o->m_in_constructor = false; +#endif TORRENT_ASSERT(!m_transactions[m_next_transaction_id]); o->sent = time_now(); o->target_addr = m.addr; diff --git a/libtorrent/src/pe_crypto.cpp b/libtorrent/src/pe_crypto.cpp index a865b4c73..d9298e989 100644 --- a/libtorrent/src/pe_crypto.cpp +++ b/libtorrent/src/pe_crypto.cpp @@ -61,8 +61,7 @@ namespace libtorrent TORRENT_ASSERT(sizeof(m_dh_prime) == DH_size(m_DH)); - DH_generate_key(m_DH); - if (m_DH->pub_key == 0) + if (DH_generate_key(m_DH) == 0 || m_DH->pub_key == 0) { DH_free(m_DH); throw std::bad_alloc(); diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 0e120e3c8..8afc894de 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -1705,6 +1705,9 @@ namespace libtorrent TORRENT_ASSERT(block.block_index < t->torrent_file().piece_size(block.piece_index)); TORRENT_ASSERT(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); TORRENT_ASSERT(!t->have_piece(block.piece_index)); + TORRENT_ASSERT(std::find(m_download_queue.begin(), m_download_queue.end(), block) == m_download_queue.end()); + TORRENT_ASSERT(std::find(m_request_queue.begin(), m_request_queue.end(), block) == m_request_queue.end()); + piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 209e833c3..a93ad7cb0 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -1263,10 +1263,21 @@ namespace detail torrent& t = *i->second; if (t.want_more_peers()) { - if (t.try_connect_peer()) + try { - --max_connections; - steps_since_last_connect = 0; + if (t.try_connect_peer()) + { + --max_connections; + steps_since_last_connect = 0; + } + } + catch (std::bad_alloc&) + { + // we ran out of memory trying to connect to a peer + // lower the global limit to the number of peers + // we already have + m_max_connections = num_connections(); + if (m_max_connections < 2) m_max_connections = 2; } } ++m_next_connect_torrent; @@ -2406,8 +2417,10 @@ namespace detail m_buffer_usage_logger << log_time() << " protocol_buffer: " << (m_buffer_allocations * send_buffer_size) << std::endl; #endif - return std::make_pair((char*)m_send_buffers.ordered_malloc(num_buffers) + std::pair ret((char*)m_send_buffers.ordered_malloc(num_buffers) , num_buffers * send_buffer_size); + if (ret.first == 0) throw std::bad_alloc(); + return ret; } void session_impl::free_buffer(char* buf, int size) From 4eabde05ffe4a1464c3f1c36a5f9a718112135cd Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 08:22:56 +0000 Subject: [PATCH 0465/1009] Introduce the null client. --- deluge/ui/null/__init__.py | 0 deluge/ui/null/deluge-shell.py | 243 +++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 deluge/ui/null/__init__.py create mode 100755 deluge/ui/null/deluge-shell.py diff --git a/deluge/ui/null/__init__.py b/deluge/ui/null/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/null/deluge-shell.py b/deluge/ui/null/deluge-shell.py new file mode 100755 index 000000000..796dec934 --- /dev/null +++ b/deluge/ui/null/deluge-shell.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python + +""" +deluge-shell: Deluge shell. +""" + +# deluge-shell: Deluge shell. +# +# Copyright (C) 2007, 2008 Sadrul Habib Chowdhury +# +# This application is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This application is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this application; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 +# USA + +import deluge.ui.client as client +import deluge.common as common +import readline + +import sys + +status_keys = ["state", + "save_path", + "tracker", + "next_announce", + "name", + "total_size", + "progress", + "num_seeds", + "total_seeds", + "num_peers", + "total_peers", + "eta", + "download_payload_rate", + "upload_payload_rate", + "ratio", + "distributed_copies", + "num_pieces", + "piece_length", + "total_done", + "files", + "file_priorities", + "file_progress", + ] + +def add_torrent(cmd): + """Add a torrent.""" + def show_usage(): + print "Usage: add [-p ;] ; [; ...]" + print " (Note that a ';' must follow a path)" + print "" + + if len(cmd) < 2: + show_usage() + return + + save_path = None + readpath = False + if cmd[1] == '-p': + if len(cmd) < 4: + show_usage() + return + del cmd[1] + readpath = True + else: + def _got_config(configs): + global save_path + save_path = configs['download_location'] + client.get_config(_got_config) + client.force_call() + + command = " ".join(cmd[1:]) + paths = command.split(';') + if readpath: + save_path = paths[0].strip() # Perhaps verify that the path exists? + client.set_config({'download_location': save_path}) + del paths[0] + + if not save_path: + print "There's no save-path specified. You must specify a path to save the downloaded files.\n" + return + + for iter in range(0, len(paths)): + paths[iter] = paths[iter].strip() + if len(paths[iter]) == 0: + del paths[iter] + + try: + client.add_torrent_file(paths) + client.force_call() + except Exception, msg: + print "*** Error:", str(msg), "\n" + + +def show_configs(cmd): + del cmd[0] + def _on_get_config(config): + for key in config: + if cmd and key not in cmd: continue + print "%s: %s" % (key, config[key]) + print "" + client.get_config(_on_get_config) + client.force_call() + +def show_state(state): + ts = common.TORRENT_STATE + return ts.keys()[ts.values().index(state)] + +def show_info(torrent, brief): + """Show information about a torrent.""" + def _got_torrent_status(state): + print "*** ID:", torrent + print "*** Name:", state['name'] + print "*** Path:", state['save_path'] + print "*** Completed:", common.fsize(state['total_done']) + "/" + common.fsize(state['total_size']) + print "*** Status:", show_state(state['state']) + if state['state'] in [3, 4, 5, 6]: + print "*** Download Speed:", common.fspeed(state['download_payload_rate']) + print "*** Upload Speed:", common.fspeed(state['upload_payload_rate']) + if state['state'] in [3, 4]: + print "*** ETA:", "%s" % common.ftime(state['eta']) + + if brief == False: + print "*** Seeders:", "%s (%s)" % (state['num_seeds'], state['total_seeds']) + print "*** Peers:", "%s (%s)" % (state['num_peers'], state['total_peers']) + print "*** Share Ratio:", "%.1f" % state['ratio'] + print "*** Availability:", "%.1f" % state['distributed_copies'] + print "*** Files:" + for i, file in enumerate(state['files']): + print "\t*", file['path'], "(%s)" % common.fsize(file['size']), "-", "%.1f%% completed" % (state['file_progress'][i] * 100) + print "" + pr = state['file_priorities'] + print pr + if len(pr) == 0: + pr = [1] * len(state['files']) + pr[0] = 2 + print "b", pr + client.set_torrent_file_priorities(torrent, pr) + client.get_torrent_status(_got_torrent_status, torrent, status_keys) + +def info_torrents(cmd): + """Show information about the torrents.""" + torrents = [] + def _got_session_state(tors): + for tor in tors: + torrents.append(tor) + client.get_session_state(_got_session_state) + client.force_call() + for tor in torrents: + if len(cmd) < 2: + show_info(tor, True) + elif cmd[1] == tor[0:len(cmd[1])]: + show_info(tor, False) + client.force_call() + +def exit(cmd): + """Terminate.""" + print "Thanks." + sys.exit(0) + +def show_help(cmd): + """Show help.""" + print "Available commands:" + for cmd, action, help in commands: + print "\t" + cmd + ": " + help + +def pause_torrent(cmd): + """Pause a torrent""" + if len(cmd) < 2: + print "Usage: pause [ ...]" + return + try: + client.pause_torrent(cmd[1:]) + except Exception, msg: + print "Error:", str(msg), "\n" + +def resume_torrent(cmd): + """Resume a torrent.""" + if len(cmd) < 2: + print "Usage: resume [ ...]" + return + try: + client.resume_torrent(cmd[1:]) + except Exception, msg: + print "Error:", str(msg), "\n" + +def remove_torrent(cmd): + """Remove a torrent.""" + if len(cmd) < 2: + print "Usage: rm [ ...]" + print " Use 'list' to see the list of torrents." + print "" + return + try: + client.remove_torrent(cmd[1:]) + except Exception, msg: + print "*** Error:", str(msg), "\n" + +commands = (('add', add_torrent, 'Add a torrent'), + ('configs', show_configs, 'Show configurations'), + ('exit', exit, 'Terminate'), + ('help', show_help, 'Show help about a command, or generic help'), + ('info', info_torrents, 'Show information about the torrents'), + ('pause', pause_torrent, 'Pause a torrent.'), + ('quit', exit, 'Terminate'), + ('resume', resume_torrent, 'Resume a torrent.'), + ('rm', remove_torrent, 'Remove a torrent'), +) + +client.set_core_uri("http://localhost:58846") + +print "Welcome to deluge-shell. Type 'help' to see a list of available commands." + +readline.read_init_file() +while True: + inp = raw_input("> ") + if len(inp) == 0: break + inp = inp.strip().split(" ") + + print "" + cmd = inp[0] + found = False + for command, action, help in commands: + if command != cmd: + continue + action(inp) + found = True + break + if not found: + print "Invalid command!" + show_help([]) + +print "Thanks." From 5f40e030b13213883330121f582a474711c89499 Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 09:40:14 +0000 Subject: [PATCH 0466/1009] Make 'deluge -u null' launch the null client. --- .../null/{deluge-shell.py => deluge_shell.py} | 40 ++++++++++--------- deluge/ui/ui.py | 6 ++- 2 files changed, 26 insertions(+), 20 deletions(-) rename deluge/ui/null/{deluge-shell.py => deluge_shell.py} (92%) diff --git a/deluge/ui/null/deluge-shell.py b/deluge/ui/null/deluge_shell.py similarity index 92% rename from deluge/ui/null/deluge-shell.py rename to deluge/ui/null/deluge_shell.py index 796dec934..a521d5556 100755 --- a/deluge/ui/null/deluge-shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -219,25 +219,27 @@ commands = (('add', add_torrent, 'Add a torrent'), client.set_core_uri("http://localhost:58846") -print "Welcome to deluge-shell. Type 'help' to see a list of available commands." +class NullUI: + def __init__(self, args): + print "Welcome to deluge-shell. Type 'help' to see a list of available commands." -readline.read_init_file() -while True: - inp = raw_input("> ") - if len(inp) == 0: break - inp = inp.strip().split(" ") + readline.read_init_file() + while True: + inp = raw_input("> ") + if len(inp) == 0: break + inp = inp.strip().split(" ") - print "" - cmd = inp[0] - found = False - for command, action, help in commands: - if command != cmd: - continue - action(inp) - found = True - break - if not found: - print "Invalid command!" - show_help([]) + print "" + cmd = inp[0] + found = False + for command, action, help in commands: + if command != cmd: + continue + action(inp) + found = True + break + if not found: + print "Invalid command!" + show_help([]) -print "Thanks." + print "Thanks." diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 6612ae1ed..1fc6138a5 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -59,4 +59,8 @@ class UI: log.info("Starting WebUI..") from deluge.ui.webui.webui import WebUI ui = WebUI(args) - + elif selected_ui == "null": + log.info("Starting NullUI..") + from deluge.ui.null.deluge_shell import NullUI + ui = NullUI(args) + From 3d26049aebf6d73cd791a44ab73dbf8c371faa0b Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 11:11:04 +0000 Subject: [PATCH 0467/1009] Rearrange almost all of the code. --- deluge/ui/null/deluge_shell.py | 374 +++++++++++++++++++-------------- 1 file changed, 218 insertions(+), 156 deletions(-) diff --git a/deluge/ui/null/deluge_shell.py b/deluge/ui/null/deluge_shell.py index a521d5556..28b645393 100755 --- a/deluge/ui/null/deluge_shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -53,169 +53,232 @@ status_keys = ["state", "file_progress", ] -def add_torrent(cmd): - """Add a torrent.""" - def show_usage(): +class Command: + def __init__(self): + pass + + def execute(self, cmd): + pass + + def usage(self): + print "" + + def help(self): + pass + +class CommandAdd(Command): + """Command to add a torrent.""" + def execute(self, cmd): + if len(cmd) < 2: + self.usage() + return + + save_path = None + readpath = False + if cmd[1] == '-p': + if len(cmd) < 4: + self.usage() + return + del cmd[1] + readpath = True + else: + def _got_config(configs): + global save_path + save_path = configs['download_location'] + client.get_config(_got_config) + client.force_call() + + command = " ".join(cmd[1:]) + paths = command.split(';') + if readpath: + save_path = paths[0].strip() # Perhaps verify that the path exists? + client.set_config({'download_location': save_path}) + del paths[0] + + if not save_path: + print "There's no save-path specified. You must specify a path to save the downloaded files.\n" + return + + for iter in range(0, len(paths)): + paths[iter] = paths[iter].strip() + if len(paths[iter]) == 0: + del paths[iter] + + try: + client.add_torrent_file(paths) + except Exception, msg: + print "*** Error:", str(msg), "\n" + + def usage(self): print "Usage: add [-p ;] ; [; ...]" print " (Note that a ';' must follow a path)" print "" - if len(cmd) < 2: - show_usage() - return + def help(self): + print "Add a torrent" - save_path = None - readpath = False - if cmd[1] == '-p': - if len(cmd) < 4: - show_usage() - return - del cmd[1] - readpath = True - else: - def _got_config(configs): - global save_path - save_path = configs['download_location'] - client.get_config(_got_config) - client.force_call() +class CommandConfig(Command): + def execute(self, cmd): + del cmd[0] + def _on_get_config(config): + for key in config: + if cmd and key not in cmd: continue + print "%s: %s" % (key, config[key]) + print "" + client.get_config(_on_get_config) - command = " ".join(cmd[1:]) - paths = command.split(';') - if readpath: - save_path = paths[0].strip() # Perhaps verify that the path exists? - client.set_config({'download_location': save_path}) - del paths[0] - - if not save_path: - print "There's no save-path specified. You must specify a path to save the downloaded files.\n" - return - - for iter in range(0, len(paths)): - paths[iter] = paths[iter].strip() - if len(paths[iter]) == 0: - del paths[iter] - - try: - client.add_torrent_file(paths) - client.force_call() - except Exception, msg: - print "*** Error:", str(msg), "\n" - - -def show_configs(cmd): - del cmd[0] - def _on_get_config(config): - for key in config: - if cmd and key not in cmd: continue - print "%s: %s" % (key, config[key]) + def usage(self): + print "Usage: config [key1 [key2 ...]]" print "" - client.get_config(_on_get_config) - client.force_call() -def show_state(state): - ts = common.TORRENT_STATE - return ts.keys()[ts.values().index(state)] + def help(self): + print "Show configuration values" -def show_info(torrent, brief): - """Show information about a torrent.""" - def _got_torrent_status(state): - print "*** ID:", torrent - print "*** Name:", state['name'] - print "*** Path:", state['save_path'] - print "*** Completed:", common.fsize(state['total_done']) + "/" + common.fsize(state['total_size']) - print "*** Status:", show_state(state['state']) - if state['state'] in [3, 4, 5, 6]: - print "*** Download Speed:", common.fspeed(state['download_payload_rate']) - print "*** Upload Speed:", common.fspeed(state['upload_payload_rate']) - if state['state'] in [3, 4]: - print "*** ETA:", "%s" % common.ftime(state['eta']) +class CommandExit(Command): + def execute(self, cmd): + print "Thanks" + sys.exit(0) - if brief == False: - print "*** Seeders:", "%s (%s)" % (state['num_seeds'], state['total_seeds']) - print "*** Peers:", "%s (%s)" % (state['num_peers'], state['total_peers']) - print "*** Share Ratio:", "%.1f" % state['ratio'] - print "*** Availability:", "%.1f" % state['distributed_copies'] - print "*** Files:" - for i, file in enumerate(state['files']): - print "\t*", file['path'], "(%s)" % common.fsize(file['size']), "-", "%.1f%% completed" % (state['file_progress'][i] * 100) - print "" - pr = state['file_priorities'] - print pr - if len(pr) == 0: - pr = [1] * len(state['files']) - pr[0] = 2 - print "b", pr - client.set_torrent_file_priorities(torrent, pr) - client.get_torrent_status(_got_torrent_status, torrent, status_keys) + def help(self): + print "Exit from the client." -def info_torrents(cmd): - """Show information about the torrents.""" - torrents = [] - def _got_session_state(tors): - for tor in tors: - torrents.append(tor) - client.get_session_state(_got_session_state) - client.force_call() - for tor in torrents: +class CommandHelp(Command): + def execute(self, cmd): if len(cmd) < 2: - show_info(tor, True) - elif cmd[1] == tor[0:len(cmd[1])]: - show_info(tor, False) - client.force_call() + print "Available commands:" + for cmd in sorted(commands.keys()): + print "\t*", "%s:" % cmd, + command = commands[cmd] + command.help() + else: + for c in cmd[1:]: + if c not in commands: + print "Unknown command:", c + else: + print "*", "%s:" % c, + command = commands[c] + command.help() + command.usage() -def exit(cmd): - """Terminate.""" - print "Thanks." - sys.exit(0) - -def show_help(cmd): - """Show help.""" - print "Available commands:" - for cmd, action, help in commands: - print "\t" + cmd + ": " + help - -def pause_torrent(cmd): - """Pause a torrent""" - if len(cmd) < 2: - print "Usage: pause [ ...]" - return - try: - client.pause_torrent(cmd[1:]) - except Exception, msg: - print "Error:", str(msg), "\n" - -def resume_torrent(cmd): - """Resume a torrent.""" - if len(cmd) < 2: - print "Usage: resume [ ...]" - return - try: - client.resume_torrent(cmd[1:]) - except Exception, msg: - print "Error:", str(msg), "\n" - -def remove_torrent(cmd): - """Remove a torrent.""" - if len(cmd) < 2: - print "Usage: rm [ ...]" - print " Use 'list' to see the list of torrents." + def usage(self): + print "Usage: help [cmd1 [cmd2 ...]]" print "" - return - try: - client.remove_torrent(cmd[1:]) - except Exception, msg: - print "*** Error:", str(msg), "\n" -commands = (('add', add_torrent, 'Add a torrent'), - ('configs', show_configs, 'Show configurations'), - ('exit', exit, 'Terminate'), - ('help', show_help, 'Show help about a command, or generic help'), - ('info', info_torrents, 'Show information about the torrents'), - ('pause', pause_torrent, 'Pause a torrent.'), - ('quit', exit, 'Terminate'), - ('resume', resume_torrent, 'Resume a torrent.'), - ('rm', remove_torrent, 'Remove a torrent'), -) + def help(self): + print "Show help" + +class CommandInfo(Command): + def execute(self, cmd): + torrents = [] + def _got_session_state(tors): + for tor in tors: + torrents.append(tor) + client.get_session_state(_got_session_state) + client.force_call() + for tor in torrents: + if len(cmd) < 2: + self.show_info(tor, True) + elif cmd[1] == tor[0:len(cmd[1])]: + self.show_info(tor, False) + + def usage(self): + print "Usage: info [ [ ...]]" + print " You can give the first few characters of a torrent-id to identify the torrent." + print "" + + def help(self): + print "Show information about the torrents" + + def show_info(self, torrent, brief): + def show_state(state): + ts = common.TORRENT_STATE + return ts.keys()[ts.values().index(state)] + def _got_torrent_status(state): + print "*** ID:", torrent + print "*** Name:", state['name'] + print "*** Path:", state['save_path'] + print "*** Completed:", common.fsize(state['total_done']) + "/" + common.fsize(state['total_size']) + print "*** Status:", show_state(state['state']) + if state['state'] in [3, 4, 5, 6]: + print "*** Download Speed:", common.fspeed(state['download_payload_rate']) + print "*** Upload Speed:", common.fspeed(state['upload_payload_rate']) + if state['state'] in [3, 4]: + print "*** ETA:", "%s" % common.ftime(state['eta']) + + if brief == False: + print "*** Seeders:", "%s (%s)" % (state['num_seeds'], state['total_seeds']) + print "*** Peers:", "%s (%s)" % (state['num_peers'], state['total_peers']) + print "*** Share Ratio:", "%.1f" % state['ratio'] + print "*** Availability:", "%.1f" % state['distributed_copies'] + print "*** Files:" + for i, file in enumerate(state['files']): + print "\t*", file['path'], "(%s)" % common.fsize(file['size']), "-", "%.1f%% completed" % (state['file_progress'][i] * 100) + print "" + client.get_torrent_status(_got_torrent_status, torrent, status_keys) + +class CommandPause(Command): + def execute(self, cmd): + if len(cmd) < 2: + self.usage() + return + try: + client.pause_torrent(cmd[1:]) + except Exception, msg: + print "Error:", str(msg), "\n" + + def usage(self): + print "Usage: pause [ ...]" + print "" + + def help(self): + print "Pause a torrent" + +class CommandResume(Command): + def execute(self, cmd): + if len(cmd) < 2: + self.usage() + return + try: + client.resume_torrent(cmd[1:]) + except Exception, msg: + print "Error:", str(msg), "\n" + + def usage(self): + print "Usage: resume [ ...]" + print "" + + def help(self): + print "Resume a torrent" + +class CommandRemove(Command): + def execute(self, cmd): + if len(cmd) < 2: + self.usage() + return + try: + client.remove_torrent(cmd[1:]) + except Exception, msg: + print "*** Error:", str(msg), "\n" + + def usage(self): + print "Usage: rm [ ...]" + print "" + + def help(self): + print "Remove a torrent" + +commands = { + 'add' : CommandAdd(), + 'configs' : CommandConfig(), + 'exit' : CommandExit(), + 'help' : CommandHelp(), + 'info' : CommandInfo(), + 'pause' : CommandPause(), + 'quit' : CommandExit(), + 'resume' : CommandResume(), + 'rm' : CommandRemove(), + 'del' : CommandRemove(), +} client.set_core_uri("http://localhost:58846") @@ -224,6 +287,7 @@ class NullUI: print "Welcome to deluge-shell. Type 'help' to see a list of available commands." readline.read_init_file() + while True: inp = raw_input("> ") if len(inp) == 0: break @@ -232,14 +296,12 @@ class NullUI: print "" cmd = inp[0] found = False - for command, action, help in commands: - if command != cmd: - continue - action(inp) - found = True - break - if not found: + if cmd not in commands: print "Invalid command!" - show_help([]) + commands['help'].execute([]) + else: + command = commands[cmd] + command.execute(inp) + client.force_call() print "Thanks." From 47406335ac6256343a603e25b7e0c348fa3e4a3f Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 11:29:36 +0000 Subject: [PATCH 0468/1009] Debug/Info level log messages are noisy for the null-client. --- deluge/ui/null/deluge_shell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/ui/null/deluge_shell.py b/deluge/ui/null/deluge_shell.py index 28b645393..b736c6a9f 100755 --- a/deluge/ui/null/deluge_shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -26,6 +26,7 @@ deluge-shell: Deluge shell. import deluge.ui.client as client import deluge.common as common import readline +import logging import sys @@ -280,6 +281,7 @@ commands = { 'del' : CommandRemove(), } +logging.disable(logging.ERROR) client.set_core_uri("http://localhost:58846") class NullUI: From ca3f08690ee746c510ea65ae0a25a767a7b17dd8 Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 11:39:29 +0000 Subject: [PATCH 0469/1009] Gracefully handle ctrl-d, and don't exit on blank command. --- deluge/ui/null/deluge_shell.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/deluge/ui/null/deluge_shell.py b/deluge/ui/null/deluge_shell.py index b736c6a9f..e59b0d2bb 100755 --- a/deluge/ui/null/deluge_shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -291,9 +291,13 @@ class NullUI: readline.read_init_file() while True: - inp = raw_input("> ") - if len(inp) == 0: break - inp = inp.strip().split(" ") + try: + inp = raw_input("> ").strip() + except: + inp = 'quit' + + if len(inp) == 0: continue + inp = inp.split(" ") print "" cmd = inp[0] From 885ca2c899964a7e3dfc68205a96b9475b820172 Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 12:02:57 +0000 Subject: [PATCH 0470/1009] Two new commands 'halt' and 'connect'. --- deluge/ui/null/deluge_shell.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/deluge/ui/null/deluge_shell.py b/deluge/ui/null/deluge_shell.py index e59b0d2bb..1e2e09199 100755 --- a/deluge/ui/null/deluge_shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -268,6 +268,35 @@ class CommandRemove(Command): def help(self): print "Remove a torrent" +class CommandHalt(Command): + def execute(self, cmd): + client.shutdown() + + def help(self): + print "Shutdown the deluge server." + +class CommandConnect(Command): + def execute(self, cmd): + host = 'localhost' + port = 58846 + if len(cmd) > 1: + host = cmd[1] + if len(cmd) > 2: + port = int(cmd[2]) + + if host[:7] != "http://": + host = "http://" + host + + client.set_core_uri("%s:%d" % (host, port)) + + def usage(self): + print "Usage: connect [ []]" + print " 'localhost' is the default server. 58846 is the default port." + print "" + + def help(self): + print "Connect to a new deluge server." + commands = { 'add' : CommandAdd(), 'configs' : CommandConfig(), @@ -279,6 +308,8 @@ commands = { 'resume' : CommandResume(), 'rm' : CommandRemove(), 'del' : CommandRemove(), + 'halt' : CommandHalt(), + 'connect' : CommandConnect(), } logging.disable(logging.ERROR) From e55414027667c6d8b6a6116a6d1a656df5b6f63b Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 13:29:01 +0000 Subject: [PATCH 0471/1009] Do not try to send signals to a client after 30 consecutive failures. --- deluge/core/signalmanager.py | 24 +++++++++++++++--------- deluge/ui/signalreceiver.py | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index 164d3d7b7..31db89dd1 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -43,10 +43,10 @@ class SignalManager(component.Component): def __init__(self): component.Component.__init__(self, "SignalManager") self.clients = {} - + def shutdown(self): del self.clients - + def deregister_client(self, address): """Deregisters a client""" log.debug("Deregistering %s as a signal reciever..", address) @@ -60,14 +60,20 @@ class SignalManager(component.Component): uri = "http://" + str(address) + ":" + str(port) log.debug("Registering %s as a signal reciever..", uri) self.clients[uri] = xmlrpclib.ServerProxy(uri) - + def emit(self, signal, *data): - for client in self.clients.values(): - gobject.idle_add(self._emit, client, signal, *data) - - def _emit(self, client, signal, *data): + for uri in self.clients: + gobject.idle_add(self._emit, uri, signal, 1, *data) + + def _emit(self, uri, signal, count, *data): + client = self.clients[uri] try: client.emit_signal(signal, *data) except (socket.error, Exception), e: - log.warning("Unable to emit signal to client %s: %s", client, e) - + log.warning("Unable to emit signal to client %s: %s (%d)", client, e, count) + if count < 30: + gobject.timeout_add(1000, self._emit, uri, signal, count + 1, *data) + else: + log.info("Removing %s because it couldn't be reached..", uri) + del self.clients[uri] + diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 61b11054e..9d085fb2d 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -135,7 +135,7 @@ class SignalReceiver( log.warning("Unable to call callback for signal %s", signal) except KeyError: - log.debug("There are no callbacks registered for this signal..") + log.debug("There are no callbacks registered for signal '%s'", signal) def connect_to_signal(self, signal, callback): """Connect to a signal""" From 3ad87b0e911762d9b84b3717f488ee7d11ac3f20 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 10 Feb 2008 13:36:58 +0000 Subject: [PATCH 0472/1009] Add error.py. Remove old cache decorator classes from client.py. Raise deluge.error.NoCoreError when attempting to use a client function when not connected to a daemon. --- deluge/error.py | 42 +++++++++++++++ deluge/ui/client.py | 125 ++++++++++++++++---------------------------- 2 files changed, 87 insertions(+), 80 deletions(-) create mode 100644 deluge/error.py diff --git a/deluge/error.py b/deluge/error.py new file mode 100644 index 000000000..256962366 --- /dev/null +++ b/deluge/error.py @@ -0,0 +1,42 @@ +# +# error.py +# +# Copyright (C) 2008 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +class DelugeError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class NoCoreError(DelugeError): + pass + diff --git a/deluge/ui/client.py b/deluge/ui/client.py index e67ef7380..b1677d49a 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -41,85 +41,20 @@ import gobject import deluge.xmlrpclib as xmlrpclib import deluge.common +import deluge.error from deluge.log import LOG as log -CACHE_TTL = 1.5 # seconds - -class cache: +class no_core_error: + # This decorator class will throw a NoCoreError exception if core is None def __init__(self, func): self.func = func - - #self.cache_values = {(args, kwargs): (time, ret)} - self.cache_values = {} - - def __call__(self, *__args, **__kw): - # Turn the arguments into hashable values - if __args == (): - args = None - else: - args = __args - - if __kw == {}: - kw = None - else: - kw = __kw - - # See if there is a cached return value for this call - if self.cache_values.has_key((args, kw)): - # Check the timestamp on the value to ensure it's still valid - if time.time() - self.cache_values[(args, kw)][0] < CACHE_TTL: - return self.cache_values[(args, kw)][1] - - # No return value in cache - ret = self.func(*__args, **__kw) - self.cache_values[(args, kw)] = [time.time(), ret] - return ret - -class cache_dict: - """Special cache decorator for get_torrent_status and the like.. It expects - passing a str, list and returns a dict""" - def __init__(self, func): - self.func = func - self.cache_values = {} def __call__(self, *__args, **__kw): - if __args == (): - return - # Check for a cache value for these parameters - if self.cache_values.has_key(__args[0]): - # Check the timestamp on the value to ensure it's still valid - if time.time() - self.cache_values[__args[0]][0] < CACHE_TTL: - # Check to see if we have the right keys in cache - cache_dict = self.cache_values[__args[0]][1] - if cache_dict == None: - cache_dict = {} - keys = __args[1] - non_cached = [] - ret_dict = {} - for key in keys: - if key in cache_dict.keys(): - ret_dict[key] = cache_dict[key] - else: - non_cached.append(key) - - # If there aren't any non_cached keys then lets just return - # cached values - if len(non_cached) == 0: - return ret_dict - - # We need to request the remaining non-cached keys from the func - ret = self.func(*(__args[0], non_cached), **__kw) - if ret == None: - return ret - ret.update(ret_dict) - self.cache_values[__args[0]] = [time.time(), ret] - return ret - - # Not cached - ret = self.func(*__args, **__kw) - self.cache_values[__args[0]] = [time.time(), ret] - return ret - + if _core.get_core() == None: + raise deluge.error.NoCoreError("The core proxy is invalid.") + else: + return self.func(*__args, **__kw) + class CoreProxy(gobject.GObject): __gsignals__ = { "new_core" : ( @@ -253,12 +188,15 @@ def connected(): return True return False +@no_core_error def register_client(port): get_core().call("register_client", None, port) +@no_core_error def deregister_client(): get_core().call("deregister_client", None) - + +@no_core_error def shutdown(): """Shutdown the core daemon""" try: @@ -268,11 +206,13 @@ def shutdown(): # Ignore everything set_core_uri(None) +@no_core_error def force_call(block=True): """Forces the multicall batch to go now and not wait for the timer. This call also blocks until all callbacks have been dealt with.""" get_core().do_multicall(block=block) - + +@no_core_error def add_torrent_file(torrent_files, torrent_options=None): """Adds torrent files to the core Expects a list of torrent files @@ -308,6 +248,7 @@ def add_torrent_file(torrent_files, torrent_options=None): get_core().call("add_torrent_file", None, filename, str(), fdump, options) +@no_core_error def add_torrent_url(torrent_url, options=None): """Adds torrents to the core via url""" from deluge.common import is_url @@ -316,97 +257,121 @@ def add_torrent_url(torrent_url, options=None): torrent_url, str(), options) else: log.warning("Invalid URL %s", torrent_url) - + +@no_core_error def remove_torrent(torrent_ids, remove_torrent=False, remove_data=False): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) for torrent_id in torrent_ids: get_core().call("remove_torrent", None, torrent_id, remove_torrent, remove_data) +@no_core_error def pause_torrent(torrent_ids): """Pauses torrent_ids""" for torrent_id in torrent_ids: get_core().call("pause_torrent", None, torrent_id) +@no_core_error def move_torrent(torrent_ids, folder): """Pauses torrent_ids""" for torrent_id in torrent_ids: get_core().call("move_torrent", None, torrent_id, folder) +@no_core_error def pause_all_torrents(): """Pauses all torrents""" get_core().call("pause_all_torrents", None) +@no_core_error def resume_all_torrents(): """Resumes all torrents""" get_core().call("resume_all_torrents", None) - + +@no_core_error def resume_torrent(torrent_ids): """Resume torrent_ids""" for torrent_id in torrent_ids: get_core().call("resume_torrent", None, torrent_id) - + +@no_core_error def force_reannounce(torrent_ids): """Reannounce to trackers""" for torrent_id in torrent_ids: get_core().call("force_reannounce", None, torrent_id) +@no_core_error def get_torrent_status(callback, torrent_id, keys): """Builds the status dictionary and returns it""" get_core().call("get_torrent_status", callback, torrent_id, keys) +@no_core_error def get_torrents_status(callback, torrent_ids, keys): """Builds a dictionary of torrent_ids status. Expects 2 lists. This is asynchronous so the return value will be sent as the signal 'torrent_status'""" get_core().call("get_torrents_status", callback, torrent_ids, keys) +@no_core_error def get_session_state(callback): get_core().call("get_session_state", callback) +@no_core_error def get_config(callback): get_core().call("get_config", callback) +@no_core_error def get_config_value(callback, key): get_core().call("get_config_value", callback, key) - + +@no_core_error def set_config(config): if config == {}: return get_core().call("set_config", None, config) +@no_core_error def get_listen_port(callback): get_core().call("get_listen_port", callback) +@no_core_error def get_available_plugins(callback): get_core().call("get_available_plugins", callback) +@no_core_error def get_enabled_plugins(callback): get_core().call("get_enabled_plugins", callback) +@no_core_error def get_download_rate(callback): get_core().call("get_download_rate", callback) +@no_core_error def get_upload_rate(callback): get_core().call("get_upload_rate", callback) +@no_core_error def get_num_connections(callback): get_core().call("get_num_connections", callback) +@no_core_error def get_dht_nodes(callback): get_core().call("get_dht_nodes", callback) +@no_core_error def enable_plugin(plugin): get_core().call("enable_plugin", None, plugin) - + +@no_core_error def disable_plugin(plugin): get_core().call("disable_plugin", None, plugin) +@no_core_error def force_recheck(torrent_ids): """Forces a data recheck on torrent_ids""" for torrent_id in torrent_ids: get_core().call("force_recheck", None, torrent_id) +@no_core_error def set_torrent_trackers(torrent_id, trackers): """Sets the torrents trackers""" get_core().call("set_torrent_trackers", None, torrent_id, trackers) From e95be5d1313b4687d9b447cb2911b91002cfe8bc Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 13:39:47 +0000 Subject: [PATCH 0473/1009] Prevent an exception when multiple signals are tried to send to an unreachable client. --- deluge/core/signalmanager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index 31db89dd1..10e61c3f0 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -66,6 +66,8 @@ class SignalManager(component.Component): gobject.idle_add(self._emit, uri, signal, 1, *data) def _emit(self, uri, signal, count, *data): + if uri not in self.clients: + return client = self.clients[uri] try: client.emit_signal(signal, *data) From 72cb14f1d7539cf75812826c7862a149d038a9f1 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 10 Feb 2008 13:42:14 +0000 Subject: [PATCH 0474/1009] Remove the decorator class and just raise the NoCoreError in CoreProxy.call(). --- deluge/ui/client.py | 58 +++++++-------------------------------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index b1677d49a..dc94df27c 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -43,18 +43,7 @@ import deluge.xmlrpclib as xmlrpclib import deluge.common import deluge.error from deluge.log import LOG as log - -class no_core_error: - # This decorator class will throw a NoCoreError exception if core is None - def __init__(self, func): - self.func = func - - def __call__(self, *__args, **__kw): - if _core.get_core() == None: - raise deluge.error.NoCoreError("The core proxy is invalid.") - else: - return self.func(*__args, **__kw) - + class CoreProxy(gobject.GObject): __gsignals__ = { "new_core" : ( @@ -74,6 +63,7 @@ class CoreProxy(gobject.GObject): def call(self, func, callback, *args): if self._core is None or self._multi is None: if self.get_core() is None: + raise deluge.error.NoCoreError("The core proxy is invalid.") return _func = getattr(self._multi, func) @@ -188,15 +178,12 @@ def connected(): return True return False -@no_core_error def register_client(port): get_core().call("register_client", None, port) -@no_core_error def deregister_client(): get_core().call("deregister_client", None) - -@no_core_error + def shutdown(): """Shutdown the core daemon""" try: @@ -206,13 +193,11 @@ def shutdown(): # Ignore everything set_core_uri(None) -@no_core_error def force_call(block=True): """Forces the multicall batch to go now and not wait for the timer. This call also blocks until all callbacks have been dealt with.""" get_core().do_multicall(block=block) - -@no_core_error + def add_torrent_file(torrent_files, torrent_options=None): """Adds torrent files to the core Expects a list of torrent files @@ -248,7 +233,6 @@ def add_torrent_file(torrent_files, torrent_options=None): get_core().call("add_torrent_file", None, filename, str(), fdump, options) -@no_core_error def add_torrent_url(torrent_url, options=None): """Adds torrents to the core via url""" from deluge.common import is_url @@ -257,121 +241,97 @@ def add_torrent_url(torrent_url, options=None): torrent_url, str(), options) else: log.warning("Invalid URL %s", torrent_url) - -@no_core_error + def remove_torrent(torrent_ids, remove_torrent=False, remove_data=False): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) for torrent_id in torrent_ids: get_core().call("remove_torrent", None, torrent_id, remove_torrent, remove_data) -@no_core_error def pause_torrent(torrent_ids): """Pauses torrent_ids""" for torrent_id in torrent_ids: get_core().call("pause_torrent", None, torrent_id) -@no_core_error def move_torrent(torrent_ids, folder): """Pauses torrent_ids""" for torrent_id in torrent_ids: get_core().call("move_torrent", None, torrent_id, folder) -@no_core_error def pause_all_torrents(): """Pauses all torrents""" get_core().call("pause_all_torrents", None) -@no_core_error def resume_all_torrents(): """Resumes all torrents""" get_core().call("resume_all_torrents", None) - -@no_core_error + def resume_torrent(torrent_ids): """Resume torrent_ids""" for torrent_id in torrent_ids: get_core().call("resume_torrent", None, torrent_id) - -@no_core_error + def force_reannounce(torrent_ids): """Reannounce to trackers""" for torrent_id in torrent_ids: get_core().call("force_reannounce", None, torrent_id) -@no_core_error def get_torrent_status(callback, torrent_id, keys): """Builds the status dictionary and returns it""" get_core().call("get_torrent_status", callback, torrent_id, keys) -@no_core_error def get_torrents_status(callback, torrent_ids, keys): """Builds a dictionary of torrent_ids status. Expects 2 lists. This is asynchronous so the return value will be sent as the signal 'torrent_status'""" get_core().call("get_torrents_status", callback, torrent_ids, keys) -@no_core_error def get_session_state(callback): get_core().call("get_session_state", callback) -@no_core_error def get_config(callback): get_core().call("get_config", callback) -@no_core_error def get_config_value(callback, key): get_core().call("get_config_value", callback, key) - -@no_core_error + def set_config(config): if config == {}: return get_core().call("set_config", None, config) -@no_core_error def get_listen_port(callback): get_core().call("get_listen_port", callback) -@no_core_error def get_available_plugins(callback): get_core().call("get_available_plugins", callback) -@no_core_error def get_enabled_plugins(callback): get_core().call("get_enabled_plugins", callback) -@no_core_error def get_download_rate(callback): get_core().call("get_download_rate", callback) -@no_core_error def get_upload_rate(callback): get_core().call("get_upload_rate", callback) -@no_core_error def get_num_connections(callback): get_core().call("get_num_connections", callback) -@no_core_error def get_dht_nodes(callback): get_core().call("get_dht_nodes", callback) -@no_core_error def enable_plugin(plugin): get_core().call("enable_plugin", None, plugin) - -@no_core_error + def disable_plugin(plugin): get_core().call("disable_plugin", None, plugin) -@no_core_error def force_recheck(torrent_ids): """Forces a data recheck on torrent_ids""" for torrent_id in torrent_ids: get_core().call("force_recheck", None, torrent_id) -@no_core_error def set_torrent_trackers(torrent_id, trackers): """Sets the torrents trackers""" get_core().call("set_torrent_trackers", None, torrent_id, trackers) From 76da1ac525b1a1ef012293cb60dbaa501e5cc8f7 Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Sun, 10 Feb 2008 14:12:14 +0000 Subject: [PATCH 0475/1009] Show an error message when trying to do an operation while disconnected from a deluge daemon. --- deluge/ui/null/deluge_shell.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deluge/ui/null/deluge_shell.py b/deluge/ui/null/deluge_shell.py index 1e2e09199..4794c6559 100755 --- a/deluge/ui/null/deluge_shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -25,6 +25,7 @@ deluge-shell: Deluge shell. import deluge.ui.client as client import deluge.common as common +import deluge.error import readline import logging @@ -338,7 +339,13 @@ class NullUI: commands['help'].execute([]) else: command = commands[cmd] - command.execute(inp) - client.force_call() + try: + command.execute(inp) + client.force_call() + except deluge.error.NoCoreError, e: + print "*** Operation failed. You are not connected to a deluge daemon." + print " Perhaps you want to 'connect' first." + print "" print "Thanks." + From 42e75fbcdd7772591b01f69b315697d4408fe0c9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 10 Feb 2008 15:29:15 +0000 Subject: [PATCH 0476/1009] Fix test port button. --- deluge/ui/gtkui/preferences.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 6499ac5c1..1b24ba4b8 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -542,9 +542,11 @@ class Preferences(component.Component): def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") - url = "http://deluge-torrent.org/test-port.php?port=%s" % \ - client.get_listen_port() - client.open_url_in_browser(url) + def on_get_listen_port(port): + deluge.common.open_url_in_browser( + "http://deluge-torrent.org/test-port.php?port=%s" % port) + client.get_listen_port(on_get_listen_port) + client.force_call() def on_plugin_toggled(self, renderer, path): log.debug("on_plugin_toggled") From 494c0cc558d7098feb802fb8fa50d283d440b258 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Sun, 10 Feb 2008 22:07:23 +0000 Subject: [PATCH 0477/1009] sync asio --- libtorrent/include/asio/buffer.hpp | 5 + .../include/asio/detail/epoll_reactor.hpp | 14 ++- .../include/asio/detail/kqueue_reactor.hpp | 14 ++- .../asio/detail/old_win_sdk_compat.hpp | 16 ++- .../asio/detail/posix_fd_set_adapter.hpp | 13 ++- .../include/asio/detail/push_options.hpp | 6 + .../include/asio/detail/reactor_op_queue.hpp | 7 +- libtorrent/include/asio/detail/socket_ops.hpp | 21 +++- .../include/asio/detail/socket_types.hpp | 15 ++- .../asio/detail/win_fd_set_adapter.hpp | 8 +- .../asio/detail/win_iocp_io_service.hpp | 104 ++++++++++++++---- .../asio/detail/win_iocp_socket_service.hpp | 49 ++++----- libtorrent/include/asio/error.hpp | 10 +- libtorrent/include/asio/error_code.hpp | 8 +- libtorrent/include/asio/impl/error_code.ipp | 2 + .../include/asio/ssl/detail/openssl_init.hpp | 16 +++ .../asio/ssl/detail/openssl_operation.hpp | 81 ++++++++++---- .../ssl/detail/openssl_stream_service.hpp | 53 ++++++--- libtorrent/include/asio/ssl/stream.hpp | 7 +- libtorrent/include/asio/version.hpp | 2 +- 20 files changed, 347 insertions(+), 104 deletions(-) diff --git a/libtorrent/include/asio/buffer.hpp b/libtorrent/include/asio/buffer.hpp index 881bc1176..57df33edf 100644 --- a/libtorrent/include/asio/buffer.hpp +++ b/libtorrent/include/asio/buffer.hpp @@ -392,7 +392,12 @@ public: ~buffer_debug_check() { +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) + // MSVC's string iterator checking may crash in a std::string::iterator + // object's destructor when the iterator points to an already-destroyed + // std::string object, unless the iterator is cleared first. iter_ = Iterator(); +#endif // BOOST_WORKAROUND(BOOST_MSVC, >= 1400) } void operator()() diff --git a/libtorrent/include/asio/detail/epoll_reactor.hpp b/libtorrent/include/asio/detail/epoll_reactor.hpp index 93d39a23c..68b6fff4c 100644 --- a/libtorrent/include/asio/detail/epoll_reactor.hpp +++ b/libtorrent/include/asio/detail/epoll_reactor.hpp @@ -66,7 +66,8 @@ public: pending_cancellations_(), stop_thread_(false), thread_(0), - shutdown_(false) + shutdown_(false), + need_epoll_wait_(true) { // Start the reactor's internal thread only if needed. if (Own_Thread) @@ -387,7 +388,9 @@ private: // Block on the epoll descriptor. epoll_event events[128]; - int num_events = epoll_wait(epoll_fd_, events, 128, timeout); + int num_events = (block || need_epoll_wait_) + ? epoll_wait(epoll_fd_, events, 128, timeout) + : 0; lock.lock(); wait_in_progress_ = false; @@ -478,6 +481,10 @@ private: cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); + // Determine whether epoll_wait should be called when the reactor next runs. + need_epoll_wait_ = !read_op_queue_.empty() + || !write_op_queue_.empty() || !except_op_queue_.empty(); + cleanup_operations_and_timers(lock); } @@ -632,6 +639,9 @@ private: // Whether the service has been shut down. bool shutdown_; + + // Whether we need to call epoll_wait the next time the reactor is run. + bool need_epoll_wait_; }; } // namespace detail diff --git a/libtorrent/include/asio/detail/kqueue_reactor.hpp b/libtorrent/include/asio/detail/kqueue_reactor.hpp index 896ff5403..6b09d4dbc 100644 --- a/libtorrent/include/asio/detail/kqueue_reactor.hpp +++ b/libtorrent/include/asio/detail/kqueue_reactor.hpp @@ -74,7 +74,8 @@ public: pending_cancellations_(), stop_thread_(false), thread_(0), - shutdown_(false) + shutdown_(false), + need_kqueue_wait_(true) { // Start the reactor's internal thread only if needed. if (Own_Thread) @@ -373,7 +374,9 @@ private: // Block on the kqueue descriptor. struct kevent events[128]; - int num_events = kevent(kqueue_fd_, 0, 0, events, 128, timeout); + int num_events = (block || need_kqueue_wait_) + ? kevent(kqueue_fd_, 0, 0, events, 128, timeout) + : 0; lock.lock(); wait_in_progress_ = false; @@ -478,6 +481,10 @@ private: cancel_ops_unlocked(pending_cancellations_[i]); pending_cancellations_.clear(); + // Determine whether kqueue needs to be called next time the reactor is run. + need_kqueue_wait_ = !read_op_queue_.empty() + || !write_op_queue_.empty() || !except_op_queue_.empty(); + cleanup_operations_and_timers(lock); } @@ -630,6 +637,9 @@ private: // Whether the service has been shut down. bool shutdown_; + + // Whether we need to call kqueue the next time the reactor is run. + bool need_kqueue_wait_; }; } // namespace detail diff --git a/libtorrent/include/asio/detail/old_win_sdk_compat.hpp b/libtorrent/include/asio/detail/old_win_sdk_compat.hpp index 2fb957aa9..e32552197 100644 --- a/libtorrent/include/asio/detail/old_win_sdk_compat.hpp +++ b/libtorrent/include/asio/detail/old_win_sdk_compat.hpp @@ -31,6 +31,10 @@ #if defined(ASIO_HAS_OLD_WIN_SDK) // Emulation of types that are missing from old Platform SDKs. +// +// N.B. this emulation is also used if building for a Windows 2000 target with +// a recent (i.e. Vista or later) SDK, as the SDK does not provide IPv6 support +// in that case. namespace asio { namespace detail { @@ -54,9 +58,19 @@ struct sockaddr_storage_emulation struct in6_addr_emulation { - u_char s6_addr[16]; + union + { + u_char Byte[16]; + u_short Word[8]; + } u; }; +#if !defined(s6_addr) +# define _S6_un u +# define _S6_u8 Byte +# define s6_addr _S6_un._S6_u8 +#endif // !defined(s6_addr) + struct sockaddr_in6_emulation { short sin6_family; diff --git a/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp b/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp index c43904f4d..28e717bb1 100644 --- a/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp +++ b/libtorrent/include/asio/detail/posix_fd_set_adapter.hpp @@ -35,11 +35,16 @@ public: FD_ZERO(&fd_set_); } - void set(socket_type descriptor) + bool set(socket_type descriptor) { - if (max_descriptor_ == invalid_socket || descriptor > max_descriptor_) - max_descriptor_ = descriptor; - FD_SET(descriptor, &fd_set_); + if (descriptor < (socket_type)FD_SETSIZE) + { + if (max_descriptor_ == invalid_socket || descriptor > max_descriptor_) + max_descriptor_ = descriptor; + FD_SET(descriptor, &fd_set_); + return true; + } + return false; } bool is_set(socket_type descriptor) const diff --git a/libtorrent/include/asio/detail/push_options.hpp b/libtorrent/include/asio/detail/push_options.hpp index 0b68d2933..96ddf7d04 100644 --- a/libtorrent/include/asio/detail/push_options.hpp +++ b/libtorrent/include/asio/detail/push_options.hpp @@ -90,6 +90,12 @@ # pragma warning (disable:4244) # pragma warning (disable:4355) # pragma warning (disable:4675) +# if defined(_M_IX86) && defined(_Wp64) +// The /Wp64 option is broken. If you want to check 64 bit portability, use a +// 64 bit compiler! +# pragma warning (disable:4311) +# pragma warning (disable:4312) +# endif // defined(_M_IX86) && defined(_Wp64) # pragma pack (push, 8) // Note that if the /Og optimisation flag is enabled with MSVC6, the compiler // has a tendency to incorrectly optimise away some calls to member template diff --git a/libtorrent/include/asio/detail/reactor_op_queue.hpp b/libtorrent/include/asio/detail/reactor_op_queue.hpp index b2d1054c6..583636047 100644 --- a/libtorrent/include/asio/detail/reactor_op_queue.hpp +++ b/libtorrent/include/asio/detail/reactor_op_queue.hpp @@ -173,8 +173,13 @@ public: typename operation_map::iterator i = operations_.begin(); while (i != operations_.end()) { - descriptors.set(i->first); + Descriptor descriptor = i->first; ++i; + if (!descriptors.set(descriptor)) + { + asio::error_code ec(error::fd_set_failure); + dispatch_all_operations(descriptor, ec); + } } } diff --git a/libtorrent/include/asio/detail/socket_ops.hpp b/libtorrent/include/asio/detail/socket_ops.hpp index ee0b4b582..06ffe9f91 100644 --- a/libtorrent/include/asio/detail/socket_ops.hpp +++ b/libtorrent/include/asio/detail/socket_ops.hpp @@ -1124,8 +1124,12 @@ inline void gai_free(void* p) inline void gai_strcpy(char* target, const char* source, std::size_t max_size) { using namespace std; +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) && !defined(UNDER_CE) + strcpy_s(target, max_size, source); +#else *target = 0; strncat(target, source, max_size); +#endif } enum { gai_clone_flag = 1 << 30 }; @@ -1658,7 +1662,11 @@ inline asio::error_code getnameinfo_emulation( { return ec = asio::error::no_buffer_space; } +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) && !defined(UNDER_CE) + sprintf_s(serv, servlen, "%u", ntohs(port)); +#else sprintf(serv, "%u", ntohs(port)); +#endif } else { @@ -1677,7 +1685,11 @@ inline asio::error_code getnameinfo_emulation( { return ec = asio::error::no_buffer_space; } +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) && !defined(UNDER_CE) + sprintf_s(serv, servlen, "%u", ntohs(port)); +#else sprintf(serv, "%u", ntohs(port)); +#endif } #if defined(BOOST_HAS_THREADS) && defined(BOOST_HAS_PTHREADS) ::pthread_mutex_unlock(&mutex); @@ -1799,19 +1811,22 @@ inline asio::error_code getnameinfo(const socket_addr_type* addr, # if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) || defined(UNDER_CE) // Building for Windows XP, Windows Server 2003, or later. clear_error(ec); - int error = ::getnameinfo(addr, addrlen, host, static_cast(hostlen), + int error = ::getnameinfo(addr, static_cast(addrlen), + host, static_cast(hostlen), serv, static_cast(servlen), flags); return ec = translate_addrinfo_error(error); # else // Building for Windows 2000 or earlier. typedef int (WSAAPI *gni_t)(const socket_addr_type*, - int, char*, std::size_t, char*, std::size_t, int); + int, char*, DWORD, char*, DWORD, int); if (HMODULE winsock_module = ::GetModuleHandleA("ws2_32")) { if (gni_t gni = (gni_t)::GetProcAddress(winsock_module, "getnameinfo")) { clear_error(ec); - int error = gni(addr, addrlen, host, hostlen, serv, servlen, flags); + int error = gni(addr, static_cast(addrlen), + host, static_cast(hostlen), + serv, static_cast(servlen), flags); return ec = translate_addrinfo_error(error); } } diff --git a/libtorrent/include/asio/detail/socket_types.hpp b/libtorrent/include/asio/detail/socket_types.hpp index e2b68910c..d1497a345 100644 --- a/libtorrent/include/asio/detail/socket_types.hpp +++ b/libtorrent/include/asio/detail/socket_types.hpp @@ -92,7 +92,11 @@ # include # include # include -# include +# if defined(__hpux) && !defined(__HP_aCC) +# include +# else +# include +# endif # include # include # include @@ -156,7 +160,16 @@ const int max_addr_v4_str_len = INET_ADDRSTRLEN; const int max_addr_v6_str_len = INET6_ADDRSTRLEN + 1 + IF_NAMESIZE; typedef sockaddr socket_addr_type; typedef in_addr in4_addr_type; +# if defined(__hpux) +// HP-UX doesn't provide ip_mreq when _XOPEN_SOURCE_EXTENDED is defined. +struct in4_mreq_type +{ + struct in_addr imr_multiaddr; + struct in_addr imr_interface; +}; +# else typedef ip_mreq in4_mreq_type; +# endif typedef sockaddr_in sockaddr_in4_type; typedef in6_addr in6_addr_type; typedef ipv6_mreq in6_mreq_type; diff --git a/libtorrent/include/asio/detail/win_fd_set_adapter.hpp b/libtorrent/include/asio/detail/win_fd_set_adapter.hpp index f2632c4d0..c97063a53 100644 --- a/libtorrent/include/asio/detail/win_fd_set_adapter.hpp +++ b/libtorrent/include/asio/detail/win_fd_set_adapter.hpp @@ -36,13 +36,17 @@ public: fd_set_.fd_count = 0; } - void set(socket_type descriptor) + bool set(socket_type descriptor) { for (u_int i = 0; i < fd_set_.fd_count; ++i) if (fd_set_.fd_array[i] == descriptor) - return; + return true; if (fd_set_.fd_count < win_fd_set_size) + { fd_set_.fd_array[fd_set_.fd_count++] = descriptor; + return true; + } + return false; } bool is_set(socket_type descriptor) const diff --git a/libtorrent/include/asio/detail/win_iocp_io_service.hpp b/libtorrent/include/asio/detail/win_iocp_io_service.hpp index 46e651653..fe2e58a0e 100644 --- a/libtorrent/include/asio/detail/win_iocp_io_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_io_service.hpp @@ -34,7 +34,6 @@ #include "asio/detail/service_base.hpp" #include "asio/detail/socket_types.hpp" #include "asio/detail/timer_queue.hpp" -#include "asio/detail/win_iocp_operation.hpp" #include "asio/detail/mutex.hpp" namespace asio { @@ -44,14 +43,64 @@ class win_iocp_io_service : public asio::detail::service_base { public: - // Base class for all operations. - typedef win_iocp_operation operation; + // Base class for all operations. A function pointer is used instead of + // virtual functions to avoid the associated overhead. + // + // This class inherits from OVERLAPPED so that we can downcast to get back to + // the operation pointer from the LPOVERLAPPED out parameter of + // GetQueuedCompletionStatus. + class operation + : public OVERLAPPED + { + public: + typedef void (*invoke_func_type)(operation*, DWORD, size_t); + typedef void (*destroy_func_type)(operation*); + + operation(win_iocp_io_service& iocp_service, + invoke_func_type invoke_func, destroy_func_type destroy_func) + : outstanding_operations_(&iocp_service.outstanding_operations_), + invoke_func_(invoke_func), + destroy_func_(destroy_func) + { + Internal = 0; + InternalHigh = 0; + Offset = 0; + OffsetHigh = 0; + hEvent = 0; + + ::InterlockedIncrement(outstanding_operations_); + } + + void do_completion(DWORD last_error, size_t bytes_transferred) + { + invoke_func_(this, last_error, bytes_transferred); + } + + void destroy() + { + destroy_func_(this); + } + + protected: + // Prevent deletion through this type. + ~operation() + { + ::InterlockedDecrement(outstanding_operations_); + } + + private: + long* outstanding_operations_; + invoke_func_type invoke_func_; + destroy_func_type destroy_func_; + }; + // Constructor. win_iocp_io_service(asio::io_service& io_service) : asio::detail::service_base(io_service), iocp_(), outstanding_work_(0), + outstanding_operations_(0), stopped_(0), shutdown_(0), timer_thread_(0), @@ -79,7 +128,7 @@ public: { ::InterlockedExchange(&shutdown_, 1); - for (;;) + while (::InterlockedExchangeAdd(&outstanding_operations_, 0) > 0) { DWORD bytes_transferred = 0; #if (WINVER < 0x0500) @@ -88,12 +137,8 @@ public: DWORD_PTR completion_key = 0; #endif LPOVERLAPPED overlapped = 0; - ::SetLastError(0); - BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, - &bytes_transferred, &completion_key, &overlapped, 0); - DWORD last_error = ::GetLastError(); - if (!ok && overlapped == 0 && last_error == WAIT_TIMEOUT) - break; + ::GetQueuedCompletionStatus(iocp_.handle, &bytes_transferred, + &completion_key, &overlapped, INFINITE); if (overlapped) static_cast(overlapped)->destroy(); } @@ -249,7 +294,7 @@ public: } // Request invocation of the given OVERLAPPED-derived operation. - void post_completion(win_iocp_operation* op, DWORD op_last_error, + void post_completion(operation* op, DWORD op_last_error, DWORD bytes_transferred) { // Enqueue the operation on the I/O completion port. @@ -347,7 +392,7 @@ private: &timer_thread_, this_thread_id, 0) == 0); // Calculate timeout for GetQueuedCompletionStatus call. - DWORD timeout = max_timeout; + DWORD timeout = INFINITE; if (dispatching_timers) { asio::detail::mutex::scoped_lock lock(timer_mutex_); @@ -371,13 +416,28 @@ private: // Dispatch any pending timers. if (dispatching_timers) { - asio::detail::mutex::scoped_lock lock(timer_mutex_); - timer_queues_copy_ = timer_queues_; - for (std::size_t i = 0; i < timer_queues_.size(); ++i) + try { - timer_queues_[i]->dispatch_timers(); - timer_queues_[i]->dispatch_cancellations(); - timer_queues_[i]->cleanup_timers(); + asio::detail::mutex::scoped_lock lock(timer_mutex_); + timer_queues_copy_ = timer_queues_; + for (std::size_t i = 0; i < timer_queues_.size(); ++i) + { + timer_queues_[i]->dispatch_timers(); + timer_queues_[i]->dispatch_cancellations(); + timer_queues_[i]->cleanup_timers(); + } + } + catch (...) + { + // Transfer responsibility for dispatching timers to another thread. + if (::InterlockedCompareExchange(&timer_thread_, + 0, this_thread_id) == this_thread_id) + { + ::PostQueuedCompletionStatus(iocp_.handle, + 0, transfer_timer_dispatching, 0); + } + + throw; } } @@ -532,7 +592,7 @@ private: { handler_operation(win_iocp_io_service& io_service, Handler handler) - : operation(&handler_operation::do_completion_impl, + : operation(io_service, &handler_operation::do_completion_impl, &handler_operation::destroy_impl), io_service_(io_service), handler_(handler) @@ -593,6 +653,10 @@ private: // The count of unfinished work. long outstanding_work_; + // The count of unfinished operations. + long outstanding_operations_; + friend class operation; + // Flag to indicate whether the event loop has been stopped. long stopped_; @@ -602,7 +666,7 @@ private: enum { // Maximum GetQueuedCompletionStatus timeout, in milliseconds. - max_timeout = 1000, + max_timeout = 500, // Completion key value to indicate that responsibility for dispatching // timers is being cooperatively transferred from one thread to another. diff --git a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp index cb3640638..faf6a5c1e 100644 --- a/libtorrent/include/asio/detail/win_iocp_socket_service.hpp +++ b/libtorrent/include/asio/detail/win_iocp_socket_service.hpp @@ -56,7 +56,7 @@ public: typedef typename Protocol::endpoint endpoint_type; // Base class for all operations. - typedef win_iocp_operation operation; + typedef win_iocp_io_service::operation operation; struct noop_deleter { void operator()(void*) {} }; typedef boost::shared_ptr shared_cancel_token_type; @@ -680,13 +680,13 @@ public: : public operation { public: - send_operation(asio::io_service& io_service, + send_operation(win_iocp_io_service& io_service, weak_cancel_token_type cancel_token, const ConstBufferSequence& buffers, Handler handler) - : operation( + : operation(io_service, &send_operation::do_completion_impl, &send_operation::destroy_impl), - work_(io_service), + work_(io_service.get_io_service()), cancel_token_(cancel_token), buffers_(buffers), handler_(handler) @@ -782,8 +782,8 @@ public: typedef send_operation value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); - handler_ptr ptr(raw_ptr, - this->get_io_service(), impl.cancel_token_, buffers, handler); + handler_ptr ptr(raw_ptr, iocp_service_, + impl.cancel_token_, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -860,7 +860,7 @@ public: // Send the data. DWORD bytes_transferred = 0; int result = ::WSASendTo(impl.socket_, bufs, i, &bytes_transferred, - flags, destination.data(), destination.size(), 0, 0); + flags, destination.data(), static_cast(destination.size()), 0, 0); if (result != 0) { DWORD last_error = ::WSAGetLastError(); @@ -880,12 +880,12 @@ public: : public operation { public: - send_to_operation(asio::io_service& io_service, + send_to_operation(win_iocp_io_service& io_service, const ConstBufferSequence& buffers, Handler handler) - : operation( + : operation(io_service, &send_to_operation::do_completion_impl, &send_to_operation::destroy_impl), - work_(io_service), + work_(io_service.get_io_service()), buffers_(buffers), handler_(handler) { @@ -973,8 +973,7 @@ public: typedef send_to_operation value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); - handler_ptr ptr(raw_ptr, - this->get_io_service(), buffers, handler); + handler_ptr ptr(raw_ptr, iocp_service_, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -991,8 +990,8 @@ public: // Send the data. DWORD bytes_transferred = 0; - int result = ::WSASendTo(impl.socket_, bufs, i, &bytes_transferred, - flags, destination.data(), destination.size(), ptr.get(), 0); + int result = ::WSASendTo(impl.socket_, bufs, i, &bytes_transferred, flags, + destination.data(), static_cast(destination.size()), ptr.get(), 0); DWORD last_error = ::WSAGetLastError(); // Check if the operation completed immediately. @@ -1074,15 +1073,15 @@ public: : public operation { public: - receive_operation(asio::io_service& io_service, + receive_operation(win_iocp_io_service& io_service, weak_cancel_token_type cancel_token, const MutableBufferSequence& buffers, Handler handler) - : operation( + : operation(io_service, &receive_operation< MutableBufferSequence, Handler>::do_completion_impl, &receive_operation< MutableBufferSequence, Handler>::destroy_impl), - work_(io_service), + work_(io_service.get_io_service()), cancel_token_(cancel_token), buffers_(buffers), handler_(handler) @@ -1185,8 +1184,8 @@ public: typedef receive_operation value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); - handler_ptr ptr(raw_ptr, - this->get_io_service(), impl.cancel_token_, buffers, handler); + handler_ptr ptr(raw_ptr, iocp_service_, + impl.cancel_token_, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -1290,17 +1289,17 @@ public: : public operation { public: - receive_from_operation(asio::io_service& io_service, + receive_from_operation(win_iocp_io_service& io_service, endpoint_type& endpoint, const MutableBufferSequence& buffers, Handler handler) - : operation( + : operation(io_service, &receive_from_operation< MutableBufferSequence, Handler>::do_completion_impl, &receive_from_operation< MutableBufferSequence, Handler>::destroy_impl), endpoint_(endpoint), endpoint_size_(static_cast(endpoint.capacity())), - work_(io_service), + work_(io_service.get_io_service()), buffers_(buffers), handler_(handler) { @@ -1405,8 +1404,8 @@ public: typedef receive_from_operation value_type; typedef handler_alloc_traits alloc_traits; raw_handler_ptr raw_ptr(handler); - handler_ptr ptr(raw_ptr, - this->get_io_service(), sender_endp, buffers, handler); + handler_ptr ptr(raw_ptr, iocp_service_, + sender_endp, buffers, handler); // Copy buffers into WSABUF array. ::WSABUF bufs[max_buffers]; @@ -1508,7 +1507,7 @@ public: socket_type socket, socket_type new_socket, Socket& peer, const protocol_type& protocol, endpoint_type* peer_endpoint, bool enable_connection_aborted, Handler handler) - : operation( + : operation(io_service, &accept_operation::do_completion_impl, &accept_operation::destroy_impl), io_service_(io_service), diff --git a/libtorrent/include/asio/error.hpp b/libtorrent/include/asio/error.hpp index c65599c91..1c1946af6 100644 --- a/libtorrent/include/asio/error.hpp +++ b/libtorrent/include/asio/error.hpp @@ -70,6 +70,11 @@ enum basic_errors /// Operation already in progress. already_started = ASIO_SOCKET_ERROR(EALREADY), + /// Broken pipe. + broken_pipe = ASIO_WIN_OR_POSIX( + ASIO_NATIVE_ERROR(ERROR_BROKEN_PIPE), + ASIO_NATIVE_ERROR(EPIPE)), + /// A connection has been aborted. connection_aborted = ASIO_SOCKET_ERROR(ECONNABORTED), @@ -194,7 +199,10 @@ enum misc_errors eof, /// Element not found. - not_found + not_found, + + /// The descriptor cannot fit into the select system call's fd_set. + fd_set_failure }; enum ssl_errors diff --git a/libtorrent/include/asio/error_code.hpp b/libtorrent/include/asio/error_code.hpp index 516d599d7..6c8370d42 100644 --- a/libtorrent/include/asio/error_code.hpp +++ b/libtorrent/include/asio/error_code.hpp @@ -112,7 +112,11 @@ public: { }; - typedef unspecified_bool_type_t* unspecified_bool_type; + typedef void (*unspecified_bool_type)(unspecified_bool_type_t); + + static void unspecified_bool_true(unspecified_bool_type_t) + { + } /// Operator returns non-null if there is a non-success error code. operator unspecified_bool_type() const @@ -120,7 +124,7 @@ public: if (value_ == 0) return 0; else - return reinterpret_cast(1); + return &error_code::unspecified_bool_true; } /// Operator to test if the error represents success. diff --git a/libtorrent/include/asio/impl/error_code.ipp b/libtorrent/include/asio/impl/error_code.ipp index 9925cb484..52aef2851 100644 --- a/libtorrent/include/asio/impl/error_code.ipp +++ b/libtorrent/include/asio/impl/error_code.ipp @@ -35,6 +35,8 @@ inline std::string error_code::message() const return "Already open."; if (*this == error::not_found) return "Not found."; + if (*this == error::fd_set_failure) + return "The descriptor does not fit into the select call's fd_set."; if (category_ == error::get_ssl_category()) return "SSL error."; #if defined(BOOST_WINDOWS) || defined(__CYGWIN__) diff --git a/libtorrent/include/asio/ssl/detail/openssl_init.hpp b/libtorrent/include/asio/ssl/detail/openssl_init.hpp index 06fbf86fe..e646acf48 100755 --- a/libtorrent/include/asio/ssl/detail/openssl_init.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_init.hpp @@ -20,10 +20,12 @@ #include "asio/detail/push_options.hpp" #include +#include #include #include "asio/detail/pop_options.hpp" #include "asio/detail/mutex.hpp" +#include "asio/detail/tss_ptr.hpp" #include "asio/ssl/detail/openssl_types.hpp" namespace asio { @@ -51,6 +53,7 @@ private: for (size_t i = 0; i < mutexes_.size(); ++i) mutexes_[i].reset(new asio::detail::mutex); ::CRYPTO_set_locking_callback(&do_init::openssl_locking_func); + ::CRYPTO_set_id_callback(&do_init::openssl_id_func); } } @@ -58,6 +61,7 @@ private: { if (Do_Init) { + ::CRYPTO_set_id_callback(0); ::CRYPTO_set_locking_callback(0); ::ERR_free_strings(); ::ERR_remove_state(0); @@ -80,6 +84,15 @@ private: } private: + static unsigned long openssl_id_func() + { + void* id = instance()->thread_id_; + if (id == 0) + instance()->thread_id_ = id = &id; // Ugh. + BOOST_ASSERT(sizeof(unsigned long) >= sizeof(void*)); + return reinterpret_cast(id); + } + static void openssl_locking_func(int mode, int n, const char *file, int line) { @@ -91,6 +104,9 @@ private: // Mutexes to be used in locking callbacks. std::vector > mutexes_; + + // The thread identifiers to be used by openssl. + asio::detail::tss_ptr thread_id_; }; public: diff --git a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp index c254aceb2..503559e7f 100755 --- a/libtorrent/include/asio/ssl/detail/openssl_operation.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_operation.hpp @@ -19,6 +19,7 @@ #include "asio/detail/push_options.hpp" #include +#include #include #include "asio/detail/pop_options.hpp" @@ -87,10 +88,12 @@ public: net_buffer& recv_buf, SSL* session, BIO* ssl_bio, - user_handler_func handler + user_handler_func handler, + asio::io_service::strand& strand ) : primitive_(primitive) , user_handler_(handler) + , strand_(&strand) , recv_buf_(recv_buf) , socket_(socket) , ssl_bio_(ssl_bio) @@ -100,6 +103,10 @@ public: &openssl_operation::do_async_write, this, boost::arg<1>(), boost::arg<2>() ); + read_ = boost::bind( + &openssl_operation::do_async_read, + this + ); handler_= boost::bind( &openssl_operation::async_user_handler, this, boost::arg<1>(), boost::arg<2>() @@ -113,6 +120,7 @@ public: SSL* session, BIO* ssl_bio) : primitive_(primitive) + , strand_(0) , recv_buf_(recv_buf) , socket_(socket) , ssl_bio_(ssl_bio) @@ -122,6 +130,10 @@ public: &openssl_operation::do_sync_write, this, boost::arg<1>(), boost::arg<2>() ); + read_ = boost::bind( + &openssl_operation::do_sync_read, + this + ); handler_ = boost::bind( &openssl_operation::sync_user_handler, this, boost::arg<1>(), boost::arg<2>() @@ -134,7 +146,7 @@ public: int start() { int rc = primitive_( session_ ); - int sys_error_code = ERR_get_error(); + bool is_operation_done = (rc > 0); // For connect/accept/shutdown, the operation // is done, when return code is 1 @@ -144,6 +156,8 @@ public: int error_code = !is_operation_done ? ::SSL_get_error( session_, rc ) : 0; + int sys_error_code = ERR_get_error(); + bool is_read_needed = (error_code == SSL_ERROR_WANT_READ); bool is_write_needed = (error_code == SSL_ERROR_WANT_WRITE || ::BIO_ctrl_pending( ssl_bio_ )); @@ -211,6 +225,10 @@ public: return start(); } + else if (is_read_needed) + { + return read_(); + } } // Continue with operation, flush any SSL data out to network... @@ -222,10 +240,13 @@ private: typedef boost::function int_handler_func; typedef boost::function write_func; + typedef boost::function read_func; ssl_primitive_func primitive_; user_handler_func user_handler_; + asio::io_service::strand* strand_; write_func write_; + read_func read_; int_handler_func handler_; net_buffer send_buf_; // buffers for network IO @@ -249,8 +270,15 @@ private: throw asio::system_error(error); } - int async_user_handler(const asio::error_code& error, int rc) + int async_user_handler(asio::error_code error, int rc) { + if (rc < 0) + { + if (!error) + error = asio::error::no_recovery; + rc = 0; + } + user_handler_(error, rc); return 0; } @@ -280,19 +308,23 @@ private: { unsigned char *data_start = send_buf_.get_unused_start(); send_buf_.data_added(len); - + + BOOST_ASSERT(strand_); asio::async_write ( socket_, asio::buffer(data_start, len), - boost::bind + strand_->wrap ( - &openssl_operation::async_write_handler, - this, - is_operation_done, - rc, - asio::placeholders::error, - asio::placeholders::bytes_transferred + boost::bind + ( + &openssl_operation::async_write_handler, + this, + is_operation_done, + rc, + asio::placeholders::error, + asio::placeholders::bytes_transferred + ) ) ); @@ -315,8 +347,8 @@ private: } // OPeration is not done and writing to net has been made... - // start reading... - do_async_read(); + // start operation again + start(); return 0; } @@ -339,21 +371,26 @@ private: handler_(error, rc); } - void do_async_read() + int do_async_read() { // Wait for new data + BOOST_ASSERT(strand_); socket_.async_read_some ( asio::buffer(recv_buf_.get_unused_start(), recv_buf_.get_unused_len()), - boost::bind + strand_->wrap ( - &openssl_operation::async_read_handler, - this, - asio::placeholders::error, - asio::placeholders::bytes_transferred - ) + boost::bind + ( + &openssl_operation::async_read_handler, + this, + asio::placeholders::error, + asio::placeholders::bytes_transferred + ) + ) ); + return 0; } void async_read_handler(const asio::error_code& error, @@ -432,8 +469,8 @@ private: // Finish the operation, with success return rc; - // Operation is not finished, read data from net... - return do_sync_read(); + // Operation is not finished, start again. + return start(); } int do_sync_read() diff --git a/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp b/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp index 3812deb80..ea0339326 100644 --- a/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp +++ b/libtorrent/include/asio/ssl/detail/openssl_stream_service.hpp @@ -20,6 +20,7 @@ #include "asio/detail/push_options.hpp" #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "asio/error.hpp" #include "asio/io_service.hpp" +#include "asio/strand.hpp" #include "asio/detail/service_base.hpp" #include "asio/ssl/basic_context.hpp" #include "asio/ssl/stream_base.hpp" @@ -42,6 +44,8 @@ class openssl_stream_service : public asio::detail::service_base { private: + enum { max_buffer_size = INT_MAX }; + //Base handler for asyncrhonous operations template class base_handler @@ -160,7 +164,8 @@ public: // Construct a new stream socket service for the specified io_service. explicit openssl_stream_service(asio::io_service& io_service) - : asio::detail::service_base(io_service) + : asio::detail::service_base(io_service), + strand_(io_service) { } @@ -255,11 +260,12 @@ public: local_handler, boost::arg<1>(), boost::arg<2>() - ) + ), + strand_ ); local_handler->set_operation(op); - get_io_service().post(boost::bind(&openssl_operation::start, op)); + strand_.post(boost::bind(&openssl_operation::start, op)); } // Shut down SSL on the stream. @@ -309,11 +315,12 @@ public: local_handler, boost::arg<1>(), boost::arg<2>() - ) + ), + strand_ ); local_handler->set_operation(op); - get_io_service().post(boost::bind(&openssl_operation::start, op)); + strand_.post(boost::bind(&openssl_operation::start, op)); } // Write some data to the stream. @@ -324,10 +331,14 @@ public: size_t bytes_transferred = 0; try { + std::size_t buffer_size = asio::buffer_size(*buffers.begin()); + if (buffer_size > max_buffer_size) + buffer_size = max_buffer_size; + boost::function send_func = boost::bind(&::SSL_write, boost::arg<1>(), asio::buffer_cast(*buffers.begin()), - static_cast(asio::buffer_size(*buffers.begin()))); + static_cast(buffer_size)); openssl_operation op( send_func, next_layer, @@ -356,10 +367,14 @@ public: send_handler* local_handler = new send_handler(handler, get_io_service()); + std::size_t buffer_size = asio::buffer_size(*buffers.begin()); + if (buffer_size > max_buffer_size) + buffer_size = max_buffer_size; + boost::function send_func = boost::bind(&::SSL_write, boost::arg<1>(), asio::buffer_cast(*buffers.begin()), - static_cast(asio::buffer_size(*buffers.begin()))); + static_cast(buffer_size)); openssl_operation* op = new openssl_operation ( @@ -374,11 +389,12 @@ public: local_handler, boost::arg<1>(), boost::arg<2>() - ) + ), + strand_ ); local_handler->set_operation(op); - get_io_service().post(boost::bind(&openssl_operation::start, op)); + strand_.post(boost::bind(&openssl_operation::start, op)); } // Read some data from the stream. @@ -389,10 +405,14 @@ public: size_t bytes_transferred = 0; try { + std::size_t buffer_size = asio::buffer_size(*buffers.begin()); + if (buffer_size > max_buffer_size) + buffer_size = max_buffer_size; + boost::function recv_func = boost::bind(&::SSL_read, boost::arg<1>(), asio::buffer_cast(*buffers.begin()), - asio::buffer_size(*buffers.begin())); + static_cast(buffer_size)); openssl_operation op(recv_func, next_layer, impl->recv_buf, @@ -421,10 +441,14 @@ public: recv_handler* local_handler = new recv_handler(handler, get_io_service()); + std::size_t buffer_size = asio::buffer_size(*buffers.begin()); + if (buffer_size > max_buffer_size) + buffer_size = max_buffer_size; + boost::function recv_func = boost::bind(&::SSL_read, boost::arg<1>(), asio::buffer_cast(*buffers.begin()), - asio::buffer_size(*buffers.begin())); + static_cast(buffer_size)); openssl_operation* op = new openssl_operation ( @@ -439,11 +463,12 @@ public: local_handler, boost::arg<1>(), boost::arg<2>() - ) + ), + strand_ ); local_handler->set_operation(op); - get_io_service().post(boost::bind(&openssl_operation::start, op)); + strand_.post(boost::bind(&openssl_operation::start, op)); } // Peek at the incoming data on the stream. @@ -465,6 +490,8 @@ public: } private: + asio::io_service::strand strand_; + typedef asio::detail::mutex mutex_type; template diff --git a/libtorrent/include/asio/ssl/stream.hpp b/libtorrent/include/asio/ssl/stream.hpp index 9ee48fef1..e22f6b06c 100644 --- a/libtorrent/include/asio/ssl/stream.hpp +++ b/libtorrent/include/asio/ssl/stream.hpp @@ -44,16 +44,15 @@ namespace ssl { * @e Shared @e objects: Unsafe. * * @par Example - * To use the SSL stream template with a stream_socket, you would write: + * To use the SSL stream template with an ip::tcp::socket, you would write: * @code * asio::io_service io_service; * asio::ssl::context context(io_service, asio::ssl::context::sslv23); - * asio::ssl::stream sock(io_service, context); + * asio::ssl::stream sock(io_service, context); * @endcode * * @par Concepts: - * Async_Object, Async_Read_Stream, Async_Write_Stream, Error_Source, Stream, - * Sync_Read_Stream, Sync_Write_Stream. + * AsyncReadStream, AsyncWriteStream, Stream, SyncRead_Stream, SyncWriteStream. */ template class stream diff --git a/libtorrent/include/asio/version.hpp b/libtorrent/include/asio/version.hpp index 82cf4e15a..f2d68f986 100644 --- a/libtorrent/include/asio/version.hpp +++ b/libtorrent/include/asio/version.hpp @@ -18,6 +18,6 @@ // ASIO_VERSION % 100 is the sub-minor version // ASIO_VERSION / 100 % 1000 is the minor version // ASIO_VERSION / 100000 is the major version -#define ASIO_VERSION 308 // 0.3.8 +#define ASIO_VERSION 309 // 0.3.9 #endif // ASIO_VERSION_HPP From d32ffa7ace5fd837fe91b5109c799a4105ffd327 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 11 Feb 2008 03:13:07 +0000 Subject: [PATCH 0478/1009] Remove shutdown() from TorrentManager because stop() is already called during a component shutdown. --- deluge/core/torrentmanager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 6417390a9..78209c32b 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -119,9 +119,6 @@ class TorrentManager(component.Component): self.pause_all() for key in self.torrents.keys(): self.torrents[key].write_fastresume() - - def shutdown(self): - self.stop() def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" From 9df4492083395616bc21138cf0fc2131abb522ae Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 11 Feb 2008 16:59:29 +0000 Subject: [PATCH 0479/1009] refactor forms --- deluge/ui/webui/webui_plugin/config.py | 110 +---------- .../webui/webui_plugin/config_tabs_deluge.py | 73 +++---- .../webui/webui_plugin/config_tabs_webui.py | 19 +- .../webui/webui_plugin/lib/newforms/forms.py | 2 +- .../webui/webui_plugin/lib/newforms_plus.py | 178 ++++++++++++++++++ .../templates/advanced/index.html | 2 +- 6 files changed, 231 insertions(+), 153 deletions(-) create mode 100644 deluge/ui/webui/webui_plugin/lib/newforms_plus.py diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index d8b6f5ae1..b590e9b25 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import lib.newforms as forms +import lib.newforms_plus as forms import page_decorators as deco import lib.webpy022 as web from webserver_common import ws @@ -43,37 +43,7 @@ import os groups = [] blocks = forms.utils.datastructures.SortedDict() -class Form(forms.Form): - info = "" - title = "No Title" - def __init__(self,data = None): - if data == None: - data = self.initial_data() - forms.Form.__init__(self,data) - - def initial_data(self): - "override in subclass" - return None - - def start_save(self): - "called by config_page" - data = web.Storage(self.clean_data) - self.validate(data) - self.save(data) - self.post_save() - - def save(self, vars): - "override in subclass" - raise NotImplementedError() - - def post_save(self): - pass - - def validate(self, data): - pass - - -class WebCfgForm(Form): +class WebCfgForm(forms.Form): "config base for webui" def initial_data(self): return ws.config @@ -82,7 +52,7 @@ class WebCfgForm(Form): ws.config.update(data) ws.save_config() -class CookieCfgForm(Form): +class CookieCfgForm(forms.Form): "config base for webui" def initial_data(self): return ws.config @@ -92,85 +62,13 @@ class CookieCfgForm(Form): ws.save_config() -class CfgForm(Form): +class CfgForm(forms.Form): "config base for deluge-cfg" def initial_data(self): return ws.proxy.get_config() def save(self, data): ws.proxy.set_config(dict(data)) - -#convenience Input Fields. -class _IntInput(forms.TextInput): - """ - because deluge-floats are edited as ints. - """ - def render(self, name, value, attrs=None): - try: - value = int(float(value)) - if value == -1: - value = _("Unlimited") - except: - pass - return forms.TextInput.render(self, name, value, attrs) - -class CheckBox(forms.BooleanField): - "Non Required ChoiceField" - def __init__(self,label, **kwargs): - forms.BooleanField.__init__(self,label=label,required=False,**kwargs) - -class IntCombo(forms.ChoiceField): - """ - choices are the display-values - returns int for the chosen display-value. - """ - def __init__(self, label, choices, **kwargs): - forms.ChoiceField.__init__(self, label=label, - choices=enumerate(choices), **kwargs) - - def clean(self, value): - return int(forms.ChoiceField.clean(self, value)) - -class DelugeInt(forms.IntegerField): - def __init__(self, label , **kwargs): - forms.IntegerField.__init__(self, label=label, min_value=-1, - max_value=sys.maxint, widget=_IntInput, **kwargs) - - def clean(self, value): - if str(value).lower() == _('Unlimited').lower(): - value = -1 - return int(forms.IntegerField.clean(self, value)) - -class DelugeFloat(DelugeInt): - def clean(self, value): - return int(DelugeInt.clean(self, value)) - -class MultipleChoice(forms.MultipleChoiceField): - #temp/test - def __init__(self, label, choices, **kwargs): - forms.MultipleChoiceField.__init__(self, label=label, choices=choices, - widget=forms.CheckboxSelectMultiple, required=False) - -class ServerFolder(forms.CharField): - def __init__(self, label, **kwargs): - forms.CharField.__init__(self, label=label,**kwargs) - - def clean(self, value): - value = value.rstrip('/').rstrip('\\') - self.validate(value) - return forms.CharField.clean(self, value) - - def validate(self, value): - if (value and not os.path.isdir(value)): - raise forms.ValidationError(_("This folder does not exist.")) - -class Password(forms.CharField): - def __init__(self, label, **kwargs): - forms.CharField.__init__(self, label=label, widget=forms.PasswordInput, - **kwargs) - -#/fields - class config_page: """ web.py config page diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index e8489e79b..092732db8 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import lib.newforms as forms +import lib.newforms_plus as forms import config import utils from webserver_common import ws @@ -42,7 +42,7 @@ class NetworkPorts(config.CfgForm ): info = _("Restart daemon after changing these values.") _port_from = forms.IntegerField(_("From")) _port_to = forms.IntegerField(_("To")) - random_port = config.CheckBox(_("Random")) + random_port = forms.CheckBox(_("Random")) def initial_data(self): data = config.CfgForm.initial_data(self) @@ -63,24 +63,24 @@ config.register_block('network','ports', NetworkPorts) class NetworkExtra(config.CfgForm ): title = _("Extra's") - dht = config.CheckBox(_("Mainline DHT")) - upnp = config.CheckBox(_("UpNP")) - natpmp = config.CheckBox(_("NAT-PMP")) - utpex = config.CheckBox(_("Peer-Exchange")) - lsd = config.CheckBox(_("LSD")) + dht = forms.CheckBox(_("Mainline DHT")) + upnp = forms.CheckBox(_("UpNP")) + natpmp = forms.CheckBox(_("NAT-PMP")) + utpex = forms.CheckBox(_("Peer-Exchange")) + lsd = forms.CheckBox(_("LSD")) config.register_block('network','extra', NetworkExtra) class NetworkEnc(config.CfgForm ): title = _("Encryption") - _enc_choices = [_("Forced"),_("Enabled"),_("Disabled")] - _level_choices = [_("Handshake"), _("Full") , _("Either")] + _enc_choices = list(enumerate([_("Forced"),_("Enabled"),_("Disabled")])) + _level_choices = list(enumerate([_("Handshake"), _("Full") , _("Either")])) - enc_in_policy = config.IntCombo(_("Inbound"), _enc_choices) - enc_out_policy = config.IntCombo(_("Outbound"), _enc_choices) - enc_level = config.IntCombo(_("Level"), _level_choices) - enc_prefer_rc4 = config.CheckBox("Prefer to encrypt entire stream") + enc_in_policy = forms.IntChoiceField(_("Inbound"), _enc_choices) + enc_out_policy = forms.IntChoiceField(_("Outbound"), _enc_choices) + enc_level = forms.IntChoiceField(_("Level"), _level_choices) + enc_prefer_rc4 = forms.CheckBox("Prefer to encrypt entire stream") config.register_block('network','encryption', NetworkEnc) @@ -88,44 +88,45 @@ config.register_block('network','encryption', NetworkEnc) class BandwithGlobal(config.CfgForm): title = _("Global") info = _("-1 = Unlimited") - max_connections_global = config.DelugeInt(_("Maximum Connections")) - max_download_speed = config.DelugeFloat(_("Maximum Download Speed (Kib/s)")) - max_upload_speed = config.DelugeFloat(_("Maximum Upload Speed (Kib/s)")) - max_upload_slots_global = config.DelugeInt(_("Maximum Upload Slots")) + max_connections_global = forms.DelugeInt(_("Maximum Connections")) + max_download_speed = forms.DelugeFloat(_("Maximum Download Speed (Kib/s)")) + max_upload_speed = forms.DelugeFloat(_("Maximum Upload Speed (Kib/s)")) + max_upload_slots_global = forms.DelugeInt(_("Maximum Upload Slots")) config.register_block('bandwidth','global', BandwithGlobal) class BandwithTorrent(config.CfgForm): title = _("Per Torrent") info = _("-1 = Unlimited") - max_connections_per_torrent = config.DelugeInt(_("Maximum Connections")) - max_upload_slots_per_torrent = config.DelugeInt(_("Maximum Upload Slots")) + max_connections_per_torrent = forms.DelugeInt(_("Maximum Connections")) + max_upload_slots_per_torrent = forms.DelugeInt(_("Maximum Upload Slots")) config.register_block('bandwidth','torrent', BandwithTorrent) class Download(config.CfgForm): title = _("Download") - download_location = config.ServerFolder(_("Store all downoads in")) - torrentfiles_location = config.ServerFolder(_("Save .torrent files to")) - autoadd_location = config.ServerFolder(_("Auto Add folder"), required=False) - compact_allocation = config.CheckBox(_('Use Compact Allocation')) - prioritize_first_last_pieces = config.CheckBox( + download_location = forms.ServerFolder(_("Store all downoads in")) + torrentfiles_location = forms.ServerFolder(_("Save .torrent files to")) + autoadd_location = forms.ServerFolder(_("Auto Add folder"), required=False) + compact_allocation = forms.CheckBox(_('Use Compact Allocation')) + prioritize_first_last_pieces = forms.CheckBox( _('Prioritize first and last pieces')) config.register_block('deluge','download', Download) class Daemon(config.CfgForm): title = _("Daemon") + info = _("Restart daemon and webui after changing these settings") daemon_port = forms.IntegerField(_("Port")) - allow_remote = config.CheckBox(_("Allow Remote Connections")) + allow_remote = forms.CheckBox(_("Allow Remote Connections")) config.register_block('deluge','daemon', Daemon) -class Plugins(config.Form): +class Plugins(forms.Form): title = _("Enabled Plugins") _choices = [(p,p) for p in ws.proxy.get_available_plugins()] - enabled_plugins = config.MultipleChoice(_(""), _choices) + enabled_plugins = forms.MultipleChoice(_(""), _choices) def initial_data(self): return {'enabled_plugins':ws.proxy.get_enabled_plugins()} @@ -136,19 +137,19 @@ class Plugins(config.Form): config.register_block('deluge','plugins', Plugins) -class Queue(config.Form): +class Queue(forms.Form): title = _("Queue") info = _("queue-cfg not finished") - queue_top = config.CheckBox(_("Queue new torrents to top")) - total_active = config.DelugeInt(_("Total active torrents")) - total_seeding = config.DelugeInt(_("Total active seeding")) - total_downloading = config.DelugeInt(_("Total active downloading")) + queue_top = forms.CheckBox(_("Queue new torrents to top")) + total_active = forms.DelugeInt(_("Total active torrents")) + total_seeding = forms.DelugeInt(_("Total active seeding")) + total_downloading = forms.DelugeInt(_("Total active downloading")) - queue_bottom = config.CheckBox(_("Queue completed torrents to bottom")) - stop_on_ratio = config.CheckBox(_("Stop seeding when ratio reaches")) - stop_ratio = config.DelugeInt(_("TODO:float-edit-box")) - remove_after_stop = config.CheckBox(_("Remve torrent when ratio reached")) + queue_bottom = forms.CheckBox(_("Queue completed torrents to bottom")) + stop_on_ratio = forms.CheckBox(_("Stop seeding when ratio reaches")) + stop_ratio = forms.DelugeInt(_("TODO:float-edit-box")) + remove_after_stop = forms.CheckBox(_("Remve torrent when ratio reached")) def save(self, value): raise forms.ValidationError("SAVE:TODO") diff --git a/deluge/ui/webui/webui_plugin/config_tabs_webui.py b/deluge/ui/webui/webui_plugin/config_tabs_webui.py index 5937ca699..f61aa8fa1 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_webui.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_webui.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import lib.newforms as forms +import lib.newforms_plus as forms import config import utils from webserver_common import ws @@ -41,11 +41,12 @@ class Template(config.WebCfgForm): title = _("Template") _templates = [(t,t) for t in ws.get_templates()] - _button_choices = [_('Text and image'), _('Image Only'), _('Text Only')] + _button_choices = enumerate([_('Text and image'), _('Image Only') + , _('Text Only')]) template = forms.ChoiceField( label=_("Template"), choices = _templates) - button_style = config.IntCombo(_("Button style"),_button_choices) - cache_templates = config.CheckBox(_("Cache templates")) + button_style = forms.IntChoiceField(_("Button style"),_button_choices) + cache_templates = forms.CheckBox(_("Cache templates")) def post_save(self): from render import render @@ -62,7 +63,7 @@ class Server(config.WebCfgForm): port = forms.IntegerField(label = _("Port"),min_value=80) - use_https = config.CheckBox(_("Use https")) + use_https = forms.CheckBox(_("Use https")) def post_save(self): @@ -70,12 +71,12 @@ class Server(config.WebCfgForm): #raise forms.ValidationError( # _("Manually restart server to apply these changes.")) -class Password(config.Form): +class Password(forms.Form): title = _("Password") - old_pwd = config.Password(_("Current Password")) - new1 = config.Password(_("New Password")) - new2 = config.Password(_("New Password (Confirm)")) + old_pwd = forms.Password(_("Current Password")) + new1 = forms.Password(_("New Password")) + new2 = forms.Password(_("New Password (Confirm)")) def save(self,data): ws.update_pwd(data.new1) diff --git a/deluge/ui/webui/webui_plugin/lib/newforms/forms.py b/deluge/ui/webui/webui_plugin/lib/newforms/forms.py index 97f0efcb6..df4af3b54 100644 --- a/deluge/ui/webui/webui_plugin/lib/newforms/forms.py +++ b/deluge/ui/webui/webui_plugin/lib/newforms/forms.py @@ -9,7 +9,7 @@ from widgets import TextInput, Textarea, HiddenInput, MultipleHiddenInput from util import flatatt, StrAndUnicode, ErrorDict, ErrorList, ValidationError import copy -__all__ = ('BaseForm', 'Form') +#__all__ = ('BaseForm', 'Form') NON_FIELD_ERRORS = '__all__' diff --git a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py new file mode 100644 index 000000000..e11c56b4d --- /dev/null +++ b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) Martijn Voncken 2008 +# Django Lisence, see ./newforms/LICENCE +# + +from newforms import * +import newforms +from newforms.forms import BoundField + +import sys, os + + +import webpy022 as web #todo:remove this dependency. + +#Form +class FilteredForm(newforms.Form): + """ + used to enable more complex layouts. + the filter argument contains the names of the fields to render. + """ + def _html_output_filtered(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row, filter): + """ + Helper function for outputting HTML. Used by as_table(), as_ul(), as_p(). + newforms_plus: 99% c&p from newforms, added filter. + """ + top_errors = self.non_field_errors() # Errors that should be displayed above all fields. + output, hidden_fields = [], [] + for name, field in self.fields.items(): + #FilteredForm + if (filter != None) and (not name in filter): + continue + #/FilteredForm + bf = BoundField(self, field, name) + bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable. + if bf.is_hidden: + if bf_errors: + top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors]) + hidden_fields.append(unicode(bf)) + else: + if errors_on_separate_row and bf_errors: + output.append(error_row % bf_errors) + label = bf.label and bf.label_tag(escape(bf.label + ':')) or '' + if field.help_text: + help_text = help_text_html % field.help_text + else: + help_text = u'' + output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text}) + if top_errors: + output.insert(0, error_row % top_errors) + if hidden_fields: # Insert any hidden fields in the last row. + str_hidden = u''.join(hidden_fields) + if output: + last_row = output[-1] + # Chop off the trailing row_ender (e.g. '') and insert the hidden fields. + output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender + else: # If there aren't any rows in the output, just append the hidden fields. + output.append(str_hidden) + return u'\n'.join(output) + + def as_table(self , filter = None): + "Returns this form rendered as HTML s -- excluding the
        $torrent.category$torrent.num_seeds ($torrent.total_seeds)$torrent.num_peers ($torrent.total_peers) + $if torrent.total_seeds != -1: + $torrent.num_seeds ($torrent.total_seeds) + $else: + - + + $if torrent.total_peers != -1: + $torrent.num_peers ($torrent.total_peers) $if (torrent.download_rate): $fspeed(torrent.download_rate) $else: -   + - $if (torrent.upload_rate): $fspeed(torrent.upload_rate) $else: -   + - $ftime(torrent.eta) $("%.3f" % torrent.distributed_copies) $if torrent.total_peers != -1: $torrent.num_peers ($torrent.total_peers) $if (torrent.download_rate): $fspeed(torrent.download_rate) $else: - - +   $if (torrent.upload_rate): $fspeed(torrent.upload_rate) $else: - - +   $ftime(torrent.eta) $("%.3f" % torrent.distributed_copies)
        ." + return self._html_output_filtered(u'
        %(label)s%(errors)s%(field)s%(help_text)s
        %s
        +
        $:(sort_head('calc_state_str', 'S')) From 8f8a6e41aa2b3ea88948e6542d449844453f8e6d Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 11 Feb 2008 19:09:23 +0000 Subject: [PATCH 0480/1009] torrent_add:options ui --- deluge/ui/webui/webui_plugin/config.py | 2 - .../webui/webui_plugin/lib/newforms_plus.py | 6 +-- deluge/ui/webui/webui_plugin/pages.py | 38 ++----------------- deluge/ui/webui/webui_plugin/render.py | 10 ++++- .../templates/advanced/static/advanced.css | 6 +++ .../templates/deluge/torrent_add.html | 23 +++++++++++ .../webui_plugin/tests/multicall_notepad.py | 10 ++--- deluge/ui/webui/webui_plugin/utils.py | 8 +--- 8 files changed, 50 insertions(+), 53 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index b590e9b25..737fe5b7f 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# deluge_webserver.py -# # Copyright (C) Martijn Voncken 2008 # # This program is free software; you can redistribute it and/or modify diff --git a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py index e11c56b4d..e093e5c20 100644 --- a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py +++ b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py @@ -58,9 +58,9 @@ class FilteredForm(newforms.Form): output.append(str_hidden) return u'\n'.join(output) - def as_table(self , filter = None): + def as_table(self , filter = None): #add class="newforms" "Returns this form rendered as HTML s -- excluding the
        ." - return self._html_output_filtered(u'%(label)s%(errors)s%(field)s%(help_text)s', u'%s', '', u'
        %s', False, filter) + return self._html_output_filtered(u'%(label)s%(errors)s%(field)s%(help_text)s', u'%s', '', u'
        %s', False, filter) def as_ul(self, filter = None): "Returns this form rendered as HTML
      • s -- excluding the
          ." @@ -70,7 +70,7 @@ class FilteredForm(newforms.Form): "Returns this form rendered as HTML

          s." return self._html_output_filtered(u'

          %(label)s %(field)s%(help_text)s

          ', u'

          %s

          ', '

          ', u' %s', True, filter) -class Form(newforms.Form): +class Form(FilteredForm): info = "" title = "No Title" def __init__(self,data = None): diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 8ed068048..f31aff872 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -54,6 +54,9 @@ import os from json_api import json_api +#special/complex pages: +from torrent_add import torrent_add + #routing: urls = ( "/login", "login", @@ -176,41 +179,6 @@ class torrent_reannounce: ws.proxy.force_reannounce([torrent_id]) do_redirect() -class torrent_add: - @deco.deluge_page - def GET(self, name): - return render.torrent_add() - - @deco.check_session - def POST(self, name): - """ - allows: - *posting of url - *posting file-upload - *posting of data as string(for greasemonkey-private) - """ - - vars = web.input(url = None, torrent = {}) - - torrent_name = None - torrent_data = None - if vars.torrent.filename: - torrent_name = vars.torrent.filename - torrent_data = vars.torrent.file.read() - - if vars.url and torrent_name: - error_page(_("Choose an url or a torrent, not both.")) - if vars.url: - ws.proxy.add_torrent_url(vars.url) - do_redirect() - elif torrent_name: - data_b64 = base64.b64encode(torrent_data) - #b64 because of strange bug-reports related to binary data - ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64) - do_redirect() - else: - error_page(_("no data.")) - class remote_torrent_add: """ For use in remote scripts etc. diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index dbcf5ca1b..b304ae4c3 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -48,6 +48,9 @@ class subclassed_render(object): self.apply_cfg() def apply_cfg(self): + if not hasattr(ws,"config"): + print "render.py: WARNING,no config" + return cache = ws.config.get('cache_templates') self.base_template = template.render( os.path.join(ws.webui_path, 'templates/deluge/'), @@ -79,8 +82,11 @@ def category_tabs(torrent_list): def template_crop(text, end): - if len(text) > end: - return text[0:end - 3] + '...' + try: + if len(text) > end: + return text[0:end - 3] + '...' + except: + return "[ERROR NOT A STRING:(%s)]" % text return text def template_sort_head(id,name): diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index b8135c2d8..7dc2777dc 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -318,6 +318,12 @@ ul.errorlist { #torrent_list { -moz-border-radius:7px; } + +th.newforms { + font-size: 12px; + text-align:right; + color:#FFFFFF; +} /* Hides from IE-mac \*/ * html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */ diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html index 490f300bf..067845ac5 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html @@ -1,3 +1,4 @@ +$def with (add_form) $:render.header(_("Add Torrent"))
          @@ -27,6 +28,28 @@ $if get_config('add_another'): +

          Options

          +
          Options are not used yet!
          + +

          $_("Allocation/Location")

          + + $:add_form.as_table(["download_location", "compact_allocation"]) + + +
          + +

          $_("BandWidth")

          + +$:add_form.as_table(["max_download_speed_per_torrent", "max_upload_speed_per_torrent", "max_connections_per_torrent", "max_upload_slots_per_torrent"]) +
          + + +

          $_("General")

          + + $:add_form.as_table(["prioritize_first_last_pieces", "add_paused", "default_private"]) +
          + +
        • diff --git a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py index 501f48c45..62bf7a1ff 100644 --- a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py +++ b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py @@ -68,14 +68,14 @@ start = time.time() torrent_ids = ws.proxy.get_session_state() #Syc-api. torrent_dict = {} for id in torrent_ids: - async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, []) + async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, ["name"]) async_proxy.force_call(block=True) print "Async-list:",time.time() - start -print torrent_dict - - -print sorted(torrent_list[0].keys()) +print torrent_dict[torrent_ids[0]].keys() +print torrent_dict[torrent_ids[0]]["name"] + + diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 2ebdb708f..4a9986302 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -237,16 +237,12 @@ def get_torrent_list(): torrent_ids = ws.proxy.get_session_state() #Syc-api. torrent_dict = {} for id in torrent_ids: - ws.async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, []) + ws.async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, + TORRENT_KEYS) ws.async_proxy.force_call(block=True) - return [enhance_torrent_status(id, status) for id, status in torrent_dict.iteritems()] - - - - def get_categories(torrent_list): trackers = [(torrent['category'] or 'unknown') for torrent in torrent_list] categories = {} From 8719ad3c50457c66f23c443936bfbfdc7c6b6597 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 11 Feb 2008 19:13:54 +0000 Subject: [PATCH 0481/1009] torrent_add : start of options ui --- deluge/ui/webui/webui_plugin/torrent_add.py | 112 ++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 deluge/ui/webui/webui_plugin/torrent_add.py diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py new file mode 100644 index 000000000..8152fea26 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) Martijn Voncken 2008 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from webserver_common import ws +from utils import * +from render import render, error_page +import page_decorators as deco +import lib.newforms_plus as forms + +""" + self.glade.get_widget("button_location").set_current_folder( + options["download_location"]) + self.glade.get_widget("radio_compact").set_active( + options["compact_allocation"]) + self.glade.get_widget("spin_maxdown").set_value( + options["max_download_speed_per_torrent"]) + self.glade.get_widget("spin_maxup").set_value( + options["max_upload_speed_per_torrent"]) + self.glade.get_widget("spin_maxconnections").set_value( + options["max_connections_per_torrent"]) + self.glade.get_widget("spin_maxupslots").set_value( + options["max_upload_slots_per_torrent"]) + self.glade.get_widget("chk_paused").set_active( + options["add_paused"]) + self.glade.get_widget("chk_prioritize").set_active( + options["prioritize_first_last_pieces"]) + self.glade.get_widget("chk_private").set_active( + options["default_private"]) +""" +class AddForm(forms.Form): + download_location = forms.ServerFolder(_("Download Location")) + compact_allocation = forms.CheckBox(_("Compact Allocation")) + + #download + max_download_speed_per_torrent = forms.DelugeFloat( + _("Maximum Down Speed")) + max_upload_speed_per_torrent = forms.DelugeFloat( + _("Maximum Up Speed")) + max_connections_per_torrent = forms.DelugeInt(_("Maximum Connections")) + max_upload_slots_per_torrent = forms.DelugeInt(_("Maximum Upload Slots")) + #general + prioritize_first_last_pieces = forms.CheckBox( + _('Prioritize first and last pieces')) + add_paused = forms.CheckBox(_('Add In Paused State')) + default_private = forms.CheckBox(_('Set Private Flag')) + + def initial_data(self): + return ws.proxy.get_config() + +class torrent_add: + + @deco.deluge_page + def GET(self, name): + return render.torrent_add(AddForm()) + + @deco.check_session + def POST(self, name): + """ + allows: + *posting of url + *posting file-upload + *posting of data as string(for greasemonkey-private) + """ + + vars = web.input(url = None, torrent = {}) + + torrent_name = None + torrent_data = None + if vars.torrent.filename: + torrent_name = vars.torrent.filename + torrent_data = vars.torrent.file.read() + + if vars.url and torrent_name: + error_page(_("Choose an url or a torrent, not both.")) + if vars.url: + ws.proxy.add_torrent_url(vars.url) + do_redirect() + elif torrent_name: + data_b64 = base64.b64encode(torrent_data) + #b64 because of strange bug-reports related to binary data + ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64) + do_redirect() + else: + error_page(_("no data.")) From 9be62d205626228b516af9c63433e34cf320d1b5 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 11 Feb 2008 20:30:44 +0000 Subject: [PATCH 0482/1009] add form again --- deluge/ui/webui/webui_plugin/pages.py | 2 +- deluge/ui/webui/webui_plugin/run_webserver | 5 -- .../templates/deluge/torrent_add.html | 63 ++++++++++++++----- 3 files changed, 50 insertions(+), 20 deletions(-) delete mode 100755 deluge/ui/webui/webui_plugin/run_webserver diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index f31aff872..d73497a47 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -48,7 +48,7 @@ import lib.webpy022 as web from lib.webpy022.http import seeother, url from lib.static_handler import static_handler -import base64 + from operator import attrgetter import os diff --git a/deluge/ui/webui/webui_plugin/run_webserver b/deluge/ui/webui/webui_plugin/run_webserver deleted file mode 100755 index 7122e41dd..000000000 --- a/deluge/ui/webui/webui_plugin/run_webserver +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -import deluge_webserver -deluge_webserver.ws.init_05() -deluge_webserver.run() - diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html index 067845ac5..0488f6f8b 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html @@ -1,9 +1,15 @@ -$def with (add_form) +$def with (add_form, options_form) $:render.header(_("Add Torrent"))
          -
          + + +
          + + $:add_form.as_table() +
          + + + -
          - - +$_('Options') + -

          Options

          -
          Options are not used yet!
          +
          -

          $_("Allocation/Location")

          +
          + +
          + + +
          +
          + + $:render.footer() From 22352888f63072b7858c83fed8868c05233c01af Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 11 Feb 2008 20:42:48 +0000 Subject: [PATCH 0483/1009] add form again --- deluge/ui/webui/webui_plugin/pages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index d73497a47..ab2f922ff 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -30,7 +30,7 @@ # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. - +# from webserver_common import ws from utils import * from render import render, error_page From 9beda7c96ed7ddb806791bb47b627e26269217f9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 12 Feb 2008 02:42:09 +0000 Subject: [PATCH 0484/1009] Return a full status, including functions, when len(keys) is 0. --- deluge/core/torrent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 23cfdec60..4e4ce905a 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -272,6 +272,8 @@ class Torrent: if len(keys) == 0: status_dict = full_status + for key in fns: + status_dict[key] = fns[key]() else: for key in keys: if key in full_status: From b0a9bf49fe0a57ec0953abba6668a7b7208a2237 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 12 Feb 2008 16:53:48 +0000 Subject: [PATCH 0485/1009] fix broken svn state on torrent_add --- deluge/ui/webui/webui_plugin/torrent_add.py | 38 ++++++++------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index 8152fea26..d4185c073 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -28,34 +28,15 @@ # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. - +# from webserver_common import ws from utils import * from render import render, error_page import page_decorators as deco import lib.newforms_plus as forms +import base64 -""" - self.glade.get_widget("button_location").set_current_folder( - options["download_location"]) - self.glade.get_widget("radio_compact").set_active( - options["compact_allocation"]) - self.glade.get_widget("spin_maxdown").set_value( - options["max_download_speed_per_torrent"]) - self.glade.get_widget("spin_maxup").set_value( - options["max_upload_speed_per_torrent"]) - self.glade.get_widget("spin_maxconnections").set_value( - options["max_connections_per_torrent"]) - self.glade.get_widget("spin_maxupslots").set_value( - options["max_upload_slots_per_torrent"]) - self.glade.get_widget("chk_paused").set_active( - options["add_paused"]) - self.glade.get_widget("chk_prioritize").set_active( - options["prioritize_first_last_pieces"]) - self.glade.get_widget("chk_private").set_active( - options["default_private"]) -""" -class AddForm(forms.Form): +class OptionsForm(forms.Form): download_location = forms.ServerFolder(_("Download Location")) compact_allocation = forms.CheckBox(_("Compact Allocation")) @@ -75,11 +56,20 @@ class AddForm(forms.Form): def initial_data(self): return ws.proxy.get_config() +class AddForm(forms.Form): + url = forms.CharField(label=_("Url"), + widget=forms.TextInput(attrs={'size':60})) + torrent = forms.CharField(label=_("Upload torrent"), + widget=forms.FileInput(attrs={'size':60})) + hash = forms.CharField(label=_("Hash"), + widget=forms.TextInput(attrs={'size':60})) + ret = forms.CheckBox(_('Add more')) + class torrent_add: @deco.deluge_page def GET(self, name): - return render.torrent_add(AddForm()) + return render.torrent_add(AddForm(),OptionsForm()) @deco.check_session def POST(self, name): @@ -109,4 +99,4 @@ class torrent_add: ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64) do_redirect() else: - error_page(_("no data.")) + error_page(_("no data, press back button and try again")) From 0b306717b665a0479064460e61c6e1354b1a7cec Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 12 Feb 2008 18:14:18 +0000 Subject: [PATCH 0486/1009] torrent_add:options --- deluge/ui/webui/webui_plugin/config.py | 14 +------ .../webui/webui_plugin/lib/newforms_plus.py | 8 +++- .../templates/advanced/index.html | 2 +- .../templates/deluge/torrent_add.html | 11 ++++- deluge/ui/webui/webui_plugin/torrent_add.py | 42 +++++++++++++------ deluge/ui/webui/webui_plugin/utils.py | 18 ++++++++ .../ui/webui/webui_plugin/webserver_common.py | 4 +- 7 files changed, 69 insertions(+), 30 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index 737fe5b7f..c89992d4b 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -37,6 +37,7 @@ from render import render from lib.webpy022.http import seeother import sys import os +import utils groups = [] blocks = forms.utils.datastructures.SortedDict() @@ -90,18 +91,7 @@ class config_page: @deco.deluge_page def POST(self,name): - form_class = self.get_form_class(name) - fields = form_class.base_fields.keys() - form_data = web.Storage() - vars = web.input() - for field in fields: - form_data[field] = vars.get(field) - #DIRTY HACK: (for multiple-select) - if isinstance(form_class.base_fields[field], - forms.MultipleChoiceField): - form_data[field] = web.input(**{field:[]})[field] - #/DIRTY HACK - + form_data = utils.get_newforms_data(form_class) form = form_class(form_data) if form.is_valid(): ws.log.debug('save config %s' % form_data) diff --git a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py index e093e5c20..46ba19ddf 100644 --- a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py +++ b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py @@ -10,9 +10,14 @@ from newforms.forms import BoundField import sys, os - import webpy022 as web #todo:remove this dependency. + + + + + + #Form class FilteredForm(newforms.Form): """ @@ -99,7 +104,6 @@ class Form(FilteredForm): def validate(self, data): pass - #convenience Input Fields. class CheckBox(newforms.BooleanField): "Non Required BooleanField,why the f is it required by default?" diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index e5684560e..5652b78d6 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -93,7 +93,7 @@ $for torrent in torrent_list: $fsize(torrent.total_size)
          -
          +
          $torrent.message $int(torrent.progress) %
          diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html index 0488f6f8b..849b171f9 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_add.html @@ -1,10 +1,12 @@ -$def with (add_form, options_form) +$def with (add_form, options_form, error) $:render.header(_("Add Torrent"))
          +$if error: +
          $error
          $:add_form.as_table() @@ -83,10 +85,17 @@ function toggle_options(){ el = document.getElementById("torrent_add_options"); if (el.style.display == "block"){ el.style.display = "none"; + setCookie("torrent_add_options","hide"); } else{ el.style.display = "block"; + setCookie("torrent_add_options","show"); } } + +if (getCookie("torrent_add_options") == "show") { + el = document.getElementById("torrent_add_options"); + el.style.display = "block"; +} $:render.footer() diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index d4185c073..622c43afd 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -30,10 +30,11 @@ # statement from all source files in the program, then also delete it here. # from webserver_common import ws -from utils import * +import utils from render import render, error_page import page_decorators as deco import lib.newforms_plus as forms +import lib.webpy022 as web import base64 class OptionsForm(forms.Form): @@ -57,19 +58,27 @@ class OptionsForm(forms.Form): return ws.proxy.get_config() class AddForm(forms.Form): - url = forms.CharField(label=_("Url"), + url = forms.CharField(label=_("Url"), required=False, widget=forms.TextInput(attrs={'size':60})) - torrent = forms.CharField(label=_("Upload torrent"), + torrent = forms.CharField(label=_("Upload torrent"), required=False, widget=forms.FileInput(attrs={'size':60})) - hash = forms.CharField(label=_("Hash"), + hash = forms.CharField(label=_("Hash"), required=False, widget=forms.TextInput(attrs={'size':60})) ret = forms.CheckBox(_('Add more')) class torrent_add: + def add_page(self,error = None): + form_data = utils.get_newforms_data(AddForm) + options_data = None + if error: + options_data = utils.get_newforms_data(OptionsForm) + return render.torrent_add(AddForm(form_data),OptionsForm(options_data), error) + @deco.deluge_page def GET(self, name): - return render.torrent_add(AddForm(),OptionsForm()) + return self.add_page() + @deco.check_session def POST(self, name): @@ -80,8 +89,14 @@ class torrent_add: *posting of data as string(for greasemonkey-private) """ - vars = web.input(url = None, torrent = {}) + options = dict(utils.get_newforms_data(OptionsForm)) + options_form = OptionsForm(options) + if not options_form.is_valid(): + print self.add_page(error = _("Error in torrent options.")) + return + + vars = web.input(url = None, torrent = {}) torrent_name = None torrent_data = None if vars.torrent.filename: @@ -89,14 +104,17 @@ class torrent_add: torrent_data = vars.torrent.file.read() if vars.url and torrent_name: - error_page(_("Choose an url or a torrent, not both.")) + #error_page(_("Choose an url or a torrent, not both.")) + print self.add_page(error = _("Choose an url or a torrent, not both.")) + return if vars.url: - ws.proxy.add_torrent_url(vars.url) - do_redirect() + ws.proxy.add_torrent_url(vars.url, options) + utils.do_redirect() elif torrent_name: data_b64 = base64.b64encode(torrent_data) #b64 because of strange bug-reports related to binary data - ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64) - do_redirect() + ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64, options) + utils.do_redirect() else: - error_page(_("no data, press back button and try again")) + print self.add_page(error = _("No data")) + return diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 4a9986302..1d7ca20c7 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -296,6 +296,24 @@ def get_category_choosers(torrent_list): return filter_tabs, category_tabs +def get_newforms_data(form_class): + """ + glue for using web.py and newforms. + returns a storified dict with name/value of the post-data. + """ + import lib.newforms_plus as forms + fields = form_class.base_fields.keys() + form_data = web.Storage() + vars = web.input() + for field in fields: + form_data[field] = vars.get(field) + #DIRTY HACK: (for multiple-select) + if isinstance(form_class.base_fields[field], + forms.MultipleChoiceField): + form_data[field] = web.input(**{field:[]})[field] + #/DIRTY HACK + return form_data + #/utils class WebUiError(Exception): diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index f9b901601..61d3bf8f1 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -178,7 +178,7 @@ class Ws: #MONKEY PATCH, TODO->REMOVE!!! - def add_torrent_filecontent(name , data_b64): + def add_torrent_filecontent(name , data_b64, options): self.log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' % (name , len(data_b64))) @@ -191,7 +191,7 @@ class Ws: f.write(base64.b64decode(data_b64)) f.close() - self.proxy.add_torrent_file([filename]) + self.proxy.add_torrent_file([filename] , options) self.proxy.add_torrent_filecontent = add_torrent_filecontent self.log.debug('cfg-file %s' % self.config_file) From 345f61b740b0316970910fb5fa5e5e9b6e8ca189 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 12 Feb 2008 18:39:07 +0000 Subject: [PATCH 0487/1009] torrent_add options bugfix --- deluge/ui/webui/webui_plugin/config.py | 2 +- deluge/ui/webui/webui_plugin/torrent_add.py | 7 ++++--- deluge/ui/webui/webui_plugin/utils.py | 5 +++-- deluge/ui/webui/webui_plugin/webserver_common.py | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index c89992d4b..2384212fc 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -91,7 +91,7 @@ class config_page: @deco.deluge_page def POST(self,name): - form_data = utils.get_newforms_data(form_class) + form_data = web.Storage(utils.get_newforms_data(form_class)) form = form_class(form_data) if form.is_valid(): ws.log.debug('save config %s' % form_data) diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index 622c43afd..ad5bf0806 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -89,12 +89,13 @@ class torrent_add: *posting of data as string(for greasemonkey-private) """ - options = dict(utils.get_newforms_data(OptionsForm)) - options_form = OptionsForm(options) + options_form = OptionsForm(utils.get_newforms_data(OptionsForm)) if not options_form.is_valid(): print self.add_page(error = _("Error in torrent options.")) return + options = options_form.clean_data + vars = web.input(url = None, torrent = {}) torrent_name = None @@ -108,7 +109,7 @@ class torrent_add: print self.add_page(error = _("Choose an url or a torrent, not both.")) return if vars.url: - ws.proxy.add_torrent_url(vars.url, options) + ws.proxy.add_torrent_url(vars.url,options) utils.do_redirect() elif torrent_name: data_b64 = base64.b64encode(torrent_data) diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 1d7ca20c7..12cc9b577 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -224,7 +224,7 @@ def get_torrent_status(torrent_id): helper method. enhance ws.proxy.get_torrent_status with some extra data """ - status = ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS) + status = ws.proxy.get_torrent_status(torrent_id,[]) return enhance_torrent_status(torrent_id, status) @@ -303,10 +303,11 @@ def get_newforms_data(form_class): """ import lib.newforms_plus as forms fields = form_class.base_fields.keys() - form_data = web.Storage() + form_data = {} vars = web.input() for field in fields: form_data[field] = vars.get(field) + #ws.log.debug("form-field:%s=%s" % (field, form_data[field])) #DIRTY HACK: (for multiple-select) if isinstance(form_class.base_fields[field], forms.MultipleChoiceField): diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 61d3bf8f1..d10a9278a 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -190,8 +190,8 @@ class Ws: f = open(filename,"wb") f.write(base64.b64decode(data_b64)) f.close() - - self.proxy.add_torrent_file([filename] , options) + self.log.debug("options:%s" % options) + self.proxy.add_torrent_file([filename] , [options]) self.proxy.add_torrent_filecontent = add_torrent_filecontent self.log.debug('cfg-file %s' % self.config_file) From dfaf263d6645406d43678abd2bcc4a7fd8f53af4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 12 Feb 2008 19:51:45 +0000 Subject: [PATCH 0488/1009] Fix preferences to actually set global per torrent download/upload speeds. Apply global per torrent download/upload speeds to all torrents on change. --- deluge/core/torrent.py | 4 ++-- deluge/core/torrentmanager.py | 24 +++++++++++++++++------- deluge/ui/gtkui/preferences.py | 6 ++++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 4e4ce905a..b2fd84b7b 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -116,11 +116,11 @@ class Torrent: self.handle.set_max_uploads(max_slots) def set_max_upload_speed(self, m_up_speed): - self.set_max_upload_speed = m_up_speed + self.max_upload_speed = m_up_speed self.handle.set_upload_limit(int(m_up_speed * 1024)) def set_max_download_speed(self, m_down_speed): - self.set_max_download_speed = m_down_speed + self.max_download_speed = m_down_speed self.handle.set_download_limit(int(m_down_speed * 1024)) def set_private_flag(self, private): diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 78209c32b..36f38e182 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -76,9 +76,7 @@ class TorrentManager(component.Component): self.alerts = alerts # Get the core config self.config = ConfigManager("core.conf") - # Per torrent connection limit and upload slot limit - self.max_connections = -1 - self.max_uploads = -1 + # Create the torrents dict { torrent_id: Torrent } self.torrents = {} @@ -87,7 +85,11 @@ class TorrentManager(component.Component): self.on_set_max_connections_per_torrent) self.config.register_set_function("max_upload_slots_per_torrent", self.on_set_max_upload_slots_per_torrent) - + self.config.register_set_function("max_upload_speed_per_torrent", + self.on_set_max_upload_speed_per_torrent) + self.config.register_set_function("max_download_speed_per_torrent", + self.on_set_max_download_speed_per_torrent) + # Register alert functions self.alerts.register_handler("torrent_finished_alert", self.on_alert_torrent_finished) @@ -435,17 +437,25 @@ class TorrentManager(component.Component): def on_set_max_connections_per_torrent(self, key, value): """Sets the per-torrent connection limit""" log.debug("max_connections_per_torrent set to %s..", value) - self.max_connections = value for key in self.torrents.keys(): self.torrents[key].set_max_connections(value) def on_set_max_upload_slots_per_torrent(self, key, value): """Sets the per-torrent upload slot limit""" log.debug("max_upload_slots_per_torrent set to %s..", value) - self.max_uploads = value for key in self.torrents.keys(): - self.torrents[key].set_max_uploads(value) + self.torrents[key].set_max_upload_slots(value) + def on_set_max_upload_speed_per_torrent(self, key, value): + log.debug("max_upload_speed_per_torrent set to %s..", value) + for key in self.torrents.keys(): + self.torrents[key].set_max_upload_speed(value) + + def on_set_max_download_speed_per_torrent(self, key, value): + log.debug("max_download_speed_per_torrent set to %s..", value) + for key in self.torrents.keys(): + self.torrents[key].set_max_download_speed(value) + ## Alert handlers ## def on_alert_torrent_finished(self, alert): log.debug("on_alert_torrent_finished") diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 1b24ba4b8..3f6cbd850 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -418,6 +418,12 @@ class Preferences(component.Component): new_core_config["max_upload_slots_per_torrent"] = \ self.glade.get_widget( "spin_max_upload_slots_per_torrent").get_value_as_int() + new_core_config["max_upload_speed_per_torrent"] = \ + self.glade.get_widget( + "spin_max_upload_per_torrent").get_value() + new_core_config["max_download_speed_per_torrent"] = \ + self.glade.get_widget( + "spin_max_download_per_torrent").get_value() ## Interface tab ## new_gtkui_config["enable_system_tray"] = \ From 75663de03c9f5e1b4caf3779b2778b8fa55b891b Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 12 Feb 2008 19:56:11 +0000 Subject: [PATCH 0489/1009] polish --- deluge/ui/webui/webui_plugin/config.py | 1 + deluge/ui/webui/webui_plugin/torrent_add.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index 2384212fc..8b8ea05c8 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -91,6 +91,7 @@ class config_page: @deco.deluge_page def POST(self,name): + form_class = self.get_form_class(name) form_data = web.Storage(utils.get_newforms_data(form_class)) form = form_class(form_data) if form.is_valid(): diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index ad5bf0806..6a996ca39 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -62,9 +62,10 @@ class AddForm(forms.Form): widget=forms.TextInput(attrs={'size':60})) torrent = forms.CharField(label=_("Upload torrent"), required=False, widget=forms.FileInput(attrs={'size':60})) - hash = forms.CharField(label=_("Hash"), required=False, - widget=forms.TextInput(attrs={'size':60})) - ret = forms.CheckBox(_('Add more')) + + #hash = forms.CharField(label=_("Hash"), required=False, + # widget=forms.TextInput(attrs={'size':60})) + #ret = forms.CheckBox(_('Add more')) class torrent_add: @@ -93,7 +94,6 @@ class torrent_add: if not options_form.is_valid(): print self.add_page(error = _("Error in torrent options.")) return - options = options_form.clean_data From 991a4133270c788ed2b77263abb2a11c0c3a9c26 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 12 Feb 2008 21:03:33 +0000 Subject: [PATCH 0490/1009] torrent_info:display torrent files --- deluge/ui/webui/webui_plugin/render.py | 10 ++++++++++ .../templates/advanced/static/advanced.css | 8 ++++++++ .../webui_plugin/templates/deluge/torrent_info.html | 5 ++++- .../ui/webui/webui_plugin/tests/multicall_notepad.py | 6 +++--- deluge/ui/webui/webui_plugin/utils.py | 2 +- deluge/ui/webui/webui_plugin/webserver_common.py | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index b304ae4c3..d3456bce4 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -89,6 +89,15 @@ def template_crop(text, end): return "[ERROR NOT A STRING:(%s)]" % text return text +def template_crop_right(text, maxlen): + try: + if len(text) > maxlen: + return "..." + text[-(maxlen + 3):] + except: + return "[ERROR NOT A STRING:(%s)]" % text + return text + + def template_sort_head(id,name): #got tired of doing these complex things inside templetor.. vars = web.input(sort = None, order = None) @@ -136,6 +145,7 @@ template.Template.globals.update({ 'part_stats':template_part_stats, 'category_tabs':category_tabs, 'crop': template_crop, + 'crop_right': template_crop_right, '_': _ , #gettext/translations 'str': str, #because % in templetor is broken. 'int':int, diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index 7dc2777dc..bcba9c4ad 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -86,6 +86,14 @@ tr.altrow1{ background-color: #37506f; } +tr.tab_altrow0{ + background-color: #304663; +} + +tr.tab_altrow1{ + background-color: #37506f; +} + tr.torrent_table_selected { diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html index 660273e1a..e0cd572c2 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html @@ -2,8 +2,8 @@ $def with (torrent) $:(render.header(torrent.message + '/' + torrent.name))
          -

          $_('Details')

          +

          $_('Details')

          $:render.tab_meta(torrent) $if (torrent.action == 'start'): @@ -18,6 +18,9 @@ $:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reanno $:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png') $:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png') +
          +$:render.tab_files(torrent) +
          - Deluge:$torrent.name - - - + - -$:render.tab_meta(torrent) -$:render.footer() +$:render.part_tab_button('details', _("Details"), active_tab) +$:render.part_tab_button('files', _("Files"), active_tab) +$:render.part_tab_button('options', _("Options"), active_tab) + +$if active_tab == 'details': + $:render.tab_meta(torrent) +$if active_tab == 'files': + $:render.tab_files(torrent) +$if active_tab == 'options': + $:render.tab_options(torrent) + + + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html index dfc0c1b77..5567f12f9 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html @@ -4,6 +4,17 @@ $def with (torrent)
          $altrow(True) -$for file in torrent.files: - +$for i, file in enumerate(torrent.files): + + +
          $_("File")$_("Size")
          $(crop_right(file["path"], 70))$fsize(file["size"])
          + +$(crop_left(file["path"], 70)) + +$fsize(file["size"])
          + +
          Save/update: Todo
          diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html index 4f53e23c4..c3fc0b866 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html @@ -27,8 +27,13 @@ $def with (torrent) $torrent.num_pieces x $fsize(torrent.piece_length) -  -  +$_('Allocation'): + +$if torrent.compact: + $_('Compact') +$else: + $_('Full') + @@ -50,10 +55,11 @@ $fspeed(torrent.download_rate) $_('Availability'): $("%.3f" % torrent.distributed_copies) -  -  +$_('Queue Position'): +$torrent.queue_pos + @@ -68,17 +74,16 @@ $fspeed(torrent.download_rate) $torrent.num_files $_('Tracker'): -$(crop(torrent.tracker, 30)) +$(crop(torrent.tracker, 25)) $_('Tracker Status'): -$(crop(torrent.tracker_status, 30)) +$(crop(torrent.tracker_status, 25)) $_('Next Announce'): $torrent.next_announce - -$_('Queue Position'): -$torrent.queue_pos +$('Save Path'): +$crop_left(torrent.save_path, 25) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_options.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_options.html new file mode 100644 index 000000000..8a3bd03ca --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_options.html @@ -0,0 +1,19 @@ +$def with (torrent) + +
          + + $:(forms.torrent_options(torrent).as_table(["max_download_speed", "max_upload_speed", "max_connections", "max_upload_slots"])) + + +
          +
          + + + $:(forms.torrent_options(torrent).as_table(["prioritize_first_last", "private"])) + +
          + +
          +
          Save/update: Todo
          + + diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html index e0cd572c2..c8a1e63b9 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html @@ -3,9 +3,6 @@ $def with (torrent) $:(render.header(torrent.message + '/' + torrent.name))
          -

          $_('Details')

          -$:render.tab_meta(torrent) - $if (torrent.action == 'start'): $:render.part_button('POST', '/torrent/start/' + str(torrent.id), _('Resume'), 'tango/start.png') $else: @@ -18,9 +15,20 @@ $:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reanno $:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png') $:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png') -
          +$:render.part_button('GET', '/torrent/move/' + str(torrent.id), _('Move'), 'tango/move.png') + +

          $_('Details')

          +$:render.tab_meta(torrent) + + +

          $_('Files')

          + $:render.tab_files(torrent) +

          $_('Options')

          + +$:render.tab_options(torrent) +
          +
          not working yet, but close..
          + +$:form.as_table() + + + + +
          +$:render.footer() \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py index 032633060..07a7b5066 100644 --- a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py +++ b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py @@ -7,82 +7,100 @@ from WebUi.webserver_common import ws ws.init_06() async_proxy = ws.async_proxy - - -# -#A: translate this into 1 multicall: - -start = time.time() -stats = { - 'download_rate':ws.proxy.get_download_rate(), - 'upload_rate':ws.proxy.get_upload_rate(), - 'max_download':ws.proxy.get_config_value('max_download_speed'), - 'max_upload':ws.proxy.get_config_value('max_upload_speed'), - 'num_connections':ws.proxy.get_num_connections(), - 'max_num_connections':ws.proxy.get_config_value('max_connections_global') -} - -print "sync-stats:",time.time() - start - -print stats - -# -#map callback to a a dict-setter -def dict_cb(key,d): - def callback(result): - d[key] = result - return callback - -start = time.time() -d = {} -async_proxy.get_download_rate(dict_cb('download_rate',d)) -async_proxy.get_upload_rate(dict_cb('upload_rate',d)) -async_proxy.get_config_value(dict_cb('max_download',d),"max_download_speed") -async_proxy.get_config_value(dict_cb('max_upload',d),"max_upload_speed") -async_proxy.get_num_connections(dict_cb("num_connections",d)) -async_proxy.get_config_value(dict_cb('max_num_connections',d),"max_connections_global") - -async_proxy.force_call(block=True) - -print "Async-stats:",time.time() - start -print d - -# -#B: translate this to multicall: -# - -#old-sync: -start = time.time() - -torrent_list = [ws.proxy.get_torrent_status(id,[]) - for id in ws.proxy.get_session_state() +TORRENT_KEYS = ['name', 'total_size', 'num_files', 'num_pieces', 'piece_length', + 'eta', 'ratio', 'file_progress', 'distributed_copies', 'total_done', + 'total_uploaded', 'state', 'paused', 'progress', 'next_announce', + 'total_payload_download', 'total_payload_upload', 'download_payload_rate', + 'upload_payload_rate', 'num_peers', 'num_seeds', 'total_peers', 'total_seeds', + 'total_wanted', 'tracker', 'trackers', 'tracker_status', 'save_path', + 'files', 'file_priorities', 'compact', 'max_connections', + 'max_upload_slots', 'max_download_speed', 'prioritize_first_last', 'private' ] -print "sync-list:",time.time() - start -print torrent_list - -#new async: - -start = time.time() - -torrent_ids = ws.proxy.get_session_state() #Syc-api. -torrent_dict = {} -for id in torrent_ids: - async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, []) -async_proxy.force_call(block=True) - -print "Async-list:",time.time() - start -print "\n".join(torrent_dict[torrent_ids[0]].keys()) -print torrent_dict[torrent_ids[0]]["files"] - - - +if False: + # + #A: translate this into 1 multicall: + + start = time.time() + stats = { + 'download_rate':ws.proxy.get_download_rate(), + 'upload_rate':ws.proxy.get_upload_rate(), + 'max_download':ws.proxy.get_config_value('max_download_speed'), + 'max_upload':ws.proxy.get_config_value('max_upload_speed'), + 'num_connections':ws.proxy.get_num_connections(), + 'max_num_connections':ws.proxy.get_config_value('max_connections_global') + } + + print "sync-stats:",time.time() - start + + print stats + + # + #map callback to a a dict-setter + def dict_cb(key,d): + def callback(result): + d[key] = result + return callback + + start = time.time() + d = {} + async_proxy.get_download_rate(dict_cb('download_rate',d)) + async_proxy.get_upload_rate(dict_cb('upload_rate',d)) + async_proxy.get_config_value(dict_cb('max_download',d),"max_download_speed") + async_proxy.get_config_value(dict_cb('max_upload',d),"max_upload_speed") + async_proxy.get_num_connections(dict_cb("num_connections",d)) + async_proxy.get_config_value(dict_cb('max_num_connections',d),"max_connections_global") + + async_proxy.force_call(block=True) + + print "Async-stats:",time.time() - start + print d + + # + #B: translate this to multicall: + # + + #old-sync: + start = time.time() + + torrent_list = [ws.proxy.get_torrent_status(id, TORRENT_KEYS ) + for id in ws.proxy.get_session_state() + ] + + print "sync-list:",time.time() - start + print torrent_list[0] + + #new async: + """ + torrent.compact, + torrent.max_connections, + torrent.max_upload_slots, + torrent.max_upload_speed, + torrent.max_download_speed, + torrent.prioritize_first_last, + torrent.private + """ + start = time.time() + torrent_ids = ws.proxy.get_session_state() #Syc-api. + torrent_dict = {} + for id in torrent_ids: + async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, TORRENT_KEYS ) + async_proxy.force_call(block=True) + print "Async-list:",time.time() - start + print "\n".join(torrent_dict[torrent_ids[0]].keys()) + print torrent_dict[torrent_ids[0]] +if False: + print ws.proxy.get_config_value('download_location') +if True: + torrent_id = ws.proxy.get_session_state()[0] + print torrent_id + ws.proxy.move_torrent([torrent_id],"/media/sdb1/test") \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index 6a996ca39..e9b262b91 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -70,7 +70,12 @@ class AddForm(forms.Form): class torrent_add: def add_page(self,error = None): - form_data = utils.get_newforms_data(AddForm) + #form_data = utils.get_newforms_data(AddForm) + + #TODO: CLEANUP!!! + vars = web.input(url = None) + form_data = {'url':vars.url} + options_data = None if error: options_data = utils.get_newforms_data(OptionsForm) diff --git a/deluge/ui/webui/webui_plugin/torrent_move.py b/deluge/ui/webui/webui_plugin/torrent_move.py new file mode 100644 index 000000000..e6f4174c1 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/torrent_move.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) Martijn Voncken 2008 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# + +from webserver_common import ws +import utils +from render import render +import page_decorators as deco +import lib.newforms_plus as forms +import lib.webpy022 as web + +#Too much boilerplate code here, todo : fix it. + +class MoveForm(forms.Form): + save_path = forms.ServerFolder(_("Move To")) + def initial_data(self): + return {'save_path':ws.proxy.get_config_value('download_location')} + +class torrent_move: + + def move_page(self, name, error = None): + torrent_ids = name.split(',') + torrent_list = [utils.get_torrent_status(id) for id in torrent_ids] + + data = None + if error: + data = utils.get_newforms_data(MoveForm) + + form = MoveForm(data) + + return render.torrent_move(name, torrent_list , form, error) + + @deco.deluge_page + def GET(self, name): + return self.move_page(name) + + @deco.check_session + def POST(self, name): + torrent_ids = name.split(',') + form = MoveForm(utils.get_newforms_data(MoveForm)) + if not form.is_valid(): + print self.move_page(name, error = _("Error in Path.")) + return + save_path = form.clean_data["save_path"] + ws.proxy.move_torrent(torrent_ids, save_path) + utils.do_redirect() diff --git a/deluge/ui/webui/webui_plugin/torrent_options.py b/deluge/ui/webui/webui_plugin/torrent_options.py new file mode 100644 index 000000000..24704f791 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/torrent_options.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) Martijn Voncken 2008 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +from webserver_common import ws +import utils +from render import template +import page_decorators as deco +import lib.newforms_plus as forms +import lib.webpy022 as web + +class TorrentOptionsForm(forms.Form): + +#download + max_download_speed = forms.DelugeFloat( + _("Maximum Down Speed")) + max_upload_speed = forms.DelugeFloat( + _("Maximum Up Speed")) + max_connections = forms.DelugeInt(_("Maximum Connections")) + max_upload_slots = forms.DelugeInt(_("Maximum Upload Slots")) + #general + prioritize_first_last = forms.CheckBox( + _('Prioritize first and last pieces')) + private = forms.CheckBox(_('Private Flag')) + +template.Template.globals["forms"].torrent_options = lambda torrent : TorrentOptionsForm(torrent) + diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 020224a02..c2246262e 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -64,14 +64,26 @@ try: except: VERSION = '' -TORRENT_KEYS = ['distributed_copies', 'download_payload_rate', - 'eta', 'is_seed', 'name', 'next_announce', - 'num_files', 'num_peers', 'num_pieces', 'num_seeds', 'paused', - 'piece_length','progress', 'ratio', 'total_done', 'total_download', - 'total_payload_download', 'total_payload_upload', 'total_peers', - 'total_seeds', 'total_size', 'total_upload', 'total_wanted', - 'tracker_status', 'upload_payload_rate', - 'uploaded_memory','tracker','state','queue_pos','user_paused','files'] + +TORRENT_KEYS = ['name', 'total_size', 'num_files', 'num_pieces', 'piece_length', + 'eta', 'ratio', 'file_progress', 'distributed_copies', 'total_done', + 'total_uploaded', 'state', 'paused', 'progress', 'next_announce', + 'total_payload_download', 'total_payload_upload', 'download_payload_rate', + 'upload_payload_rate', 'num_peers', 'num_seeds', 'total_peers', 'total_seeds', + 'total_wanted', 'tracker', 'trackers', 'tracker_status', 'save_path', + 'files', 'file_priorities', 'compact', 'max_connections', + 'max_upload_slots', 'max_download_speed', 'prioritize_first_last', + 'private','max_upload_speed', + + #REMOVE: + "is_seed","total_download","total_upload","uploaded_memory","queue_pos", + "user_paused" + + ] +""" +NOT:is_seed,total_download,total_upload,uploaded_memory,queue_pos,user_paused +""" + STATE_MESSAGES = (_("Queued"), _("Checking"), From 89278153bfa68a361fccbdf26f58a645fd96d379 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 15 Feb 2008 18:13:06 +0000 Subject: [PATCH 0499/1009] part_button --- .../webui_plugin/templates/advanced/part_tab_button.html | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 deluge/ui/webui/webui_plugin/templates/advanced/part_tab_button.html diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_tab_button.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_tab_button.html new file mode 100644 index 000000000..00621ec6f --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_tab_button.html @@ -0,0 +1,8 @@ +$def with (id, title, active) + +$if (active == id): + $title + From 9db2824bfef2272ef1b95d70c1476ecfde3632fd Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 15 Feb 2008 23:14:15 +0000 Subject: [PATCH 0500/1009] Expose set_torrent_max_connections(), set_torrent_max_upload_slots(), set_torrent_max_upload_speed(), set_torrent_max_download_speed(), set_torrent_private_flag(), set_torrent_file_priorities(). --- deluge/core/core.py | 24 ++++++++++++++++++++++++ deluge/ui/client.py | 23 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/deluge/core/core.py b/deluge/core/core.py index 5107e94ad..97a247275 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -474,7 +474,31 @@ class Core( def export_set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" return self.torrents[torrent_id].set_trackers(trackers) + + def export_set_torrent_max_connections(self, torrent_id, value): + """Sets a torrents max number of connections""" + return self.torrents[torrent_id].set_max_connections(value) + + def export_set_torrent_max_upload_slots(self, torrent_id, value): + """Sets a torrents max number of upload slots""" + return self.torrents[torrent_id].set_max_upload_slots(value) + def export_set_torrent_max_upload_speed(self, torrent_id, value): + """Sets a torrents max upload speed""" + return self.torrents[torrent_id].set_max_upload_speed(value) + + def export_set_torrent_max_download_speed(self, torrent_id, value): + """Sets a torrents max download speed""" + return self.torrents[torrent_id].set_max_download_speed(value) + + def export_set_torrent_private_flag(self, torrent_id, value): + """Sets a torrents private flag""" + return self.torrents[torrent_id].set_private_flag(value) + + def export_set_torrent_file_priorities(self, torrent_id, priorities): + """Sets a torrents file priorities""" + return self.torrents[torrent_id].set_file_priorities(priorities) + # Signals def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 341b8f508..247c525a5 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -344,3 +344,26 @@ def set_torrent_trackers(torrent_id, trackers): """Sets the torrents trackers""" get_core().call("set_torrent_trackers", None, torrent_id, trackers) +def set_torrent_max_connections(torrent_id, value): + """Sets a torrents max number of connections""" + get_core().call("set_torrent_max_connections", None, torrent_id, value) + +def set_torrent_max_upload_slots(torrent_id, value): + """Sets a torrents max number of upload slots""" + get_core().call("set_torrent_max_upload_slots", None, torrent_id, value) + +def set_torrent_max_upload_speed(torrent_id, value): + """Sets a torrents max upload speed""" + get_core().call("set_torrent_max_upload_speed", None, torrent_id, value) + +def set_torrent_max_download_speed(torrent_id, value): + """Sets a torrents max download speed""" + get_core().call("set_torrent_max_download_speed", None, torrent_id, value) + +def set_torrent_private_flag(torrent_id, value): + """Sets a torrents private flag""" + get_core().call("set_torrent_private_flag", None, torrent_id, value) + +def set_torrent_file_priorities(torrent_id, priorities): + """Sets a torrents file priorities""" + get_core().call("set_torrent_file_priorities", None, torrent_id, priorities) From dc2f8d6c9c5fe5a7886346a3ddc96a0e8d49bc45 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 16 Feb 2008 01:44:23 +0000 Subject: [PATCH 0501/1009] Only show limits when set. Increase spacing of StatusBarItems from 5 to 10. --- deluge/ui/gtkui/statusbar.py | 37 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index cc681cf0d..d6ea4f9c7 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -115,7 +115,7 @@ class StatusBar(component.Component): } # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() - self.hbox.set_spacing(5) + self.hbox.set_spacing(10) frame = self.statusbar.get_children()[0] frame.remove(frame.get_children()[0]) frame.add(self.hbox) @@ -246,12 +246,12 @@ class StatusBar(component.Component): def update_connections_label(self): # Set the max connections label - max_connections = self.max_connections - if max_connections < 0: - max_connections = _("Unlimited") + if self.max_connections < 0: + label_string = "%s" % self.num_connections + else: + label_string = "%s (%s)" % (self.num_connections, self.max_connections) - self.connections_item.set_text("%s (%s)" % ( - self.num_connections, max_connections)) + self.connections_item.set_text(label_string) def update_dht_label(self): # Set the max connections label @@ -259,26 +259,23 @@ class StatusBar(component.Component): def update_download_label(self): # Set the download speed label - max_download_speed = self.max_download_speed - if max_download_speed < 0: - max_download_speed = _("Unlimited") + if self.max_download_speed < 0: + label_string = "%s/s" % self.download_rate else: - max_download_speed = "%s %s" % (max_download_speed, _("KiB/s")) - - self.download_item.set_text("%s/s (%s)" % ( - self.download_rate, max_download_speed)) + label_string = "%s/s (%s %s)" % ( + self.download_rate, self.max_download_speed, _("KiB/s")) + + self.download_item.set_text(label_string) def update_upload_label(self): # Set the upload speed label - max_upload_speed = self.max_upload_speed - if max_upload_speed < 0: - max_upload_speed = _("Unlimited") + if self.max_upload_speed < 0: + label_string = "%s/s" % self.upload_rate else: - max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s")) + label_string = "%s/s (%s %s)" % ( + self.upload_rate, self.max_upload_speed, _("KiB/s")) - self.upload_item.set_text("%s/s (%s)" % ( - self.upload_rate, - max_upload_speed)) + self.upload_item.set_text(label_string) def update(self): # Send status request From 6b49e4e623b27e1260c8e04d48596146ffc5c691 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 08:25:24 +0000 Subject: [PATCH 0502/1009] extra status fields for torrent options --- deluge/core/torrent.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 5267dd306..aa9495676 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -245,7 +245,14 @@ class Torrent: "tracker_status": self.tracker_status, "save_path": self.save_path, "files": self.files, - "file_priorities": self.file_priorities + "file_priorities": self.file_priorities, + "compact":self.compact, + "max_connections":self.max_connections, + "max_upload_slots":self.max_upload_slots, + "max_upload_speed":self.max_upload_speed, + "max_download_speed":self.max_download_speed, + "prioritize_first_last":self.prioritize_first_last, + "private":self.private } fns = { From ed7b75b956c4400bb5bfcb5b426201fbb110b872 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 10:33:40 +0000 Subject: [PATCH 0503/1009] fix move,fix add using url --- deluge/core/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 97a247275..ed73938d5 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -328,7 +328,7 @@ class Core( # Add the torrent to session return self.export_add_torrent_file( - filename, save_path, filedump, options) + filename, filedump, options) def export_remove_torrent(self, torrent_id, remove_torrent, remove_data): log.debug("Removing torrent %s from the core.", torrent_id) @@ -349,7 +349,7 @@ class Core( def export_move_torrent(self, torrent_id, dest): log.debug("Moving torrent %s to %s", torrent_id, dest) - if not self.torrents[torrent_id].move(dest): + if not self.torrents[torrent_id].move_storage(dest): log.warning("Error moving torrent %s to %s", torrent_id, dest) def export_pause_all_torrents(self): From 89e2e3ae5c3b721eb8f169156bd9e18203e78f76 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 13:56:04 +0000 Subject: [PATCH 0504/1009] update file_prio+bugfixes --- .../webui/webui_plugin/lib/newforms_plus.py | 2 +- .../ui/webui/webui_plugin/page_decorators.py | 44 +++++++++-- deluge/ui/webui/webui_plugin/pages.py | 73 ++++++++++++------- .../templates/advanced/static/advanced.css | 4 +- .../templates/deluge/tab_files.html | 31 +++++--- .../templates/deluge/torrent_info.html | 7 +- .../templates/deluge/torrent_move.html | 1 - .../ui/webui/webui_plugin/webserver_common.py | 19 +++-- 8 files changed, 121 insertions(+), 60 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py index 16ef233ea..50f8c5ac9 100644 --- a/deluge/ui/webui/webui_plugin/lib/newforms_plus.py +++ b/deluge/ui/webui/webui_plugin/lib/newforms_plus.py @@ -156,7 +156,7 @@ class _DelugeIntInput(newforms.TextInput): def render(self, name, value, attrs=None): try: value = int(float(value)) - if value == -1: + if value == -1 or value == None: value = _("Unlimited") except: pass diff --git a/deluge/ui/webui/webui_plugin/page_decorators.py b/deluge/ui/webui/webui_plugin/page_decorators.py index ccb252606..023193329 100644 --- a/deluge/ui/webui/webui_plugin/page_decorators.py +++ b/deluge/ui/webui/webui_plugin/page_decorators.py @@ -14,8 +14,7 @@ from lib.webpy022 import changequery as self_url #deco's: def deluge_page_noauth(func): """ - add http headers - print result of func + add http headers;print result of func """ def deco(self, name = None): web.header("Content-Type", "text/html; charset=utf-8") @@ -27,8 +26,8 @@ def deluge_page_noauth(func): def check_session(func): """ - a decorator return func if session is valid, else redirect to login page. + mostly used for POST-pages. """ def deco(self, name = None): ws.log.debug('%s.%s(name=%s)' % (self.__class__.__name__, func.__name__, @@ -41,14 +40,49 @@ def check_session(func): seeother(url("/login",redir=self_url())) else: seeother("/login") #do not continue, and redirect to login page + deco.__name__ = func.__name__ return deco def deluge_page(func): + "deluge_page_noauth+check_session" return check_session(deluge_page_noauth(func)) #combi-deco's: +#decorators to use in combination with the ones above. +def torrent_ids(func): + """ + change page(self, name) to page(self, torrent_ids) + for pages that allow a list of torrents. + """ + def deco(self, name): + return func (self, name.split(',')) + deco.__name__ = func.__name__ + return deco + +def torrent_list(func): + """ + change page(self, name) to page(self, torrent_ids) + for pages that allow a list of torrents. + """ + def deco(self, name): + torrent_list = [get_torrent_status(id) for id in name.split(',')] + return func (self, torrent_list) + deco.__name__ = func.__name__ + return deco + +def torrent(func): + """ + change page(self, name) to page(self, get_torrent_status(torrent_id)) + """ + def deco(self, name): + torrent_id = name.split(',')[0] + torrent =get_torrent_status(torrent_id) + return func (self, torrent) + deco.__name__ = func.__name__ + return deco + def auto_refreshed(func): - "decorator:adds a refresh header" + "adds a refresh header" def deco(self, name = None): if getcookie('auto_refresh') == '1': web.header("Refresh", "%i ; url=%s" % @@ -58,7 +92,7 @@ def auto_refreshed(func): return deco def remote(func): - "decorator for remote api's" + "decorator for remote (string) api's" def deco(self, name = None): try: ws.log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name ) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 30090a5d7..a8d48a18f 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -73,6 +73,7 @@ urls = ( "/torrent/move/(.*)", "torrent_move", "/torrent/queue/up/(.*)", "torrent_queue_up", "/torrent/queue/down/(.*)", "torrent_queue_down", + "/torrent/files/(.*)","torrent_files", "/pause_all", "pause_all", "/resume_all", "resume_all", "/refresh/set", "refresh_set", @@ -151,34 +152,34 @@ class index: class torrent_info: @deco.deluge_page @deco.auto_refreshed - def GET(self, name): - torrent_id = name.split(',')[0] - return render.torrent_info(get_torrent_status(torrent_id)) + @deco.torrent + def GET(self, torrent): + return render.torrent_info(torrent) class torrent_info_inner: @deco.deluge_page - def GET(self, torrent_ids): + @deco.torrent + def GET(self, torrent): vars = web.input(tab = None) if vars.tab: active_tab = vars.tab else: active_tab = getcookie("torrent_info_tab") or "details" setcookie("torrent_info_tab", active_tab) - torrent_ids = torrent_ids.split(',') - info = get_torrent_status(torrent_ids[0]) - return render.torrent_info_inner(info, active_tab) + + return render.torrent_info_inner(torrent, active_tab) class torrent_start: @deco.check_session - def POST(self, name): - torrent_ids = name.split(',') + @deco.torrent_ids + def POST(self, torrent_ids): ws.proxy.resume_torrent(torrent_ids) do_redirect() class torrent_stop: @deco.check_session - def POST(self, name): - torrent_ids = name.split(',') + @deco.torrent_ids + def POST(self, torrent_ids): ws.proxy.pause_torrent(torrent_ids) do_redirect() @@ -214,45 +215,60 @@ class remote_torrent_add: class torrent_delete: @deco.deluge_page - def GET(self, name): - torrent_ids = name.split(',') - torrent_list = [get_torrent_status(id) for id in torrent_ids] - return render.torrent_delete(name, torrent_list) + @deco.torrent_list + def GET(self, torrent_list): + torrent_str = ",".join([t.id for t in torrent_list]) + #todo: remove the ",".join! + return render.torrent_delete(torrent_str, torrent_list) @deco.check_session - def POST(self, name): - torrent_ids = name.split(',') + @deco.torrent_ids + def POST(self, torrent_ids): vars = web.input(data_also = None, torrent_also = None) data_also = bool(vars.data_also) torrent_also = bool(vars.torrent_also) ws.proxy.remove_torrent(torrent_ids, torrent_also, data_also) do_redirect() - class torrent_queue_up: @deco.check_session - def POST(self, name): + @deco.torrent_list + def POST(self, torrent_list): #a bit too verbose.. - torrent_ids = name.split(',') - torrents = [get_torrent_status(id) for id in torrent_ids] - torrents.sort(lambda x, y : x.queue_pos - y.queue_pos) - torrent_ids = [t.id for t in torrents] + torrent_list.sort(lambda x, y : x.queue_pos - y.queue_pos) + torrent_ids = [t.id for t in torrent_list] for torrent_id in torrent_ids: ws.proxy.queue_up(torrent_id) do_redirect() class torrent_queue_down: @deco.check_session - def POST(self, name): + @deco.torrent_list + def POST(self, torrent_list): #a bit too verbose.. - torrent_ids = name.split(',') - torrents = [get_torrent_status(id) for id in torrent_ids] - torrents.sort(lambda x, y : x.queue_pos - y.queue_pos) - torrent_ids = [t.id for t in torrents] + torrent_list.sort(lambda x, y : x.queue_pos - y.queue_pos) + torrent_ids = [t.id for t in torrent_list] for torrent_id in reversed(torrent_ids): ws.proxy.queue_down(torrent_id) do_redirect() +class torrent_files: + @deco.check_session + def POST(self, torrent_id): + torrent = get_torrent_status(torrent_id) + file_priorities = web.input(file_priorities=[]).file_priorities + + #ws.log.debug("file-prio:%s" % file_priorities) + #file_priorities contains something like ['0','2','3','4'] + #transform to: [1,0,1,1,1] + proxy_prio = [0 for x in xrange(len(torrent.file_priorities))] + for pos in file_priorities: + proxy_prio[int(pos)] = 1 + #ws.log.debug("proxy-prio:%s" % proxy_prio) + + ws.proxy.set_torrent_file_priorities(torrent_id, proxy_prio) + do_redirect() + class pause_all: @deco.check_session def POST(self, name): @@ -306,6 +322,7 @@ class logout: end_session() seeother('/login') + class static(static_handler): base_dir = os.path.join(os.path.dirname(__file__), 'static') diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index c2723b3ee..a7bcd23ce 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -286,7 +286,7 @@ form { /*all forms!*/ float: left; width:150px; text-align:left; - height:60%; + height:none; } #config_chooser ul { @@ -303,7 +303,7 @@ form { /*all forms!*/ } #config_panel { - height:60%; + height:none; float:left; width:500px; margin-left:20px; diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html index 5567f12f9..9d62e9513 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_files.html @@ -1,20 +1,27 @@ $def with (torrent) +
          + + -
          $altrow(True) - + + + + $for i, file in enumerate(torrent.files): - - + + + +
          $_("File")$_("Size")
          $_("File")$_("Size")$_("Progress")
          - -$(crop_left(file["path"], 70)) - -$fsize(file["size"])
          + + $(crop_left(file["path"], 60)) + $fsize(file["size"])$int(torrent.file_progress[i] * 100)%
          + +
          -
          Save/update: Todo
          diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html index c8a1e63b9..84ce0f056 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html @@ -20,14 +20,15 @@ $:render.part_button('GET', '/torrent/move/' + str(torrent.id), _('Move'), 'tang

          $_('Details')

          $:render.tab_meta(torrent) +

          $_('Options')

          + +$:render.tab_options(torrent) +

          $_('Files')

          $:render.tab_files(torrent) -

          $_('Options')

          - -$:render.tab_options(torrent)
          -
          not working yet, but close..
          $:form.as_table() diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index c2246262e..48367b00a 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -85,14 +85,17 @@ NOT:is_seed,total_download,total_upload,uploaded_memory,queue_pos,user_paused """ -STATE_MESSAGES = (_("Queued"), - _("Checking"), - _("Connecting"), - _("Downloading Metadata"), - _("Downloading"), - _("Finished"), - _("Seeding"), - _("Allocating")) + + +STATE_MESSAGES = [ + "Allocating", + "Checking", + "Downloading", + "Seeding", + "Paused", + "Error" + ] + CONFIG_DEFAULTS = { "port":8112, From 07939d46b205c719c390b97742379100e376ff2a Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 14:20:00 +0000 Subject: [PATCH 0505/1009] fix advanced webui statusbar for shwouchk --- deluge/ui/webui/webui_plugin/pages.py | 8 ++++++++ .../templates/advanced/part_stats.html | 10 +++++----- .../templates/advanced/static/advanced.css | 20 +++++++++---------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index a8d48a18f..4a693f517 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -282,6 +282,10 @@ class resume_all: do_redirect() class refresh: + def GET(self, name): + return self.POST(name) + #WRONG, but makes it easyer to link with $_('Set') + $_('Disable') $else: $_('Off')   - $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png') + $_('Enable') ] -$:render.part_button('POST', '/logout', _('Logout'), 'tango/system-log-out.png') +$_('Logout') +$_('Settings') -$:render.part_button('GET', '/config/', _('Settings'), 'tango/preferences-system.png')
          diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css index a7bcd23ce..5f97efbbc 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/webui_plugin/templates/advanced/static/advanced.css @@ -167,19 +167,17 @@ body.inner { z-index:999; } -#refresh_panel button { - background-color:#304663; - color:#FFFFFF; - border:0; - position:relative; - top:-2px; - height:15px; - background-color:#ddd; - color:#00F; +#refresh_panel a { + color:0000FF; + text-decoration:none; } -#refresh_panel button:hover { - text-decoration: underline; +#refresh_panel a:visited { + color:0000FF; +} + +#refresh_panel a:hover { + color:0000FF; } #stats_panel button { From 596e8bbf21040d730e750f0eb2294ed13c2cfbc7 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 14:29:35 +0000 Subject: [PATCH 0506/1009] webui : robots.txt, prevent search-engine indexing --- deluge/ui/webui/webui_plugin/pages.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 4a693f517..5e274ae79 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -91,7 +91,8 @@ urls = ( #"/downloads/(.*)","downloads" disabled until it can handle large downloads #default-pages "/", "home", - "", "home" + "", "home", + "/robots.txt","robots" ) #/routing @@ -345,5 +346,12 @@ class downloads(static_handler): if not ws.config.get('share_downloads'): raise Exception('Access to downloads is forbidden.') return static_handler.GET(self, name) + +class robots: + def GET(self): + "no robots/prevent searchengines from indexing" + web.header("Content-Type", "text/plain") + print "User-agent: *\nDisallow:\n" + #/pages From de77db0eae59ee98efa045901411e1d47674e974 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 14:31:36 +0000 Subject: [PATCH 0507/1009] webui : robots.txt fix --- deluge/ui/webui/webui_plugin/pages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 5e274ae79..19504e24d 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -351,7 +351,7 @@ class robots: def GET(self): "no robots/prevent searchengines from indexing" web.header("Content-Type", "text/plain") - print "User-agent: *\nDisallow:\n" + print "User-agent: *\nDisallow:/\n" #/pages From bd99ebf1909e271717f2a3293d4d5ea49e39ec67 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 16 Feb 2008 15:13:26 +0000 Subject: [PATCH 0508/1009] webui:recheck --- deluge/ui/webui/webui_plugin/pages.py | 65 ++++++++++--------- .../templates/deluge/torrent_info.html | 1 + .../ui/webui/webui_plugin/torrent_options.py | 2 +- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 19504e24d..11d451573 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -68,6 +68,7 @@ urls = ( "/torrent/stop/(.*)", "torrent_stop", "/torrent/start/(.*)", "torrent_start", "/torrent/reannounce/(.*)", "torrent_reannounce", + "/torrent/recheck/(.*)", "torrent_recheck", "/torrent/add(.*)", "torrent_add", "/torrent/delete/(.*)", "torrent_delete", "/torrent/move/(.*)", "torrent_move", @@ -167,9 +168,9 @@ class torrent_info_inner: else: active_tab = getcookie("torrent_info_tab") or "details" setcookie("torrent_info_tab", active_tab) - return render.torrent_info_inner(torrent, active_tab) +#next 4 classes: a pattern is emerging here. class torrent_start: @deco.check_session @deco.torrent_ids @@ -186,33 +187,17 @@ class torrent_stop: class torrent_reannounce: @deco.check_session - def POST(self, torrent_id): - ws.proxy.force_reannounce([torrent_id]) + @deco.torrent_ids + def POST(self, torrent_ids): + ws.proxy.force_reannounce(torrent_ids) do_redirect() -class remote_torrent_add: - """ - For use in remote scripts etc. - curl ->POST pwd and torrent as file - greasemonkey: POST pwd torrent_name and data_b64 - """ - @deco.remote - def POST(self, name): - vars = web.input(pwd = None, torrent = {}, - data_b64 = None , torrent_name= None) - - if not ws.check_pwd(vars.pwd): - return 'error:wrong password' - - if vars.data_b64: #b64 post (greasemonkey) - data_b64 = unicode(vars.data_b64) - torrent_name = vars.torrent_name - else: #file-post (curl) - data_b64 = base64.b64encode(vars.torrent.file.read()) - torrent_name = vars.torrent.filename - - ws.proxy.add_torrent_filecontent(torrent_name, data_b64) - return 'ok' +class torrent_recheck: + @deco.check_session + @deco.torrent_ids + def POST(self, torrent_ids): + ws.proxy.force_recheck(torrent_ids) + do_redirect() class torrent_delete: @deco.deluge_page @@ -258,14 +243,11 @@ class torrent_files: def POST(self, torrent_id): torrent = get_torrent_status(torrent_id) file_priorities = web.input(file_priorities=[]).file_priorities - - #ws.log.debug("file-prio:%s" % file_priorities) #file_priorities contains something like ['0','2','3','4'] - #transform to: [1,0,1,1,1] + #transform to: [1,0,0,1,1,1] proxy_prio = [0 for x in xrange(len(torrent.file_priorities))] for pos in file_priorities: proxy_prio[int(pos)] = 1 - #ws.log.debug("proxy-prio:%s" % proxy_prio) ws.proxy.set_torrent_file_priorities(torrent_id, proxy_prio) do_redirect() @@ -331,6 +313,29 @@ class logout: end_session() seeother('/login') +#other stuff: +class remote_torrent_add: + """ + For use in remote scripts etc. + curl ->POST pwd and torrent as file + greasemonkey: POST pwd torrent_name and data_b64 + """ + @deco.remote + def POST(self, name): + vars = web.input(pwd = None, torrent = {}, + data_b64 = None , torrent_name= None) + + if not ws.check_pwd(vars.pwd): + return 'error:wrong password' + + if vars.data_b64: #b64 post (greasemonkey) + data_b64 = unicode(vars.data_b64) + torrent_name = vars.torrent_name + else: #file-post (curl) + data_b64 = base64.b64encode(vars.torrent.file.read()) + torrent_name = vars.torrent.filename + ws.proxy.add_torrent_filecontent(torrent_name, data_b64) + return 'ok' class static(static_handler): base_dir = os.path.join(os.path.dirname(__file__), 'static') diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html index 84ce0f056..270bce8ae 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html @@ -11,6 +11,7 @@ $else: $:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/list-remove.png') $:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reannounce'), 'tango/view-refresh.png') +$:render.part_button('POST', '/torrent/recheck/' + str(torrent.id), _('Recheck'), 'tango/view-refresh.png') $:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png') $:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png') diff --git a/deluge/ui/webui/webui_plugin/torrent_options.py b/deluge/ui/webui/webui_plugin/torrent_options.py index 24704f791..1285639cc 100644 --- a/deluge/ui/webui/webui_plugin/torrent_options.py +++ b/deluge/ui/webui/webui_plugin/torrent_options.py @@ -48,7 +48,7 @@ class TorrentOptionsForm(forms.Form): #general prioritize_first_last = forms.CheckBox( _('Prioritize first and last pieces')) - private = forms.CheckBox(_('Private Flag')) + private = forms.CheckBox(_('Private')) template.Template.globals["forms"].torrent_options = lambda torrent : TorrentOptionsForm(torrent) From 72e0a35986e311ca65217967854799805dc3d2d7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 17 Feb 2008 05:33:41 +0000 Subject: [PATCH 0509/1009] Add ip_filter to libtorrent bindings. --- libtorrent/bindings/python/src/docstrings.cpp | 3 ++- libtorrent/bindings/python/src/module.cpp | 2 ++ libtorrent/bindings/python/src/session.cpp | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index d6e473836..647e375cd 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -179,7 +179,8 @@ char const* session_start_natpmp_doc = ""; char const* session_stop_natpmp_doc = ""; - +char const* session_set_ip_filter_doc = + ""; // -- alert ----------------------------------------------------------------- char const* alert_doc = diff --git a/libtorrent/bindings/python/src/module.cpp b/libtorrent/bindings/python/src/module.cpp index 5c8d891d2..ea718388e 100755 --- a/libtorrent/bindings/python/src/module.cpp +++ b/libtorrent/bindings/python/src/module.cpp @@ -21,6 +21,7 @@ void bind_extensions(); void bind_peer_plugin(); void bind_torrent(); void bind_peer_info(); +void bind_ip_filter(); BOOST_PYTHON_MODULE(libtorrent) { @@ -44,5 +45,6 @@ BOOST_PYTHON_MODULE(libtorrent) bind_peer_plugin(); bind_torrent(); bind_peer_info(); + bind_ip_filter(); } diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 91befd094..dbaee26b6 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "gil.hpp" @@ -57,6 +58,7 @@ extern char const* session_stop_lsd_doc; extern char const* session_stop_upnp_doc; extern char const* session_start_natpmp_doc; extern char const* session_stop_natpmp_doc; +extern char const* session_set_ip_filter_doc; namespace { @@ -260,6 +262,7 @@ void bind_session() .def("stop_lsd", allow_threads(&session::stop_lsd), session_stop_lsd_doc) .def("start_natpmp", allow_threads(&session::start_natpmp), session_start_natpmp_doc) .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) + .def("set_ip_filter", allow_threads(&session::set_ip_filter), session_set_ip_filter_doc) ; register_ptr_to_python >(); From 0940f9072ebf55012434dc5e4a9122b344b43015 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 17 Feb 2008 05:33:59 +0000 Subject: [PATCH 0510/1009] Add ip_filter.cpp --- libtorrent/bindings/python/src/ip_filter.cpp | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 libtorrent/bindings/python/src/ip_filter.cpp diff --git a/libtorrent/bindings/python/src/ip_filter.cpp b/libtorrent/bindings/python/src/ip_filter.cpp new file mode 100644 index 000000000..d8e6808a4 --- /dev/null +++ b/libtorrent/bindings/python/src/ip_filter.cpp @@ -0,0 +1,27 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + void add_rule(ip_filter& filter, std::string start, std::string end, int flags) + { + return filter.add_rule(address_v4::from_string(start), address_v4::from_string(end), flags); + } +} + +void bind_ip_filter() +{ + class_("ip_filter") + .def("add_rule", &add_rule) + .def("access", allow_threads(&ip_filter::access)) + .def_readonly("export_filter", allow_threads(&ip_filter::export_filter)) + ; +} From c9caab1b4d7b5019e5031ff93298efdb2a47e079 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 17 Feb 2008 05:46:51 +0000 Subject: [PATCH 0511/1009] Add block_ip_range() in core and expose it in client. --- deluge/core/core.py | 10 +++++++++- deluge/ui/client.py | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index ed73938d5..67512e09a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -187,6 +187,10 @@ class Core( self.settings = lt.session_settings() self.settings.user_agent = "Deluge %s" % deluge.common.get_version() + # Create the IP Filter + self.ip_filter = lt.ip_filter() + self.session.set_ip_filter(self.ip_filter) + # Set lazy bitfield self.settings.lazy_bitfields = 1 self.session.set_settings(self.settings) @@ -498,7 +502,11 @@ class Core( def export_set_torrent_file_priorities(self, torrent_id, priorities): """Sets a torrents file priorities""" return self.torrents[torrent_id].set_file_priorities(priorities) - + + def export_block_ip_range(self, range): + """Block an ip range""" + self.ip_filter.add_rule(range[0], range[1], 1) + # Signals def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 247c525a5..03cd05252 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -367,3 +367,7 @@ def set_torrent_private_flag(torrent_id, value): def set_torrent_file_priorities(torrent_id, priorities): """Sets a torrents file priorities""" get_core().call("set_torrent_file_priorities", None, torrent_id, priorities) + +def block_ip_range(range): + """Blocks a ip range.. (start, end)""" + get_core().call("block_ip_range", None, range) From 2eec78f9fbd21422e3c00c1f61fc0183ee148f24 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 17 Feb 2008 05:51:07 +0000 Subject: [PATCH 0512/1009] Expose block_ip_range() to plugins. --- deluge/core/pluginmanager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index eb69f4e18..63e3f592f 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -141,4 +141,9 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def get_torrent_list(self): """Returns a list of torrent_id's in the current session.""" - return component.get("TorrentManager").get_torrent_list() + return component.get("TorrentManager").get_torrent_list() + + def block_ip_range(self, range): + """Blocks the ip range in the core""" + return self.core.export_block_ip_range(range) + From 89bea0a40f2fd50090d46669cdfcec923d812dba Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 17 Feb 2008 18:56:39 +0000 Subject: [PATCH 0513/1009] Modify SignalReceiver to only start the XMLRPC server once the thread is started. Allow components to directly connect to signals in Signals. --- deluge/ui/gtkui/signals.py | 12 ++++--- deluge/ui/signalreceiver.py | 67 ++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index d7d701c23..4fe32554d 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -39,12 +39,12 @@ from deluge.log import LOG as log class Signals(component.Component): def __init__(self): component.Component.__init__(self, "Signals") + self.receiver = SignalReceiver() def start(self): - remote = False if not client.is_localhost(): - remote = True - self.receiver = SignalReceiver(remote) + self.receiver.set_remote(True) + self.receiver.start() self.receiver.connect_to_signal("torrent_added", self.torrent_added_signal) @@ -65,7 +65,11 @@ class Signals(component.Component): self.receiver.shutdown() except: pass - + + def connect_to_signal(self, signal, callback): + """Connects a callback to a signal""" + self.receiver.connect_to_signal(signal, callback) + def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 9d085fb2d..7e89c0b45 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -50,7 +50,7 @@ class SignalReceiver( ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): - def __init__(self, remote=False): + def __init__(self): log.debug("SignalReceiver init..") gobject.threads_init() threading.Thread.__init__(self) @@ -60,34 +60,11 @@ class SignalReceiver( # Daemonize the thread so it exits when the main program does self.setDaemon(True) - - host = "localhost" - if remote == True: - host = "" - - # Setup the xmlrpc server - server_ready = False - while not server_ready: - port = random.randint(40000, 65535) - try: - SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, (host, port), logRequests=False, allow_none=True) - except socket.error, e: - log.debug("Trying again with another port: %s", e) - except: - log.error("Could not start SignalReceiver XMLRPC server: %s", e) - sys.exit(0) - else: - self.port = port - server_ready = True - + self.signals = {} - - # Register the emit_signal function - self.register_function(self.emit_signal) - - # Register the signal receiver with the core - client.register_client(str(self.port)) + + self.remote = False + def shutdown(self): """Shutdowns receiver thread""" @@ -105,9 +82,38 @@ class SignalReceiver( except: # We don't care about errors at this point pass - + + def set_remote(self, remote): + self.remote = remote + def run(self): """This gets called when we start the thread""" + host = "localhost" + if self.remote == True: + host = "" + + # Setup the xmlrpc server + server_ready = False + while not server_ready: + port = random.randint(40000, 65535) + try: + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( + self, (host, port), logRequests=False, allow_none=True) + except socket.error, e: + log.debug("Trying again with another port: %s", e) + except: + log.error("Could not start SignalReceiver XMLRPC server: %s", e) + sys.exit(0) + else: + self.port = port + server_ready = True + + # Register the emit_signal function + self.register_function(self.emit_signal) + + # Register the signal receiver with the core + client.register_client(str(self.port)) + t = threading.Thread(target=self.handle_thread) t.start() @@ -140,7 +146,8 @@ class SignalReceiver( def connect_to_signal(self, signal, callback): """Connect to a signal""" try: - self.signals[signal].append(callback) + if callback not in self.signals[signal]: + self.signals[signal].append(callback) except KeyError: self.signals[signal] = [callback] From 335c037add45deaa73fd738902e10596c14a529b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 17 Feb 2008 22:39:50 +0000 Subject: [PATCH 0514/1009] Modify the remove torrent behaviour to be more HIG compliant. Add the ability to set per-torrent options from the Torrent menu. Add new CoreConfig component for keeping a local mirror of the core config. --- deluge/ui/gtkui/glade/dgtkpopups.glade | 234 +----------------- .../gtkui/glade/remove_torrent_dialog.glade | 103 +++++--- deluge/ui/gtkui/glade/torrent_menu.glade | 139 ++++++++++- deluge/ui/gtkui/gtkui.py | 5 +- deluge/ui/gtkui/menubar.py | 134 +++++++++- deluge/ui/gtkui/removetorrentdialog.py | 26 +- 6 files changed, 353 insertions(+), 288 deletions(-) diff --git a/deluge/ui/gtkui/glade/dgtkpopups.glade b/deluge/ui/gtkui/glade/dgtkpopups.glade index 1022651f5..249640db1 100644 --- a/deluge/ui/gtkui/glade/dgtkpopups.glade +++ b/deluge/ui/gtkui/glade/dgtkpopups.glade @@ -1,239 +1,7 @@ - + - - Remove Torrent - GTK_WIN_POS_CENTER - GDK_WINDOW_TYPE_HINT_NORMAL - True - True - False - - - True - - - True - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-dialog-warning - 6 - - - False - False - 5 - - - - - True - 0 - <span size="large"><b>Are you sure you want to remove the selected torrent(s) from Deluge?</b></span> - True - True - - - 10 - 1 - - - - - False - False - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 20 - - - True - Delete downloaded files - 0 - True - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 20 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete .torrent file - 0 - True - True - - - - - False - False - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - 3 - - - - - False - False - 5 - 1 - - - - - True - GTK_BUTTONBOX_END - - - True - gtk-no - True - 0 - - - - - True - gtk-yes - True - 1 - - - 1 - - - - - False - GTK_PACK_END - - - - - - - True - - - True - Show/Hide - True - - - - - - True - Add a Torrent... - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add - 1 - - - - - - - True - Clear Finished - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-clear - 1 - - - - - - - True - - - - - True - gtk-preferences - True - True - - - - - - True - Plugins - True - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-execute - 1 - - - - - - - True - - - - - True - gtk-quit - True - True - - - - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK diff --git a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade index 244d2f62b..addf5689d 100644 --- a/deluge/ui/gtkui/glade/remove_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/remove_torrent_dialog.glade @@ -1,12 +1,11 @@ - + - 350 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - Remove Torrent + Remove Torrent? False GTK_WIN_POS_CENTER_ON_PARENT GDK_WINDOW_TYPE_HINT_DIALOG @@ -25,13 +24,13 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 + 10 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-dialog-warning - 5 + 6 False @@ -39,11 +38,12 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <big><b>Remove Torrent(s)?</b></big> + <big><b>Are you sure you want to remove the selected torrent?</b></big> True + True False @@ -59,7 +59,6 @@ - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -67,40 +66,44 @@ 1 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - 10 - <b>Options</b> - True - - - False - False - 2 - - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 15 - - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete .torrent file(s) - 0 - True - True + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-dialog-warning + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <i>The associated .torrent will be deleted!</i> + True + + + 1 + + - 3 + 2 @@ -109,18 +112,37 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 15 - - True - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Delete saved data - 0 - True + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-dialog-warning + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <i>The downloaded data will be deleted!</i> + True + + + 1 + + - 4 + 3 @@ -135,7 +157,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END + GTK_BUTTONBOX_CENTER True @@ -154,8 +176,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-ok - True + Remove Selected Torrent 0 diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 1e046c273..148c078e9 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -64,6 +64,28 @@ True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Opt_ions + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-preferences + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + True @@ -108,7 +130,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Remove Torrent True - True @@ -142,7 +163,7 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Move _Torrent @@ -159,4 +180,118 @@ + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + From Session + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + .. And Delete Torrent File + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + .. And Delete Downloaded Files + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + .. And Delete All Files + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Download Speed Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Upload Speed Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Connection Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-network + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Upload _Slot Limit + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-sort-ascending + 1 + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Private + True + + + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 633839773..368f44a52 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -59,9 +59,9 @@ from signals import Signals from pluginmanager import PluginManager from dbusinterface import DbusInterface from queuedtorrents import QueuedTorrents +from coreconfig import CoreConfig from deluge.configmanager import ConfigManager import deluge.common -import deluge.configmanager DEFAULT_PREFS = { "config_location": deluge.common.get_config_dir(), @@ -151,7 +151,8 @@ class GtkUI: # Start the signal receiver self.signal_receiver = Signals() - + self.coreconfig = CoreConfig() + # Initalize the plugins self.plugins = PluginManager() diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 5dc72af4d..ca408c178 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -52,6 +52,42 @@ class MenuBar(component.Component): pkg_resources.resource_filename("deluge.ui.gtkui", "glade/torrent_menu.glade")) + # Attach remove torrent menu + self.torrentmenu_glade.get_widget("menuitem_remove").set_submenu( + self.torrentmenu_glade.get_widget("remove_torrent_menu")) + + # Attach options torrent menu + self.torrentmenu_glade.get_widget("menuitem_options").set_submenu( + self.torrentmenu_glade.get_widget("options_torrent_menu")) + self.torrentmenu_glade.get_widget("download-limit-image").set_from_file( + common.get_pixmap("downloading16.png")) + self.torrentmenu_glade.get_widget("upload-limit-image").set_from_file( + common.get_pixmap("seeding16.png")) + + for menuitem in ("menuitem_down_speed", "menuitem_up_speed", + "menuitem_max_connections", "menuitem_upload_slots"): + submenu = gtk.Menu() + item = gtk.MenuItem(_("Set Unlimited")) + item.set_name(menuitem) + item.connect("activate", self.on_menuitem_set_unlimited) + submenu.append(item) + item = gtk.MenuItem(_("Other..")) + item.set_name(menuitem) + item.connect("activate", self.on_menuitem_set_other) + submenu.append(item) + submenu.show_all() + self.torrentmenu_glade.get_widget(menuitem).set_submenu(submenu) + + submenu = gtk.Menu() + item = gtk.MenuItem(_("Set Private On")) + item.connect("activate", self.on_menuitem_set_private_on) + submenu.append(item) + item = gtk.MenuItem(_("Set Private Off")) + item.connect("activate", self.on_menuitem_set_private_off) + submenu.append(item) + submenu.show_all() + self.torrentmenu_glade.get_widget("menuitem_private").set_submenu(submenu) + self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu") self.menu_torrent = self.window.main_glade.get_widget("menu_torrent") @@ -94,7 +130,15 @@ class MenuBar(component.Component): self.on_menuitem_updatetracker_activate, "on_menuitem_edittrackers_activate": \ self.on_menuitem_edittrackers_activate, - "on_menuitem_remove_activate": self.on_menuitem_remove_activate, + "on_menuitem_remove_session_activate": \ + self.on_menuitem_remove_session_activate, + "on_menuitem_remove_torrentfile_activate": \ + self.on_menuitem_remove_torrentfile_activate, + "on_menuitem_remove_data_activate": \ + self.on_menuitem_remove_data_activate, + "on_menuitem_remove_both_activate": \ + self.on_menuitem_remove_both_activate, + "on_menuitem_recheck_activate": self.on_menuitem_recheck_activate, "on_menuitem_open_folder": self.on_menuitem_open_folder_activate, "on_menuitem_move_activate": self.on_menuitem_move_activate @@ -195,14 +239,34 @@ class MenuBar(component.Component): component.get("TorrentView").get_selected_torrent(), component.get("MainWindow").window) dialog.run() - - def on_menuitem_remove_activate(self, data=None): - log.debug("on_menuitem_remove_activate") + + def on_menuitem_remove_session_activate(self, data=None): + log.debug("on_menuitem_remove_session_activate") from removetorrentdialog import RemoveTorrentDialog RemoveTorrentDialog( component.get("TorrentView").get_selected_torrents()).run() - #client.remove_torrent( - # component.get("TorrentView").get_selected_torrents()) + + def on_menuitem_remove_torrentfile_activate(self, data=None): + log.debug("on_menuitem_remove_torrentfile_activate") + from removetorrentdialog import RemoveTorrentDialog + RemoveTorrentDialog( + component.get("TorrentView").get_selected_torrents(), + remove_torrentfile=True).run() + + def on_menuitem_remove_data_activate(self, data=None): + log.debug("on_menuitem_remove_data_activate") + from removetorrentdialog import RemoveTorrentDialog + RemoveTorrentDialog( + component.get("TorrentView").get_selected_torrents(), + remove_data=True).run() + + def on_menuitem_remove_both_activate(self, data=None): + log.debug("on_menuitem_remove_both_activate") + from removetorrentdialog import RemoveTorrentDialog + RemoveTorrentDialog( + component.get("TorrentView").get_selected_torrents(), + remove_torrentfile=True, + remove_data=True).run() def on_menuitem_recheck_activate(self, data=None): log.debug("on_menuitem_recheck_activate") @@ -262,3 +326,61 @@ class MenuBar(component.Component): from aboutdialog import AboutDialog AboutDialog().run() + def on_menuitem_set_unlimited(self, widget): + log.debug("widget.name: %s", widget.name) + funcs = { + "menuitem_down_speed": client.set_torrent_max_download_speed, + "menuitem_up_speed": client.set_torrent_max_upload_speed, + "menuitem_max_connections": client.set_torrent_max_connections, + "menuitem_upload_slots": client.set_torrent_max_upload_slots + } + if widget.name in funcs.keys(): + for torrent in component.get("TorrentView").get_selected_torrents(): + funcs[widget.name](torrent, -1) + + def on_menuitem_set_other(self, widget): + log.debug("widget.name: %s", widget.name) + funcs = { + "menuitem_down_speed": client.set_torrent_max_download_speed, + "menuitem_up_speed": client.set_torrent_max_upload_speed, + "menuitem_max_connections": client.set_torrent_max_connections, + "menuitem_upload_slots": client.set_torrent_max_upload_slots + } + dialog_glade = gtk.glade.XML( + pkg_resources.resource_filename("deluge.ui.gtkui", + "glade/dgtkpopups.glade")) + speed_dialog = dialog_glade.get_widget("speed_dialog") + spin_title = dialog_glade.get_widget("spin_title") + if widget.name == "menuitem_down_speed": + spin_title.set_text(_("Set Max Download Speed (KiB/s):")) + elif widget.name == "menuitem_up_speed": + spin_title.set_text(_("Set Max Upload Speed (KiB/s):")) + elif widget.name == "menuitem_max_connections": + spin_title.set_text(_("Set Max Connections:")) + elif widget.name == "menuitem_upload_slots": + spin_title.set_text(_("Set Max Upload Slots:")) + + spin_speed = dialog_glade.get_widget("spin_speed") + spin_speed.set_value(-1) + spin_speed.select_region(0, -1) + response = speed_dialog.run() + if response == 1: # OK Response + if widget.name == "menuitem_down_speed" or widget.name == "menuitem_up_speed": + value = spin_speed.get_value() + else: + value = spin_speed.get_value_as_int() + else: + speed_dialog.destroy() + return + speed_dialog.destroy() + if widget.name in funcs.keys(): + for torrent in component.get("TorrentView").get_selected_torrents(): + funcs[widget.name](torrent, value) + + def on_menuitem_set_private_on(self, widget): + for torrent in component.get("TorrentView").get_selected_torrents(): + client.set_torrent_private_flag(torrent, True) + + def on_menuitem_set_private_off(self, widget): + for torrent in component.get("TorrentView").get_selected_torrents(): + client.set_torrent_private_flag(torrent, False) diff --git a/deluge/ui/gtkui/removetorrentdialog.py b/deluge/ui/gtkui/removetorrentdialog.py index b9de23bed..5218b4201 100644 --- a/deluge/ui/gtkui/removetorrentdialog.py +++ b/deluge/ui/gtkui/removetorrentdialog.py @@ -40,8 +40,11 @@ import deluge.component as component from deluge.log import LOG as log class RemoveTorrentDialog: - def __init__(self, torrent_ids): + def __init__(self, torrent_ids, remove_torrentfile=False, remove_data=False): self.torrent_ids = torrent_ids + self.remove_torrentfile = remove_torrentfile + self.remove_data = remove_data + self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/remove_torrent_dialog.glade")) @@ -54,6 +57,20 @@ class RemoveTorrentDialog: "on_button_ok_clicked": self.on_button_ok_clicked, "on_button_cancel_clicked": self.on_button_cancel_clicked }) + + if len(self.torrent_ids) > 1: + # We need to pluralize the dialog + self.dialog.set_title("Remove Torrents?") + self.glade.get_widget("label_title").set_markup( + _("Are you sure you want to remove the selected torrents?")) + self.glade.get_widget("button_ok").set_label(_("Remove Selected Torrents")) + + if self.remove_torrentfile or self.remove_data: + self.glade.get_widget("hseparator1").show() + if self.remove_torrentfile: + self.glade.get_widget("hbox_torrentfile").show() + if self.remove_data: + self.glade.get_widget("hbox_data").show() def run(self): if self.torrent_ids == None or self.torrent_ids == []: @@ -62,9 +79,10 @@ class RemoveTorrentDialog: self.dialog.show() def on_button_ok_clicked(self, widget): - data = self.glade.get_widget("chk_data").get_active() - torrent = self.glade.get_widget("chk_torrents").get_active() - client.remove_torrent(self.torrent_ids, torrent, data) + #data = self.glade.get_widget("chk_data").get_active() + #torrent = self.glade.get_widget("chk_torrents").get_active() + client.remove_torrent( + self.torrent_ids, self.remove_torrentfile, self.remove_data) self.dialog.destroy() def on_button_cancel_clicked(self, widget): From c2a7c9fe920a9edaceb304cfb87a859bcc221ca4 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 18 Feb 2008 16:51:49 +0000 Subject: [PATCH 0515/1009] queue --- deluge/ui/webui/webui_plugin/__init__.py | 251 ------------------ deluge/ui/webui/webui_plugin/pages.py | 16 +- .../templates/advanced/index.html | 4 +- .../webui_plugin/templates/deluge/index.html | 2 +- .../templates/deluge/tab_meta.html | 2 +- .../ui/webui/webui_plugin/torrent_options.py | 9 +- .../ui/webui/webui_plugin/webserver_common.py | 4 +- 7 files changed, 23 insertions(+), 265 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/__init__.py b/deluge/ui/webui/webui_plugin/__init__.py index 5ccb24fe6..9a24e958f 100644 --- a/deluge/ui/webui/webui_plugin/__init__.py +++ b/deluge/ui/webui/webui_plugin/__init__.py @@ -28,255 +28,4 @@ # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception -plugin_name = "Web User Interface" -plugin_author = "Martijn Voncken" -plugin_version = "rev." -plugin_description = """A Web based User Interface -Firefox greasemonkey script: http://userscripts.org/scripts/show/12639 - -Remotely add a file: "curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add" - -Advanced template is only tested on firefox and garanteed not to work in IE6 - -ssl keys are located in WebUi/ssl/ - -Other contributors: -*somedude : template enhancements. -*markybob : stability : synced with his changes in deluge-svn. -""" - - -import deluge.common -from webserver_common import ws,REVNO,VERSION - -try: - import deluge.pref - from deluge.dialogs import show_popup_warning -except ImportError: - print 'WebUi:not imported as a plugin' - - - -try: - from dbus_interface import get_dbus_manager -except: - print 'error importing dbus-interface!' - pass #for unit-test. - -import time - -import gtk -import os -from subprocess import Popen -from md5 import md5 -from threading import Thread -import random -random.seed() - -plugin_version += REVNO -plugin_description += VERSION - -def deluge_init(deluge_path): - global path - path = deluge_path - -def enable(core, interface): - global path - return plugin_WebUi(path, core, interface) - -class plugin_WebUi(object): - def __init__(self, path, deluge_core, deluge_interface): - self.path = path - self.core = deluge_core - self.interface = deluge_interface - self.proc = None - self.web_server = None - if not deluge.common.windows_check(): - import commands - status = commands.getstatusoutput( - 'ps x |grep -v grep |grep run_webserver') - if status[0] == 0: - os.kill(int(status[1].split()[0]), 9) - time.sleep(1) #safe time to wait for kill to finish. - self.config_file = deluge.common.CONFIG_DIR + "/webui.conf" - self.config = deluge.pref.Preferences(self.config_file, False) - try: - self.config.load() - except IOError: - # File does not exist - pass - - if not self.config.get('port'): #ugly way to detect new config file. - #set default values: - self.config.set("port", 8112) - self.config.set("button_style", 2) - self.config.set("auto_refresh", False) - self.config.set("auto_refresh_secs", 4) - self.config.set("template", "deluge") - self.config.save(self.config_file) - - if not self.config.get("pwd_salt"): - self.config.set("pwd_salt", "invalid") - self.config.set("pwd_md5", "invalid") - - if self.config.get("cache_templates") == None: - self.config.set("cache_templates", True) - - if deluge.common.windows_check(): - self.config.set("run_in_thread", True) - else: - self.config.set("run_in_thread", False) - - if self.config.get("use_https") == None: - self.config.set("use_https", False) - - self.dbus_manager = get_dbus_manager(deluge_core, deluge_interface, - self.config, self.config_file) - - self.start_server() - - def unload(self): - print 'WebUI:unload..' - self.kill_server() - - def update(self): - pass - - ## This will be only called if your plugin is configurable - def configure(self,parent_dialog): - d = ConfigDialog(self.config, self, parent_dialog) - if d.run() == gtk.RESPONSE_OK: - d.save_config() - d.destroy() - - def start_server(self): - self.kill_server() - - if self.config.get("run_in_thread"): - print 'Start Webui(inside gtk)..' - ws.init_gtk_05() #reload changed config. - from deluge_webserver import WebServer #only import in threaded mode - - - self.web_server = WebServer() - self.web_server.start_gtk() - - else: - print 'Start Webui(in process)..' - server_bin = os.path.dirname(__file__) + '/run_webserver' - self.proc = Popen((server_bin,'env=0.5')) - - def kill_server(self): - if self.web_server: - print "webserver: stop" - self.web_server.stop_gtk() - self.web_server = None - if self.proc: - print "webserver: kill %i" % self.proc.pid - os.system("kill -9 %i" % self.proc.pid) - time.sleep(1) #safe time to wait for kill to finish. - self.proc = None - - def __del__(self): - self.kill_server() - -class ConfigDialog(gtk.Dialog): - """ - sorry, can't get used to gui builders. - from what I read glade is better, but i dont want to invest time in them. - """ - def __init__(self, config, plugin, parent): - gtk.Dialog.__init__(self ,parent=parent) - self.config = config - self.plugin = plugin - self.vb = gtk.VBox() - self.set_title(_("WebUi Config")) - - template_path = os.path.join(os.path.dirname(__file__), 'templates') - self.templates = [dirname for dirname - in os.listdir(template_path) - if os.path.isdir(os.path.join(template_path, dirname)) - and not dirname.startswith('.')] - - self.port = self.add_widget(_('Port Number'), gtk.SpinButton()) - self.pwd1 = self.add_widget(_('New Password'), gtk.Entry()) - self.pwd2 = self.add_widget(_('New Password(confirm)'), gtk.Entry()) - self.template = self.add_widget(_('Template'), gtk.combo_box_new_text()) - self.button_style = self.add_widget(_('Button Style'), - gtk.combo_box_new_text()) - self.cache_templates = self.add_widget(_('Cache Templates'), - gtk.CheckButton()) - self.use_https = self.add_widget(_('https://'), - gtk.CheckButton()) - - #self.share_downloads = self.add_widget(_('Share Download Directory'), - # gtk.CheckButton()) - - self.port.set_range(80, 65536) - self.port.set_increments(1, 10) - self.pwd1.set_visibility(False) - self.pwd2.set_visibility(False) - - for item in self.templates: - self.template.append_text(item) - - if not self.config.get("template") in self.templates: - self.config.set("template","deluge") - - for item in [_('Text and image'), _('Image Only'), _('Text Only')]: - self.button_style.append_text(item) - if self.config.get("button_style") == None: - self.config.set("button_style", 2) - - self.port.set_value(int(self.config.get("port"))) - self.template.set_active( - self.templates.index(self.config.get("template"))) - self.button_style.set_active(self.config.get("button_style")) - #self.share_downloads.set_active( - # bool(self.config.get("share_downloads"))) - - self.cache_templates.set_active(self.config.get("cache_templates")) - self.use_https.set_active(self.config.get("use_https")) - - self.vbox.pack_start(self.vb, True, True, 0) - self.vb.show_all() - - self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL - ,gtk.STOCK_OK, gtk.RESPONSE_OK) - - def add_widget(self,label,w=None): - hb = gtk.HBox() - lbl = gtk.Label(label) - lbl.set_size_request(200,20) - hb.pack_start(lbl,False,False, 0) - hb.pack_start(w,True,True, 0) - - self.vb.pack_start(hb,False,False, 0) - return w - self.add_buttons(dgtk.STOCK_CLOSE, dgtk.RESPONSE_CLOSE) - - def save_config(self): - if self.pwd1.get_text() > '': - if self.pwd1.get_text() <> self.pwd2.get_text(): - show_popup_warning(self,_("Confirmed Password <> New Password\n" - + "Password was not changed")) - else: - sm = md5() - sm.update(str(random.getrandbits(5000))) - salt = sm.digest() - self.config.set("pwd_salt", salt) - # - m = md5() - m.update(salt) - m.update(unicode(self.pwd1.get_text())) - self.config.set("pwd_md5", m.digest()) - - self.config.set("port", int(self.port.get_value())) - self.config.set("template", self.template.get_active_text()) - self.config.set("button_style", self.button_style.get_active()) - self.config.set("cache_templates", self.cache_templates.get_active()) - self.config.set("use_https", self.use_https.get_active()) - #self.config.set("share_downloads", self.share_downloads.get_active()) - self.config.save(self.plugin.config_file) - self.plugin.start_server() #restarts server diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 11d451573..cd5dfbe7c 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -38,7 +38,7 @@ import page_decorators as deco import config_tabs_webui #auto registers import config_tabs_deluge #auto registers from config import config_page -import torrent_options +from torrent_options import torrent_options from torrent_move import torrent_move #import forms # @@ -75,6 +75,7 @@ urls = ( "/torrent/queue/up/(.*)", "torrent_queue_up", "/torrent/queue/down/(.*)", "torrent_queue_down", "/torrent/files/(.*)","torrent_files", + "/torrent/options/(.*)","torrent_options", "/pause_all", "pause_all", "/resume_all", "resume_all", "/refresh/set", "refresh_set", @@ -171,6 +172,7 @@ class torrent_info_inner: return render.torrent_info_inner(torrent, active_tab) #next 4 classes: a pattern is emerging here. +#todo: DRY (in less lines of code) class torrent_start: @deco.check_session @deco.torrent_ids @@ -221,10 +223,10 @@ class torrent_queue_up: @deco.torrent_list def POST(self, torrent_list): #a bit too verbose.. - torrent_list.sort(lambda x, y : x.queue_pos - y.queue_pos) + torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in torrent_ids: - ws.proxy.queue_up(torrent_id) + ws.proxy.queue_queue_up(torrent_id) do_redirect() class torrent_queue_down: @@ -232,10 +234,10 @@ class torrent_queue_down: @deco.torrent_list def POST(self, torrent_list): #a bit too verbose.. - torrent_list.sort(lambda x, y : x.queue_pos - y.queue_pos) + torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in reversed(torrent_ids): - ws.proxy.queue_down(torrent_id) + ws.proxy.queue_queue_down(torrent_id) do_redirect() class torrent_files: @@ -267,7 +269,7 @@ class resume_all: class refresh: def GET(self, name): return self.POST(name) - #WRONG, but makes it easyer to link with in the status-bar @deco.check_session def POST(self, name): @@ -306,7 +308,7 @@ class about: class logout: def GET(self): return self.POST() - #WRONG, but makes it easyer to link with in the status-bar @deco.check_session def POST(self, name): diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index 1812b1d05..99305f446 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -65,7 +65,7 @@ $for t in torrent_list: $:(sort_head('calc_state_str', 'S')) - $:(sort_head('queue_pos', '#')) + $:(sort_head('queue', '#')) $:(sort_head('name', _('Name'))) $:(sort_head('total_size', _('Size'))) $:(sort_head('progress', _('Progress'))) @@ -93,7 +93,7 @@ $for torrent in torrent_list: name="pauseresume" value="submit" /> - $torrent.queue_pos + $torrent.queue $(crop(torrent.name, 40)) $fsize(torrent.total_size) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/index.html b/deluge/ui/webui/webui_plugin/templates/deluge/index.html index 1bcda5173..4b20f7908 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/index.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/index.html @@ -4,7 +4,7 @@ $:render.header(_('Torrent list')) $:(sort_head('calc_state_str', 'S')) - $:(sort_head('queue_pos', '#')) + $:(sort_head('queue', '#')) $:(sort_head('name', _('Name'))) $:(sort_head('total_size', _('Size'))) $:(sort_head('progress', _('Progress'))) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html index c3fc0b866..9055647f1 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/tab_meta.html @@ -58,7 +58,7 @@ $fspeed(torrent.download_rate) - +
          $_('Queue Position'):$torrent.queue_pos $torrent.queue
          diff --git a/deluge/ui/webui/webui_plugin/torrent_options.py b/deluge/ui/webui/webui_plugin/torrent_options.py index 1285639cc..d24b08df6 100644 --- a/deluge/ui/webui/webui_plugin/torrent_options.py +++ b/deluge/ui/webui/webui_plugin/torrent_options.py @@ -50,5 +50,12 @@ class TorrentOptionsForm(forms.Form): _('Prioritize first and last pieces')) private = forms.CheckBox(_('Private')) -template.Template.globals["forms"].torrent_options = lambda torrent : TorrentOptionsForm(torrent) +class torrent_options: + @deco.check_session + @deco.torrent + def POST(self, torrent): + pass + + +template.Template.globals["forms"].torrent_options = lambda torrent : TorrentOptionsForm(torrent) \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 48367b00a..d8dd12752 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -73,10 +73,10 @@ TORRENT_KEYS = ['name', 'total_size', 'num_files', 'num_pieces', 'piece_length', 'total_wanted', 'tracker', 'trackers', 'tracker_status', 'save_path', 'files', 'file_priorities', 'compact', 'max_connections', 'max_upload_slots', 'max_download_speed', 'prioritize_first_last', - 'private','max_upload_speed', + 'private','max_upload_speed','queue', #REMOVE: - "is_seed","total_download","total_upload","uploaded_memory","queue_pos", + "is_seed","total_download","total_upload","uploaded_memory", "user_paused" ] From 3d76649b78fafe6588ab83ed6b5d40def6cb48c4 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 19 Feb 2008 01:44:12 +0000 Subject: [PATCH 0516/1009] Add missing file. Fixes #43. --- deluge/ui/gtkui/coreconfig.py | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 deluge/ui/gtkui/coreconfig.py diff --git a/deluge/ui/gtkui/coreconfig.py b/deluge/ui/gtkui/coreconfig.py new file mode 100644 index 000000000..985ad3d98 --- /dev/null +++ b/deluge/ui/gtkui/coreconfig.py @@ -0,0 +1,63 @@ +# +# coreconfig.py +# +# Copyright (C) 2008 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import deluge.component as component +import deluge.ui.client as client +from deluge.log import LOG as log + +class CoreConfig(component.Component): + def __init__(self): + log.debug("CoreConfig init..") + component.Component.__init__(self, "CoreConfig", ["Signals"]) + self.config = {} + component.get("Signals").connect_to_signal("config_value_changed", + self._on_config_value_changed) + + def start(self): + client.get_config(self._on_get_config) + + def stop(self): + self.config = {} + + def __getitem__(self, key): + return self.config[key] + + def __setitem__(self, key, value): + client.set_config({key: value}) + + def _on_get_config(self, config): + self.config = config + + def _on_config_value_changed(self, key, value): + self.config[key] = value + From 35446801c613af50ae47204a62c01a8f204052ca Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 19 Feb 2008 18:27:28 +0000 Subject: [PATCH 0517/1009] queue+add logging --- .../webui/webui_plugin/config_tabs_deluge.py | 9 ++++-- deluge/ui/webui/webui_plugin/pages.py | 31 +++++++++++++++++-- .../webui_plugin/templates/deluge/index.html | 2 +- deluge/ui/webui/webui_plugin/torrent_add.py | 10 ++++-- deluge/ui/webui/webui_plugin/utils.py | 6 ++-- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index 092732db8..97c832a2b 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -125,8 +125,13 @@ config.register_block('deluge','daemon', Daemon) class Plugins(forms.Form): title = _("Enabled Plugins") - _choices = [(p,p) for p in ws.proxy.get_available_plugins()] - enabled_plugins = forms.MultipleChoice(_(""), _choices) + try: + _choices = [(p,p) for p in ws.proxy.get_available_plugins()] + enabled_plugins = forms.MultipleChoice(_(""), _choices) + except: + ws.log.error("Not connected to daemon, Unable to load plugin-list") + #TODO: reload on reconnect! + def initial_data(self): return {'enabled_plugins':ws.proxy.get_enabled_plugins()} diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index cd5dfbe7c..a80a0c818 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -84,6 +84,7 @@ urls = ( "/home", "home", "/about", "about", "/logout", "logout", + "/connect","connect", #remote-api: "/remote/torrent/add(.*)", "remote_torrent_add", "/json/(.*)","json_api", @@ -173,6 +174,7 @@ class torrent_info_inner: #next 4 classes: a pattern is emerging here. #todo: DRY (in less lines of code) +#deco.deluge_command, or a subclass? class torrent_start: @deco.check_session @deco.torrent_ids @@ -226,7 +228,7 @@ class torrent_queue_up: torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in torrent_ids: - ws.proxy.queue_queue_up(torrent_id) + ws.async_proxy.get_core().call("queue_queue_up", None, torrent_id) do_redirect() class torrent_queue_down: @@ -237,7 +239,7 @@ class torrent_queue_down: torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in reversed(torrent_ids): - ws.proxy.queue_queue_down(torrent_id) + ws.async_proxy.get_core().call("queue_queue_down", None, torrent_id) do_redirect() class torrent_files: @@ -309,12 +311,35 @@ class logout: def GET(self): return self.POST() #WRONG, but makes it easyer to link with
          in the status-bar - @deco.check_session def POST(self, name): end_session() seeother('/login') +class connect: + @deco.deluge_page + def GET(self, name): + #if ws.proxy.connected(): + # error = _("Not Connected to a daemon") + #else: + error = None + connect_list = ["http://localhost:58846"] + return render.connect(connect_list, error) + + def POST(self): + vars = web.input(uri = None, other_uri = None) + uri = '' + if vars.uri == 'other_uri': + if not vars.other: + return error_page(_("no uri")) + url = vars.other + else: + uri = vars.uri + #TODO: more error-handling + ws.init_06(uri) + do_redirect() + + #other stuff: class remote_torrent_add: """ diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/index.html b/deluge/ui/webui/webui_plugin/templates/deluge/index.html index 4b20f7908..5dd658574 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/index.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/index.html @@ -24,7 +24,7 @@ $for torrent in torrent_list: - $torrent.queue_pos + $torrent.queue $(crop(torrent.name, 40)) diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index e9b262b91..09af27726 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -55,7 +55,9 @@ class OptionsForm(forms.Form): default_private = forms.CheckBox(_('Set Private Flag')) def initial_data(self): - return ws.proxy.get_config() + data = ws.proxy.get_config() + ws.log.debug("add:Init options with:%s" % data) + return data class AddForm(forms.Form): url = forms.CharField(label=_("Url"), required=False, @@ -71,7 +73,7 @@ class torrent_add: def add_page(self,error = None): #form_data = utils.get_newforms_data(AddForm) - + ws.log.debug("add-page") #TODO: CLEANUP!!! vars = web.input(url = None) form_data = {'url':vars.url} @@ -79,6 +81,7 @@ class torrent_add: options_data = None if error: options_data = utils.get_newforms_data(OptionsForm) + ws.log.debug("add:(error-state):Init options with:%s" % options_data) return render.torrent_add(AddForm(form_data),OptionsForm(options_data), error) @deco.deluge_page @@ -92,7 +95,6 @@ class torrent_add: allows: *posting of url *posting file-upload - *posting of data as string(for greasemonkey-private) """ options_form = OptionsForm(utils.get_newforms_data(OptionsForm)) @@ -115,11 +117,13 @@ class torrent_add: return if vars.url: ws.proxy.add_torrent_url(vars.url,options) + ws.log.debug("add-url:options :%s" % options) utils.do_redirect() elif torrent_name: data_b64 = base64.b64encode(torrent_data) #b64 because of strange bug-reports related to binary data ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64, options) + ws.log.debug("add-file:options :%s" % options) utils.do_redirect() else: print self.add_page(error = _("No data")) diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 1b065f535..4a28ce505 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -127,7 +127,7 @@ def get_stats(): ws.async_proxy.force_call(block=True) - ws.log.debug(str(stats)) + #ws.log.debug(str(stats)) stats.download_rate = fspeed(stats.download_rate) stats.upload_rate = fspeed(stats.upload_rate) @@ -157,10 +157,10 @@ def enhance_torrent_status(torrent_id,status): for key in TORRENT_KEYS: if not key in status: status[key] = 0 - ws.log.warning('torrent_status:empty key in status:%s' % key) + #ws.log.warning('torrent_status:empty key in status:%s' % key) elif status[key] == None: status[key] = 0 - ws.log.warning('torrent_status:None key in status:%s' % key) + #ws.log.warning('torrent_status:None key in status:%s' % key) if status.tracker == 0: From 86eeef4b92f5fcbff8f561c42833cef389808327 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 19 Feb 2008 20:50:29 +0000 Subject: [PATCH 0518/1009] refactor:remove ws.(part1) --- deluge/ui/webui/webui_plugin/config.py | 10 +-- .../webui/webui_plugin/config_tabs_deluge.py | 8 +- deluge/ui/webui/webui_plugin/json_api.py | 8 +- .../ui/webui/webui_plugin/page_decorators.py | 6 +- deluge/ui/webui/webui_plugin/pages.py | 28 +++--- .../webui_plugin/tests/multicall_notepad.py | 30 +++---- .../ui/webui/webui_plugin/tests/test_all.py | 24 +++--- deluge/ui/webui/webui_plugin/torrent_add.py | 18 ++-- deluge/ui/webui/webui_plugin/torrent_move.py | 6 +- deluge/ui/webui/webui_plugin/utils.py | 38 ++++---- .../ui/webui/webui_plugin/webserver_common.py | 86 ++++++------------- 11 files changed, 111 insertions(+), 151 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index 8b8ea05c8..c4c3ab3f2 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -32,7 +32,7 @@ import lib.newforms_plus as forms import page_decorators as deco import lib.webpy022 as web -from webserver_common import ws +from webserver_common import ws, proxy, log from render import render from lib.webpy022.http import seeother import sys @@ -64,9 +64,9 @@ class CookieCfgForm(forms.Form): class CfgForm(forms.Form): "config base for deluge-cfg" def initial_data(self): - return ws.proxy.get_config() + return proxy.get_config() def save(self, data): - ws.proxy.set_config(dict(data)) + proxy.set_config(dict(data)) class config_page: """ @@ -95,12 +95,12 @@ class config_page: form_data = web.Storage(utils.get_newforms_data(form_class)) form = form_class(form_data) if form.is_valid(): - ws.log.debug('save config %s' % form_data) + log.debug('save config %s' % form_data) try: form.start_save() return self.render(form , name, _('These changes were saved')) except forms.ValidationError, e: - ws.log.debug(e.message) + log.debug(e.message) return self.render(form , name, error = e.message) else: return self.render(form , name, diff --git a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py index 97c832a2b..0c9ef5ce4 100644 --- a/deluge/ui/webui/webui_plugin/config_tabs_deluge.py +++ b/deluge/ui/webui/webui_plugin/config_tabs_deluge.py @@ -34,7 +34,7 @@ import lib.newforms_plus as forms import config import utils -from webserver_common import ws +from webserver_common import ws, proxy , log class NetworkPorts(config.CfgForm ): @@ -126,15 +126,15 @@ config.register_block('deluge','daemon', Daemon) class Plugins(forms.Form): title = _("Enabled Plugins") try: - _choices = [(p,p) for p in ws.proxy.get_available_plugins()] + _choices = [(p,p) for p in proxy.get_available_plugins()] enabled_plugins = forms.MultipleChoice(_(""), _choices) except: - ws.log.error("Not connected to daemon, Unable to load plugin-list") + log.error("Not connected to daemon, Unable to load plugin-list") #TODO: reload on reconnect! def initial_data(self): - return {'enabled_plugins':ws.proxy.get_enabled_plugins()} + return {'enabled_plugins':proxy.get_enabled_plugins()} def save(self, value): raise forms.ValidationError("SAVE:TODO") diff --git a/deluge/ui/webui/webui_plugin/json_api.py b/deluge/ui/webui/webui_plugin/json_api.py index 92aece07d..e159fbd02 100644 --- a/deluge/ui/webui/webui_plugin/json_api.py +++ b/deluge/ui/webui/webui_plugin/json_api.py @@ -38,11 +38,11 @@ it would be possible not to include the python-json dependency. from new import instancemethod from inspect import getargspec -from utils import ws,get_torrent_status,get_category_choosers, get_stats,filter_torrent_state,fsize,fspeed +from utils import get_torrent_status,get_category_choosers, get_stats,filter_torrent_state,fsize,fspeed from page_decorators import remote from operator import attrgetter import lib.webpy022 as web -proxy = ws.proxy +from webserver_common import proxy, log def to_json(obj): from lib.pythonize import pythonize @@ -80,7 +80,7 @@ class json_api: method = getattr(self,name) vars = web.input(kwargs= None) - ws.log.debug('vars=%s' % vars) + log.debug('vars=%s' % vars) if vars.kwargs: kwargs = json.read(vars.kwargs) else: @@ -118,7 +118,7 @@ class json_api: #extra's: def list_torrents(self): return [get_torrent_status(torrent_id) - for torrent_id in ws.proxy.get_session_state()] + for torrent_id in proxy.get_session_state()] def simplify_torrent_status(self, torrent): """smaller subset and preformatted data for the treelist""" diff --git a/deluge/ui/webui/webui_plugin/page_decorators.py b/deluge/ui/webui/webui_plugin/page_decorators.py index 023193329..a9bfabb25 100644 --- a/deluge/ui/webui/webui_plugin/page_decorators.py +++ b/deluge/ui/webui/webui_plugin/page_decorators.py @@ -3,7 +3,7 @@ decorators for html-pages. """ #relative imports from render import render -from webserver_common import ws +from webserver_common import ws, log from utils import * #/relative @@ -30,7 +30,7 @@ def check_session(func): mostly used for POST-pages. """ def deco(self, name = None): - ws.log.debug('%s.%s(name=%s)' % (self.__class__.__name__, func.__name__, + log.debug('%s.%s(name=%s)' % (self.__class__.__name__, func.__name__, name)) vars = web.input(redir_after_login = None) ck = cookies() @@ -95,7 +95,7 @@ def remote(func): "decorator for remote (string) api's" def deco(self, name = None): try: - ws.log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name ) + log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name ) print func(self, name) except Exception, e: print 'error:%s' % e.message diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index a80a0c818..750031662 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -31,7 +31,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # -from webserver_common import ws +from webserver_common import ws, proxy, log from utils import * from render import render, error_page import page_decorators as deco @@ -179,28 +179,28 @@ class torrent_start: @deco.check_session @deco.torrent_ids def POST(self, torrent_ids): - ws.proxy.resume_torrent(torrent_ids) + proxy.resume_torrent(torrent_ids) do_redirect() class torrent_stop: @deco.check_session @deco.torrent_ids def POST(self, torrent_ids): - ws.proxy.pause_torrent(torrent_ids) + proxy.pause_torrent(torrent_ids) do_redirect() class torrent_reannounce: @deco.check_session @deco.torrent_ids def POST(self, torrent_ids): - ws.proxy.force_reannounce(torrent_ids) + proxy.force_reannounce(torrent_ids) do_redirect() class torrent_recheck: @deco.check_session @deco.torrent_ids def POST(self, torrent_ids): - ws.proxy.force_recheck(torrent_ids) + proxy.force_recheck(torrent_ids) do_redirect() class torrent_delete: @@ -217,7 +217,7 @@ class torrent_delete: vars = web.input(data_also = None, torrent_also = None) data_also = bool(vars.data_also) torrent_also = bool(vars.torrent_also) - ws.proxy.remove_torrent(torrent_ids, torrent_also, data_also) + proxy.remove_torrent(torrent_ids, torrent_also, data_also) do_redirect() class torrent_queue_up: @@ -228,7 +228,7 @@ class torrent_queue_up: torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in torrent_ids: - ws.async_proxy.get_core().call("queue_queue_up", None, torrent_id) + async_proxy.get_core().call("queue_queue_up", None, torrent_id) do_redirect() class torrent_queue_down: @@ -239,7 +239,7 @@ class torrent_queue_down: torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in reversed(torrent_ids): - ws.async_proxy.get_core().call("queue_queue_down", None, torrent_id) + async_proxy.get_core().call("queue_queue_down", None, torrent_id) do_redirect() class torrent_files: @@ -253,19 +253,19 @@ class torrent_files: for pos in file_priorities: proxy_prio[int(pos)] = 1 - ws.proxy.set_torrent_file_priorities(torrent_id, proxy_prio) + proxy.set_torrent_file_priorities(torrent_id, proxy_prio) do_redirect() class pause_all: @deco.check_session def POST(self, name): - ws.proxy.pause_torrent(ws.proxy.get_session_state()) + proxy.pause_torrent(proxy.get_session_state()) do_redirect() class resume_all: @deco.check_session def POST(self, name): - ws.proxy.resume_torrent(ws.proxy.get_session_state()) + proxy.resume_torrent(proxy.get_session_state()) do_redirect() class refresh: @@ -319,7 +319,7 @@ class logout: class connect: @deco.deluge_page def GET(self, name): - #if ws.proxy.connected(): + #if proxy.connected(): # error = _("Not Connected to a daemon") #else: error = None @@ -361,7 +361,7 @@ class remote_torrent_add: else: #file-post (curl) data_b64 = base64.b64encode(vars.torrent.file.read()) torrent_name = vars.torrent.filename - ws.proxy.add_torrent_filecontent(torrent_name, data_b64) + proxy.add_torrent_filecontent(torrent_name, data_b64) return 'ok' class static(static_handler): @@ -374,7 +374,7 @@ class template_static(static_handler): class downloads(static_handler): def GET(self, name): - self.base_dir = ws.proxy.get_config_value('default_download_path') + self.base_dir = proxy.get_config_value('default_download_path') if not ws.config.get('share_downloads'): raise Exception('Access to downloads is forbidden.') return static_handler.GET(self, name) diff --git a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py index 07a7b5066..4781e5d3f 100644 --- a/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py +++ b/deluge/ui/webui/webui_plugin/tests/multicall_notepad.py @@ -3,9 +3,8 @@ test multicall. """ import time -from WebUi.webserver_common import ws +from WebUi.webserver_common import ws, proxy, async_proxy ws.init_06() -async_proxy = ws.async_proxy TORRENT_KEYS = ['name', 'total_size', 'num_files', 'num_pieces', 'piece_length', 'eta', 'ratio', 'file_progress', 'distributed_copies', 'total_done', @@ -17,21 +16,18 @@ TORRENT_KEYS = ['name', 'total_size', 'num_files', 'num_pieces', 'piece_length', 'max_upload_slots', 'max_download_speed', 'prioritize_first_last', 'private' ] - - - if False: # #A: translate this into 1 multicall: start = time.time() stats = { - 'download_rate':ws.proxy.get_download_rate(), - 'upload_rate':ws.proxy.get_upload_rate(), - 'max_download':ws.proxy.get_config_value('max_download_speed'), - 'max_upload':ws.proxy.get_config_value('max_upload_speed'), - 'num_connections':ws.proxy.get_num_connections(), - 'max_num_connections':ws.proxy.get_config_value('max_connections_global') + 'download_rate':proxy.get_download_rate(), + 'upload_rate':proxy.get_upload_rate(), + 'max_download':proxy.get_config_value('max_download_speed'), + 'max_upload':proxy.get_config_value('max_upload_speed'), + 'num_connections':proxy.get_num_connections(), + 'max_num_connections':proxy.get_config_value('max_connections_global') } print "sync-stats:",time.time() - start @@ -66,8 +62,8 @@ if False: #old-sync: start = time.time() - torrent_list = [ws.proxy.get_torrent_status(id, TORRENT_KEYS ) - for id in ws.proxy.get_session_state() + torrent_list = [proxy.get_torrent_status(id, TORRENT_KEYS ) + for id in proxy.get_session_state() ] print "sync-list:",time.time() - start @@ -87,7 +83,7 @@ if False: start = time.time() - torrent_ids = ws.proxy.get_session_state() #Syc-api. + torrent_ids = proxy.get_session_state() #Syc-api. torrent_dict = {} for id in torrent_ids: async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, TORRENT_KEYS ) @@ -98,9 +94,9 @@ if False: print torrent_dict[torrent_ids[0]] if False: - print ws.proxy.get_config_value('download_location') + print proxy.get_config_value('download_location') if True: - torrent_id = ws.proxy.get_session_state()[0] + torrent_id = proxy.get_session_state()[0] print torrent_id - ws.proxy.move_torrent([torrent_id],"/media/sdb1/test") \ No newline at end of file + proxy.move_torrent([torrent_id],"/media/sdb1/test") \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/tests/test_all.py b/deluge/ui/webui/webui_plugin/tests/test_all.py index 68b073e79..c9f6127ab 100644 --- a/deluge/ui/webui/webui_plugin/tests/test_all.py +++ b/deluge/ui/webui/webui_plugin/tests/test_all.py @@ -6,7 +6,7 @@ unittest the right way feels so unpythonic :( """ import unittest import cookielib, urllib2 , urllib -from WebUi.webserver_common import ws,TORRENT_KEYS +from WebUi.webserver_common import ws,TORRENT_KEYS, proxy import operator ws.init_06() @@ -17,7 +17,7 @@ BASE_URL = 'http://localhost:8112' PWD = 'deluge' def get_status(id): - return ws.proxy.get_torrent_status(id,TORRENT_KEYS) + return proxy.get_torrent_status(id,TORRENT_KEYS) #BASE: #303 = see other @@ -94,7 +94,7 @@ class TestWebUiBase(unittest.TestCase): else: pass - first_torrent_id = property(lambda self: ws.proxy.get_session_state()[0]) + first_torrent_id = property(lambda self: proxy.get_session_state()[0]) first_torrent = property(lambda self: get_status(self.first_torrent_id)) @@ -200,17 +200,17 @@ class TestIntegration(TestWebUiBase): 'http://torrents.aelitis.com:88/torrents/azautoseeder_0.1.1.jar.torrent' ]) - torrent_ids = ws.proxy.get_session_state() + torrent_ids = proxy.get_session_state() #avoid hammering, investigate current torrent-list and do not re-add. #correct means : 3 torrent's in list (for now) if len(torrent_ids) <> 3: #delete all, nice use case for refactoring delete.. - torrent_ids = ws.proxy.get_session_state() + torrent_ids = proxy.get_session_state() for torrent in torrent_ids: - ws.proxy.remove_torrent([torrent], False, False) + proxy.remove_torrent([torrent], False, False) - torrent_ids = ws.proxy.get_session_state() + torrent_ids = proxy.get_session_state() self.assertEqual(torrent_ids, []) #add 3 using url. @@ -218,7 +218,7 @@ class TestIntegration(TestWebUiBase): self.assert_303('/torrent/add','/index',{'url':url,'torrent':None}) #added? - self.torrent_ids = ws.proxy.get_session_state() + self.torrent_ids = proxy.get_session_state() self.assertEqual(len(self.torrent_ids), 3) else: @@ -231,14 +231,14 @@ class TestIntegration(TestWebUiBase): #pause all self.assert_303('/pause_all','/index', post=1) #pause worked? - pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()] + pause_status = [get_status(id)["user_paused"] for id in proxy.get_session_state()] for paused in pause_status: self.assertEqual(paused, True) #resume all self.assert_303('/resume_all','/index', post=1) #resume worked? - pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()] + pause_status = [get_status(id)["user_paused"] for id in proxy.get_session_state()] for paused in pause_status: self.assertEqual(paused,False) #pause again. @@ -254,7 +254,7 @@ class TestIntegration(TestWebUiBase): def testQueue(self): #find last: - torrent_id = [id for id in ws.proxy.get_session_state() + torrent_id = [id for id in proxy.get_session_state() if (get_status(id)['queue_pos'] ==3 )][0] #queue @@ -285,7 +285,7 @@ class TestIntegration(TestWebUiBase): def testMeta(self): #info available? - for torrent_id in ws.proxy.get_session_state(): + for torrent_id in proxy.get_session_state(): self.assert_exists('/torrent/info/%s' % torrent_id) self.assert_exists('/torrent/delete/%s' % torrent_id) diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index 09af27726..31da1f7d9 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -29,7 +29,7 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # -from webserver_common import ws +from webserver_common import ws, log, proxy import utils from render import render, error_page import page_decorators as deco @@ -55,8 +55,8 @@ class OptionsForm(forms.Form): default_private = forms.CheckBox(_('Set Private Flag')) def initial_data(self): - data = ws.proxy.get_config() - ws.log.debug("add:Init options with:%s" % data) + data = proxy.get_config() + log.debug("add:Init options with:%s" % data) return data class AddForm(forms.Form): @@ -73,7 +73,7 @@ class torrent_add: def add_page(self,error = None): #form_data = utils.get_newforms_data(AddForm) - ws.log.debug("add-page") + log.debug("add-page") #TODO: CLEANUP!!! vars = web.input(url = None) form_data = {'url':vars.url} @@ -81,7 +81,7 @@ class torrent_add: options_data = None if error: options_data = utils.get_newforms_data(OptionsForm) - ws.log.debug("add:(error-state):Init options with:%s" % options_data) + log.debug("add:(error-state):Init options with:%s" % options_data) return render.torrent_add(AddForm(form_data),OptionsForm(options_data), error) @deco.deluge_page @@ -116,14 +116,14 @@ class torrent_add: print self.add_page(error = _("Choose an url or a torrent, not both.")) return if vars.url: - ws.proxy.add_torrent_url(vars.url,options) - ws.log.debug("add-url:options :%s" % options) + proxy.add_torrent_url(vars.url,options) + log.debug("add-url:options :%s" % options) utils.do_redirect() elif torrent_name: data_b64 = base64.b64encode(torrent_data) #b64 because of strange bug-reports related to binary data - ws.proxy.add_torrent_filecontent(vars.torrent.filename, data_b64, options) - ws.log.debug("add-file:options :%s" % options) + proxy.add_torrent_filecontent(vars.torrent.filename, data_b64, options) + log.debug("add-file:options :%s" % options) utils.do_redirect() else: print self.add_page(error = _("No data")) diff --git a/deluge/ui/webui/webui_plugin/torrent_move.py b/deluge/ui/webui/webui_plugin/torrent_move.py index e6f4174c1..9660c7a9e 100644 --- a/deluge/ui/webui/webui_plugin/torrent_move.py +++ b/deluge/ui/webui/webui_plugin/torrent_move.py @@ -30,7 +30,7 @@ # statement from all source files in the program, then also delete it here. # -from webserver_common import ws +from webserver_common import ws, proxy import utils from render import render import page_decorators as deco @@ -42,7 +42,7 @@ import lib.webpy022 as web class MoveForm(forms.Form): save_path = forms.ServerFolder(_("Move To")) def initial_data(self): - return {'save_path':ws.proxy.get_config_value('download_location')} + return {'save_path':proxy.get_config_value('download_location')} class torrent_move: @@ -70,5 +70,5 @@ class torrent_move: print self.move_page(name, error = _("Error in Path.")) return save_path = form.clean_data["save_path"] - ws.proxy.move_torrent(torrent_ids, save_path) + proxy.move_torrent(torrent_ids, save_path) utils.do_redirect() diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 4a28ce505..ac2e43866 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -47,7 +47,7 @@ import pickle from urlparse import urlparse from webserver_common import REVNO, VERSION, TORRENT_KEYS, STATE_MESSAGES -from webserver_common import ws +from webserver_common import ws, proxy, async_proxy, log debug_unicode = False @@ -64,7 +64,7 @@ def setcookie(key, val): #really simple sessions, to bad i had to implement them myself. def start_session(): - ws.log.debug('start session') + log.debug('start session') session_id = str(random.random()) ws.SESSIONS.append(session_id) #if len(ws.SESSIONS) > 20: #save max 20 sessions? @@ -113,21 +113,21 @@ def getcookie(key, default = None): def get_stats(): stats = Storage() - ws.async_proxy.get_download_rate(dict_cb('download_rate',stats)) - ws.async_proxy.get_upload_rate(dict_cb('upload_rate',stats)) - ws.async_proxy.get_config_value(dict_cb('max_download',stats) + async_proxy.get_download_rate(dict_cb('download_rate',stats)) + async_proxy.get_upload_rate(dict_cb('upload_rate',stats)) + async_proxy.get_config_value(dict_cb('max_download',stats) ,"max_download_speed") - ws.async_proxy.get_config_value(dict_cb('max_upload',stats) + async_proxy.get_config_value(dict_cb('max_upload',stats) ,"max_upload_speed") - ws.async_proxy.get_num_connections(dict_cb("num_connections",stats)) - ws.async_proxy.get_config_value(dict_cb('max_num_connections',stats) + async_proxy.get_num_connections(dict_cb("num_connections",stats)) + async_proxy.get_config_value(dict_cb('max_num_connections',stats) ,"max_connections_global") - ws.async_proxy.get_dht_nodes(dict_cb('dht_nodes',stats)) + async_proxy.get_dht_nodes(dict_cb('dht_nodes',stats)) - ws.async_proxy.force_call(block=True) + async_proxy.force_call(block=True) - #ws.log.debug(str(stats)) + #log.debug(str(stats)) stats.download_rate = fspeed(stats.download_rate) stats.upload_rate = fspeed(stats.upload_rate) @@ -157,10 +157,10 @@ def enhance_torrent_status(torrent_id,status): for key in TORRENT_KEYS: if not key in status: status[key] = 0 - #ws.log.warning('torrent_status:empty key in status:%s' % key) + #log.warning('torrent_status:empty key in status:%s' % key) elif status[key] == None: status[key] = 0 - #ws.log.warning('torrent_status:None key in status:%s' % key) + #log.warning('torrent_status:None key in status:%s' % key) if status.tracker == 0: @@ -222,9 +222,9 @@ def enhance_torrent_status(torrent_id,status): def get_torrent_status(torrent_id): """ helper method. - enhance ws.proxy.get_torrent_status with some extra data + enhance proxy.get_torrent_status with some extra data """ - status = ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS) + status = proxy.get_torrent_status(torrent_id,TORRENT_KEYS) return enhance_torrent_status(torrent_id, status) @@ -234,12 +234,12 @@ def get_torrent_list(): """ uses async. """ - torrent_ids = ws.proxy.get_session_state() #Syc-api. + torrent_ids = proxy.get_session_state() #Syc-api. torrent_dict = {} for id in torrent_ids: - ws.async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, + async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, TORRENT_KEYS) - ws.async_proxy.force_call(block=True) + async_proxy.force_call(block=True) return [enhance_torrent_status(id, status) for id, status in torrent_dict.iteritems()] @@ -307,7 +307,7 @@ def get_newforms_data(form_class): vars = web.input() for field in fields: form_data[field] = vars.get(field) - #ws.log.debug("form-field:%s=%s" % (field, form_data[field])) + #log.debug("form-field:%s=%s" % (field, form_data[field])) #DIRTY HACK: (for multiple-select) if isinstance(form_class.base_fields[field], forms.MultipleChoiceField): diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index d8dd12752..235e60a94 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -43,6 +43,8 @@ import sys import base64 from md5 import md5 import inspect +from deluge.ui import client +from deluge.log import LOG as log random.seed() @@ -80,12 +82,6 @@ TORRENT_KEYS = ['name', 'total_size', 'num_files', 'num_pieces', 'piece_length', "user_paused" ] -""" -NOT:is_seed,total_download,total_upload,uploaded_memory,queue_pos,user_paused -""" - - - STATE_MESSAGES = [ "Allocating", @@ -111,29 +107,28 @@ CONFIG_DEFAULTS = { #/constants - -class SyncProxyFunction: +#some magic to transform the async-proxy back to sync: +class SyncProxyMethod: """ helper class for SyncProxy """ - def __init__(self,client, func_name): + def __init__(self, func_name): self.func_name = func_name - self.client = client def __call__(self,*args,**kwargs): - func = getattr(self.client,self.func_name) + func = getattr(client,self.func_name) if self.has_callback(func): + #(ab)using list.append as a builtin callback method sync_result = [] func(sync_result.append,*args, **kwargs) - self.client.force_call(block=True) + client.force_call(block=True) if not sync_result: return None return sync_result[0] else: - ws.log.debug('no-cb: %s' % self.func_name) func(*args, **kwargs) - self.client.force_call(block=True) + client.force_call(block=True) return @staticmethod @@ -142,11 +137,14 @@ class SyncProxyFunction: class SyncProxy(object): """acts like the old synchonous proxy""" - def __init__(self, client): - self.client = client + def __getattr__(self, attr): + return SyncProxyMethod(attr) - def __getattr__(self, attr,*args,**kwargs): - return SyncProxyFunction(self.client, attr) +#moving stuff from WS to module +#goal: eliminate WS, because the 05 compatiblilty is not needed anymore +proxy = SyncProxy() +async_proxy = client +#log is already imported. class Ws: @@ -182,37 +180,31 @@ class Ws: self.config = pickle.load(open(self.config_file)) def init_06(self, uri = 'http://localhost:58846'): - import deluge.ui.client as async_proxy - from deluge.log import LOG as log - self.log = log - async_proxy.set_core_uri(uri) - self.async_proxy = async_proxy - - self.proxy = SyncProxy(self.async_proxy) - + client.set_core_uri(uri) + self.async_proxy = client #MONKEY PATCH, TODO->REMOVE!!! def add_torrent_filecontent(name , data_b64, options): - self.log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' % + log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' % (name , len(data_b64))) name = name.replace('\\','/') name = 'deluge06_' + str(random.random()) + '_' + name.split('/')[-1] filename = os.path.join('/tmp', name) - self.log.debug('write: %s' % filename) + log.debug('write: %s' % filename) f = open(filename,"wb") f.write(base64.b64decode(data_b64)) f.close() - self.log.debug("options:%s" % options) + log.debug("options:%s" % options) self.proxy.add_torrent_file([filename] , [options]) - self.proxy.add_torrent_filecontent = add_torrent_filecontent - self.log.debug('cfg-file %s' % self.config_file) + proxy.add_torrent_filecontent = add_torrent_filecontent + log.debug('cfg-file %s' % self.config_file) if not os.path.exists(self.config_file): - self.log.debug('create cfg file %s' % self.config_file) + log.debug('create cfg file %s' % self.config_file) #load&save defaults. f = file(self.config_file,'wb') pickle.dump(CONFIG_DEFAULTS,f) @@ -221,34 +213,6 @@ class Ws: self.init_process() self.env = '0.6' - def init_05(self): - import dbus - self.init_process() - bus = dbus.SessionBus() - self.proxy = bus.get_object("org.deluge_torrent.dbusplugin" - , "/org/deluge_torrent/DelugeDbusPlugin") - - self.env = '0.5_process' - self.init_logger() - - def init_gtk_05(self): - #appy possibly changed config-vars, only called in when runing inside gtk. - #new bug ws.render will not update!!!! - #other bug: must warn if blocklist plugin is active! - from dbus_interface import get_dbus_manager - self.proxy = get_dbus_manager() - self.config = deluge.pref.Preferences(self.config_file, False) - self.env = '0.5_gtk' - self.init_logger() - - def init_logger(self): - #only for 0.5.. - import logging - logging.basicConfig(level=logging.DEBUG, - format="[%(levelname)s] %(message)s") - self.log = logging - - #utils for config: def get_templates(self): template_path = os.path.join(os.path.dirname(__file__), 'templates') @@ -258,7 +222,7 @@ class Ws: and not dirname.startswith('.')] def save_config(self): - self.log.debug('Save Webui Config') + log.debug('Save Webui Config') data = pickle.dumps(self.config) f = open(self.config_file,'wb') f.write(data) From 4fa375bd6c3f0c37b06279054f37d05c227b6391 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Wed, 20 Feb 2008 19:36:24 +0000 Subject: [PATCH 0519/1009] proxy for plugin methods --- deluge/ui/webui/webui_plugin/pages.py | 6 +- .../ui/webui/webui_plugin/webserver_common.py | 63 ++++++++++++++----- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 750031662..7e58ab71a 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -228,7 +228,8 @@ class torrent_queue_up: torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in torrent_ids: - async_proxy.get_core().call("queue_queue_up", None, torrent_id) + #async_proxy.get_core().call("queue_queue_up", None, torrent_id) + proxy.queue_queue_up(torrent_id) do_redirect() class torrent_queue_down: @@ -239,7 +240,8 @@ class torrent_queue_down: torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] for torrent_id in reversed(torrent_ids): - async_proxy.get_core().call("queue_queue_down", None, torrent_id) + #async_proxy.get_core().call("queue_queue_down", None, torrent_id) + proxy.queue_queue_down(torrent_id) do_redirect() class torrent_files: diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 235e60a94..9c9e3deb1 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -108,42 +108,77 @@ CONFIG_DEFAULTS = { #/constants #some magic to transform the async-proxy back to sync: -class SyncProxyMethod: +class BlockingMethod: """ helper class for SyncProxy """ - def __init__(self, func_name): - self.func_name = func_name + def __init__(self, method_name): + self.method_name = method_name + self.result = None - def __call__(self,*args,**kwargs): - func = getattr(client,self.func_name) + def __call__(self, *args, **kwargs): + func = getattr(client, self.method_name) if self.has_callback(func): - #(ab)using list.append as a builtin callback method - sync_result = [] - func(sync_result.append,*args, **kwargs) + func(self.callback ,*args, **kwargs) client.force_call(block=True) - if not sync_result: - return None - return sync_result[0] + return self.result else: func(*args, **kwargs) client.force_call(block=True) return + def callback(self, result): + self.result = result + @staticmethod def has_callback(func): return "callback" in inspect.getargspec(func)[0] +class CoreMethod: + """ + plugins etc are not exposed in client.py + wrapper to make plugin methods behave like the ones in client.py + """ + def __init__(self, method_name): + self.method_name = method_name + self.result = None + + def __call__(self,*args,**kwargs): + client.get_core().call(self.method_name, self.callback ,*args, **kwargs) + return self.result + + def callback(self, result): + self.result = result + +class BlockingCoreMethod(CoreMethod): + """ + for syncProcy + """ + def __call__(self,*args,**kwargs): + client.get_core().call(self.method_name, self.callback ,*args, **kwargs) + client.force_call(block=True) + return self.result + class SyncProxy(object): """acts like the old synchonous proxy""" - def __getattr__(self, attr): - return SyncProxyMethod(attr) + def __getattr__(self, method_name): + if hasattr(client, method_name): + return BlockingMethod(method_name) + else: + return BlockingCoreMethod( method_name ) + +class ASyncProxy(object): + def __getattr__(self, method_name): + if hasattr(client, method_name): + return getattr(client, method_name) + else: + return CoreMethod( method_name ) #moving stuff from WS to module #goal: eliminate WS, because the 05 compatiblilty is not needed anymore proxy = SyncProxy() -async_proxy = client +async_proxy = ASyncProxy() #log is already imported. From 88f26387642f6ea1e7883f41868455ddc8581e7a Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Wed, 20 Feb 2008 20:24:08 +0000 Subject: [PATCH 0520/1009] fix CoreMethod --- deluge/ui/webui/webui_plugin/pages.py | 4 ++-- .../ui/webui/webui_plugin/webserver_common.py | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 7e58ab71a..212258054 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -229,7 +229,7 @@ class torrent_queue_up: torrent_ids = [t.id for t in torrent_list] for torrent_id in torrent_ids: #async_proxy.get_core().call("queue_queue_up", None, torrent_id) - proxy.queue_queue_up(torrent_id) + async_proxy.queue_queue_up(None, torrent_id) do_redirect() class torrent_queue_down: @@ -241,7 +241,7 @@ class torrent_queue_down: torrent_ids = [t.id for t in torrent_list] for torrent_id in reversed(torrent_ids): #async_proxy.get_core().call("queue_queue_down", None, torrent_id) - proxy.queue_queue_down(torrent_id) + async_proxy.queue_queue_down(None, torrent_id) do_redirect() class torrent_files: diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 9c9e3deb1..86e44caaa 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -99,8 +99,8 @@ CONFIG_DEFAULTS = { "auto_refresh":False, "auto_refresh_secs": 10, "template":"advanced", - "pwd_salt":"2540626806573060601127357001536142078273646936492343724296134859793541603059837926595027859394922651189016967573954758097008242073480355104215558310954", - "pwd_md5":"\xea\x8d\x90\x98^\x9f\xa9\xe2\x19l\x7f\x1a\xca\x82u%", + "pwd_salt":"2\xe8\xc7\xa6(n\x81_\x8f\xfc\xdf\x8b\xd1\x1e\xd5\x90", + "pwd_md5":".\xe8w\\+\xec\xdb\xf2id4F\xdb\rUc", "cache_templates":False, "use_https":False } @@ -142,24 +142,27 @@ class CoreMethod: """ def __init__(self, method_name): self.method_name = method_name - self.result = None - def __call__(self,*args,**kwargs): - client.get_core().call(self.method_name, self.callback ,*args, **kwargs) - return self.result - - def callback(self, result): - self.result = result + def __call__(self, callback, *args,**kwargs): + client.get_core().call(self.method_name, callback ,*args, **kwargs) class BlockingCoreMethod(CoreMethod): """ for syncProcy """ + def __init__(self, method_name): + self.method_name = method_name + self.result = None + def __call__(self,*args,**kwargs): client.get_core().call(self.method_name, self.callback ,*args, **kwargs) client.force_call(block=True) return self.result + def callback(self, result): + self.result = result + + class SyncProxy(object): """acts like the old synchonous proxy""" def __getattr__(self, method_name): From 361a276da4da95893a6de0145bbb0af5419c87be Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 21 Feb 2008 04:19:13 +0000 Subject: [PATCH 0521/1009] Add reset_ip_filter() to core. --- deluge/core/core.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 67512e09a..c938ea225 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -186,10 +186,6 @@ class Core( # Set the user agent self.settings = lt.session_settings() self.settings.user_agent = "Deluge %s" % deluge.common.get_version() - - # Create the IP Filter - self.ip_filter = lt.ip_filter() - self.session.set_ip_filter(self.ip_filter) # Set lazy bitfield self.settings.lazy_bitfields = 1 @@ -505,7 +501,16 @@ class Core( def export_block_ip_range(self, range): """Block an ip range""" - self.ip_filter.add_rule(range[0], range[1], 1) + try: + self.ip_filter.add_rule(range[0], range[1], 1) + except AttributeError: + self.export_reset_ip_filter() + self.ip_filter.add_rule(range[0], range[1], 1) + + def export_reset_ip_filter(self): + """Clears the ip filter""" + self.ip_filter = lt.ip_filter() + self.session.set_ip_filter(self.ip_filter) # Signals def torrent_added(self, torrent_id): From 1f5d9a10c1e888b22a0bf969f89e6bfeda1117d9 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 21 Feb 2008 04:20:30 +0000 Subject: [PATCH 0522/1009] Expose reset_ip_filter() to plugins. --- deluge/core/pluginmanager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 63e3f592f..c34f244b4 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -146,4 +146,8 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def block_ip_range(self, range): """Blocks the ip range in the core""" return self.core.export_block_ip_range(range) + + def reset_ip_filter(self): + """Resets the ip filter""" + return self.core.export_reset_ip_filter() From 6d63307623b3fbbaa197f57e977af0da04af3372 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Thu, 21 Feb 2008 18:37:58 +0000 Subject: [PATCH 0523/1009] webui:fix torrent_add --- deluge/ui/webui/webui_plugin/webserver_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index 86e44caaa..a91aecbba 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -236,7 +236,7 @@ class Ws: f.write(base64.b64decode(data_b64)) f.close() log.debug("options:%s" % options) - self.proxy.add_torrent_file([filename] , [options]) + proxy.add_torrent_file([filename] , [options]) proxy.add_torrent_filecontent = add_torrent_filecontent log.debug('cfg-file %s' % self.config_file) From 27ffaab15b65ba0c5d7a9d3e7870429af7f35fb2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Thu, 21 Feb 2008 20:23:02 +0000 Subject: [PATCH 0524/1009] Change core methods to use a list of torrent_ids. --- deluge/core/core.py | 56 +++++++++++++++++++++++++-------------------- deluge/ui/client.py | 36 +++++++++++++---------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index c938ea225..ad1fc5f45 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -330,27 +330,31 @@ class Core( return self.export_add_torrent_file( filename, filedump, options) - def export_remove_torrent(self, torrent_id, remove_torrent, remove_data): - log.debug("Removing torrent %s from the core.", torrent_id) - if self.torrents.remove(torrent_id, remove_torrent, remove_data): - # Run the plugin hooks for 'post_torrent_remove' - self.plugins.run_post_torrent_remove(torrent_id) - # Emit the torrent_removed signal - self.torrent_removed(torrent_id) + def export_remove_torrent(self, torrent_ids, remove_torrent, remove_data): + log.debug("Removing torrent %s from the core.", torrent_ids) + for torrent_id in torrent_ids: + if self.torrents.remove(torrent_id, remove_torrent, remove_data): + # Run the plugin hooks for 'post_torrent_remove' + self.plugins.run_post_torrent_remove(torrent_id) + # Emit the torrent_removed signal + self.torrent_removed(torrent_id) - def export_force_reannounce(self, torrent_id): - log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id) - self.torrents[torrent_id].force_reannounce() + def export_force_reannounce(self, torrent_ids): + log.debug("Forcing reannouncment to: %s", torrent_ids) + for torrent_id in torrent_ids: + self.torrents[torrent_id].force_reannounce() - def export_pause_torrent(self, torrent_id): - log.debug("Pausing torrent %s", torrent_id) - if not self.torrents[torrent_id].pause(): - log.warning("Error pausing torrent %s", torrent_id) + def export_pause_torrent(self, torrent_ids): + log.debug("Pausing: %s", torrent_ids) + for torrent_id in torrent_ids: + if not self.torrents[torrent_id].pause(): + log.warning("Error pausing torrent %s", torrent_id) - def export_move_torrent(self, torrent_id, dest): - log.debug("Moving torrent %s to %s", torrent_id, dest) - if not self.torrents[torrent_id].move_storage(dest): - log.warning("Error moving torrent %s to %s", torrent_id, dest) + def export_move_torrent(self, torrent_ids, dest): + log.debug("Moving torrents %s to %s", torrent_id, dest) + for torrent_id in torrent_ids: + if not self.torrents[torrent_id].move_storage(dest): + log.warning("Error moving torrent %s to %s", torrent_id, dest) def export_pause_all_torrents(self): """Pause all torrents in the session""" @@ -363,10 +367,11 @@ class Core( # Emit the 'torrent_all_resumed' signal self.torrent_all_resumed() - def export_resume_torrent(self, torrent_id): - log.debug("Resuming torrent %s", torrent_id) - if self.torrents[torrent_id].resume(): - self.torrent_resumed(torrent_id) + def export_resume_torrent(self, torrent_ids): + log.debug("Resuming: %s", torrent_ids) + for torrent_id in torrent_ids: + if self.torrents[torrent_id].resume(): + self.torrent_resumed(torrent_id) def export_get_torrent_status(self, torrent_id, keys): # Build the status dictionary @@ -467,9 +472,10 @@ class Core( self.plugins.disable_plugin(plugin) return None - def export_force_recheck(self, torrent_id): - """Forces a data recheck on torrent_id""" - return self.torrents.force_recheck(torrent_id) + def export_force_recheck(self, torrent_ids): + """Forces a data recheck on torrent_ids""" + for torrent_id in torrent_ids: + gobject.idle_add(self.torrents.force_recheck, torrent_id) def export_set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 03cd05252..5790959c5 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -187,12 +187,6 @@ def connected(): return True return False -def register_client(port): - get_core().call("register_client", None, port) - -def deregister_client(): - get_core().call("deregister_client", None) - def shutdown(): """Shutdown the core daemon""" try: @@ -205,7 +199,15 @@ def force_call(block=True): """Forces the multicall batch to go now and not wait for the timer. This call also blocks until all callbacks have been dealt with.""" get_core().do_multicall(block=block) - + +## Core methods ## + +def register_client(port): + get_core().call("register_client", None, port) + +def deregister_client(): + get_core().call("deregister_client", None) + def add_torrent_file(torrent_files, torrent_options=None): """Adds torrent files to the core Expects a list of torrent files @@ -252,19 +254,16 @@ def add_torrent_url(torrent_url, options=None): def remove_torrent(torrent_ids, remove_torrent=False, remove_data=False): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" - log.debug("Attempting to removing torrents: %s", torrent_ids) - for torrent_id in torrent_ids: - get_core().call("remove_torrent", None, torrent_id, remove_torrent, remove_data) + log.debug("Attempting to remove torrents: %s", torrent_ids) + get_core().call("remove_torrent", None, torrent_ids, remove_torrent, remove_data) def pause_torrent(torrent_ids): """Pauses torrent_ids""" - for torrent_id in torrent_ids: - get_core().call("pause_torrent", None, torrent_id) + get_core().call("pause_torrent", None, torrent_ids) def move_torrent(torrent_ids, folder): """Pauses torrent_ids""" - for torrent_id in torrent_ids: - get_core().call("move_torrent", None, torrent_id, folder) + get_core().call("move_torrent", None, torrent_ids, folder) def pause_all_torrents(): """Pauses all torrents""" @@ -276,13 +275,11 @@ def resume_all_torrents(): def resume_torrent(torrent_ids): """Resume torrent_ids""" - for torrent_id in torrent_ids: - get_core().call("resume_torrent", None, torrent_id) + get_core().call("resume_torrent", None, torrent_ids) def force_reannounce(torrent_ids): """Reannounce to trackers""" - for torrent_id in torrent_ids: - get_core().call("force_reannounce", None, torrent_id) + get_core().call("force_reannounce", None, torrent_ids) def get_torrent_status(callback, torrent_id, keys): """Builds the status dictionary and returns it""" @@ -337,8 +334,7 @@ def disable_plugin(plugin): def force_recheck(torrent_ids): """Forces a data recheck on torrent_ids""" - for torrent_id in torrent_ids: - get_core().call("force_recheck", None, torrent_id) + get_core().call("force_recheck", None, torrent_ids) def set_torrent_trackers(torrent_id, trackers): """Sets the torrents trackers""" From 1e02029fe1d4e2d3e650f1899ce3d5017a770f36 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 22 Feb 2008 06:26:08 +0000 Subject: [PATCH 0525/1009] lt sync 2020 --- libtorrent/include/libtorrent/config.hpp | 16 +++ libtorrent/include/libtorrent/enum_net.hpp | 21 ++- libtorrent/include/libtorrent/upnp.hpp | 3 +- libtorrent/src/broadcast_socket.cpp | 22 +-- libtorrent/src/entry.cpp | 15 ++- libtorrent/src/enum_net.cpp | 147 ++++++++++++++++----- libtorrent/src/piece_picker.cpp | 1 + libtorrent/src/policy.cpp | 3 + libtorrent/src/session_impl.cpp | 41 +++--- libtorrent/src/storage.cpp | 2 +- libtorrent/src/torrent_handle.cpp | 9 +- libtorrent/src/upnp.cpp | 39 +++--- 12 files changed, 227 insertions(+), 92 deletions(-) diff --git a/libtorrent/include/libtorrent/config.hpp b/libtorrent/include/libtorrent/config.hpp index b36d4da22..dc9769840 100755 --- a/libtorrent/include/libtorrent/config.hpp +++ b/libtorrent/include/libtorrent/config.hpp @@ -67,5 +67,21 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_DEPRECATED #endif +// set up defines for target environments +#if (defined __APPLE__ && __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD +#elif defined __linux__ +#define TORRENT_LINUX +#elif defined WIN32 +#define TORRENT_WINDOWS +#else +#warning unkown OS, assuming BSD +#define TORRENT_BSD +#endif + + + #endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp index a794c705c..4570e30eb 100644 --- a/libtorrent/include/libtorrent/enum_net.hpp +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -37,7 +37,26 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - std::vector
          enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); + + struct ip_interface + { + address interface_address; + address netmask; + }; + + // returns a list of the configured IP interfaces + // on the machine + std::vector enum_net_interfaces(asio::io_service& ios + , asio::error_code& ec); + + // returns true if the specified address is on the same + // local network as the specified interface + bool in_subnet(address const& addr, ip_interface const& iface); + + // returns true if the specified address is on the same + // local network as us + bool in_local_network(asio::io_service& ios, address const& addr, asio::error_code& ec); + address router_for_interface(address const interface, asio::error_code& ec); } diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index be8ec15cb..1d0f8d425 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -230,11 +230,10 @@ private: bool m_disabled; bool m_closing; + bool m_ignore_outside_network; connection_queue& m_cc; - std::vector
          m_filter; - #ifdef TORRENT_UPNP_LOGGING std::ofstream m_log; #endif diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 3437648d9..16f0a0728 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -79,12 +79,12 @@ namespace libtorrent { // make a best guess of the interface we're using and its IP asio::error_code ec; - std::vector
          const& interfaces = enum_net_interfaces(ios, ec); + std::vector const& interfaces = enum_net_interfaces(ios, ec); address ret = address_v4::any(); - for (std::vector
          ::const_iterator i = interfaces.begin() + for (std::vector::const_iterator i = interfaces.begin() , end(interfaces.end()); i != end; ++i) { - address const& a = *i; + address const& a = i->interface_address; if (is_loopback(a) || is_multicast(a) || is_any(a)) continue; @@ -111,20 +111,20 @@ namespace libtorrent using namespace asio::ip::multicast; asio::error_code ec; - std::vector
          interfaces = enum_net_interfaces(ios, ec); + std::vector interfaces = enum_net_interfaces(ios, ec); - for (std::vector
          ::const_iterator i = interfaces.begin() + for (std::vector::const_iterator i = interfaces.begin() , end(interfaces.end()); i != end; ++i) { // only broadcast to IPv4 addresses that are not local - if (!is_local(*i)) continue; + if (!is_local(i->interface_address)) continue; // only multicast on compatible networks - if (i->is_v4() != multicast_endpoint.address().is_v4()) continue; + if (i->interface_address.is_v4() != multicast_endpoint.address().is_v4()) continue; // ignore any loopback interface - if (is_loopback(*i)) continue; + if (is_loopback(i->interface_address)) continue; boost::shared_ptr s(new datagram_socket(ios)); - if (i->is_v4()) + if (i->interface_address.is_v4()) { s->open(udp::v4(), ec); if (ec) continue; @@ -134,7 +134,7 @@ namespace libtorrent if (ec) continue; s->set_option(join_group(multicast_endpoint.address()), ec); if (ec) continue; - s->set_option(outbound_interface(i->to_v4()), ec); + s->set_option(outbound_interface(i->interface_address.to_v4()), ec); if (ec) continue; } else @@ -147,7 +147,7 @@ namespace libtorrent if (ec) continue; s->set_option(join_group(multicast_endpoint.address()), ec); if (ec) continue; -// s->set_option(outbound_interface(i->to_v6()), ec); +// s->set_option(outbound_interface(i->interface_address.to_v6()), ec); // if (ec) continue; } s->set_option(hops(255), ec); diff --git a/libtorrent/src/entry.cpp b/libtorrent/src/entry.cpp index 8219ecc06..c3e91044a 100755 --- a/libtorrent/src/entry.cpp +++ b/libtorrent/src/entry.cpp @@ -143,24 +143,28 @@ namespace libtorrent #endif entry::entry(dictionary_type const& v) + : m_type(undefined_t) { new(data) dictionary_type(v); m_type = dictionary_t; } entry::entry(string_type const& v) + : m_type(undefined_t) { new(data) string_type(v); m_type = string_t; } entry::entry(list_type const& v) + : m_type(undefined_t) { new(data) list_type(v); m_type = list_t; } entry::entry(integer_type const& v) + : m_type(undefined_t) { new(data) integer_type(v); m_type = int_t; @@ -216,8 +220,7 @@ namespace libtorrent void entry::construct(data_type t) { - m_type = t; - switch(m_type) + switch(t) { case int_t: new(data) integer_type; @@ -234,13 +237,14 @@ namespace libtorrent default: TORRENT_ASSERT(m_type == undefined_t); m_type = undefined_t; + return; } + m_type = t; } void entry::copy(entry const& e) { - m_type = e.m_type; - switch(m_type) + switch(e.m_type) { case int_t: new(data) integer_type(e.integer()); @@ -256,7 +260,9 @@ namespace libtorrent break; default: m_type = undefined_t; + return; } + m_type = e.m_type; } void entry::destruct() @@ -279,6 +285,7 @@ namespace libtorrent TORRENT_ASSERT(m_type == undefined_t); break; } + m_type = undefined_t; } void entry::swap(entry& e) diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index 585fa0f38..3eb8df837 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -30,11 +30,13 @@ POSSIBILITY OF SUCH DAMAGE. */ -#if defined __linux__ || defined __MACH__ +#include "libtorrent/config.hpp" + +#if defined TORRENT_BSD || defined TORRENT_LINUX #include #include #include -#elif defined WIN32 +#elif defined TORRENT_WINDOWS #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif @@ -46,11 +48,58 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - std::vector
          enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) + namespace { - std::vector
          ret; + address sockaddr_to_address(sockaddr const* sin) + { + if (sin->sa_family == AF_INET) + { + typedef asio::ip::address_v4::bytes_type bytes_t; + bytes_t b; + memcpy(&b[0], &((sockaddr_in const*)sin)->sin_addr, b.size()); + return address_v4(b); + } + else if (sin->sa_family == AF_INET6) + { + typedef asio::ip::address_v6::bytes_type bytes_t; + bytes_t b; + memcpy(&b[0], &((sockaddr_in6 const*)sin)->sin6_addr, b.size()); + return address_v6(b); + } + return address(); + } + } + + bool in_subnet(address const& addr, ip_interface const& iface) + { + if (addr.is_v4() != iface.interface_address.is_v4()) return false; + // since netmasks seems unreliable for IPv6 interfaces + // (MacOS X returns AF_INET addresses as bitmasks) assume + // that any IPv6 address belongs to the subnet of any + // interface with an IPv6 address + if (addr.is_v6()) return true; -#if defined __linux__ || defined __MACH__ || defined(__FreeBSD__) + return (addr.to_v4().to_ulong() & iface.netmask.to_v4().to_ulong()) + == (iface.interface_address.to_v4().to_ulong() & iface.netmask.to_v4().to_ulong()); + } + + bool in_local_network(asio::io_service& ios, address const& addr, asio::error_code& ec) + { + std::vector const& net = enum_net_interfaces(ios, ec); + if (ec) return false; + for (std::vector::const_iterator i = net.begin() + , end(net.end()); i != end; ++i) + { + if (in_subnet(addr, *i)) return true; + } + return false; + } + + std::vector enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) + { + std::vector ret; + +#if defined TORRENT_LINUX || defined TORRENT_BSD int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { @@ -63,11 +112,10 @@ namespace libtorrent ifc.ifc_buf = buf; if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + ec = asio::error_code(errno, asio::error::system_category); close(s); - ec = asio::error::fault; return ret; } - close(s); char *ifr = (char*)ifc.ifc_req; int remaining = ifc.ifc_len; @@ -75,36 +123,51 @@ namespace libtorrent while (remaining) { ifreq const& item = *reinterpret_cast(ifr); - if (item.ifr_addr.sa_family == AF_INET) + + if (item.ifr_addr.sa_family == AF_INET + || item.ifr_addr.sa_family == AF_INET6) { - typedef asio::ip::address_v4::bytes_type bytes_t; - bytes_t b; - memcpy(&b[0], &((sockaddr_in const*)&item.ifr_addr)->sin_addr, b.size()); - ret.push_back(address_v4(b)); - } - else if (item.ifr_addr.sa_family == AF_INET6) - { - typedef asio::ip::address_v6::bytes_type bytes_t; - bytes_t b; - memcpy(&b[0], &((sockaddr_in6 const*)&item.ifr_addr)->sin6_addr, b.size()); - ret.push_back(address_v6(b)); + ip_interface iface; + iface.interface_address = sockaddr_to_address(&item.ifr_addr); + + ifreq netmask = item; + if (ioctl(s, SIOCGIFNETMASK, &netmask) < 0) + { + if (iface.interface_address.is_v6()) + { + // this is expected to fail (at least on MacOS X) + iface.netmask = address_v6::any(); + } + else + { + ec = asio::error_code(errno, asio::error::system_category); + close(s); + return ret; + } + } + else + { + iface.netmask = sockaddr_to_address(&netmask.ifr_addr); + } + ret.push_back(iface); } -#if defined __MACH__ || defined(__FreeBSD__) +#if defined TORRENT_BSD int current_size = item.ifr_addr.sa_len + IFNAMSIZ; -#elif defined __linux__ +#elif defined TORRENT_LINUX int current_size = sizeof(ifreq); #endif ifr += current_size; remaining -= current_size; } + close(s); -#elif defined WIN32 +#elif defined TORRENT_WINDOWS SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); if (s == SOCKET_ERROR) { - ec = asio::error::fault; + ec = asio::error_code(WSAGetLastError(), asio::error::system_category); return ret; } @@ -114,29 +177,36 @@ namespace libtorrent if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, sizeof(buffer), &size, 0, 0) != 0) { + ec = asio::error_code(WSAGetLastError(), asio::error::system_category); closesocket(s); - ec = asio::error::fault; return ret; } closesocket(s); int n = size / sizeof(INTERFACE_INFO); + ip_interface iface; for (int i = 0; i < n; ++i) { - sockaddr_in *sockaddr = (sockaddr_in*)&buffer[i].iiAddress; - address a(address::from_string(inet_ntoa(sockaddr->sin_addr))); - if (a == address_v4::any()) continue; - ret.push_back(a); + iface.interface_address = sockaddr_to_address(&buffer[i].iiAddress.Address); + iface.netmask = sockaddr_to_address(&buffer[i].iiNetmask.Address); + if (iface.interface_address == address_v4::any()) continue; + ret.push_back(iface); } #else +#warning THIS OS IS NOT RECOGNIZED, enum_net_interfaces WILL PROBABLY NOT WORK // make a best guess of the interface we're using and its IP udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(ec), "0")); + if (ec) return ret; + ip_interface iface; for (;i != udp::resolver_iterator(); ++i) { - ret.push_back(i->endpoint().address()); + iface.interface_address = i->endpoint().address(); + if (iface.interface_address.is_v4()) + iface.netmask = address_v4::netmask(iface.interface_address.to_v4()); + ret.push_back(iface); } #endif return ret; @@ -144,7 +214,7 @@ namespace libtorrent address router_for_interface(address const interface, asio::error_code& ec) { -#ifdef WIN32 +#ifdef TORRENT_WINDOWS // Load Iphlpapi library HMODULE iphlp = LoadLibraryA("Iphlpapi.dll"); @@ -184,15 +254,22 @@ namespace libtorrent address ret; if (GetAdaptersInfo(adapter_info, &out_buf_size) == NO_ERROR) { - PIP_ADAPTER_INFO adapter = adapter_info; - while (adapter != 0) + + for (PIP_ADAPTER_INFO adapter = adapter_info; + adapter != 0; adapter = adapter->Next) { - if (interface == address::from_string(adapter->IpAddressList.IpAddress.String, ec)) + address iface = address::from_string(adapter->IpAddressList.IpAddress.String, ec); + if (ec) + { + ec = asio::error_code(); + continue; + } + if (is_loopback(iface) || is_any(iface)) continue; + if (interface == address() || interface == iface) { ret = address::from_string(adapter->GatewayList.IpAddress.String, ec); break; } - adapter = adapter->Next; } } diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index dbdba17f0..9bd02f3c4 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -334,6 +334,7 @@ namespace libtorrent { ++num_requested; blocks_requested = true; + TORRENT_ASSERT(i->info[k].num_peers > 0); } if (i->info[k].state == block_info::state_writing) { diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 2fdc5358a..0599096d5 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -294,6 +294,7 @@ namespace libtorrent || std::find(rq.begin(), rq.end(), *i) != rq.end()) continue; + TORRENT_ASSERT(p.num_peers(*i) > 0); busy_pieces.push_back(*i); continue; } @@ -333,6 +334,8 @@ namespace libtorrent p.piece_info(i->piece_index, st); TORRENT_ASSERT(st.requested + st.finished + st.writing == p.blocks_in_piece(i->piece_index)); #endif + TORRENT_ASSERT(p.is_requested(*i)); + TORRENT_ASSERT(p.num_peers(*i) > 0); c.add_request(*i); c.send_block_requests(); } diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index a93ad7cb0..a78507af0 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -617,6 +617,7 @@ namespace detail "\n"; m_buffer_usage_logger.open("buffer_stats.log", std::ios::trunc); m_second_counter = 0; + m_buffer_allocations = 0; #endif // ---- generate a peer id ---- @@ -797,12 +798,13 @@ namespace detail INVARIANT_CHECK; - TORRENT_ASSERT(s.connection_speed > 0); TORRENT_ASSERT(s.file_pool_size > 0); // less than 5 seconds unchoke interval is insane TORRENT_ASSERT(s.unchoke_interval >= 5); m_settings = s; + if (m_settings.connection_speed <= 0) m_settings.connection_speed = 200; + m_files.resize(m_settings.file_pool_size); // replace all occurances of '\n' with ' '. std::string::iterator i = m_settings.user_agent.begin(); @@ -959,12 +961,14 @@ namespace detail { // if we're listening on any IPv6 address, enumerate them and // pick the first non-local address - std::vector
          const& ifs = enum_net_interfaces(m_io_service, ec); - for (std::vector
          ::const_iterator i = ifs.begin() + std::vector const& ifs = enum_net_interfaces(m_io_service, ec); + for (std::vector::const_iterator i = ifs.begin() , end(ifs.end()); i != end; ++i) { - if (i->is_v4() || i->to_v6().is_link_local() || i->to_v6().is_loopback()) continue; - m_ipv6_interface = tcp::endpoint(*i, ep.port()); + if (i->interface_address.is_v4() + || i->interface_address.to_v6().is_link_local() + || i->interface_address.to_v6().is_loopback()) continue; + m_ipv6_interface = tcp::endpoint(i->interface_address, ep.port()); break; } break; @@ -2643,20 +2647,23 @@ namespace detail TORRENT_ASSERT(*slot_iter == p.index); int slot_index = static_cast(slot_iter - tmp_pieces.begin()); - unsigned long adler - = torrent_ptr->filesystem().piece_crc( - slot_index - , torrent_ptr->block_size() - , p.info); - - const entry& ad = (*i)["adler32"]; + const entry* ad = i->find_key("adler32"); - // crc's didn't match, don't use the resume data - if (ad.integer() != entry::integer_type(adler)) + if (ad && ad->type() == entry::int_t) { - error = "checksum mismatch on piece " - + boost::lexical_cast(p.index); - return; + unsigned long adler + = torrent_ptr->filesystem().piece_crc( + slot_index + , torrent_ptr->block_size() + , p.info); + + // crc's didn't match, don't use the resume data + if (ad->integer() != entry::integer_type(adler)) + { + error = "checksum mismatch on piece " + + boost::lexical_cast(p.index); + return; + } } tmp_unfinished.push_back(p); diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 2f3c7e7f9..810626f52 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -1151,7 +1151,7 @@ namespace libtorrent m_slot_to_piece.begin(); i != last.base(); ++i) { - p.push_back(have[*i] ? *i : unassigned); + p.push_back((*i >= 0 && have[*i]) ? *i : unassigned); } } else diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 635390537..bd893644d 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -569,7 +569,7 @@ namespace libtorrent TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); } piece_struct["bitmask"] = bitmask; - +/* TORRENT_ASSERT(t->filesystem().slot_for(i->index) >= 0); unsigned long adler = t->filesystem().piece_crc( @@ -578,7 +578,7 @@ namespace libtorrent , i->info); piece_struct["adler32"] = adler; - +*/ // push the struct onto the unfinished-piece list up.push_back(piece_struct); } @@ -596,6 +596,8 @@ namespace libtorrent policy& pol = t->get_policy(); + int max_failcount = t->settings().max_failcount; + for (policy::iterator i = pol.begin_peer() , end(pol.end_peer()); i != end; ++i) { @@ -619,6 +621,9 @@ namespace libtorrent // been banned, don't save it. if (i->second.type == policy::peer::not_connectable) continue; + // don't save peers that doesn't work + if (i->second.failcount >= max_failcount) continue; + tcp::endpoint ip = i->second.ip; entry peer(entry::dictionary_t); peer["ip"] = ip.address().to_string(ec); diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 4f6ac7fbf..5e9953fbc 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -76,27 +76,13 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_refresh_timer(ios) , m_disabled(false) , m_closing(false) + , m_ignore_outside_network(ignore_nonrouters) , m_cc(cc) { #ifdef TORRENT_UPNP_LOGGING m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif m_retry_count = 0; - - if (ignore_nonrouters) - { - asio::error_code ec; - std::vector
          const& net = enum_net_interfaces(m_io_service, ec); - m_filter.reserve(net.size()); - for (std::vector
          ::const_iterator i = net.begin() - , end(net.end()); i != end; ++i) - { - asio::error_code e; - address a = router_for_interface(*i, e); - if (e || is_loopback(a)) continue; - m_filter.push_back(a); - } - } } upnp::~upnp() @@ -281,14 +267,29 @@ try Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0 */ - if (!m_filter.empty() && std::find(m_filter.begin(), m_filter.end() - , from.address()) == m_filter.end()) + asio::error_code ec; + if (m_ignore_outside_network && !in_local_network(m_io_service, from.address(), ec)) { // this upnp device is filtered because it's not in the // list of configured routers #ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() << " <== (" << from << ") Rootdevice " - "ignored because it's not out router" << std::endl; + if (ec) + { + m_log << time_now_string() << " <== (" << from << ") error: " + << ec.message() << std::endl; + } + else + { + std::vector const& net = enum_net_interfaces(m_io_service, ec); + m_log << time_now_string() << " <== (" << from << ") UPnP device " + "ignored because it's not on our network "; + for (std::vector::const_iterator i = net.begin() + , end(net.end()); i != end; ++i) + { + m_log << "(" << i->interface_address << ", " << i->netmask << ") "; + } + m_log << std::endl; + } #endif return; } From 5aa3bfd16d0cae38197f2f1dbf40b03883de9001 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 22 Feb 2008 09:10:59 +0000 Subject: [PATCH 0526/1009] Fix stopping signalreceiver --- deluge/ui/signalreceiver.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 7e89c0b45..ce053efc2 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -70,9 +70,13 @@ class SignalReceiver( """Shutdowns receiver thread""" self._shutdown = True # De-register with the daemon so it doesn't try to send us more signals - client.deregister_client() - client.force_call() - + try: + client.deregister_client() + client.force_call() + except: + pass + log.debug("Shutting down signalreceiver") + # Hacky.. sends a request to our local receiver to ensure that it # shutdowns.. This is because handle_request() is a blocking call. receiver = xmlrpclib.ServerProxy("http://localhost:" + str(self.port), From 5cb5ce15d6f18e824039ebe2a93b67d7e8f3a98c Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 22 Feb 2008 09:11:40 +0000 Subject: [PATCH 0527/1009] Fix formatting --- deluge/core/torrent.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index aa9495676..01e3e58ec 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -246,24 +246,24 @@ class Torrent: "save_path": self.save_path, "files": self.files, "file_priorities": self.file_priorities, - "compact":self.compact, - "max_connections":self.max_connections, - "max_upload_slots":self.max_upload_slots, - "max_upload_speed":self.max_upload_speed, - "max_download_speed":self.max_download_speed, - "prioritize_first_last":self.prioritize_first_last, - "private":self.private + "compact": self.compact, + "max_connections": self.max_connections, + "max_upload_slots": self.max_upload_slots, + "max_upload_speed": self.max_upload_speed, + "max_download_speed": self.max_download_speed, + "prioritize_first_last": self.prioritize_first_last, + "private": self.private } fns = { - "name" : self.torrent_info.name, - "total_size" : self.torrent_info.total_size, - "num_files" : self.torrent_info.num_files, - "num_pieces" : self.torrent_info.num_pieces, - "piece_length" : self.torrent_info.piece_length, - "eta" : self.get_eta, - "ratio" : self.get_ratio, - "file_progress" : self.handle.file_progress + "name": self.torrent_info.name, + "total_size": self.torrent_info.total_size, + "num_files": self.torrent_info.num_files, + "num_pieces": self.torrent_info.num_pieces, + "piece_length": self.torrent_info.piece_length, + "eta": self.get_eta, + "ratio": self.get_ratio, + "file_progress": self.handle.file_progress } self.status = None From 21c4025a7b8084cdcb67cf6ef9e6e32942d74003 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 22 Feb 2008 09:12:16 +0000 Subject: [PATCH 0528/1009] Show max_speeds in torrent details. --- deluge/ui/gtkui/torrentdetails.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index f9d63d894..3711ad30c 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -55,6 +55,12 @@ def fratio(value): def fpcnt(value): return "%.2f%%" % value +def fspeed(value, max_value=-1): + if max_value > -1: + return "%s [%s KiB/s]" % (deluge.common.fspeed(value), max_value) + else: + return deluge.common.fspeed(value) + class TorrentDetails(component.Component): def __init__(self): component.Component.__init__(self, "TorrentDetails", interval=2000) @@ -82,8 +88,8 @@ class TorrentDetails(component.Component): (glade.get_widget("summary_availability"), fratio, ("distributed_copies",)), (glade.get_widget("summary_total_downloaded"), fpeer_sized, ("total_done", "total_payload_download")), (glade.get_widget("summary_total_uploaded"), fpeer_sized, ("total_uploaded", "total_payload_upload")), - (glade.get_widget("summary_download_speed"), deluge.common.fspeed, ("download_payload_rate",)), - (glade.get_widget("summary_upload_speed"), deluge.common.fspeed, ("upload_payload_rate",)), + (glade.get_widget("summary_download_speed"), fspeed, ("download_payload_rate", "max_download_speed")), + (glade.get_widget("summary_upload_speed"), fspeed, ("upload_payload_rate", "max_upload_speed")), (glade.get_widget("summary_seeders"), deluge.common.fpeer, ("num_seeds", "total_seeds")), (glade.get_widget("summary_peers"), deluge.common.fpeer, ("num_peers", "total_peers")), (glade.get_widget("summary_eta"), deluge.common.ftime, ("eta",)), @@ -133,7 +139,9 @@ class TorrentDetails(component.Component): "total_payload_upload", "download_payload_rate", "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", - "tracker_status", "save_path"] + "tracker_status", "save_path", "max_connections", + "max_upload_slots", "max_upload_speed", "max_download_speed", + "private"] client.get_torrent_status( self._on_get_torrent_status, selected, status_keys) From d53a606b0e352f61c31c6ed8d04715a73bd12359 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 22 Feb 2008 22:16:13 +0000 Subject: [PATCH 0529/1009] big client.py refactor --- deluge/plugins/queue/queue/ui.py | 10 +- deluge/ui/client.py | 447 +++++++++++++------------ deluge/ui/gtkui/aboutdialog.py | 2 +- deluge/ui/gtkui/addtorrentdialog.py | 2 +- deluge/ui/gtkui/connectionmanager.py | 2 +- deluge/ui/gtkui/coreconfig.py | 2 +- deluge/ui/gtkui/dbusinterface.py | 2 +- deluge/ui/gtkui/edittrackersdialog.py | 2 +- deluge/ui/gtkui/gtkui.py | 2 +- deluge/ui/gtkui/mainwindow.py | 2 +- deluge/ui/gtkui/menubar.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 2 +- deluge/ui/gtkui/preferences.py | 2 +- deluge/ui/gtkui/queuedtorrents.py | 2 +- deluge/ui/gtkui/removetorrentdialog.py | 2 +- deluge/ui/gtkui/signals.py | 2 +- deluge/ui/gtkui/statusbar.py | 2 +- deluge/ui/gtkui/systemtray.py | 2 +- deluge/ui/gtkui/toolbar.py | 2 +- deluge/ui/gtkui/torrentdetails.py | 2 +- deluge/ui/gtkui/torrentview.py | 2 +- deluge/ui/null/deluge_shell.py | 2 +- deluge/ui/signalreceiver.py | 2 +- 23 files changed, 257 insertions(+), 242 deletions(-) diff --git a/deluge/plugins/queue/queue/ui.py b/deluge/plugins/queue/queue/ui.py index d50b8d7ca..8cb120950 100644 --- a/deluge/plugins/queue/queue/ui.py +++ b/deluge/plugins/queue/queue/ui.py @@ -34,7 +34,7 @@ import gettext import locale import pkg_resources -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.log import LOG as log class UI: @@ -78,7 +78,7 @@ class UI: torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: try: - client.get_core().queue_queue_top(torrent_id) + client.queue_queue_top(torrent_id) except Exception, e: log.debug("Unable to queue top torrent: %s", e) return @@ -89,7 +89,7 @@ class UI: torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: try: - client.get_core().queue_queue_up(torrent_id) + client.queue_queue_up(torrent_id) except Exception, e: log.debug("Unable to queue up torrent: %s", e) return @@ -100,7 +100,7 @@ class UI: torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: try: - client.get_core().queue_queue_down(torrent_id) + client.queue_queue_down(torrent_id) except Exception, e: log.debug("Unable to queue down torrent: %s", e) return @@ -111,7 +111,7 @@ class UI: torrent_ids = self.plugin.get_selected_torrents() for torrent_id in torrent_ids: try: - client.get_core().queue_queue_bottom(torrent_id) + client.queue_queue_bottom(torrent_id) except Exception, e: log.debug("Unable to queue bottom torrent: %s", e) return diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 5790959c5..f4cff75a4 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -1,20 +1,21 @@ # # client.py # -# Copyright (C) 2007 Andrew Resch ('andar') -# +# Copyright (C) 2007/2008 Andrew Resch ('andar') +# Copyright (C) 2008 Martijn Voncken +# # Deluge is free software. -# +# # You may redistribute it and/or modify it under the terms of the # GNU General Public License, as published by the Free Software # Foundation; either version 2 of the License, or (at your option) # any later version. -# +# # deluge is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: # The Free Software Foundation, Inc., @@ -43,26 +44,26 @@ import deluge.xmlrpclib as xmlrpclib import deluge.common import deluge.error from deluge.log import LOG as log - + class CoreProxy(gobject.GObject): - __gsignals__ = { - "new_core" : ( + __gsignals__ = { + "new_core" : ( + gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), + "no_core" : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), - "no_core" : ( - gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), } def __init__(self): log.debug("CoreProxy init..") gobject.GObject.__init__(self) self._uri = None - self._core = None + self.rpc_core = None self._multi = None self._callbacks = [] self._multi_timer = None def call(self, func, callback, *args): - if self._core is None or self._multi is None: - if self.get_core() is None: + if self.rpc_core is None or self._multi is None: + if self.rpc_core is None: raise deluge.error.NoCoreError("The core proxy is invalid.") return _func = getattr(self._multi, func) @@ -77,14 +78,14 @@ class CoreProxy(gobject.GObject): _func() else: _func(*args) - + self._callbacks.append([callback]) def do_multicall(self, block=False): if len(self._callbacks) == 0: return True - - if self._multi is not None and self._core is not None: + + if self._multi is not None and self.rpc_core is not None: try: try: for i, ret in enumerate(self._multi()): @@ -99,18 +100,18 @@ class CoreProxy(gobject.GObject): except socket.error, e: log.warning("Could not contact daemon: %s", e) self.set_core_uri(None) - finally: + finally: self._callbacks = [] - - self._multi = xmlrpclib.MultiCall(self._core) + + self._multi = xmlrpclib.MultiCall(self.rpc_core) return True - + def set_core_uri(self, uri): log.info("Setting core uri as %s", uri) - + if uri == None and self._uri != None: self._uri = None - self._core = None + self.rpc_core = None self._multi = None try: gobject.source_remove(self._multi_timer) @@ -118,252 +119,266 @@ class CoreProxy(gobject.GObject): pass self.emit("no_core") return - + if uri != self._uri and self._uri != None: - self._core = None + self.rpc_core = None self._multi = None try: gobject.source_remove(self._multi_timer) except: pass self.emit("no_core") - + self._uri = uri # Get a new core - self.get_core() - + self.get_rpc_core() + def get_core_uri(self): """Returns the URI of the core currently being used.""" return self._uri - - def get_core(self): - if self._core is None and self._uri is not None: + + def get_rpc_core(self): + if self.rpc_core is None and self._uri is not None: log.debug("Creating ServerProxy..") - self._core = xmlrpclib.ServerProxy(self._uri, allow_none=True) - self._multi = xmlrpclib.MultiCall(self._core) + self.rpc_core = xmlrpclib.ServerProxy(self._uri, allow_none=True) + self._multi = xmlrpclib.MultiCall(self.rpc_core) self._multi_timer = gobject.timeout_add(200, self.do_multicall) # Call any callbacks registered self.emit("new_core") - - return self._core - + + return self.rpc_core + _core = CoreProxy() -def get_core(): - """Get the core object and return it""" - return _core - -def connect_on_new_core(callback): - """Connect a callback whenever a new core is connected to.""" - return _core.connect("new_core", callback) +class BaseClient(object): + """ + wraps all calls to core/coreproxy + base for AClient and SClient + """ + no_callback_list = [ "add_torrent_url", "pause_all_torrents", + "resume_all_torrent", "set_config", "enable_plugin", + "disable_plugin", "set_torrent_trackers", + "set_torrent_max_connections", "set_torrent_max_upload_slots", + "set_torrent_max_upload_speed", "set_torrent_max_download_speed", + "set_torrent_private_flag", "set_torrent_file_priorities", + "block_ip_range", + "remove_torrent", "pause_torrent", "move_torrent" , + "resume_torrent","force_reannounce", "force_recheck", + "deregister_client","register_client", + "add_torrent_file"] -def connect_on_no_core(callback): - """Connect a callback whenever the core is disconnected from.""" - return _core.connect("no_core", callback) - -def set_core_uri(uri): - """Sets the core uri""" - return _core.set_core_uri(uri) + def __init__(self): + self.core = _core -def get_core_uri(): - """Get the core URI""" - return _core.get_core_uri() + #xml-rpc introspection + def list_methods(self): + registered = self.core.rpc_core.system.listMethods( ) + return sorted(registered) -def is_localhost(): - """Returns True if core is a localhost""" - # Get the uri - uri = _core.get_core_uri() - if uri != None: - # Get the host - host = uri[7:].split(":")[0] - if host == "localhost" or host == "127.0.0.1": - return True - - return False + def methodSignature(self, method_name): + "broken :(" + return self.core.rpc_core.system.methodSignature(method_name) -def connected(): - """Returns True if connected to a host, and False if not.""" - if get_core_uri() != None: - return True - return False + def methodHelp(self, method_name): + return self.core.rpc_core.system.methodHelp(method_name) -def shutdown(): - """Shutdown the core daemon""" - try: - get_core().call("shutdown", None) - force_call(block=False) - finally: - set_core_uri(None) + #wrappers, getattr + def get_method(self, method_name): + "Override this in subclass." + raise NotImplementedError() -def force_call(block=True): - """Forces the multicall batch to go now and not wait for the timer. This - call also blocks until all callbacks have been dealt with.""" - get_core().do_multicall(block=block) + def __getattr__(self, method_name): + return self.get_method(method_name) + #raise AttributeError("no attr/method named:%s" % attr) -## Core methods ## - -def register_client(port): - get_core().call("register_client", None, port) - -def deregister_client(): - get_core().call("deregister_client", None) - -def add_torrent_file(torrent_files, torrent_options=None): - """Adds torrent files to the core + #custom wrapped methods: + def add_torrent_file(self, torrent_files, torrent_options=None): + """Adds torrent files to the core Expects a list of torrent files A list of torrent_option dictionaries in the same order of torrent_files - """ - if torrent_files is None: - log.debug("No torrent files selected..") - return - log.debug("Attempting to add torrent files: %s", torrent_files) - for torrent_file in torrent_files: - # Open the .torrent file for reading because we need to send it's - # contents to the core. - try: - f = open(torrent_file, "rb") - except Exception, e: - log.warning("Unable to open %s: %s", torrent_file, e) - continue - - # Get the filename because the core doesn't want a path. - (path, filename) = os.path.split(torrent_file) - fdump = xmlrpclib.Binary(f.read()) - f.close() - - # Get the options for the torrent - if torrent_options != None: + """ + if torrent_files is None: + log.debug("No torrent files selected..") + return + log.debug("Attempting to add torrent files: %s", torrent_files) + for torrent_file in torrent_files: + # Open the .torrent file for reading because we need to send it's + # contents to the core. try: - options = torrent_options[torrent_files.index(torrent_file)] - except: + f = open(torrent_file, "rb") + except Exception, e: + log.warning("Unable to open %s: %s", torrent_file, e) + continue + + # Get the filename because the core doesn't want a path. + (path, filename) = os.path.split(torrent_file) + fdump = xmlrpclib.Binary(f.read()) + f.close() + + # Get the options for the torrent + if torrent_options != None: + try: + options = torrent_options[torrent_files.index(torrent_file)] + except: + options = None + else: options = None + self.get_method("add_torrent_file")(filename, fdump, options) + + #utility: + def has_callback(self, method_name): + return (method_name in self.no_callback_list) + + def is_localhost(self): + """Returns True if core is a localhost""" + # Get the uri + uri = self.core.get_core_uri() + if uri != None: + # Get the host + host = uri[7:].split(":")[0] + if host == "localhost" or host == "127.0.0.1": + return True + return False + + def get_core_uri(self): + """Get the core URI""" + return self.core.get_core_uri() + + def set_core_uri(self, uri = 'http://localhost:58846'): + """Sets the core uri""" + return self.core.set_core_uri(uri) + + def connected(self): + """Returns True if connected to a host, and False if not.""" + if self.get_core_uri() != None: + return True + return False + + def shutdown(self): + """Shutdown the core daemon""" + try: + self.core.call("shutdown", None) + self.core.force_call(block=False) + finally: + self.set_core_uri(None) + + #events: + def connect_on_new_core(self, callback): + """Connect a callback whenever a new core is connected to.""" + return self.core.connect("new_core", callback) + + def connect_on_no_core(self, callback): + """Connect a callback whenever the core is disconnected from.""" + return self.core.connect("no_core", callback) + +class SClient(BaseClient): + """ + sync proxy + """ + def get_method(self, method_name): + return getattr(self.core.rpc_core, method_name) + +class AClient(BaseClient): + """ + async proxy + """ + def get_method(self, method_name): + if self.has_callback(method_name): + def async_proxy_nocb(*args, **kwargs): + return self.core.call(method_name,None, *args, **kwargs) + return async_proxy_nocb else: - options = None - - get_core().call("add_torrent_file", None, - filename, fdump, options) + def async_proxy(*args, **kwargs): + return self.core.call(method_name, *args, **kwargs) + return async_proxy -def add_torrent_url(torrent_url, options=None): - """Adds torrents to the core via url""" - from deluge.common import is_url - if is_url(torrent_url): - get_core().call("add_torrent_url", None, - torrent_url, str(), options) - else: - log.warning("Invalid URL %s", torrent_url) - -def remove_torrent(torrent_ids, remove_torrent=False, remove_data=False): - """Removes torrent_ids from the core.. Expects a list of torrent_ids""" - log.debug("Attempting to remove torrents: %s", torrent_ids) - get_core().call("remove_torrent", None, torrent_ids, remove_torrent, remove_data) + def force_call(self, block=True): + """Forces the multicall batch to go now and not wait for the timer. This + call also blocks until all callbacks have been dealt with.""" + self.core.do_multicall(block=block) -def pause_torrent(torrent_ids): - """Pauses torrent_ids""" - get_core().call("pause_torrent", None, torrent_ids) +sclient = SClient() +aclient = AClient() -def move_torrent(torrent_ids, folder): - """Pauses torrent_ids""" - get_core().call("move_torrent", None, torrent_ids, folder) +#------------------------------------------------------------------------------ +#tests: +#------------------------------------------------------------------------------ -def pause_all_torrents(): - """Pauses all torrents""" - get_core().call("pause_all_torrents", None) +def test_introspection(): + print "*start introspection test*" + sclient.set_core_uri() + print "list_methods", sclient.list_methods() + print "sig of block_ip_range", sclient.methodSignature('block_ip_range') + print "doc of block_ip_range", sclient.methodHelp('block_ip_range') -def resume_all_torrents(): - """Resumes all torrents""" - get_core().call("resume_all_torrents", None) - -def resume_torrent(torrent_ids): - """Resume torrent_ids""" - get_core().call("resume_torrent", None, torrent_ids) - -def force_reannounce(torrent_ids): - """Reannounce to trackers""" - get_core().call("force_reannounce", None, torrent_ids) +def test_sync(): + print "*start sync test*" + sclient.set_core_uri() -def get_torrent_status(callback, torrent_id, keys): - """Builds the status dictionary and returns it""" - get_core().call("get_torrent_status", callback, torrent_id, keys) + #get list of torrents and display the 1st. + torrent_ids = sclient.get_session_state() + print "session_state():", torrent_ids + print ("get_torrent_status(%s):" % torrent_ids[0], + sclient.get_torrent_status(torrent_ids[0], [])) -def get_torrents_status(callback, torrent_ids, keys): - """Builds a dictionary of torrent_ids status. Expects 2 lists. This is - asynchronous so the return value will be sent as the signal - 'torrent_status'""" - get_core().call("get_torrents_status", callback, torrent_ids, keys) + sclient.pause_torrent(torrent_ids) -def get_session_state(callback): - get_core().call("get_session_state", callback) + print "paused:", [ + sclient.get_torrent_status(id, ['paused'])['paused'] + for id in torrent_ids] -def get_config(callback): - get_core().call("get_config", callback) + sclient.resume_torrent(torrent_ids) + print "resumed:", [ + sclient.get_torrent_status(id, ['paused'])['paused'] + for id in torrent_ids] -def get_config_value(callback, key): - get_core().call("get_config_value", callback, key) - -def set_config(config): - if config == {}: - return - get_core().call("set_config", None, config) +def test_async(): + print "*start async test*" + torrent_ids = [] -def get_listen_port(callback): - get_core().call("get_listen_port", callback) + #callbacks: + def cb_session_state(temp_torrent_list): + print "session_state:" , temp_torrent_list + torrent_ids.extend(temp_torrent_list) -def get_available_plugins(callback): - get_core().call("get_available_plugins", callback) + def cb_torrent_status_full(status): + print "\ntorrent_status_full=", status -def get_enabled_plugins(callback): - get_core().call("get_enabled_plugins", callback) + def cb_torrent_status_paused(torrent_state): + print "(paused=%s)," % torrent_state['paused'], -def get_download_rate(callback): - get_core().call("get_download_rate", callback) + #/callbacks -def get_upload_rate(callback): - get_core().call("get_upload_rate", callback) + aclient.set_core_uri() + aclient.get_session_state(cb_session_state) -def get_num_connections(callback): - get_core().call("get_num_connections", callback) + print "force_call 1" + aclient.force_call(block=True) + print "end force_call 1:", len(torrent_ids) -def get_dht_nodes(callback): - get_core().call("get_dht_nodes", callback) -def enable_plugin(plugin): - get_core().call("enable_plugin", None, plugin) - -def disable_plugin(plugin): - get_core().call("disable_plugin", None, plugin) + #has_callback+multicall + aclient.pause_torrent(torrent_ids) + aclient.force_call(block=True) + for id in torrent_ids: + aclient.get_torrent_status(cb_torrent_status_paused, id , ['paused']) -def force_recheck(torrent_ids): - """Forces a data recheck on torrent_ids""" - get_core().call("force_recheck", None, torrent_ids) + aclient.get_torrent_status(cb_torrent_status_full, torrent_ids[0], []) -def set_torrent_trackers(torrent_id, trackers): - """Sets the torrents trackers""" - get_core().call("set_torrent_trackers", None, torrent_id, trackers) + print "force_call 2" + aclient.force_call(block=True) + print "end force-call 2" -def set_torrent_max_connections(torrent_id, value): - """Sets a torrents max number of connections""" - get_core().call("set_torrent_max_connections", None, torrent_id, value) -def set_torrent_max_upload_slots(torrent_id, value): - """Sets a torrents max number of upload slots""" - get_core().call("set_torrent_max_upload_slots", None, torrent_id, value) - -def set_torrent_max_upload_speed(torrent_id, value): - """Sets a torrents max upload speed""" - get_core().call("set_torrent_max_upload_speed", None, torrent_id, value) - -def set_torrent_max_download_speed(torrent_id, value): - """Sets a torrents max download speed""" - get_core().call("set_torrent_max_download_speed", None, torrent_id, value) -def set_torrent_private_flag(torrent_id, value): - """Sets a torrents private flag""" - get_core().call("set_torrent_private_flag", None, torrent_id, value) + print "resume:" + aclient.resume_torrent(torrent_ids) + for id in torrent_ids: + aclient.get_torrent_status(cb_torrent_status_paused, id , ['paused']) -def set_torrent_file_priorities(torrent_id, priorities): - """Sets a torrents file priorities""" - get_core().call("set_torrent_file_priorities", None, torrent_id, priorities) + aclient.force_call(block=True) -def block_ip_range(range): - """Blocks a ip range.. (start, end)""" - get_core().call("block_ip_range", None, range) +if __name__ == "__main__": + test_introspection() + test_sync() + test_async() diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index a47f4f37e..9999f3e55 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -37,7 +37,7 @@ import gtk import pkg_resources import deluge.common -import deluge.ui.client as client +from deluge.ui.client import aclient as client class AboutDialog: def __init__(self): diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 8f23becc7..6b7036ab7 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -38,7 +38,7 @@ import gettext import pkg_resources -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.component as component import deluge.ui.gtkui.listview as listview from deluge.configmanager import ConfigManager diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index e20b7d4ad..f9b228104 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -42,7 +42,7 @@ import threading import deluge.component as component import deluge.xmlrpclib as xmlrpclib import deluge.common -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.configmanager import ConfigManager from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/coreconfig.py b/deluge/ui/gtkui/coreconfig.py index 985ad3d98..ccb9658c7 100644 --- a/deluge/ui/gtkui/coreconfig.py +++ b/deluge/ui/gtkui/coreconfig.py @@ -32,7 +32,7 @@ # statement from all source files in the program, then also delete it here. import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.log import LOG as log class CoreConfig(component.Component): diff --git a/deluge/ui/gtkui/dbusinterface.py b/deluge/ui/gtkui/dbusinterface.py index ff44a70c1..08aa1a33e 100644 --- a/deluge/ui/gtkui/dbusinterface.py +++ b/deluge/ui/gtkui/dbusinterface.py @@ -46,7 +46,7 @@ elif dbus.version >= (0,80,0): DBusGMainLoop(set_as_default=True) import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index 1ad9033a6..1b37ee035 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -35,7 +35,7 @@ import gtk, gtk.glade import pkg_resources import deluge.common -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.component as component from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 368f44a52..19f6e3a80 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -44,7 +44,7 @@ import pkg_resources import signal import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client from mainwindow import MainWindow from menubar import MenuBar from toolbar import ToolBar diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 21cf379cf..a1e2b66d4 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -37,7 +37,7 @@ import gtk, gtk.glade import gobject import pkg_resources -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.component as component from deluge.configmanager import ConfigManager diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index ca408c178..31d4cefd4 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -37,7 +37,7 @@ import gtk, gtk.glade import pkg_resources import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common as common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 4a6c7fd23..c211f3574 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -33,7 +33,7 @@ import deluge.component as component import deluge.pluginmanagerbase -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.configmanager import ConfigManager from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 3f6cbd850..77786a74d 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -38,7 +38,7 @@ import pkg_resources import deluge.component as component from deluge.log import LOG as log -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common from deluge.configmanager import ConfigManager diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index 847bd4e2f..70d669838 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -38,7 +38,7 @@ import gobject import pkg_resources import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/removetorrentdialog.py b/deluge/ui/gtkui/removetorrentdialog.py index 5218b4201..8e8b88917 100644 --- a/deluge/ui/gtkui/removetorrentdialog.py +++ b/deluge/ui/gtkui/removetorrentdialog.py @@ -35,7 +35,7 @@ import gtk, gtk.glade import pkg_resources import deluge.common -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.component as component from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 4fe32554d..1f06942ac 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -32,7 +32,7 @@ # statement from all source files in the program, then also delete it here. import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.ui.signalreceiver import SignalReceiver from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index d6ea4f9c7..8fb17ac3e 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -35,7 +35,7 @@ import gtk import deluge.component as component import deluge.common -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.log import LOG as log class StatusBarItem: diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 1c78190fc..1b9f93077 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -35,7 +35,7 @@ import gtk import pkg_resources import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 09132c22f..0f5b1ef28 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -39,7 +39,7 @@ import gobject import deluge.component as component from deluge.log import LOG as log from deluge.common import TORRENT_STATE -import deluge.ui.client as client +from deluge.ui.client import aclient as client class ToolBar(component.Component): def __init__(self): diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 3711ad30c..aef516478 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -39,7 +39,7 @@ import gtk, gtk.glade import gettext import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common from deluge.log import LOG as log diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 488105789..48ffb34ba 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -44,7 +44,7 @@ import traceback import deluge.common import deluge.component as component -import deluge.ui.client as client +from deluge.ui.client import aclient as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview diff --git a/deluge/ui/null/deluge_shell.py b/deluge/ui/null/deluge_shell.py index 4794c6559..980a6b0e9 100755 --- a/deluge/ui/null/deluge_shell.py +++ b/deluge/ui/null/deluge_shell.py @@ -23,7 +23,7 @@ deluge-shell: Deluge shell. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 # USA -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.common as common import deluge.error import readline diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index ce053efc2..0997fe86a 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -37,7 +37,7 @@ import random import gobject -import deluge.ui.client as client +from deluge.ui.client import aclient as client import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import deluge.xmlrpclib as xmlrpclib From e192b38ab67d5847afe465cad9409c4993e3db82 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 22 Feb 2008 22:16:43 +0000 Subject: [PATCH 0530/1009] webui:client.py refactor --- .../ui/webui/webui_plugin/webserver_common.py | 87 ++----------------- 1 file changed, 7 insertions(+), 80 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index a91aecbba..d413cf4cc 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -43,9 +43,12 @@ import sys import base64 from md5 import md5 import inspect -from deluge.ui import client + from deluge.log import LOG as log +from deluge.ui.client import sclient as proxy +from deluge.ui.client import aclient as async_proxy + random.seed() try: @@ -107,83 +110,6 @@ CONFIG_DEFAULTS = { #/constants -#some magic to transform the async-proxy back to sync: -class BlockingMethod: - """ - helper class for SyncProxy - """ - def __init__(self, method_name): - self.method_name = method_name - self.result = None - - def __call__(self, *args, **kwargs): - func = getattr(client, self.method_name) - - if self.has_callback(func): - func(self.callback ,*args, **kwargs) - client.force_call(block=True) - return self.result - else: - func(*args, **kwargs) - client.force_call(block=True) - return - - def callback(self, result): - self.result = result - - @staticmethod - def has_callback(func): - return "callback" in inspect.getargspec(func)[0] - -class CoreMethod: - """ - plugins etc are not exposed in client.py - wrapper to make plugin methods behave like the ones in client.py - """ - def __init__(self, method_name): - self.method_name = method_name - - def __call__(self, callback, *args,**kwargs): - client.get_core().call(self.method_name, callback ,*args, **kwargs) - -class BlockingCoreMethod(CoreMethod): - """ - for syncProcy - """ - def __init__(self, method_name): - self.method_name = method_name - self.result = None - - def __call__(self,*args,**kwargs): - client.get_core().call(self.method_name, self.callback ,*args, **kwargs) - client.force_call(block=True) - return self.result - - def callback(self, result): - self.result = result - - -class SyncProxy(object): - """acts like the old synchonous proxy""" - def __getattr__(self, method_name): - if hasattr(client, method_name): - return BlockingMethod(method_name) - else: - return BlockingCoreMethod( method_name ) - -class ASyncProxy(object): - def __getattr__(self, method_name): - if hasattr(client, method_name): - return getattr(client, method_name) - else: - return CoreMethod( method_name ) - -#moving stuff from WS to module -#goal: eliminate WS, because the 05 compatiblilty is not needed anymore -proxy = SyncProxy() -async_proxy = ASyncProxy() -#log is already imported. - class Ws: """ @@ -218,8 +144,7 @@ class Ws: self.config = pickle.load(open(self.config_file)) def init_06(self, uri = 'http://localhost:58846'): - client.set_core_uri(uri) - self.async_proxy = client + proxy.set_core_uri(uri) #MONKEY PATCH, TODO->REMOVE!!! @@ -236,6 +161,8 @@ class Ws: f.write(base64.b64decode(data_b64)) f.close() log.debug("options:%s" % options) + + log.debug("TF:%s" % proxy.add_torrent_file) proxy.add_torrent_file([filename] , [options]) proxy.add_torrent_filecontent = add_torrent_filecontent From 80f11bff0f4a79d2c10d7ac23c39ff7618fa0b59 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 22 Feb 2008 22:27:21 +0000 Subject: [PATCH 0531/1009] client.has_callback->fix api/readability --- deluge/ui/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index f4cff75a4..b5ea874d3 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -227,7 +227,7 @@ class BaseClient(object): #utility: def has_callback(self, method_name): - return (method_name in self.no_callback_list) + return not (method_name in self.no_callback_list) def is_localhost(self): """Returns True if core is a localhost""" @@ -283,7 +283,7 @@ class AClient(BaseClient): async proxy """ def get_method(self, method_name): - if self.has_callback(method_name): + if not self.has_callback(method_name): def async_proxy_nocb(*args, **kwargs): return self.core.call(method_name,None, *args, **kwargs) return async_proxy_nocb From 616dd6288ab2c6cbb0f3e5e49f6886908e0a4537 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 22 Feb 2008 22:36:31 +0000 Subject: [PATCH 0532/1009] Minor cosmetic touch-ups. Removed some imports that are no longer needed. --- deluge/ui/client.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index b5ea874d3..0d0882232 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -33,9 +33,7 @@ # statement from all source files in the program, then also delete it here. import os.path -import pickle import socket -import time import gobject @@ -155,24 +153,22 @@ class BaseClient(object): wraps all calls to core/coreproxy base for AClient and SClient """ - no_callback_list = [ "add_torrent_url", "pause_all_torrents", + no_callback_list = ["add_torrent_url", "pause_all_torrents", "resume_all_torrent", "set_config", "enable_plugin", "disable_plugin", "set_torrent_trackers", "set_torrent_max_connections", "set_torrent_max_upload_slots", "set_torrent_max_upload_speed", "set_torrent_max_download_speed", "set_torrent_private_flag", "set_torrent_file_priorities", - "block_ip_range", - "remove_torrent", "pause_torrent", "move_torrent" , - "resume_torrent","force_reannounce", "force_recheck", - "deregister_client","register_client", - "add_torrent_file"] + "block_ip_range", "remove_torrent", "pause_torrent", "move_torrent", + "resume_torrent", "force_reannounce", "force_recheck", + "deregister_client", "register_client", "add_torrent_file"] def __init__(self): self.core = _core #xml-rpc introspection def list_methods(self): - registered = self.core.rpc_core.system.listMethods( ) + registered = self.core.rpc_core.system.listMethods() return sorted(registered) def methodSignature(self, method_name): @@ -244,7 +240,7 @@ class BaseClient(object): """Get the core URI""" return self.core.get_core_uri() - def set_core_uri(self, uri = 'http://localhost:58846'): + def set_core_uri(self, uri='http://localhost:58846'): """Sets the core uri""" return self.core.set_core_uri(uri) @@ -285,7 +281,7 @@ class AClient(BaseClient): def get_method(self, method_name): if not self.has_callback(method_name): def async_proxy_nocb(*args, **kwargs): - return self.core.call(method_name,None, *args, **kwargs) + return self.core.call(method_name, None, *args, **kwargs) return async_proxy_nocb else: def async_proxy(*args, **kwargs): From 2c12368fadc89cf550bd54860db0afa89d4e2ea9 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 23 Feb 2008 15:18:18 +0000 Subject: [PATCH 0533/1009] webui:add_torrent_file_binary --- deluge/ui/webui/webui_plugin/debugerror.py | 8 ++++++- deluge/ui/webui/webui_plugin/torrent_add.py | 5 +--- .../ui/webui/webui_plugin/webserver_common.py | 23 ------------------- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/debugerror.py b/deluge/ui/webui/webui_plugin/debugerror.py index eed0b2cab..5570f357e 100644 --- a/deluge/ui/webui/webui_plugin/debugerror.py +++ b/deluge/ui/webui/webui_plugin/debugerror.py @@ -173,7 +173,11 @@ $def with (exception_type, exception_value, frames, exception_message, version_i
          -Paste the contents of this text-box when you are asked for a traceback:
          +Paste the contents of this text-box when you are asked for a traceback.
          +Try to explain what you where doing, +and how you could work around the problem.
          +Don't paste without context and expect us to know what went wrong. +
          diff --git a/deluge/ui/webui/webui_plugin/torrent_add.py b/deluge/ui/webui/webui_plugin/torrent_add.py index 31da1f7d9..0eb66276b 100644 --- a/deluge/ui/webui/webui_plugin/torrent_add.py +++ b/deluge/ui/webui/webui_plugin/torrent_add.py @@ -35,7 +35,6 @@ from render import render, error_page import page_decorators as deco import lib.newforms_plus as forms import lib.webpy022 as web -import base64 class OptionsForm(forms.Form): download_location = forms.ServerFolder(_("Download Location")) @@ -120,9 +119,7 @@ class torrent_add: log.debug("add-url:options :%s" % options) utils.do_redirect() elif torrent_name: - data_b64 = base64.b64encode(torrent_data) - #b64 because of strange bug-reports related to binary data - proxy.add_torrent_filecontent(vars.torrent.filename, data_b64, options) + proxy.add_torrent_file_binary(vars.torrent.filename, torrent_data, options) log.debug("add-file:options :%s" % options) utils.do_redirect() else: diff --git a/deluge/ui/webui/webui_plugin/webserver_common.py b/deluge/ui/webui/webui_plugin/webserver_common.py index d413cf4cc..82816ec1f 100644 --- a/deluge/ui/webui/webui_plugin/webserver_common.py +++ b/deluge/ui/webui/webui_plugin/webserver_common.py @@ -146,26 +146,6 @@ class Ws: def init_06(self, uri = 'http://localhost:58846'): proxy.set_core_uri(uri) - - #MONKEY PATCH, TODO->REMOVE!!! - def add_torrent_filecontent(name , data_b64, options): - log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' % - (name , len(data_b64))) - - name = name.replace('\\','/') - name = 'deluge06_' + str(random.random()) + '_' + name.split('/')[-1] - filename = os.path.join('/tmp', name) - - log.debug('write: %s' % filename) - f = open(filename,"wb") - f.write(base64.b64decode(data_b64)) - f.close() - log.debug("options:%s" % options) - - log.debug("TF:%s" % proxy.add_torrent_file) - proxy.add_torrent_file([filename] , [options]) - - proxy.add_torrent_filecontent = add_torrent_filecontent log.debug('cfg-file %s' % self.config_file) if not os.path.exists(self.config_file): @@ -211,6 +191,3 @@ class Ws: return (m.digest() == self.config.get('pwd_md5')) ws =Ws() - - - From ada8d5641ffe8692e88252eec5a22106a5de5076 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 23 Feb 2008 15:19:47 +0000 Subject: [PATCH 0534/1009] add_torrent_file_binary --- deluge/ui/client.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 0d0882232..86543893a 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -221,6 +221,15 @@ class BaseClient(object): options = None self.get_method("add_torrent_file")(filename, fdump, options) + def add_torrent_file_binary(self, filename, fdump, options = None): + """ + Core-wrapper. + Adds 1 torrent file to the core. + Expects fdump as a bytestring (== result of f.read()). + """ + fdump_xmlrpc = xmlrpclib.Binary(fdump) + self.get_method("add_torrent_file")(filename, fdump_xmlrpc, options) + #utility: def has_callback(self, method_name): return not (method_name in self.no_callback_list) From 8f039eb0fa5ee91a7c1b51824f75d59cf7d6e96b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 24 Feb 2008 05:36:36 +0000 Subject: [PATCH 0535/1009] Fix shutdown(). --- deluge/ui/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 86543893a..9ef932ac0 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -263,7 +263,7 @@ class BaseClient(object): """Shutdown the core daemon""" try: self.core.call("shutdown", None) - self.core.force_call(block=False) + self.core.do_multicall(block=False) finally: self.set_core_uri(None) From fb85b8758058c9cc32cde1059a392ab0c960ff90 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sun, 24 Feb 2008 12:59:36 +0000 Subject: [PATCH 0536/1009] basic reconnect --- .../ui/webui/webui_plugin/page_decorators.py | 25 +++++++++++++++--- deluge/ui/webui/webui_plugin/pages.py | 13 +++++----- .../templates/deluge/connect.html | 26 +++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/connect.html diff --git a/deluge/ui/webui/webui_plugin/page_decorators.py b/deluge/ui/webui/webui_plugin/page_decorators.py index a9bfabb25..a99aebacd 100644 --- a/deluge/ui/webui/webui_plugin/page_decorators.py +++ b/deluge/ui/webui/webui_plugin/page_decorators.py @@ -3,7 +3,7 @@ decorators for html-pages. """ #relative imports from render import render -from webserver_common import ws, log +from webserver_common import ws, log, proxy from utils import * #/relative @@ -35,7 +35,7 @@ def check_session(func): vars = web.input(redir_after_login = None) ck = cookies() if ck.has_key("session_id") and ck["session_id"] in ws.SESSIONS: - return func(self, name) #ok, continue.. + return func(self, name) #check_session:ok elif vars.redir_after_login: seeother(url("/login",redir=self_url())) else: @@ -43,9 +43,26 @@ def check_session(func): deco.__name__ = func.__name__ return deco +def check_connected(func): + def deco(self, name = None): + connected = False + try: + proxy.ping() + connected = True + except: + pass + if connected: + return func(self, name) #check_connected:ok + else: + seeother("/connect") + + + deco.__name__ = func.__name__ + return deco + def deluge_page(func): - "deluge_page_noauth+check_session" - return check_session(deluge_page_noauth(func)) + "deluge_page_noauth+check_session+check connected" + return check_session(check_connected(deluge_page_noauth(func))) #combi-deco's: #decorators to use in combination with the ones above. diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 212258054..c13dd652c 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -319,12 +319,13 @@ class logout: seeother('/login') class connect: - @deco.deluge_page + @deco.check_session + @deco.deluge_page_noauth def GET(self, name): - #if proxy.connected(): - # error = _("Not Connected to a daemon") - #else: - error = None + if proxy.connected(): + error = _("Not Connected to a daemon") + else: + error = None connect_list = ["http://localhost:58846"] return render.connect(connect_list, error) @@ -338,7 +339,7 @@ class connect: else: uri = vars.uri #TODO: more error-handling - ws.init_06(uri) + proxy.set_core_uri(uri) do_redirect() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/connect.html b/deluge/ui/webui/webui_plugin/templates/deluge/connect.html new file mode 100644 index 000000000..f13eda6d2 --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/connect.html @@ -0,0 +1,26 @@ +$def with (connect_list, error) + +$:render.header(_("Connect to Daemon")) +
          +$if error: +
          $error
          + +
          +$for i, uri in enumerate(connect_list): +

          $uri

          +

          + $_("Other"): + +

          + + + + +
          +
          + +
          +$:render.footer() From d9868b0ce21dea08096f829ed475b1705d824fdc Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sun, 24 Feb 2008 13:24:29 +0000 Subject: [PATCH 0537/1009] disable queue --- deluge/ui/webui/webui_plugin/page_decorators.py | 16 ++++++++-------- deluge/ui/webui/webui_plugin/pages.py | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/page_decorators.py b/deluge/ui/webui/webui_plugin/page_decorators.py index a99aebacd..2c326f96f 100644 --- a/deluge/ui/webui/webui_plugin/page_decorators.py +++ b/deluge/ui/webui/webui_plugin/page_decorators.py @@ -19,7 +19,7 @@ def deluge_page_noauth(func): def deco(self, name = None): web.header("Content-Type", "text/html; charset=utf-8") web.header("Cache-Control", "no-cache, must-revalidate") - res = func(self, name) + res = func(self, name) #deluge_page_noauth print res deco.__name__ = func.__name__ return deco @@ -49,8 +49,8 @@ def check_connected(func): try: proxy.ping() connected = True - except: - pass + except Exception, e: + log.debug("not_connected: %s" % e) if connected: return func(self, name) #check_connected:ok else: @@ -72,7 +72,7 @@ def torrent_ids(func): for pages that allow a list of torrents. """ def deco(self, name): - return func (self, name.split(',')) + return func (self, name.split(',')) #torrent_ids deco.__name__ = func.__name__ return deco @@ -83,7 +83,7 @@ def torrent_list(func): """ def deco(self, name): torrent_list = [get_torrent_status(id) for id in name.split(',')] - return func (self, torrent_list) + return func (self, torrent_list) #torrent_list deco.__name__ = func.__name__ return deco @@ -94,7 +94,7 @@ def torrent(func): def deco(self, name): torrent_id = name.split(',')[0] torrent =get_torrent_status(torrent_id) - return func (self, torrent) + return func (self, torrent) #torrent deco.__name__ = func.__name__ return deco @@ -104,7 +104,7 @@ def auto_refreshed(func): if getcookie('auto_refresh') == '1': web.header("Refresh", "%i ; url=%s" % (int(getcookie('auto_refresh_secs',10)),self_url())) - return func(self, name) + return func(self, name) #auto_refreshed deco.__name__ = func.__name__ return deco @@ -113,7 +113,7 @@ def remote(func): def deco(self, name = None): try: log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name ) - print func(self, name) + print func(self, name) #remote except Exception, e: print 'error:%s' % e.message print '-'*20 diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index c13dd652c..81890fe47 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -224,6 +224,7 @@ class torrent_queue_up: @deco.check_session @deco.torrent_list def POST(self, torrent_list): + return error_page('Queue is broken, we know about it.') #a bit too verbose.. torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] @@ -236,6 +237,7 @@ class torrent_queue_down: @deco.check_session @deco.torrent_list def POST(self, torrent_list): + return error_page('Queue is broken, we know about it.') #a bit too verbose.. torrent_list.sort(lambda x, y : x.queue - y.queue) torrent_ids = [t.id for t in torrent_list] From ae0652227b84319c87db69622585833280b899e7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 25 Feb 2008 00:05:09 +0000 Subject: [PATCH 0538/1009] Add post_session_load() plugin hook. This change does break persistent.state. Add some queue exports and stuff to the core. --- deluge/core/core.py | 54 +++++++++++++++++++++++++++++++---- deluge/core/pluginmanager.py | 11 ++++++- deluge/core/torrent.py | 3 +- deluge/core/torrentmanager.py | 37 ++++++++++++++++++------ 4 files changed, 90 insertions(+), 15 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index ad1fc5f45..dfb75918d 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -229,12 +229,12 @@ class Core( # Start the SignalManager self.signals = SignalManager() - - # Start the TorrentManager - self.torrents = TorrentManager(self.session, self.alerts) - + # Load plugins self.plugins = PluginManager(self) + + # Start the TorrentManager + self.torrents = TorrentManager(self.session, self.alerts) # Register alert handlers self.alerts.register_handler("torrent_paused_alert", @@ -517,7 +517,47 @@ class Core( """Clears the ip filter""" self.ip_filter = lt.ip_filter() self.session.set_ip_filter(self.ip_filter) - + + ## Queueing functions ## + def export_queue_top(self, torrent_id): + log.debug("Attempting to queue %s to top", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.top(torrent_id): + self._torrent_queue_changed(torrent_id) + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + def export_queue_up(self, torrent_id): + log.debug("Attempting to queue %s to up", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.up(torrent_id): + self._torrent_queue_changed(torrent_id) + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + def export_queue_down(self, torrent_id): + log.debug("Attempting to queue %s to down", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.down(torrent_id): + self._torrent_queue_changed(torrent_id) + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) + + def export_queue_bottom(self, torrent_id): + log.debug("Attempting to queue %s to bottom", torrent_id) + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.bottom(torrent_id): + self._torrent_queue_changed(torrent_id) + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", + torrent_id) # Signals def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" @@ -553,6 +593,10 @@ class Core( """Emitted when a config value has changed""" log.debug("config_value_changed signal emitted") self.signals.emit("config_value_changed", key, value) + + def _torrent_queue_changed(self): + """Emitted when a torrent queue position is changed""" + log.debug("torrent_queue_changed signal emitted") # Config set functions def _on_config_value_change(self, key, value): diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index c34f244b4..8c80f6ba4 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -50,7 +50,8 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, # Set up the hooks dictionary self.hooks = { "post_torrent_add": [], - "post_torrent_remove": [] + "post_torrent_remove": [], + "post_session_load": [] } self.status_fields = {} @@ -139,6 +140,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, for function in self.hooks["post_torrent_remove"]: function(torrent_id) + def run_post_session_load(self): + """This hook is run after all the torrents have been loaded into the + session from the saved state. It is called prior to resuming the + torrents and they all will have a 'Paused' state.""" + log.debug("run_post_session_load") + for function in self.hooks["post_session_load"]: + function() + def get_torrent_list(self): """Returns a list of torrent_id's in the current session.""" return component.get("TorrentManager").get_torrent_list() diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 01e3e58ec..13e465277 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -252,7 +252,8 @@ class Torrent: "max_upload_speed": self.max_upload_speed, "max_download_speed": self.max_download_speed, "prioritize_first_last": self.prioritize_first_last, - "private": self.private + "private": self.private, + "queue": 0 } fns = { diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index ff4ac45f4..0429da39a 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -48,7 +48,8 @@ from deluge.core.torrent import Torrent from deluge.log import LOG as log class TorrentState: - def __init__(self, + def __init__(self, + torrent_id, filename, total_uploaded, trackers, @@ -63,6 +64,7 @@ class TorrentState: private, file_priorities ): + self.torrent_id = torrent_id self.filename = filename self.total_uploaded = total_uploaded self.trackers = trackers @@ -129,6 +131,9 @@ class TorrentManager(component.Component): self.on_alert_storage_moved) def start(self): + # Get the pluginmanager reference + self.plugins = component.get("PluginManager") + # Try to load the state from file self.load_state() @@ -151,7 +156,8 @@ class TorrentManager(component.Component): """Returns a list of torrent_ids""" return self.torrents.keys() - def add(self, filename, filedump=None, options=None, total_uploaded=0, trackers=None): + def add(self, filename, filedump=None, options=None, total_uploaded=0, + trackers=None, save_state=True): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) log.debug("options: %s", options) @@ -262,8 +268,10 @@ class TorrentManager(component.Component): # Save the torrent file torrent.save_torrent_file(filedump) - # Save the session state - self.save_state() + if save_state: + # Save the session state + self.save_state() + return torrent.torrent_id def load_torrent(self, filename): @@ -410,7 +418,8 @@ class TorrentManager(component.Component): except IOError: log.warning("Unable to load state file.") - # Try to add the torrents in the state to the session + # Try to add the torrents in the state to the session + add_paused = {} for torrent_state in state.torrents: try: options = { @@ -421,25 +430,37 @@ class TorrentManager(component.Component): "max_download_speed_per_torrent": torrent_state.max_download_speed, "prioritize_first_last_pieces": torrent_state.prioritize_first_last, "download_location": torrent_state.save_path, - "add_paused": torrent_state.paused, + "add_paused": True, "default_private": torrent_state.private, "file_priorities": torrent_state.file_priorities } + # We need to resume all non-add_paused torrents after plugin hook + add_paused[torrent_state.torrent_id] = torrent_state.paused self.add( torrent_state.filename, options=options, total_uploaded=torrent_state.total_uploaded, - trackers=torrent_state.trackers) + trackers=torrent_state.trackers, + save_state=False) except AttributeError, e: log.error("Torrent state file is either corrupt or incompatible!") break - + + # Run the post_session_load plugin hooks + self.plugins.run_post_session_load() + + # Resume any torrents that need to be resumed + for key in add_paused.keys(): + if add_paused[key] == True: + self.torrents[key].handle.resume() + def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" state = TorrentManagerState() # Create the state for each Torrent and append to the list for torrent in self.torrents.values(): torrent_state = TorrentState( + torrent.torrent_id, torrent.filename, torrent.get_status(["total_uploaded"])["total_uploaded"], torrent.trackers, From a39b8baa7281bb7f54d025241c3917ab22003548 Mon Sep 17 00:00:00 2001 From: Sadrul Habib Chowdhury Date: Mon, 25 Feb 2008 01:22:46 +0000 Subject: [PATCH 0539/1009] Ensure int-ness for max-connections and upload-slots of torrents. --- deluge/core/torrent.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 13e465277..4a8e6a32e 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -110,12 +110,12 @@ class Torrent: self.tracker_status = status def set_max_connections(self, max_connections): - self.max_connections = max_connections - self.handle.set_max_connections(max_connections) + self.max_connections = int(max_connections) + self.handle.set_max_connections(self.max_connections) def set_max_upload_slots(self, max_slots): - self.max_upload_slots = max_slots - self.handle.set_max_uploads(max_slots) + self.max_upload_slots = int(max_slots) + self.handle.set_max_uploads(self.max_upload_slots) def set_max_upload_speed(self, m_up_speed): self.max_upload_speed = m_up_speed From 4513531ef48d085c05e6eab36f58d9b22c713b0f Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 25 Feb 2008 02:49:30 +0000 Subject: [PATCH 0540/1009] Attempt to call 'apply_prefs()' in enabled plugins when the user clicks OK or Apply in preferences. This is designed to allow plugins to save their preferences. --- deluge/ui/gtkui/pluginmanager.py | 12 +++++++++++- deluge/ui/gtkui/preferences.py | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index c211f3574..1741af071 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -60,7 +60,17 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, # Enable the plugins that are enabled in the config and core self.enable_plugins() - + + def apply_prefs(self): + """Attempts to call 'apply_prefs()' in each enabled plugin. This is + called when a user clicks OK or Apply in the preferences window and is + designed to give plugins the opportunity to save their prefs.""" + for key in self.plugins.keys(): + try: + self.plugins[key].apply_prefs() + except AttributeError: + pass + ## Plugin functions.. will likely move to own class.. def add_torrentview_text_column(self, *args, **kwargs): diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 77786a74d..740c28a84 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -529,12 +529,14 @@ class Preferences(component.Component): def on_button_ok_clicked(self, data): log.debug("on_button_ok_clicked") self.set_config() + component.get("PluginManager").apply_prefs() self.hide() return True def on_button_apply_clicked(self, data): log.debug("on_button_apply_clicked") self.set_config() + component.get("PluginManager").apply_prefs() def on_button_cancel_clicked(self, data): log.debug("on_button_cancel_clicked") From a3522022477eeb78f72539c348dc19210d271290 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 25 Feb 2008 06:52:56 +0000 Subject: [PATCH 0541/1009] Account for large files in the files list. --- deluge/ui/gtkui/addtorrentdialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 6b7036ab7..e416619ae 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -35,6 +35,7 @@ import pygtk pygtk.require('2.0') import gtk, gtk.glade import gettext +import gobject import pkg_resources @@ -68,7 +69,7 @@ class AddTorrentDialog: }) self.torrent_liststore = gtk.ListStore(str, str, str) - self.files_liststore = gtk.ListStore(bool, str, int) + self.files_liststore = gtk.ListStore(bool, str, gobject.TYPE_UINT64) # Holds the files info self.files = {} self.infos = {} From 9c0dc5f9e302118a9865cb56b5580619bd7d8734 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 25 Feb 2008 08:52:57 +0000 Subject: [PATCH 0542/1009] lt sync 2828 --- libtorrent/include/libtorrent/storage.hpp | 3 +++ libtorrent/src/enum_net.cpp | 2 ++ libtorrent/src/session_impl.cpp | 1 - libtorrent/src/storage.cpp | 11 +++++++++-- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index a3f97b589..a7b5e37a4 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -376,6 +376,9 @@ namespace libtorrent // the piece_manager destructs. This is because // the torrent_info object is owned by the torrent. boost::shared_ptr m_torrent; +#ifndef NDEBUG + bool m_resume_data_verified; +#endif }; } diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index 3eb8df837..5431b7c53 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -45,6 +45,8 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include "libtorrent/enum_net.hpp" +// for is_loopback and is_any +#include "libtorrent/broadcast_socket.hpp" namespace libtorrent { diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index a78507af0..80e7ee7bc 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -1855,7 +1855,6 @@ namespace detail { tracker_request req = t.generate_tracker_request(); TORRENT_ASSERT(req.event == tracker_request::stopped); - TORRENT_ASSERT(!m_listen_sockets.empty()); req.listen_port = 0; if (!m_listen_sockets.empty()) req.listen_port = m_listen_sockets.front().external_port; diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 810626f52..69701f734 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -581,8 +581,9 @@ namespace libtorrent // the resume data says we have the entire torrent // make sure the file sizes are the right ones for (torrent_info::file_iterator i = m_info->begin_files(true) - , end(m_info->end_files(true)); i != end; ++i, ++fs) + , end(m_info->end_files(true)); i != end; ++i, ++fs) { + std::cerr << "filesize: " << i->size << std::endl; if (i->size != fs->first) { error = "file size for '" + i->path.native_file_string() @@ -591,7 +592,6 @@ namespace libtorrent return false; } } - return true; } return match_filesizes(*m_info, m_save_path, file_sizes @@ -998,6 +998,9 @@ namespace libtorrent , m_io_thread(io) , m_torrent(torrent) { +#ifndef NDEBUG + m_resume_data_verified = false; +#endif } piece_manager::~piece_manager() @@ -1011,6 +1014,9 @@ namespace libtorrent bool piece_manager::verify_resume_data(entry& rd, std::string& error) { +#ifndef NDEBUG + m_resume_data_verified = true; +#endif return m_storage->verify_resume_data(rd, error); } @@ -1473,6 +1479,7 @@ namespace libtorrent if (!data.piece_map.empty() && int(data.piece_map.size()) <= m_info->num_pieces()) { + TORRENT_ASSERT(m_resume_data_verified); for (int i = 0; i < (int)data.piece_map.size(); ++i) { m_slot_to_piece[i] = data.piece_map[i]; From d1b8075fe6822b966faccb9d0eb547af97fabe76 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 25 Feb 2008 17:44:32 +0000 Subject: [PATCH 0543/1009] webui:torrent_list:use sclient.get_torrents_status instead of async --- .../webui/webui_plugin/templates/deluge/config.html | 3 --- deluge/ui/webui/webui_plugin/utils.py | 12 +++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/config.html b/deluge/ui/webui/webui_plugin/templates/deluge/config.html index 4ee2d75cc..cbc35956e 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/config.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/config.html @@ -14,7 +14,6 @@ $for group in groups: $else:
        • $pages[page].title
        • -
          @@ -31,8 +30,6 @@ $if error: - -
          diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index ac2e43866..adac995c7 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -232,14 +232,12 @@ def get_torrent_status(torrent_id): def get_torrent_list(): """ - uses async. + returns a list of storified-torrent-dicts. """ - torrent_ids = proxy.get_session_state() #Syc-api. - torrent_dict = {} - for id in torrent_ids: - async_proxy.get_torrent_status(dict_cb(id,torrent_dict), id, - TORRENT_KEYS) - async_proxy.force_call(block=True) + torrent_ids = proxy.get_session_state() + + torrent_dict = proxy.get_torrents_status(torrent_ids, TORRENT_KEYS) + return [enhance_torrent_status(id, status) for id, status in torrent_dict.iteritems()] From 79b9eda35107f21ef9e8e2c65d3bb28c035d0f60 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 26 Feb 2008 00:22:54 +0000 Subject: [PATCH 0544/1009] fix config template --- .../webui/webui_plugin/templates/deluge/config.html | 2 +- deluge/ui/webui/webui_plugin/utils.py | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/config.html b/deluge/ui/webui/webui_plugin/templates/deluge/config.html index cbc35956e..576f503ab 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/config.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/config.html @@ -14,7 +14,7 @@ $for group in groups: $else:
        • $pages[page].title
        • - +

          $form.group / $form.title

          diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index adac995c7..5ccb9d841 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -124,7 +124,6 @@ def get_stats(): ,"max_connections_global") async_proxy.get_dht_nodes(dict_cb('dht_nodes',stats)) - async_proxy.force_call(block=True) #log.debug(str(stats)) @@ -162,7 +161,6 @@ def enhance_torrent_status(torrent_id,status): status[key] = 0 #log.warning('torrent_status:None key in status:%s' % key) - if status.tracker == 0: #0.6 does not raise a decent error on non-existing torrent. raise UnknownTorrentError(torrent_id) @@ -225,19 +223,14 @@ def get_torrent_status(torrent_id): enhance proxy.get_torrent_status with some extra data """ status = proxy.get_torrent_status(torrent_id,TORRENT_KEYS) - return enhance_torrent_status(torrent_id, status) - - def get_torrent_list(): """ returns a list of storified-torrent-dicts. """ - torrent_ids = proxy.get_session_state() - - torrent_dict = proxy.get_torrents_status(torrent_ids, TORRENT_KEYS) - + torrent_dict = proxy.get_torrents_status( + proxy.get_session_state(), TORRENT_KEYS) return [enhance_torrent_status(id, status) for id, status in torrent_dict.iteritems()] From a7b12cc1591e71336fed7c96190885bf0dc4df2a Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 26 Feb 2008 00:33:27 +0000 Subject: [PATCH 0545/1009] no exception on invalid sort --- deluge/ui/webui/webui_plugin/pages.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 81890fe47..926fdefee 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -144,7 +144,11 @@ class index: #sorting: if vars.sort: - torrent_list.sort(key=attrgetter(vars.sort)) + try: + torrent_list.sort(key=attrgetter(vars.sort)) + except: + log.debug('Sorting Failed') + if vars.order == 'up': torrent_list = reversed(torrent_list) From 3103a69754e16381fbcf50282ab9052918ac5a96 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 26 Feb 2008 05:27:12 +0000 Subject: [PATCH 0546/1009] Update Test plugin to work properly. --- deluge/plugins/testp/testp/core.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deluge/plugins/testp/testp/core.py b/deluge/plugins/testp/testp/core.py index b49b6a84d..9660ccd91 100644 --- a/deluge/plugins/testp/testp/core.py +++ b/deluge/plugins/testp/testp/core.py @@ -32,13 +32,12 @@ # statement from all source files in the program, then also delete it here. from deluge.log import LOG as log +from deluge.plugins.corepluginbase import CorePluginBase -class Core: - def __init__(self, plugin_api): - pass - +class Core(CorePluginBase): def enable(self): pass def disable(self): pass + From 28f0a0b9f83f8d5b3b3905565ac71c1db7689abc Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 26 Feb 2008 05:27:32 +0000 Subject: [PATCH 0547/1009] Catch possible exception on shutdown. --- deluge/ui/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 9ef932ac0..486815dec 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -95,7 +95,7 @@ class CoreProxy(gobject.GObject): callback(ret) except: pass - except socket.error, e: + except (socket.error, xmlrpc.ProtocolError), e: log.warning("Could not contact daemon: %s", e) self.set_core_uri(None) finally: From 8c44dd40fa7a93a30936ae3257adbe2e4868af1e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 26 Feb 2008 05:28:29 +0000 Subject: [PATCH 0548/1009] Make PluginManager start before TorrentManager. --- deluge/core/torrentmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 0429da39a..29bec80ea 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -91,7 +91,7 @@ class TorrentManager(component.Component): session for use on restart.""" def __init__(self, session, alerts): - component.Component.__init__(self, "TorrentManager") + component.Component.__init__(self, "TorrentManager", depend=["PluginManager"]) log.debug("TorrentManager init..") # Set the libtorrent session self.session = session From 06ffa8c62805f85a1e7956ac1e407836c960ad42 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 26 Feb 2008 05:31:05 +0000 Subject: [PATCH 0549/1009] Fix commit 2866 --- deluge/ui/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 486815dec..88ed5618f 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -95,7 +95,7 @@ class CoreProxy(gobject.GObject): callback(ret) except: pass - except (socket.error, xmlrpc.ProtocolError), e: + except (socket.error, xmlrpclib.ProtocolError), e: log.warning("Could not contact daemon: %s", e) self.set_core_uri(None) finally: From 8ee529d2298ce83ed6e8f2cced847e7b812fccd3 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 26 Feb 2008 17:10:44 +0000 Subject: [PATCH 0550/1009] Add 'on_apply_prefs' plugin hook. --- deluge/ui/gtkui/pluginmanager.py | 37 +++++++++++++++++++++++--------- deluge/ui/gtkui/preferences.py | 4 ++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 1741af071..bf1dd39dc 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -44,6 +44,24 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, self.config = ConfigManager("gtkui.conf") deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "gtkui.conf", "deluge.plugin.gtkui") + + self.hooks = { + "on_apply_prefs": [] + } + + def register_hook(self, hook, function): + """Register a hook function with the plugin manager""" + try: + self.hooks[hook].append(function) + except KeyError: + log.warning("Plugin attempting to register invalid hook.") + + def deregister_hook(self, hook, function): + """Deregisters a hook function""" + try: + self.hooks[hook].remove(function) + except: + log.warning("Unable to deregister hook %s", hook) def start(self): """Start the plugin manager""" @@ -60,17 +78,16 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, # Enable the plugins that are enabled in the config and core self.enable_plugins() - - def apply_prefs(self): - """Attempts to call 'apply_prefs()' in each enabled plugin. This is - called when a user clicks OK or Apply in the preferences window and is - designed to give plugins the opportunity to save their prefs.""" - for key in self.plugins.keys(): - try: - self.plugins[key].apply_prefs() - except AttributeError: - pass + ## Hook functions + def run_on_apply_prefs(self): + """This hook is run after the user clicks Apply or OK in the preferences + dialog. + """ + log.debug("run_on_apply_prefs") + for function in self.hooks["on_apply_prefs"]: + function() + ## Plugin functions.. will likely move to own class.. def add_torrentview_text_column(self, *args, **kwargs): diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 740c28a84..5d5e6dc0d 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -529,14 +529,14 @@ class Preferences(component.Component): def on_button_ok_clicked(self, data): log.debug("on_button_ok_clicked") self.set_config() - component.get("PluginManager").apply_prefs() + component.get("PluginManager").run_on_apply_prefs() self.hide() return True def on_button_apply_clicked(self, data): log.debug("on_button_apply_clicked") self.set_config() - component.get("PluginManager").apply_prefs() + component.get("PluginManager").run_on_apply_prefs() def on_button_cancel_clicked(self, data): log.debug("on_button_cancel_clicked") From c5cb204344b85b72ed43656d02c0e2dd694f3b7e Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 26 Feb 2008 17:59:24 +0000 Subject: [PATCH 0551/1009] minimal admin toolbar --- deluge/ui/webui/webui_plugin/templates/deluge/config.html | 1 + deluge/ui/webui/webui_plugin/templates/deluge/connect.html | 3 +++ 2 files changed, 4 insertions(+) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/config.html b/deluge/ui/webui/webui_plugin/templates/deluge/config.html index 576f503ab..249f2f61a 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/config.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/config.html @@ -2,6 +2,7 @@ $def with (groups, pages, form, selected, message, error) $:render.header(_("Config")) +$:render.admin_toolbar('config')
          $for group in groups: diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/connect.html b/deluge/ui/webui/webui_plugin/templates/deluge/connect.html index f13eda6d2..9c29a19a0 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/connect.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/connect.html @@ -1,6 +1,9 @@ $def with (connect_list, error) $:render.header(_("Connect to Daemon")) + +$:render.admin_toolbar('connect') +
          $if error:
          $error
          From 26b8fcf086106f8b3f405c891398f45201fac56f Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 26 Feb 2008 17:59:46 +0000 Subject: [PATCH 0552/1009] minimal admin toolbar2 --- .../webui_plugin/templates/deluge/admin_toolbar.html | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html b/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html new file mode 100644 index 000000000..cec7fb69a --- /dev/null +++ b/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html @@ -0,0 +1,8 @@ +$def with (active_tab) +
          + Config + + Connect +
          + From f1808a0cc37599d6bfdb12c5848c57051fa8ad18 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 27 Feb 2008 07:43:47 +0000 Subject: [PATCH 0553/1009] Add pausing and resuming of components. This stops the update() timer for the component and restarts it upon resume. --- deluge/component.py | 52 +++++++++++++++++++++++++++++++++-- deluge/ui/gtkui/mainwindow.py | 16 +++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/deluge/component.py b/deluge/component.py index dce2f2997..720ba82d2 100644 --- a/deluge/component.py +++ b/deluge/component.py @@ -36,7 +36,8 @@ from deluge.log import LOG as log COMPONENT_STATE = [ "Stopped", - "Started" + "Started", + "Paused" ] class Component: @@ -67,7 +68,17 @@ class Component: gobject.source_remove(self._timer) except: pass - + + def _pause(self): + self._state = COMPONENT_STATE.index("Paused") + try: + gobject.source_remove(self._timer) + except: + pass + + def _resume(self): + self._start() + def shutdown(self): pass @@ -126,7 +137,28 @@ class ComponentRegistry: log.debug("Stopping component %s..", component) self.components[component].stop() self.components[component]._stop() - + + def pause(self): + """Pauses all components. Stops calling update()""" + for component in self.components.keys(): + self.pause_component(component) + + def pause_component(self, component): + if self.components[component].get_state() not in \ + [COMPONENT_STATE.index("Paused"), COMPONENT_STATE.index("Stopped")]: + log.debug("Pausing component %s..", component) + self.components[component]._pause() + + def resume(self): + """Resumes all components. Starts calling update()""" + for component in self.components.keys(): + self.resume_component(component) + + def resume_component(self, component): + if self.components[component].get_state() == COMPONENT_STATE.index("Paused"): + log.debug("Resuming component %s..", component) + self.components[component]._resume() + def update(self): """Updates all components""" for component in self.components.keys(): @@ -170,6 +202,20 @@ def stop(component=None): else: _ComponentRegistry.stop_component(component) +def pause(component=None): + """Pauses all or specificed components""" + if component == None: + _ComponentRegistry.pause() + else: + _ComponentRegistry.pause_component(component) + +def resume(component=None): + """Resumes all or specificed components""" + if component == None: + _ComponentRegistry.resume() + else: + _ComponentRegistry.resume_component(component) + def update(): """Updates all components""" _ComponentRegistry.update() diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index a1e2b66d4..c92c5ff22 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -78,8 +78,8 @@ class MainWindow(component.Component): def show(self): try: - component.start("TorrentView") - component.start("StatusBar") + component.resume("TorrentView") + component.resume("StatusBar") except: pass @@ -88,8 +88,8 @@ class MainWindow(component.Component): self.window.show() def hide(self): - component.stop("TorrentView") - component.stop("StatusBar") + component.pause("TorrentView") + component.pause("StatusBar") self.window.hide() def present(self): @@ -135,14 +135,14 @@ class MainWindow(component.Component): if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: log.debug("MainWindow is minimized..") - component.stop("TorrentView") - component.stop("StatusBar") + component.pause("TorrentView") + component.pause("StatusBar") self.is_minimized = True else: log.debug("MainWindow is not minimized..") try: - component.start("TorrentView") - component.start("StatusBar") + component.resume("TorrentView") + component.resume("StatusBar") except: pass self.is_minimized = False From 53ebbe011cf0d09ac12da9018e3bd3ceb3cb7bb5 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 27 Feb 2008 08:09:47 +0000 Subject: [PATCH 0554/1009] Updates to the test plugin. --- deluge/plugins/testp/testp/__init__.py | 10 +++++----- deluge/plugins/testp/testp/gtkui.py | 12 ++++++++---- deluge/plugins/testp/testp/ui.py | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/deluge/plugins/testp/testp/__init__.py b/deluge/plugins/testp/testp/__init__.py index 0aeca2568..37219e170 100644 --- a/deluge/plugins/testp/testp/__init__.py +++ b/deluge/plugins/testp/testp/__init__.py @@ -41,14 +41,14 @@ class CorePlugin(PluginBase): try: from core import Core self.plugin = Core(plugin_api, plugin_name) - except: - pass + except Exception, e: + log.debug("Did not load a Core plugin: %s", e) class GtkUIPlugin(PluginBase): def __init__(self, plugin_api, plugin_name): # Load the GtkUI portion of the plugin try: from gtkui import GtkUI - self.plugin = GtkUI() - except: - pass + self.plugin = GtkUI(plugin_api, plugin_name) + except Exception, e: + log.debug("Did not load a GtkUI plugin: %s", e) diff --git a/deluge/plugins/testp/testp/gtkui.py b/deluge/plugins/testp/testp/gtkui.py index 9beb6e76d..220a4ef5b 100644 --- a/deluge/plugins/testp/testp/gtkui.py +++ b/deluge/plugins/testp/testp/gtkui.py @@ -35,9 +35,13 @@ from deluge.log import LOG as log import ui class GtkUI(ui.UI): - def __init__(self): - ui.UI.__init__(self) - log.debug("gtkui plugin initialized..") + def __init__(self, plugin_api, plugin_name): + log.debug("Calling UI init") + # Call UI constructor + ui.UI.__init__(self, plugin_api, plugin_name) + log.debug("test plugin initialized..") + self.plugin.register_hook("on_apply_prefs", self.apply_prefs) - + def apply_prefs(self): + log.debug("applying prefs in test plugin!!") diff --git a/deluge/plugins/testp/testp/ui.py b/deluge/plugins/testp/testp/ui.py index babd15a3e..92ea56ecf 100644 --- a/deluge/plugins/testp/testp/ui.py +++ b/deluge/plugins/testp/testp/ui.py @@ -34,8 +34,8 @@ from deluge.log import LOG as log class UI: - def __init__(self): - pass + def __init__(self, plugin_api, plugin_name): + self.plugin = plugin_api def enable(self): pass From b72098e561eb7f686672e87837ddca86b5709e9a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 27 Feb 2008 08:10:14 +0000 Subject: [PATCH 0555/1009] Start of work migrating the Queue plugin into core. --- deluge/ui/gtkui/glade/main_window.glade | 1157 +++++++++-------- .../ui/gtkui/glade/preferences_dialog.glade | 1058 ++++++++++----- deluge/ui/gtkui/glade/torrent_menu.glade | 65 + deluge/ui/gtkui/menubar.py | 3 + deluge/ui/gtkui/torrentview.py | 1 + 5 files changed, 1366 insertions(+), 918 deletions(-) diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 9704c58a4..7211ddb76 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -323,6 +323,41 @@ False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue Torrent Up + Queue Up + gtk-go-up + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue Torrent Down + Queue Down + gtk-go-down + + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + + True @@ -452,6 +487,298 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_CHAR + True + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + True + True + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + True + PANGO_WRAP_CHAR + True + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_CHAR + True + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_CHAR + True + + + 1 + 2 + + + + + + True + 0 + True + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + True + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -484,277 +811,18 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - + True 0 + True + PANGO_WRAP_WORD_CHAR 3 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 4 5 + @@ -781,18 +849,277 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 - 4 - 5 - + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 @@ -822,298 +1149,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - True - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - True - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_CHAR - True - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_CHAR - True - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - True - PANGO_WRAP_CHAR - True - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - True - True - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_CHAR - True - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 78ecbeb5e..54f6a1c15 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -298,27 +298,17 @@ 2 2 - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - - - 1 - 2 - - - - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrent files that are placed in this folder. + Client Folder: + 0 + True - 1 - 2 - 1 - 2 + GTK_FILL @@ -337,17 +327,27 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrent files that are placed in this folder. - Client Folder: - 0 - True - GTK_FILL + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + 1 + 2 @@ -1118,71 +1118,40 @@ Disabled 2 15 - + True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: + True + The maximum upload slots for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + 1 + 2 1 2 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload speed for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 - True + 1 True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 + 3 + 4 GTK_FILL @@ -1207,43 +1176,74 @@ Disabled - + True - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - 1 - True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): - 1 - 2 - 3 - 4 + 2 + 3 GTK_FILL - + True True - The maximum upload slots for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 True True + GTK_UPDATE_IF_VALID 1 2 + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: + + 1 2 GTK_FILL + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + @@ -1287,18 +1287,85 @@ Disabled 2 15 - + True True - The maximum number of connections per torrent. Set -1 for unlimited. + The maximum upload slots per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 + 1 + True True 1 2 + 1 + 2 + GTK_FILL + + + + + True + True + The maximum number of connections per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + True + True + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + 3 4 GTK_FILL @@ -1323,87 +1390,20 @@ Disabled - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - True + 1 True 1 2 - GTK_FILL - - - - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True - - - 1 - 2 - 1 - 2 + 3 + 4 GTK_FILL @@ -1439,9 +1439,15 @@ Disabled - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + page 8 + tab + 2 + False @@ -1652,15 +1658,33 @@ Disabled 2 10 - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL @@ -1690,38 +1714,20 @@ Thunar - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - @@ -1803,6 +1809,179 @@ Thunar tab + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Daemon</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon port: + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 0 0 65535 1 10 10 + + + False + False + 1 + + + + + False + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Port</b> + True + + + label_item + + + + + False + False + 5 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Allow Remote Connections + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Connections</b> + True + + + label_item + + + + + False + False + 5 + 3 + + + + + + + + + 5 + + + + + + tab + + True @@ -1992,179 +2171,6 @@ Thunar tab - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0.05000000074505806 - 10 - <i><b><big>Daemon</big></b></i> - True - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Daemon port: - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 0 0 65535 1 10 10 - - - False - False - 1 - - - - - False - False - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Port</b> - True - - - label_item - - - - - False - False - 5 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 10 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Allow Remote Connections - 0 - True - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Connections</b> - True - - - label_item - - - - - False - False - 5 - 3 - - - - - - - - - 5 - - - - - - tab - - True @@ -2257,6 +2263,344 @@ Thunar tab + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Queue</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue new torrents to top + 0 + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>General</b> + True + + + label_item + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 2 + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active torrents: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active seeding: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active downloading: + + + 2 + 3 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + 1 + 2 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + 2 + 3 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Active Torrents</b> + True + + + label_item + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue newly finished torrents to bottom + 0 + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Stop seeding when share ratio reaches: + 0 + True + + + False + False + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 1 0.5 100 0.10000000000000001 1 1 + 2 + True + + + False + False + 1 + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remove torrent when share ratio reached + 0 + True + + + + + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Seeding</b> + True + + + label_item + + + + + False + False + 2 + + + + + 2 + + + + + 7 + + + + + + tab + + True diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index 148c078e9..efe8e75f3 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -86,6 +86,27 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Queue + True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-sort-descending + 1 + + + + + + + True + + True @@ -294,4 +315,48 @@ + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-top + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-up + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-down + True + True + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-goto-bottom + True + True + + + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 31d4cefd4..3a75f30a8 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -56,6 +56,9 @@ class MenuBar(component.Component): self.torrentmenu_glade.get_widget("menuitem_remove").set_submenu( self.torrentmenu_glade.get_widget("remove_torrent_menu")) + self.torrentmenu_glade.get_widget("menuitem_queue").set_submenu( + self.torrentmenu_glade.get_widget("queue_torrent_menu")) + # Attach options torrent menu self.torrentmenu_glade.get_widget("menuitem_options").set_submenu( self.torrentmenu_glade.get_widget("options_torrent_menu")) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 48ffb34ba..959f4c171 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -116,6 +116,7 @@ class TorrentView(listview.ListView, component.Component): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) self.add_bool_column("filter", hidden=True) + self.add_text_column("#", col_type=int, status_field=["queue"]) self.add_texticon_column(_("Name"), status_field=["state", "name"], function=cell_data_statusicon) self.add_func_column(_("Size"), From 51dccc21aeacbc410cbf57a7c0a91162e51ec9d2 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Thu, 28 Feb 2008 06:45:21 +0000 Subject: [PATCH 0556/1009] lt sync 2039 --- .../include/libtorrent/aux_/session_impl.hpp | 2 + .../include/libtorrent/bandwidth_manager.hpp | 4 ++ .../include/libtorrent/broadcast_socket.hpp | 4 ++ .../include/libtorrent/disk_io_thread.hpp | 3 +- libtorrent/src/broadcast_socket.cpp | 38 ++++++++++++++++++ libtorrent/src/bt_peer_connection.cpp | 21 ++++++++++ libtorrent/src/disk_io_thread.cpp | 8 ++-- libtorrent/src/peer_connection.cpp | 40 +++++++++++++++---- libtorrent/src/policy.cpp | 35 +++++++++++++--- 9 files changed, 137 insertions(+), 18 deletions(-) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index c9b6cb218..03e9cb619 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -372,6 +372,8 @@ namespace libtorrent void free_buffer(char* buf, int size); void free_disk_buffer(char* buf); + address m_external_address; + // private: void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index c3b4f5942..19f0cc790 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -117,6 +117,9 @@ struct bandwidth_manager void close() { m_abort = true; + m_queue.clear(); + m_history.clear(); + m_current_quota = 0; m_history_timer.cancel(); } @@ -153,6 +156,7 @@ struct bandwidth_manager { mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; + if (m_abort) return; TORRENT_ASSERT(blk > 0); TORRENT_ASSERT(!peer->ignore_bandwidth_limits()); diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index df656f4ea..a4c448d82 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -45,6 +45,10 @@ namespace libtorrent bool is_loopback(address const& addr); bool is_multicast(address const& addr); bool is_any(address const& addr); + int cidr_distance(address const& a1, address const& a2); + + int common_bits(unsigned char const* b1 + , unsigned char const* b2, int n); address guess_local_address(asio::io_service&); diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index 1d9d88534..9db30a58c 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "libtorrent/config.hpp" namespace libtorrent @@ -133,7 +134,7 @@ namespace libtorrent mutable mutex_t m_mutex; boost::condition m_signal; bool m_abort; - std::deque m_jobs; + std::list m_jobs; size_type m_queue_buffer_size; // memory pool for read and write operations diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index 16f0a0728..c7b7e71c8 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -99,6 +99,44 @@ namespace libtorrent return ret; } + // count the length of the common bit prefix + int common_bits(unsigned char const* b1 + , unsigned char const* b2, int n) + { + for (int i = 0; i < n; ++i, ++b1, ++b2) + { + unsigned char a = *b1 ^ *b2; + if (a == 0) continue; + int ret = i * 8 + 8; + for (; a > 0; a >>= 1) --ret; + return ret; + } + return n * 8; + } + + // returns the number of bits in that differ from the right + // between the addresses. + int cidr_distance(address const& a1, address const& a2) + { + if (a1.is_v4() == a2.is_v4()) + { + // both are v4 + address_v4::bytes_type b1 = a1.to_v4().to_bytes(); + address_v4::bytes_type b2 = a2.to_v4().to_bytes(); + return address_v4::bytes_type::static_size * 8 + - common_bits(b1.c_array(), b2.c_array(), b1.size()); + } + + address_v6::bytes_type b1; + address_v6::bytes_type b2; + if (a1.is_v4()) b1 = address_v6::v4_mapped(a1.to_v4()).to_bytes(); + else b1 = a1.to_v6().to_bytes(); + if (a2.is_v4()) b2 = address_v6::v4_mapped(a2.to_v4()).to_bytes(); + else b2 = a2.to_v6().to_bytes(); + return address_v6::bytes_type::static_size * 8 + - common_bits(b1.c_array(), b2.c_array(), b1.size()); + } + broadcast_socket::broadcast_socket(asio::io_service& ios , udp::endpoint const& multicast_endpoint , receive_handler_t const& handler diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 1fbc3b8fa..b1884496b 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -1228,6 +1228,27 @@ namespace libtorrent if (m_max_out_request_queue < 1) m_max_out_request_queue = 1; } + + if (entry* myip = root.find_key("yourip")) + { + // TODO: don't trust this blindly + if (myip->type() == entry::string_t) + { + std::string const& my_ip = myip->string().c_str(); + if (my_ip.size() == address_v4::bytes_type::static_size) + { + address_v4::bytes_type bytes; + std::copy(my_ip.begin(), my_ip.end(), bytes.begin()); + m_ses.m_external_address = address_v4(bytes); + } + else if (my_ip.size() == address_v6::bytes_type::static_size) + { + address_v6::bytes_type bytes; + std::copy(my_ip.begin(), my_ip.end(), bytes.begin()); + m_ses.m_external_address = address_v6(bytes); + } + } + } } bool bt_peer_connection::dispatch_message(int received) diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index e1cfbfe5f..4c44ca307 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -70,7 +70,7 @@ namespace libtorrent , int action, int piece) const { mutex_t::scoped_lock l(m_mutex); - for (std::deque::const_iterator i = m_jobs.begin(); + for (std::list::const_iterator i = m_jobs.begin(); i != m_jobs.end(); ++i) { if (i->storage != s) @@ -105,7 +105,7 @@ namespace libtorrent { mutex_t::scoped_lock l(m_mutex); // read jobs are aborted, write and move jobs are syncronized - for (std::deque::iterator i = m_jobs.begin(); + for (std::list::iterator i = m_jobs.begin(); i != m_jobs.end();) { if (i->storage != s) @@ -158,7 +158,7 @@ namespace libtorrent TORRENT_ASSERT(j.storage); mutex_t::scoped_lock l(m_mutex); - std::deque::reverse_iterator i = m_jobs.rbegin(); + std::list::reverse_iterator i = m_jobs.rbegin(); if (j.action == disk_io_job::read) { // when we're reading, we may not skip @@ -201,7 +201,7 @@ namespace libtorrent if (i == m_jobs.rend() && (m_jobs.empty() || j.priority <= m_jobs.back().priority)) i = m_jobs.rbegin(); - std::deque::iterator k = m_jobs.insert(i.base(), j); + std::list::iterator k = m_jobs.insert(i.base(), j); k->callback.swap(const_cast&>(f)); if (j.action == disk_io_job::write) m_queue_buffer_size += j.buffer_size; diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 8afc894de..1f977f4fb 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -1393,6 +1393,8 @@ namespace libtorrent #ifndef NDEBUG t->check_invariant(); #endif + request_a_block(*t, *this); + send_block_requests(); } void peer_connection::on_disk_write_complete(int ret, disk_io_job const& j @@ -2485,7 +2487,7 @@ namespace libtorrent if (m_bandwidth_limit[upload_channel].max_assignable() > 0) { #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "req bandwidth [ " << upload_channel << " ]\n"; + (*m_logger) << time_now_string() << " *** REQUEST_BANDWIDTH [ upload ]\n"; #endif TORRENT_ASSERT(!m_writing); @@ -2497,7 +2499,18 @@ namespace libtorrent return; } - if (!can_write()) return; + if (!can_write()) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** CANNOT WRITE [" + " quota: " << m_bandwidth_limit[download_channel].quota_left() << + " ignore: " << (m_ignore_bandwidth_limits?"yes":"no") << + " buf: " << m_send_buffer.size() << + " connecting: " << (m_connecting?"yes":"no") << + " ]\n"; +#endif + return; + } TORRENT_ASSERT(!m_writing); @@ -2512,7 +2525,7 @@ namespace libtorrent TORRENT_ASSERT(amount_to_send > 0); #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "async_write " << amount_to_send << " bytes\n"; + (*m_logger) << time_now_string() << " *** ASYNC_WRITE [ bytes: " << amount_to_send << " ]\n"; #endif std::list const& vec = m_send_buffer.build_iovec(amount_to_send); m_socket->async_write_some(vec, bind(&peer_connection::on_send_data, self(), _1, _2)); @@ -2528,7 +2541,7 @@ namespace libtorrent INVARIANT_CHECK; #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "setup_receive: reading = " << m_reading << "\n"; + (*m_logger) << time_now_string() << " *** SETUP_RECEIVE [ reading: " << (m_reading?"yes":"no") << "]\n"; #endif if (m_reading) return; @@ -2542,7 +2555,7 @@ namespace libtorrent if (m_bandwidth_limit[download_channel].max_assignable() > 0) { #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "req bandwidth [ " << download_channel << " ]\n"; + (*m_logger) << time_now_string() << " *** REQUEST_BANDWIDTH [ download ]\n"; #endif m_reading = true; t->request_bandwidth(download_channel, self(), m_priority); @@ -2550,7 +2563,18 @@ namespace libtorrent return; } - if (!can_read()) return; + if (!can_read()) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** CANNOT READ [" + " quota: " << m_bandwidth_limit[download_channel].quota_left() << + " ignore: " << (m_ignore_bandwidth_limits?"yes":"no") << + " outstanding: " << m_outstanding_writing_bytes << + " outstanding-limit: " << m_ses.settings().max_outstanding_disk_bytes_per_connection << + " ]\n"; +#endif + return; + } TORRENT_ASSERT(m_packet_size > 0); int max_receive = m_packet_size - m_recv_pos; @@ -2565,7 +2589,7 @@ namespace libtorrent TORRENT_ASSERT(can_read()); #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "async_read " << max_receive << " bytes\n"; + (*m_logger) << time_now_string() << " *** ASYNC_READ [ max: " << max_receive << " bytes ]\n"; #endif m_socket->async_read_some(asio::buffer(&m_recv_buffer[m_recv_pos] , max_receive), bind(&peer_connection::on_receive_data, self(), _1, _2)); @@ -2782,7 +2806,7 @@ namespace libtorrent m_ses.settings().max_outstanding_disk_bytes_per_connection; #if defined(TORRENT_VERBOSE_LOGGING) - (*m_logger) << "*** can_read() " << ret << " reading: " << m_reading << "\n"; + (*m_logger) << time_now_string() << " *** can_read() " << (ret?"yes":"no") << " reading: " << m_reading << "\n"; #endif return ret; diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 0599096d5..4f3f8cfa4 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -525,7 +525,18 @@ namespace libtorrent int max_failcount = m_torrent->settings().max_failcount; int min_reconnect_time = m_torrent->settings().min_reconnect_time; + int min_cidr_distance = (std::numeric_limits::max)(); bool finished = m_torrent->is_finished(); + address external_ip = m_torrent->session().m_external_address; + + if (external_ip == address()) + { + // set external_ip to a random value, to + // radomize which peers we prefer + address_v4::bytes_type bytes; + std::generate(bytes.begin(), bytes.end(), &std::rand); + external_ip = address_v4(bytes); + } aux::session_impl& ses = m_torrent->session(); @@ -549,15 +560,29 @@ namespace libtorrent TORRENT_ASSERT(i->second.connected <= now); - if (i->second.connected <= min_connect_time) - { - min_connect_time = i->second.connected; - candidate = i; - } + if (i->second.connected > min_connect_time) continue; + int distance = cidr_distance(external_ip, i->second.ip.address()); + if (distance > min_cidr_distance) continue; + + min_cidr_distance = distance; + min_connect_time = i->second.connected; + candidate = i; } TORRENT_ASSERT(min_connect_time <= now); +#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING + if (candidate != m_peers.end()) + { + (*m_torrent->session().m_logger) << "*** FOUND CONNECTION CANDIDATE [" + " ip: " << candidate->second.ip << + " d: " << min_cidr_distance << + " external: " << external_ip << + " t: " << total_seconds(time_now() - min_connect_time) << + " ]\n"; + } +#endif + return candidate; } /* From 8a4bf3ab151d3e9911b43d0870eb75a98bc6ea20 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Thu, 28 Feb 2008 16:55:23 +0000 Subject: [PATCH 0557/1009] improved connect-page+web.reloader --- .../ui/webui/webui_plugin/deluge_webserver.py | 21 +++++---- deluge/ui/webui/webui_plugin/pages.py | 47 ++++++++++++++++--- deluge/ui/webui/webui_plugin/run_webserver06 | 3 +- .../templates/advanced/part_stats.html | 4 +- .../webui_plugin/templates/deluge/about.html | 17 ++----- .../templates/deluge/admin_toolbar.html | 2 + .../templates/deluge/connect.html | 28 +++++++++-- .../webui_plugin/templates/deluge/login.html | 3 -- deluge/ui/webui/webui_plugin/utils.py | 24 ++++++++++ 9 files changed, 112 insertions(+), 37 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/deluge_webserver.py b/deluge/ui/webui/webui_plugin/deluge_webserver.py index 2f9d31c2a..c3aeed0d7 100644 --- a/deluge/ui/webui/webui_plugin/deluge_webserver.py +++ b/deluge/ui/webui/webui_plugin/deluge_webserver.py @@ -35,10 +35,11 @@ from webserver_common import ws from lib.webpy022.request import webpyfunc from lib.webpy022 import webapi from lib.gtk_cherrypy_wsgiserver import CherryPyWSGIServer +import lib.webpy022 as web import os -def create_webserver(urls, methods): - func = webapi.wsgifunc(webpyfunc(urls, methods, False)) +def create_webserver(urls, methods, middleware): + func = webapi.wsgifunc(webpyfunc(urls, methods, False), *middleware) server_address=("0.0.0.0", int(ws.config.get('port'))) server = CherryPyWSGIServer(server_address, func, server_name="localhost") @@ -49,14 +50,18 @@ def create_webserver(urls, methods): print "http://%s:%d/" % server_address return server -def WebServer(): - import pages - return create_webserver(pages.urls, pages) +def WebServer(debug = False): + if debug: + middleware = [web.reloader] + else: + middleware = [] -def run(): - server = WebServer() + import pages + return create_webserver(pages.urls, pages, middleware) + +def run(debug = False): + server = WebServer(debug) try: server.start() except KeyboardInterrupt: server.stop() - diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 926fdefee..26511e632 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -33,6 +33,7 @@ # from webserver_common import ws, proxy, log from utils import * +import utils #todo remove the line above. from render import render, error_page import page_decorators as deco import config_tabs_webui #auto registers @@ -40,6 +41,7 @@ import config_tabs_deluge #auto registers from config import config_page from torrent_options import torrent_options from torrent_move import torrent_move + #import forms # from debugerror import deluge_debugerror @@ -54,7 +56,7 @@ from lib.static_handler import static_handler from operator import attrgetter import os -from json_api import json_api +#from json_api import json_api #secuity leak, todo:fix #special/complex pages: from torrent_add import torrent_add @@ -85,9 +87,10 @@ urls = ( "/about", "about", "/logout", "logout", "/connect","connect", + "/daemon/control/(.*)","daemon_control", #remote-api: "/remote/torrent/add(.*)", "remote_torrent_add", - "/json/(.*)","json_api", + #"/json/(.*)","json_api", #static: "/static/(.*)", "static", "/template/static/(.*)", "template_static", @@ -328,12 +331,14 @@ class connect: @deco.check_session @deco.deluge_page_noauth def GET(self, name): - if proxy.connected(): - error = _("Not Connected to a daemon") - else: - error = None + try: + proxy.ping() + connected = proxy.get_core_uri() + except: + connected = None + connect_list = ["http://localhost:58846"] - return render.connect(connect_list, error) + return render.connect(connect_list, connected) def POST(self): vars = web.input(uri = None, other_uri = None) @@ -348,6 +353,34 @@ class connect: proxy.set_core_uri(uri) do_redirect() +class daemon_control: + @deco.check_session + def POST(self, command): + if command == 'stop': + proxy.shutdown() + elif command == 'start': + self.start() + elif command == 'restart': + proxy.shutdown() + self.start() + else: + raise Exception('Unknown command:"%s"' % command) + + seeother('/connect') + + def start(self): + import time + uri = web.input(uri = None).uri + if not uri: + uri = 'http://localhost:58846' + + port = int(uri.split(':')[2]) + utils.daemon_start_localhost(port) + + time.sleep(1) #pause a while to let it start? + proxy.set_core_uri( uri ) + + #other stuff: class remote_torrent_add: diff --git a/deluge/ui/webui/webui_plugin/run_webserver06 b/deluge/ui/webui/webui_plugin/run_webserver06 index 9c59c3749..5780cbf1f 100755 --- a/deluge/ui/webui/webui_plugin/run_webserver06 +++ b/deluge/ui/webui/webui_plugin/run_webserver06 @@ -1,5 +1,6 @@ #!/usr/bin/env python +#only for development/debugging. import deluge_webserver deluge_webserver.ws.init_06(uri = 'http://localhost:58846') -deluge_webserver.run() +deluge_webserver.run(debug = True) \ No newline at end of file diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html index ff729ce19..fca2e0bad 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/part_stats.html @@ -13,8 +13,8 @@ $else: $_('Enable') ] -$_('Logout') -$_('Settings') + +$_('Admin')  
          diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/about.html b/deluge/ui/webui/webui_plugin/templates/deluge/about.html index b34b7877a..f0d1c2b1e 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/about.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/about.html @@ -1,4 +1,5 @@ $:render.header(_('About')) +$:render.admin_toolbar('about')

          Version

          $version 
          @@ -20,20 +21,12 @@ $:render.header(_('About'))
        • Martijn Voncken
        • -

          Template

          -
            -
          • Martijn Voncken
          • -
          • somedude
          • -
          -

          Deluge

            -
          • Zach Tibbitts
          • -
          • Alon Zakai
          • - -
          • Alon Zakai
          • Marcos Pinto
          • Andrew Resch
          • +
          • Zach Tibbitts
          • +
          • Alon Zakai
          • Alex Dedul
          @@ -41,9 +34,7 @@ $:render.header(_('About'))
          • Slurdge
          - - -*and all other authors/helpers/contributors I forgot to mention.
          +*and all other authors/helpers/contributors I forgot to mention $:render.footer() diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html b/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html index cec7fb69a..7b047cf11 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html @@ -4,5 +4,7 @@ $def with (active_tab) Connect + About + Logout
          diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/connect.html b/deluge/ui/webui/webui_plugin/templates/deluge/connect.html index 9c29a19a0..9ae35785e 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/connect.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/connect.html @@ -1,12 +1,34 @@ -$def with (connect_list, error) +$def with (connect_list, connected) $:render.header(_("Connect to Daemon")) $:render.admin_toolbar('connect')
          -$if error: -
          $error
          +$if connected: + +
          + $_('Connected to') + $connected + +
          + +
          +
          +
          + +
          +
          +$else: +
          $_("Not Connected to a daemon") +
          +
          + + +
          $for i, uri in enumerate(connect_list): diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/login.html b/deluge/ui/webui/webui_plugin/templates/deluge/login.html index 0436e9076..3aecccfd1 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/login.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/login.html @@ -16,9 +16,6 @@ $if error > 0:
          - -
          - $_('About')
          diff --git a/deluge/ui/webui/webui_plugin/utils.py b/deluge/ui/webui/webui_plugin/utils.py index 5ccb9d841..005346494 100644 --- a/deluge/ui/webui/webui_plugin/utils.py +++ b/deluge/ui/webui/webui_plugin/utils.py @@ -308,6 +308,30 @@ def get_newforms_data(form_class): #/utils +#generic/ non-webui utils todo: move to trunk/core. +def daemon_test_online_status(uri): + """Tests the status of URI.. Returns True or False depending on status. + """ + online = True + host = None + try: + host = xmlrpclib.ServerProxy(uri) + host.ping() + except socket.error: + online = False + + del host + self.online_status[uri] = online + return online + +def daemon_start_localhost(port): + """Starts a localhost daemon""" + port = str(port) + log.info("Starting localhost:%s daemon..", port) + # Spawn a local daemon + os.popen("deluged -p %s" % port) + +#exceptions: class WebUiError(Exception): """the message of these exceptions will be rendered in render.error(e.message) in debugerror.py""" From d844337cbb4eff0ecf18edb70890c55d1bde6737 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Thu, 28 Feb 2008 19:35:45 +0000 Subject: [PATCH 0558/1009] prepare for plugins --- deluge/ui/webui/webui_plugin/config.py | 2 + deluge/ui/webui/webui_plugin/pages.py | 38 ++++++++++++++ deluge/ui/webui/webui_plugin/render.py | 4 ++ .../templates/advanced/index.html | 51 +++---------------- .../advanced/torrent_info_inner.html | 14 ++--- .../templates/deluge/admin_toolbar.html | 14 ++--- .../templates/deluge/torrent_info.html | 33 +++--------- deluge/ui/webui/webui_plugin/utils.py | 9 ++++ .../ui/webui/webui_plugin/webserver_common.py | 2 +- 9 files changed, 79 insertions(+), 88 deletions(-) diff --git a/deluge/ui/webui/webui_plugin/config.py b/deluge/ui/webui/webui_plugin/config.py index c4c3ab3f2..6d0c9c062 100644 --- a/deluge/ui/webui/webui_plugin/config.py +++ b/deluge/ui/webui/webui_plugin/config.py @@ -29,6 +29,8 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +#TODO: rename to config_manager.py + import lib.newforms_plus as forms import page_decorators as deco import lib.webpy022 as web diff --git a/deluge/ui/webui/webui_plugin/pages.py b/deluge/ui/webui/webui_plugin/pages.py index 26511e632..a8f80a610 100644 --- a/deluge/ui/webui/webui_plugin/pages.py +++ b/deluge/ui/webui/webui_plugin/pages.py @@ -42,6 +42,11 @@ from config import config_page from torrent_options import torrent_options from torrent_move import torrent_move + +#plugin like api's +import menu_manager +from menu_manager import TB + #import forms # from debugerror import deluge_debugerror @@ -61,6 +66,39 @@ import os #special/complex pages: from torrent_add import torrent_add +#plugin-like api's : register + +menu_manager.register_admin_page("config", _("Config"), "/config/") +menu_manager.register_admin_page("connect", _("Connect"), "/connect") +menu_manager.register_admin_page("about", _("About"), "/about") +menu_manager.register_admin_page("logout", _("Logout"), "/logout") + +menu_manager.register_detail_tab("details", _("Details"), "tab_meta") +menu_manager.register_detail_tab("files", _("Files"), "tab_files") +menu_manager.register_detail_tab("options", _("Options"), "tab_options") + +menu_manager.register_toolbar_item("add", _("Add"), "list-add.png" , TB.generic, + "GET","/torrent/add/", True) +menu_manager.register_toolbar_item("delete",_("Delete"), "list-remove.png" ,TB.torrent_list, + "GET","/torrent/delete/" , True) +menu_manager.register_toolbar_item("stop",_("Stop"), "pause.png" ,TB.torrent_list, + "POST","/torrent/stop/", True) +menu_manager.register_toolbar_item("start",_("Start"), "start.png" ,TB.torrent_list, + "POST","/torrent/start/", True) +menu_manager.register_toolbar_item("queue_up",_("Up"), "queue-up.png" ,TB.torrent_list, + "POST","/torrent/queue/up/", True) +menu_manager.register_toolbar_item("queue_down",_("Down"), "queue-down.png" ,TB.torrent_list, + "POST","/torrent/queue/down/", True) +menu_manager.register_toolbar_item("details",_("Details"), "details.png" ,TB.torrent, + "GET","/torrent/info/", True) +menu_manager.register_toolbar_item("move",_("Move"), "move.png" ,TB.torrent_list, + "POST","/torrent/move/", True) + +menu_manager.register_toolbar_item("reannounce",_("Reannounce"), "view-refresh.png" ,TB.torrent_list, + "POST","'/torrent/reannounce/", False) +menu_manager.register_toolbar_item("recheck",_("Recheck"), "view-refresh.png" ,TB.torrent_list, + "POST","'/torrent/recheck/", False) + #routing: urls = ( "/login", "login", diff --git a/deluge/ui/webui/webui_plugin/render.py b/deluge/ui/webui/webui_plugin/render.py index 60aafc264..f14b11ec9 100644 --- a/deluge/ui/webui/webui_plugin/render.py +++ b/deluge/ui/webui/webui_plugin/render.py @@ -66,6 +66,10 @@ class subclassed_render(object): else: return getattr(self.base_template, attr) + def __getitem__(self, item): + "for plugins/templates" + return getattr(self, item) + render = subclassed_render() def error_page(error): diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/index.html b/deluge/ui/webui/webui_plugin/templates/advanced/index.html index 99305f446..9348c3162 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/index.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/index.html @@ -12,51 +12,14 @@ $for t in torrent_list:
          - +$for id, title, image, flag, method, url, important in toolbar_items: + $if important: + - - - - - - - - - - - - - - - - - - $:category_tabs(all_torrents) +$:category_tabs(all_torrents)
          diff --git a/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html b/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html index 7e0d31d59..735301689 100644 --- a/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html +++ b/deluge/ui/webui/webui_plugin/templates/advanced/torrent_info_inner.html @@ -7,16 +7,12 @@ $def with (torrent, active_tab) -$:render.part_tab_button('details', _("Details"), active_tab) -$:render.part_tab_button('files', _("Files"), active_tab) -$:render.part_tab_button('options', _("Options"), active_tab) +$for id, title, tab in detail_tabs: + $:render.part_tab_button(id, title, active_tab) -$if active_tab == 'details': - $:render.tab_meta(torrent) -$if active_tab == 'files': - $:render.tab_files(torrent) -$if active_tab == 'options': - $:render.tab_options(torrent) +$for id, title, tab in detail_tabs: + $if active_tab == id: + $:render[tab](torrent) diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html b/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html index 7b047cf11..c46ad6af3 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/admin_toolbar.html @@ -1,10 +1,10 @@ $def with (active_tab)
          - Config - - Connect - About - Logout +$for id, title, url in admin_pages: + $title
          - diff --git a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html index 270bce8ae..0dfa9722f 100644 --- a/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html +++ b/deluge/ui/webui/webui_plugin/templates/deluge/torrent_info.html @@ -2,34 +2,13 @@ $def with (torrent) $:(render.header(torrent.message + '/' + torrent.name))
          +$for id, title, image, flag, method, url, important in toolbar_items: + $if (flag > 0) and (id != 'details'): + $:render.part_button(method, (url + str(torrent.id)), title, 'tango/' + image) -$if (torrent.action == 'start'): - $:render.part_button('POST', '/torrent/start/' + str(torrent.id), _('Resume'), 'tango/start.png') -$else: - $:render.part_button('POST', '/torrent/stop/' + str(torrent.id), _('Pause'), 'tango/pause.png') - - -$:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/list-remove.png') -$:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reannounce'), 'tango/view-refresh.png') -$:render.part_button('POST', '/torrent/recheck/' + str(torrent.id), _('Recheck'), 'tango/view-refresh.png') - -$:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png') -$:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png') - -$:render.part_button('GET', '/torrent/move/' + str(torrent.id), _('Move'), 'tango/move.png') - -

          $_('Details')

          -$:render.tab_meta(torrent) - -

          $_('Options')

          - -$:render.tab_options(torrent) - - -

          $_('Files')

          - -$:render.tab_files(torrent) - +$for id, title, tab in detail_tabs: +

          $title

          + $:render[tab](torrent)
          - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-goto-top - True - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-go-up - True - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-go-down - True - True - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-goto-bottom - True - True - - - - - diff --git a/deluge/plugins/queue/queue/glade/queueprefs.glade b/deluge/plugins/queue/queue/glade/queueprefs.glade deleted file mode 100644 index 67d89aae3..000000000 --- a/deluge/plugins/queue/queue/glade/queueprefs.glade +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Queue new torrents to top - 0 - True - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>General</b> - True - - - label_item - - - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 2 - 10 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 0 -1 9999 1 10 10 - True - True - - - 1 - 2 - 2 - 3 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 0 -1 9999 1 10 10 - True - True - - - 1 - 2 - 1 - 2 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 0 -1 9999 1 10 10 - True - True - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Total active downloading: - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Total active seeding: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Total active torrents: - - - GTK_FILL - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Active Torrents</b> - True - - - label_item - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Queue newly finished torrents to bottom - 0 - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Stop seeding when share ratio reaches: - 0 - True - - - - False - False - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 0 0 100 0.10000000000000001 1 1 - 2 - True - - - False - False - 1 - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Remove torrent when share ratio reached - 0 - True - - - - - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Seeding</b> - True - - - label_item - - - - - False - False - 2 - - - - - - diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py deleted file mode 100644 index fb6a7bf1a..000000000 --- a/deluge/plugins/queue/queue/gtkui.py +++ /dev/null @@ -1,119 +0,0 @@ -# -# gtkui.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -import pkg_resources -import gtk.glade -from deluge.log import LOG as log -import ui - -class GtkUI(ui.UI): - def __init__(self, plugin_api, plugin_name): - log.debug("Calling UI init") - # Call UI constructor - ui.UI.__init__(self, plugin_api, plugin_name) - log.debug("Queue GtkUI plugin initalized..") - - def load_interface(self): - # Get the queue menu from the glade file - menu_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", - "glade/queuemenu.glade")) - - prefs_glade = gtk.glade.XML(pkg_resources.resource_filename("queue", - "glade/queueprefs.glade")) - - menu_glade.signal_autoconnect({ - "on_menuitem_queuetop_activate": \ - self.on_queuetop_activate, - "on_menuitem_queueup_activate": self.on_queueup_activate, - "on_menuitem_queuedown_activate": \ - self.on_queuedown_activate, - "on_menuitem_queuebottom_activate": \ - self.on_queuebottom_activate - }) - - menu = menu_glade.get_widget("menu_queue") - - # Connect to the 'torrent_queue_changed' signal - #self.core.connect_to_signal("torrent_queue_changed", - # self.torrent_queue_changed_signal) - - # Add the '#' column at the first position - self.plugin.add_torrentview_text_column("#", - col_type=int, - position=0, - status_field=["queue"]) - # Update the new column right away - self.update() - - # Add a toolbar buttons - self.toolbar_sep = self.plugin.add_toolbar_separator() - self.toolbutton_up = self.plugin.add_toolbar_button( - stock="gtk-go-up", - label=_("Queue Up"), - tooltip=_("Queue selected torrents up"), - callback=self.on_queueup_activate) - - self.toolbutton_down = self.plugin.add_toolbar_button( - stock="gtk-go-down", - label=_("Queue Down"), - tooltip=_("Queue selected torrents down"), - callback=self.on_queuedown_activate) - - # Add a separator before menu - self.menu_sep = self.plugin.add_torrentmenu_separator() - - # Add the queue menu to the torrent menu - self.queue_menuitem = gtk.ImageMenuItem("Queue") - queue_image = gtk.Image() - queue_image.set_from_stock(gtk.STOCK_SORT_ASCENDING, gtk.ICON_SIZE_MENU) - self.queue_menuitem.set_image(queue_image) - self.queue_menuitem.set_submenu(menu) - self.queue_menuitem.show_all() - self.plugin.add_torrentmenu_menu(self.queue_menuitem) - - # Add preferences page - self.queue_pref_page = \ - prefs_glade.get_widget("queue_prefs_box") - self.plugin.add_preferences_page("Queue", self.queue_pref_page) - - def unload_interface(self): - self.plugin.remove_torrentmenu_item(self.menu_sep) - self.plugin.remove_torrentmenu_item(self.queue_menuitem) - self.plugin.remove_toolbar_button(self.toolbar_sep) - self.plugin.remove_toolbar_button(self.toolbutton_up) - self.plugin.remove_toolbar_button(self.toolbutton_down) - self.plugin.remove_torrentview_column("#") - self.plugin.remove_preferences_page("Queue") - - def update(self): - self.plugin.update_torrent_view() diff --git a/deluge/plugins/queue/queue/torrentqueue.py b/deluge/plugins/queue/queue/torrentqueue.py deleted file mode 100644 index 87ddd6281..000000000 --- a/deluge/plugins/queue/queue/torrentqueue.py +++ /dev/null @@ -1,183 +0,0 @@ -# -# torrentqueue.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -import pickle - -import deluge.common -from deluge.log import LOG as log - -class TorrentQueue: - def __init__(self, torrent_list): - # Try to load the queue state from file - self.queue = self.load_state() - - # First remove any torrent_ids in self.queue that are not in the current - # session list. - for torrent_id in self.queue: - if torrent_id not in torrent_list: - self.queue.remove(torrent_id) - - # Next we append any torrents in the session list to self.queue - for torrent_id in torrent_list: - if torrent_id not in self.queue: - self.queue.append(torrent_id) - - def __getitem__(self, torrent_id): - """Return the queue position of the torrent_id""" - try: - return self.queue.index(torrent_id) - except ValueError: - return None - - def load_state(self): - """Load the queue state""" - try: - log.debug("Opening queue state file for load.") - state_file = open(deluge.common.get_config_dir("queue.state"), - "rb") - state = pickle.load(state_file) - state_file.close() - return state - except IOError, e: - log.warning("Unable to load queue state file: %s", e) - - return [] - - def save_state(self): - """Save the queue state""" - try: - log.debug("Saving queue state file.") - state_file = open(deluge.common.get_config_dir("queue.state"), - "wb") - pickle.dump(self.queue, state_file) - state_file.close() - except IOError: - log.warning("Unable to save queue state file.") - - def append(self, torrent_id): - """Append torrent_id to the bottom of the queue""" - log.debug("Append torrent %s to queue..", torrent_id) - self.queue.append(torrent_id) - self.save_state() - - def prepend(self, torrent_id): - """Prepend torrent_id to the top of the queue""" - log.debug("Prepend torrent %s to queue..", torrent_id) - self.queue.insert(0, torrent_id) - self.save_state() - - def remove(self, torrent_id): - """Removes torrent_id from the list""" - log.debug("Remove torrent %s from queue..", torrent_id) - self.queue.remove(torrent_id) - self.save_state() - - def up(self, torrent_id): - """Move torrent_id up one in the queue""" - if torrent_id not in self.queue: - # Raise KeyError if the torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s up..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue up if torrent is already at top - if index is 0: - return False - - # Pop and insert the torrent_id at index - 1 - self.queue.insert(index - 1, self.queue.pop(index)) - - self.save_state() - - return True - - def top(self, torrent_id): - """Move torrent_id to top of the queue""" - if torrent_id not in self.queue: - # Raise KeyError if the torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s to top..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue up if torrent is already at top - if index is 0: - return False - - # Pop and prepend the torrent_id - self.prepend(self.queue.pop(index)) - - return True - - def down(self, torrent_id): - """Move torrent_id down one in the queue""" - if torrent_id not in self.queue: - # Raise KeyError if torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s down..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue down of torrent_id is at bottom - if index is len(self.queue) - 1: - return False - - # Pop and insert the torrent_id at index + 1 - self.queue.insert(index + 1, self.queue.pop(index)) - - self.save_state() - - return True - - def bottom(self, torrent_id): - """Move torrent_id to bottom of the queue""" - if torrent_id not in self.queue: - # Raise KeyError if torrent_id is not in the queue - raise KeyError - - log.debug("Move torrent %s to bottom..", torrent_id) - # Get the index of the torrent_id - index = self.queue.index(torrent_id) - - # Can't queue down of torrent_id is at bottom - if index is len(self.queue) - 1: - return False - - # Pop and append the torrent_id - self.append(self.queue.pop(index)) - - return True diff --git a/deluge/plugins/queue/queue/ui.py b/deluge/plugins/queue/queue/ui.py deleted file mode 100644 index 8cb120950..000000000 --- a/deluge/plugins/queue/queue/ui.py +++ /dev/null @@ -1,128 +0,0 @@ -# -# ui.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# Deluge is free software. -# -# You may redistribute it and/or modify it under the terms of the -# GNU General Public License, as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# deluge is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -import gettext -import locale -import pkg_resources -from deluge.ui.client import aclient as client -from deluge.log import LOG as log - -class UI: - def __init__(self, plugin_api, plugin_name): - self.plugin = plugin_api - # Initialize gettext - locale.setlocale(locale.LC_MESSAGES, '') - locale.bindtextdomain("deluge", - pkg_resources.resource_filename( - "deluge", "i18n")) - locale.textdomain("deluge") - gettext.bindtextdomain("deluge", - pkg_resources.resource_filename( - "deluge", "i18n")) - gettext.textdomain("deluge") - gettext.install("deluge", - pkg_resources.resource_filename( - "deluge", "i18n")) - - def enable(self): - log.debug("Enabling UI plugin") - # Load the interface and connect the callbacks - self.load_interface() - - def disable(self): - self.unload_interface() - - def load_interface(self): - pass - - def unload_interface(self): - pass - - def update(self): - pass - - ## Menu callbacks ## - def on_queuetop_activate(self, data=None): - log.debug("on_menuitem_queuetop_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - try: - client.queue_queue_top(torrent_id) - except Exception, e: - log.debug("Unable to queue top torrent: %s", e) - return - - def on_queueup_activate(self, data=None): - log.debug("on_menuitem_queueup_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - try: - client.queue_queue_up(torrent_id) - except Exception, e: - log.debug("Unable to queue up torrent: %s", e) - return - - def on_queuedown_activate(self, data=None): - log.debug("on_menuitem_queuedown_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - try: - client.queue_queue_down(torrent_id) - except Exception, e: - log.debug("Unable to queue down torrent: %s", e) - return - - def on_queuebottom_activate(self, data=None): - log.debug("on_menuitem_queuebottom_activate") - # Get the selected torrents - torrent_ids = self.plugin.get_selected_torrents() - for torrent_id in torrent_ids: - try: - client.queue_queue_bottom(torrent_id) - except Exception, e: - log.debug("Unable to queue bottom torrent: %s", e) - return - - ## Signals ## - def torrent_queue_changed_signal(self): - """This function is called whenever we receive a 'torrent_queue_changed' - signal from the core plugin. - """ - log.debug("torrent_queue_changed signal received..") - # We only need to update the queue column - self.update() - return - diff --git a/deluge/plugins/queue/setup.py b/deluge/plugins/queue/setup.py deleted file mode 100644 index 9f7231642..000000000 --- a/deluge/plugins/queue/setup.py +++ /dev/null @@ -1,52 +0,0 @@ -# setup.py -# -# Copyright (C) 2007 Andrew Resch ('andar') -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -""" -Allow torrents to be queued in a specific order -""" - -from setuptools import setup - -__author__ = "Andrew Resch" - -setup( - name="Queue", - version="1.0", - description=__doc__, - author=__author__, - packages=["queue"], - package_data = {"queue": ["glade/*.glade"]}, - entry_points=""" - [deluge.plugin.core] - Queue = queue:CorePlugin - [deluge.plugin.gtkui] - Queue = queue:GtkUIPlugin - """ -) From ea3d25e8e1f4e057714b90b9801dacab8f1b2d69 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 2 Mar 2008 04:47:35 +0000 Subject: [PATCH 0569/1009] Add Queue functionality from the plugin to the core. This breaks torrents.state. --- deluge/core/core.py | 74 +++++----- deluge/core/torrent.py | 20 ++- deluge/core/torrentmanager.py | 28 +++- deluge/core/torrentqueue.py | 168 +++++++++++++++++++++++ deluge/ui/gtkui/glade/torrent_menu.glade | 16 +-- deluge/ui/gtkui/menubar.py | 23 +++- deluge/ui/gtkui/preferences.py | 2 +- deluge/ui/gtkui/signals.py | 7 + deluge/ui/gtkui/toolbar.py | 14 +- 9 files changed, 293 insertions(+), 59 deletions(-) create mode 100644 deluge/core/torrentqueue.py diff --git a/deluge/core/core.py b/deluge/core/core.py index dfb75918d..f0e4354fb 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -519,45 +519,46 @@ class Core( self.session.set_ip_filter(self.ip_filter) ## Queueing functions ## - def export_queue_top(self, torrent_id): - log.debug("Attempting to queue %s to top", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.top(torrent_id): - self._torrent_queue_changed(torrent_id) - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) + def export_queue_top(self, torrent_ids): + log.debug("Attempting to queue %s to top", torrent_ids) + for torrent_id in torrent_ids: + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.top(torrent_id): + self._torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", torrent_id) - def export_queue_up(self, torrent_id): - log.debug("Attempting to queue %s to up", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.up(torrent_id): - self._torrent_queue_changed(torrent_id) - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) + def export_queue_up(self, torrent_ids): + log.debug("Attempting to queue %s to up", torrent_ids) + for torrent_id in torrent_ids: + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.up(torrent_id): + self._torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", torrent_id) - def export_queue_down(self, torrent_id): - log.debug("Attempting to queue %s to down", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.down(torrent_id): - self._torrent_queue_changed(torrent_id) - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) + def export_queue_down(self, torrent_ids): + log.debug("Attempting to queue %s to down", torrent_ids) + for torrent_id in torrent_ids: + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.down(torrent_id): + self._torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", torrent_id) + + def export_queue_bottom(self, torrent_ids): + log.debug("Attempting to queue %s to bottom", torrent_ids) + for torrent_id in torrent_ids: + try: + # If the queue method returns True, then we should emit a signal + if self.torrents.queue.bottom(torrent_id): + self._torrent_queue_changed() + except KeyError: + log.warning("torrent_id: %s does not exist in the queue", torrent_id) - def export_queue_bottom(self, torrent_id): - log.debug("Attempting to queue %s to bottom", torrent_id) - try: - # If the queue method returns True, then we should emit a signal - if self.torrents.queue.bottom(torrent_id): - self._torrent_queue_changed(torrent_id) - except KeyError: - log.warning("torrent_id: %s does not exist in the queue", - torrent_id) # Signals def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" @@ -597,6 +598,7 @@ class Core( def _torrent_queue_changed(self): """Emitted when a torrent queue position is changed""" log.debug("torrent_queue_changed signal emitted") + self.signals.emit("torrent_queue_changed") # Config set functions def _on_config_value_change(self, key, value): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 4a8e6a32e..1553c75cc 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -37,6 +37,7 @@ import os import deluge.libtorrent as lt import deluge.common +import deluge.component as component from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -50,6 +51,9 @@ class Torrent: # Get the core config self.config = ConfigManager("core.conf") + # Get a reference to the TorrentQueue + self.torrentqueue = component.get("TorrentQueue") + # Set the filename self.filename = filename # Set the libtorrent handle @@ -151,7 +155,7 @@ class Torrent: self.state = TORRENT_STATE[state] except: pass - + def get_eta(self): """Returns the ETA in seconds for this torrent""" if self.status == None: @@ -208,7 +212,13 @@ class Torrent: 'offset': file.offset }) return ret - + + def get_queue_position(self): + # We augment the queue position + 1 so that the user sees a 1 indexed + # list. + + return self.torrentqueue[self.torrent_id] + 1 + def get_status(self, keys): """Returns the status of the torrent based on the keys provided""" # Create the full dictionary @@ -252,8 +262,7 @@ class Torrent: "max_upload_speed": self.max_upload_speed, "max_download_speed": self.max_download_speed, "prioritize_first_last": self.prioritize_first_last, - "private": self.private, - "queue": 0 + "private": self.private } fns = { @@ -264,7 +273,8 @@ class Torrent: "piece_length": self.torrent_info.piece_length, "eta": self.get_eta, "ratio": self.get_ratio, - "file_progress": self.handle.file_progress + "file_progress": self.handle.file_progress, + "queue": self.get_queue_position } self.status = None diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c44a095e5..21ba9e9c0 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -43,6 +43,7 @@ import deluge.libtorrent as lt import deluge.common import deluge.component as component +from deluge.core.torrentqueue import TorrentQueue from deluge.configmanager import ConfigManager from deluge.core.torrent import Torrent from deluge.log import LOG as log @@ -62,12 +63,14 @@ class TorrentState: max_download_speed, prioritize_first_last, private, - file_priorities + file_priorities, + queue ): self.torrent_id = torrent_id self.filename = filename self.total_uploaded = total_uploaded self.trackers = trackers + self.queue = queue # Options self.compact = compact @@ -99,6 +102,8 @@ class TorrentManager(component.Component): self.alerts = alerts # Get the core config self.config = ConfigManager("core.conf") + # Create the TorrentQueue object + self.queue = TorrentQueue() # Create the torrents dict { torrent_id: Torrent } self.torrents = {} @@ -157,7 +162,7 @@ class TorrentManager(component.Component): return self.torrents.keys() def add(self, filename, filedump=None, options=None, total_uploaded=0, - trackers=None, save_state=True): + trackers=None, queue=-1, save_state=True): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) log.debug("options: %s", options) @@ -242,10 +247,13 @@ class TorrentManager(component.Component): # Create a Torrent object torrent = Torrent(filename, handle, options["compact_allocation"], options["download_location"], total_uploaded, trackers) - + # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent - + + # Add the torrent to the queue + self.queue.insert(queue, torrent.torrent_id) + # Set per-torrent options torrent.set_max_connections(options["max_connections_per_torrent"]) torrent.set_max_upload_slots(options["max_upload_slots_per_torrent"]) @@ -420,6 +428,10 @@ class TorrentManager(component.Component): # Try to add the torrents in the state to the session add_paused = {} + # First lets clear the queue and make it the correct length.. This will + # help with inserting values at the right position. + self.queue.set_size(len(state.torrents)) + for torrent_state in state.torrents: try: options = { @@ -441,11 +453,14 @@ class TorrentManager(component.Component): options=options, total_uploaded=torrent_state.total_uploaded, trackers=torrent_state.trackers, + queue=torrent_state.queue, save_state=False) + except AttributeError, e: log.error("Torrent state file is either corrupt or incompatible!") + add_paused = {} break - + # Run the post_session_load plugin hooks self.plugins.run_post_session_load() @@ -473,7 +488,8 @@ class TorrentManager(component.Component): torrent.max_download_speed, torrent.prioritize_first_last, torrent.private, - torrent.file_priorities + torrent.file_priorities, + torrent.get_status(["queue"])["queue"] - 1 # We subtract 1 due to augmentation ) state.torrents.append(torrent_state) diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py new file mode 100644 index 000000000..687150d1b --- /dev/null +++ b/deluge/core/torrentqueue.py @@ -0,0 +1,168 @@ +# +# torrentqueue.py +# +# Copyright (C) 2007,2008 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import deluge.component as component +import deluge.common +from deluge.log import LOG as log + +class TorrentQueue(component.Component): + def __init__(self): + component.Component.__init__(self, "TorrentQueue", depend=["TorrentManager"]) + # This is a list of torrent_ids in the queueing order + self.queue = [] + + def set_size(self, size): + """Clear and set the self.queue list to the length of size""" + log.debug("Setting queue size to %s..", size) + self.queue = [None] * size + + def __getitem__(self, torrent_id): + """Return the queue position of the torrent_id""" + try: + return self.queue.index(torrent_id) + except ValueError: + return None + + def append(self, torrent_id): + """Append torrent_id to the bottom of the queue""" + log.debug("Append torrent %s to queue..", torrent_id) + self.queue.append(torrent_id) + return self.queue.index(torrent_id) + + def prepend(self, torrent_id): + """Prepend torrent_id to the top of the queue""" + log.debug("Prepend torrent %s to queue..", torrent_id) + self.queue.insert(0, torrent_id) + return self.queue.index(torrent_id) + + def insert(self, position, torrent_id): + """Inserts torrent_id at position in queue.""" + log.debug("Inserting torrent %s at position %s..", torrent_id, position) + + if position < 0: + for q in self.queue: + if q == None: + self.queue[self.queue.index(q)] = torrent_id + break + else: + if self.queue[position] == None: + self.queue[position] = torrent_id + else: + self.queue.insert(position, torrent_id) + + + try: + return self.queue.index(torrent_id) + except ValueError: + self.queue.append(torrent_id) + return self.queue.index(torrent_id) + + def remove(self, torrent_id): + """Removes torrent_id from the list""" + log.debug("Remove torrent %s from queue..", torrent_id) + self.queue.remove(torrent_id) + + def up(self, torrent_id): + """Move torrent_id up one in the queue""" + if torrent_id not in self.queue: + # Raise KeyError if the torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s up..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue up if torrent is already at top + if index is 0: + return False + + # Pop and insert the torrent_id at index - 1 + self.queue.insert(index - 1, self.queue.pop(index)) + + return True + + def top(self, torrent_id): + """Move torrent_id to top of the queue""" + if torrent_id not in self.queue: + # Raise KeyError if the torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s to top..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue up if torrent is already at top + if index is 0: + return False + + self.queue.insert(0, self.queue.pop(index)) + + return True + + def down(self, torrent_id): + """Move torrent_id down one in the queue""" + if torrent_id not in self.queue: + # Raise KeyError if torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s down..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue down of torrent_id is at bottom + if index is len(self.queue) - 1: + return False + + # Pop and insert the torrent_id at index + 1 + self.queue.insert(index + 1, self.queue.pop(index)) + + return True + + def bottom(self, torrent_id): + """Move torrent_id to bottom of the queue""" + if torrent_id not in self.queue: + # Raise KeyError if torrent_id is not in the queue + raise KeyError + + log.debug("Move torrent %s to bottom..", torrent_id) + # Get the index of the torrent_id + index = self.queue.index(torrent_id) + + # Can't queue down of torrent_id is at bottom + if index is len(self.queue) - 1: + return False + + # Pop and append the torrent_id + self.append(self.queue.pop(index)) + + return True diff --git a/deluge/ui/gtkui/glade/torrent_menu.glade b/deluge/ui/gtkui/glade/torrent_menu.glade index efe8e75f3..9979e1a03 100644 --- a/deluge/ui/gtkui/glade/torrent_menu.glade +++ b/deluge/ui/gtkui/glade/torrent_menu.glade @@ -319,43 +319,43 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-goto-top True True - + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-go-up True True - + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-go-down True True - + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-goto-bottom True True - + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 3a75f30a8..a062a6867 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -144,7 +144,12 @@ class MenuBar(component.Component): "on_menuitem_recheck_activate": self.on_menuitem_recheck_activate, "on_menuitem_open_folder": self.on_menuitem_open_folder_activate, - "on_menuitem_move_activate": self.on_menuitem_move_activate + "on_menuitem_move_activate": self.on_menuitem_move_activate, + "on_menuitem_queue_top_activate": self.on_menuitem_queue_top_activate, + "on_menuitem_queue_up_activate": self.on_menuitem_queue_up_activate, + "on_menuitem_queue_down_activate": self.on_menuitem_queue_down_activate, + "on_menuitem_queue_bottom_activate": self.on_menuitem_queue_bottom_activate, + }) self.change_sensitivity = [ @@ -298,6 +303,22 @@ class MenuBar(component.Component): component.get("TorrentView").get_selected_torrents(), result) chooser.destroy() + def on_menuitem_queue_top_activate(self, value): + log.debug("on_menuitem_queue_top_activate") + client.queue_top(None, component.get("TorrentView").get_selected_torrents()) + + def on_menuitem_queue_up_activate(self, value): + log.debug("on_menuitem_queue_up_activate") + client.queue_up(None, component.get("TorrentView").get_selected_torrents()) + + def on_menuitem_queue_down_activate(self, value): + log.debug("on_menuitem_queue_down_activate") + client.queue_down(None, component.get("TorrentView").get_selected_torrents()) + + def on_menuitem_queue_bottom_activate(self, value): + log.debug("on_menuitem_queue_bottom_activate") + client.queue_bottom(None, component.get("TorrentView").get_selected_torrents()) + ## View Menu ## def on_menuitem_toolbar_toggled(self, value): log.debug("on_menuitem_toolbar_toggled") diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 5d5e6dc0d..3b1427d74 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -63,7 +63,7 @@ class Preferences(component.Component): # Add the default categories i = 0 for category in ["Downloads", "Network", "Bandwidth", "Interface", - "Other", "Daemon", "Plugins"]: + "Other", "Daemon", "Plugins", "Queue"]: self.liststore.append([i, category]) i += 1 diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 1f06942ac..28cb1f1e2 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -59,6 +59,8 @@ class Signals(component.Component): self.torrent_all_resumed) self.receiver.connect_to_signal("config_value_changed", self.config_value_changed) + self.receiver.connect_to_signal("torrent_queue_changed", + self.torrent_queue_changed) def stop(self): try: @@ -108,3 +110,8 @@ class Signals(component.Component): log.debug("config_value_changed signal received..") component.get("StatusBar").config_value_changed(key, value) component.get("SystemTray").config_value_changed(key, value) + + def torrent_queue_changed(self): + log.debug("torrent_queue_changed signal received..") + component.get("TorrentView").update() + diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 0f5b1ef28..230c33fe6 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -56,7 +56,9 @@ class ToolBar(component.Component): "on_toolbutton_preferences_clicked": \ self.on_toolbutton_preferences_clicked, "on_toolbutton_connectionmanager_clicked": \ - self.on_toolbutton_connectionmanager_clicked + self.on_toolbutton_connectionmanager_clicked, + "on_toolbutton_queue_up_clicked": self.on_toolbutton_queue_up_clicked, + "on_toolbutton_queue_down_clicked": self.on_toolbutton_queue_down_clicked }) self.change_sensitivity = [ "toolbutton_add", @@ -153,7 +155,15 @@ class ToolBar(component.Component): log.debug("on_toolbutton_connectionmanager_clicked") # Use the menubar's callbacks component.get("MenuBar").on_menuitem_connectionmanager_activate(data) - + + def on_toolbutton_queue_up_clicked(self, data): + log.debug("on_toolbutton_queue_up_clicked") + component.get("MenuBar").on_menuitem_queue_up_activate(data) + + def on_toolbutton_queue_down_clicked(self, data): + log.debug("on_toolbutton_queue_down_clicked") + component.get("MenuBar").on_menuitem_queue_down_activate(data) + def update_buttons(self, action=None, torrent_id=None): if action == None: # If all the selected torrents are paused, then disable the 'Pause' From 25d2f99ecec97710cbe394b1865833ca99f5bd57 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 2 Mar 2008 06:50:34 +0000 Subject: [PATCH 0570/1009] Update the host list on initialization. --- deluge/ui/gtkui/connectionmanager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index f9b228104..1a99cee0e 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -117,6 +117,8 @@ class ConnectionManager(component.Component): self.hostlist.get_selection().connect("changed", self.on_selection_changed) + self._update() + # Auto connect to a host if applicable if self.gtkui_config["autoconnect"] and \ self.gtkui_config["autoconnect_host_uri"] != None: From e031c4b3ee1a16a4b0c64174f5e9ad194d904bff Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 3 Mar 2008 02:14:51 +0000 Subject: [PATCH 0571/1009] Add new Queued state icon. --- deluge/data/pixmaps/queued.svg | 295 +++++++++++++++++++++++++++++++ deluge/data/pixmaps/queued16.png | Bin 0 -> 659 bytes 2 files changed, 295 insertions(+) create mode 100644 deluge/data/pixmaps/queued.svg create mode 100644 deluge/data/pixmaps/queued16.png diff --git a/deluge/data/pixmaps/queued.svg b/deluge/data/pixmaps/queued.svg new file mode 100644 index 000000000..6206803e5 --- /dev/null +++ b/deluge/data/pixmaps/queued.svg @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Internet Category + + + Jakub Steiner + + + + + Tuomas Kuosmanen + + + + http://jimmac.musichall.cz + + + internet + tools + applications + category + + + + + + + + + + + + + + + + + + + diff --git a/deluge/data/pixmaps/queued16.png b/deluge/data/pixmaps/queued16.png new file mode 100644 index 0000000000000000000000000000000000000000..551432f85faf38834c491e56e5c4030504ca20f8 GIT binary patch literal 659 zcmV;E0&M+>P) z+LIXLNlzS1yy)5AVB$|8(RlSxn@IG=kr0Y21X3xfMKpmFfp)te?~aGHwU*~LGtV+%p~-6et#BGoa)nwGec=5GMLYFM360EpJOTg@`s-3}uv!|uD{IN8t%DUX z#%I4SOg!rsu+B>5qLI`i*>$^B8zSEZz@SIR9Q(L(Z9HFHJ~2VjQ}e<*{7VJ^g59jD z+Cb)gxoE`x6Rei9b25+S-S)3wwMqh8I~vG$;5qwgHJpCe6_krcQVpkH+s$nd0cJIY z8#A|X=KOVR)mYLXCB~F( z#)1#IJ^34J<`UYrhwt@02 Date: Mon, 3 Mar 2008 02:40:43 +0000 Subject: [PATCH 0572/1009] Setting options for the Queueing stuff now works in preferences. --- deluge/core/core.py | 9 +- .../ui/gtkui/glade/preferences_dialog.glade | 1372 ++++++++--------- deluge/ui/gtkui/preferences.py | 36 +- 3 files changed, 720 insertions(+), 697 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index f0e4354fb..4d00d9a7a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -86,7 +86,14 @@ DEFAULT_PREFS = { "autoadd_location": "", "autoadd_enable": False, "add_paused": False, - "default_private": False + "default_private": False, + "max_active_seeding": -1, + "max_active_downloading": -1, + "queue_new_to_top": False, + "queue_finished_to_bottom": False, + "stop_seed_at_ratio": False, + "remove_seed_at_ratio": False, + "stop_seed_ratio": 1.00 } class Core( diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 54f6a1c15..e2bebcb4e 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -298,17 +298,27 @@ 2 2 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + + + 1 + 2 + + + + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically add torrent files that are placed in this folder. - Client Folder: - 0 - True - GTK_FILL + 1 + 2 + 1 + 2 @@ -327,27 +337,17 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically add torrent files that are placed in this folder. + Client Folder: + 0 + True - 1 - 2 - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - - - 1 - 2 + GTK_FILL @@ -1118,40 +1118,71 @@ Disabled 2 15 - + True - True - The maximum upload slots for all torrents. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + The maximum number of connections allowed. Set -1 for unlimited. + 0 + Maximum Connections: + + + GTK_FILL + + + + + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 0 + Maximum Upload Slots: - 1 - 2 1 2 GTK_FILL - + True True - The maximum upload speed for all torrents. Set -1 for unlimited. + The maximum number of connections allowed. Set -1 for unlimited. + 4 1 -1 -1 9000 1 10 10 1 - 1 + True True + GTK_UPDATE_IF_VALID 1 2 - 3 - 4 + GTK_FILL + + + + + True + The maximum download speed for all torrents. Set -1 for unlimited. + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 GTK_FILL @@ -1176,74 +1207,43 @@ Disabled - + True - The maximum download speed for all torrents. Set -1 for unlimited. - 0 - Maximum Download Speed (KiB/s): + True + The maximum upload speed for all torrents. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + 1 + True - 2 - 3 + 1 + 2 + 3 + 4 GTK_FILL - + True True - The maximum number of connections allowed. Set -1 for unlimited. - 4 + The maximum upload slots for all torrents. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 1 True True - GTK_UPDATE_IF_VALID 1 2 - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Slots: - - 1 2 GTK_FILL - - - True - The maximum number of connections allowed. Set -1 for unlimited. - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - The maximum upload speed for all torrents. Set -1 for unlimited. - 0 - Maximum Upload Speed (KiB/s): - - - 3 - 4 - GTK_FILL - - @@ -1287,85 +1287,18 @@ Disabled 2 15 - - True - True - The maximum upload slots per torrent. Set -1 for unlimited. - 1 - -1 -1 9000 1 10 10 - 1 - True - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - True + 1 True 1 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Connections: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Slots: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Download Speed (KiB/s): - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Maximum Upload Speed (KiB/s): - - 3 4 GTK_FILL @@ -1390,20 +1323,87 @@ Disabled - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Speed (KiB/s): + + + 3 + 4 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Download Speed (KiB/s): + + + 2 + 3 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Upload Slots: + + + 1 + 2 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Maximum Connections: + + + GTK_FILL + + + + True True The maximum number of connections per torrent. Set -1 for unlimited. 1 -1 -1 9000 1 10 10 - 1 + True True 1 2 - 3 - 4 + GTK_FILL + + + + + True + True + The maximum upload slots per torrent. Set -1 for unlimited. + 1 + -1 -1 9000 1 10 10 + 1 + True + True + + + 1 + 2 + 1 + 2 GTK_FILL @@ -1658,33 +1658,15 @@ Disabled 2 10 - + True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Open folder with: - 0 - True - True - - - - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Custom: - 0 - True - True - radio_open_folder_stock - + 1 + 2 1 2 GTK_FILL @@ -1714,20 +1696,38 @@ Thunar - + True - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Custom: + 0 + True + True + radio_open_folder_stock + - 1 - 2 1 2 GTK_FILL + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Open folder with: + 0 + True + True + + + + GTK_FILL + + @@ -1809,179 +1809,6 @@ Thunar tab - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0.05000000074505806 - 10 - <i><b><big>Daemon</big></b></i> - True - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Daemon port: - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 0 0 65535 1 10 10 - - - False - False - 1 - - - - - False - False - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Port</b> - True - - - label_item - - - - - False - False - 5 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 10 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Allow Remote Connections - 0 - True - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Connections</b> - True - - - label_item - - - - - False - False - 5 - 3 - - - - - - - - - 5 - - - - - - tab - - True @@ -2171,6 +1998,503 @@ Thunar tab + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Daemon</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Daemon port: + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 0 0 65535 1 10 10 + + + False + False + 1 + + + + + False + False + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Port</b> + True + + + label_item + + + + + False + False + 5 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 10 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Allow Remote Connections + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Connections</b> + True + + + label_item + + + + + False + False + 5 + 3 + + + + + + + + + 5 + + + + + + tab + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.05000000074505806 + 10 + <i><b><big>Queue</big></b></i> + True + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue new torrents to top + 0 + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>General</b> + True + + + label_item + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + 2 + 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active seeding: + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + Total active downloading: + + + 1 + 2 + GTK_FILL + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + -1 -1 9999 1 10 10 + True + True + + + 1 + 2 + 1 + 2 + + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Active Torrents</b> + True + + + label_item + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + GTK_SHADOW_NONE + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Queue newly finished torrents to bottom + 0 + True + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Stop seeding when share ratio reaches: + 0 + True + + + False + False + + + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + 1 0.5 100 0.10000000000000001 1 1 + 2 + True + + + False + False + 1 + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + + + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Remove torrent when share ratio reached + 0 + True + + + + + 2 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Seeding</b> + True + + + label_item + + + + + False + False + 2 + + + + + 5 + 2 + + + + + + + + + 6 + + + + + + tab + + True @@ -2253,344 +2577,6 @@ Thunar - - 6 - - - - - - tab - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0.05000000074505806 - 10 - <i><b><big>Queue</big></b></i> - True - - - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Queue new torrents to top - 0 - True - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>General</b> - True - - - label_item - - - - - False - False - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 3 - 2 - 10 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Total active torrents: - - - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Total active seeding: - - - 1 - 2 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - Total active downloading: - - - 2 - 3 - GTK_FILL - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - True - True - - - 1 - 2 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - True - True - - - 1 - 2 - 1 - 2 - - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - -1 -1 9999 1 10 10 - True - True - - - 1 - 2 - 2 - 3 - - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Active Torrents</b> - True - - - label_item - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - GTK_SHADOW_NONE - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - 12 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 2 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Queue newly finished torrents to bottom - 0 - True - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Stop seeding when share ratio reaches: - 0 - True - - - False - False - - - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 1 - 1 0.5 100 0.10000000000000001 1 1 - 2 - True - - - False - False - 1 - - - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - - - True - False - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Remove torrent when share ratio reached - 0 - True - - - - - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Seeding</b> - True - - - label_item - - - - - False - False - 2 - - - - - 2 - - - 7 diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 3b1427d74..11ba1d675 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -63,7 +63,7 @@ class Preferences(component.Component): # Add the default categories i = 0 for category in ["Downloads", "Network", "Bandwidth", "Interface", - "Other", "Daemon", "Plugins", "Queue"]: + "Other", "Daemon", "Queue", "Plugins"]: self.liststore.append([i, category]) i += 1 @@ -225,7 +225,14 @@ class Preferences(component.Component): "spin_daemon_port": \ ("value", self.core_config["daemon_port"]), "chk_allow_remote_connections": \ - ("active", self.core_config["allow_remote"]) + ("active", self.core_config["allow_remote"]), + "spin_seeding": ("value", self.core_config["max_active_seeding"]), + "spin_downloading": ("value", self.core_config["max_active_downloading"]), + "chk_queue_new_top": ("active", self.core_config["queue_new_to_top"]), + "chk_finished_bottom": ("active", self.core_config["queue_finished_to_bottom"]), + "chk_seed_ratio": ("active", self.core_config["stop_seed_at_ratio"]), + "spin_share_ratio": ("value", self.core_config["stop_seed_ratio"]), + "chk_remove_ratio": ("active", self.core_config["remove_seed_at_ratio"]) } # Update the widgets accordingly @@ -280,7 +287,14 @@ class Preferences(component.Component): "spin_max_download_per_torrent", "spin_max_upload_per_torrent", "spin_daemon_port", - "chk_allow_remote_connections" + "chk_allow_remote_connections", + "spin_seeding", + "spin_downloading", + "chk_queue_new_top", + "chk_finished_bottom", + "chk_seed_ratio", + "spin_share_ratio", + "chk_remove_ratio" ] # We don't appear to be connected to a daemon for key in core_widget_list: @@ -457,6 +471,22 @@ class Preferences(component.Component): self.glade.get_widget("spin_daemon_port").get_value_as_int() new_core_config["allow_remote"] = \ self.glade.get_widget("chk_allow_remote_connections").get_active() + + ## Queue tab ## + new_core_config["queue_new_to_top"] = \ + self.glade.get_widget("chk_queue_new_top").get_active() + new_core_config["max_active_seeding"] = \ + self.glade.get_widget("spin_seeding").get_value_as_int() + new_core_config["max_active_downloading"] = \ + self.glade.get_widget("spin_downloading").get_value_as_int() + new_core_config["queue_finished_to_bottom"] = \ + self.glade.get_widget("chk_finished_bottom").get_active() + new_core_config["stop_seed_at_ratio"] = \ + self.glade.get_widget("chk_seed_ratio").get_active() + new_core_config["remove_seed_at_ratio"] = \ + self.glade.get_widget("chk_remove_ratio").get_active() + new_core_config["stop_seed_ratio"] = \ + self.glade.get_widget("spin_share_ratio").get_value() # GtkUI for key in new_gtkui_config.keys(): From 87a59662e4b1d678f95ef968e3ef8307f80e6484 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 3 Mar 2008 02:41:44 +0000 Subject: [PATCH 0573/1009] Modify common.TORRENT_STATE to be a list instead of a dictionary. The state is now sent as a string instead of an int. This will break UIs. --- deluge/common.py | 18 +++++++------ deluge/core/torrent.py | 13 +++++----- deluge/core/torrentqueue.py | 12 ++++++++- deluge/ui/gtkui/listview.py | 2 +- deluge/ui/gtkui/sidebar.py | 8 +++--- deluge/ui/gtkui/toolbar.py | 2 +- deluge/ui/gtkui/torrentview.py | 46 +++++++++++++++++----------------- 7 files changed, 56 insertions(+), 45 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index 22f1526cb..7361c0183 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -51,14 +51,16 @@ LT_TORRENT_STATE = { "Paused": 8 } -TORRENT_STATE = { - "Allocating": 0, - "Checking": 1, - "Downloading": 2, - "Seeding": 3, - "Paused": 4, - "Error": 5 -} +TORRENT_STATE = [ + "Allocating", + "Checking", + "Downloading", + "Seeding", + "Paused", + "Error", + "Queued" +] + def get_version(): """Returns the program version from the egg metadata""" return pkg_resources.require("Deluge")[0].version.split("r")[0] diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 1553c75cc..f6f2ce569 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -67,7 +67,7 @@ class Torrent: # Where the torrent is being saved to self.save_path = save_path # The state of the torrent - self.state = None + self.state = "Paused" # Holds status info so that we don't need to keep getting it from lt self.status = self.handle.status() @@ -146,15 +146,16 @@ class Torrent: def set_state(self, state): """Accepts state strings, ie, "Paused", "Seeding", etc.""" + if state not in TORRENT_STATE: + log.debug("Trying to set an invalid state %s", state) + return + # Only set 'Downloading' or 'Seeding' state if not paused if state == "Downloading" or state == "Seeding": if self.handle.is_paused(): state = "Paused" - try: - self.state = TORRENT_STATE[state] - except: - pass + self.state = state def get_eta(self): """Returns the ETA in seconds for this torrent""" @@ -308,7 +309,7 @@ class Torrent: def resume(self): """Resumes this torrent""" - if self.state != TORRENT_STATE["Paused"]: + if self.state != "Paused": return False try: diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py index 687150d1b..08922fbe2 100644 --- a/deluge/core/torrentqueue.py +++ b/deluge/core/torrentqueue.py @@ -40,7 +40,17 @@ class TorrentQueue(component.Component): component.Component.__init__(self, "TorrentQueue", depend=["TorrentManager"]) # This is a list of torrent_ids in the queueing order self.queue = [] - + + self.torrents = component.get("TorrentManager") + + def update(self): + pass + # seeding = [] + # downloading = [] + + # for torrent_id in self.torrents.get_torrent_list(): + # if self.torrents[torrent_id].get_status(["state"], + def set_size(self, size): """Clear and set the self.queue list to the length of size""" log.debug("Setting queue size to %s..", size) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 75bc1dcc2..8da2ac506 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -462,7 +462,7 @@ class ListView: return True - def add_texticon_column(self, header, col_types=[int, str], + def add_texticon_column(self, header, col_types=[str, str], sortid=1, hidden=False, position=None, diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 3e8ad2379..1af71b4e2 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -38,8 +38,6 @@ import deluge.component as component import deluge.common from deluge.log import LOG as log -TORRENT_STATE = deluge.common.TORRENT_STATE - class SideBar(component.Component): def __init__(self): component.Component.__init__(self, "SideBar") @@ -104,11 +102,11 @@ class SideBar(component.Component): component.get("TorrentView").set_filter(None, None) if value == "Downloading": component.get("TorrentView").set_filter("state", - TORRENT_STATE["Downloading"]) + "Downloading") if value == "Seeding": component.get("TorrentView").set_filter("state", - TORRENT_STATE["Seeding"]) + "Seeding") if value == "Paused": component.get("TorrentView").set_filter("state", - TORRENT_STATE["Paused"]) + "Paused") diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index 230c33fe6..5f00e6727 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -182,7 +182,7 @@ class ToolBar(component.Component): except KeyError, e: log.debug("Error getting torrent state: %s", e) continue - if status == TORRENT_STATE["Paused"]: + if status == "Paused": resume = True else: pause = True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 959f4c171..5ccdbf335 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -48,8 +48,6 @@ from deluge.ui.client import aclient as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview -TORRENT_STATE = deluge.common.TORRENT_STATE - # Status icons.. Create them from file only once to avoid constantly # re-creating them. icon_downloading = gtk.gdk.pixbuf_new_from_file( @@ -60,35 +58,37 @@ icon_inactive = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("inactive16.png")) icon_alert = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("alert16.png")) - +icon_queued = gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("queued16.png")) + # Holds the info for which status icon to display based on state -ICON_STATE = [ - icon_inactive, - icon_inactive, - icon_downloading, - icon_seeding, - icon_inactive, - icon_alert -] +ICON_STATE = { + "Allocating": icon_inactive, + "Checking": icon_inactive, + "Downloading": icon_downloading, + "Seeding": icon_seeding, + "Paused": icon_inactive, + "Error": icon_alert, + "Queued": icon_queued +} def cell_data_statusicon(column, cell, model, row, data): """Display text with an icon""" - icon = ICON_STATE[model.get_value(row, data)] - if cell.get_property("pixbuf") != icon: - cell.set_property("pixbuf", icon) - + try: + icon = ICON_STATE[model.get_value(row, data)] + if cell.get_property("pixbuf") != icon: + cell.set_property("pixbuf", icon) + except KeyError: + pass + def cell_data_progress(column, cell, model, row, data): """Display progress bar with text""" - (value, text) = model.get(row, *data) + (value, state_str) = model.get(row, *data) if cell.get_property("value") != value: cell.set_property("value", value) - state_str = "" - for key in TORRENT_STATE.keys(): - if TORRENT_STATE[key] == text: - state_str = key - break + textstr = "%s" % state_str - if state_str != "Seeding" and state_str != "Finished" and value < 100: + if state_str != "Seeding" and value < 100: textstr = textstr + " %.2f%%" % value if cell.get_property("text") != textstr: cell.set_property("text", textstr) @@ -125,7 +125,7 @@ class TorrentView(listview.ListView, component.Component): status_field=["total_size"]) self.add_progress_column(_("Progress"), status_field=["progress", "state"], - col_types=[float, int], + col_types=[float, str], function=cell_data_progress) self.add_func_column(_("Seeders"), listview.cell_data_peer, From 8de2946da2b44322780c96212c5faa66212a26c2 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 3 Mar 2008 04:54:52 +0000 Subject: [PATCH 0574/1009] Queueing updates. --- deluge/core/torrent.py | 3 +- deluge/core/torrentmanager.py | 5 ++- deluge/core/torrentqueue.py | 65 +++++++++++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index f6f2ce569..981da709e 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -275,7 +275,8 @@ class Torrent: "eta": self.get_eta, "ratio": self.get_ratio, "file_progress": self.handle.file_progress, - "queue": self.get_queue_position + "queue": self.get_queue_position, + "is_seed": self.handle.is_seed, } self.status = None diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 21ba9e9c0..04cb8d5d5 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -327,12 +327,15 @@ class TorrentManager(component.Component): # Remove the .fastresume if it exists self.torrents[torrent_id].delete_fastresume() + # Remove the torrent from the queue + self.queue.remove(torrent_id) + # Remove the torrent from deluge's session try: del self.torrents[torrent_id] except KeyError, ValueError: return False - + # Save the session state self.save_state() return True diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py index 08922fbe2..af87c5bb0 100644 --- a/deluge/core/torrentqueue.py +++ b/deluge/core/torrentqueue.py @@ -33,6 +33,7 @@ import deluge.component as component import deluge.common +from deluge.configmanager import ConfigManager from deluge.log import LOG as log class TorrentQueue(component.Component): @@ -42,15 +43,67 @@ class TorrentQueue(component.Component): self.queue = [] self.torrents = component.get("TorrentManager") + self.config = ConfigManager("core.conf") def update(self): - pass - # seeding = [] - # downloading = [] - - # for torrent_id in self.torrents.get_torrent_list(): - # if self.torrents[torrent_id].get_status(["state"], + seeding = [] + queued_seeding = [] + downloading = [] + queued_downloading = [] + for torrent_id in self.torrents.get_torrent_list(): + if self.torrents[torrent_id].get_status(["state"])["state"] == "Seeding": + seeding.append((self.queue.index(torrent_id), torrent_id)) + elif self.torrents[torrent_id].get_status(["state"])["state"] == "Downloading": + downloading.append((self.queue.index(torrent_id), torrent_id)) + elif self.torrents[torrent_id].get_status(["state"])["state"] == "Queued": + if self.torrents[torrent_id].get_status(["is_seed"])["is_seed"]: + queued_seeding.append((self.queue.index(torrent_id), torrent_id)) + else: + queued_downloading.append((self.queue.index(torrent_id), torrent_id)) + + # We need to sort these lists by queue position + seeding.sort() + downloading.sort() + queued_downloading.sort() + queued_seeding.sort() + +# log.debug("total seeding: %s", len(seeding)) +# log.debug("total downloading: %s", len(downloading)) + + if self.config["max_active_seeding"] > -1: + if len(seeding) > self.config["max_active_seeding"]: + # We need to queue some more torrents because we're over the active limit + num_to_queue = len(seeding) - self.config["max_active_seeding"] + for (pos, torrent_id) in seeding[-num_to_queue:]: + self.torrents[torrent_id].set_state("Queued") + else: + # We need to unqueue more torrents if possible + num_to_unqueue = self.config["max_active_seeding"] - len(seeding) + to_unqueue = [] + if num_to_unqueue <= len(queued_seeding): + to_unqueue = queued_seeding[:num_to_unqueue] + else: + to_unqueue = queued_seeding + for (pos, torrent_id) in to_unqueue: + self.torrents[torrent_id].set_state("Seeding") + + if self.config["max_active_downloading"] > -1: + if len(downloading) > self.config["max_active_downloading"]: + num_to_queue = len(downloading) - self.config["max_active_downloading"] + for (pos, torrent_id) in downloading[-num_to_queue:]: + self.torrents[torrent_id].set_state("Queued") + else: + # We need to unqueue more torrents if possible + num_to_unqueue = self.config["max_active_downloading"] - len(downloading) + to_unqueue = [] + if num_to_unqueue <= len(queued_downloading): + to_unqueue = queued_downloading[:num_to_unqueue] + else: + to_unqueue = queued_downloading + for (pos, torrent_id) in to_unqueue: + self.torrents[torrent_id].set_state("Downloading") + def set_size(self, size): """Clear and set the self.queue list to the length of size""" log.debug("Setting queue size to %s..", size) From 0e31d4243c47b8e0df2c41c54e4b697ea4f136b7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 3 Mar 2008 09:05:02 +0000 Subject: [PATCH 0575/1009] Add 'Queued' label to SideBar. Fix the labels filter from not displaying properly. --- deluge/ui/gtkui/listview.py | 11 +++++++++++ deluge/ui/gtkui/sidebar.py | 11 +++++++---- deluge/ui/gtkui/torrentview.py | 19 +++++++++++++------ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 8da2ac506..1c67eeb53 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -211,6 +211,17 @@ class ListView: else: return self.columns[name].column_indices[0] + def get_state_field_column(self, field): + """Returns the column number for the state field""" + for column in self.columns.keys(): + if self.columns[column].status_field == None: + continue + + for f in self.columns[column].status_field: + if field == f: + return self.columns[column].column_indices[ + self.columns[column].status_field.index(f)] + def on_menuitem_toggled(self, widget): """Callback for the generated column menuitems.""" # Get the column name from the widget diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 1af71b4e2..a48dc2f22 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -57,6 +57,9 @@ class SideBar(component.Component): self.liststore.append([_("Seeding"), gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("seeding16.png"))]) + self.liststore.append([_("Queued"), + gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("queued16.png"))]) self.liststore.append([_("Paused"), gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("inactive16.png"))]) @@ -104,9 +107,9 @@ class SideBar(component.Component): component.get("TorrentView").set_filter("state", "Downloading") if value == "Seeding": - component.get("TorrentView").set_filter("state", - "Seeding") + component.get("TorrentView").set_filter("state", "Seeding") + if value == "Queued": + component.get("TorrentView").set_filter("state", "Queued") if value == "Paused": - component.get("TorrentView").set_filter("state", - "Paused") + component.get("TorrentView").set_filter("state", "Paused") diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 5ccdbf335..dd268ff89 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -187,7 +187,8 @@ class TorrentView(listview.ListView, component.Component): def _on_session_state(self, state): for torrent_id in state: self.add_row(torrent_id) - + + self.update_filter() self.update() def stop(self): @@ -201,7 +202,12 @@ class TorrentView(listview.ListView, component.Component): def set_filter(self, field, condition): """Sets filters for the torrentview..""" + if self.filter != (None, None): + self.filter = (None, None) + self.update_filter() + self.filter = (field, condition) + self.update_filter() self.update() def send_status_request(self, columns=None): @@ -254,7 +260,7 @@ class TorrentView(listview.ListView, component.Component): client.get_torrents_status( self._on_get_torrents_status, torrent_ids, status_keys) - def update(self): + def update_filter(self): # Update the filter view def foreachrow(model, path, row, data): filter_column = self.columns["filter"].column_indices[0] @@ -266,10 +272,8 @@ class TorrentView(listview.ListView, component.Component): return torrent_id = model.get_value(row, 0) - try: - value = self.status[torrent_id][field] - except: - return + value = model.get_value(row, self.get_state_field_column(field)) + # Condition is True, so lets show this row, if not we hide it if value == condition: model.set_value(row, filter_column, True) @@ -277,6 +281,8 @@ class TorrentView(listview.ListView, component.Component): model.set_value(row, filter_column, False) self.liststore.foreach(foreachrow, self.filter) + + def update(self): # Send a status request self.send_status_request() @@ -349,6 +355,7 @@ class TorrentView(listview.ListView, component.Component): row, self.columns["torrent_id"].column_indices[0], torrent_id) + self.update() def remove_row(self, torrent_id): """Removes a row with torrent_id""" From 985ccb3187600f6767b2710060f30f1d6625406a Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 4 Mar 2008 12:04:08 +0000 Subject: [PATCH 0576/1009] webui:fix TORRENT_STATE --- deluge/ui/webui/pages.py | 2 ++ .../templates/advanced/static/advanced.css | 5 +++-- deluge/ui/webui/utils.py | 17 ++++++++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/deluge/ui/webui/pages.py b/deluge/ui/webui/pages.py index efe7e44b8..20bffd9e5 100644 --- a/deluge/ui/webui/pages.py +++ b/deluge/ui/webui/pages.py @@ -396,9 +396,11 @@ class daemon_control: proxy.shutdown() elif command == 'start': self.start() + return do_redirect() elif command == 'restart': proxy.shutdown() self.start() + return do_redirect() else: raise Exception('Unknown command:"%s"' % command) diff --git a/deluge/ui/webui/templates/advanced/static/advanced.css b/deluge/ui/webui/templates/advanced/static/advanced.css index 5f97efbbc..4861d7f8f 100644 --- a/deluge/ui/webui/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/templates/advanced/static/advanced.css @@ -222,8 +222,9 @@ body.inner { height:20px; } +/* #toolbar select{ - /*border:1px solid #68a;*/ + border:1px solid #68a; border:0; background-color: #37506f; color: #FFF; @@ -231,7 +232,7 @@ body.inner { #toolbar select:hover{ background-color:#68a; } - +*/ a.toolbar_btn { width:20px; height:20px; diff --git a/deluge/ui/webui/utils.py b/deluge/ui/webui/utils.py index 9310dc3ac..e441f7f07 100644 --- a/deluge/ui/webui/utils.py +++ b/deluge/ui/webui/utils.py @@ -190,7 +190,7 @@ def enhance_torrent_status(torrent_id,status): else: status.action = "stop" - status.message = (STATE_MESSAGES[status.state]) + status.message = _(status.state) #add some pre-calculated values status.update({ @@ -241,12 +241,13 @@ def get_categories(torrent_list): def filter_torrent_state(torrent_list,filter_name): #redesign filters on status field. filters = { - 'allocating': lambda t: (t.state == 0), - 'checking': lambda t: (t.state == 1), - 'downloading': lambda t: (t.state == 2), - 'seeding':lambda t: (t.state == 3), - 'paused':lambda t: (t.state == 4), - 'error':lambda t: (t.state == 5), + 'allocating': lambda t: (t.state == 'Allocating'), + 'checking': lambda t: (t.state == 'Checking'), + 'downloading': lambda t: (t.state == 'Downloadig'), + 'seeding':lambda t: (t.state == 'Seeding'), + 'paused':lambda t: (t.state == 'Paused'), + 'error':lambda t: (t.state == 'Error'), + 'queued':lambda t: (t.state == 'Queued'), 'traffic':lambda t: (t.download_rate > 0 or t.upload_rate > 0) } filter_func = filters[filter_name] @@ -266,8 +267,10 @@ def get_category_choosers(torrent_list): (_('Allocating'),'allocating') , (_('Checking'),'checking') , (_('Downloading'),'downloading') , + (_('Seeding'),'seeding') , (_('Paused'),'paused'), (_('Error'),'error'), + (_('Queued'),'queued'), (_('Traffic'),'traffic') ]: title += ' (%s)' % ( From 8cf72ca2dc97900546d4de96d4d30527fba811a7 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 4 Mar 2008 19:51:31 +0000 Subject: [PATCH 0577/1009] organize core-plugin --- deluge/plugins/organize/organize/__init__.py | 56 ++++++++ deluge/plugins/organize/organize/core.py | 135 +++++++++++++++++++ deluge/plugins/organize/organize/test.py | 41 ++++++ deluge/plugins/organize/setup.py | 55 ++++++++ 4 files changed, 287 insertions(+) create mode 100644 deluge/plugins/organize/organize/__init__.py create mode 100644 deluge/plugins/organize/organize/core.py create mode 100644 deluge/plugins/organize/organize/test.py create mode 100644 deluge/plugins/organize/setup.py diff --git a/deluge/plugins/organize/organize/__init__.py b/deluge/plugins/organize/organize/__init__.py new file mode 100644 index 000000000..140cfc73f --- /dev/null +++ b/deluge/plugins/organize/organize/__init__.py @@ -0,0 +1,56 @@ +# +# __init__.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + +from deluge.plugins.init import PluginBase + +class CorePlugin(PluginBase): + def __init__(self, plugin_api, plugin_name): + # Load the Core portion of the plugin + try: + from core import Core + self.plugin = Core(plugin_api, plugin_name) + except Exception, e: + log.debug("Did not load a Core plugin: %s", e) +""" +class GtkUIPlugin(PluginBase): + def __init__(self, plugin_api, plugin_name): + # Load the GtkUI portion of the plugin + try: + from gtkui import GtkUI + self.plugin = GtkUI(plugin_api, plugin_name) + except Exception, e: + log.debug("Did not load a GtkUI plugin: %s", e) +""" + diff --git a/deluge/plugins/organize/organize/core.py b/deluge/plugins/organize/organize/core.py new file mode 100644 index 000000000..0b7078c86 --- /dev/null +++ b/deluge/plugins/organize/organize/core.py @@ -0,0 +1,135 @@ +""" +torrent-organize core plugin. + +adds a status field for tracker. +""" + +from deluge.log import LOG as log +from deluge.plugins.corepluginbase import CorePluginBase + +from urlparse import urlparse + +STATE_FILTERS = { + 'Allocating': lambda t: (t.state == 'Allocating'), + 'Checking': lambda t: (t.state == 'Checking'), + 'Downloading': lambda t: (t.state == 'Downloadig'), + 'Seeding':lambda t: (t.state == 'Seeding'), + 'Paused':lambda t: (t.state == 'Paused'), + 'Error':lambda t: (t.state == 'Error'), + 'Queued':lambda t: (t.state == 'Queued') + #'Traffic':lambda t: (t.download_payload_rate > 0 or t.upload_payload_rate > 0) +} + +class Core(CorePluginBase): + def enable(self): + log.info("*** START Organize plugin***") + + self.plugin.register_status_field("tracker_name", self._status_get_tracker) + log.debug("Organize plugin enabled..") + + #__init__.... + core = self.plugin.get_core() + self.torrents = core.torrents.torrents + + def disable(self): + # De-register the label field + + self.plugin.deregister_status_field("tracker_name") + + def update(self): + pass + + + ## Utils ## + def get_tracker(self, torrent): + "returns 1st tracker." + log.debug(torrent) + log.debug(torrent.trackers) + if not torrent.trackers: + return 'tracker-less' + url = urlparse(torrent.trackers[0]['url']) + if hasattr(url,'hostname'): + return (url.hostname or 'unknown?') + return 'No-tracker?' + + ## Filters ## + def filter_state(self, torrents, state): + "in/out: a list of torrent objects." + filter_func = STATE_FILTERS[state] + return [t for t in torrents if filter_func(t)] + + def filter_tracker(self, torrents, tracker): + "in/out: a list of torrent objects." + return [t for t in torrents if self.get_tracker(t) == tracker] + + def filter_keyword(self, torrents, keyword): + "in/out: a list of torrent objects." + return [t for t in torrents if keyword in t.filename.lower()] + + ## Public ## + def export_state_filter_items(self): + """ + returns a sorted list of tuples: + [(state, count), ...] + """ + #maybe I should explain this.. .. + #or just fix it to be less generic, and more readable. + return [("All", len(self.torrents))] + [ + (state, len(self.filter_state(self.torrents.values(), state))) + for state in STATE_FILTERS] + + def export_tracker_filter_items(self): + """ + returns a sorted list of tuples: + [(tracker_name, count), ...] + """ + trackers = [self.get_tracker(t) for t in self.torrents.values()] + tcounter = {} + for tracker in trackers: + tcounter[tracker] = tcounter.get(tracker,0) + 1 + #tcounter= {'tracker-name':count, ...} + return sorted([x for x in tcounter.iteritems()]) + + def export_all_filter_items(self): + """ + for sclient, returns:{ + 'tracker':, + 'state':} + """ + return { + 'tracker':self.export_tracker_filter_items(), + 'state':self.export_state_filter_items() + } + + def export_get_session_state(self,filter_dict): + """ + in: a dict of filters: + { + 'keyword':'a keyword', + 'state':'Seeding', + 'tracker':'tracker.aelitis.com' + } + returns a list of torrent_id's + """ + torrents = self.torrents.values() + + if 'keyword' in filter_dict: + filter_dict['keyword'] = filter_dict['keyword'].lower() + torrents = self.filter_keyword(torrents, filter_dict['keyword']) + + if 'state' in filter_dict and filter_dict['state'] <> "All": + torrents = self.filter_state(torrents, filter_dict['state']) + + if 'tracker' in filter_dict: + torrents = self.filter_tracker(torrents, filter_dict['tracker']) + + return [t.torrent_id for t in torrents] + + + ## Status fields ## + def _status_get_tracker(self, torrent_id): + return self.get_tracker(self.torrents[torrent_id]) + +if __name__ == "__main__": + import test + diff --git a/deluge/plugins/organize/organize/test.py b/deluge/plugins/organize/organize/test.py new file mode 100644 index 000000000..fbc575e45 --- /dev/null +++ b/deluge/plugins/organize/organize/test.py @@ -0,0 +1,41 @@ +from deluge.ui.client import sclient + +sclient.set_core_uri() + +ids = sclient.get_session_state() + +for t in sclient.get_torrents_status(ids, ['name','tracker_name','tracker']).itervalues(): + print t + +for tracker,count in sclient.organize_tracker_filter_items(): + print tracker, count + +for state,count in sclient.organize_state_filter_items(): + print state, count + +print sclient.organize_all_filter_items() + + +print 'tracker.aelitis.com:' +print sclient.organize_get_session_state({'tracker':'tracker.aelitis.com'} ) + +print 'no results' +print sclient.organize_get_session_state({'tracker':'no results'} ) + + +print 'seeding' +print sclient.organize_get_session_state({'state':'Seeding'} ) + +print 'paused' +print sclient.organize_get_session_state({'state':'Paused'} ) + +print 'seeding+tracker.aelitis.com' +print sclient.organize_get_session_state({ + 'tracker':'tracker.aelitis.com', + 'state':'Seeding'}) + +print 'on keyword:' +print sclient.organize_get_session_state({'keyword':'client'}) + +print 'on keyword:no results' +print sclient.organize_get_session_state({'keyword':'lasjhdinewhjdeg'}) diff --git a/deluge/plugins/organize/setup.py b/deluge/plugins/organize/setup.py new file mode 100644 index 000000000..486b83947 --- /dev/null +++ b/deluge/plugins/organize/setup.py @@ -0,0 +1,55 @@ +# setup.py +# +# Copyright (C) 2008 Martijn Voncken +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +""" +Organize plugin. + +Offers filters on state,tracker and keyword. +Core only for now, moving builtin webui stuff to a plugin. + +Future: Labels/Tags +""" + +from setuptools import setup + +__author__ = "Martijn Voncken" + +setup( + name="Organize", + version="0.1", + description=__doc__, + author=__author__, + packages=["organize"], + #package_data = {"testp": ["glade/*.glade"]}, + entry_points=""" + [deluge.plugin.core] + Organize = organize:CorePlugin + """ +) From db24c543fee4a7cd3fee729a2d2a0a850293677a Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 4 Mar 2008 19:52:06 +0000 Subject: [PATCH 0578/1009] webui:move filter-logic to organize-plugin --- deluge/ui/webui/pages.py | 48 +++++--- deluge/ui/webui/render.py | 5 +- deluge/ui/webui/templates/advanced/index.html | 5 +- .../templates/advanced/part_categories.html | 23 ++-- deluge/ui/webui/utils.py | 103 +++--------------- 5 files changed, 66 insertions(+), 118 deletions(-) diff --git a/deluge/ui/webui/pages.py b/deluge/ui/webui/pages.py index 20bffd9e5..21b19ed16 100644 --- a/deluge/ui/webui/pages.py +++ b/deluge/ui/webui/pages.py @@ -162,39 +162,53 @@ class index: @deco.deluge_page @deco.auto_refreshed def GET(self, name): - vars = web.input(sort=None, order=None ,filter=None , category=None) + vars = web.input(sort=None, order=None ,state=None , tracker=None,keyword=None) - torrent_list = get_torrent_list() - all_torrents = torrent_list[:] + organize_filters = {} + if 'Organize' in proxy.get_enabled_plugins(): + filter_dict = {} + #organize-filters + #todo: DRY (in less lines of code) + if vars.state: + filter_dict['state'] = vars.state + setcookie("state", vars.state) + else: + setcookie("filter", "") + + if vars.tracker: + filter_dict['tracker'] = vars.tracker + setcookie("tracker", vars.tracker) + else: + setcookie("tracker", "") + + if vars.keyword: + filter_dict['keyword'] = vars.keyword + setcookie("keyword", vars.keyword) + else: + setcookie("keyword", "") + + torrent_ids = proxy.organize_get_session_state(filter_dict) + organize_filters = Storage(proxy.organize_all_filter_items()) - #filter-state - if vars.filter: - torrent_list = filter_torrent_state(torrent_list, vars.filter) - setcookie("filter", vars.filter) else: - setcookie("filter", "") + torrent_ids = proxy.get_session_state() - #filter-cat - if vars.category: - torrent_list = [t for t in torrent_list if t.category == vars.category] - setcookie("category", vars.category) - else: - setcookie("category", "") + torrent_list = utils.get_enhanced_torrent_list(torrent_ids) #sorting: if vars.sort: try: torrent_list.sort(key=attrgetter(vars.sort)) except: - log.debug('Sorting Failed') + log.error('Sorting Failed') if vars.order == 'up': torrent_list = list(reversed(torrent_list)) setcookie("order", vars.order) setcookie("sort", vars.sort) - - return render.index(torrent_list, all_torrents) + log.debug("filters=%s,plugins=%s" % (organize_filters, proxy.get_enabled_plugins())) + return render.index(torrent_list, organize_filters) class torrent_info: @deco.deluge_page diff --git a/deluge/ui/webui/render.py b/deluge/ui/webui/render.py index 15061e3f8..87f1a7152 100644 --- a/deluge/ui/webui/render.py +++ b/deluge/ui/webui/render.py @@ -93,10 +93,12 @@ def error_page(error): print render.error(error) #template-defs: - +""" +obsolete: def category_tabs(torrent_list): filter_tabs, category_tabs = get_category_choosers(torrent_list) return render.part_categories(filter_tabs, category_tabs) +""" def template_crop(text, end): try: @@ -160,7 +162,6 @@ def ftime(val): template.Template.globals.update({ 'sort_head': template_sort_head, 'part_stats':template_part_stats, - 'category_tabs':category_tabs, 'crop': template_crop, 'crop_left': template_crop_left, '_': _ , #gettext/translations diff --git a/deluge/ui/webui/templates/advanced/index.html b/deluge/ui/webui/templates/advanced/index.html index 9348c3162..7c735d7e6 100644 --- a/deluge/ui/webui/templates/advanced/index.html +++ b/deluge/ui/webui/templates/advanced/index.html @@ -1,4 +1,4 @@ -$def with (torrent_list, all_torrents) +$def with (torrent_list, organize_filters) $:render.header(_('Torrent list')) + diff --git a/deluge/ui/webui/templates/advanced/index.html b/deluge/ui/webui/templates/advanced/index.html index bde035128..e42c81519 100644 --- a/deluge/ui/webui/templates/advanced/index.html +++ b/deluge/ui/webui/templates/advanced/index.html @@ -1,112 +1,16 @@ $def with (torrent_list, organize_filters) $:render.header(_('Torrent list')) - -
          - -$for id, title, image, flag, method, url, important in toolbar_items: - $if important: - - +$:render.part_toolbar() $if organize_filters: - $:render.part_categories(organize_filters) - -
          - -
          - - - - $:(sort_head('calc_state_str', 'S')) - $:(sort_head('queue', '#')) - $:(sort_head('name', _('Name'))) - $:(sort_head('total_size', _('Size'))) - $:(sort_head('progress', _('Progress'))) - - $if organize_filters: - - $:(sort_head('tracker_name', _('Tracker'))) - - $:(sort_head('num_seeds', _('Seeders'))) - $:(sort_head('num_peers', _('Peers'))) - $:(sort_head('download_rate', _('Download'))) - $:(sort_head('upload_rate', _('Upload'))) - $:(sort_head('eta', _('Eta'))) - $:(sort_head('distributed_copies', _('Ava'))) - $:(sort_head('ratio', _('Ratio'))) - - - -$altrow(True) -$#4-space indentation is mandatory for for-loops in templetor! -$for torrent in torrent_list: - - - - - - - $if organize_filters: - - - - $else: - 0 - - - - - - -
          -
          - -
          -
          $torrent.queue - $(crop(torrent.name, 40))$fsize(torrent.total_size) -
          -
          - $torrent.message $int(torrent.progress) % -
          -
          -
          $torrent.tracker_name - $if torrent.total_seeds != -1: - $torrent.num_seeds ($torrent.total_seeds) - $else: - 0 - - $if torrent.total_peers != -1: - $torrent.num_peers ($torrent.total_peers) - $if (torrent.download_rate): - $fspeed(torrent.download_rate) - $else: -   - - $if (torrent.upload_rate): - $fspeed(torrent.upload_rate) - $else: -   - $ftime(torrent.eta)$("%.3f" % torrent.distributed_copies)$("%.3f" % torrent.ratio) -
          + $:render.part_organize(organize_filters)
          +$:render.part_torrent_list(torrent_list, organize_filters) +$:render.part_auto_refresh() $:part_stats()
          diff --git a/deluge/ui/webui/templates/advanced/meta.cfg b/deluge/ui/webui/templates/advanced/meta.cfg new file mode 100644 index 000000000..0ceae245a --- /dev/null +++ b/deluge/ui/webui/templates/advanced/meta.cfg @@ -0,0 +1,10 @@ +{ + 'authors':['Martijn Voncken '], + 'inherits':['deluge'], + 'comment':""" + The default template. + Uses advanced css/javascript. + You need a newer standards compliant browser. + """ +} + diff --git a/deluge/ui/webui/templates/advanced/part_auto_refresh.html b/deluge/ui/webui/templates/advanced/part_auto_refresh.html new file mode 100644 index 000000000..2c2b7153a --- /dev/null +++ b/deluge/ui/webui/templates/advanced/part_auto_refresh.html @@ -0,0 +1,16 @@ +
          +[ +$_('Auto refresh:') +$if getcookie('auto_refresh') == '1': + ($getcookie('auto_refresh_secs')) $_('seconds')   + $_('Set') + $_('Disable') +$else: + $_('Off')   + $_('Enable') +] + + +$_('Admin')   + +
          \ No newline at end of file diff --git a/deluge/ui/webui/templates/advanced/part_stats.html b/deluge/ui/webui/templates/advanced/part_stats.html index fca2e0bad..0a38f0789 100644 --- a/deluge/ui/webui/templates/advanced/part_stats.html +++ b/deluge/ui/webui/templates/advanced/part_stats.html @@ -1,23 +1,6 @@ $def with (stats) -
          -[ -$_('Auto refresh:') -$if getcookie('auto_refresh') == '1': - ($getcookie('auto_refresh_secs')) $_('seconds')   - $_('Set') - $_('Disable') -$else: - $_('Off')   - $_('Enable') -] - - -$_('Admin')   - -
          -
          @@ -36,10 +19,3 @@ $else:
          - - - - - diff --git a/deluge/ui/webui/templates/advanced/part_toolbar.html b/deluge/ui/webui/templates/advanced/part_toolbar.html new file mode 100644 index 000000000..0399b2c39 --- /dev/null +++ b/deluge/ui/webui/templates/advanced/part_toolbar.html @@ -0,0 +1,6 @@ +$for id, title, image, flag, method, url, important in toolbar_items: + $if important: + diff --git a/deluge/ui/webui/templates/advanced/part_torrent_list.html b/deluge/ui/webui/templates/advanced/part_torrent_list.html new file mode 100644 index 000000000..bd27abb48 --- /dev/null +++ b/deluge/ui/webui/templates/advanced/part_torrent_list.html @@ -0,0 +1,90 @@ +$def with (torrent_list, organize_filters) + + + + + + + + $:(sort_head('calc_state_str', 'S')) + $:(sort_head('queue', '#')) + $:(sort_head('name', _('Name'))) + $:(sort_head('total_size', _('Size'))) + $:(sort_head('progress', _('Progress'))) + + $if organize_filters: + + $:(sort_head('tracker_name', _('Tracker'))) + + $:(sort_head('num_seeds', _('Seeders'))) + $:(sort_head('num_peers', _('Peers'))) + $:(sort_head('download_rate', _('Download'))) + $:(sort_head('upload_rate', _('Upload'))) + $:(sort_head('eta', _('Eta'))) + $:(sort_head('distributed_copies', _('Ava'))) + $:(sort_head('ratio', _('Ratio'))) + + + +$altrow(True) +$#4-space indentation is mandatory for for-loops in templetor! +$for torrent in torrent_list: + + + + + + + $if organize_filters: + + + + $else: + 0 + + + + + + +
          +
          + +
          +
          $torrent.queue + $(crop(torrent.name, 40))$fsize(torrent.total_size) +
          +
          + $torrent.message $int(torrent.progress) % +
          +
          +
          $torrent.tracker_name + $if torrent.total_seeds != -1: + $torrent.num_seeds ($torrent.total_seeds) + $else: + 0 + + $if torrent.total_peers != -1: + $torrent.num_peers ($torrent.total_peers) + $if (torrent.download_rate): + $fspeed(torrent.download_rate) + $else: +   + + $if (torrent.upload_rate): + $fspeed(torrent.upload_rate) + $else: +   + $ftime(torrent.eta)$("%.3f" % torrent.distributed_copies)$("%.3f" % torrent.ratio) +
          diff --git a/deluge/ui/webui/templates/advanced/static/advanced.css b/deluge/ui/webui/templates/advanced/static/advanced.css index 4861d7f8f..f94c64068 100644 --- a/deluge/ui/webui/templates/advanced/static/advanced.css +++ b/deluge/ui/webui/templates/advanced/static/advanced.css @@ -71,7 +71,7 @@ div.error { } -tr.torrent_table:hover { +#torrent_table tr:hover { background-color:#68a; } diff --git a/deluge/ui/webui/templates/deluge/about.html b/deluge/ui/webui/templates/deluge/about.html index f0d1c2b1e..9e82db43f 100644 --- a/deluge/ui/webui/templates/deluge/about.html +++ b/deluge/ui/webui/templates/deluge/about.html @@ -1,8 +1,15 @@ -$:render.header(_('About')) +$:render.header(_('About'), 'about') $:render.admin_toolbar('about')

          Version

          $version 
          + +

          Template

          +Name: $get_config('template')
          +Authors: $render.meta.authors
          +Inherits: $render.meta.inherits
          +Comment: $render.meta.comment +

          Links

          • Deluge
          • @@ -34,6 +41,13 @@ $:render.admin_toolbar('about')
            • Slurdge
            + +

            Console UI

            +
              +
            • sadrul
            • +
            + +
          *and all other authors/helpers/contributors I forgot to mention diff --git a/deluge/ui/webui/templates/deluge/config.html b/deluge/ui/webui/templates/deluge/config.html index 249f2f61a..4a4b00e44 100644 --- a/deluge/ui/webui/templates/deluge/config.html +++ b/deluge/ui/webui/templates/deluge/config.html @@ -1,6 +1,6 @@ $def with (groups, pages, form, selected, message, error) -$:render.header(_("Config")) +$:render.header(_("Config"), 'config') $:render.admin_toolbar('config') diff --git a/deluge/ui/webui/templates/deluge/connect.html b/deluge/ui/webui/templates/deluge/connect.html index 9ae35785e..9524f3354 100644 --- a/deluge/ui/webui/templates/deluge/connect.html +++ b/deluge/ui/webui/templates/deluge/connect.html @@ -1,6 +1,6 @@ $def with (connect_list, connected) -$:render.header(_("Connect to Daemon")) +$:render.header(_("Connect to Daemon"), 'connect') $:render.admin_toolbar('connect') diff --git a/deluge/ui/webui/templates/deluge/header.html b/deluge/ui/webui/templates/deluge/header.html index 42cc62eed..3b0b7e8a5 100644 --- a/deluge/ui/webui/templates/deluge/header.html +++ b/deluge/ui/webui/templates/deluge/header.html @@ -1,4 +1,4 @@ -$def with (title) +$def with (title, active_tab="NONE") Deluge:$title diff --git a/deluge/ui/webui/templates/deluge/index.html b/deluge/ui/webui/templates/deluge/index.html index 1d512e98b..462b5a504 100644 --- a/deluge/ui/webui/templates/deluge/index.html +++ b/deluge/ui/webui/templates/deluge/index.html @@ -1,5 +1,9 @@ -$def with (torrent_list, organize_filter) -$:render.header(_('Torrent list')) +$def with (torrent_list, organize_filters) +$:render.header(_('Torrent list'), 'home') + + +$if organize_filters: + $:render.part_organize(organize_filters) diff --git a/deluge/ui/webui/templates/deluge/meta.cfg b/deluge/ui/webui/templates/deluge/meta.cfg new file mode 100644 index 000000000..2965a1b5e --- /dev/null +++ b/deluge/ui/webui/templates/deluge/meta.cfg @@ -0,0 +1,10 @@ +{ + 'authors':['Martijn Voncken '], + 'inherits':[], + 'comment':"""Fail-safe template. + Most other templetes inherit from this template. + Should work on ANY browser. + thanks to "somedude" for some help/enhancements in header/footer/css. + """ +} + diff --git a/deluge/ui/webui/templates/advanced/part_categories.html b/deluge/ui/webui/templates/deluge/part_organize.html similarity index 100% rename from deluge/ui/webui/templates/advanced/part_categories.html rename to deluge/ui/webui/templates/deluge/part_organize.html diff --git a/deluge/ui/webui/templates/deluge/tab_trackers.html b/deluge/ui/webui/templates/deluge/tab_trackers.html new file mode 100644 index 000000000..c0c680f3a --- /dev/null +++ b/deluge/ui/webui/templates/deluge/tab_trackers.html @@ -0,0 +1,16 @@ +$def with (torrent) + +
          +$altrow(True) + + + +$for tracker in torrent.trackers: + + + + +
          #$_("Tracker")
          + $tracker['tier'] + $crop(tracker['url'], 60)
          + diff --git a/deluge/ui/webui/templates/deluge/torrent_info.html b/deluge/ui/webui/templates/deluge/torrent_info.html index 0dfa9722f..763a1a7e4 100644 --- a/deluge/ui/webui/templates/deluge/torrent_info.html +++ b/deluge/ui/webui/templates/deluge/torrent_info.html @@ -34,9 +34,4 @@ function toggle_dump(){
          - - - -$:part_stats() - $:render.footer() diff --git a/deluge/ui/webui/templates/white/admin_toolbar.html b/deluge/ui/webui/templates/white/admin_toolbar.html new file mode 100644 index 000000000..0762da65a --- /dev/null +++ b/deluge/ui/webui/templates/white/admin_toolbar.html @@ -0,0 +1,3 @@ +$def with (active_tab) + + diff --git a/deluge/ui/webui/templates/white/header.html b/deluge/ui/webui/templates/white/header.html new file mode 100644 index 000000000..281fa0093 --- /dev/null +++ b/deluge/ui/webui/templates/white/header.html @@ -0,0 +1,46 @@ +$def with (title, active_tab="NONE") + + + + + Deluge:$_('Torrent list') + + + + + + + +
          + + + +
          +
          + Home + + +$for id, title, url in admin_pages: + $title +
          + +
          + $#:render.part_auto_refresh() + + + + + $:part_stats() +
          + +
          diff --git a/deluge/ui/webui/templates/white/index.html b/deluge/ui/webui/templates/white/index.html new file mode 100644 index 000000000..d9649e11b --- /dev/null +++ b/deluge/ui/webui/templates/white/index.html @@ -0,0 +1,80 @@ +$def with (torrent_list, organize_filters) + + + Deluge:$_('Torrent list') + + + + + + + +
          + +
          +
          +
          + Home + $for id, title, url in admin_pages: + $title + +
          + $#:render.part_auto_refresh() + + + + + $:part_stats() +
          + +
          + + + +
          +$if organize_filters: + $:render.part_organize(organize_filters) +
          +
          + +
          +
          + $:render.part_toolbar() +
          +
          + $:render.part_torrent_list(torrent_list, organize_filters) +
          + + + + + + +
          + +
          + +
          + + + + + +
          + +
          + +$:render.footer() + + diff --git a/deluge/ui/webui/templates/white/login.html b/deluge/ui/webui/templates/white/login.html new file mode 100644 index 000000000..2808a1a4f --- /dev/null +++ b/deluge/ui/webui/templates/white/login.html @@ -0,0 +1,34 @@ +$def with (error) + + + + Deluge:$_('Login') + + + + + + + +
          + +
          +$if error > 0: +
          $_("Password is invalid,try again")
          + +
          + +
          +
          + $_('Password') + +
          +
          + + +
          +
          +
          +
          +$:render.footer() diff --git a/deluge/ui/webui/templates/white/meta.cfg b/deluge/ui/webui/templates/white/meta.cfg new file mode 100644 index 000000000..3bd3a320f --- /dev/null +++ b/deluge/ui/webui/templates/white/meta.cfg @@ -0,0 +1,11 @@ +{ + 'authors':['Martijn Voncken '], + 'inherits':['deluge','advanced'], + 'comment':""" + A more conventional template. + css written from scratch + templated-css with variables. + Inspried by (but not copied from) gmail. + """ +} + diff --git a/deluge/ui/webui/templates/white/template_style.css b/deluge/ui/webui/templates/white/template_style.css new file mode 100644 index 000000000..3f2d8ca12 --- /dev/null +++ b/deluge/ui/webui/templates/white/template_style.css @@ -0,0 +1,115 @@ +$def with (style) +/* +preprocessed style. +avoid copy and paste of colours. +*/ + + +/*global:*/ +body, th, td,tr ,hr, div,table , div{ + font-family: Bitstream Vera Sans, verdana, arial, sans-serif; + font-size:12px; + padding:0px; + margin:0px; + border-style: hidden; + border-spacing:0px; +} + +a { color: #0000AA;} +a:visited { color: #0000AA;} + +th {font-weight:normal} + +form { + display:inline; +} + +img{ + border:0; +} + +/*controls:*/ +a.tab_button_active { + color: #0000AA; + text-decoration:none; + font-weight:bold; + color:#000; +} + +div.deluge_button{ + display:inline; +} + + +/*page from top to bottom:*/ + + +/*top part :*/ +#admin_toolbar { + text-align:right; + float; +} + +#home_top { + text-align:left; + float; +} + +/*toolbar*/ +#toolbar { + background-color:#E0ECFF; +} + +#torrent_list_block{ + border-style:solid; + border-width:12px; + border-color:#C3D9FF; + -moz-border-radius:8px; + /*float: right;*/ +} + +/*torrent table*/ +XX#torrent_table td { + border-bottom-style:solid; + border-bottom-width:1px; + border-bottom-color:#000000; +} + +#torrent_table th { + background-color:#C3D9FF; +} + +tr.altrow1 { + background-color:#F0F0F0; +} + + +tr.torrent_table_selected { + background-color:#FFFFCC; +} + +div.progress_bar{ + background-color:#E0ECFF; + /*color:blue;*/ + /*-moz-border-radius:5px;*/ /*ff only setting*/ + } + + +/*info panel:*/ +#info_panel_div { + border-style:solid; + border-width:12px; + border-color:#B5EDBC; + -moz-border-radius:8px; + /*float: right;*/ +} + +#organize_block { + border-style:solid; + border-width:12px; + border-color:#B5EDBC; + -moz-border-radius:8px; + float: left; +} + +/*config:*/ diff --git a/deluge/ui/webui/templates/white/torrent_info_inner.html b/deluge/ui/webui/templates/white/torrent_info_inner.html new file mode 100644 index 000000000..a7ba9cc30 --- /dev/null +++ b/deluge/ui/webui/templates/white/torrent_info_inner.html @@ -0,0 +1,19 @@ +$def with (torrent, active_tab) + + + + Deluge:$torrent.name + + + + +$for id, title, tab in detail_tabs: +   + $:render.part_tab_button(id, title, active_tab) + +$for id, title, tab in detail_tabs: + $if active_tab == id: + $:render[tab](torrent) + + + From 7d5bd22855f0eb39f14e43bd3d3ac1a0336b25a7 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sun, 9 Mar 2008 20:32:11 +0000 Subject: [PATCH 0615/1009] white template:add wip message --- .../webui/templates/advanced/torrent_info_inner.html | 2 ++ deluge/ui/webui/templates/white/index.html | 6 ++++-- deluge/ui/webui/templates/white/template_style.css | 10 +++++++++- .../ui/webui/templates/white/torrent_info_inner.html | 3 ++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/deluge/ui/webui/templates/advanced/torrent_info_inner.html b/deluge/ui/webui/templates/advanced/torrent_info_inner.html index 735301689..91f28b508 100644 --- a/deluge/ui/webui/templates/advanced/torrent_info_inner.html +++ b/deluge/ui/webui/templates/advanced/torrent_info_inner.html @@ -7,8 +7,10 @@ $def with (torrent, active_tab) +
          $for id, title, tab in detail_tabs: $:render.part_tab_button(id, title, active_tab) +
          $for id, title, tab in detail_tabs: $if active_tab == id: diff --git a/deluge/ui/webui/templates/white/index.html b/deluge/ui/webui/templates/white/index.html index d9649e11b..e4eaad79a 100644 --- a/deluge/ui/webui/templates/white/index.html +++ b/deluge/ui/webui/templates/white/index.html @@ -18,7 +18,9 @@ $def with (torrent_list, organize_filters) $for id, title, url in admin_pages: $title - + + + work in progress.. don't report bugs on this template $#:render.part_auto_refresh() @@ -50,7 +52,7 @@ $if organize_filters: -
          +
          + +
          + - - - -
          - -
          -$if error > 0: -
          $_("Password is invalid,try again")
          - -
          - -
          -
          - $_('Password') - -
          -
          - - -
          -
          -
          -
          -$:render.footer() diff --git a/deluge/ui/webui/templates/white/part_organize.html b/deluge/ui/webui/templates/white/part_organize.html index 164b09af9..6c0ee3d3e 100644 --- a/deluge/ui/webui/templates/white/part_organize.html +++ b/deluge/ui/webui/templates/white/part_organize.html @@ -39,11 +39,13 @@ $for tracker, num in filters.tracker:
          $_('Keyword')
          +$if get('keyword'): +
          $if get('keyword'): - selected +
          diff --git a/deluge/ui/webui/templates/white/template_style.css b/deluge/ui/webui/templates/white/template_style.css index 10eaf34a9..b6b221600 100644 --- a/deluge/ui/webui/templates/white/template_style.css +++ b/deluge/ui/webui/templates/white/template_style.css @@ -32,6 +32,22 @@ img{ } /*controls:*/ +div.error { + background-color:#FFFFFF; + color:#AA0000; + font-weight:bold; + -moz-border-radius:10px; + width:200px; + margin-bottom:20px; + padding:10px; + +} +div.info { + background-color:#FFFFFF; + color: #0000AA; + font-weight:bold; +} + a.tab_button_active { color: #0000AA; text-decoration:none; @@ -47,7 +63,7 @@ div.panel { border-style:solid; border-width:2px; border-color:#C3D9FF; - background-color:#E0ECFF; + background-color:#FAFAFA; /*offf-topic-hint:allo allo*/ -moz-border-radius:5px; width:700px; margin-left:20px; @@ -55,6 +71,17 @@ div.panel { } +h2 { + background-color:#E0ECFF; + margin:0; +} + +h3 { + background-color:#E0ECFF; + margin:0; +} + + /*page from top to bottom:*/ /*top part :*/ @@ -216,9 +243,6 @@ div.progress_bar{ text-align:right; } -#config_panel table { - background-color:none; -} diff --git a/deluge/ui/webui/webserver_common.py b/deluge/ui/webui/webserver_common.py index 9b44b1096..31775a52f 100644 --- a/deluge/ui/webui/webserver_common.py +++ b/deluge/ui/webui/webserver_common.py @@ -141,6 +141,11 @@ class Ws: def init_process(self): self.config = pickle.load(open(self.config_file)) + if self.config.get('enabled_plugins') == None: + self.config['enabled_plugins'] = [] + self.save_config() + + def init_06(self, uri = 'http://localhost:58846'): proxy.set_core_uri(uri) @@ -153,8 +158,11 @@ class Ws: pickle.dump(CONFIG_DEFAULTS,f) f.close() + self.init_process() self.env = '0.6' + from render import render + render.apply_cfg() #utils for config: def get_templates(self): From 4882f5cb88010e2e06aaba47b90a6203f6a17f28 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Mon, 17 Mar 2008 20:47:27 +0000 Subject: [PATCH 0640/1009] refactor:webpy022->web.py --- deluge/ui/webui/config_forms.py | 4 ++-- deluge/ui/webui/debugerror.py | 8 +++++--- deluge/ui/webui/deluge_webserver.py | 11 ++++++++--- deluge/ui/webui/json_api.py | 2 +- deluge/ui/webui/lib/newforms_plus.py | 8 +------- deluge/ui/webui/lib/static_handler.py | 4 ++-- deluge/ui/webui/lib/webpy022/wsgi.py | 10 +++++----- deluge/ui/webui/page_decorators.py | 6 +++--- deluge/ui/webui/pages.py | 9 ++------- deluge/ui/webui/plugin_manager.py | 2 +- deluge/ui/webui/render.py | 2 +- deluge/ui/webui/torrent_add.py | 2 +- deluge/ui/webui/torrent_move.py | 2 +- deluge/ui/webui/torrent_options.py | 2 +- deluge/ui/webui/utils.py | 10 +++++----- deluge/ui/webui/web.py | 4 ++++ 16 files changed, 43 insertions(+), 43 deletions(-) create mode 100644 deluge/ui/webui/web.py diff --git a/deluge/ui/webui/config_forms.py b/deluge/ui/webui/config_forms.py index 472b274d4..7ae53a95f 100644 --- a/deluge/ui/webui/config_forms.py +++ b/deluge/ui/webui/config_forms.py @@ -33,10 +33,10 @@ import lib.newforms_plus as forms import page_decorators as deco -import lib.webpy022 as web +import web from webserver_common import ws, proxy, log from render import render -from lib.webpy022.http import seeother +from web import seeother import sys import os import utils diff --git a/deluge/ui/webui/debugerror.py b/deluge/ui/webui/debugerror.py index 5570f357e..96f1fe07e 100644 --- a/deluge/ui/webui/debugerror.py +++ b/deluge/ui/webui/debugerror.py @@ -38,12 +38,14 @@ pretty_errors_cls = { } import sys, urlparse, pprint -from lib.webpy022.net import websafe -from lib.webpy022.template import Template -import lib.webpy022.webapi as web +from web import websafe +from web import template +import web #import lib.webpy022.webapi as web import webserver_common as ws from traceback import format_tb +Template = template.Template + import os, os.path whereami = os.path.join(os.getcwd(), __file__) whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1]) diff --git a/deluge/ui/webui/deluge_webserver.py b/deluge/ui/webui/deluge_webserver.py index 5f7c847a4..ea4ff8a01 100644 --- a/deluge/ui/webui/deluge_webserver.py +++ b/deluge/ui/webui/deluge_webserver.py @@ -41,13 +41,18 @@ import config_tabs_webui #auto registers in ConfigUiManager import config_tabs_deluge #auto registers in ConfigUiManager import register_menu +#debugerror +from debugerror import deluge_debugerror +import web +web.webapi.internalerror = deluge_debugerror + from webserver_common import ws #todo: remove ws. def create_webserver(urls, methods, middleware): from webserver_common import ws - from lib.webpy022.request import webpyfunc - from lib.webpy022 import webapi + from web import webpyfunc + from web import webapi from lib.gtk_cherrypy_wsgiserver import CherryPyWSGIServer import os @@ -63,7 +68,7 @@ def create_webserver(urls, methods, middleware): return server def WebServer(debug = False): - import lib.webpy022 as web + import web from deluge import component if debug: middleware = [web.reloader] diff --git a/deluge/ui/webui/json_api.py b/deluge/ui/webui/json_api.py index e159fbd02..9459a3f4c 100644 --- a/deluge/ui/webui/json_api.py +++ b/deluge/ui/webui/json_api.py @@ -41,7 +41,7 @@ from inspect import getargspec from utils import get_torrent_status,get_category_choosers, get_stats,filter_torrent_state,fsize,fspeed from page_decorators import remote from operator import attrgetter -import lib.webpy022 as web +import web from webserver_common import proxy, log def to_json(obj): diff --git a/deluge/ui/webui/lib/newforms_plus.py b/deluge/ui/webui/lib/newforms_plus.py index 50f8c5ac9..ae55664d7 100644 --- a/deluge/ui/webui/lib/newforms_plus.py +++ b/deluge/ui/webui/lib/newforms_plus.py @@ -10,13 +10,7 @@ from newforms.forms import BoundField import sys, os -import webpy022 as web #todo:remove this dependency. - - - - - - +import web #Form class FilteredForm(newforms.Form): diff --git a/deluge/ui/webui/lib/static_handler.py b/deluge/ui/webui/lib/static_handler.py index 829d97792..98322a9ea 100644 --- a/deluge/ui/webui/lib/static_handler.py +++ b/deluge/ui/webui/lib/static_handler.py @@ -6,8 +6,8 @@ static fileserving for web.py without the need for wsgi wrapper magic. """ -import webpy022 as web -from webpy022.http import seeother, url +import web +from web import seeother, url import posixpath import urlparse diff --git a/deluge/ui/webui/lib/webpy022/wsgi.py b/deluge/ui/webui/lib/webpy022/wsgi.py index 26abf9291..e7a79f9fb 100644 --- a/deluge/ui/webui/lib/webpy022/wsgi.py +++ b/deluge/ui/webui/lib/webpy022/wsgi.py @@ -10,7 +10,7 @@ import webapi as web from utils import listget from net import validaddr, validip import httpserver - + def runfcgi(func, addr=('localhost', 8000)): """Runs a WSGI function as a FastCGI server.""" import flup.server.fcgi as flups @@ -26,14 +26,14 @@ def runwsgi(func): Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server, as appropriate based on context and `sys.argv`. """ - + if os.environ.has_key('SERVER_SOFTWARE'): # cgi os.environ['FCGI_FORCE_CGI'] = 'Y' if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi or os.environ.has_key('SERVER_SOFTWARE')): return runfcgi(func, None) - + if 'fcgi' in sys.argv or 'fastcgi' in sys.argv: args = sys.argv[1:] if 'fastcgi' in args: args.remove('fastcgi') @@ -42,7 +42,7 @@ def runwsgi(func): return runfcgi(func, validaddr(args[0])) else: return runfcgi(func, None) - + if 'scgi' in sys.argv: args = sys.argv[1:] args.remove('scgi') @@ -50,5 +50,5 @@ def runwsgi(func): return runscgi(func, validaddr(args[0])) else: return runscgi(func) - + return httpserver.runsimple(func, validip(listget(sys.argv, 1, ''))) diff --git a/deluge/ui/webui/page_decorators.py b/deluge/ui/webui/page_decorators.py index 2c326f96f..ce479aae4 100644 --- a/deluge/ui/webui/page_decorators.py +++ b/deluge/ui/webui/page_decorators.py @@ -7,9 +7,9 @@ from webserver_common import ws, log, proxy from utils import * #/relative -from lib.webpy022.webapi import cookies, setcookie as w_setcookie -from lib.webpy022.http import seeother, url -from lib.webpy022 import changequery as self_url +from web import cookies, setcookie as w_setcookie +from web import seeother, url +from web import changequery as self_url #deco's: def deluge_page_noauth(func): diff --git a/deluge/ui/webui/pages.py b/deluge/ui/webui/pages.py index e5837b521..2e2a796a5 100644 --- a/deluge/ui/webui/pages.py +++ b/deluge/ui/webui/pages.py @@ -45,12 +45,8 @@ from torrent_move import torrent_move from deluge.common import get_pixmap -#debugerror -from debugerror import deluge_debugerror -web.webapi.internalerror = deluge_debugerror - -import lib.webpy022 as web -from lib.webpy022.http import seeother, url +import web +from web import seeother, url from lib.static_handler import static_handler from torrent_add import torrent_add @@ -62,7 +58,6 @@ page_manager = component.get("PageManager") #from json_api import json_api #secuity leak, todo:fix - #routing: urls = [ "/login", "login", diff --git a/deluge/ui/webui/plugin_manager.py b/deluge/ui/webui/plugin_manager.py index d05ba0b0e..47a5d1d9f 100644 --- a/deluge/ui/webui/plugin_manager.py +++ b/deluge/ui/webui/plugin_manager.py @@ -53,7 +53,7 @@ class PluginManager(pluginmanagerbase.PluginManagerBase, self.disable_plugins() def _on_get_enabled_plugins(self, enabled_plugins): - log.debug("Core has these plugins enabled: %s", enabled_plugins) + log.debug("Webui has these plugins enabled: %s", enabled_plugins) self.config["enabled_plugins"] = enabled_plugins # Enable the plugins that are enabled in the config and core diff --git a/deluge/ui/webui/render.py b/deluge/ui/webui/render.py index ea15d55b9..b69c144be 100644 --- a/deluge/ui/webui/render.py +++ b/deluge/ui/webui/render.py @@ -34,7 +34,7 @@ from webserver_common import ws,REVNO,VERSION from utils import * #/relative from deluge import common -from lib.webpy022 import changequery as self_url, template, Storage +from web import changequery as self_url, template, Storage import os class subclassed_render(object): diff --git a/deluge/ui/webui/torrent_add.py b/deluge/ui/webui/torrent_add.py index 0eb66276b..49c9f19f3 100644 --- a/deluge/ui/webui/torrent_add.py +++ b/deluge/ui/webui/torrent_add.py @@ -34,7 +34,7 @@ import utils from render import render, error_page import page_decorators as deco import lib.newforms_plus as forms -import lib.webpy022 as web +import web class OptionsForm(forms.Form): download_location = forms.ServerFolder(_("Download Location")) diff --git a/deluge/ui/webui/torrent_move.py b/deluge/ui/webui/torrent_move.py index 9660c7a9e..ec1ed4de8 100644 --- a/deluge/ui/webui/torrent_move.py +++ b/deluge/ui/webui/torrent_move.py @@ -35,7 +35,7 @@ import utils from render import render import page_decorators as deco import lib.newforms_plus as forms -import lib.webpy022 as web +import web #Too much boilerplate code here, todo : fix it. diff --git a/deluge/ui/webui/torrent_options.py b/deluge/ui/webui/torrent_options.py index 1329a29fb..feaaade3c 100644 --- a/deluge/ui/webui/torrent_options.py +++ b/deluge/ui/webui/torrent_options.py @@ -34,7 +34,7 @@ import utils from render import template import page_decorators as deco import lib.newforms_plus as forms -import lib.webpy022 as web +import web class TorrentOptionsForm(forms.Form): diff --git a/deluge/ui/webui/utils.py b/deluge/ui/webui/utils.py index f8f810452..0409c2b77 100644 --- a/deluge/ui/webui/utils.py +++ b/deluge/ui/webui/utils.py @@ -30,12 +30,12 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import lib.webpy022 as web +import web import os -from lib.webpy022.webapi import cookies, setcookie as w_setcookie -from lib.webpy022 import changequery as self_url, template -from lib.webpy022.utils import Storage -from lib.webpy022.http import seeother, url +from web import cookies, setcookie as w_setcookie +from web import changequery as self_url, template +from web import Storage +from web import seeother, url from deluge.common import fsize,fspeed,ftime diff --git a/deluge/ui/webui/web.py b/deluge/ui/webui/web.py new file mode 100644 index 000000000..c1fca2b54 --- /dev/null +++ b/deluge/ui/webui/web.py @@ -0,0 +1,4 @@ +#compatibility: use the included version/release of web.py. +from lib.webpy022 import * +print 'tpl=',template +print list(sorted(globals().keys())) From d34356a8003026686b8ad7cb000aaff29020f88d Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Mar 2008 00:21:23 +0000 Subject: [PATCH 0641/1009] Modify how we iterate through the liststore. --- deluge/ui/gtkui/torrentview.py | 62 +++++++++++++--------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 70df9aaaf..7abfd14a7 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -246,14 +246,10 @@ class TorrentView(listview.ListView, component.Component): # Create list of torrent_ids in need of status updates torrent_ids = [] - row = self.liststore.get_iter_first() - while row != None: + for row in self.liststore: # Only add this torrent_id if it's not filtered - if self.liststore.get_value( - row, self.columns["filter"].column_indices[0]) == True: - torrent_ids.append(self.liststore.get_value( - row, self.columns["torrent_id"].column_indices[0])) - row = self.liststore.iter_next(row) + if row[self.columns["filter"].column_indices[0]] == True: + torrent_ids.append(row[self.columns["torrent_id"].column_indices[0]]) if torrent_ids == []: return @@ -265,25 +261,22 @@ class TorrentView(listview.ListView, component.Component): def update_filter(self): # Update the filter view - def foreachrow(model, path, row, data): + for row in self.liststore: filter_column = self.columns["filter"].column_indices[0] # Create a function to create a new liststore with only the # desired rows based on the filter. - field, condition = data + field, condition = self.filter if field == None and condition == None: - model.set_value(row, filter_column, True) - return - - torrent_id = model.get_value(row, 0) - value = model.get_value(row, self.get_state_field_column(field)) + row[filter_column] = True + continue + + value = row[self.get_state_field_column(field)] # Condition is True, so lets show this row, if not we hide it if value == condition: - model.set_value(row, filter_column, True) + row[filter_column] = True else: - model.set_value(row, filter_column, False) - - self.liststore.foreach(foreachrow, self.filter) + row[filter_column] = False def update(self): # Send a status request @@ -295,10 +288,8 @@ class TorrentView(listview.ListView, component.Component): """ # Update the torrent view model with data we've received status = self.status - row = self.liststore.get_iter_first() - while row != None: - torrent_id = self.liststore.get_value( - row, self.columns["torrent_id"].column_indices[0]) + for row in self.liststore: + torrent_id = row[self.columns["torrent_id"].column_indices[0]] if torrent_id in status.keys(): # Set values for each column in the row for column in self.columns_to_update: @@ -308,13 +299,10 @@ class TorrentView(listview.ListView, component.Component): # update try: # Only update if different - if self.liststore.get_value(row, column_index) != \ - status[torrent_id][ - self.columns[column].status_field[0]]: - self.liststore.set_value(row, - column_index, - status[torrent_id][ - self.columns[column].status_field[0]]) + if row[column_index] != \ + status[torrent_id][self.columns[column].status_field[0]]: + row[column_index] = status[torrent_id][ + self.columns[column].status_field[0]] except (TypeError, KeyError), e: log.warning("Unable to update column %s: %s", column, e) @@ -324,19 +312,17 @@ class TorrentView(listview.ListView, component.Component): # Only update the column if the status field exists try: # Only update if different - if self.liststore.get_value(row, index) != \ + if row[index] != \ status[torrent_id][ self.columns[column].status_field[ column_index.index(index)]]: - self.liststore.set_value(row, - index, + row[index] = \ status[torrent_id][ self.columns[column].status_field[ - column_index.index(index)]]) + column_index.index(index)]] except: pass - row = self.liststore.iter_next(row) def _on_get_torrents_status(self, status): """Callback function for get_torrents_status(). 'status' should be a @@ -363,16 +349,14 @@ class TorrentView(listview.ListView, component.Component): def remove_row(self, torrent_id): """Removes a row with torrent_id""" - row = self.liststore.get_iter_first() - while row is not None: + for row in self.liststore: # Check if this row is the row we want to remove - if self.liststore.get_value(row, 0) == torrent_id: - self.liststore.remove(row) + if row[0] == torrent_id: + self.liststore.remove(row.iter) # Force an update of the torrentview self.update() self.update_filter() break - row = self.liststore.iter_next(row) def get_selected_torrent(self): """Returns a torrent_id or None. If multiple torrents are selected, From fc9bb503df05944097902e589271495fc92640c7 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Tue, 18 Mar 2008 01:41:09 +0000 Subject: [PATCH 0642/1009] Attempt to fix issue where core will no longer pause/resume torrents. --- deluge/core/torrent.py | 8 ++++---- deluge/core/torrentmanager.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index cf1732230..7a5231475 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -151,11 +151,11 @@ class Torrent: log.debug("Trying to set an invalid state %s", state) return - if state == "Queued" and not self.handle.is_paused(): - component.get("TorrentManager").append_not_state_paused(self.torrent_id) - self.handle.pause() - if state != self.state: + if state == "Queued" and not self.handle.is_paused(): + component.get("TorrentManager").append_not_state_paused(self.torrent_id) + self.handle.pause() + self.state = state # Update the torrentqueue on any state changes diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 43f55a1cc..f38a6ec2c 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -168,7 +168,8 @@ class TorrentManager(component.Component): """Appends to a list of torrents that we will not set state Paused to when we receive the paused alert from libtorrent. The torrents are removed from this list once we receive the alert they have been paused in libtorrent.""" - self.not_state_paused.append(torrent_id) + if torrent_id not in self.not_state_paused: + self.not_state_paused.append(torrent_id) def add(self, filename, filedump=None, options=None, total_uploaded=0, trackers=None, queue=-1, state=None, save_state=True): From f78d794079c7e7199c64e2a202b7ae96ad1e5f7f Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 18 Mar 2008 17:08:30 +0000 Subject: [PATCH 0643/1009] fix webpy022 import for /lib/ --- deluge/ui/webui/config_forms.py | 2 -- deluge/ui/webui/config_tabs_deluge.py | 3 --- deluge/ui/webui/deluge_webserver.py | 2 -- deluge/ui/webui/lib/newforms_plus.py | 2 +- deluge/ui/webui/lib/static_handler.py | 4 ++-- deluge/ui/webui/templates/deluge/config.html | 7 ++++++- deluge/ui/webui/web.py | 2 -- 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/deluge/ui/webui/config_forms.py b/deluge/ui/webui/config_forms.py index 7ae53a95f..7cc48c554 100644 --- a/deluge/ui/webui/config_forms.py +++ b/deluge/ui/webui/config_forms.py @@ -29,8 +29,6 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -#TODO: rename to config_manager.py - import lib.newforms_plus as forms import page_decorators as deco import web diff --git a/deluge/ui/webui/config_tabs_deluge.py b/deluge/ui/webui/config_tabs_deluge.py index 170178374..66ccca875 100644 --- a/deluge/ui/webui/config_tabs_deluge.py +++ b/deluge/ui/webui/config_tabs_deluge.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # # deluge_webserver.py @@ -39,7 +38,6 @@ from deluge import component config_page = component.get("ConfigPageManager") - class NetworkPorts(config_forms.CfgForm ): title = _("Ports") info = _("Restart daemon after changing these values.") @@ -106,7 +104,6 @@ class BandwithTorrent(config_forms.CfgForm): config_page.register('bandwidth','torrent', BandwithTorrent) - class Download(config_forms.CfgForm): title = _("Download") download_location = forms.ServerFolder(_("Store all downoads in")) diff --git a/deluge/ui/webui/deluge_webserver.py b/deluge/ui/webui/deluge_webserver.py index ea4ff8a01..b3f5ad039 100644 --- a/deluge/ui/webui/deluge_webserver.py +++ b/deluge/ui/webui/deluge_webserver.py @@ -1,6 +1,4 @@ # -# webserver_framework.py -# # Copyright (C) Martijn Voncken 2007 # # This program is free software; you can redistribute it and/or modify diff --git a/deluge/ui/webui/lib/newforms_plus.py b/deluge/ui/webui/lib/newforms_plus.py index ae55664d7..272867387 100644 --- a/deluge/ui/webui/lib/newforms_plus.py +++ b/deluge/ui/webui/lib/newforms_plus.py @@ -10,7 +10,7 @@ from newforms.forms import BoundField import sys, os -import web +import webpy022 as web #Form class FilteredForm(newforms.Form): diff --git a/deluge/ui/webui/lib/static_handler.py b/deluge/ui/webui/lib/static_handler.py index 98322a9ea..3cf76554e 100644 --- a/deluge/ui/webui/lib/static_handler.py +++ b/deluge/ui/webui/lib/static_handler.py @@ -6,8 +6,8 @@ static fileserving for web.py without the need for wsgi wrapper magic. """ -import web -from web import seeother, url +import webpy022 as web +from webpy022 import seeother, url import posixpath import urlparse diff --git a/deluge/ui/webui/templates/deluge/config.html b/deluge/ui/webui/templates/deluge/config.html index a5f2d3671..8a4012c25 100644 --- a/deluge/ui/webui/templates/deluge/config.html +++ b/deluge/ui/webui/templates/deluge/config.html @@ -23,13 +23,18 @@ $for group in groups:
          $:form.as_table() + + + + +
          $if message:
          $message
          $if error:
          $error
          - +
          diff --git a/deluge/ui/webui/web.py b/deluge/ui/webui/web.py index c1fca2b54..da5155e11 100644 --- a/deluge/ui/webui/web.py +++ b/deluge/ui/webui/web.py @@ -1,4 +1,2 @@ #compatibility: use the included version/release of web.py. from lib.webpy022 import * -print 'tpl=',template -print list(sorted(globals().keys())) From 85d9e6ceb882b45eaae7b0b93ba5bc92e0269316 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 18 Mar 2008 20:32:01 +0000 Subject: [PATCH 0644/1009] webui: minor css --- deluge/ui/webui/config_tabs_webui.py | 2 +- deluge/ui/webui/templates/white/template_style.css | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/deluge/ui/webui/config_tabs_webui.py b/deluge/ui/webui/config_tabs_webui.py index 62d25efd5..502344e3e 100644 --- a/deluge/ui/webui/config_tabs_webui.py +++ b/deluge/ui/webui/config_tabs_webui.py @@ -103,7 +103,7 @@ class WebUiPlugins(forms.Form): return {'enabled_plugins':plugins.get_enabled_plugins()} def save(self, data): - log.debug() + log.debug(data) for plugin_name in data['enabled_plugins']: plugins.enable_plugin(plugin_name) diff --git a/deluge/ui/webui/templates/white/template_style.css b/deluge/ui/webui/templates/white/template_style.css index b6b221600..018a68465 100644 --- a/deluge/ui/webui/templates/white/template_style.css +++ b/deluge/ui/webui/templates/white/template_style.css @@ -5,7 +5,6 @@ avoid copy and pase of colours. */ - /*global:*/ body, th, td,tr , div,table , div{ font-family: Bitstream Vera Sans, verdana, arial, sans-serif; @@ -138,7 +137,7 @@ h3 { #organize_state li.selected { background-color:#C3D9FF; - -moz-border-radius:5 0 0 5; + -moz-border-radius:5px 0px 0px 5px; } @@ -149,7 +148,7 @@ h3 { #torrent_list_block{ border-style:solid; - border-width:2 2 2 12; + border-width:2px 2px 2px 12px; border-color:#C3D9FF; -moz-border-radius:5px; height:400px; From f55fdcf1188da287422b6ef92b468b8a24359439 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Tue, 18 Mar 2008 20:33:04 +0000 Subject: [PATCH 0645/1009] blocklist-plugin config pt1 --- .../plugins/blocklist/blocklist/__init__.py | 20 +++- deluge/plugins/blocklist/blocklist/webui.py | 91 +++++++++++++++++++ deluge/plugins/blocklist/setup.py | 6 +- 3 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 deluge/plugins/blocklist/blocklist/webui.py diff --git a/deluge/plugins/blocklist/blocklist/__init__.py b/deluge/plugins/blocklist/blocklist/__init__.py index db3e19d50..3baa6ea7e 100644 --- a/deluge/plugins/blocklist/blocklist/__init__.py +++ b/deluge/plugins/blocklist/blocklist/__init__.py @@ -4,19 +4,19 @@ # Copyright (C) 2007 Andrew Resch ('andar') # Copyright (C) 2008 Mark Stahler ('kramed') -# +# # Deluge is free software. -# +# # You may redistribute it and/or modify it under the terms of the # GNU General Public License, as published by the Free Software # Foundation; either version 2 of the License, or (at your option) # any later version. -# +# # deluge is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: # The Free Software Foundation, Inc., @@ -48,9 +48,19 @@ class CorePlugin(PluginBase): class GtkUIPlugin(PluginBase): def __init__(self, plugin_api, plugin_name): - # Load the GtkUI portion of the plugin + # Load the GtkUI portion of the plugin try: from gtkui import GtkUI self.plugin = GtkUI(plugin_api, plugin_name) except Exception, e: log.debug("Did not load a GtkUI plugin: %s", e) + +class WebUIPlugin(PluginBase): + def __init__(self, plugin_api, plugin_name): + # Load the GtkUI portion of the plugin + try: + from webui import WebUI + self.plugin = WebUI(plugin_api, plugin_name) + except Exception, e: + log.debug("Did not load a WebUI plugin: %s", e) + diff --git a/deluge/plugins/blocklist/blocklist/webui.py b/deluge/plugins/blocklist/blocklist/webui.py new file mode 100644 index 000000000..09c98779c --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/webui.py @@ -0,0 +1,91 @@ +# +# blocklist/webui.py +# +# Copyright (C) 2008 Martijn Voncken + +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import os +from deluge.log import LOG as log +from deluge.ui.client import sclient +from deluge import component +import ui + +import deluge.ui.webui.lib.newforms_plus as forms + +config_page_manager = component.get("ConfigPageManager") + +FORMAT_LIST = [ + ('gzmule',_("Emule IP list (GZip)")), + ('spzip',_("SafePeer Text (Zipped)")), + ('pgtext',_("PeerGuardian Text (Uncompressed)")), + ('p2bgz',_("PeerGuardian P2B (GZip)")) +] + +class BlockListCfgForm(forms.Form): + """ + a config form based on django forms. + see webui.lib.newforms_plus, config_forms, etc. + """ + #meta: + title = _("BlockList") + + #load/save: + def initial_data(self): + return sclient.block_list_get_options() + + def save(self, data): + sclient.block_list_set_options(dict(data)) + + #input fields : + listtype = forms.ChoiceField(FORMAT_LIST) + url = forms.URLField(label=_("Url")) + check_after_days = forms.IntegerField(label=_("Check for a blocklist every (days)"), min_value=-1, max_value=14) + timeout = forms.IntegerField(label=_("Timeout (seconds)"), min_value=15, max_value=360) + try_times = forms.IntegerField(label=_("Times to attemptdownload of new list"), min_value=1, max_value=5) + load_on_start = forms.CheckBox(_('Import blocklist on daemon startup')) + +class WebUI(ui.UI): + def __init__(self, plugin_api, plugin_name): + log.debug("Calling UI init") + # Call UI constructor + ui.UI.__init__(self, plugin_api, plugin_name) + log.debug("Blocklist WebUI plugin initalized..") + + def enable(self): + config_page_manager.register('plugins','blocklist',BlockListCfgForm) + + def disable(self): + config_page_manager.unregister('blocklist') + + + + + diff --git a/deluge/plugins/blocklist/setup.py b/deluge/plugins/blocklist/setup.py index bebce4d0f..c17541615 100644 --- a/deluge/plugins/blocklist/setup.py +++ b/deluge/plugins/blocklist/setup.py @@ -8,12 +8,12 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, write to: # The Free Software Foundation, Inc., @@ -50,5 +50,7 @@ setup( Blocklist = blocklist:CorePlugin [deluge.plugin.gtkui] Blocklist = blocklist:GtkUIPlugin + [deluge.plugin.webui] + Blocklist = blocklist:WebUIPlugin """ ) From d2d0e26f4c56850639daa3ef04eee52c5e189b89 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Fri, 21 Mar 2008 02:00:26 +0000 Subject: [PATCH 0646/1009] fix multiple router_node support lt rev 2089 --- .../include/libtorrent/kademlia/node_id.hpp | 2 ++ .../kademlia/traversal_algorithm.hpp | 2 +- libtorrent/src/kademlia/node.cpp | 15 --------------- libtorrent/src/kademlia/node_id.cpp | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/libtorrent/include/libtorrent/kademlia/node_id.hpp b/libtorrent/include/libtorrent/kademlia/node_id.hpp index 5e732acac..4173808c9 100644 --- a/libtorrent/include/libtorrent/kademlia/node_id.hpp +++ b/libtorrent/include/libtorrent/kademlia/node_id.hpp @@ -54,6 +54,8 @@ bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref); // usefult for finding out which bucket a node belongs to int distance_exp(node_id const& n1, node_id const& n2); +node_id generate_id(); + } } // namespace libtorrent::dht #endif // NODE_ID_HPP diff --git a/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp b/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp index d51ed5506..b333b385e 100644 --- a/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp +++ b/libtorrent/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -150,7 +150,7 @@ traversal_algorithm::traversal_algorithm( for (routing_table::router_iterator i = table.router_begin() , end(table.router_end()); i != end; ++i) { - add_entry(node_id(0), *i, result::initial); + add_entry(generate_id(), *i, result::initial); } } diff --git a/libtorrent/src/kademlia/node.cpp b/libtorrent/src/kademlia/node.cpp index 5b919393c..d4b343519 100644 --- a/libtorrent/src/kademlia/node.cpp +++ b/libtorrent/src/kademlia/node.cpp @@ -72,21 +72,6 @@ using asio::ip::udp; TORRENT_DEFINE_LOG(node) #endif -node_id generate_id() -{ - char random[20]; - std::srand(std::time(0)); -#ifdef _MSC_VER - std::generate(random, random + 20, &rand); -#else - std::generate(random, random + 20, &std::rand); -#endif - - hasher h; - h.update(random, 20); - return h.final(); -} - // remove peers that have timed out void purge_peers(std::set& peers) { diff --git a/libtorrent/src/kademlia/node_id.cpp b/libtorrent/src/kademlia/node_id.cpp index 99f3df219..758af2f8d 100644 --- a/libtorrent/src/kademlia/node_id.cpp +++ b/libtorrent/src/kademlia/node_id.cpp @@ -34,9 +34,11 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/hasher.hpp" #include "libtorrent/assert.hpp" using boost::bind; @@ -95,5 +97,21 @@ int distance_exp(node_id const& n1, node_id const& n2) return 0; } +struct static_ { static_() { std::srand(std::time(0)); } } static__; + +node_id generate_id() +{ + char random[20]; +#ifdef _MSC_VER + std::generate(random, random + 20, &rand); +#else + std::generate(random, random + 20, &std::rand); +#endif + + hasher h; + h.update(random, 20); + return h.final(); +} + } } // namespace libtorrent::dht From b4e1051fe0b38e870c9c2c03d8f05e590c124c56 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 21 Mar 2008 17:11:00 +0000 Subject: [PATCH 0647/1009] remove all logic from webserver_common --- deluge/ui/webui/config_forms.py | 22 +++-- deluge/ui/webui/config_tabs_deluge.py | 14 +-- deluge/ui/webui/config_tabs_webui.py | 21 +---- deluge/ui/webui/deluge_webserver.py | 57 ++++++++++--- deluge/ui/webui/lib/newforms_plus.py | 49 ++++++++--- deluge/ui/webui/lib/static_handler.py | 4 +- deluge/ui/webui/lib/web.py | 2 + deluge/ui/webui/page_decorators.py | 6 +- deluge/ui/webui/pages.py | 17 ++-- deluge/ui/webui/plugin_manager.py | 3 + deluge/ui/webui/render.py | 30 +++++-- deluge/ui/webui/run_webserver06 | 1 - deluge/ui/webui/torrent_add.py | 4 +- deluge/ui/webui/torrent_move.py | 5 +- deluge/ui/webui/torrent_options.py | 3 - deluge/ui/webui/utils.py | 62 ++++++++++++-- deluge/ui/webui/web.py | 2 +- deluge/ui/webui/webserver_common.py | 118 +------------------------- 18 files changed, 215 insertions(+), 205 deletions(-) create mode 100644 deluge/ui/webui/lib/web.py diff --git a/deluge/ui/webui/config_forms.py b/deluge/ui/webui/config_forms.py index 7cc48c554..460a95b21 100644 --- a/deluge/ui/webui/config_forms.py +++ b/deluge/ui/webui/config_forms.py @@ -32,33 +32,39 @@ import lib.newforms_plus as forms import page_decorators as deco import web -from webserver_common import ws, proxy, log +from deluge.ui.client import sclient as proxy +from deluge.log import LOG as log + from render import render from web import seeother import sys import os import utils -from deluge import component + +from deluge import component +from deluge.configmanager import ConfigManager +config = ConfigManager("webui.conf") config_page_manager = component.get("ConfigPageManager") class WebCfgForm(forms.Form): "config base for webui" def initial_data(self): - return ws.config + return config.get_config() def save(self, data): - ws.config.update(data) - ws.save_config() + for key, value in data.iteritems(): + config.set(key, value) + config.save() class CookieCfgForm(forms.Form): "config base for webui" def initial_data(self): - return ws.config + return dict(config) def save(self, data): - ws.config.update(data) - ws.save_config() + config.update(data) + config.save_config() class CfgForm(forms.Form): diff --git a/deluge/ui/webui/config_tabs_deluge.py b/deluge/ui/webui/config_tabs_deluge.py index 66ccca875..44696c7ac 100644 --- a/deluge/ui/webui/config_tabs_deluge.py +++ b/deluge/ui/webui/config_tabs_deluge.py @@ -30,8 +30,11 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +from deluge.ui.client import sclient as proxy +from deluge.log import LOG as log + import utils -from webserver_common import ws, proxy , log + import lib.newforms_plus as forms import config_forms from deluge import component @@ -125,13 +128,10 @@ config_page.register('deluge','daemon', Daemon) class Plugins(forms.Form): title = _("Enabled Plugins") - try: - _choices = [(p,p) for p in proxy.get_available_plugins()] - enabled_plugins = forms.MultipleChoice(_(""), _choices) - except: - log.error("Not connected to daemon, Unable to load plugin-list") - #TODO: reload on reconnect! + enabled_plugins = forms.LazyMultipleChoice( + choices_getter = lambda: [(p,p) for p in proxy.get_available_plugins()] + ) def initial_data(self): return {'enabled_plugins':proxy.get_enabled_plugins()} diff --git a/deluge/ui/webui/config_tabs_webui.py b/deluge/ui/webui/config_tabs_webui.py index 502344e3e..bbc4f9048 100644 --- a/deluge/ui/webui/config_tabs_webui.py +++ b/deluge/ui/webui/config_tabs_webui.py @@ -31,12 +31,14 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +from deluge.ui.client import sclient as proxy +from deluge.log import LOG as log import utils -from webserver_common import ws, log import lib.newforms_plus as forms import config_forms from deluge import component +from render import render config_page = component.get("ConfigPageManager") plugins = component.get("WebPluginManager") @@ -44,7 +46,7 @@ plugins = component.get("WebPluginManager") class Template(config_forms.WebCfgForm): title = _("Template") - _templates = [(t,t) for t in ws.get_templates()] + _templates = [(t,t) for t in render.get_templates()] _button_choices = enumerate([_('Text and image'), _('Image Only') , _('Text Only')]) @@ -93,21 +95,6 @@ class Password(forms.Form): utils.end_session() #raise forms.ValidationError(_("Password changed,please login again")) -class WebUiPlugins(forms.Form): - title = _("WebUi Plugins") - - _choices = [(p,p) for p in plugins.get_available_plugins()] - enabled_plugins = forms.MultipleChoice(_(""), _choices) - - def initial_data(self): - return {'enabled_plugins':plugins.get_enabled_plugins()} - - def save(self, data): - log.debug(data) - for plugin_name in data['enabled_plugins']: - plugins.enable_plugin(plugin_name) - config_page.register('webui','template', Template) config_page.register('webui','server',Server) config_page.register('webui','password',Password) -config_page.register('webui','webuiplugins',WebUiPlugins) diff --git a/deluge/ui/webui/deluge_webserver.py b/deluge/ui/webui/deluge_webserver.py index b3f5ad039..a93ef8937 100644 --- a/deluge/ui/webui/deluge_webserver.py +++ b/deluge/ui/webui/deluge_webserver.py @@ -27,40 +27,71 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -#initialize components: +import web +import random +import gettext +import locale +from deluge.configmanager import ConfigManager +import pkg_resources +from deluge.ui.client import sclient + +# Initialize gettext +locale.setlocale(locale.LC_MESSAGES, '') +locale.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) +locale.textdomain("deluge") +gettext.bindtextdomain("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) +gettext.textdomain("deluge") +gettext.install("deluge", + pkg_resources.resource_filename( + "deluge", "i18n")) + + +#self registering components: +import plugin_manager #registers as "WebPluginManager" import menu_manager #registers as "MenuManager" import config_page_manager #registers as "ConfigPageManager" -import plugin_manager #registers as "WebPluginManager" import page_manager #registers as "PageManager" +from debugerror import deluge_debugerror +from render import render +import utils + + +## Init ## +config = ConfigManager("webui.conf") +random.seed() +web.webapi.internalerror = deluge_debugerror + #self registering pages etc. import pages import config_tabs_webui #auto registers in ConfigUiManager import config_tabs_deluge #auto registers in ConfigUiManager import register_menu -#debugerror -from debugerror import deluge_debugerror -import web -web.webapi.internalerror = deluge_debugerror +utils.set_config_defaults() + +sclient.set_core_uri(config.get('daemon')) + -from webserver_common import ws #todo: remove ws. def create_webserver(urls, methods, middleware): - from webserver_common import ws - from web import webpyfunc - from web import webapi + from web import webpyfunc, wsgifunc from lib.gtk_cherrypy_wsgiserver import CherryPyWSGIServer import os - func = webapi.wsgifunc(webpyfunc(urls, methods, False), *middleware) - server_address=("0.0.0.0", int(ws.config.get('port'))) + func = wsgifunc(webpyfunc(urls, methods, False), *middleware) + server_address=("0.0.0.0", int(config.get('port'))) server = CherryPyWSGIServer(server_address, func, server_name="localhost") - if ws.config.get('use_https'): + """if config.get('use_https'): server.ssl_certificate = os.path.join(ws.webui_path,'ssl/deluge.pem') server.ssl_private_key = os.path.join(ws.webui_path,'ssl/deluge.key') + """ print "http://%s:%d/" % server_address return server diff --git a/deluge/ui/webui/lib/newforms_plus.py b/deluge/ui/webui/lib/newforms_plus.py index 272867387..7e7877670 100644 --- a/deluge/ui/webui/lib/newforms_plus.py +++ b/deluge/ui/webui/lib/newforms_plus.py @@ -10,7 +10,7 @@ from newforms.forms import BoundField import sys, os -import webpy022 as web +import web #Form class FilteredForm(newforms.Form): @@ -115,13 +115,6 @@ class IntChoiceField(newforms.ChoiceField): def clean(self, value): return int(newforms.ChoiceField.clean(self, value)) -class MultipleChoice(newforms.MultipleChoiceField): - #temp/test/debug!! - "Non Required MultipleChoiceField,why the f is it required by default?" - def __init__(self, label, choices, **kwargs): - newforms.MultipleChoiceField.__init__(self, label=label, choices=choices, - widget=newforms.CheckboxSelectMultiple, required=False) - class ServerFolder(newforms.CharField): def __init__(self, label, **kwargs): newforms.CharField.__init__(self, label=label,**kwargs) @@ -142,8 +135,44 @@ class Password(newforms.CharField): newforms.CharField.__init__(self, label=label, widget=newforms.PasswordInput, **kwargs) +#Lazy multiple select: +class _LazyCheckboxSelectMultiple(newforms.CheckboxSelectMultiple): + """ + choices are not know at define-time + choices_getter returns self.choices. + """ + def __init__(self, attrs=None,choices_getter = None): + self.choices_getter = choices_getter + newforms.CheckboxSelectMultiple.__init__(self,attrs) + + def render(self, name, value, attrs=None, choices=()): + self.choices = self.choices_getter() + return newforms.CheckboxSelectMultiple.render(self, name, value, attrs, choices) + + +class LazyMultipleChoice(newforms.MultipleChoiceField): + """ + choices are not know at define-time + choices_getter returns self.choices. + defaults to non-required. + """ + def __init__(self, label = "",widget=_LazyCheckboxSelectMultiple, + choices_getter = None, **kwargs): + + self.choices_getter = choices_getter + #default to non-required + if not 'required' in kwargs: + kwargs['required'] = False + #init, and pass get_choices to the widget. + newforms.MultipleChoiceField.__init__(self, label=label, + widget=widget(choices_getter=choices_getter),**kwargs) + + def clean(self, value): + self.choices = self.choices_getter() + return newforms.MultipleChoiceField.clean(self, value) + #Deluge specific: -class _DelugeIntInput(newforms.TextInput): +class _DelugeIntInputWidget(newforms.TextInput): """ because deluge-floats are edited as ints. """ @@ -159,7 +188,7 @@ class _DelugeIntInput(newforms.TextInput): class DelugeInt(newforms.IntegerField): def __init__(self, label , **kwargs): newforms.IntegerField.__init__(self, label=label, min_value=-1, - max_value=sys.maxint, widget=_DelugeIntInput, **kwargs) + max_value=sys.maxint, widget=_DelugeIntInputWidget, **kwargs) def clean(self, value): if str(value).lower() == _('Unlimited').lower(): diff --git a/deluge/ui/webui/lib/static_handler.py b/deluge/ui/webui/lib/static_handler.py index 3cf76554e..98322a9ea 100644 --- a/deluge/ui/webui/lib/static_handler.py +++ b/deluge/ui/webui/lib/static_handler.py @@ -6,8 +6,8 @@ static fileserving for web.py without the need for wsgi wrapper magic. """ -import webpy022 as web -from webpy022 import seeother, url +import web +from web import seeother, url import posixpath import urlparse diff --git a/deluge/ui/webui/lib/web.py b/deluge/ui/webui/lib/web.py new file mode 100644 index 000000000..61ddaf14d --- /dev/null +++ b/deluge/ui/webui/lib/web.py @@ -0,0 +1,2 @@ +#compatibility: use the included version/release of web.py. +from webpy022 import * diff --git a/deluge/ui/webui/page_decorators.py b/deluge/ui/webui/page_decorators.py index ce479aae4..29eb252b9 100644 --- a/deluge/ui/webui/page_decorators.py +++ b/deluge/ui/webui/page_decorators.py @@ -3,8 +3,10 @@ decorators for html-pages. """ #relative imports from render import render -from webserver_common import ws, log, proxy from utils import * +import utils +from deluge.ui.client import sclient as proxy +from deluge.log import LOG as log #/relative from web import cookies, setcookie as w_setcookie @@ -34,7 +36,7 @@ def check_session(func): name)) vars = web.input(redir_after_login = None) ck = cookies() - if ck.has_key("session_id") and ck["session_id"] in ws.SESSIONS: + if ck.has_key("session_id") and ck["session_id"] in utils.SESSIONS: return func(self, name) #check_session:ok elif vars.redir_after_login: seeother(url("/login",redir=self_url())) diff --git a/deluge/ui/webui/pages.py b/deluge/ui/webui/pages.py index 2e2a796a5..b15183f5d 100644 --- a/deluge/ui/webui/pages.py +++ b/deluge/ui/webui/pages.py @@ -34,7 +34,6 @@ #todo: remove useless imports. -from webserver_common import ws, proxy, log from utils import * import utils #todo remove the line above. from render import render, error_page @@ -44,6 +43,7 @@ from torrent_options import torrent_options from torrent_move import torrent_move from deluge.common import get_pixmap +from deluge.log import LOG as log import web from web import seeother, url @@ -53,6 +53,7 @@ from torrent_add import torrent_add from operator import attrgetter import os from deluge import component +from deluge.ui.client import sclient as proxy page_manager = component.get("PageManager") @@ -112,7 +113,7 @@ class login: def POST(self): vars = web.input(pwd = None, redir = None) - if ws.check_pwd(vars.pwd): + if utils.check_pwd(vars.pwd): #start new session start_session() do_redirect() @@ -347,11 +348,11 @@ class connect: if vars.uri == 'other_uri': if not vars.other: return error_page(_("no uri")) - url = vars.other + uri = vars.other else: uri = vars.uri #TODO: more error-handling - proxy.set_core_uri(uri) + utils.daemon_connect(uri) do_redirect() class daemon_control: @@ -372,16 +373,16 @@ class daemon_control: seeother('/connect') def start(self): - import time uri = web.input(uri = None).uri if not uri: uri = 'http://localhost:58846' port = int(uri.split(':')[2]) utils.daemon_start_localhost(port) - time.sleep(1) #pause a while to let it start? - proxy.set_core_uri( uri ) + + utils.daemon_connect( uri ) + #other stuff: class remote_torrent_add: @@ -395,7 +396,7 @@ class remote_torrent_add: vars = web.input(pwd = None, torrent = {}, data_b64 = None , torrent_name= None) - if not ws.check_pwd(vars.pwd): + if not utils.check_pwd(vars.pwd): return 'error:wrong password' if vars.data_b64: #b64 post (greasemonkey) diff --git a/deluge/ui/webui/plugin_manager.py b/deluge/ui/webui/plugin_manager.py index 47a5d1d9f..b3f2d99ed 100644 --- a/deluge/ui/webui/plugin_manager.py +++ b/deluge/ui/webui/plugin_manager.py @@ -34,6 +34,8 @@ from deluge import component, pluginmanagerbase from deluge.configmanager import ConfigManager from deluge.log import LOG as log +from deluge.ui.client import aclient as client + class PluginManager(pluginmanagerbase.PluginManagerBase, component.Component): @@ -52,6 +54,7 @@ class PluginManager(pluginmanagerbase.PluginManagerBase, # Disable the plugins self.disable_plugins() + def _on_get_enabled_plugins(self, enabled_plugins): log.debug("Webui has these plugins enabled: %s", enabled_plugins) self.config["enabled_plugins"] = enabled_plugins diff --git a/deluge/ui/webui/render.py b/deluge/ui/webui/render.py index b69c144be..36bc3ee66 100644 --- a/deluge/ui/webui/render.py +++ b/deluge/ui/webui/render.py @@ -30,13 +30,16 @@ # statement from all source files in the program, then also delete it here. #relative: -from webserver_common import ws,REVNO,VERSION +from webserver_common import REVNO, VERSION from utils import * #/relative from deluge import common from web import changequery as self_url, template, Storage import os +from deluge.configmanager import ConfigManager +config = ConfigManager("webui.conf") + class subclassed_render(object): """ adds limited subclassing for templates. @@ -46,22 +49,23 @@ class subclassed_render(object): self.apply_cfg() def apply_cfg(self): - self.cache = ws.config.get('cache_templates') + self.cache = config.get('cache_templates') self.renderers = [] self.plugin_renderers = [] self.template_cache = {} + self.webui_path = os.path.dirname(__file__) #load template-meta-data - cfg_template = ws.config.get('template') - template_path = os.path.join(ws.webui_path, 'templates/%s/' % cfg_template) + cfg_template = config.get('template') + template_path = os.path.join(self.webui_path, 'templates/%s/' % cfg_template) if not os.path.exists(template_path): - template_path = os.path.join(ws.webui_path, 'templates/deluge/') + template_path = os.path.join(self.webui_path, 'templates/deluge/') self.meta = Storage(eval(open(os.path.join(template_path,'meta.cfg')).read())) #load renerders for template_name in [cfg_template] + list(reversed(self.meta.inherits)): self.renderers.append(template.render( - os.path.join(ws.webui_path, 'templates/%s/' % template_name),cache=False)) + os.path.join(self.webui_path, 'templates/%s/' % template_name),cache=False)) @logcall def register_template_path(self, path): @@ -89,6 +93,16 @@ class subclassed_render(object): "for plugins/templates" return getattr(self, item) + @staticmethod + def get_templates(): + "utility method." + template_path = os.path.join(os.path.dirname(__file__), 'templates') + return [dirname for dirname + in os.listdir(template_path) + if os.path.isdir(os.path.join(template_path, dirname)) + and not dirname.startswith('.')] + + render = subclassed_render() def error_page(error): @@ -137,7 +151,7 @@ def template_part_stats(): return '[not connected]' def get_config(var): - return ws.config.get(var) + return config.get(var) irow = 0 def altrow(reset = False): @@ -181,7 +195,7 @@ template.Template.globals.update({ 'version': VERSION, 'getcookie':getcookie, 'get': lambda (var): getattr(web.input(**{var:None}), var), # unreadable :-( - 'env':ws.env, + 'env':'0.6', 'forms':web.Storage(), 'enumerate':enumerate diff --git a/deluge/ui/webui/run_webserver06 b/deluge/ui/webui/run_webserver06 index 5780cbf1f..4f9cc7970 100755 --- a/deluge/ui/webui/run_webserver06 +++ b/deluge/ui/webui/run_webserver06 @@ -2,5 +2,4 @@ #only for development/debugging. import deluge_webserver -deluge_webserver.ws.init_06(uri = 'http://localhost:58846') deluge_webserver.run(debug = True) \ No newline at end of file diff --git a/deluge/ui/webui/torrent_add.py b/deluge/ui/webui/torrent_add.py index 49c9f19f3..35f1b19dc 100644 --- a/deluge/ui/webui/torrent_add.py +++ b/deluge/ui/webui/torrent_add.py @@ -29,7 +29,9 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # -from webserver_common import ws, log, proxy +from deluge.ui.client import sclient as proxy +from deluge.log import LOG as log + import utils from render import render, error_page import page_decorators as deco diff --git a/deluge/ui/webui/torrent_move.py b/deluge/ui/webui/torrent_move.py index ec1ed4de8..0afb04209 100644 --- a/deluge/ui/webui/torrent_move.py +++ b/deluge/ui/webui/torrent_move.py @@ -30,12 +30,13 @@ # statement from all source files in the program, then also delete it here. # -from webserver_common import ws, proxy +from deluge.ui.client import sclient as proxy +from deluge.log import LOG as log + import utils from render import render import page_decorators as deco import lib.newforms_plus as forms -import web #Too much boilerplate code here, todo : fix it. diff --git a/deluge/ui/webui/torrent_options.py b/deluge/ui/webui/torrent_options.py index feaaade3c..fc5b68bdc 100644 --- a/deluge/ui/webui/torrent_options.py +++ b/deluge/ui/webui/torrent_options.py @@ -29,12 +29,9 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # -from webserver_common import ws -import utils from render import template import page_decorators as deco import lib.newforms_plus as forms -import web class TorrentOptionsForm(forms.Form): diff --git a/deluge/ui/webui/utils.py b/deluge/ui/webui/utils.py index 0409c2b77..766acc379 100644 --- a/deluge/ui/webui/utils.py +++ b/deluge/ui/webui/utils.py @@ -38,6 +38,7 @@ from web import Storage from web import seeother, url from deluge.common import fsize,fspeed,ftime +from deluge.log import LOG as log import traceback import random @@ -45,11 +46,18 @@ from operator import attrgetter import datetime import pickle from urlparse import urlparse +from md5 import md5 -from webserver_common import REVNO, VERSION, TORRENT_KEYS, STATE_MESSAGES -from webserver_common import ws, proxy, async_proxy, log +from webserver_common import REVNO, VERSION, TORRENT_KEYS, STATE_MESSAGES, CONFIG_DEFAULTS +from deluge.ui.client import sclient as proxy +from deluge.ui.client import aclient as async_proxy -debug_unicode = False + +from deluge import component +from deluge.configmanager import ConfigManager + +webui_plugin_manager = component.get("WebPluginManager") +config = ConfigManager("webui.conf") #async-proxy: map callback to a a dict-setter def dict_cb(key,d): @@ -62,16 +70,18 @@ def setcookie(key, val): """add 30 days expires header for persistent cookies""" return w_setcookie(key, val , expires=2592000) + #really simple sessions, to bad i had to implement them myself. +SESSIONS = [] def start_session(): - log.debug('start session') session_id = str(random.random()) - ws.SESSIONS.append(session_id) + SESSIONS.append(session_id) setcookie("session_id", session_id) def end_session(): session_id = getcookie("session_id") setcookie("session_id","") +#/sessions def do_redirect(): """for redirects after a POST""" @@ -201,7 +211,7 @@ def get_newforms_data(form_class): #/utils -#generic/ non-webui utils todo: move to trunk/core. +#daemon: def daemon_test_online_status(uri): """Tests the status of URI.. Returns True or False depending on status. """ @@ -224,14 +234,52 @@ def daemon_start_localhost(port): # Spawn a local daemon os.popen("deluged -p %s" % port) +def daemon_connect(uri): + if config.get('daemon') <> uri: + config.set('daemon', uri) + config.save() + + proxy.set_core_uri(uri) + webui_plugin_manager.start() + +#generic: def logcall(func): - "log a function/method-call" + "deco to log a function/method-call" def deco(*args, **kwargs): log.debug("call: %s<%s,%s>" % (func.__name__, args, kwargs)) return func(*args, **kwargs) #logdeco return deco +#c&p from ws: +def update_pwd(pwd): + sm = md5() + sm.update(str(random.getrandbits(5000))) + salt = sm.digest() + config["pwd_salt"] = salt + # + m = md5() + m.update(salt) + m.update(pwd) + config["pwd_md5"] = m.digest() + +def check_pwd(pwd): + m = md5() + m.update(config.get('pwd_salt')) + m.update(pwd) + return (m.digest() == config.get('pwd_md5')) + +def set_config_defaults(): + changed = False + for key, value in CONFIG_DEFAULTS.iteritems(): + if not key in config.config: + config.config[key] = value + changed = True + if changed: + config.save() + + + #exceptions: class WebUiError(Exception): diff --git a/deluge/ui/webui/web.py b/deluge/ui/webui/web.py index da5155e11..6217a1dbb 100644 --- a/deluge/ui/webui/web.py +++ b/deluge/ui/webui/web.py @@ -1,2 +1,2 @@ #compatibility: use the included version/release of web.py. -from lib.webpy022 import * +from lib.web import * diff --git a/deluge/ui/webui/webserver_common.py b/deluge/ui/webui/webserver_common.py index 31775a52f..7a79a21f7 100644 --- a/deluge/ui/webui/webserver_common.py +++ b/deluge/ui/webui/webserver_common.py @@ -30,34 +30,10 @@ # statement from all source files in the program, then also delete it here. """ -initializes config,render and proxy. -All hacks go here, so this is a really ugly source-file.. -Support running in process0.5 ,run inside-gtk0.5 and run in process0.6 +webui constants """ import os -import deluge -import random -import pickle -import sys -import base64 -from md5 import md5 -import inspect - -from deluge.log import LOG as log - -from deluge.ui.client import sclient as proxy -from deluge.ui.client import aclient as async_proxy - -random.seed() - -try: - _('translate something') -except: - import gettext - gettext.install('~/') - log.error('no translations :(') - #constants try: @@ -102,98 +78,10 @@ CONFIG_DEFAULTS = { "pwd_salt":"2\xe8\xc7\xa6(n\x81_\x8f\xfc\xdf\x8b\xd1\x1e\xd5\x90", "pwd_md5":".\xe8w\\+\xec\xdb\xf2id4F\xdb\rUc", "cache_templates":True, - "use_https":False + "use_https":False, + "daemon":"http://localhost:58846" } #/constants -class Ws: - """ - singleton - important attributes here are environment dependent. - - Most important public attrs: - ws.proxy - ws.log - ws.config - - Other: - ws.env - ws.config_dir - ws.session_file - ws.SESSIONS - """ - def __init__(self): - self.webui_path = os.path.dirname(__file__) - self.env = 'UNKNOWN' - self.config = {} - - try: - self.config_dir = deluge.common.CONFIG_DIR - except: - self.config_dir = os.path.expanduser("~/.config/deluge") - - self.config_file = os.path.join(self.config_dir,'webui.conf') - self.session_file = os.path.join(self.config_dir,'webui.sessions') - self.SESSIONS = [] - - def init_process(self): - self.config = pickle.load(open(self.config_file)) - - if self.config.get('enabled_plugins') == None: - self.config['enabled_plugins'] = [] - self.save_config() - - - def init_06(self, uri = 'http://localhost:58846'): - proxy.set_core_uri(uri) - - log.debug('cfg-file %s' % self.config_file) - - if not os.path.exists(self.config_file): - log.debug('create cfg file %s' % self.config_file) - #load&save defaults. - f = file(self.config_file,'wb') - pickle.dump(CONFIG_DEFAULTS,f) - f.close() - - - self.init_process() - self.env = '0.6' - from render import render - render.apply_cfg() - - #utils for config: - def get_templates(self): - template_path = os.path.join(os.path.dirname(__file__), 'templates') - return [dirname for dirname - in os.listdir(template_path) - if os.path.isdir(os.path.join(template_path, dirname)) - and not dirname.startswith('.')] - - def save_config(self): - log.debug('Save Webui Config') - data = pickle.dumps(self.config) - f = open(self.config_file,'wb') - f.write(data) - f.close() - - def update_pwd(self,pwd): - sm = md5() - sm.update(str(random.getrandbits(5000))) - salt = sm.digest() - self.config["pwd_salt"] = salt - # - m = md5() - m.update(salt) - m.update(pwd) - self.config["pwd_md5"] = m.digest() - - def check_pwd(self,pwd): - m = md5() - m.update(self.config.get('pwd_salt')) - m.update(pwd) - return (m.digest() == self.config.get('pwd_md5')) - -ws =Ws() From 36fa568d16568b474cac07cdf16f8eb6287061ec Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 21 Mar 2008 17:11:29 +0000 Subject: [PATCH 0648/1009] oops --- deluge/ui/webui/webui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deluge/ui/webui/webui.py b/deluge/ui/webui/webui.py index 6c2932903..d414015df 100644 --- a/deluge/ui/webui/webui.py +++ b/deluge/ui/webui/webui.py @@ -34,6 +34,5 @@ class WebUI: def __init__(self, args): import deluge_webserver - deluge_webserver.ws.init_06(uri = 'http://localhost:58846') deluge_webserver.run(debug = False) From 2852820a50f00db06a070ac10328264bb95caf24 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 21 Mar 2008 17:25:30 +0000 Subject: [PATCH 0649/1009] fix non-connected daemon --- deluge/ui/webui/deluge_webserver.py | 2 -- deluge/ui/webui/pages.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/deluge/ui/webui/deluge_webserver.py b/deluge/ui/webui/deluge_webserver.py index a93ef8937..96efb05c5 100644 --- a/deluge/ui/webui/deluge_webserver.py +++ b/deluge/ui/webui/deluge_webserver.py @@ -77,8 +77,6 @@ utils.set_config_defaults() sclient.set_core_uri(config.get('daemon')) - - def create_webserver(urls, methods, middleware): from web import webpyfunc, wsgifunc from lib.gtk_cherrypy_wsgiserver import CherryPyWSGIServer diff --git a/deluge/ui/webui/pages.py b/deluge/ui/webui/pages.py index b15183f5d..2e67656bb 100644 --- a/deluge/ui/webui/pages.py +++ b/deluge/ui/webui/pages.py @@ -37,6 +37,7 @@ from utils import * import utils #todo remove the line above. from render import render, error_page +import time import page_decorators as deco from config_forms import config_page from torrent_options import torrent_options From dcc10b781923a743e91d2c494fba3f39d5a32a42 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 21 Mar 2008 17:29:37 +0000 Subject: [PATCH 0650/1009] remove broken ssl --- deluge/ui/webui/config_tabs_webui.py | 2 +- deluge/ui/webui/ssl/deluge.key | 27 --------------------------- deluge/ui/webui/ssl/deluge.pem | 22 ---------------------- 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 deluge/ui/webui/ssl/deluge.key delete mode 100644 deluge/ui/webui/ssl/deluge.pem diff --git a/deluge/ui/webui/config_tabs_webui.py b/deluge/ui/webui/config_tabs_webui.py index bbc4f9048..db358856f 100644 --- a/deluge/ui/webui/config_tabs_webui.py +++ b/deluge/ui/webui/config_tabs_webui.py @@ -61,7 +61,7 @@ class Template(config_forms.WebCfgForm): class Server(config_forms.WebCfgForm): title = _("Server") port = forms.IntegerField(label = _("Port"),min_value=80) - use_https = forms.CheckBox(_("Use https")) + use_https = forms.CheckBox(_("Use https (BROKEN)")) try: import OpenSSL diff --git a/deluge/ui/webui/ssl/deluge.key b/deluge/ui/webui/ssl/deluge.key deleted file mode 100644 index a9d5db5ce..000000000 --- a/deluge/ui/webui/ssl/deluge.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA1sPXr1O6l2J9NAEvEYQ/JFDSVcJHh9YxP7kPdjsu7k9Ih845 -BHMX52A3Ypbe5MHe2bCj/8dRYCixRdF1KUTAKXdzc7mw9prgf3sS3RvmfcRsln6u -x7XRg7YprZJ46hFmcHiUPRgtTFLuFO2YWBnqxu/caTtAxx3PdoK6LDVnuVjHYofC -8uD4A9k6yL/jj3Yrkf8WYQqJ6pJcMAz/2c8ZXlBuiUCb9j5xKTzYoJaiUkKN2YrA -hoxRxfI7Zc7MH2yWw8/fTZJbGXo8nrfek7coSE7yQS1M6ciwkYk5VO2mBVJBJgAT -QUR/jGfLzEqNKXghQ564v9wmuFmUMd99a0tkVwIDAQABAoIBACID6sluLYOEqefu -uBHCLG4IDwheOQ4esrYxDW3gedJs5EP+ObGmuQaAisUmuC7rNeysuYzteMoOJ+Wz -AyeCKB1pOfP+WTT12tDWIWq73InW7ov3jJ89AO4nj/pZ1KTeFKeDsZbrmWEZUXQn -HZX2pOTVYMeaBuyCoDVZBzuxSbhlON4wS6ClMhem+eBOxg351CDTZa2cbq7Ffcos -VP7LY2ORQYNDTQSLguV/dJrFSotB8Eoz2xIpg5XR7msp6lzPzyAd+Aoz/T1lYxCY -IFZCJYKnIpgoYQvmtUlhQrdD8P0J4Kth7I8NgkWvXCKazQjhpUm+wojLKD0G7Kcz -9znIV+ECgYEA+qfp1C8jWbaAn1yAeORUA9aB6aGIURfOpZjnCvtMWM0Nu0nAJYDv -X7L5GRa1ulfKhfUG1Jv/ynMKXYuBUDhyccYLpP7BHpd29Arr7YAgb52KaD1PoKNa -Z45c61dj4sFoCmJEbDoL21UGb0LX3mc4XzPzwWs8AKfLW4aZh1NwCisCgYEA21gJ -Hy3egBgMT9+nVjqsgtIXgJOnzQRhvRwT7IFf392ZyFi8iM+pDUsx1yj0zSG4XNPw -NY8VtZuTBUlG73RKcrrz31jhCMfLCnoRkQeweZv0QWzbLU3V8DleUYdjFc/t0me5 -4NBR9lBlwYHgyU3GQ814vum+m0IAH0Ng1UxAVIUCgYAFOHwZTEYLN07kgtO2MOND -FTOtfwzMy5clQdMGGofTjanMjdOvtEjIEH05tYxhbjSsp5bV1M32FIFRw3cVCafw -kLRrYlb5YSQ8HwIc9z81s+1PEH/ZE63tXDy5Nh/BeE/Hb5aHPopCrjmtFZJTcojt -CrL4A1jDlrsYk+wcsnMx8wKBgEhJJQhvd2pDgps4G8+hGoUqc7Bd+OjpzsQh4rcI -k+4U+7847zkvJolJBK3hw3tu53FAL2OXOhJVqQgO9B+p9XcGAaTTh6X7IgDb5bok -DJanPMHq+/hcNGssnNbFhXQEyF2U7X8XaEuCh2ZURR5SUUq7BlX0dmp4P84NyHXC -4Vh5AoGAZYWkXxQUGzVm+H3fPpmETWGRNFDTimzi+6N+/uHkqkiDa3LGSnabmKh+ -voKm//DUjEVGlAZ3CGOjO/5SlZc/zjkgh1vg7KOU4x7DqVOuZjom5Tx3ZI4xVVVt -tVtvK0qjzUTVcwAQALN/PNak+gs9534e954rmA9kmc3xBe4ho9M= ------END RSA PRIVATE KEY----- diff --git a/deluge/ui/webui/ssl/deluge.pem b/deluge/ui/webui/ssl/deluge.pem deleted file mode 100644 index effef476e..000000000 --- a/deluge/ui/webui/ssl/deluge.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDlzCCAn+gAwIBAgIJAPnW/GEzRy8xMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNV -BAYTAkFVMRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBX -ZWJ1aTAeFw0wNzExMjQxMDAzNDRaFw0wODExMjMxMDAzNDRaMDsxCzAJBgNVBAYT -AkFVMRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBXZWJ1 -aTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbD169TupdifTQBLxGE -PyRQ0lXCR4fWMT+5D3Y7Lu5PSIfOOQRzF+dgN2KW3uTB3tmwo//HUWAosUXRdSlE -wCl3c3O5sPaa4H97Et0b5n3EbJZ+rse10YO2Ka2SeOoRZnB4lD0YLUxS7hTtmFgZ -6sbv3Gk7QMcdz3aCuiw1Z7lYx2KHwvLg+APZOsi/4492K5H/FmEKieqSXDAM/9nP -GV5QbolAm/Y+cSk82KCWolJCjdmKwIaMUcXyO2XOzB9slsPP302SWxl6PJ633pO3 -KEhO8kEtTOnIsJGJOVTtpgVSQSYAE0FEf4xny8xKjSl4IUOeuL/cJrhZlDHffWtL -ZFcCAwEAAaOBnTCBmjAdBgNVHQ4EFgQU1BbX1/4WtAKRKmWI1gqryIoj7BQwawYD -VR0jBGQwYoAU1BbX1/4WtAKRKmWI1gqryIoj7BShP6Q9MDsxCzAJBgNVBAYTAkFV -MRUwEwYDVQQIEwxUaGUgSW50ZXJuZXQxFTATBgNVBAoTDERlbHVnZSBXZWJ1aYIJ -APnW/GEzRy8xMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEoiSz5x -hRCplxUG34g3F5yJe0QboqzJ/XmECfO80a980C/WVeivM2Kb1uafsKNp+WK7wD8g -mei+todYXG+fD8WmG41LG87Xi2Xe4SlAcemEpGcC5F1bpCdvqnVAWFnqoF88FOHx -NDlrq5H5lhMH9wVrX9qJvxL+StaDJ0sFk4kMGWEN+bdSYfFdBQzF903nPtm+PlvO -1Uo6gCuRTMYM5J1DC/GpNpo/Fzrkgm8mMf1MYy3rljiNgMt2rnxhtwi6jugwyMui -id6Of6gYAtvhi7kmaUpdI5PHO35dqRK7pHXH+YXaulosCPw/+bSRptFTykeEMrBj -CzotqJ+74MwXZyM= ------END CERTIFICATE----- From 0eaa0aca27c2779c90eb2461153a6b5f23c8de26 Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Fri, 21 Mar 2008 18:31:27 +0000 Subject: [PATCH 0651/1009] consolidate manager-componets --- deluge/ui/webui/TODO | 20 ---- .../webui/{menu_manager.py => components.py} | 99 +++++++++++++++++-- deluge/ui/webui/config_page_manager.py | 58 ----------- deluge/ui/webui/deluge_webserver.py | 8 +- deluge/ui/webui/page_manager.py | 69 ------------- deluge/ui/webui/plugin_manager.py | 69 ------------- deluge/ui/webui/register_menu.py | 4 +- 7 files changed, 96 insertions(+), 231 deletions(-) delete mode 100644 deluge/ui/webui/TODO rename deluge/ui/webui/{menu_manager.py => components.py} (52%) delete mode 100644 deluge/ui/webui/config_page_manager.py delete mode 100644 deluge/ui/webui/page_manager.py delete mode 100644 deluge/ui/webui/plugin_manager.py diff --git a/deluge/ui/webui/TODO b/deluge/ui/webui/TODO deleted file mode 100644 index e0944ed43..000000000 --- a/deluge/ui/webui/TODO +++ /dev/null @@ -1,20 +0,0 @@ -0.5.7 -SSL -torrent/add http-post for private sites -rename reannounce->update-tracker. -queued displays as seeding/downloading - - -0.5.7 advanced layout -fonts -fix auto-refresh-layout -buttons -hide 0.0 kbps like in gtk-ui -update-tracker. - -0.6 -prepare for cat: - filters on status (prepare for cat) - filters on tracker -categories -greasemonkey : private sites. \ No newline at end of file diff --git a/deluge/ui/webui/menu_manager.py b/deluge/ui/webui/components.py similarity index 52% rename from deluge/ui/webui/menu_manager.py rename to deluge/ui/webui/components.py index b3df3ae22..19e12d6f2 100644 --- a/deluge/ui/webui/menu_manager.py +++ b/deluge/ui/webui/components.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # -# Copyright (C) Martijn Voncken 2007 +# Copyright (C) Martijn Voncken 2008 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -32,12 +32,24 @@ # """ -register toolbar and menu items. -future : required for plugin-api. -""" -from utils import logcall -from render import template +deluge components. + +MenuManager:add torrent-menu-items and torrent-detail tabs. +PageManager: add pages(urls) +PluginManager: deluge plugin manager +ConfigPageManager: add config pages(tabs) + +These managers/components are accesible as: + from deluge import component +manager = component.get("ClassName") + +""" +from deluge import component +import lib.newforms_plus as forms +from deluge.ui.client import aclient +from deluge import component, pluginmanagerbase +from deluge.configmanager import ConfigManager class TOOLBAR_FLAGS: generic = 0 @@ -54,6 +66,7 @@ class MenuManager(component.Component): #register vars in template. + from render import template template.Template.globals["admin_pages"] = self.admin_pages template.Template.globals["detail_tabs"] = self.detail_tabs template.Template.globals["toolbar_items"] = self.toolbar_items @@ -88,5 +101,77 @@ class MenuManager(component.Component): del self.detail_tabs[i] return -__menu_manager = MenuManager() +class PageManager(component.Component): + """ + web,py 0.2 mapping hack.. + see deluge_webserver.py + """ + def __init__(self): + component.Component.__init__(self, "PageManager") + self.page_classes = {} + self.urls = [] + + def register_pages(self, url_list, class_list): + self.urls += url_list + self.page_classes.update(class_list) + + def register_page(self, url, klass): + self.urls.append(url) + self.urls.append(klass.__name__) + self.page_classes[klass.__name__] = klass + + def unregister_page(self, url): + raise NotImplemenetedError() + #self.page_classes[klass.__name__] = None + +class PluginManager(pluginmanagerbase.PluginManagerBase, + component.Component): + def __init__(self): + component.Component.__init__(self, "WebPluginManager") + self.config = ConfigManager("webui.conf") + pluginmanagerbase.PluginManagerBase.__init__( + self, "webui.conf", "deluge.plugin.webui") + + def start(self): + """Start the plugin manager""" + # Update the enabled_plugins from the core + aclient.get_enabled_plugins(self._on_get_enabled_plugins) + + def stop(self): + # Disable the plugins + self.disable_plugins() + + + def _on_get_enabled_plugins(self, enabled_plugins): + log.debug("Webui has these plugins enabled: %s", enabled_plugins) + self.config["enabled_plugins"] = enabled_plugins + + # Enable the plugins that are enabled in the config and core + self.enable_plugins() + +class ConfigPageManager(component.Component): + def __init__(self): + component.Component.__init__(self, "ConfigPageManager") + self.groups = [] + self.blocks = forms.utils.datastructures.SortedDict() + + def register(self, group, name, form): + if not group in self.groups: + self.groups.append(group) + form.group = group + self.blocks[name] = form + + def unregister(self, name): + del self.blocks[name] + + +def register(): + __plugin_manager = PluginManager() + __menu_manager = MenuManager() + __page_manager = PageManager() + __config_page_manager = ConfigPageManager() + + + + diff --git a/deluge/ui/webui/config_page_manager.py b/deluge/ui/webui/config_page_manager.py deleted file mode 100644 index 5c22e303f..000000000 --- a/deluge/ui/webui/config_page_manager.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) Martijn Voncken 2008 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - - -from utils import logcall -from render import template -from deluge import component -import lib.newforms_plus as forms - -class ConfigPageManager(component.Component): - def __init__(self): - component.Component.__init__(self, "ConfigPageManager") - self.groups = [] - self.blocks = forms.utils.datastructures.SortedDict() - - def register(self, group, name, form): - if not group in self.groups: - self.groups.append(group) - form.group = group - self.blocks[name] = form - - def unregister(self, name): - del self.blocks[name] - -__config_page_manager = ConfigPageManager() - - - - - diff --git a/deluge/ui/webui/deluge_webserver.py b/deluge/ui/webui/deluge_webserver.py index 96efb05c5..911d1cc0e 100644 --- a/deluge/ui/webui/deluge_webserver.py +++ b/deluge/ui/webui/deluge_webserver.py @@ -34,6 +34,7 @@ import locale from deluge.configmanager import ConfigManager import pkg_resources from deluge.ui.client import sclient +import components # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') @@ -49,12 +50,7 @@ gettext.install("deluge", pkg_resources.resource_filename( "deluge", "i18n")) - -#self registering components: -import plugin_manager #registers as "WebPluginManager" -import menu_manager #registers as "MenuManager" -import config_page_manager #registers as "ConfigPageManager" -import page_manager #registers as "PageManager" +components.register() #after gettext!! from debugerror import deluge_debugerror from render import render diff --git a/deluge/ui/webui/page_manager.py b/deluge/ui/webui/page_manager.py deleted file mode 100644 index 6e6dd345c..000000000 --- a/deluge/ui/webui/page_manager.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# -# Copyright (C) Martijn Voncken 2007 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. -# - -from deluge import component - -class PageManager(component.Component): - """ - web,py 0.2 mapping hack.. - see deluge_webserver.py - """ - def __init__(self): - component.Component.__init__(self, "PageManager") - self.page_classes = {} - self.urls = [] - - def register_pages(self, url_list, class_list): - self.urls += url_list - self.page_classes.update(class_list) - - def register_page(self, url, klass): - self.urls.append(url) - self.urls.append(klass.__name__) - self.page_classes[klass.__name__] = klass - - def unregister_page(self, url): - raise NotImplemenetedError() - #self.page_classes[klass.__name__] = None - -__page_manager = PageManager() - - - - - - - - - - diff --git a/deluge/ui/webui/plugin_manager.py b/deluge/ui/webui/plugin_manager.py deleted file mode 100644 index b3f2d99ed..000000000 --- a/deluge/ui/webui/plugin_manager.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# -# Copyright (C) Martijn Voncken 2008 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. -# - -from deluge import component, pluginmanagerbase -from deluge.configmanager import ConfigManager -from deluge.log import LOG as log -from deluge.ui.client import aclient as client - - -class PluginManager(pluginmanagerbase.PluginManagerBase, - component.Component): - def __init__(self): - component.Component.__init__(self, "WebPluginManager") - self.config = ConfigManager("webui.conf") - pluginmanagerbase.PluginManagerBase.__init__( - self, "webui.conf", "deluge.plugin.webui") - - def start(self): - """Start the plugin manager""" - # Update the enabled_plugins from the core - client.get_enabled_plugins(self._on_get_enabled_plugins) - - def stop(self): - # Disable the plugins - self.disable_plugins() - - - def _on_get_enabled_plugins(self, enabled_plugins): - log.debug("Webui has these plugins enabled: %s", enabled_plugins) - self.config["enabled_plugins"] = enabled_plugins - - # Enable the plugins that are enabled in the config and core - self.enable_plugins() - - -__plugin_manager = PluginManager() - - - diff --git a/deluge/ui/webui/register_menu.py b/deluge/ui/webui/register_menu.py index a2c620fe6..222133c3b 100644 --- a/deluge/ui/webui/register_menu.py +++ b/deluge/ui/webui/register_menu.py @@ -54,5 +54,5 @@ menu.register_toolbar_item("queue_up",_("Up"), "queue-up.png" ,TB.torrent_list, menu.register_toolbar_item("queue_down",_("Down"), "queue-down.png" ,TB.torrent_list, "POST","/torrent/queue/down/", True) menu.register_toolbar_item("details",_("Details"), "details.png" ,TB.torrent, "GET","/torrent/info/", True) menu.register_toolbar_item("move",_("Move"), "move.png" ,TB.torrent_list,"POST","/torrent/move/", True) -menu.register_toolbar_item("reannounce",_("Reannounce"), "view-refresh.png" ,TB.torrent_list, "POST","'/torrent/reannounce/", False) -menu.register_toolbar_item("recheck",_("Recheck"), "view-refresh.png" ,TB.torrent_list, "POST","'/torrent/recheck/", False) +menu.register_toolbar_item("reannounce",_("Reannounce"), "view-refresh.png" ,TB.torrent_list, "POST","/torrent/reannounce/", False) +menu.register_toolbar_item("recheck",_("Recheck"), "view-refresh.png" ,TB.torrent_list, "POST","/torrent/recheck/", False) From b87089c7d2b48d89ca91c911b7d9881e88d35f2f Mon Sep 17 00:00:00 2001 From: Martijn Voncken Date: Sat, 22 Mar 2008 12:55:49 +0000 Subject: [PATCH 0652/1009] newforms->newforms_portable --- deluge/ui/webui/components.py | 2 +- deluge/ui/webui/config_tabs_deluge.py | 2 +- deluge/ui/webui/lib/newforms/fields.py | 492 ----------- deluge/ui/webui/lib/newforms/util.py | 78 -- deluge/ui/webui/lib/newforms/utils/html.py | 7 - deluge/ui/webui/lib/newforms_plus.py | 12 +- .../{newforms => newforms_portable}/LICENSE | 0 .../__init__.py | 2 + .../ui/webui/lib/newforms_portable/about.txt | 3 + .../django}/__init__.py | 0 .../newforms_portable/django/core/__init__.py | 0 .../django/core/exceptions.py | 29 + .../django/utils/__init__.py | 0 .../django}/utils/datastructures.py | 213 +++-- .../django/utils/encoding.py | 102 +++ .../django/utils/functional.py | 241 ++++++ .../newforms_portable/django/utils/html.py | 163 ++++ .../newforms_portable/django/utils/http.py | 67 ++ .../django/utils/safestring.py | 119 +++ .../django/utils/translation.py | 9 + .../ui/webui/lib/newforms_portable/fields.py | 784 ++++++++++++++++++ .../{newforms => newforms_portable}/forms.py | 205 +++-- .../ui/webui/lib/newforms_portable/models.py | 398 +++++++++ deluge/ui/webui/lib/newforms_portable/util.py | 69 ++ .../widgets.py | 266 ++++-- deluge/ui/webui/pages.py | 1 - deluge/ui/webui/torrent_add.py | 2 +- deluge/ui/webui/torrent_move.py | 2 +- 28 files changed, 2460 insertions(+), 808 deletions(-) delete mode 100644 deluge/ui/webui/lib/newforms/fields.py delete mode 100644 deluge/ui/webui/lib/newforms/util.py delete mode 100644 deluge/ui/webui/lib/newforms/utils/html.py rename deluge/ui/webui/lib/{newforms => newforms_portable}/LICENSE (100%) rename deluge/ui/webui/lib/{newforms => newforms_portable}/__init__.py (91%) create mode 100644 deluge/ui/webui/lib/newforms_portable/about.txt rename deluge/ui/webui/lib/{newforms/utils => newforms_portable/django}/__init__.py (100%) create mode 100644 deluge/ui/webui/lib/newforms_portable/django/core/__init__.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/__init__.py rename deluge/ui/webui/lib/{newforms => newforms_portable/django}/utils/datastructures.py (50%) create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/functional.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/html.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/http.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/translation.py create mode 100644 deluge/ui/webui/lib/newforms_portable/fields.py rename deluge/ui/webui/lib/{newforms => newforms_portable}/forms.py (60%) create mode 100644 deluge/ui/webui/lib/newforms_portable/models.py create mode 100644 deluge/ui/webui/lib/newforms_portable/util.py rename deluge/ui/webui/lib/{newforms => newforms_portable}/widgets.py (56%) diff --git a/deluge/ui/webui/components.py b/deluge/ui/webui/components.py index 19e12d6f2..4f877a31b 100644 --- a/deluge/ui/webui/components.py +++ b/deluge/ui/webui/components.py @@ -153,7 +153,7 @@ class ConfigPageManager(component.Component): def __init__(self): component.Component.__init__(self, "ConfigPageManager") self.groups = [] - self.blocks = forms.utils.datastructures.SortedDict() + self.blocks = forms.django.utils.datastructures.SortedDict() def register(self, group, name, form): if not group in self.groups: diff --git a/deluge/ui/webui/config_tabs_deluge.py b/deluge/ui/webui/config_tabs_deluge.py index 44696c7ac..0b7562162 100644 --- a/deluge/ui/webui/config_tabs_deluge.py +++ b/deluge/ui/webui/config_tabs_deluge.py @@ -57,7 +57,7 @@ class NetworkPorts(config_forms.CfgForm ): data['listen_ports'] = [data['_port_from'] , data['_port_to'] ] del(data['_port_from']) del(data['_port_to']) - config_forms.config.CfgForm.save(self, data) + config_forms.CfgForm.save(self, data) def validate(self, data): if (data['_port_to'] < data['_port_from']): diff --git a/deluge/ui/webui/lib/newforms/fields.py b/deluge/ui/webui/lib/newforms/fields.py deleted file mode 100644 index 5076c5824..000000000 --- a/deluge/ui/webui/lib/newforms/fields.py +++ /dev/null @@ -1,492 +0,0 @@ -""" -Field classes -""" - -from gettext import gettext -from util import ErrorList, ValidationError, smart_unicode -from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple -import datetime -import re -import time - -__all__ = ( - 'Field', 'CharField', 'IntegerField', - 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', - 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', - 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', - 'RegexField', 'EmailField', 'URLField', 'BooleanField', - 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', - 'ComboField', 'MultiValueField', - 'SplitDateTimeField', -) - -# These values, if given to to_python(), will trigger the self.required check. -EMPTY_VALUES = (None, '') - -try: - set # Only available in Python 2.4+ -except NameError: - from sets import Set as set # Python 2.3 fallback - -class Field(object): - widget = TextInput # Default widget to use when rendering this type of Field. - hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". - - # Tracks each time a Field instance is created. Used to retain order. - creation_counter = 0 - - def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None): - # required -- Boolean that specifies whether the field is required. - # True by default. - # widget -- A Widget class, or instance of a Widget class, that should be - # used for this Field when displaying it. Each Field has a default - # Widget that it'll use if you don't specify this. In most cases, - # the default widget is TextInput. - # label -- A verbose name for this field, for use in displaying this field in - # a form. By default, Django will use a "pretty" version of the form - # field name, if the Field is part of a Form. - # initial -- A value to use in this Field's initial display. This value is - # *not* used as a fallback if data isn't given. - # help_text -- An optional string to use as "help text" for this Field. - if label is not None: - label = smart_unicode(label) - self.required, self.label, self.initial = required, label, initial - self.help_text = smart_unicode(help_text or '') - widget = widget or self.widget - if isinstance(widget, type): - widget = widget() - - # Hook into self.widget_attrs() for any Field-specific HTML attributes. - extra_attrs = self.widget_attrs(widget) - if extra_attrs: - widget.attrs.update(extra_attrs) - - self.widget = widget - - # Increase the creation counter, and save our local copy. - self.creation_counter = Field.creation_counter - Field.creation_counter += 1 - - def clean(self, value): - """ - Validates the given value and returns its "cleaned" value as an - appropriate Python object. - - Raises ValidationError for any errors. - """ - if self.required and value in EMPTY_VALUES: - raise ValidationError(gettext(u'This field is required.')) - return value - - def widget_attrs(self, widget): - """ - Given a Widget instance (*not* a Widget class), returns a dictionary of - any HTML attributes that should be added to the Widget, based on this - Field. - """ - return {} - -class CharField(Field): - def __init__(self, max_length=None, min_length=None, *args, **kwargs): - self.max_length, self.min_length = max_length, min_length - super(CharField, self).__init__(*args, **kwargs) - - def clean(self, value): - "Validates max_length and min_length. Returns a Unicode object." - super(CharField, self).clean(value) - if value in EMPTY_VALUES: - return u'' - value = smart_unicode(value) - if self.max_length is not None and len(value) > self.max_length: - raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length) - if self.min_length is not None and len(value) < self.min_length: - raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length) - return value - - def widget_attrs(self, widget): - if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): - return {'maxlength': str(self.max_length)} - -class IntegerField(Field): - def __init__(self, max_value=None, min_value=None, *args, **kwargs): - self.max_value, self.min_value = max_value, min_value - super(IntegerField, self).__init__(*args, **kwargs) - - def clean(self, value): - """ - Validates that int() can be called on the input. Returns the result - of int(). Returns None for empty values. - """ - super(IntegerField, self).clean(value) - if value in EMPTY_VALUES: - return None - try: - value = int(value) - except (ValueError, TypeError): - raise ValidationError(gettext(u'Enter a whole number.')) - if self.max_value is not None and value > self.max_value: - raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value) - if self.min_value is not None and value < self.min_value: - raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) - return value - -DEFAULT_DATE_INPUT_FORMATS = ( - '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' - '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' - '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' - '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' - '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' -) - -class DateField(Field): - def __init__(self, input_formats=None, *args, **kwargs): - super(DateField, self).__init__(*args, **kwargs) - self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS - - def clean(self, value): - """ - Validates that the input can be converted to a date. Returns a Python - datetime.date object. - """ - super(DateField, self).clean(value) - if value in EMPTY_VALUES: - return None - if isinstance(value, datetime.datetime): - return value.date() - if isinstance(value, datetime.date): - return value - for format in self.input_formats: - try: - return datetime.date(*time.strptime(value, format)[:3]) - except ValueError: - continue - raise ValidationError(gettext(u'Enter a valid date.')) - -DEFAULT_TIME_INPUT_FORMATS = ( - '%H:%M:%S', # '14:30:59' - '%H:%M', # '14:30' -) - -class TimeField(Field): - def __init__(self, input_formats=None, *args, **kwargs): - super(TimeField, self).__init__(*args, **kwargs) - self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS - - def clean(self, value): - """ - Validates that the input can be converted to a time. Returns a Python - datetime.time object. - """ - super(TimeField, self).clean(value) - if value in EMPTY_VALUES: - return None - if isinstance(value, datetime.time): - return value - for format in self.input_formats: - try: - return datetime.time(*time.strptime(value, format)[3:6]) - except ValueError: - continue - raise ValidationError(gettext(u'Enter a valid time.')) - -DEFAULT_DATETIME_INPUT_FORMATS = ( - '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' - '%Y-%m-%d %H:%M', # '2006-10-25 14:30' - '%Y-%m-%d', # '2006-10-25' - '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' - '%m/%d/%Y %H:%M', # '10/25/2006 14:30' - '%m/%d/%Y', # '10/25/2006' - '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' - '%m/%d/%y %H:%M', # '10/25/06 14:30' - '%m/%d/%y', # '10/25/06' -) - -class DateTimeField(Field): - def __init__(self, input_formats=None, *args, **kwargs): - super(DateTimeField, self).__init__(*args, **kwargs) - self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS - - def clean(self, value): - """ - Validates that the input can be converted to a datetime. Returns a - Python datetime.datetime object. - """ - super(DateTimeField, self).clean(value) - if value in EMPTY_VALUES: - return None - if isinstance(value, datetime.datetime): - return value - if isinstance(value, datetime.date): - return datetime.datetime(value.year, value.month, value.day) - for format in self.input_formats: - try: - return datetime.datetime(*time.strptime(value, format)[:6]) - except ValueError: - continue - raise ValidationError(gettext(u'Enter a valid date/time.')) - -class RegexField(Field): - def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): - """ - regex can be either a string or a compiled regular expression object. - error_message is an optional error message to use, if - 'Enter a valid value' is too generic for you. - """ - super(RegexField, self).__init__(*args, **kwargs) - if isinstance(regex, basestring): - regex = re.compile(regex) - self.regex = regex - self.max_length, self.min_length = max_length, min_length - self.error_message = error_message or gettext(u'Enter a valid value.') - - def clean(self, value): - """ - Validates that the input matches the regular expression. Returns a - Unicode object. - """ - super(RegexField, self).clean(value) - if value in EMPTY_VALUES: - value = u'' - value = smart_unicode(value) - if value == u'': - return value - if self.max_length is not None and len(value) > self.max_length: - raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length) - if self.min_length is not None and len(value) < self.min_length: - raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length) - if not self.regex.search(value): - raise ValidationError(self.error_message) - return value - -email_re = re.compile( - r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain - -class EmailField(RegexField): - def __init__(self, max_length=None, min_length=None, *args, **kwargs): - RegexField.__init__(self, email_re, max_length, min_length, - gettext(u'Enter a valid e-mail address.'), *args, **kwargs) - -url_re = re.compile( - r'^https?://' # http:// or https:// - r'(?:[A-Z0-9-]+\.)+[A-Z]{2,6}' # domain - r'(?::\d+)?' # optional port - r'(?:/?|/\S+)$', re.IGNORECASE) - -try: - from django.conf import settings - URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT -except ImportError: - # It's OK if Django settings aren't configured. - URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' - -class URLField(RegexField): - def __init__(self, max_length=None, min_length=None, verify_exists=False, - validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): - super(URLField, self).__init__(url_re, max_length, min_length, gettext(u'Enter a valid URL.'), *args, **kwargs) - self.verify_exists = verify_exists - self.user_agent = validator_user_agent - - def clean(self, value): - value = super(URLField, self).clean(value) - if value == u'': - return value - if self.verify_exists: - import urllib2 - from django.conf import settings - headers = { - "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", - "Accept-Language": "en-us,en;q=0.5", - "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", - "Connection": "close", - "User-Agent": self.user_agent, - } - try: - req = urllib2.Request(value, None, headers) - u = urllib2.urlopen(req) - except ValueError: - raise ValidationError(gettext(u'Enter a valid URL.')) - except: # urllib2.URLError, httplib.InvalidURL, etc. - raise ValidationError(gettext(u'This URL appears to be a broken link.')) - return value - -class BooleanField(Field): - widget = CheckboxInput - - def clean(self, value): - "Returns a Python boolean object." - super(BooleanField, self).clean(value) - return bool(value) - -class NullBooleanField(BooleanField): - """ - A field whose valid values are None, True and False. Invalid values are - cleaned to None. - """ - widget = NullBooleanSelect - - def clean(self, value): - return {True: True, False: False}.get(value, None) - -class ChoiceField(Field): - def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None, help_text=None): - super(ChoiceField, self).__init__(required, widget, label, initial, help_text) - self.choices = choices - - def _get_choices(self): - return self._choices - - def _set_choices(self, value): - # Setting choices also sets the choices on the widget. - # choices can be any iterable, but we call list() on it because - # it will be consumed more than once. - self._choices = self.widget.choices = list(value) - - choices = property(_get_choices, _set_choices) - - def clean(self, value): - """ - Validates that the input is in self.choices. - """ - value = super(ChoiceField, self).clean(value) - if value in EMPTY_VALUES: - value = u'' - value = smart_unicode(value) - if value == u'': - return value - valid_values = set([str(k) for k, v in self.choices]) - if value not in valid_values: - raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) - return value - -class MultipleChoiceField(ChoiceField): - hidden_widget = MultipleHiddenInput - - def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None, help_text=None): - super(MultipleChoiceField, self).__init__(choices, required, widget, label, initial, help_text) - - def clean(self, value): - """ - Validates that the input is a list or tuple. - """ - if self.required and not value: - raise ValidationError(gettext(u'This field is required.')) - elif not self.required and not value: - return [] - if not isinstance(value, (list, tuple)): - raise ValidationError(gettext(u'Enter a list of values.')) - new_value = [] - for val in value: - val = smart_unicode(val) - new_value.append(val) - # Validate that each value in the value list is in self.choices. - valid_values = set([smart_unicode(k) for k, v in self.choices]) - for val in new_value: - if val not in valid_values: - raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val) - return new_value - -class ComboField(Field): - """ - A Field whose clean() method calls multiple Field clean() methods. - """ - def __init__(self, fields=(), *args, **kwargs): - super(ComboField, self).__init__(*args, **kwargs) - # Set 'required' to False on the individual fields, because the - # required validation will be handled by ComboField, not by those - # individual fields. - for f in fields: - f.required = False - self.fields = fields - - def clean(self, value): - """ - Validates the given value against all of self.fields, which is a - list of Field instances. - """ - super(ComboField, self).clean(value) - for field in self.fields: - value = field.clean(value) - return value - -class MultiValueField(Field): - """ - A Field that is composed of multiple Fields. - - Its clean() method takes a "decompressed" list of values. Each value in - this list is cleaned by the corresponding field -- the first value is - cleaned by the first field, the second value is cleaned by the second - field, etc. Once all fields are cleaned, the list of clean values is - "compressed" into a single value. - - Subclasses should implement compress(), which specifies how a list of - valid values should be converted to a single value. Subclasses should not - have to implement clean(). - - You'll probably want to use this with MultiWidget. - """ - def __init__(self, fields=(), *args, **kwargs): - super(MultiValueField, self).__init__(*args, **kwargs) - # Set 'required' to False on the individual fields, because the - # required validation will be handled by MultiValueField, not by those - # individual fields. - for f in fields: - f.required = False - self.fields = fields - - def clean(self, value): - """ - Validates every value in the given list. A value is validated against - the corresponding Field in self.fields. - - For example, if this MultiValueField was instantiated with - fields=(DateField(), TimeField()), clean() would call - DateField.clean(value[0]) and TimeField.clean(value[1]). - """ - clean_data = [] - errors = ErrorList() - if self.required and not value: - raise ValidationError(gettext(u'This field is required.')) - elif not self.required and not value: - return self.compress([]) - if not isinstance(value, (list, tuple)): - raise ValidationError(gettext(u'Enter a list of values.')) - for i, field in enumerate(self.fields): - try: - field_value = value[i] - except KeyError: - field_value = None - if self.required and field_value in EMPTY_VALUES: - raise ValidationError(gettext(u'This field is required.')) - try: - clean_data.append(field.clean(field_value)) - except ValidationError, e: - # Collect all validation errors in a single list, which we'll - # raise at the end of clean(), rather than raising a single - # exception for the first error we encounter. - errors.extend(e.messages) - if errors: - raise ValidationError(errors) - return self.compress(clean_data) - - def compress(self, data_list): - """ - Returns a single value for the given list of values. The values can be - assumed to be valid. - - For example, if this MultiValueField was instantiated with - fields=(DateField(), TimeField()), this might return a datetime - object created by combining the date and time in data_list. - """ - raise NotImplementedError('Subclasses must implement this method.') - -class SplitDateTimeField(MultiValueField): - def __init__(self, *args, **kwargs): - fields = (DateField(), TimeField()) - super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) - - def compress(self, data_list): - if data_list: - return datetime.datetime.combine(*data_list) - return None diff --git a/deluge/ui/webui/lib/newforms/util.py b/deluge/ui/webui/lib/newforms/util.py deleted file mode 100644 index 8b234df04..000000000 --- a/deluge/ui/webui/lib/newforms/util.py +++ /dev/null @@ -1,78 +0,0 @@ -from utils.html import escape - -class settings(object): - DEFAULT_CHARSET = 'utf-8' - - -# Converts a dictionary to a single string with key="value", XML-style with -# a leading space. Assumes keys do not need to be XML-escaped. -flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()]) - -def smart_unicode(s): - if not isinstance(s, basestring): - if hasattr(s, '__unicode__'): - s = unicode(s) - else: - s = unicode(str(s), settings.DEFAULT_CHARSET) - elif not isinstance(s, unicode): - s = unicode(s, settings.DEFAULT_CHARSET) - return s - -class StrAndUnicode(object): - """ - A class whose __str__ returns its __unicode__ as a bytestring - according to settings.DEFAULT_CHARSET. - - Useful as a mix-in. - """ - def __str__(self): - return self.__unicode__().encode(settings.DEFAULT_CHARSET) - -class ErrorDict(dict): - """ - A collection of errors that knows how to display itself in various formats. - - The dictionary keys are the field names, and the values are the errors. - """ - def __str__(self): - return self.as_ul() - - def as_ul(self): - if not self: return u'' - return u'
            %s
          ' % ''.join([u'
        • %s%s
        • ' % (k, v) for k, v in self.items()]) - - def as_text(self): - return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % i for i in v])) for k, v in self.items()]) - -class ErrorList(list): - """ - A collection of errors that knows how to display itself in various formats. - """ - def __str__(self): - return self.as_ul() - - def as_ul(self): - if not self: return u'' - return u'
            %s
          ' % ''.join([u'
        • %s
        • ' % e for e in self]) - - def as_text(self): - if not self: return u'' - return u'\n'.join([u'* %s' % e for e in self]) - -class ValidationError(Exception): - def __init__(self, message): - "ValidationError can be passed a string or a list." - self.message = message - if isinstance(message, list): - self.messages = ErrorList([smart_unicode(msg) for msg in message]) - else: - assert isinstance(message, basestring), ("%s should be a basestring" % repr(message)) - message = smart_unicode(message) - self.messages = ErrorList([message]) - - def __str__(self): - # This is needed because, without a __str__(), printing an exception - # instance would result in this: - # AttributeError: ValidationError instance has no attribute 'args' - # See http://www.python.org/doc/current/tut/node10.html#handling - return repr(self.messages) diff --git a/deluge/ui/webui/lib/newforms/utils/html.py b/deluge/ui/webui/lib/newforms/utils/html.py deleted file mode 100644 index e1f67cd43..000000000 --- a/deluge/ui/webui/lib/newforms/utils/html.py +++ /dev/null @@ -1,7 +0,0 @@ -"HTML utilities suitable for global use." - -def escape(html): - "Returns the given HTML with ampersands, quotes and carets encoded" - if not isinstance(html, basestring): - html = str(html) - return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') diff --git a/deluge/ui/webui/lib/newforms_plus.py b/deluge/ui/webui/lib/newforms_plus.py index 7e7877670..e1e8ffa4d 100644 --- a/deluge/ui/webui/lib/newforms_plus.py +++ b/deluge/ui/webui/lib/newforms_plus.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- # # Copyright (C) Martijn Voncken 2008 -# Django Lisence, see ./newforms/LICENCE +# Django Licence, see ./newforms_portable/LICENCE # -from newforms import * -import newforms -from newforms.forms import BoundField +from newforms_portable import * +import newforms_portable as newforms +from newforms_portable.forms import BoundField +from newforms_portable.util import ErrorList, escape + import sys, os @@ -83,7 +85,7 @@ class Form(FilteredForm): def start_save(self): "called by config_page" - data = web.Storage(self.clean_data) + data = web.Storage(self.cleaned_data) self.validate(data) self.save(data) self.post_save() diff --git a/deluge/ui/webui/lib/newforms/LICENSE b/deluge/ui/webui/lib/newforms_portable/LICENSE similarity index 100% rename from deluge/ui/webui/lib/newforms/LICENSE rename to deluge/ui/webui/lib/newforms_portable/LICENSE diff --git a/deluge/ui/webui/lib/newforms/__init__.py b/deluge/ui/webui/lib/newforms_portable/__init__.py similarity index 91% rename from deluge/ui/webui/lib/newforms/__init__.py rename to deluge/ui/webui/lib/newforms_portable/__init__.py index 62125e218..a34f46c8e 100644 --- a/deluge/ui/webui/lib/newforms/__init__.py +++ b/deluge/ui/webui/lib/newforms_portable/__init__.py @@ -14,3 +14,5 @@ from util import ValidationError from widgets import * from fields import * from forms import * +from models import * +import django diff --git a/deluge/ui/webui/lib/newforms_portable/about.txt b/deluge/ui/webui/lib/newforms_portable/about.txt new file mode 100644 index 000000000..333869ab8 --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/about.txt @@ -0,0 +1,3 @@ +based on django rev.7350 +,/django/ contains the parts of django required to run newforms. + diff --git a/deluge/ui/webui/lib/newforms/utils/__init__.py b/deluge/ui/webui/lib/newforms_portable/django/__init__.py similarity index 100% rename from deluge/ui/webui/lib/newforms/utils/__init__.py rename to deluge/ui/webui/lib/newforms_portable/django/__init__.py diff --git a/deluge/ui/webui/lib/newforms_portable/django/core/__init__.py b/deluge/ui/webui/lib/newforms_portable/django/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py b/deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py new file mode 100644 index 000000000..d9fc326cf --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py @@ -0,0 +1,29 @@ +"Global Django exceptions" + +class ObjectDoesNotExist(Exception): + "The requested object does not exist" + silent_variable_failure = True + +class MultipleObjectsReturned(Exception): + "The query returned multiple objects when only one was expected." + pass + +class SuspiciousOperation(Exception): + "The user did something suspicious" + pass + +class PermissionDenied(Exception): + "The user did not have permission to do that" + pass + +class ViewDoesNotExist(Exception): + "The requested view does not exist" + pass + +class MiddlewareNotUsed(Exception): + "This middleware is not used in this server configuration" + pass + +class ImproperlyConfigured(Exception): + "Django is somehow improperly configured" + pass diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/__init__.py b/deluge/ui/webui/lib/newforms_portable/django/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/ui/webui/lib/newforms/utils/datastructures.py b/deluge/ui/webui/lib/newforms_portable/django/utils/datastructures.py similarity index 50% rename from deluge/ui/webui/lib/newforms/utils/datastructures.py rename to deluge/ui/webui/lib/newforms_portable/django/utils/datastructures.py index 7b7fa2b0f..4c278c0d8 100644 --- a/deluge/ui/webui/lib/newforms/utils/datastructures.py +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/datastructures.py @@ -1,24 +1,24 @@ class MergeDict(object): """ - A simple class for creating new "virtual" dictionaries that actualy look + A simple class for creating new "virtual" dictionaries that actually look up values in more than one dictionary, passed in the constructor. + + If a key appears in more than one of the given dictionaries, only the + first occurrence will be used. """ def __init__(self, *dicts): self.dicts = dicts def __getitem__(self, key): - for dict in self.dicts: + for dict_ in self.dicts: try: - return dict[key] + return dict_[key] except KeyError: pass raise KeyError - def __contains__(self, key): - return self.has_key(key) - - def __copy__(self): - return self.__class__(*self.dicts) + def __copy__(self): + return self.__class__(*self.dicts) def get(self, key, default=None): try: @@ -27,84 +27,145 @@ class MergeDict(object): return default def getlist(self, key): - for dict in self.dicts: - try: - return dict.getlist(key) - except KeyError: - pass - raise KeyError + for dict_ in self.dicts: + if key in dict_.keys(): + return dict_.getlist(key) + return [] def items(self): item_list = [] - for dict in self.dicts: - item_list.extend(dict.items()) + for dict_ in self.dicts: + item_list.extend(dict_.items()) return item_list def has_key(self, key): - for dict in self.dicts: - if dict.has_key(key): + for dict_ in self.dicts: + if key in dict_: return True return False - - def copy(self): - """ returns a copy of this object""" + + __contains__ = has_key + + def copy(self): + """Returns a copy of this object.""" return self.__copy__() class SortedDict(dict): - "A dictionary that keeps its keys in the order in which they're inserted." + """ + A dictionary that keeps its keys in the order in which they're inserted. + """ def __init__(self, data=None): - if data is None: data = {} - dict.__init__(self, data) - self.keyOrder = data.keys() + if data is None: + data = {} + super(SortedDict, self).__init__(data) + if isinstance(data, dict): + self.keyOrder = data.keys() + else: + self.keyOrder = [] + for key, value in data: + if key not in self.keyOrder: + self.keyOrder.append(key) + + def __deepcopy__(self, memo): + from copy import deepcopy + return self.__class__([(key, deepcopy(value, memo)) + for key, value in self.iteritems()]) def __setitem__(self, key, value): - dict.__setitem__(self, key, value) + super(SortedDict, self).__setitem__(key, value) if key not in self.keyOrder: self.keyOrder.append(key) def __delitem__(self, key): - dict.__delitem__(self, key) + super(SortedDict, self).__delitem__(key) self.keyOrder.remove(key) def __iter__(self): for k in self.keyOrder: yield k + def pop(self, k, *args): + result = super(SortedDict, self).pop(k, *args) + try: + self.keyOrder.remove(k) + except ValueError: + # Key wasn't in the dictionary in the first place. No problem. + pass + return result + + def popitem(self): + result = super(SortedDict, self).popitem() + self.keyOrder.remove(result[0]) + return result + def items(self): return zip(self.keyOrder, self.values()) + def iteritems(self): + for key in self.keyOrder: + yield key, super(SortedDict, self).__getitem__(key) + def keys(self): return self.keyOrder[:] - def values(self): - return [dict.__getitem__(self, k) for k in self.keyOrder] + def iterkeys(self): + return iter(self.keyOrder) - def update(self, dict): - for k, v in dict.items(): + def values(self): + return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder] + + def itervalues(self): + for key in self.keyOrder: + yield super(SortedDict, self).__getitem__(key) + + def update(self, dict_): + for k, v in dict_.items(): self.__setitem__(k, v) def setdefault(self, key, default): if key not in self.keyOrder: self.keyOrder.append(key) - return dict.setdefault(self, key, default) + return super(SortedDict, self).setdefault(key, default) def value_for_index(self, index): - "Returns the value of the item at the given zero-based index." + """Returns the value of the item at the given zero-based index.""" return self[self.keyOrder[index]] + def insert(self, index, key, value): + """Inserts the key, value pair before the item with the given index.""" + if key in self.keyOrder: + n = self.keyOrder.index(key) + del self.keyOrder[n] + if n < index: + index -= 1 + self.keyOrder.insert(index, key) + super(SortedDict, self).__setitem__(key, value) + def copy(self): - "Returns a copy of this object." + """Returns a copy of this object.""" # This way of initializing the copy means it works for subclasses, too. obj = self.__class__(self) - obj.keyOrder = self.keyOrder + obj.keyOrder = self.keyOrder[:] return obj + def __repr__(self): + """ + Replaces the normal dict.__repr__ with a version that returns the keys + in their sorted order. + """ + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + + def clear(self): + super(SortedDict, self).clear() + self.keyOrder = [] + class MultiValueDictKeyError(KeyError): pass class MultiValueDict(dict): """ - A subclass of dictionary customized to handle multiple values for the same key. + A subclass of dictionary customized to handle multiple values for the + same key. >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) >>> d['name'] @@ -120,10 +181,11 @@ class MultiValueDict(dict): single name-value pairs. """ def __init__(self, key_to_list_mapping=()): - dict.__init__(self, key_to_list_mapping) + super(MultiValueDict, self).__init__(key_to_list_mapping) def __repr__(self): - return "" % dict.__repr__(self) + return "<%s: %s>" % (self.__class__.__name__, + super(MultiValueDict, self).__repr__()) def __getitem__(self, key): """ @@ -131,7 +193,7 @@ class MultiValueDict(dict): raises KeyError if not found. """ try: - list_ = dict.__getitem__(self, key) + list_ = super(MultiValueDict, self).__getitem__(key) except KeyError: raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self) try: @@ -140,22 +202,27 @@ class MultiValueDict(dict): return [] def __setitem__(self, key, value): - dict.__setitem__(self, key, [value]) + super(MultiValueDict, self).__setitem__(key, [value]) def __copy__(self): - return self.__class__(dict.items(self)) + return self.__class__(super(MultiValueDict, self).items()) def __deepcopy__(self, memo=None): import copy - if memo is None: memo = {} + if memo is None: + memo = {} result = self.__class__() memo[id(self)] = result for key, value in dict.items(self): - dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)) + dict.__setitem__(result, copy.deepcopy(key, memo), + copy.deepcopy(value, memo)) return result def get(self, key, default=None): - "Returns the default value if the requested data doesn't exist" + """ + Returns the last data value for the passed key. If key doesn't exist + or value is an empty list, then default is returned. + """ try: val = self[key] except KeyError: @@ -165,14 +232,17 @@ class MultiValueDict(dict): return val def getlist(self, key): - "Returns an empty list if the requested data doesn't exist" + """ + Returns the list of values for the passed key. If key doesn't exist, + then an empty list is returned. + """ try: - return dict.__getitem__(self, key) + return super(MultiValueDict, self).__getitem__(key) except KeyError: return [] def setlist(self, key, list_): - dict.__setitem__(self, key, list_) + super(MultiValueDict, self).__setitem__(key, list_) def setdefault(self, key, default=None): if key not in self: @@ -185,9 +255,9 @@ class MultiValueDict(dict): return self.getlist(key) def appendlist(self, key, value): - "Appends an item to the internal list associated with key" + """Appends an item to the internal list associated with key.""" self.setlistdefault(key, []) - dict.__setitem__(self, key, self.getlist(key) + [value]) + super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value]) def items(self): """ @@ -197,21 +267,24 @@ class MultiValueDict(dict): return [(key, self[key]) for key in self.keys()] def lists(self): - "Returns a list of (key, list) pairs." - return dict.items(self) + """Returns a list of (key, list) pairs.""" + return super(MultiValueDict, self).items() def values(self): - "Returns a list of the last value on every key list." + """Returns a list of the last value on every key list.""" return [self[key] for key in self.keys()] def copy(self): - "Returns a copy of this object." + """Returns a copy of this object.""" return self.__deepcopy__() def update(self, *args, **kwargs): - "update() extends rather than replaces existing key lists. Also accepts keyword args." + """ + update() extends rather than replaces existing key lists. + Also accepts keyword args. + """ if len(args) > 1: - raise TypeError, "update expected at most 1 arguments, got %d", len(args) + raise TypeError, "update expected at most 1 arguments, got %d" % len(args) if args: other_dict = args[0] if isinstance(other_dict, MultiValueDict): @@ -232,22 +305,20 @@ class DotExpandedDict(dict): may contain dots to specify inner dictionaries. It's confusing, but this example should make sense. - >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], - 'person.1.lastname': ['Willison'], - 'person.2.firstname': ['Adrian'], + >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \ + 'person.1.lastname': ['Willison'], \ + 'person.2.firstname': ['Adrian'], \ 'person.2.lastname': ['Holovaty']}) >>> d - {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, - '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}} + {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}} >>> d['person'] - {'1': {'firstname': ['Simon'], 'lastname': ['Willison'], - '2': {'firstname': ['Adrian'], 'lastname': ['Holovaty']} + {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}} >>> d['person']['1'] - {'firstname': ['Simon'], 'lastname': ['Willison']} + {'lastname': ['Willison'], 'firstname': ['Simon']} # Gotcha: Results are unpredictable if the dots are "uneven": >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1}) - >>> {'c': 1} + {'c': 1} """ def __init__(self, key_to_list_mapping): for k, v in key_to_list_mapping.items(): @@ -259,4 +330,16 @@ class DotExpandedDict(dict): try: current[bits[-1]] = v except TypeError: # Special-case if current isn't a dict. - current = {bits[-1] : v} + current = {bits[-1]: v} + +class FileDict(dict): + """ + A dictionary used to hold uploaded file contents. The only special feature + here is that repr() of this object won't dump the entire contents of the + file to the output. A handy safeguard for a large file upload. + """ + def __repr__(self): + if 'content' in self: + d = dict(self, content='') + return dict.__repr__(d) + return dict.__repr__(self) diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py b/deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py new file mode 100644 index 000000000..2b5219cc6 --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py @@ -0,0 +1,102 @@ +import types +import urllib +import datetime + +from functional import Promise +from safestring import SafeData, mark_safe + +class DjangoUnicodeDecodeError(UnicodeDecodeError): + def __init__(self, obj, *args): + self.obj = obj + UnicodeDecodeError.__init__(self, *args) + + def __str__(self): + original = UnicodeDecodeError.__str__(self) + return '%s. You passed in %r (%s)' % (original, self.obj, + type(self.obj)) + +class StrAndUnicode(object): + """ + A class whose __str__ returns its __unicode__ as a UTF-8 bytestring. + + Useful as a mix-in. + """ + def __str__(self): + return self.__unicode__().encode('utf-8') + +def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): + """ + Returns a unicode object representing 's'. Treats bytestrings using the + 'encoding' codec. + + If strings_only is True, don't convert (some) non-string-like objects. + """ + if isinstance(s, Promise): + # The input is the result of a gettext_lazy() call. + return s + return force_unicode(s, encoding, strings_only, errors) + +def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): + """ + Similar to smart_unicode, except that lazy instances are resolved to + strings, rather than kept as lazy objects. + + If strings_only is True, don't convert (some) non-string-like objects. + """ + if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)): + return s + try: + if not isinstance(s, basestring,): + if hasattr(s, '__unicode__'): + s = unicode(s) + else: + s = unicode(str(s), encoding, errors) + elif not isinstance(s, unicode): + # Note: We use .decode() here, instead of unicode(s, encoding, + # errors), so that if s is a SafeString, it ends up being a + # SafeUnicode at the end. + s = s.decode(encoding, errors) + except UnicodeDecodeError, e: + raise DjangoUnicodeDecodeError(s, *e.args) + return s + +def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): + """ + Returns a bytestring version of 's', encoded as specified in 'encoding'. + + If strings_only is True, don't convert (some) non-string-like objects. + """ + if strings_only and isinstance(s, (types.NoneType, int)): + return s + if isinstance(s, Promise): + return unicode(s).encode(encoding, errors) + elif not isinstance(s, basestring): + try: + return str(s) + except UnicodeEncodeError: + return unicode(s).encode(encoding, errors) + elif isinstance(s, unicode): + return s.encode(encoding, errors) + elif s and encoding != 'utf-8': + return s.decode('utf-8', errors).encode(encoding, errors) + else: + return s + +def iri_to_uri(iri): + """ + Convert an Internationalized Resource Identifier (IRI) portion to a URI + portion that is suitable for inclusion in a URL. + + This is the algorithm from section 3.1 of RFC 3987. However, since we are + assuming input is either UTF-8 or unicode already, we can simplify things a + little from the full method. + + Returns an ASCII string containing the encoded result. + """ + # The list of safe characters here is constructed from the printable ASCII + # characters that are not explicitly excluded by the list at the end of + # section 3.1 of RFC 3987. + if iri is None: + return iri + return urllib.quote(smart_str(iri), safe='/#%[]=:;$&()+,!?*') + diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/functional.py b/deluge/ui/webui/lib/newforms_portable/django/utils/functional.py new file mode 100644 index 000000000..3de693e18 --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/functional.py @@ -0,0 +1,241 @@ +# License for code in this file that was taken from Python 2.5. + +# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +# -------------------------------------------- +# +# 1. This LICENSE AGREEMENT is between the Python Software Foundation +# ("PSF"), and the Individual or Organization ("Licensee") accessing and +# otherwise using this software ("Python") in source or binary form and +# its associated documentation. +# +# 2. Subject to the terms and conditions of this License Agreement, PSF +# hereby grants Licensee a nonexclusive, royalty-free, world-wide +# license to reproduce, analyze, test, perform and/or display publicly, +# prepare derivative works, distribute, and otherwise use Python +# alone or in any derivative version, provided, however, that PSF's +# License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +# 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; +# All Rights Reserved" are retained in Python alone or in any derivative +# version prepared by Licensee. +# +# 3. In the event Licensee prepares a derivative work that is based on +# or incorporates Python or any part thereof, and wants to make +# the derivative work available to others as provided herein, then +# Licensee hereby agrees to include in any such work a brief summary of +# the changes made to Python. +# +# 4. PSF is making Python available to Licensee on an "AS IS" +# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +# INFRINGE ANY THIRD PARTY RIGHTS. +# +# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +# +# 6. This License Agreement will automatically terminate upon a material +# breach of its terms and conditions. +# +# 7. Nothing in this License Agreement shall be deemed to create any +# relationship of agency, partnership, or joint venture between PSF and +# Licensee. This License Agreement does not grant permission to use PSF +# trademarks or trade name in a trademark sense to endorse or promote +# products or services of Licensee, or any third party. +# +# 8. By copying, installing or otherwise using Python, Licensee +# agrees to be bound by the terms and conditions of this License +# Agreement. + + +def curry(_curried_func, *args, **kwargs): + def _curried(*moreargs, **morekwargs): + return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) + return _curried + +### Begin from Python 2.5 functools.py ######################################## + +# Summary of changes made to the Python 2.5 code below: +# * swapped ``partial`` for ``curry`` to maintain backwards-compatibility +# in Django. +# * Wrapped the ``setattr`` call in ``update_wrapper`` with a try-except +# block to make it compatible with Python 2.3, which doesn't allow +# assigning to ``__name__``. + +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation. +# All Rights Reserved. + +############################################################################### + +# update_wrapper() and wraps() are tools to help write +# wrapper functions that can handle naive introspection + +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') +WRAPPER_UPDATES = ('__dict__',) +def update_wrapper(wrapper, + wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Update a wrapper function to look like the wrapped function + + wrapper is the function to be updated + wrapped is the original function + assigned is a tuple naming the attributes assigned directly + from the wrapped function to the wrapper function (defaults to + functools.WRAPPER_ASSIGNMENTS) + updated is a tuple naming the attributes off the wrapper that + are updated with the corresponding attribute from the wrapped + function (defaults to functools.WRAPPER_UPDATES) + """ + for attr in assigned: + try: + setattr(wrapper, attr, getattr(wrapped, attr)) + except TypeError: # Python 2.3 doesn't allow assigning to __name__. + pass + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr)) + # Return the wrapper so this can be used as a decorator via curry() + return wrapper + +def wraps(wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Decorator factory to apply update_wrapper() to a wrapper function + + Returns a decorator that invokes update_wrapper() with the decorated + function as the wrapper argument and the arguments to wraps() as the + remaining arguments. Default arguments are as for update_wrapper(). + This is a convenience function to simplify applying curry() to + update_wrapper(). + """ + return curry(update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + +### End from Python 2.5 functools.py ########################################## + +def memoize(func, cache, num_args): + """ + Wrap a function so that results for any argument tuple are stored in + 'cache'. Note that the args to the function must be usable as dictionary + keys. + + Only the first num_args are considered when creating the key. + """ + def wrapper(*args): + mem_args = args[:num_args] + if mem_args in cache: + return cache[mem_args] + result = func(*args) + cache[mem_args] = result + return result + return wraps(func)(wrapper) + +class Promise(object): + """ + This is just a base class for the proxy class created in + the closure of the lazy function. It can be used to recognize + promises in code. + """ + pass + +def lazy(func, *resultclasses): + """ + Turns any callable into a lazy evaluated callable. You need to give result + classes or types -- at least one is needed so that the automatic forcing of + the lazy evaluation code is triggered. Results are not memoized; the + function is evaluated on every access. + """ + class __proxy__(Promise): + # This inner class encapsulates the code that should be evaluated + # lazily. On calling of one of the magic methods it will force + # the evaluation and store the result. Afterwards, the result + # is delivered directly. So the result is memoized. + def __init__(self, args, kw): + self.__func = func + self.__args = args + self.__kw = kw + self.__dispatch = {} + for resultclass in resultclasses: + self.__dispatch[resultclass] = {} + for (k, v) in resultclass.__dict__.items(): + setattr(self, k, self.__promise__(resultclass, k, v)) + self._delegate_str = str in resultclasses + self._delegate_unicode = unicode in resultclasses + assert not (self._delegate_str and self._delegate_unicode), "Cannot call lazy() with both str and unicode return types." + if self._delegate_unicode: + # Each call to lazy() makes a new __proxy__ object, so this + # doesn't interfere with any other lazy() results. + __proxy__.__unicode__ = __proxy__.__unicode_cast + elif self._delegate_str: + __proxy__.__str__ = __proxy__.__str_cast + + def __promise__(self, klass, funcname, func): + # Builds a wrapper around some magic method and registers that magic + # method for the given type and method name. + def __wrapper__(*args, **kw): + # Automatically triggers the evaluation of a lazy value and + # applies the given magic method of the result type. + res = self.__func(*self.__args, **self.__kw) + return self.__dispatch[type(res)][funcname](res, *args, **kw) + + if klass not in self.__dispatch: + self.__dispatch[klass] = {} + self.__dispatch[klass][funcname] = func + return __wrapper__ + + def __unicode_cast(self): + return self.__func(*self.__args, **self.__kw) + + def __str_cast(self): + return str(self.__func(*self.__args, **self.__kw)) + + def __cmp__(self, rhs): + if self._delegate_str: + s = str(self.__func(*self.__args, **self.__kw)) + elif self._delegate_unicode: + s = unicode(self.__func(*self.__args, **self.__kw)) + else: + s = self.__func(*self.__args, **self.__kw) + if isinstance(rhs, Promise): + return -cmp(rhs, s) + else: + return cmp(s, rhs) + + def __mod__(self, rhs): + if self._delegate_str: + return str(self) % rhs + elif self._delegate_unicode: + return unicode(self) % rhs + else: + raise AssertionError('__mod__ not supported for non-string types') + + def __deepcopy__(self, memo): + # Instances of this class are effectively immutable. It's just a + # collection of functions. So we don't need to do anything + # complicated for copying. + memo[id(self)] = self + return self + + def __wrapper__(*args, **kw): + # Creates the proxy object, instead of the actual value. + return __proxy__(args, kw) + + return wraps(func)(__wrapper__) + +def allow_lazy(func, *resultclasses): + """ + A decorator that allows a function to be called with one or more lazy + arguments. If none of the args are lazy, the function is evaluated + immediately, otherwise a __proxy__ is returned that will evaluate the + function when needed. + """ + def wrapper(*args, **kwargs): + for arg in list(args) + kwargs.values(): + if isinstance(arg, Promise): + break + else: + return func(*args, **kwargs) + return lazy(func, *resultclasses)(*args, **kwargs) + return wraps(func)(wrapper) diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/html.py b/deluge/ui/webui/lib/newforms_portable/django/utils/html.py new file mode 100644 index 000000000..3ac39864d --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/html.py @@ -0,0 +1,163 @@ +"""HTML utilities suitable for global use.""" + +import re +import string + +from safestring import SafeData, mark_safe +from encoding import force_unicode +from functional import allow_lazy +from http import urlquote + +# Configuration for urlize() function. +LEADING_PUNCTUATION = ['(', '<', '<'] +TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>'] + +# List of possible strings used for bullets in bulleted lists. +DOTS = ['·', '*', '\xe2\x80\xa2', '•', '•', '•'] + +unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)') +word_split_re = re.compile(r'(\s+)') +punctuation_re = re.compile('^(?P(?:%s)*)(?P.*?)(?P(?:%s)*)$' % \ + ('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]), + '|'.join([re.escape(x) for x in TRAILING_PUNCTUATION]))) +simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') +link_target_attribute_re = re.compile(r'(]*?)target=[^\s>]+') +html_gunk_re = re.compile(r'(?:
          |<\/i>|<\/b>|<\/em>|<\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE) +hard_coded_bullets_re = re.compile(r'((?:

          (?:%s).*?[a-zA-Z].*?

          \s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL) +trailing_empty_content_re = re.compile(r'(?:

          (?: |\s|
          )*?

          \s*)+\Z') +del x # Temporary variable + +def escape(html): + """Returns the given HTML with ampersands, quotes and carets encoded.""" + return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) +escape = allow_lazy(escape, unicode) + +def conditional_escape(html): + """ + Similar to escape(), except that it doesn't operate on pre-escaped strings. + """ + if isinstance(html, SafeData): + return html + else: + return escape(html) + +def linebreaks(value, autoescape=False): + """Converts newlines into

          and
          s.""" + value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines + paras = re.split('\n{2,}', value) + if autoescape: + paras = [u'

          %s

          ' % escape(p.strip()).replace('\n', '
          ') for p in paras] + else: + paras = [u'

          %s

          ' % p.strip().replace('\n', '
          ') for p in paras] + return u'\n\n'.join(paras) +linebreaks = allow_lazy(linebreaks, unicode) + +def strip_tags(value): + """Returns the given HTML with all tags stripped.""" + return re.sub(r'<[^>]*?>', '', force_unicode(value)) +strip_tags = allow_lazy(strip_tags) + +def strip_spaces_between_tags(value): + """Returns the given HTML with spaces between tags removed.""" + return re.sub(r'>\s+<', '><', force_unicode(value)) +strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode) + +def strip_entities(value): + """Returns the given HTML with all entities (&something;) stripped.""" + return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value)) +strip_entities = allow_lazy(strip_entities, unicode) + +def fix_ampersands(value): + """Returns the given HTML with all unencoded ampersands encoded correctly.""" + return unencoded_ampersands_re.sub('&', force_unicode(value)) +fix_ampersands = allow_lazy(fix_ampersands, unicode) + +def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): + """ + Converts any URLs in text into clickable links. + + Works on http://, https://, and www. links. Links can have trailing + punctuation (periods, commas, close-parens) and leading punctuation + (opening parens) and it'll still do the right thing. + + If trim_url_limit is not None, the URLs in link text longer than this limit + will truncated to trim_url_limit-3 characters and appended with an elipsis. + + If nofollow is True, the URLs in link text will get a rel="nofollow" + attribute. + """ + if autoescape: + trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x) + else: + trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + safe_input = isinstance(text, SafeData) + words = word_split_re.split(force_unicode(text)) + nofollow_attr = nofollow and ' rel="nofollow"' or '' + for i, word in enumerate(words): + match = punctuation_re.match(word) + if match: + lead, middle, trail = match.groups() + if safe_input: + middle = mark_safe(middle) + if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \ + len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \ + (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): + middle = 'http://%s' % middle + if middle.startswith('http://') or middle.startswith('https://'): + url = urlquote(middle, safe='/&=:;#?+*') + if autoescape and not safe_input: + url = escape(url) + trimmed_url = trim_url(middle) + middle = '
          %s' % (url, nofollow_attr, + trimmed_url) + elif '@' in middle and not middle.startswith('www.') and \ + not ':' in middle and simple_email_re.match(middle): + if autoescape: + middle = conditional_escape(middle) + middle = '%s' % (middle, middle) + if lead + middle + trail != word: + if autoescape and not safe_input: + lead, trail = escape(lead), escape(trail) + words[i] = mark_safe('%s%s%s' % (lead, middle, trail)) + elif autoescape and not safe_input: + words[i] = escape(word) + elif safe_input: + words[i] = mark_safe(word) + elif autoescape: + words[i] = escape(word) + return u''.join(words) +urlize = allow_lazy(urlize, unicode) + +def clean_html(text): + """ + Clean the given HTML. Specifically, do the following: + * Convert and to and . + * Encode all ampersands correctly. + * Remove all "target" attributes from tags. + * Remove extraneous HTML, such as presentational tags that open and + immediately close and
          . + * Convert hard-coded bullets into HTML unordered lists. + * Remove stuff like "

            

          ", but only if it's at the + bottom of the text. + """ + from django.utils.text import normalize_newlines + text = normalize_newlines(force_unicode(text)) + text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text) + text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text) + text = fix_ampersands(text) + # Remove all target="" attributes from
          tags. + text = link_target_attribute_re.sub('\\1', text) + # Trim stupid HTML such as
          . + text = html_gunk_re.sub('', text) + # Convert hard-coded bullets into HTML unordered lists. + def replace_p_tags(match): + s = match.group().replace('

          ', '') + for d in DOTS: + s = s.replace('

          %s' % d, '

        • ') + return u'
            \n%s\n
          ' % s + text = hard_coded_bullets_re.sub(replace_p_tags, text) + # Remove stuff like "

            

          ", but only if it's at the bottom + # of the text. + text = trailing_empty_content_re.sub('', text) + return text +clean_html = allow_lazy(clean_html, unicode) diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/http.py b/deluge/ui/webui/lib/newforms_portable/django/utils/http.py new file mode 100644 index 000000000..db8bb9644 --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/http.py @@ -0,0 +1,67 @@ +import urllib +from email.Utils import formatdate + +from encoding import smart_str, force_unicode +from functional import allow_lazy + +def urlquote(url, safe='/'): + """ + A version of Python's urllib.quote() function that can operate on unicode + strings. The url is first UTF-8 encoded before quoting. The returned string + can safely be used as part of an argument to a subsequent iri_to_uri() call + without double-quoting occurring. + """ + return force_unicode(urllib.quote(smart_str(url), safe)) + +urlquote = allow_lazy(urlquote, unicode) + +def urlquote_plus(url, safe=''): + """ + A version of Python's urllib.quote_plus() function that can operate on + unicode strings. The url is first UTF-8 encoded before quoting. The + returned string can safely be used as part of an argument to a subsequent + iri_to_uri() call without double-quoting occurring. + """ + return force_unicode(urllib.quote_plus(smart_str(url), safe)) +urlquote_plus = allow_lazy(urlquote_plus, unicode) + +def urlencode(query, doseq=0): + """ + A version of Python's urllib.urlencode() function that can operate on + unicode strings. The parameters are first case to UTF-8 encoded strings and + then encoded as per normal. + """ + if hasattr(query, 'items'): + query = query.items() + return urllib.urlencode( + [(smart_str(k), + isinstance(v, (list,tuple)) and [smart_str(i) for i in v] or smart_str(v)) + for k, v in query], + doseq) + +def cookie_date(epoch_seconds=None): + """ + Formats the time to ensure compatibility with Netscape's cookie standard. + + Accepts a floating point number expressed in seconds since the epoch, in + UTC - such as that outputted by time.time(). If set to None, defaults to + the current time. + + Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'. + """ + rfcdate = formatdate(epoch_seconds) + return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25]) + +def http_date(epoch_seconds=None): + """ + Formats the time to match the RFC1123 date format as specified by HTTP + RFC2616 section 3.3.1. + + Accepts a floating point number expressed in seconds since the epoch, in + UTC - such as that outputted by time.time(). If set to None, defaults to + the current time. + + Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. + """ + rfcdate = formatdate(epoch_seconds) + return '%s GMT' % rfcdate[:25] diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py b/deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py new file mode 100644 index 000000000..99658fb8b --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py @@ -0,0 +1,119 @@ +""" +Functions for working with "safe strings": strings that can be displayed safely +without further escaping in HTML. Marking something as a "safe string" means +that the producer of the string has already turned characters that should not +be interpreted by the HTML engine (e.g. '<') into the appropriate entities. +""" +from functional import curry, Promise + +class EscapeData(object): + pass + +class EscapeString(str, EscapeData): + """ + A string that should be HTML-escaped when output. + """ + pass + +class EscapeUnicode(unicode, EscapeData): + """ + A unicode object that should be HTML-escaped when output. + """ + pass + +class SafeData(object): + pass + +class SafeString(str, SafeData): + """ + A string subclass that has been specifically marked as "safe" (requires no + further escaping) for HTML output purposes. + """ + def __add__(self, rhs): + """ + Concatenating a safe string with another safe string or safe unicode + object is safe. Otherwise, the result is no longer safe. + """ + t = super(SafeString, self).__add__(rhs) + if isinstance(rhs, SafeUnicode): + return SafeUnicode(t) + elif isinstance(rhs, SafeString): + return SafeString(t) + return t + + def _proxy_method(self, *args, **kwargs): + """ + Wrap a call to a normal unicode method up so that we return safe + results. The method that is being wrapped is passed in the 'method' + argument. + """ + method = kwargs.pop('method') + data = method(self, *args, **kwargs) + if isinstance(data, str): + return SafeString(data) + else: + return SafeUnicode(data) + + decode = curry(_proxy_method, method = str.decode) + +class SafeUnicode(unicode, SafeData): + """ + A unicode subclass that has been specifically marked as "safe" for HTML + output purposes. + """ + def __add__(self, rhs): + """ + Concatenating a safe unicode object with another safe string or safe + unicode object is safe. Otherwise, the result is no longer safe. + """ + t = super(SafeUnicode, self).__add__(rhs) + if isinstance(rhs, SafeData): + return SafeUnicode(t) + return t + + def _proxy_method(self, *args, **kwargs): + """ + Wrap a call to a normal unicode method up so that we return safe + results. The method that is being wrapped is passed in the 'method' + argument. + """ + method = kwargs.pop('method') + data = method(self, *args, **kwargs) + if isinstance(data, str): + return SafeString(data) + else: + return SafeUnicode(data) + + encode = curry(_proxy_method, method = unicode.encode) + +def mark_safe(s): + """ + Explicitly mark a string as safe for (HTML) output purposes. The returned + object can be used everywhere a string or unicode object is appropriate. + + Can be called multiple times on a single string. + """ + if isinstance(s, SafeData): + return s + if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str): + return SafeString(s) + if isinstance(s, (unicode, Promise)): + return SafeUnicode(s) + return SafeString(str(s)) + +def mark_for_escaping(s): + """ + Explicitly mark a string as requiring HTML escaping upon output. Has no + effect on SafeData subclasses. + + Can be called multiple times on a single string (the resulting escaping is + only applied once). + """ + if isinstance(s, (SafeData, EscapeData)): + return s + if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str): + return EscapeString(s) + if isinstance(s, (unicode, Promise)): + return EscapeUnicode(s) + return EscapeString(str(s)) + diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/translation.py b/deluge/ui/webui/lib/newforms_portable/django/utils/translation.py new file mode 100644 index 000000000..ad65bd959 --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/django/utils/translation.py @@ -0,0 +1,9 @@ +try: + _('translate something') +except: + import gettext + gettext.install('locale') + +ugettext = _ +ugettext_lazy = _ + diff --git a/deluge/ui/webui/lib/newforms_portable/fields.py b/deluge/ui/webui/lib/newforms_portable/fields.py new file mode 100644 index 000000000..c20899ada --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/fields.py @@ -0,0 +1,784 @@ +""" +Field classes. +""" + +import copy +import datetime +import os +import re +import time +# Python 2.3 fallbacks +try: + from decimal import Decimal, DecimalException +except ImportError: + from django.utils._decimal import Decimal, DecimalException +try: + set +except NameError: + from sets import Set as set + +from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str + +from util import ErrorList, ValidationError +from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput + + +__all__ = ( + 'Field', 'CharField', 'IntegerField', + 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', + 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', + 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', + 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', + 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', + 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', + 'SplitDateTimeField', 'IPAddressField', 'FilePathField', +) + +# These values, if given to to_python(), will trigger the self.required check. +EMPTY_VALUES = (None, '') + + +class Field(object): + widget = TextInput # Default widget to use when rendering this type of Field. + hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". + default_error_messages = { + 'required': _(u'This field is required.'), + 'invalid': _(u'Enter a valid value.'), + } + + # Tracks each time a Field instance is created. Used to retain order. + creation_counter = 0 + + def __init__(self, required=True, widget=None, label=None, initial=None, + help_text=None, error_messages=None): + # required -- Boolean that specifies whether the field is required. + # True by default. + # widget -- A Widget class, or instance of a Widget class, that should + # be used for this Field when displaying it. Each Field has a + # default Widget that it'll use if you don't specify this. In + # most cases, the default widget is TextInput. + # label -- A verbose name for this field, for use in displaying this + # field in a form. By default, Django will use a "pretty" + # version of the form field name, if the Field is part of a + # Form. + # initial -- A value to use in this Field's initial display. This value + # is *not* used as a fallback if data isn't given. + # help_text -- An optional string to use as "help text" for this Field. + if label is not None: + label = smart_unicode(label) + self.required, self.label, self.initial = required, label, initial + self.help_text = smart_unicode(help_text or '') + widget = widget or self.widget + if isinstance(widget, type): + widget = widget() + + # Hook into self.widget_attrs() for any Field-specific HTML attributes. + extra_attrs = self.widget_attrs(widget) + if extra_attrs: + widget.attrs.update(extra_attrs) + + self.widget = widget + + # Increase the creation counter, and save our local copy. + self.creation_counter = Field.creation_counter + Field.creation_counter += 1 + + def set_class_error_messages(messages, klass): + for base_class in klass.__bases__: + set_class_error_messages(messages, base_class) + messages.update(getattr(klass, 'default_error_messages', {})) + + messages = {} + set_class_error_messages(messages, self.__class__) + messages.update(error_messages or {}) + self.error_messages = messages + + def clean(self, value): + """ + Validates the given value and returns its "cleaned" value as an + appropriate Python object. + + Raises ValidationError for any errors. + """ + if self.required and value in EMPTY_VALUES: + raise ValidationError(self.error_messages['required']) + return value + + def widget_attrs(self, widget): + """ + Given a Widget instance (*not* a Widget class), returns a dictionary of + any HTML attributes that should be added to the Widget, based on this + Field. + """ + return {} + + def __deepcopy__(self, memo): + result = copy.copy(self) + memo[id(self)] = result + result.widget = copy.deepcopy(self.widget, memo) + return result + +class CharField(Field): + default_error_messages = { + 'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'), + 'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'), + } + + def __init__(self, max_length=None, min_length=None, *args, **kwargs): + self.max_length, self.min_length = max_length, min_length + super(CharField, self).__init__(*args, **kwargs) + + def clean(self, value): + "Validates max_length and min_length. Returns a Unicode object." + super(CharField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = smart_unicode(value) + value_length = len(value) + if self.max_length is not None and value_length > self.max_length: + raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) + if self.min_length is not None and value_length < self.min_length: + raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) + return value + + def widget_attrs(self, widget): + if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): + # The HTML attribute is maxlength, not max_length. + return {'maxlength': str(self.max_length)} + +class IntegerField(Field): + default_error_messages = { + 'invalid': _(u'Enter a whole number.'), + 'max_value': _(u'Ensure this value is less than or equal to %s.'), + 'min_value': _(u'Ensure this value is greater than or equal to %s.'), + } + + def __init__(self, max_value=None, min_value=None, *args, **kwargs): + self.max_value, self.min_value = max_value, min_value + super(IntegerField, self).__init__(*args, **kwargs) + + def clean(self, value): + """ + Validates that int() can be called on the input. Returns the result + of int(). Returns None for empty values. + """ + super(IntegerField, self).clean(value) + if value in EMPTY_VALUES: + return None + try: + value = int(str(value)) + except (ValueError, TypeError): + raise ValidationError(self.error_messages['invalid']) + if self.max_value is not None and value > self.max_value: + raise ValidationError(self.error_messages['max_value'] % self.max_value) + if self.min_value is not None and value < self.min_value: + raise ValidationError(self.error_messages['min_value'] % self.min_value) + return value + +class FloatField(Field): + default_error_messages = { + 'invalid': _(u'Enter a number.'), + 'max_value': _(u'Ensure this value is less than or equal to %s.'), + 'min_value': _(u'Ensure this value is greater than or equal to %s.'), + } + + def __init__(self, max_value=None, min_value=None, *args, **kwargs): + self.max_value, self.min_value = max_value, min_value + Field.__init__(self, *args, **kwargs) + + def clean(self, value): + """ + Validates that float() can be called on the input. Returns a float. + Returns None for empty values. + """ + super(FloatField, self).clean(value) + if not self.required and value in EMPTY_VALUES: + return None + try: + value = float(value) + except (ValueError, TypeError): + raise ValidationError(self.error_messages['invalid']) + if self.max_value is not None and value > self.max_value: + raise ValidationError(self.error_messages['max_value'] % self.max_value) + if self.min_value is not None and value < self.min_value: + raise ValidationError(self.error_messages['min_value'] % self.min_value) + return value + +class DecimalField(Field): + default_error_messages = { + 'invalid': _(u'Enter a number.'), + 'max_value': _(u'Ensure this value is less than or equal to %s.'), + 'min_value': _(u'Ensure this value is greater than or equal to %s.'), + 'max_digits': _('Ensure that there are no more than %s digits in total.'), + 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), + 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') + } + + def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): + self.max_value, self.min_value = max_value, min_value + self.max_digits, self.decimal_places = max_digits, decimal_places + Field.__init__(self, *args, **kwargs) + + def clean(self, value): + """ + Validates that the input is a decimal number. Returns a Decimal + instance. Returns None for empty values. Ensures that there are no more + than max_digits in the number, and no more than decimal_places digits + after the decimal point. + """ + super(DecimalField, self).clean(value) + if not self.required and value in EMPTY_VALUES: + return None + value = smart_str(value).strip() + try: + value = Decimal(value) + except DecimalException: + raise ValidationError(self.error_messages['invalid']) + pieces = str(value).lstrip("-").split('.') + decimals = (len(pieces) == 2) and len(pieces[1]) or 0 + digits = len(pieces[0]) + if self.max_value is not None and value > self.max_value: + raise ValidationError(self.error_messages['max_value'] % self.max_value) + if self.min_value is not None and value < self.min_value: + raise ValidationError(self.error_messages['min_value'] % self.min_value) + if self.max_digits is not None and (digits + decimals) > self.max_digits: + raise ValidationError(self.error_messages['max_digits'] % self.max_digits) + if self.decimal_places is not None and decimals > self.decimal_places: + raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) + if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): + raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) + return value + +DEFAULT_DATE_INPUT_FORMATS = ( + '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' + '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' + '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' + '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' + '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' +) + +class DateField(Field): + default_error_messages = { + 'invalid': _(u'Enter a valid date.'), + } + + def __init__(self, input_formats=None, *args, **kwargs): + super(DateField, self).__init__(*args, **kwargs) + self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS + + def clean(self, value): + """ + Validates that the input can be converted to a date. Returns a Python + datetime.date object. + """ + super(DateField, self).clean(value) + if value in EMPTY_VALUES: + return None + if isinstance(value, datetime.datetime): + return value.date() + if isinstance(value, datetime.date): + return value + for format in self.input_formats: + try: + return datetime.date(*time.strptime(value, format)[:3]) + except ValueError: + continue + raise ValidationError(self.error_messages['invalid']) + +DEFAULT_TIME_INPUT_FORMATS = ( + '%H:%M:%S', # '14:30:59' + '%H:%M', # '14:30' +) + +class TimeField(Field): + default_error_messages = { + 'invalid': _(u'Enter a valid time.') + } + + def __init__(self, input_formats=None, *args, **kwargs): + super(TimeField, self).__init__(*args, **kwargs) + self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS + + def clean(self, value): + """ + Validates that the input can be converted to a time. Returns a Python + datetime.time object. + """ + super(TimeField, self).clean(value) + if value in EMPTY_VALUES: + return None + if isinstance(value, datetime.time): + return value + for format in self.input_formats: + try: + return datetime.time(*time.strptime(value, format)[3:6]) + except ValueError: + continue + raise ValidationError(self.error_messages['invalid']) + +DEFAULT_DATETIME_INPUT_FORMATS = ( + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' + '%Y-%m-%d', # '2006-10-25' + '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' + '%m/%d/%Y %H:%M', # '10/25/2006 14:30' + '%m/%d/%Y', # '10/25/2006' + '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' + '%m/%d/%y %H:%M', # '10/25/06 14:30' + '%m/%d/%y', # '10/25/06' +) + +class DateTimeField(Field): + widget = DateTimeInput + default_error_messages = { + 'invalid': _(u'Enter a valid date/time.'), + } + + def __init__(self, input_formats=None, *args, **kwargs): + super(DateTimeField, self).__init__(*args, **kwargs) + self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS + + def clean(self, value): + """ + Validates that the input can be converted to a datetime. Returns a + Python datetime.datetime object. + """ + super(DateTimeField, self).clean(value) + if value in EMPTY_VALUES: + return None + if isinstance(value, datetime.datetime): + return value + if isinstance(value, datetime.date): + return datetime.datetime(value.year, value.month, value.day) + if isinstance(value, list): + # Input comes from a SplitDateTimeWidget, for example. So, it's two + # components: date and time. + if len(value) != 2: + raise ValidationError(self.error_messages['invalid']) + value = '%s %s' % tuple(value) + for format in self.input_formats: + try: + return datetime.datetime(*time.strptime(value, format)[:6]) + except ValueError: + continue + raise ValidationError(self.error_messages['invalid']) + +class RegexField(CharField): + def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): + """ + regex can be either a string or a compiled regular expression object. + error_message is an optional error message to use, if + 'Enter a valid value' is too generic for you. + """ + # error_message is just kept for backwards compatibility: + if error_message: + error_messages = kwargs.get('error_messages') or {} + error_messages['invalid'] = error_message + kwargs['error_messages'] = error_messages + super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) + if isinstance(regex, basestring): + regex = re.compile(regex) + self.regex = regex + + def clean(self, value): + """ + Validates that the input matches the regular expression. Returns a + Unicode object. + """ + value = super(RegexField, self).clean(value) + if value == u'': + return value + if not self.regex.search(value): + raise ValidationError(self.error_messages['invalid']) + return value + +email_re = re.compile( + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string + r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain + +class EmailField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid e-mail address.'), + } + + def __init__(self, max_length=None, min_length=None, *args, **kwargs): + RegexField.__init__(self, email_re, max_length, min_length, *args, + **kwargs) + +try: + from django.conf import settings + URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT +except ImportError: + # It's OK if Django settings aren't configured. + URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' + +class UploadedFile(StrAndUnicode): + "A wrapper for files uploaded in a FileField" + def __init__(self, filename, content): + self.filename = filename + self.content = content + + def __unicode__(self): + """ + The unicode representation is the filename, so that the pre-database-insertion + logic can use UploadedFile objects + """ + return self.filename + +class FileField(Field): + widget = FileInput + default_error_messages = { + 'invalid': _(u"No file was submitted. Check the encoding type on the form."), + 'missing': _(u"No file was submitted."), + 'empty': _(u"The submitted file is empty."), + } + + def __init__(self, *args, **kwargs): + super(FileField, self).__init__(*args, **kwargs) + + def clean(self, data, initial=None): + super(FileField, self).clean(initial or data) + if not self.required and data in EMPTY_VALUES: + return None + elif not data and initial: + return initial + try: + f = UploadedFile(data['filename'], data['content']) + except TypeError: + raise ValidationError(self.error_messages['invalid']) + except KeyError: + raise ValidationError(self.error_messages['missing']) + if not f.content: + raise ValidationError(self.error_messages['empty']) + return f + +class ImageField(FileField): + default_error_messages = { + 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), + } + + def clean(self, data, initial=None): + """ + Checks that the file-upload field data contains a valid image (GIF, JPG, + PNG, possibly others -- whatever the Python Imaging Library supports). + """ + f = super(ImageField, self).clean(data, initial) + if f is None: + return None + elif not data and initial: + return initial + from PIL import Image + from cStringIO import StringIO + try: + # load() is the only method that can spot a truncated JPEG, + # but it cannot be called sanely after verify() + trial_image = Image.open(StringIO(f.content)) + trial_image.load() + # verify() is the only method that can spot a corrupt PNG, + # but it must be called immediately after the constructor + trial_image = Image.open(StringIO(f.content)) + trial_image.verify() + except Exception: # Python Imaging Library doesn't recognize it as an image + raise ValidationError(self.error_messages['invalid_image']) + return f + +url_re = re.compile( + r'^https?://' # http:// or https:// + r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain... + r'localhost|' #localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'(?::\d+)?' # optional port + r'(?:/?|/\S+)$', re.IGNORECASE) + +class URLField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid URL.'), + 'invalid_link': _(u'This URL appears to be a broken link.'), + } + + def __init__(self, max_length=None, min_length=None, verify_exists=False, + validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): + super(URLField, self).__init__(url_re, max_length, min_length, *args, + **kwargs) + self.verify_exists = verify_exists + self.user_agent = validator_user_agent + + def clean(self, value): + # If no URL scheme given, assume http:// + if value and '://' not in value: + value = u'http://%s' % value + value = super(URLField, self).clean(value) + if value == u'': + return value + if self.verify_exists: + import urllib2 + from django.conf import settings + headers = { + "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", + "Accept-Language": "en-us,en;q=0.5", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Connection": "close", + "User-Agent": self.user_agent, + } + try: + req = urllib2.Request(value, None, headers) + u = urllib2.urlopen(req) + except ValueError: + raise ValidationError(self.error_messages['invalid']) + except: # urllib2.URLError, httplib.InvalidURL, etc. + raise ValidationError(self.error_messages['invalid_link']) + return value + +class BooleanField(Field): + widget = CheckboxInput + + def clean(self, value): + """Returns a Python boolean object.""" + super(BooleanField, self).clean(value) + # Explicitly check for the string 'False', which is what a hidden field + # will submit for False. Because bool("True") == True, we don't need to + # handle that explicitly. + if value == 'False': + return False + return bool(value) + +class NullBooleanField(BooleanField): + """ + A field whose valid values are None, True and False. Invalid values are + cleaned to None. + """ + widget = NullBooleanSelect + + def clean(self, value): + return {True: True, False: False}.get(value, None) + +class ChoiceField(Field): + widget = Select + default_error_messages = { + 'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'), + } + + def __init__(self, choices=(), required=True, widget=None, label=None, + initial=None, help_text=None, *args, **kwargs): + super(ChoiceField, self).__init__(required, widget, label, initial, + help_text, *args, **kwargs) + self.choices = choices + + def _get_choices(self): + return self._choices + + def _set_choices(self, value): + # Setting choices also sets the choices on the widget. + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + def clean(self, value): + """ + Validates that the input is in self.choices. + """ + value = super(ChoiceField, self).clean(value) + if value in EMPTY_VALUES: + value = u'' + value = smart_unicode(value) + if value == u'': + return value + valid_values = set([smart_unicode(k) for k, v in self.choices]) + if value not in valid_values: + raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) + return value + +class MultipleChoiceField(ChoiceField): + hidden_widget = MultipleHiddenInput + widget = SelectMultiple + default_error_messages = { + 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), + 'invalid_list': _(u'Enter a list of values.'), + } + + def clean(self, value): + """ + Validates that the input is a list or tuple. + """ + if self.required and not value: + raise ValidationError(self.error_messages['required']) + elif not self.required and not value: + return [] + if not isinstance(value, (list, tuple)): + raise ValidationError(self.error_messages['invalid_list']) + new_value = [smart_unicode(val) for val in value] + # Validate that each value in the value list is in self.choices. + valid_values = set([smart_unicode(k) for k, v in self.choices]) + for val in new_value: + if val not in valid_values: + raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) + return new_value + +class ComboField(Field): + """ + A Field whose clean() method calls multiple Field clean() methods. + """ + def __init__(self, fields=(), *args, **kwargs): + super(ComboField, self).__init__(*args, **kwargs) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by ComboField, not by those + # individual fields. + for f in fields: + f.required = False + self.fields = fields + + def clean(self, value): + """ + Validates the given value against all of self.fields, which is a + list of Field instances. + """ + super(ComboField, self).clean(value) + for field in self.fields: + value = field.clean(value) + return value + +class MultiValueField(Field): + """ + A Field that aggregates the logic of multiple Fields. + + Its clean() method takes a "decompressed" list of values, which are then + cleaned into a single value according to self.fields. Each value in + this list is cleaned by the corresponding field -- the first value is + cleaned by the first field, the second value is cleaned by the second + field, etc. Once all fields are cleaned, the list of clean values is + "compressed" into a single value. + + Subclasses should not have to implement clean(). Instead, they must + implement compress(), which takes a list of valid values and returns a + "compressed" version of those values -- a single value. + + You'll probably want to use this with MultiWidget. + """ + default_error_messages = { + 'invalid': _(u'Enter a list of values.'), + } + + def __init__(self, fields=(), *args, **kwargs): + super(MultiValueField, self).__init__(*args, **kwargs) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by MultiValueField, not by those + # individual fields. + for f in fields: + f.required = False + self.fields = fields + + def clean(self, value): + """ + Validates every value in the given list. A value is validated against + the corresponding Field in self.fields. + + For example, if this MultiValueField was instantiated with + fields=(DateField(), TimeField()), clean() would call + DateField.clean(value[0]) and TimeField.clean(value[1]). + """ + cleaned_data = [] + errors = ErrorList() + if not value or isinstance(value, (list, tuple)): + if not value or not [v for v in value if v not in EMPTY_VALUES]: + if self.required: + raise ValidationError(self.error_messages['required']) + else: + return self.compress([]) + else: + raise ValidationError(self.error_messages['invalid']) + for i, field in enumerate(self.fields): + try: + field_value = value[i] + except IndexError: + field_value = None + if self.required and field_value in EMPTY_VALUES: + raise ValidationError(self.error_messages['required']) + try: + cleaned_data.append(field.clean(field_value)) + except ValidationError, e: + # Collect all validation errors in a single list, which we'll + # raise at the end of clean(), rather than raising a single + # exception for the first error we encounter. + errors.extend(e.messages) + if errors: + raise ValidationError(errors) + return self.compress(cleaned_data) + + def compress(self, data_list): + """ + Returns a single value for the given list of values. The values can be + assumed to be valid. + + For example, if this MultiValueField was instantiated with + fields=(DateField(), TimeField()), this might return a datetime + object created by combining the date and time in data_list. + """ + raise NotImplementedError('Subclasses must implement this method.') + +class FilePathField(ChoiceField): + def __init__(self, path, match=None, recursive=False, required=True, + widget=Select, label=None, initial=None, help_text=None, + *args, **kwargs): + self.path, self.match, self.recursive = path, match, recursive + super(FilePathField, self).__init__(choices=(), required=required, + widget=widget, label=label, initial=initial, help_text=help_text, + *args, **kwargs) + self.choices = [] + if self.match is not None: + self.match_re = re.compile(self.match) + if recursive: + for root, dirs, files in os.walk(self.path): + for f in files: + if self.match is None or self.match_re.search(f): + f = os.path.join(root, f) + self.choices.append((f, f.replace(path, "", 1))) + else: + try: + for f in os.listdir(self.path): + full_file = os.path.join(self.path, f) + if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)): + self.choices.append((full_file, f)) + except OSError: + pass + self.widget.choices = self.choices + +class SplitDateTimeField(MultiValueField): + default_error_messages = { + 'invalid_date': _(u'Enter a valid date.'), + 'invalid_time': _(u'Enter a valid time.'), + } + + def __init__(self, *args, **kwargs): + errors = self.default_error_messages.copy() + if 'error_messages' in kwargs: + errors.update(kwargs['error_messages']) + fields = ( + DateField(error_messages={'invalid': errors['invalid_date']}), + TimeField(error_messages={'invalid': errors['invalid_time']}), + ) + super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) + + def compress(self, data_list): + if data_list: + # Raise a validation error if time or date is empty + # (possible if SplitDateTimeField has required=False). + if data_list[0] in EMPTY_VALUES: + raise ValidationError(self.error_messages['invalid_date']) + if data_list[1] in EMPTY_VALUES: + raise ValidationError(self.error_messages['invalid_time']) + return datetime.datetime.combine(*data_list) + return None + +ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') + +class IPAddressField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid IPv4 address.'), + } + + def __init__(self, *args, **kwargs): + super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) diff --git a/deluge/ui/webui/lib/newforms/forms.py b/deluge/ui/webui/lib/newforms_portable/forms.py similarity index 60% rename from deluge/ui/webui/lib/newforms/forms.py rename to deluge/ui/webui/lib/newforms_portable/forms.py index df4af3b54..2c481e47a 100644 --- a/deluge/ui/webui/lib/newforms/forms.py +++ b/deluge/ui/webui/lib/newforms_portable/forms.py @@ -2,14 +2,18 @@ Form classes """ -from utils.datastructures import SortedDict, MultiValueDict -from utils.html import escape -from fields import Field -from widgets import TextInput, Textarea, HiddenInput, MultipleHiddenInput -from util import flatatt, StrAndUnicode, ErrorDict, ErrorList, ValidationError -import copy +from copy import deepcopy -#__all__ = ('BaseForm', 'Form') +from django.utils.datastructures import SortedDict +from django.utils.html import escape +from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode +from django.utils.safestring import mark_safe + +from fields import Field, FileField +from widgets import TextInput, Textarea +from util import flatatt, ErrorDict, ErrorList, ValidationError + +__all__ = ('BaseForm', 'Form') NON_FIELD_ERRORS = '__all__' @@ -18,17 +22,32 @@ def pretty_name(name): name = name[0].upper() + name[1:] return name.replace('_', ' ') -class SortedDictFromList(SortedDict): - "A dictionary that keeps its keys in the order in which they're inserted." - # This is different than django.utils.datastructures.SortedDict, because - # this takes a list/tuple as the argument to __init__(). - def __init__(self, data=None): - if data is None: data = [] - self.keyOrder = [d[0] for d in data] - dict.__init__(self, dict(data)) +def get_declared_fields(bases, attrs, with_base_fields=True): + """ + Create a list of form field instances from the passed in 'attrs', plus any + similar fields on the base classes (in 'bases'). This is used by both the + Form and ModelForm metclasses. - def copy(self): - return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()]) + If 'with_base_fields' is True, all fields from the bases are used. + Otherwise, only fields in the 'declared_fields' attribute on the bases are + used. The distinction is useful in ModelForm subclassing. + """ + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + if with_base_fields: + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + else: + for base in bases[::-1]: + if hasattr(base, 'declared_fields'): + fields = base.declared_fields.items() + fields + + return SortedDict(fields) class DeclarativeFieldsMetaclass(type): """ @@ -36,17 +55,7 @@ class DeclarativeFieldsMetaclass(type): 'base_fields', taking into account parent class 'base_fields' as well. """ def __new__(cls, name, bases, attrs): - fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] - fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) - - # If this class is subclassing another Form, add that Form's fields. - # Note that we loop over the bases in *reverse*. This is necessary in - # order to preserve the correct order of fields. - for base in bases[::-1]: - if hasattr(base, 'base_fields'): - fields = base.base_fields.items() + fields - - attrs['base_fields'] = SortedDictFromList(fields) + attrs['base_fields'] = get_declared_fields(bases, attrs) return type.__new__(cls, name, bases, attrs) class BaseForm(StrAndUnicode): @@ -54,20 +63,24 @@ class BaseForm(StrAndUnicode): # class is different than Form. See the comments by the Form class for more # information. Any improvements to the form API should be made to *this* # class, not to the Form class. - def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None): - self.is_bound = data is not None + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=':'): + self.is_bound = data is not None or files is not None self.data = data or {} + self.files = files or {} self.auto_id = auto_id self.prefix = prefix self.initial = initial or {} - self.__errors = None # Stores the errors after clean() has been called. + self.error_class = error_class + self.label_suffix = label_suffix + self._errors = None # Stores the errors after clean() has been called. # The base_fields class attribute is the *class-wide* definition of # fields. Because a particular *instance* of the class might want to # alter self.fields, we create self.fields here by copying base_fields. # Instances should always modify self.fields; they should not modify # self.base_fields. - self.fields = self.base_fields.copy() + self.fields = deepcopy(self.base_fields) def __unicode__(self): return self.as_table() @@ -84,12 +97,12 @@ class BaseForm(StrAndUnicode): raise KeyError('Key %r not found in Form' % name) return BoundField(self, field, name) - def _errors(self): - "Returns an ErrorDict for self.data" - if self.__errors is None: + def _get_errors(self): + "Returns an ErrorDict for the data provided for the form" + if self._errors is None: self.full_clean() - return self.__errors - errors = property(_errors) + return self._errors + errors = property(_get_errors) def is_valid(self): """ @@ -113,31 +126,43 @@ class BaseForm(StrAndUnicode): output, hidden_fields = [], [] for name, field in self.fields.items(): bf = BoundField(self, field, name) - bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable. + bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: - top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors]) + top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) hidden_fields.append(unicode(bf)) else: if errors_on_separate_row and bf_errors: - output.append(error_row % bf_errors) - label = bf.label and bf.label_tag(escape(bf.label + ':')) or '' + output.append(error_row % force_unicode(bf_errors)) + if bf.label: + label = escape(force_unicode(bf.label)) + # Only add the suffix if the label does not end in + # punctuation. + if self.label_suffix: + if label[-1] not in ':?.!': + label += self.label_suffix + label = bf.label_tag(label) or '' + else: + label = '' if field.help_text: - help_text = help_text_html % field.help_text + help_text = help_text_html % force_unicode(field.help_text) else: help_text = u'' - output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text}) + output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) if top_errors: - output.insert(0, error_row % top_errors) + output.insert(0, error_row % force_unicode(top_errors)) if hidden_fields: # Insert any hidden fields in the last row. str_hidden = u''.join(hidden_fields) if output: last_row = output[-1] - # Chop off the trailing row_ender (e.g. '') and insert the hidden fields. + # Chop off the trailing row_ender (e.g. '') and + # insert the hidden fields. output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender - else: # If there aren't any rows in the output, just append the hidden fields. + else: + # If there aren't any rows in the output, just append the + # hidden fields. output.append(str_hidden) - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) def as_table(self): "Returns this form rendered as HTML s -- excluding the
          ." @@ -149,7 +174,7 @@ class BaseForm(StrAndUnicode): def as_p(self): "Returns this form rendered as HTML

          s." - return self._html_output(u'

          %(label)s %(field)s%(help_text)s

          ', u'

          %s

          ', '

          ', u' %s', True) + return self._html_output(u'

          %(label)s %(field)s%(help_text)s

          ', u'%s', '

          ', u' %s', True) def non_field_errors(self): """ @@ -157,37 +182,42 @@ class BaseForm(StrAndUnicode): field -- i.e., from Form.clean(). Returns an empty ErrorList if there are none. """ - return self.errors.get(NON_FIELD_ERRORS, ErrorList()) + return self.errors.get(NON_FIELD_ERRORS, self.error_class()) def full_clean(self): """ - Cleans all of self.data and populates self.__errors and self.clean_data. + Cleans all of self.data and populates self._errors and + self.cleaned_data. """ - errors = ErrorDict() + self._errors = ErrorDict() if not self.is_bound: # Stop further processing. - self.__errors = errors return - self.clean_data = {} + self.cleaned_data = {} for name, field in self.fields.items(): - # value_from_datadict() gets the data from the dictionary. + # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. - value = field.widget.value_from_datadict(self.data, self.add_prefix(name)) + value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: - value = field.clean(value) - self.clean_data[name] = value + if isinstance(field, FileField): + initial = self.initial.get(name, field.initial) + value = field.clean(value, initial) + else: + value = field.clean(value) + self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() - self.clean_data[name] = value + self.cleaned_data[name] = value except ValidationError, e: - errors[name] = e.messages + self._errors[name] = e.messages + if name in self.cleaned_data: + del self.cleaned_data[name] try: - self.clean_data = self.clean() + self.cleaned_data = self.clean() except ValidationError, e: - errors[NON_FIELD_ERRORS] = e.messages - if errors: - delattr(self, 'clean_data') - self.__errors = errors + self._errors[NON_FIELD_ERRORS] = e.messages + if self._errors: + delattr(self, 'cleaned_data') def clean(self): """ @@ -196,7 +226,17 @@ class BaseForm(StrAndUnicode): not be associated with a particular field; it will have a special-case association with the field named '__all__'. """ - return self.clean_data + return self.cleaned_data + + def is_multipart(self): + """ + Returns True if the form needs to be multipart-encrypted, i.e. it has + FileInput. Otherwise, False. + """ + for field in self.fields.values(): + if field.widget.needs_multipart_form: + return True + return False class Form(BaseForm): "A collection of Fields, plus their associated data." @@ -221,32 +261,33 @@ class BoundField(StrAndUnicode): self.help_text = field.help_text or '' def __unicode__(self): - "Renders this field as an HTML widget." - # Use the 'widget' attribute on the field to determine which type - # of HTML widget to use. - value = self.as_widget(self.field.widget) - if not isinstance(value, basestring): - # Some Widget render() methods -- notably RadioSelect -- return a - # "special" object rather than a string. Call the __str__() on that - # object to get its rendered value. - value = value.__str__() - return value + """Renders this field as an HTML widget.""" + return self.as_widget() def _errors(self): """ Returns an ErrorList for this field. Returns an empty ErrorList if there are none. """ - return self.form.errors.get(self.name, ErrorList()) + return self.form.errors.get(self.name, self.form.error_class()) errors = property(_errors) - def as_widget(self, widget, attrs=None): + def as_widget(self, widget=None, attrs=None): + """ + Renders the field by rendering the passed widget, adding any HTML + attributes passed as attrs. If no widget is specified, then the + field's default widget will be used. + """ + if not widget: + widget = self.field.widget attrs = attrs or {} auto_id = self.auto_id - if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'): + if auto_id and 'id' not in attrs and 'id' not in widget.attrs: attrs['id'] = auto_id if not self.form.is_bound: data = self.form.initial.get(self.name, self.field.initial) + if callable(data): + data = data() else: data = self.data return widget.render(self.html_name, data, attrs=attrs) @@ -271,7 +312,7 @@ class BoundField(StrAndUnicode): """ Returns the data for this BoundField, or None if it wasn't given. """ - return self.field.widget.value_from_datadict(self.form.data, self.html_name) + return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) data = property(_data) def label_tag(self, contents=None, attrs=None): @@ -288,7 +329,7 @@ class BoundField(StrAndUnicode): if id_: attrs = attrs and flatatt(attrs) or '' contents = '' % (widget.id_for_label(id_), attrs, contents) - return contents + return mark_safe(contents) def _is_hidden(self): "Returns True if this BoundField's widget is hidden." @@ -301,8 +342,8 @@ class BoundField(StrAndUnicode): associated Form has specified auto_id. Returns an empty string otherwise. """ auto_id = self.form.auto_id - if auto_id and '%s' in str(auto_id): - return str(auto_id) % self.html_name + if auto_id and '%s' in smart_unicode(auto_id): + return smart_unicode(auto_id) % self.html_name elif auto_id: return self.html_name return '' diff --git a/deluge/ui/webui/lib/newforms_portable/models.py b/deluge/ui/webui/lib/newforms_portable/models.py new file mode 100644 index 000000000..0590839b2 --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/models.py @@ -0,0 +1,398 @@ +""" +Helper functions for creating Form classes from Django models +and database field objects. +""" + +from warnings import warn + +from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import smart_unicode +from django.utils.datastructures import SortedDict +from django.core.exceptions import ImproperlyConfigured + +from util import ValidationError, ErrorList +from forms import BaseForm, get_declared_fields +from fields import Field, ChoiceField, EMPTY_VALUES +from widgets import Select, SelectMultiple, MultipleHiddenInput + +__all__ = ( + 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', + 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', + 'ModelChoiceField', 'ModelMultipleChoiceField' +) + +def save_instance(form, instance, fields=None, fail_message='saved', + commit=True): + """ + Saves bound Form ``form``'s cleaned_data into model instance ``instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + """ + from django.db import models + opts = instance.__class__._meta + if form.errors: + raise ValueError("The %s could not be %s because the data didn't" + " validate." % (opts.object_name, fail_message)) + cleaned_data = form.cleaned_data + for f in opts.fields: + if not f.editable or isinstance(f, models.AutoField) \ + or not f.name in cleaned_data: + continue + if fields and f.name not in fields: + continue + f.save_form_data(instance, cleaned_data[f.name]) + # Wrap up the saving of m2m data as a function. + def save_m2m(): + opts = instance.__class__._meta + cleaned_data = form.cleaned_data + for f in opts.many_to_many: + if fields and f.name not in fields: + continue + if f.name in cleaned_data: + f.save_form_data(instance, cleaned_data[f.name]) + if commit: + # If we are committing, save the instance and the m2m data immediately. + instance.save() + save_m2m() + else: + # We're not committing. Add a method to the form to allow deferred + # saving of m2m data. + form.save_m2m = save_m2m + return instance + +def make_model_save(model, fields, fail_message): + """Returns the save() method for a Form.""" + def save(self, commit=True): + return save_instance(self, model(), fields, fail_message, commit) + return save + +def make_instance_save(instance, fields, fail_message): + """Returns the save() method for a Form.""" + def save(self, commit=True): + return save_instance(self, instance, fields, fail_message, commit) + return save + +def form_for_model(model, form=BaseForm, fields=None, + formfield_callback=lambda f: f.formfield()): + """ + Returns a Form class for the given Django model class. + + Provide ``form`` if you want to use a custom BaseForm subclass. + + Provide ``formfield_callback`` if you want to define different logic for + determining the formfield for a given database field. It's a callable that + takes a database Field instance and returns a form Field instance. + """ + warn("form_for_model is deprecated. Use ModelForm instead.", + PendingDeprecationWarning, stacklevel=3) + opts = model._meta + field_list = [] + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + formfield = formfield_callback(f) + if formfield: + field_list.append((f.name, formfield)) + base_fields = SortedDict(field_list) + return type(opts.object_name + 'Form', (form,), + {'base_fields': base_fields, '_model': model, + 'save': make_model_save(model, fields, 'created')}) + +def form_for_instance(instance, form=BaseForm, fields=None, + formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): + """ + Returns a Form class for the given Django model instance. + + Provide ``form`` if you want to use a custom BaseForm subclass. + + Provide ``formfield_callback`` if you want to define different logic for + determining the formfield for a given database field. It's a callable that + takes a database Field instance, plus **kwargs, and returns a form Field + instance with the given kwargs (i.e. 'initial'). + """ + warn("form_for_instance is deprecated. Use ModelForm instead.", + PendingDeprecationWarning, stacklevel=3) + model = instance.__class__ + opts = model._meta + field_list = [] + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + current_value = f.value_from_object(instance) + formfield = formfield_callback(f, initial=current_value) + if formfield: + field_list.append((f.name, formfield)) + base_fields = SortedDict(field_list) + return type(opts.object_name + 'InstanceForm', (form,), + {'base_fields': base_fields, '_model': model, + 'save': make_instance_save(instance, fields, 'changed')}) + +def form_for_fields(field_list): + """ + Returns a Form class for the given list of Django database field instances. + """ + fields = SortedDict([(f.name, f.formfield()) + for f in field_list if f.editable]) + return type('FormForFields', (BaseForm,), {'base_fields': fields}) + + +# ModelForms ################################################################# + +def model_to_dict(instance, fields=None, exclude=None): + """ + Returns a dict containing the data in ``instance`` suitable for passing as + a Form's ``initial`` keyword argument. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned dict. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned dict, even if they are listed in + the ``fields`` argument. + """ + # avoid a circular import + from django.db.models.fields.related import ManyToManyField + opts = instance._meta + data = {} + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + if isinstance(f, ManyToManyField): + # If the object doesn't have a primry key yet, just use an empty + # list for its m2m fields. Calling f.value_from_object will raise + # an exception. + if instance.pk is None: + data[f.name] = [] + else: + # MultipleChoiceWidget needs a list of pks, not object instances. + data[f.name] = [obj.pk for obj in f.value_from_object(instance)] + else: + data[f.name] = f.value_from_object(instance) + return data + +def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): + """ + Returns a ``SortedDict`` containing form fields for the given model. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned fields. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned fields, even if they are listed + in the ``fields`` argument. + """ + # TODO: if fields is provided, it would be nice to return fields in that order + field_list = [] + opts = model._meta + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + formfield = formfield_callback(f) + if formfield: + field_list.append((f.name, formfield)) + return SortedDict(field_list) + +class ModelFormOptions(object): + def __init__(self, options=None): + self.model = getattr(options, 'model', None) + self.fields = getattr(options, 'fields', None) + self.exclude = getattr(options, 'exclude', None) + + +class ModelFormMetaclass(type): + def __new__(cls, name, bases, attrs, + formfield_callback=lambda f: f.formfield()): + try: + parents = [b for b in bases if issubclass(b, ModelForm)] + except NameError: + # We are defining ModelForm itself. + parents = None + if not parents: + return super(ModelFormMetaclass, cls).__new__(cls, name, bases, + attrs) + + new_class = type.__new__(cls, name, bases, attrs) + declared_fields = get_declared_fields(bases, attrs, False) + opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) + if opts.model: + # If a model is defined, extract form fields from it. + fields = fields_for_model(opts.model, opts.fields, + opts.exclude, formfield_callback) + # Override default model fields with any custom declared ones + # (plus, include all the other declared fields). + fields.update(declared_fields) + else: + fields = declared_fields + new_class.declared_fields = declared_fields + new_class.base_fields = fields + return new_class + +class BaseModelForm(BaseForm): + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=':', + instance=None): + opts = self._meta + if instance is None: + # if we didn't get an instance, instantiate a new one + self.instance = opts.model() + object_data = {} + else: + self.instance = instance + object_data = model_to_dict(instance, opts.fields, opts.exclude) + # if initial was provided, it should override the values from instance + if initial is not None: + object_data.update(initial) + BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix) + + def save(self, commit=True): + """ + Saves this ``form``'s cleaned_data into model instance + ``self.instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + """ + if self.instance.pk is None: + fail_message = 'created' + else: + fail_message = 'changed' + return save_instance(self, self.instance, self._meta.fields, fail_message, commit) + +class ModelForm(BaseModelForm): + __metaclass__ = ModelFormMetaclass + + +# Fields ##################################################################### + +class ModelChoiceIterator(object): + def __init__(self, field): + self.field = field + self.queryset = field.queryset + + def __iter__(self): + if self.field.empty_label is not None: + yield (u"", self.field.empty_label) + for obj in self.queryset: + yield (obj.pk, self.field.label_from_instance(obj)) + # Clear the QuerySet cache if required. + if not self.field.cache_choices: + self.queryset._result_cache = None + +class ModelChoiceField(ChoiceField): + """A ChoiceField whose choices are a model QuerySet.""" + # This class is a subclass of ChoiceField for purity, but it doesn't + # actually use any of ChoiceField's implementation. + default_error_messages = { + 'invalid_choice': _(u'Select a valid choice. That choice is not one of' + u' the available choices.'), + } + + def __init__(self, queryset, empty_label=u"---------", cache_choices=False, + required=True, widget=Select, label=None, initial=None, + help_text=None, *args, **kwargs): + self.empty_label = empty_label + self.cache_choices = cache_choices + + # Call Field instead of ChoiceField __init__() because we don't need + # ChoiceField.__init__(). + Field.__init__(self, required, widget, label, initial, help_text, + *args, **kwargs) + self.queryset = queryset + + def _get_queryset(self): + return self._queryset + + def _set_queryset(self, queryset): + self._queryset = queryset + self.widget.choices = self.choices + + queryset = property(_get_queryset, _set_queryset) + + # this method will be used to create object labels by the QuerySetIterator. + # Override it to customize the label. + def label_from_instance(self, obj): + """ + This method is used to convert objects into strings; it's used to + generate the labels for the choices presented by this object. Subclasses + can override this method to customize the display of the choices. + """ + return smart_unicode(obj) + + def _get_choices(self): + # If self._choices is set, then somebody must have manually set + # the property self.choices. In this case, just return self._choices. + if hasattr(self, '_choices'): + return self._choices + + # Otherwise, execute the QuerySet in self.queryset to determine the + # choices dynamically. Return a fresh QuerySetIterator that has not been + # consumed. Note that we're instantiating a new QuerySetIterator *each* + # time _get_choices() is called (and, thus, each time self.choices is + # accessed) so that we can ensure the QuerySet has not been consumed. This + # construct might look complicated but it allows for lazy evaluation of + # the queryset. + return ModelChoiceIterator(self) + + def _set_choices(self, value): + # This method is copied from ChoiceField._set_choices(). It's necessary + # because property() doesn't allow a subclass to overwrite only + # _get_choices without implementing _set_choices. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + def clean(self, value): + Field.clean(self, value) + if value in EMPTY_VALUES: + return None + try: + value = self.queryset.get(pk=value) + except self.queryset.model.DoesNotExist: + raise ValidationError(self.error_messages['invalid_choice']) + return value + +class ModelMultipleChoiceField(ModelChoiceField): + """A MultipleChoiceField whose choices are a model QuerySet.""" + hidden_widget = MultipleHiddenInput + default_error_messages = { + 'list': _(u'Enter a list of values.'), + 'invalid_choice': _(u'Select a valid choice. %s is not one of the' + u' available choices.'), + } + + def __init__(self, queryset, cache_choices=False, required=True, + widget=SelectMultiple, label=None, initial=None, + help_text=None, *args, **kwargs): + super(ModelMultipleChoiceField, self).__init__(queryset, None, + cache_choices, required, widget, label, initial, help_text, + *args, **kwargs) + + def clean(self, value): + if self.required and not value: + raise ValidationError(self.error_messages['required']) + elif not self.required and not value: + return [] + if not isinstance(value, (list, tuple)): + raise ValidationError(self.error_messages['list']) + final_values = [] + for val in value: + try: + obj = self.queryset.get(pk=val) + except self.queryset.model.DoesNotExist: + raise ValidationError(self.error_messages['invalid_choice'] % val) + else: + final_values.append(obj) + return final_values diff --git a/deluge/ui/webui/lib/newforms_portable/util.py b/deluge/ui/webui/lib/newforms_portable/util.py new file mode 100644 index 000000000..b3edf41ad --- /dev/null +++ b/deluge/ui/webui/lib/newforms_portable/util.py @@ -0,0 +1,69 @@ +from django.utils.html import escape +from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode +from django.utils.functional import Promise +from django.utils.safestring import mark_safe + +def flatatt(attrs): + """ + Convert a dictionary of attributes to a single string. + The returned string will contain a leading space followed by key="value", + XML-style pairs. It is assumed that the keys do not need to be XML-escaped. + If the passed dictionary is empty, then return an empty string. + """ + return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()]) + +class ErrorDict(dict, StrAndUnicode): + """ + A collection of errors that knows how to display itself in various formats. + + The dictionary keys are the field names, and the values are the errors. + """ + def __unicode__(self): + return self.as_ul() + + def as_ul(self): + if not self: return u'' + return mark_safe(u'
            %s
          ' + % ''.join([u'
        • %s%s
        • ' % (k, force_unicode(v)) + for k, v in self.items()])) + + def as_text(self): + return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) + +class ErrorList(list, StrAndUnicode): + """ + A collection of errors that knows how to display itself in various formats. + """ + def __unicode__(self): + return self.as_ul() + + def as_ul(self): + if not self: return u'' + return mark_safe(u'
            %s
          ' + % ''.join([u'
        • %s
        • ' % force_unicode(e) for e in self])) + + def as_text(self): + if not self: return u'' + return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) + + def __repr__(self): + return repr([force_unicode(e) for e in self]) + +class ValidationError(Exception): + def __init__(self, message): + """ + ValidationError can be passed any object that can be printed (usually + a string) or a list of objects. + """ + if isinstance(message, list): + self.messages = ErrorList([smart_unicode(msg) for msg in message]) + else: + message = smart_unicode(message) + self.messages = ErrorList([message]) + + def __str__(self): + # This is needed because, without a __str__(), printing an exception + # instance would result in this: + # AttributeError: ValidationError instance has no attribute 'args' + # See http://www.python.org/doc/current/tut/node10.html#handling + return repr(self.messages) diff --git a/deluge/ui/webui/lib/newforms/widgets.py b/deluge/ui/webui/lib/newforms_portable/widgets.py similarity index 56% rename from deluge/ui/webui/lib/newforms/widgets.py rename to deluge/ui/webui/lib/newforms_portable/widgets.py index f58c137d0..20a7cab46 100644 --- a/deluge/ui/webui/lib/newforms/widgets.py +++ b/deluge/ui/webui/lib/newforms_portable/widgets.py @@ -2,29 +2,44 @@ HTML Widget classes """ -__all__ = ( - 'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput', - 'FileInput', 'Textarea', 'CheckboxInput', - 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple', - 'MultiWidget', 'SplitDateTimeWidget', -) +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback -from util import flatatt, StrAndUnicode, smart_unicode -from utils.datastructures import MultiValueDict -from utils.html import escape -from gettext import gettext +import copy from itertools import chain -try: - set # Only available in Python 2.4+ -except NameError: - from sets import Set as set # Python 2.3 fallback +from django.utils.datastructures import MultiValueDict +from django.utils.html import escape, conditional_escape +from django.utils.translation import ugettext +from django.utils.encoding import StrAndUnicode, force_unicode +from django.utils.safestring import mark_safe +from util import flatatt + +__all__ = ( + 'Widget', 'TextInput', 'PasswordInput', + 'HiddenInput', 'MultipleHiddenInput', + 'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput', + 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', + 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget', +) class Widget(object): is_hidden = False # Determines whether this corresponds to an . + needs_multipart_form = False # Determines does this widget need multipart-encrypted form def __init__(self, attrs=None): - self.attrs = attrs or {} + if attrs is not None: + self.attrs = attrs.copy() + else: + self.attrs = {} + + def __deepcopy__(self, memo): + obj = copy.copy(self) + obj.attrs = self.attrs.copy() + memo[id(self)] = obj + return obj def render(self, name, value, attrs=None): """ @@ -42,7 +57,7 @@ class Widget(object): attrs.update(extra_attrs) return attrs - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): """ Given a dictionary of data and this widget's name, returns the value of this widget. Returns None if it's not provided. @@ -72,8 +87,10 @@ class Input(Widget): def render(self, name, value, attrs=None): if value is None: value = '' final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) - if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty. - return u'' % flatatt(final_attrs) + if value != '': + # Only add the 'value' attribute if a value is non-empty. + final_attrs['value'] = force_unicode(value) + return mark_safe(u'' % flatatt(final_attrs)) class TextInput(Input): input_type = 'text' @@ -82,7 +99,7 @@ class PasswordInput(Input): input_type = 'password' def __init__(self, attrs=None, render_value=True): - self.attrs = attrs or {} + super(PasswordInput, self).__init__(attrs) self.render_value = render_value def render(self, name, value, attrs=None): @@ -99,35 +116,68 @@ class MultipleHiddenInput(HiddenInput): of values. """ def __init__(self, attrs=None, choices=()): + super(MultipleHiddenInput, self).__init__(attrs) # choices can be any iterable - self.attrs = attrs or {} self.choices = choices def render(self, name, value, attrs=None, choices=()): if value is None: value = [] final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) - return u'\n'.join([(u'' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value]) + return mark_safe(u'\n'.join([(u'' % + flatatt(dict(value=force_unicode(v), **final_attrs))) + for v in value])) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): return data.getlist(name) return data.get(name, None) class FileInput(Input): input_type = 'file' + needs_multipart_form = True + + def render(self, name, value, attrs=None): + return super(FileInput, self).render(name, None, attrs=attrs) + + def value_from_datadict(self, data, files, name): + "File widgets take data from FILES, not POST" + return files.get(name, None) class Textarea(Widget): + def __init__(self, attrs=None): + # The 'rows' and 'cols' attributes are required for HTML correctness. + self.attrs = {'cols': '40', 'rows': '10'} + if attrs: + self.attrs.update(attrs) + def render(self, name, value, attrs=None): if value is None: value = '' - value = smart_unicode(value) + value = force_unicode(value) final_attrs = self.build_attrs(attrs, name=name) - return u'%s' % (flatatt(final_attrs), escape(value)) + return mark_safe(u'%s' % (flatatt(final_attrs), + conditional_escape(force_unicode(value)))) + +class DateTimeInput(Input): + input_type = 'text' + format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' + + def __init__(self, attrs=None, format=None): + super(DateTimeInput, self).__init__(attrs) + if format: + self.format = format + + def render(self, name, value, attrs=None): + if value is None: + value = '' + elif hasattr(value, 'strftime'): + value = value.strftime(self.format) + return super(DateTimeInput, self).render(name, value, attrs) class CheckboxInput(Widget): def __init__(self, attrs=None, check_test=bool): + super(CheckboxInput, self).__init__(attrs) # check_test is a callable that takes a value and returns True # if the checkbox should be checked for that value. - self.attrs = attrs or {} self.check_test = check_test def render(self, name, value, attrs=None): @@ -139,12 +189,20 @@ class CheckboxInput(Widget): if result: final_attrs['checked'] = 'checked' if value not in ('', True, False, None): - final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty. - return u'' % flatatt(final_attrs) + # Only add the 'value' attribute if a value is non-empty. + final_attrs['value'] = force_unicode(value) + return mark_safe(u'' % flatatt(final_attrs)) + + def value_from_datadict(self, data, files, name): + if name not in data: + # A missing value means False because HTML form submission does not + # send results for unselected checkboxes. + return False + return super(CheckboxInput, self).value_from_datadict(data, files, name) class Select(Widget): def __init__(self, attrs=None, choices=()): - self.attrs = attrs or {} + super(Select, self).__init__(attrs) # choices can be any iterable, but we may need to render this widget # multiple times. Thus, collapse it into a list so it can be consumed # more than once. @@ -154,20 +212,23 @@ class Select(Widget): if value is None: value = '' final_attrs = self.build_attrs(attrs, name=name) output = [u'' % flatatt(final_attrs)] - str_value = smart_unicode(value) # Normalize to string. + # Normalize to string. + str_value = force_unicode(value) for option_value, option_label in chain(self.choices, choices): - option_value = smart_unicode(option_value) + option_value = force_unicode(option_value) selected_html = (option_value == str_value) and u' selected="selected"' or '' - output.append(u'' % (escape(option_value), selected_html, escape(smart_unicode(option_label)))) + output.append(u'' % ( + escape(option_value), selected_html, + conditional_escape(force_unicode(option_label)))) output.append(u'') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) class NullBooleanSelect(Select): """ A Select Widget intended to be used with NullBooleanField. """ def __init__(self, attrs=None): - choices = ((u'1', gettext('Unknown')), (u'2', gettext('Yes')), (u'3', gettext('No'))) + choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No'))) super(NullBooleanSelect, self).__init__(attrs, choices) def render(self, name, value, attrs=None, choices=()): @@ -177,58 +238,68 @@ class NullBooleanSelect(Select): value = u'1' return super(NullBooleanSelect, self).render(name, value, attrs, choices) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): value = data.get(name, None) return {u'2': True, u'3': False, True: True, False: False}.get(value, None) class SelectMultiple(Widget): def __init__(self, attrs=None, choices=()): + super(SelectMultiple, self).__init__(attrs) # choices can be any iterable - self.attrs = attrs or {} self.choices = choices def render(self, name, value, attrs=None, choices=()): if value is None: value = [] final_attrs = self.build_attrs(attrs, name=name) output = [u'') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): return data.getlist(name) return data.get(name, None) class RadioInput(StrAndUnicode): - "An object used by RadioFieldRenderer that represents a single ." + """ + An object used by RadioFieldRenderer that represents a single + . + """ + def __init__(self, name, value, attrs, choice, index): self.name, self.value = name, value self.attrs = attrs - self.choice_value = smart_unicode(choice[0]) - self.choice_label = smart_unicode(choice[1]) + self.choice_value = force_unicode(choice[0]) + self.choice_label = force_unicode(choice[1]) self.index = index def __unicode__(self): - return u'' % (self.tag(), self.choice_label) + return mark_safe(u'' % (self.tag(), + conditional_escape(force_unicode(self.choice_label)))) def is_checked(self): return self.value == self.choice_value def tag(self): - if self.attrs.has_key('id'): + if 'id' in self.attrs: self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' - return u'' % flatatt(final_attrs) + return mark_safe(u'' % flatatt(final_attrs)) class RadioFieldRenderer(StrAndUnicode): - "An object used by RadioSelect to enable customization of radio widgets." + """ + An object used by RadioSelect to enable customization of radio widgets. + """ + def __init__(self, name, value, attrs, choices): self.name, self.value, self.attrs = name, value, attrs self.choices = choices @@ -242,16 +313,33 @@ class RadioFieldRenderer(StrAndUnicode): return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx) def __unicode__(self): - "Outputs a